Skip to content

Commit 25b795f

Browse files
authored
Release/1.0.1 (#370)
1 parent 8df6745 commit 25b795f

File tree

87 files changed

+902
-210
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

87 files changed

+902
-210
lines changed

backend/app/src/main/resources/application.yml

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@ springdoc:
3838
swagger-ui:
3939
path: /swagger-ui.html
4040
operations-sorter: method
41-
packages-to-scan: band.effective.office.backend.app.controller,
42-
band.effective.office.backend.feature.authorization.controller,
43-
band.effective.office.backend.feature.booking.core.controller,
44-
band.effective.office.backend.feature.workspace.core.controller
41+
packages-to-scan:
42+
- band.effective.office.backend.app.controller
43+
- band.effective.office.backend.feature.authorization.controller
44+
- band.effective.office.backend.feature.booking.core.controller
45+
- band.effective.office.backend.feature.workspace.core.controller
46+
- band.effective.office.backend.feature.notifications.controller
4547

4648
management:
4749
endpoints:
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
-- Creates the table for storing device information
2+
CREATE TABLE devices
3+
(
4+
-- Primary key
5+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
6+
-- Unique Android device ID
7+
device_id VARCHAR(255) NOT NULL UNIQUE,
8+
-- Tag for device identification (e.g., meeting room name)
9+
tag VARCHAR(255) NOT NULL,
10+
-- Creation timestamp of the record
11+
created_at TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT CURRENT_TIMESTAMP
12+
);
13+
14+
-- Index for faster lookup by device_id
15+
CREATE INDEX idx_devices_device_id ON devices (device_id);
16+
17+
-- Index for faster lookup by tag
18+
CREATE INDEX idx_devices_tag ON devices (tag);
19+
20+
-- Comments for table and columns
21+
COMMENT ON TABLE devices IS 'Table for storing information about devices';
22+
COMMENT ON COLUMN devices.device_id IS 'Unique Android device ID';
23+
COMMENT ON COLUMN devices.tag IS 'Tag for device identification';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package band.effective.office.backend.feature.notifications.config
2+
3+
import org.springframework.boot.autoconfigure.domain.EntityScan
4+
import org.springframework.context.annotation.Configuration
5+
import org.springframework.data.jpa.repository.config.EnableJpaRepositories
6+
import org.springframework.transaction.annotation.EnableTransactionManagement
7+
8+
/**
9+
* Configuration for the notifications repository.
10+
*/
11+
@Configuration
12+
@EnableTransactionManagement
13+
@EnableJpaRepositories(basePackages = ["band.effective.office.backend.feature.notifications.repository"])
14+
@EntityScan(basePackages = ["band.effective.office.backend.feature.notifications.repository.entity"])
15+
class NotificationsRepositoryConfig
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
package band.effective.office.backend.feature.notifications.controller
2+
3+
import band.effective.office.backend.core.data.ErrorDto
4+
import band.effective.office.backend.feature.notifications.dto.DeviceDto
5+
import band.effective.office.backend.feature.notifications.dto.KioskMessageDto
6+
import band.effective.office.backend.feature.notifications.dto.KioskToggleRequest
7+
import band.effective.office.backend.feature.notifications.service.DeviceService
8+
import band.effective.office.backend.feature.notifications.service.INotificationSender
9+
import io.swagger.v3.oas.annotations.Operation
10+
import io.swagger.v3.oas.annotations.media.Content
11+
import io.swagger.v3.oas.annotations.media.Schema
12+
import io.swagger.v3.oas.annotations.responses.ApiResponse
13+
import io.swagger.v3.oas.annotations.security.SecurityRequirement
14+
import io.swagger.v3.oas.annotations.tags.Tag
15+
import jakarta.validation.Valid
16+
import org.springframework.http.HttpStatus
17+
import org.springframework.http.ResponseEntity
18+
import org.springframework.web.bind.annotation.*
19+
20+
/**
21+
* REST controller for managing kiosk mode.
22+
*/
23+
@RestController
24+
@RequestMapping("/api/v1/kiosk")
25+
@Tag(name = "Kiosk", description = "API for managing kiosk mode")
26+
class KioskController(
27+
private val notificationSender: INotificationSender,
28+
private val deviceService: DeviceService
29+
) {
30+
companion object {
31+
private const val KIOSK_TOPIC = "kiosk-commands"
32+
private const val MESSAGE_TYPE = "KIOSK_TOGGLE"
33+
}
34+
35+
/**
36+
* Enables kiosk mode for a specific device.
37+
*/
38+
@PostMapping("/device/enable")
39+
@Operation(
40+
summary = "Enable kiosk mode for specific device",
41+
description = "Enables kiosk mode for a specific device by deviceId",
42+
security = [SecurityRequirement(name = "bearerAuth")]
43+
)
44+
@ApiResponse(responseCode = "200", description = "Command sent successfully")
45+
@ApiResponse(
46+
responseCode = "404",
47+
description = "Device not found",
48+
content = [Content(
49+
mediaType = "application/json",
50+
schema = Schema(implementation = ErrorDto::class)
51+
)]
52+
)
53+
fun enableKioskForDevice(@Valid @RequestBody request: KioskToggleRequest): ResponseEntity<Any> {
54+
return toggleKioskForDevice(request.deviceId, true)
55+
}
56+
57+
/**
58+
* Disables kiosk mode for a specific device.
59+
*/
60+
@PostMapping("/device/disable")
61+
@Operation(
62+
summary = "Disable kiosk mode for specific device",
63+
description = "Disables kiosk mode for a specific device by deviceId",
64+
security = [SecurityRequirement(name = "bearerAuth")]
65+
)
66+
@ApiResponse(responseCode = "200", description = "Command sent successfully")
67+
@ApiResponse(
68+
responseCode = "404",
69+
description = "Device not found",
70+
content = [Content(
71+
mediaType = "application/json",
72+
schema = Schema(implementation = ErrorDto::class)
73+
)]
74+
)
75+
fun disableKioskForDevice(@Valid @RequestBody request: KioskToggleRequest): ResponseEntity<Any> {
76+
return toggleKioskForDevice(request.deviceId, false)
77+
}
78+
79+
/**
80+
* Enables kiosk mode for all registered devices.
81+
*/
82+
@PostMapping("/all/enable")
83+
@Operation(
84+
summary = "Enable kiosk mode for all devices",
85+
description = "Enables kiosk mode for all registered devices in the database",
86+
security = [SecurityRequirement(name = "bearerAuth")]
87+
)
88+
@ApiResponse(
89+
responseCode = "200",
90+
description = "Command sent successfully",
91+
content = [Content(
92+
mediaType = "application/json",
93+
schema = Schema(implementation = KioskMessageDto::class)
94+
)]
95+
)
96+
@ApiResponse(
97+
responseCode = "400",
98+
description = "No devices found in database",
99+
content = [Content(
100+
mediaType = "application/json",
101+
schema = Schema(implementation = ErrorDto::class)
102+
)]
103+
)
104+
fun enableKioskForAllDevices(): ResponseEntity<Any> {
105+
return toggleKioskForAllDevices(true)
106+
}
107+
108+
/**
109+
* Disables kiosk mode for all registered devices.
110+
*/
111+
@PostMapping("/all/disable")
112+
@Operation(
113+
summary = "Disable kiosk mode for all devices",
114+
description = "Disables kiosk mode for all registered devices in the database",
115+
security = [SecurityRequirement(name = "bearerAuth")]
116+
)
117+
@ApiResponse(
118+
responseCode = "200",
119+
description = "Command sent successfully",
120+
content = [Content(
121+
mediaType = "application/json",
122+
schema = Schema(implementation = KioskMessageDto::class)
123+
)]
124+
)
125+
@ApiResponse(
126+
responseCode = "400",
127+
description = "No devices found in database",
128+
content = [Content(
129+
mediaType = "application/json",
130+
schema = Schema(implementation = ErrorDto::class)
131+
)]
132+
)
133+
fun disableKioskForAllDevices(): ResponseEntity<Any> {
134+
return toggleKioskForAllDevices(false)
135+
}
136+
137+
/**
138+
* Toggles kiosk mode for a specific device.
139+
*/
140+
private fun toggleKioskForDevice(deviceId: String, isKioskModeActive: Boolean): ResponseEntity<Any> {
141+
if (!deviceService.deviceExists(deviceId)) {
142+
return ResponseEntity.status(HttpStatus.NOT_FOUND)
143+
.body(ErrorDto(message = "Device with ID $deviceId not found", code = 404))
144+
}
145+
146+
val payload = buildMap {
147+
put("type", MESSAGE_TYPE)
148+
put("isKioskModeActive", isKioskModeActive.toString())
149+
put("deviceId", deviceId)
150+
}
151+
152+
notificationSender.sendDataMessage(KIOSK_TOPIC, payload)
153+
154+
val messageText = "Kiosk mode ${if (isKioskModeActive) "enabled" else "disabled"} for device: $deviceId"
155+
return ResponseEntity.ok(KioskMessageDto(messageText))
156+
}
157+
158+
/**
159+
* Toggles kiosk mode for all registered devices.
160+
* Sends one command with all device IDs from the database.
161+
*/
162+
private fun toggleKioskForAllDevices(isKioskModeActive: Boolean): ResponseEntity<Any> {
163+
val allDevices = deviceService.getAllDevices()
164+
165+
if (allDevices.isEmpty()) {
166+
return ResponseEntity.status(HttpStatus.BAD_REQUEST)
167+
.body(ErrorDto(message = "No devices found in database", code = 400))
168+
}
169+
170+
val deviceIds = allDevices.map { it.deviceId }
171+
172+
val payload = buildMap {
173+
put("type", MESSAGE_TYPE)
174+
put("isKioskModeActive", isKioskModeActive.toString())
175+
put("deviceIds", deviceIds.joinToString(","))
176+
}
177+
178+
notificationSender.sendDataMessage(KIOSK_TOPIC, payload)
179+
180+
val deviceCount = allDevices.size
181+
val messageText = "Kiosk mode ${if (isKioskModeActive) "enabled" else "disabled"} for $deviceCount devices"
182+
return ResponseEntity.ok(KioskMessageDto(messageText))
183+
}
184+
185+
/**
186+
* Retrieves a list of all registered devices.
187+
*/
188+
@GetMapping("/devices")
189+
@Operation(
190+
summary = "Get all devices",
191+
description = "Returns a list of all registered devices",
192+
security = [SecurityRequirement(name = "bearerAuth")]
193+
)
194+
@ApiResponse(responseCode = "200", description = "List of devices retrieved successfully")
195+
fun getAllDevices(): ResponseEntity<List<DeviceDto>> {
196+
val devices = deviceService.getAllDevices().map { DeviceDto.fromEntity(it) }
197+
return ResponseEntity.ok(devices)
198+
}
199+
}
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package band.effective.office.backend.feature.notifications.dto
2+
3+
import band.effective.office.backend.feature.notifications.repository.entity.DeviceEntity
4+
import io.swagger.v3.oas.annotations.media.Schema
5+
import java.time.LocalDateTime
6+
import java.util.UUID
7+
8+
/**
9+
* Data Transfer Object for device information.
10+
*/
11+
@Schema(description = "Device information")
12+
data class DeviceDto(
13+
@Schema(description = "Unique device identifier", example = "123e4567-e89b-12d3-a456-426614174000")
14+
val id: UUID,
15+
16+
@Schema(description = "Android device ID", example = "7ac6ddd9a731bbeb")
17+
val deviceId: String,
18+
19+
@Schema(description = "Device tag (e.g., meeting room name)", example = "Meeting Room A")
20+
val tag: String,
21+
22+
@Schema(description = "Device registration timestamp")
23+
val createdAt: LocalDateTime
24+
) {
25+
companion object {
26+
/**
27+
* Creates a DeviceDto from a DeviceEntity.
28+
*/
29+
fun fromEntity(entity: DeviceEntity): DeviceDto {
30+
return DeviceDto(
31+
id = entity.id,
32+
deviceId = entity.deviceId,
33+
tag = entity.tag,
34+
createdAt = entity.createdAt
35+
)
36+
}
37+
}
38+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package band.effective.office.backend.feature.notifications.dto
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
5+
/**
6+
* API response for successful operations in notifications module.
7+
*/
8+
@Schema(description = "API response for successful operations")
9+
data class KioskMessageDto(
10+
@Schema(
11+
description = "Message for the client",
12+
example = "Kiosk mode enabled for device: 7ac6ddd9a731bbeb"
13+
)
14+
val message: String
15+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package band.effective.office.backend.feature.notifications.dto
2+
3+
import io.swagger.v3.oas.annotations.media.Schema
4+
5+
/**
6+
* Data Transfer Object for kiosk mode toggle requests.
7+
*
8+
* This request allows enabling or disabling kiosk mode for a specific device
9+
*/
10+
@Schema(description = "Request to toggle kiosk mode")
11+
data class KioskToggleRequest(
12+
@Schema(
13+
description = "Unique Android device ID",
14+
example = "7ac6ddd9a731bbeb",
15+
required = false
16+
)
17+
val deviceId: String
18+
)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package band.effective.office.backend.feature.notifications.repository
2+
3+
import band.effective.office.backend.feature.notifications.repository.entity.DeviceEntity
4+
import org.springframework.data.jpa.repository.JpaRepository
5+
import org.springframework.stereotype.Repository
6+
import java.util.UUID
7+
8+
/**
9+
* Repository for working with devices
10+
*/
11+
@Repository
12+
interface DeviceRepository : JpaRepository<DeviceEntity, UUID> {
13+
14+
/**
15+
* Check if device exists by device_id
16+
* @return true if device exists, false otherwise
17+
*/
18+
fun existsByDeviceId(deviceId: String): Boolean
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package band.effective.office.backend.feature.notifications.repository.entity
2+
3+
import jakarta.persistence.Column
4+
import jakarta.persistence.Entity
5+
import jakarta.persistence.Id
6+
import jakarta.persistence.Table
7+
import java.time.LocalDateTime
8+
import java.util.UUID
9+
10+
/**
11+
* JPA entity for devices with kiosk mode functionality.
12+
*/
13+
@Entity
14+
@Table(name = "devices")
15+
class DeviceEntity(
16+
@Id
17+
val id: UUID = UUID.randomUUID(),
18+
19+
@Column(name = "device_id", nullable = false, unique = true, length = 255)
20+
val deviceId: String,
21+
22+
@Column(nullable = false, length = 255)
23+
val tag: String,
24+
25+
@Column(name = "created_at", nullable = false)
26+
val createdAt: LocalDateTime = LocalDateTime.now()
27+
)

0 commit comments

Comments
 (0)