Files
Dune/app/api/admin/rooms/[code]/control/route.ts
T
Quintine 98b1b3261b Add administrative room close and reopen controls
Preserve readable private tables, discussion and pending games while closure blocks new play, joins, messages, AI and seat mutations. Add live-authorized atomic closure audits, room-version/revision fences, dedicated controls and exact saved retries. Reopen with the saved pause/join settings and current credentials; restoring a removed but closed room keeps its AI stopped.

Verified types, lint, 5356 offline tests, 52 HTTP tests and build. Seventeen dedicated authenticated HTTP groups and independent privacy/persistence review passed. Desktop/mobile browser QA confirmed one close after a lost response and refresh, persistent readable notices, retained private draft, mobile reopening and saved-seat refresh. All 2,001 baseline rooms, 4,243 seats and related records preserved. Positive production admin acceptance awaits authorized QA access; full admin and rules gates remain open.
2026-09-22 12:31:02 +10:00

36 lines
1.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { env, waitUntil } from 'cloudflare:workers';
import { AdminError, requireAdmin } from '@/db/admin-access';
import { applyAdminRoomControl, readAdminRoomControl } from '@/db/admin-lifecycle';
import { resumeRoom } from '@/db/room-continuation';
import { adminBody, adminFailure, adminResponse, adminToken } from '@/lib/admin-http';
import { validAdminRoomControlInput } from '@/lib/room-control';
function roomCode(request: Request) {
const code = new URL(request.url).pathname.split('/').at(-2)?.toUpperCase() ?? '';
if (!/^[A-Z2-9]{8}$/.test(code)) throw new AdminError('Enter the eight-character room code.', 400);
return code;
}
export async function GET(request: Request) {
try {
const identity = await requireAdmin(env.DB, adminToken(request));
if (request.headers.has('X-Dune-Admin-Id') && request.headers.get('X-Dune-Admin-Id') !== identity.id)
throw new AdminError('The signed-in administrator changed. Reload administration before continuing.', 401);
return adminResponse(await readAdminRoomControl(env.DB, identity, roomCode(request)));
} catch (error) { return adminFailure(error); }
}
export async function POST(request: Request) {
try {
const body = await adminBody(request, env.DUNE_PUBLIC_ORIGIN);
const identity = await requireAdmin(env.DB, adminToken(request), ['owner', 'operator']);
if (request.headers.get('X-Dune-Admin-Id') !== identity.id)
throw new AdminError('The signed-in administrator changed. Reload administration before continuing.', 401);
if (!validAdminRoomControlInput(body)) throw new AdminError('Provide room settings and a reason of 1–300 characters.', 400);
const code = roomCode(request);
const result = await applyAdminRoomControl(env.DB, identity, code, body);
if (!result.paused && !result.closed) waitUntil(resumeRoom(code));
return adminResponse(result);
} catch (error) { return adminFailure(error); }
}