強化房間清理與比賽中分頁限制
This commit is contained in:
@@ -11,6 +11,7 @@ const matchTableName = process.env.DB_TABLE ?? 'badminton'
|
||||
const historyTableName = process.env.DB_HISTORY_TABLE ?? 'history'
|
||||
const appVersion = process.env.APP_VERSION ?? `${Date.now()}`
|
||||
const appStartedAt = new Date().toISOString()
|
||||
const LIVE_ROOM_STALE_MS = 30_000
|
||||
|
||||
const currentFilePath = fileURLToPath(import.meta.url)
|
||||
const currentDir = path.dirname(currentFilePath)
|
||||
@@ -77,6 +78,17 @@ app.get('/api/rooms', (_request, response) => {
|
||||
})
|
||||
})
|
||||
|
||||
app.post('/api/rooms/reconcile', (_request, response) => {
|
||||
const removedRoomIds = pruneStaleRooms()
|
||||
|
||||
response.json({
|
||||
ok: true,
|
||||
data: {
|
||||
removedRoomIds,
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/api/rooms/stream', (request, response) => {
|
||||
setupSse(response)
|
||||
roomListClients.add(response)
|
||||
@@ -106,6 +118,7 @@ app.post('/api/rooms', (request, response) => {
|
||||
clients: new Set(),
|
||||
createdAt: now,
|
||||
hostToken,
|
||||
hostSeenAt: now,
|
||||
roomId,
|
||||
status: 'live',
|
||||
updatedAt: now,
|
||||
@@ -171,6 +184,35 @@ app.post('/api/rooms/:roomId/release', (request, response) => {
|
||||
})
|
||||
})
|
||||
|
||||
app.post('/api/rooms/:roomId/heartbeat', (request, response) => {
|
||||
const room = rooms.get(request.params.roomId)
|
||||
|
||||
if (!room) {
|
||||
response.status(404).json({
|
||||
ok: false,
|
||||
message: '找不到這個房間。',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
const { hostToken } = request.body ?? {}
|
||||
|
||||
if (typeof hostToken !== 'string' || hostToken !== room.hostToken) {
|
||||
response.status(403).json({
|
||||
ok: false,
|
||||
message: '沒有權限更新房間心跳。',
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
room.hostSeenAt = new Date().toISOString()
|
||||
persistRooms()
|
||||
|
||||
response.json({
|
||||
ok: true,
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/api/rooms/:roomId', (request, response) => {
|
||||
const room = rooms.get(request.params.roomId)
|
||||
|
||||
@@ -520,6 +562,8 @@ function loadPersistedRooms() {
|
||||
|
||||
nextRooms.set(savedRoom.roomId, {
|
||||
...savedRoom,
|
||||
hostSeenAt:
|
||||
typeof savedRoom.hostSeenAt === 'string' ? savedRoom.hostSeenAt : savedRoom.updatedAt,
|
||||
clients: new Set(),
|
||||
})
|
||||
})
|
||||
@@ -540,6 +584,7 @@ function persistRooms() {
|
||||
createdAt: room.createdAt,
|
||||
groupId: room.groupId,
|
||||
hostToken: room.hostToken,
|
||||
hostSeenAt: room.hostSeenAt,
|
||||
leftTeamName: room.leftTeamName,
|
||||
matchupLabel: room.matchupLabel,
|
||||
pointLog: room.pointLog,
|
||||
@@ -618,6 +663,39 @@ function serializeRoom(room) {
|
||||
}
|
||||
}
|
||||
|
||||
function pruneStaleRooms() {
|
||||
const now = Date.now()
|
||||
const removedRoomIds = []
|
||||
|
||||
rooms.forEach((room, roomId) => {
|
||||
if (room.status !== 'live') {
|
||||
return
|
||||
}
|
||||
|
||||
const hostSeenAtTime = Date.parse(room.hostSeenAt ?? '')
|
||||
|
||||
if (!Number.isFinite(hostSeenAtTime) || now - hostSeenAtTime > LIVE_ROOM_STALE_MS) {
|
||||
room.clients.forEach((client) => {
|
||||
sendSse(client, 'room-closed', {
|
||||
roomId: room.roomId,
|
||||
status: 'stale',
|
||||
})
|
||||
client.end()
|
||||
})
|
||||
|
||||
rooms.delete(roomId)
|
||||
removedRoomIds.push(roomId)
|
||||
}
|
||||
})
|
||||
|
||||
if (removedRoomIds.length > 0) {
|
||||
persistRooms()
|
||||
broadcastRoomList()
|
||||
}
|
||||
|
||||
return removedRoomIds
|
||||
}
|
||||
|
||||
function getLiveRoomSummaries() {
|
||||
return Array.from(rooms.values())
|
||||
.filter((room) => room.status === 'live')
|
||||
|
||||
Reference in New Issue
Block a user