add see update
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
package com.android.trisolarisserver.component
|
||||
|
||||
import com.android.trisolarisserver.controller.dto.RoomBoardResponse
|
||||
import com.android.trisolarisserver.controller.dto.RoomBoardStatus
|
||||
import com.android.trisolarisserver.repo.RoomRepo
|
||||
import com.android.trisolarisserver.repo.RoomStayRepo
|
||||
import org.springframework.scheduling.annotation.Scheduled
|
||||
import org.springframework.stereotype.Component
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
|
||||
import java.io.IOException
|
||||
import java.util.UUID
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
|
||||
@Component
|
||||
class RoomBoardEvents(
|
||||
private val roomRepo: RoomRepo,
|
||||
private val roomStayRepo: RoomStayRepo
|
||||
) {
|
||||
private val emitters: MutableMap<UUID, CopyOnWriteArrayList<SseEmitter>> = ConcurrentHashMap()
|
||||
|
||||
fun subscribe(propertyId: UUID): SseEmitter {
|
||||
val emitter = SseEmitter(0L)
|
||||
emitters.computeIfAbsent(propertyId) { CopyOnWriteArrayList() }.add(emitter)
|
||||
emitter.onCompletion { emitters[propertyId]?.remove(emitter) }
|
||||
emitter.onTimeout { emitters[propertyId]?.remove(emitter) }
|
||||
emitter.onError { emitters[propertyId]?.remove(emitter) }
|
||||
try {
|
||||
emitter.send(SseEmitter.event().name("room-board").data(buildSnapshot(propertyId)))
|
||||
} catch (_: IOException) {
|
||||
emitters[propertyId]?.remove(emitter)
|
||||
}
|
||||
return emitter
|
||||
}
|
||||
|
||||
fun emit(propertyId: UUID) {
|
||||
val data = buildSnapshot(propertyId)
|
||||
val list = emitters[propertyId] ?: return
|
||||
val dead = mutableListOf<SseEmitter>()
|
||||
for (emitter in list) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event().name("room-board").data(data))
|
||||
} catch (_: IOException) {
|
||||
dead.add(emitter)
|
||||
}
|
||||
}
|
||||
if (dead.isNotEmpty()) {
|
||||
list.removeAll(dead.toSet())
|
||||
}
|
||||
}
|
||||
|
||||
@Scheduled(fixedDelayString = "25000")
|
||||
fun heartbeat() {
|
||||
emitters.forEach { (_, list) ->
|
||||
val dead = mutableListOf<SseEmitter>()
|
||||
for (emitter in list) {
|
||||
try {
|
||||
emitter.send(SseEmitter.event().name("ping").data("ok"))
|
||||
} catch (_: IOException) {
|
||||
dead.add(emitter)
|
||||
}
|
||||
}
|
||||
if (dead.isNotEmpty()) {
|
||||
list.removeAll(dead.toSet())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun buildSnapshot(propertyId: UUID): List<RoomBoardResponse> {
|
||||
val rooms = roomRepo.findByPropertyIdOrderByRoomNumber(propertyId)
|
||||
val occupiedRoomIds = roomStayRepo.findOccupiedRoomIds(propertyId).toHashSet()
|
||||
return rooms.map { room ->
|
||||
val status = when {
|
||||
room.maintenance -> RoomBoardStatus.MAINTENANCE
|
||||
!room.active -> RoomBoardStatus.INACTIVE
|
||||
occupiedRoomIds.contains(room.id) -> RoomBoardStatus.OCCUPIED
|
||||
else -> RoomBoardStatus.FREE
|
||||
}
|
||||
RoomBoardResponse(
|
||||
roomNumber = room.roomNumber,
|
||||
roomTypeName = room.roomType.name,
|
||||
status = status
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.android.trisolarisserver.controller
|
||||
|
||||
import com.android.trisolarisserver.component.PropertyAccess
|
||||
import com.android.trisolarisserver.component.RoomBoardEvents
|
||||
import com.android.trisolarisserver.controller.dto.BookingCancelRequest
|
||||
import com.android.trisolarisserver.controller.dto.BookingCheckInRequest
|
||||
import com.android.trisolarisserver.controller.dto.BookingCheckOutRequest
|
||||
@@ -35,7 +36,8 @@ class BookingFlow(
|
||||
private val bookingRepo: BookingRepo,
|
||||
private val roomRepo: RoomRepo,
|
||||
private val roomStayRepo: RoomStayRepo,
|
||||
private val appUserRepo: AppUserRepo
|
||||
private val appUserRepo: AppUserRepo,
|
||||
private val roomBoardEvents: RoomBoardEvents
|
||||
) {
|
||||
|
||||
@PostMapping("/{bookingId}/check-in")
|
||||
@@ -100,6 +102,7 @@ class BookingFlow(
|
||||
if (request.notes != null) booking.notes = request.notes
|
||||
booking.updatedAt = now
|
||||
bookingRepo.save(booking)
|
||||
roomBoardEvents.emit(propertyId)
|
||||
}
|
||||
|
||||
@PostMapping("/{bookingId}/check-out")
|
||||
@@ -128,6 +131,7 @@ class BookingFlow(
|
||||
if (request.notes != null) booking.notes = request.notes
|
||||
booking.updatedAt = now
|
||||
bookingRepo.save(booking)
|
||||
roomBoardEvents.emit(propertyId)
|
||||
}
|
||||
|
||||
@PostMapping("/{bookingId}/cancel")
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.android.trisolarisserver.controller
|
||||
|
||||
import com.android.trisolarisserver.component.PropertyAccess
|
||||
import com.android.trisolarisserver.component.RoomBoardEvents
|
||||
import com.android.trisolarisserver.controller.dto.RoomChangeRequest
|
||||
import com.android.trisolarisserver.controller.dto.RoomChangeResponse
|
||||
import com.android.trisolarisserver.models.room.RoomStay
|
||||
@@ -31,7 +32,8 @@ class RoomStayFlow(
|
||||
private val roomStayRepo: RoomStayRepo,
|
||||
private val roomStayChangeRepo: RoomStayChangeRepo,
|
||||
private val roomRepo: RoomRepo,
|
||||
private val appUserRepo: AppUserRepo
|
||||
private val appUserRepo: AppUserRepo,
|
||||
private val roomBoardEvents: RoomBoardEvents
|
||||
) {
|
||||
|
||||
@PostMapping("/{roomStayId}/change-room")
|
||||
@@ -98,6 +100,7 @@ class RoomStayFlow(
|
||||
idempotencyKey = request.idempotencyKey
|
||||
)
|
||||
val savedChange = roomStayChangeRepo.save(change)
|
||||
roomBoardEvents.emit(propertyId)
|
||||
return toResponse(savedChange)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.android.trisolarisserver.controller
|
||||
|
||||
import com.android.trisolarisserver.component.PropertyAccess
|
||||
import com.android.trisolarisserver.component.RoomBoardEvents
|
||||
import com.android.trisolarisserver.controller.dto.RoomAvailabilityRangeResponse
|
||||
import com.android.trisolarisserver.controller.dto.RoomAvailabilityResponse
|
||||
import com.android.trisolarisserver.controller.dto.RoomBoardResponse
|
||||
@@ -26,6 +27,7 @@ import org.springframework.web.bind.annotation.RequestMapping
|
||||
import org.springframework.web.bind.annotation.ResponseStatus
|
||||
import org.springframework.web.bind.annotation.RestController
|
||||
import org.springframework.web.server.ResponseStatusException
|
||||
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
import java.util.UUID
|
||||
@@ -38,7 +40,8 @@ class Rooms(
|
||||
private val roomStayRepo: RoomStayRepo,
|
||||
private val propertyRepo: PropertyRepo,
|
||||
private val roomTypeRepo: RoomTypeRepo,
|
||||
private val propertyUserRepo: PropertyUserRepo
|
||||
private val propertyUserRepo: PropertyUserRepo,
|
||||
private val roomBoardEvents: RoomBoardEvents
|
||||
) {
|
||||
|
||||
@GetMapping
|
||||
@@ -88,6 +91,16 @@ class Rooms(
|
||||
return if (isAgentOnly(roles)) mapped.filter { it.status == RoomBoardStatus.FREE } else mapped
|
||||
}
|
||||
|
||||
@GetMapping("/board/stream")
|
||||
fun roomBoardStream(
|
||||
@PathVariable propertyId: UUID,
|
||||
@AuthenticationPrincipal principal: MyPrincipal?
|
||||
): SseEmitter {
|
||||
requirePrincipal(principal)
|
||||
propertyAccess.requireMember(propertyId, principal!!.userId)
|
||||
return roomBoardEvents.subscribe(propertyId)
|
||||
}
|
||||
|
||||
@GetMapping("/availability")
|
||||
fun roomAvailability(
|
||||
@PathVariable propertyId: UUID,
|
||||
@@ -177,7 +190,9 @@ class Rooms(
|
||||
notes = request.notes
|
||||
)
|
||||
|
||||
return roomRepo.save(room).toRoomResponse()
|
||||
val saved = roomRepo.save(room).toRoomResponse()
|
||||
roomBoardEvents.emit(propertyId)
|
||||
return saved
|
||||
}
|
||||
|
||||
@PutMapping("/{roomId}")
|
||||
@@ -208,7 +223,9 @@ class Rooms(
|
||||
room.maintenance = request.maintenance
|
||||
room.notes = request.notes
|
||||
|
||||
return roomRepo.save(room).toRoomResponse()
|
||||
val saved = roomRepo.save(room).toRoomResponse()
|
||||
roomBoardEvents.emit(propertyId)
|
||||
return saved
|
||||
}
|
||||
|
||||
private fun requirePrincipal(principal: MyPrincipal?) {
|
||||
|
||||
Reference in New Issue
Block a user