import { Router, Request, Response, NextFunction } from "express"; import { config } from "../config.js"; import { getDb } from "../db/index.js"; import { verifyJwt } from "../auth/jwt.js"; const router = Router(); function adminAuth(req: Request, res: Response, next: NextFunction): void { if (config.adminPubkeys.length === 0) { res.status(503).json({ code: "admin_disabled", message: "Admin API not configured" }); return; } const auth = req.headers.authorization; const adminKey = req.headers["x-admin-key"]; let pubkey: string | null = null; if (auth?.startsWith("Bearer ")) { const token = auth.slice(7).trim(); const payload = verifyJwt(token); if (payload?.pubkey) pubkey = payload.pubkey; } if (!pubkey && typeof adminKey === "string") { pubkey = adminKey.trim(); } if (!pubkey || !config.adminPubkeys.includes(pubkey)) { res.status(403).json({ code: "forbidden", message: "Admin access required" }); return; } next(); } router.use(adminAuth); router.get("/sponsors", async (req: Request, res: Response) => { const db = getDb(); const status = typeof req.query.status === "string" ? req.query.status : undefined; const limit = Math.min(parseInt(String(req.query.limit || 100), 10) || 100, 500); const sponsors = await db.getAllSponsors({ status, limit }); res.json(sponsors); }); router.patch("/sponsors/:id", async (req: Request, res: Response) => { const id = parseInt(req.params.id, 10); if (!Number.isFinite(id) || id < 1) { res.status(400).json({ code: "invalid_id", message: "Invalid sponsor id" }); return; } const action = typeof (req.body as { action?: string }).action === "string" ? (req.body as { action: string }).action : ""; const durationDays = typeof (req.body as { duration_days?: number }).duration_days === "number" ? (req.body as { duration_days: number }).duration_days : undefined; const db = getDb(); const sponsor = await db.getSponsorById(id); if (!sponsor) { res.status(404).json({ code: "not_found", message: "Sponsor not found" }); return; } switch (action) { case "approve": await db.updateSponsorStatus(id, "active"); if (!sponsor.activated_at || !sponsor.expires_at) { const now = Math.floor(Date.now() / 1000); const expiresAt = now + sponsor.duration_days * 86400; await db.updateSponsorActivation(id, now, expiresAt); } break; case "reject": await db.updateSponsorStatus(id, "removed"); break; case "pause": await db.updateSponsorStatus(id, "pending_review"); break; case "remove": await db.updateSponsorStatus(id, "removed"); break; case "extend": if (durationDays && durationDays >= 1 && durationDays <= 365) { const now = Math.floor(Date.now() / 1000); const current = sponsor.expires_at ?? now; const newExpires = Math.max(current, now) + durationDays * 86400; await db.updateSponsorExpiresAt(id, newExpires); } else { res.status(400).json({ code: "invalid_duration", message: "duration_days 1-365 required" }); return; } break; default: res.status(400).json({ code: "invalid_action", message: "action must be approve, reject, pause, remove, or extend" }); return; } const updated = await db.getSponsorById(id); res.json(updated); }); router.delete("/sponsors/:id", async (req: Request, res: Response) => { const id = parseInt(req.params.id, 10); if (!Number.isFinite(id) || id < 1) { res.status(400).json({ code: "invalid_id", message: "Invalid sponsor id" }); return; } const db = getDb(); const sponsor = await db.getSponsorById(id); if (!sponsor) { res.status(404).json({ code: "not_found", message: "Sponsor not found" }); return; } await db.updateSponsorStatus(id, "removed"); res.status(200).json({ ok: true }); }); export default router;