import { NextResponse } from "next/server"; import { requirePlatformRole } from "@/lib/session"; import { getTenant, patchTenantSpec } from "@/lib/k8s"; import { recordSuspensionEvent } from "@/lib/db"; import { safeError } from "@/lib/errors"; /** * POST /api/admin/tenants/[name]/suspend * Toggle suspend on a PiecedTenant CR. * Body: { suspend: true } or { suspend: false } */ export async function POST( request: Request, { params }: { params: Promise<{ name: string }> } ) { try { await requirePlatformRole(); } catch { return NextResponse.json({ error: "Forbidden" }, { status: 403 }); } const { name } = await params; const body = await request.json().catch(() => ({})); const suspend = body.suspend === true; const tenant = await getTenant(name); if (!tenant) { return NextResponse.json({ error: "Tenant not found" }, { status: 404 }); } try { const updated = await patchTenantSpec(name, { suspend }); // Billing — Phase 1: record the transition. Mirrors the same // hook in the customer-side suspend route so admin actions // also produce events. Best-effort; logging failures don't // block the response. try { const orgId = tenant.metadata.labels?.["pieced.ch/zitadel-org-id"] ?? null; if (orgId) { await recordSuspensionEvent( name, orgId, suspend ? "suspended" : "resumed" ); } else { console.warn( `billing: tenant ${name} has no zitadel-org-id label; suspension event not recorded` ); } } catch (e) { console.error( `billing: failed to record suspension event for ${name}:`, e ); } return NextResponse.json({ message: suspend ? "Tenant suspended." : "Tenant resumed.", tenant: updated, }); } catch (e: any) { console.error("Failed to update tenant suspend state:", e); return NextResponse.json( { error: safeError(e, "Failed to update tenant") }, { status: 500 } ); } }