69 lines
2.0 KiB
TypeScript
69 lines
2.0 KiB
TypeScript
import { NextResponse } from "next/server";
|
|
import { z } from "zod";
|
|
import { getSessionUser, requirePlatformRole } from "@/lib/session";
|
|
import { runMonthlyIssuance } from "@/lib/cron";
|
|
import { safeError } from "@/lib/errors";
|
|
|
|
/**
|
|
* POST /api/admin/cron/issue-monthly
|
|
*
|
|
* Admin-side manual trigger for the issuance sweep — same business
|
|
* logic as /api/cron/issue-monthly, different auth (session-based
|
|
* platform role check) and the option to override the target
|
|
* year/month from the request body.
|
|
*
|
|
* Body (all optional):
|
|
* { year?: number, month?: number }
|
|
*
|
|
* Default target is the previous local month — matching what the
|
|
* automated cron would do. Override is useful for catching up after
|
|
* a failed run or re-billing a past month after fixing data.
|
|
*/
|
|
const bodySchema = z.object({
|
|
year: z.number().int().min(2000).max(3000).optional(),
|
|
month: z.number().int().min(1).max(12).optional(),
|
|
});
|
|
|
|
export async function POST(request: Request) {
|
|
let user;
|
|
try {
|
|
await requirePlatformRole();
|
|
user = await getSessionUser();
|
|
} catch {
|
|
return NextResponse.json({ error: "Forbidden" }, { status: 403 });
|
|
}
|
|
if (!user) {
|
|
return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
|
|
}
|
|
const body = await request.json().catch(() => ({}));
|
|
const parsed = bodySchema.safeParse(body);
|
|
if (!parsed.success) {
|
|
return NextResponse.json(
|
|
{ error: "Invalid request", details: parsed.error.flatten() },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
if (
|
|
(parsed.data.year && !parsed.data.month) ||
|
|
(parsed.data.month && !parsed.data.year)
|
|
) {
|
|
return NextResponse.json(
|
|
{ error: "year and month must both be provided, or neither" },
|
|
{ status: 400 }
|
|
);
|
|
}
|
|
try {
|
|
const { runId, summary } = await runMonthlyIssuance({
|
|
triggeredBy: user.id,
|
|
year: parsed.data.year,
|
|
month: parsed.data.month,
|
|
});
|
|
return NextResponse.json({ runId, ...summary });
|
|
} catch (e) {
|
|
return NextResponse.json(
|
|
{ error: safeError(e, "Issuance sweep failed.") },
|
|
{ status: 500 }
|
|
);
|
|
}
|
|
}
|