All the UI fixes for now

This commit is contained in:
2026-04-11 17:21:52 +02:00
parent 1bd51ecb5d
commit c67259ebe0
15 changed files with 565 additions and 112 deletions

View File

@@ -34,27 +34,31 @@ function getPool(): Pool {
// ---------------------------------------------------------------------------
const MIGRATION_SQL = `
CREATE TABLE IF NOT EXISTS tenant_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
zitadel_org_id TEXT NOT NULL UNIQUE,
zitadel_user_id TEXT NOT NULL,
company_name TEXT NOT NULL,
contact_name TEXT NOT NULL,
contact_email TEXT NOT NULL,
agent_name TEXT NOT NULL DEFAULT 'Assistant',
soul_md TEXT,
packages TEXT[] DEFAULT '{}',
billing_address JSONB DEFAULT '{}',
billing_notes TEXT,
status TEXT NOT NULL DEFAULT 'pending',
admin_notes TEXT,
tenant_name TEXT,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE TABLE IF NOT EXISTS tenant_requests (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
zitadel_org_id TEXT NOT NULL UNIQUE,
zitadel_user_id TEXT NOT NULL,
company_name TEXT NOT NULL,
contact_name TEXT NOT NULL,
contact_email TEXT NOT NULL,
agent_name TEXT NOT NULL DEFAULT 'Assistant',
soul_md TEXT,
packages TEXT[] DEFAULT '{}',
billing_address JSONB DEFAULT '{}',
billing_notes TEXT,
status TEXT NOT NULL DEFAULT 'pending',
admin_notes TEXT,
tenant_name TEXT,
encrypted_secrets BYTEA,
created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
CREATE INDEX IF NOT EXISTS idx_tenant_requests_status ON tenant_requests(status);
CREATE INDEX IF NOT EXISTS idx_tenant_requests_org_id ON tenant_requests(zitadel_org_id);
CREATE INDEX IF NOT EXISTS idx_tenant_requests_status ON tenant_requests(status);
CREATE INDEX IF NOT EXISTS idx_tenant_requests_org_id ON tenant_requests(zitadel_org_id);
-- Idempotent column add for existing databases
ALTER TABLE tenant_requests ADD COLUMN IF NOT EXISTS encrypted_secrets BYTEA;
`;
let migrated = false;
@@ -70,14 +74,17 @@ export async function ensureSchema(): Promise<void> {
// ---------------------------------------------------------------------------
export async function createTenantRequest(
params: Omit<TenantRequest, "id" | "status" | "createdAt" | "updatedAt">
params: Omit<TenantRequest, "id" | "status" | "createdAt" | "updatedAt"> & {
encryptedSecrets?: Buffer;
}
): Promise<TenantRequest> {
await ensureSchema();
const result = await getPool().query<TenantRequest>(
`INSERT INTO tenant_requests
(zitadel_org_id, zitadel_user_id, company_name, contact_name,
contact_email, agent_name, soul_md, packages, billing_address, billing_notes)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
(zitadel_org_id, zitadel_user_id, company_name, contact_name,
contact_email, agent_name, soul_md, packages, billing_address,
billing_notes, encrypted_secrets)
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
RETURNING *`,
[
params.zitadelOrgId,
@@ -90,6 +97,7 @@ export async function createTenantRequest(
params.packages,
JSON.stringify(params.billingAddress),
params.billingNotes,
params.encryptedSecrets ?? null,
]
);
return mapRow(result.rows[0]);
@@ -154,6 +162,41 @@ export async function updateTenantRequestStatus(
return mapRow(result.rows[0]);
}
/**
* Clear the encrypted_secrets column after secrets have been written to OpenBao.
* Called during admin approval after successful vault writes.
*/
export async function clearEncryptedSecrets(requestId: string): Promise<void> {
await ensureSchema();
await getPool().query(
"UPDATE tenant_requests SET encrypted_secrets = NULL, updated_at = now() WHERE id = $1",
[requestId]
);
}
/**
* Mark a tenant request as "deleted" when the associated tenant CR is deleted.
* This allows the customer to re-submit the onboarding wizard.
*/
export async function markTenantRequestDeletedByTenantName(
tenantName: string
): Promise<void> {
await ensureSchema();
await getPool().query(
"UPDATE tenant_requests SET status = 'deleted', tenant_name = NULL, updated_at = now() WHERE tenant_name = $1",
[tenantName]
);
}
/**
* Delete a tenant request row entirely. Used when a customer re-submits
* after their previous tenant was deleted by admin.
*/
export async function deleteTenantRequest(id: string): Promise<void> {
await ensureSchema();
await getPool().query("DELETE FROM tenant_requests WHERE id = $1", [id]);
}
/**
* Sync provisioning statuses: for all requests with status "provisioning",
* check if the PiecedTenant CR has reached "Ready" and update to "active".
@@ -205,6 +248,7 @@ function mapRow(row: any): TenantRequest {
status: row.status as TenantRequestStatus,
adminNotes: row.admin_notes,
tenantName: row.tenant_name,
encryptedSecrets: row.encrypted_secrets ?? null,
createdAt: row.created_at?.toISOString?.() ?? row.created_at,
updatedAt: row.updated_at?.toISOString?.() ?? row.updated_at,
};