Files
pieced-portal/src/app/api/admin/tenants/[name]/delete/route.ts
admin ce70fe8480
Some checks failed
Build and Push / build (push) Failing after 38s
Phase1: Schema + skill event tracking
2026-05-23 23:45:04 +02:00

73 lines
2.4 KiB
TypeScript

import { NextResponse } from "next/server";
import { requirePlatformRole } from "@/lib/session";
import { getTenant, deleteTenant } from "@/lib/k8s";
import {
markTenantRequestDeletedByTenantName,
removeAllAssignmentsForTenant,
recordTenantDeleted,
} from "@/lib/db";
import { safeError } from "@/lib/errors";
/**
* POST /api/admin/tenants/[name]/delete
* Delete a PiecedTenant CR. The operator handles cleanup
* (namespace, vault, litellm team, etc.).
*
* Slice 6: also cascades the tenant_user_assignments rows so a
* future tenant with the same name (won't happen given UUID-suffix
* naming, but defense in depth) doesn't inherit stale assignments.
*
* Also marks the associated tenant_request as "deleted" so the
* customer can re-submit the onboarding wizard.
*/
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 tenant = await getTenant(name);
if (!tenant) {
return NextResponse.json({ error: "Tenant not found" }, { status: 404 });
}
try {
await deleteTenant(name);
// Best-effort DB cleanups. Both errors are logged but not surfaced —
// the K8s deletion has already started, and the row state is just
// for portal display.
await markTenantRequestDeletedByTenantName(name).catch((e) =>
console.error("Failed to mark tenant request deleted:", e)
);
await removeAllAssignmentsForTenant(name).catch((e) =>
console.error("Failed to clean up tenant assignments:", e)
);
// Billing — Phase 1: stamp deletion timestamp on the lifecycle
// row so the final invoice covering the deletion month can
// prorate correctly. Idempotent at the DB layer; a missing
// lifecycle row (e.g. pre-Phase-1 tenants that haven't been
// backfilled yet) makes this a no-op.
await recordTenantDeleted(name).catch((e) =>
console.error("billing: failed to stamp tenant deletion:", e)
);
return NextResponse.json({
message: "Tenant deletion initiated. The operator will clean up all resources.",
});
} catch (e: any) {
console.error("Failed to delete tenant:", e);
return NextResponse.json(
{ error: safeError(e, "Failed to delete tenant") },
{ status: 500 }
);
}
}