76 lines
2.2 KiB
TypeScript
76 lines
2.2 KiB
TypeScript
import { auth } from '@/lib/auth'
|
|
import { db } from '@/lib/db'
|
|
import { scores, roundPlayers } from '@/lib/db/schema'
|
|
import { eq, and } from 'drizzle-orm'
|
|
import { headers } from 'next/headers'
|
|
|
|
export async function GET(
|
|
request: Request,
|
|
{ params }: { params: Promise<{ id: string }> },
|
|
) {
|
|
const { id: roundId } = await params
|
|
|
|
const session = await auth.api.getSession({ headers: await headers() })
|
|
if (!session) return new Response('Unauthorized', { status: 401 })
|
|
|
|
// Verify caller is a member of this round
|
|
const [membership] = await db
|
|
.select()
|
|
.from(roundPlayers)
|
|
.where(and(eq(roundPlayers.roundId, roundId), eq(roundPlayers.userId, session.user.id)))
|
|
.limit(1)
|
|
|
|
if (!membership) return new Response('Forbidden', { status: 403 })
|
|
|
|
const encoder = new TextEncoder()
|
|
|
|
const stream = new ReadableStream({
|
|
async start(controller) {
|
|
async function send() {
|
|
const rows = await db
|
|
.select({ holeId: scores.holeId, playerId: scores.playerId, strokes: scores.strokes })
|
|
.from(scores)
|
|
.where(eq(scores.roundId, roundId))
|
|
|
|
controller.enqueue(encoder.encode(`data: ${JSON.stringify(rows)}\n\n`))
|
|
}
|
|
|
|
// Initial payload
|
|
await send()
|
|
|
|
// Poll every 3 seconds
|
|
const interval = setInterval(async () => {
|
|
try { await send() } catch { clearInterval(interval); controller.close() }
|
|
}, 3000)
|
|
|
|
// Heartbeat every 30s to keep connection alive through proxies
|
|
const heartbeat = setInterval(() => {
|
|
try { controller.enqueue(encoder.encode(': heartbeat\n\n')) } catch { /* closed */ }
|
|
}, 30_000)
|
|
|
|
// Clean up on disconnect or 4-hour timeout
|
|
const timeout = setTimeout(() => {
|
|
clearInterval(interval)
|
|
clearInterval(heartbeat)
|
|
controller.close()
|
|
}, 4 * 60 * 60 * 1000)
|
|
|
|
request.signal.addEventListener('abort', () => {
|
|
clearInterval(interval)
|
|
clearInterval(heartbeat)
|
|
clearTimeout(timeout)
|
|
controller.close()
|
|
})
|
|
},
|
|
})
|
|
|
|
return new Response(stream, {
|
|
headers: {
|
|
'Content-Type': 'text/event-stream',
|
|
'Cache-Control': 'no-cache, no-transform',
|
|
Connection: 'keep-alive',
|
|
'X-Accel-Buffering': 'no', // disable nginx buffering
|
|
},
|
|
})
|
|
}
|