Prototype administrative discussion mute controls
Let owners/operators mute or unmute new public/private sends from a human seat while retaining history, incoming messages, gameplay and credentials. Fence metadata and durable audit writes with live authority, membership and versions. Preserve exact admin/message retries and inherit restrictions through seat recovery. Validation: types/lint, 5431 offline tests, build, 52 HTTP integration tests, 24 administrator HTTP groups, independent SQL/privacy/UI review and desktop/mobile refresh acceptance. All 2230 prior games and 4746 seats preserved. Additive migration 0016. Verified runtime fingerprint 588967fbb9c2e3b6abe2990c4fce96ee69e2256fb7e3ee416314b87593cebba9; documentation-only grammar/button-label review gives b5e57bdd26812debede91c588e38f247f1fa895ad23e7ae30bc8550eedcd90ec. New-revision deployed acceptance remains open; full admin/rules scope is unfinished.
This commit is contained in:
1 parent
2cddf03932
commit
160fdc1f30
26 files changed
+3008
-33
No files matched your search
+10
-2
@@ -70,7 +70,9 @@ export async function readTableTalk(
|
||||
WHERE m.room_code = r.code AND m.id = ? AND (${channel})))
|
||||
ORDER BY m.sequence DESC LIMIT ${TALK_PAGE_SIZE + 1}
|
||||
)
|
||||
) AS messages ${viewer}`)
|
||||
) AS messages,
|
||||
EXISTS (SELECT 1 FROM seat_discussion_controls WHERE room_code=r.code AND player_id=s.player_id AND muted=1) AS muted
|
||||
${viewer}`)
|
||||
.bind(
|
||||
recipientId,
|
||||
recipientId,
|
||||
@@ -86,7 +88,7 @@ export async function readTableTalk(
|
||||
auth.playerId,
|
||||
auth.tokenHash,
|
||||
)
|
||||
.first<{ messages: string }>();
|
||||
.first<{ messages: string; muted: number }>();
|
||||
if (!row) {
|
||||
await requireRoomNotRemoved(code);
|
||||
throw new TableTalkError('Your seat could not be verified.');
|
||||
@@ -96,6 +98,7 @@ export async function readTableTalk(
|
||||
return {
|
||||
messages,
|
||||
before: newestFirst.length > TALK_PAGE_SIZE ? messages[0].id : null,
|
||||
muted: row.muted === 1,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -117,6 +120,7 @@ export async function sendTableTalk(
|
||||
(SELECT json_extract(target.value, '$.name') FROM json_each(r.state, '$.players') target WHERE json_extract(target.value, '$.id') = ?), ?, ?
|
||||
${viewer}
|
||||
AND NOT EXISTS (SELECT 1 FROM room_closures WHERE room_code = r.code AND closed = 1)
|
||||
AND NOT EXISTS (SELECT 1 FROM seat_discussion_controls WHERE room_code=r.code AND player_id=s.player_id AND muted=1)
|
||||
AND (? IS NULL OR EXISTS (SELECT 1 FROM json_each(r.state, '$.players') target
|
||||
WHERE json_extract(target.value, '$.id') = ? AND json_extract(target.value, '$.bot') IS NULL))
|
||||
AND NOT EXISTS (SELECT 1 FROM room_messages recent WHERE recent.room_code = r.code
|
||||
@@ -158,6 +162,10 @@ export async function sendTableTalk(
|
||||
await requireRoomNotRemoved(code);
|
||||
if (await database().prepare('SELECT 1 FROM room_closures WHERE room_code = ? AND closed = 1').bind(code).first())
|
||||
throw new TableTalkError('An administrator closed this room. Discussion history remains readable; new messages are available after reopening.', 409, 'ROOM_CLOSED');
|
||||
// Only a still-authenticated seat may learn its own restriction. Never gate prior receipts on mute.
|
||||
if (await database().prepare(`SELECT 1 ${viewer} AND EXISTS (SELECT 1 FROM seat_discussion_controls WHERE room_code=r.code AND player_id=s.player_id AND muted=1)`)
|
||||
.bind(code, auth.playerId, auth.tokenHash).first())
|
||||
throw new TableTalkError('An administrator muted discussion for this seat. You can still read messages and play; new messages are available after unmuting.', 409, 'SEAT_DISCUSSION_MUTED');
|
||||
throw new TableTalkError(
|
||||
'Message not confirmed. Check your seat and recipient, wait a second, then try again.',
|
||||
);
|
||||
|
||||
Reference in new issue
Block a user