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.
This commit is contained in:
1 parent
238de3860d
commit
98b1b3261b
49 files changed
+4255
-86
No files matched your search
@@ -91,6 +91,51 @@ export const adminRoomRemovals = sqliteTable(
|
||||
],
|
||||
);
|
||||
|
||||
// Closure freezes mutations while retaining private read access, independent of saved game state and pause settings.
|
||||
export const roomClosures = sqliteTable(
|
||||
'room_closures',
|
||||
{
|
||||
roomCode: text('room_code')
|
||||
.primaryKey()
|
||||
.references(() => rooms.code, { onDelete: 'cascade' }),
|
||||
closed: integer('closed').notNull().default(0),
|
||||
revision: integer('revision').notNull().default(0),
|
||||
closedAt: integer('closed_at'),
|
||||
updatedAt: integer('updated_at').notNull(),
|
||||
},
|
||||
(table) => [
|
||||
check('room_closures_closed', sql`${table.closed} IN (0, 1)`),
|
||||
check('room_closures_revision', sql`${table.revision} >= 0`),
|
||||
check(
|
||||
'room_closures_time',
|
||||
sql`(${table.closed} = 1 AND ${table.closedAt} IS NOT NULL) OR (${table.closed} = 0 AND ${table.closedAt} IS NULL)`,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
// Exact operation receipts also retain safe audit evidence independently of rooms/accounts.
|
||||
export const adminRoomClosures = sqliteTable(
|
||||
'admin_room_closures',
|
||||
{
|
||||
operationId: text('operation_id').primaryKey(),
|
||||
actorAdminId: text('actor_admin_id').notNull(),
|
||||
roomCode: text('room_code').notNull(),
|
||||
requestHash: text('request_hash').notNull(),
|
||||
expectedVersion: integer('expected_version').notNull(),
|
||||
expectedRevision: integer('expected_revision').notNull(),
|
||||
appliedVersion: integer('applied_version').notNull(),
|
||||
appliedRevision: integer('applied_revision').notNull(),
|
||||
closed: integer('closed').notNull(),
|
||||
reason: text('reason').notNull(),
|
||||
beforeMetadata: text('before_metadata').notNull(),
|
||||
afterMetadata: text('after_metadata').notNull(),
|
||||
createdAt: integer('created_at').notNull(),
|
||||
},
|
||||
(table) => [
|
||||
index('admin_room_closures_room').on(table.roomCode, table.createdAt),
|
||||
],
|
||||
);
|
||||
|
||||
// Durable receipts are also the room operations audit. They intentionally survive
|
||||
// account/room removal, and contain no credentials, game state or private cards.
|
||||
export const adminRoomAudit = sqliteTable(
|
||||
|
||||
Reference in new issue
Block a user