add see update

This commit is contained in:
androidlover5842
2026-01-24 23:18:15 +05:30
parent 4a2834819f
commit ab7f02ddc6
4 changed files with 115 additions and 5 deletions

View File

@@ -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
)
}
}
}

View File

@@ -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")

View File

@@ -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)
} }

View File

@@ -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?) {