import { NextRequest, NextResponse } from "next/server"; import { sameOriginJson } from "@/lib/csrf"; import { allow, clientIp } from "@/lib/ratelimit"; import { currentOperator } from "@/lib/ops/session"; import { ControlError, deleteFacility, PLAN_NOTE_MAX, planControl, resetDemoNow, setSwitch, type PlanAct } from "@/lib/ops/controls"; export const dynamic = "force-dynamic"; /* The console's one write endpoint. Each action is a function in lib/ops/controls.ts; this route * checks the operator, shapes the input and turns a ControlError into a status. */ export async function POST(req: NextRequest) { const csrf = sameOriginJson(req); if (csrf) return NextResponse.json({ error: csrf }, { status: 403 }); const op = await currentOperator(); if (!op) return NextResponse.json({ error: "Not signed in" }, { status: 401 }); const ip = clientIp(req.headers); if (!allow("ops-controls:" + op.id, 30, 15 * 60 * 1000)) { return NextResponse.json({ error: "Too many changes — try again in a few minutes." }, { status: 429 }); } let body: Record; try { body = await req.json(); } catch { return NextResponse.json({ error: "Bad request" }, { status: 400 }); } const s = (k: string, max = 200) => String(body[k] ?? "").slice(0, max); const facilityId = s("facilityId", 40); const idOk = /^[a-z0-9]{20,40}$/.test(facilityId); try { switch (s("action", 40)) { case "switch": { const key = s("key", 40); if (key !== "signupsDisabled" && key !== "demoDisabled" && key !== "plansLive") return NextResponse.json({ error: "Unknown switch" }, { status: 400 }); await setSwitch(op, key, body.value === true, ip); return NextResponse.json({ ok: true }); } case "demo.reset": await resetDemoNow(op, ip); return NextResponse.json({ ok: true }); case "plan": { if (!idOk) return NextResponse.json({ error: "Bad request" }, { status: 400 }); const num = (k: string) => Number(body[k]); let a: PlanAct; switch (s("act", 20)) { case "set": a = { act: "set", plan: s("plan", 40), planNote: s("planNote", PLAN_NOTE_MAX + 1), grandfathered: typeof body.grandfathered === "boolean" ? body.grandfathered : undefined }; break; case "trial": a = { act: "trial", days: num("days") }; break; case "paid": a = { act: "paid", months: num("months") }; break; case "readonly": a = { act: "readonly", on: body.on === true }; break; case "free": a = { act: "free" }; break; default: return NextResponse.json({ error: "Unknown plan action" }, { status: 400 }); } await planControl(op, facilityId, a, ip); return NextResponse.json({ ok: true }); } case "facility.delete": { if (!idOk) return NextResponse.json({ error: "Bad request" }, { status: 400 }); const r = await deleteFacility(op, facilityId, s("confirm", 200), s("code", 20), ip); return NextResponse.json({ ok: true, deleted: r.name }); } default: return NextResponse.json({ error: "Unknown action" }, { status: 400 }); } } catch (e) { if (e instanceof ControlError) return NextResponse.json({ error: e.message }, { status: e.status }); throw e; } }