All the MD files via Database

This commit is contained in:
2026-04-11 21:14:09 +02:00
parent c67259ebe0
commit fdb56490dd
14 changed files with 1004 additions and 240 deletions

View File

@@ -1,19 +1,29 @@
import { NextResponse } from "next/server";
import { requirePlatformRole } from "@/lib/session";
import { getTenantRequestById, updateTenantRequestStatus, clearEncryptedSecrets } from "@/lib/db";
import {
getTenantRequestById,
updateTenantRequestStatus,
clearEncryptedSecrets,
} from "@/lib/db";
import { createTenant } from "@/lib/k8s";
import { sendApprovalEmail } from "@/lib/email";
import { decryptSecrets } from "@/lib/crypto";
import { writePackageSecrets } from "@/lib/openbao";
import {
getDefaultSoulMd,
getDefaultAgentsMd,
generateToolsMd,
} from "@/lib/workspace-defaults";
/**
* POST /api/admin/requests/[id]/approve
* Approve a tenant request:
* 1. Decrypt stored package secrets (if any)
* 2. Write each package's secrets to OpenBao at secret/data/tenants/{tenant-name}/{package}
* 3. Null the encrypted_secrets column
* 4. Create PiecedTenant CR
* 5. Update request status, notify customer.
* 1. Decrypt stored package secrets (if any)
* 2. Write each package's secrets to OpenBao at secret/data/tenants/{tenant-name}/{package}
* 3. Null the encrypted_secrets column
* 4. Build workspace files (SOUL.md, AGENTS.md, TOOLS.md)
* 5. Create PiecedTenant CR
* 6. Update request status, notify customer.
* Also supports re-approving a previously rejected request (clears admin notes).
*/
export async function POST(
@@ -38,7 +48,10 @@ export async function POST(
);
}
if (tenantRequest.status !== "pending" && tenantRequest.status !== "rejected") {
if (
tenantRequest.status !== "pending" &&
tenantRequest.status !== "rejected"
) {
return NextResponse.json(
{ error: `Request is already ${tenantRequest.status}` },
{ status: 400 }
@@ -48,47 +61,64 @@ export async function POST(
const isReApproval = tenantRequest.status === "rejected";
// Derive tenant name from company name: lowercase, alphanumeric + hyphens
const tenantName = tenantRequest.companyName
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
.slice(0, 63) || `tenant-${tenantRequest.id.slice(0, 8)}`;
const tenantName =
tenantRequest.companyName
.toLowerCase()
.replace(/[^a-z0-9]+/g, "-")
.replace(/^-|-$/g, "")
.slice(0, 63) || `tenant-${tenantRequest.id.slice(0, 8)}`;
try {
// Step 1: Decrypt and write package secrets to OpenBao (if collected during wizard)
if (tenantRequest.encryptedSecrets) {
const secrets = await decryptSecrets(tenantRequest.encryptedSecrets);
for (const [packageId, pkgSecrets] of Object.entries(secrets)) {
await writePackageSecrets(`tenant-${tenantName}`, packageId, pkgSecrets);
await writePackageSecrets(
`tenant-${tenantName}`,
packageId,
pkgSecrets
);
}
// Step 2: Null the encrypted column — secrets are now safely in OpenBao
await clearEncryptedSecrets(id);
}
// Step 3: Create the PiecedTenant CR
// Step 3: Build workspace files
const packages = tenantRequest.packages ?? [];
const soulMd =
tenantRequest.soulMd ||
(await getDefaultSoulMd(tenantRequest.companyName));
const agentsMd = tenantRequest.agentsMd || (await getDefaultAgentsMd());
const toolsMd = await generateToolsMd(packages);
const workspaceFiles: Record<string, string> = {
"SOUL.md": soulMd,
"AGENTS.md": agentsMd,
"TOOLS.md": toolsMd,
};
// Step 4: Create the PiecedTenant CR
await createTenant(
tenantName,
{
displayName: tenantRequest.companyName,
agentName: tenantRequest.agentName,
packages: tenantRequest.packages,
workspaceFiles: tenantRequest.soulMd
? { "SOUL.md": tenantRequest.soulMd }
: undefined,
packages,
workspaceFiles,
},
{
"pieced.ch/zitadel-org-id": tenantRequest.zitadelOrgId,
}
);
// Step 4: Update request status — clear admin notes on re-approval
// Step 5: Update request status — clear admin notes on re-approval
const updated = await updateTenantRequestStatus(id, "provisioning", {
adminNotes: isReApproval ? null : adminNotes,
tenantName,
clearAdminNotes: isReApproval,
});
// Step 5: Notify customer
// Step 6: Notify customer
await sendApprovalEmail(
tenantRequest.contactEmail,
tenantRequest.contactName,