import type BetterSqlite3 from "better-sqlite3"; import type { AdminAuditQuery, AdminOperationAuditItem, AdminOperationAuditResponse, PrivateContentAccessAuditItem, PrivateContentAccessAuditResponse, } from "@dada/shared-contracts"; interface AuditCursor { logId: string; occurredAt: number; } interface AdminOperationRow { actor_ref: string; actor_type: "system" | "super_admin"; after_summary: string | null; before_summary: string | null; expires_at: number; log_id: string; occurred_at: number; operation_type: string; result: "failed" | "succeeded"; target_ref: string; target_type: string; } interface PrivateContentAccessRow { actor_ref: string; content_type: "image" | "prompt"; expires_at: number; log_id: string; occurred_at: number; target_ref: string; } export class AdminAuditQueryError extends Error { constructor() { super("admin_audit_query_invalid"); this.name = "AdminAuditQueryError"; } } function encodeCursor(row: { log_id: string; occurred_at: number }) { return Buffer.from(JSON.stringify([row.occurred_at, row.log_id]), "utf8").toString("base64url"); } function decodeCursor(cursor: string | undefined): AuditCursor | undefined { if (!cursor) return undefined; try { const parsed: unknown = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8")); if (!Array.isArray(parsed) || parsed.length !== 2 || !Number.isSafeInteger(parsed[0]) || typeof parsed[1] !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{0,159}$/.test(parsed[1])) { throw new AdminAuditQueryError(); } return { occurredAt: parsed[0] as number, logId: parsed[1] }; } catch (error) { if (error instanceof AdminAuditQueryError) throw error; throw new AdminAuditQueryError(); } } function normalizeLimit(limit: number | undefined) { if (limit === undefined) return 50; if (!Number.isSafeInteger(limit) || limit < 1 || limit > 100) throw new AdminAuditQueryError(); return limit; } function pageRows(rows: Row[], limit: number) { const hasMore = rows.length > limit; const items = hasMore ? rows.slice(0, limit) : rows; return { items, nextCursor: hasMore ? encodeCursor(items[items.length - 1]!) : null }; } function iso(value: number) { return new Date(value).toISOString(); } export function listAdminOperationAudit( database: BetterSqlite3.Database, query: AdminAuditQuery, clock: () => number = Date.now, ): AdminOperationAuditResponse { const cursor = decodeCursor(query.cursor); const limit = normalizeLimit(query.limit); const rows = (cursor ? database.prepare(` SELECT actor_ref, actor_type, after_summary, before_summary, expires_at, log_id, occurred_at, operation_type, result, target_ref, target_type FROM admin_operation_logs WHERE occurred_at < ? OR (occurred_at = ? AND log_id < ?) ORDER BY occurred_at DESC, log_id DESC LIMIT ? `).all(cursor.occurredAt, cursor.occurredAt, cursor.logId, limit + 1) : database.prepare(` SELECT actor_ref, actor_type, after_summary, before_summary, expires_at, log_id, occurred_at, operation_type, result, target_ref, target_type FROM admin_operation_logs ORDER BY occurred_at DESC, log_id DESC LIMIT ? `).all(limit + 1)) as AdminOperationRow[]; const page = pageRows(rows, limit); const items: AdminOperationAuditItem[] = page.items.map((row) => ({ actor_ref: row.actor_ref, actor_type: row.actor_type, after_summary: row.after_summary, before_summary: row.before_summary, expires_at: iso(row.expires_at), log_id: row.log_id, occurred_at: iso(row.occurred_at), operation_type: row.operation_type, result: row.result, target_ref: row.target_ref, target_type: row.target_type, })); return { generated_at: iso(clock()), items, next_cursor: page.nextCursor }; } export function listPrivateContentAccessAudit( database: BetterSqlite3.Database, query: AdminAuditQuery, clock: () => number = Date.now, ): PrivateContentAccessAuditResponse { const cursor = decodeCursor(query.cursor); const limit = normalizeLimit(query.limit); const rows = (cursor ? database.prepare(` SELECT actor_ref, content_type, expires_at, log_id, occurred_at, target_ref FROM private_content_access_logs WHERE occurred_at < ? OR (occurred_at = ? AND log_id < ?) ORDER BY occurred_at DESC, log_id DESC LIMIT ? `).all(cursor.occurredAt, cursor.occurredAt, cursor.logId, limit + 1) : database.prepare(` SELECT actor_ref, content_type, expires_at, log_id, occurred_at, target_ref FROM private_content_access_logs ORDER BY occurred_at DESC, log_id DESC LIMIT ? `).all(limit + 1)) as PrivateContentAccessRow[]; const page = pageRows(rows, limit); const items: PrivateContentAccessAuditItem[] = page.items.map((row) => ({ actor_ref: row.actor_ref, content_type: row.content_type, expires_at: iso(row.expires_at), log_id: row.log_id, occurred_at: iso(row.occurred_at), target_ref: row.target_ref, })); return { generated_at: iso(clock()), items, next_cursor: page.nextCursor }; }