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
|
package com.android.trisolarisserver.controller
|
||||||
|
|
||||||
import com.android.trisolarisserver.component.PropertyAccess
|
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.BookingCancelRequest
|
||||||
import com.android.trisolarisserver.controller.dto.BookingCheckInRequest
|
import com.android.trisolarisserver.controller.dto.BookingCheckInRequest
|
||||||
import com.android.trisolarisserver.controller.dto.BookingCheckOutRequest
|
import com.android.trisolarisserver.controller.dto.BookingCheckOutRequest
|
||||||
@@ -35,7 +36,8 @@ class BookingFlow(
|
|||||||
private val bookingRepo: BookingRepo,
|
private val bookingRepo: BookingRepo,
|
||||||
private val roomRepo: RoomRepo,
|
private val roomRepo: RoomRepo,
|
||||||
private val roomStayRepo: RoomStayRepo,
|
private val roomStayRepo: RoomStayRepo,
|
||||||
private val appUserRepo: AppUserRepo
|
private val appUserRepo: AppUserRepo,
|
||||||
|
private val roomBoardEvents: RoomBoardEvents
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@PostMapping("/{bookingId}/check-in")
|
@PostMapping("/{bookingId}/check-in")
|
||||||
@@ -100,6 +102,7 @@ class BookingFlow(
|
|||||||
if (request.notes != null) booking.notes = request.notes
|
if (request.notes != null) booking.notes = request.notes
|
||||||
booking.updatedAt = now
|
booking.updatedAt = now
|
||||||
bookingRepo.save(booking)
|
bookingRepo.save(booking)
|
||||||
|
roomBoardEvents.emit(propertyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/{bookingId}/check-out")
|
@PostMapping("/{bookingId}/check-out")
|
||||||
@@ -128,6 +131,7 @@ class BookingFlow(
|
|||||||
if (request.notes != null) booking.notes = request.notes
|
if (request.notes != null) booking.notes = request.notes
|
||||||
booking.updatedAt = now
|
booking.updatedAt = now
|
||||||
bookingRepo.save(booking)
|
bookingRepo.save(booking)
|
||||||
|
roomBoardEvents.emit(propertyId)
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/{bookingId}/cancel")
|
@PostMapping("/{bookingId}/cancel")
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.android.trisolarisserver.controller
|
package com.android.trisolarisserver.controller
|
||||||
|
|
||||||
import com.android.trisolarisserver.component.PropertyAccess
|
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.RoomChangeRequest
|
||||||
import com.android.trisolarisserver.controller.dto.RoomChangeResponse
|
import com.android.trisolarisserver.controller.dto.RoomChangeResponse
|
||||||
import com.android.trisolarisserver.models.room.RoomStay
|
import com.android.trisolarisserver.models.room.RoomStay
|
||||||
@@ -31,7 +32,8 @@ class RoomStayFlow(
|
|||||||
private val roomStayRepo: RoomStayRepo,
|
private val roomStayRepo: RoomStayRepo,
|
||||||
private val roomStayChangeRepo: RoomStayChangeRepo,
|
private val roomStayChangeRepo: RoomStayChangeRepo,
|
||||||
private val roomRepo: RoomRepo,
|
private val roomRepo: RoomRepo,
|
||||||
private val appUserRepo: AppUserRepo
|
private val appUserRepo: AppUserRepo,
|
||||||
|
private val roomBoardEvents: RoomBoardEvents
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@PostMapping("/{roomStayId}/change-room")
|
@PostMapping("/{roomStayId}/change-room")
|
||||||
@@ -98,6 +100,7 @@ class RoomStayFlow(
|
|||||||
idempotencyKey = request.idempotencyKey
|
idempotencyKey = request.idempotencyKey
|
||||||
)
|
)
|
||||||
val savedChange = roomStayChangeRepo.save(change)
|
val savedChange = roomStayChangeRepo.save(change)
|
||||||
|
roomBoardEvents.emit(propertyId)
|
||||||
return toResponse(savedChange)
|
return toResponse(savedChange)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.android.trisolarisserver.controller
|
package com.android.trisolarisserver.controller
|
||||||
|
|
||||||
import com.android.trisolarisserver.component.PropertyAccess
|
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.RoomAvailabilityRangeResponse
|
||||||
import com.android.trisolarisserver.controller.dto.RoomAvailabilityResponse
|
import com.android.trisolarisserver.controller.dto.RoomAvailabilityResponse
|
||||||
import com.android.trisolarisserver.controller.dto.RoomBoardResponse
|
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.ResponseStatus
|
||||||
import org.springframework.web.bind.annotation.RestController
|
import org.springframework.web.bind.annotation.RestController
|
||||||
import org.springframework.web.server.ResponseStatusException
|
import org.springframework.web.server.ResponseStatusException
|
||||||
|
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter
|
||||||
import java.time.LocalDate
|
import java.time.LocalDate
|
||||||
import java.time.ZoneId
|
import java.time.ZoneId
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
@@ -38,7 +40,8 @@ class Rooms(
|
|||||||
private val roomStayRepo: RoomStayRepo,
|
private val roomStayRepo: RoomStayRepo,
|
||||||
private val propertyRepo: PropertyRepo,
|
private val propertyRepo: PropertyRepo,
|
||||||
private val roomTypeRepo: RoomTypeRepo,
|
private val roomTypeRepo: RoomTypeRepo,
|
||||||
private val propertyUserRepo: PropertyUserRepo
|
private val propertyUserRepo: PropertyUserRepo,
|
||||||
|
private val roomBoardEvents: RoomBoardEvents
|
||||||
) {
|
) {
|
||||||
|
|
||||||
@GetMapping
|
@GetMapping
|
||||||
@@ -88,6 +91,16 @@ class Rooms(
|
|||||||
return if (isAgentOnly(roles)) mapped.filter { it.status == RoomBoardStatus.FREE } else mapped
|
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")
|
@GetMapping("/availability")
|
||||||
fun roomAvailability(
|
fun roomAvailability(
|
||||||
@PathVariable propertyId: UUID,
|
@PathVariable propertyId: UUID,
|
||||||
@@ -177,7 +190,9 @@ class Rooms(
|
|||||||
notes = request.notes
|
notes = request.notes
|
||||||
)
|
)
|
||||||
|
|
||||||
return roomRepo.save(room).toRoomResponse()
|
val saved = roomRepo.save(room).toRoomResponse()
|
||||||
|
roomBoardEvents.emit(propertyId)
|
||||||
|
return saved
|
||||||
}
|
}
|
||||||
|
|
||||||
@PutMapping("/{roomId}")
|
@PutMapping("/{roomId}")
|
||||||
@@ -208,7 +223,9 @@ class Rooms(
|
|||||||
room.maintenance = request.maintenance
|
room.maintenance = request.maintenance
|
||||||
room.notes = request.notes
|
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?) {
|
private fun requirePrincipal(principal: MyPrincipal?) {
|
||||||
|
|||||||
Reference in New Issue
Block a user