/** * 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, type NextFunction } 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"; const logger = makeLogger("admin-relay-queue"); const router = Router(); const ADMIN_SECRET = process.env["ADMIN_SECRET"] ?? ""; const IS_PROD = process.env["NODE_ENV"] === "production"; if (!ADMIN_SECRET && IS_PROD) { logger.error("ADMIN_SECRET not set in production — admin relay queue routes are unprotected"); } // ── Admin auth middleware ───────────────────────────────────────────────────── function requireAdmin(req: Request, res: Response, next: NextFunction): void { if (ADMIN_SECRET) { const authHeader = req.headers["authorization"] ?? ""; const token = authHeader.startsWith("Bearer ") ? authHeader.slice(7).trim() : ""; if (token !== ADMIN_SECRET) { res.status(401).json({ error: "Unauthorized" }); return; } } else { const ip = req.ip ?? ""; const isLocal = ip === "127.0.0.1" || ip === "::1" || ip === "::ffff:127.0.0.1"; if (!isLocal) { res.status(401).json({ error: "Unauthorized" }); return; } } next(); } // ── 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) => ({ eventId: r.eventId, pubkey: r.pubkey, kind: r.kind, status: r.status, 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;