/** * admin-relay-queue.ts — Admin endpoints for the event moderation queue. * * Protected by ADMIN_SECRET Bearer token (same pattern as admin-relay.ts). * * Routes: * GET /api/admin/relay/queue — list queue (filterable by status) * POST /api/admin/relay/queue/:eventId/approve — admin approve * POST /api/admin/relay/queue/:eventId/reject — admin reject */ import { Router, type Request, type Response } from "express"; import { db, relayEventQueue, type QueueStatus, QUEUE_STATUSES } from "@workspace/db"; import { eq } from "drizzle-orm"; import { makeLogger } from "../lib/logger.js"; import { moderationService } from "../lib/moderation.js"; import { requireAdmin } from "./admin-relay.js"; const logger = makeLogger("admin-relay-queue"); const router = Router(); // ── GET /admin/relay/queue ──────────────────────────────────────────────────── // Query param: ?status=pending|approved|rejected|auto_approved // Default: returns all statuses. router.get("/admin/relay/queue", requireAdmin, async (req: Request, res: Response) => { const statusParam = req.query["status"] as string | undefined; if (statusParam && !QUEUE_STATUSES.includes(statusParam as QueueStatus)) { res.status(400).json({ error: `Invalid status '${statusParam}'. Must be one of: ${QUEUE_STATUSES.join(", ")}`, }); return; } const rows = statusParam ? await db .select() .from(relayEventQueue) .where(eq(relayEventQueue.status, statusParam as QueueStatus)) .orderBy(relayEventQueue.createdAt) : await db .select() .from(relayEventQueue) .orderBy(relayEventQueue.createdAt); res.json({ total: rows.length, events: rows.map((r) => { // Parse content preview from rawEvent JSON; gracefully degrade on parse failure. let contentPreview: string | null = null; try { const parsed = JSON.parse(r.rawEvent ?? "{}") as { content?: string }; contentPreview = parsed.content?.slice(0, 120) ?? null; } catch { contentPreview = null; } return { eventId: r.eventId, pubkey: r.pubkey, kind: r.kind, status: r.status, contentPreview, reviewedBy: r.reviewedBy, reviewReason: r.reviewReason, createdAt: r.createdAt, decidedAt: r.decidedAt, }; }), }); }); // ── POST /admin/relay/queue/:eventId/approve ────────────────────────────────── router.post( "/admin/relay/queue/:eventId/approve", requireAdmin, async (req: Request, res: Response) => { const { eventId } = req.params as { eventId: string }; const body = req.body as { reason?: string }; const reason = body.reason ?? "admin approval"; const rows = await db .select({ status: relayEventQueue.status }) .from(relayEventQueue) .where(eq(relayEventQueue.eventId, eventId)) .limit(1); if (!rows[0]) { res.status(404).json({ error: "Event not found in queue" }); return; } if (rows[0].status === "approved" || rows[0].status === "auto_approved") { res.status(409).json({ error: "Event already approved" }); return; } await moderationService.decide(eventId, "approved", reason, "admin"); logger.info("admin approved queued event", { eventId: eventId.slice(0, 8), reason, }); res.json({ ok: true, eventId, status: "approved", reason }); }, ); // ── POST /admin/relay/queue/:eventId/reject ─────────────────────────────────── router.post( "/admin/relay/queue/:eventId/reject", requireAdmin, async (req: Request, res: Response) => { const { eventId } = req.params as { eventId: string }; const body = req.body as { reason?: string }; const reason = body.reason ?? "admin rejection"; const rows = await db .select({ status: relayEventQueue.status }) .from(relayEventQueue) .where(eq(relayEventQueue.eventId, eventId)) .limit(1); if (!rows[0]) { res.status(404).json({ error: "Event not found in queue" }); return; } if (rows[0].status === "rejected") { res.status(409).json({ error: "Event already rejected" }); return; } await moderationService.decide(eventId, "rejected", reason, "admin"); logger.info("admin rejected queued event", { eventId: eventId.slice(0, 8), reason, }); res.json({ ok: true, eventId, status: "rejected", reason }); }, ); export default router;