import { NextResponse } from "next/server"; import { runMonthlyIssuance, verifyCronBearer } from "@/lib/cron"; import { safeError } from "@/lib/errors"; /** * POST /api/cron/issue-monthly * * Machine entry point for the monthly issuance sweep. Authentication * is the shared bearer token in CRON_BEARER_TOKEN, injected from * OpenBao via the portal-cron K8s Secret. The K8s CronJob sends: * * curl -X POST -H "Authorization: Bearer $CRON_BEARER_TOKEN" \ * https://app.pieced.ch/api/cron/issue-monthly * * The sweep targets the calendar month that ended just before * "now" in Europe/Zurich. Running it on June 1st at 00:30 Swiss * time bills May; running it on July 5th bills June; etc. The * uniqueness constraint on (org, period_start) makes re-runs * harmless — already-issued orgs are counted as skipped. * * Returns the summary {success, failure, skipped} JSON. The * CronJob doesn't look at the response body (just the status * code) but having a useful one helps debugging via curl. */ export const dynamic = "force-dynamic"; export async function POST(request: Request) { if (!verifyCronBearer(request.headers.get("authorization"))) { return NextResponse.json({ error: "Unauthorized" }, { status: 401 }); } try { const { runId, summary } = await runMonthlyIssuance({ triggeredBy: "cron", }); return NextResponse.json({ runId, ...summary }); } catch (e) { return NextResponse.json( { error: safeError(e, "Issuance sweep failed.") }, { status: 500 } ); } }