import { db } from '../db/index.js'; import { AuthError } from '../auth/index.js'; import { textField } from './core.js'; import type { ModerationCaseAction, ModerationGame } from '../../moderation.js'; type Subject = { game: ModerationGame; guid: string }; export type CaseActionReference = Pick; export function caseActionReference(value: unknown): CaseActionReference { if (!value || typeof value !== 'object') throw new AuthError('Choose a recorded action.'); const reference = value as Record; if (reference.type !== 'action' && reference.type !== 'restriction') throw new AuthError('Choose a recorded warning, action, or restriction.'); return { type: reference.type, id: textField(reference.id, 'Action ID', 128) }; } const restrictionColumns = `id,'restriction' AS type,kind,reason,actor, created_at AS createdAt,expires_at AS expiresAt, CASE WHEN revoked_at IS NOT NULL THEN 'revoked' WHEN expires_at IS NOT NULL AND expires_at<=? THEN 'expired' ELSE 'active' END AS state`; const actionColumns = `id,'action' AS type,kind,reason,actor, created_at AS createdAt,expires_at AS expiresAt,state`; /** Only actual records for this account can become case evidence. */ export function recordedCaseAction( subject: Subject, reference: CaseActionReference, now: string ): ModerationCaseAction { const handle = db(); const action = reference.type === 'restriction' ? handle .prepare( `SELECT ${restrictionColumns} FROM moderation_sanctions WHERE id=? AND guid=? AND (game=? OR scope='fleet')` ) .get(now, reference.id, subject.guid, subject.game) : handle .prepare( `SELECT ${actionColumns} FROM moderation_actions WHERE id=? AND guid=? AND game=?` ) .get(reference.id, subject.guid, subject.game); if (!action) throw new AuthError('The recorded action is unavailable for this case’s player.', 404); return action as ModerationCaseAction; } export function isCaseActionLinked(caseId: string, reference: CaseActionReference): boolean { return Boolean( db() .prepare( `SELECT 1 FROM moderation_case_action_links WHERE case_id=? AND action_type=? AND action_id=? UNION ALL SELECT 1 FROM moderation_sanctions WHERE ?='restriction' AND id=? AND case_id=? LIMIT 1` ) .get(caseId, reference.type, reference.id, reference.type, reference.id, caseId) ); } export function caseActionLists(caseId: string, subject: Subject, now: string) { const handle = db(); const records = handle .prepare( `SELECT ${restrictionColumns}, (case_id=? OR EXISTS (SELECT 1 FROM moderation_case_action_links l WHERE l.case_id=? AND l.action_type='restriction' AND l.action_id=moderation_sanctions.id)) AS linked FROM moderation_sanctions WHERE guid=? AND (game=? OR scope='fleet') UNION ALL SELECT ${actionColumns}, EXISTS (SELECT 1 FROM moderation_case_action_links l WHERE l.case_id=? AND l.action_type='action' AND l.action_id=moderation_actions.id) AS linked FROM moderation_actions WHERE guid=? AND game=? ORDER BY createdAt DESC,id` ) .all( now, caseId, caseId, subject.guid, subject.game, caseId, subject.guid, subject.game ) as Array; const linkedActions: ModerationCaseAction[] = []; const availableActions: ModerationCaseAction[] = []; for (const { linked, ...record } of records) (linked ? linkedActions : availableActions).push(record); return { linkedActions, availableActions }; }