TEN-05: backend-api + dev-server + next.js tenant-verwaltungsoberflaeche

This commit is contained in:
sysops
2026-08-27 23:58:04 +02:00
parent 962ef5a27a
commit 11ed3e3790
9 changed files with 990 additions and 0 deletions
+106
View File
@@ -0,0 +1,106 @@
// Duenner Client fuer das TEN-05-Backend-API (internal/tenantadmin) — keine
// eigene Provisioning-/Lifecycle-/Validierungslogik ausser der Pflichtfeld-
// Vorpruefung im Formular (Akzeptanzkriterium 2), die zusaetzlich serverseitig
// durchgesetzt wird.
export type TenantListItem = {
id: string;
slug: string;
name: string;
status: string;
};
export type Settings = {
DisplayName: string;
LogoURL: string;
ColorScheme: string;
Timezone: string;
Language: string;
Version: number;
};
export type TenantDetail = {
id: string;
slug: string;
name: string;
status: string;
settings: Settings;
};
function apiBase(): string {
const base = process.env.NEXT_PUBLIC_TENANTADMIN_API_URL;
if (!base) {
throw new Error(
"NEXT_PUBLIC_TENANTADMIN_API_URL ist nicht gesetzt (Umgebungsvariable erforderlich)"
);
}
return base;
}
async function handle<T>(res: Response): Promise<T> {
if (!res.ok) {
const body = await res.json().catch(() => ({}));
throw new Error(body.error || `Anfrage fehlgeschlagen (${res.status})`);
}
return res.json();
}
export async function fetchTenants(
superadminId: string,
search: string,
status: string
): Promise<TenantListItem[]> {
const params = new URLSearchParams({ superadmin: superadminId, search, status });
const res = await fetch(`${apiBase()}/admin/tenants?${params}`, { cache: "no-store" });
return handle<TenantListItem[]>(res);
}
export async function fetchTenantDetail(
superadminId: string,
slug: string
): Promise<TenantDetail> {
const params = new URLSearchParams({ superadmin: superadminId, slug });
const res = await fetch(`${apiBase()}/admin/tenants/detail?${params}`, { cache: "no-store" });
return handle<TenantDetail>(res);
}
export async function updateSettings(
superadminId: string,
slug: string,
displayName: string,
colorScheme: string,
timezone: string,
language: string
): Promise<Settings> {
const res = await fetch(`${apiBase()}/admin/tenants/settings`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
superadmin: superadminId,
slug,
display_name: displayName,
color_scheme: colorScheme,
timezone,
language,
}),
});
return handle<Settings>(res);
}
export type LifecycleAction =
| "suspend"
| "reactivate"
| "schedule_deletion"
| "cancel_deletion";
export async function performLifecycleAction(
superadminId: string,
slug: string,
action: LifecycleAction
): Promise<void> {
const res = await fetch(`${apiBase()}/admin/tenants/lifecycle`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ superadmin: superadminId, slug, action }),
});
await handle<unknown>(res);
}