feat(PROJ-24): Mandanten-Logo Upload

- DB: logo_data (BYTEA) + logo_content_type Spalten in tenants-Tabelle
- Backend: SetLogo/GetLogo/DeleteLogo im tenantstore
- API: Logo-Endpunkte für superadmin (beliebiger Mandant) und
  domain_admin (eigener Mandant), max. 2 MB, PNG/JPEG/GIF/WebP/SVG
- Frontend: Logo-Dialog in Mandantentabelle (superadmin),
  Logo-Upload-Sektion im LDAP-Tab (domain_admin)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-03-20 03:15:34 +01:00
parent cd2781bdff
commit 30c6694dff
4 changed files with 595 additions and 11 deletions
+56 -2
View File
@@ -693,6 +693,7 @@ export interface Tenant {
user_count?: number;
ldap_enabled?: boolean;
ldap_url?: string;
has_logo?: boolean;
}
export interface TenantDomain {
@@ -710,8 +711,18 @@ export async function getTenantUsers(tenantId: number): Promise<User[]> {
return request<User[]>(`/api/tenants/${tenantId}/users`);
}
export async function createTenant(name: string, slug: string): Promise<Tenant> {
return request<Tenant>("/api/tenants", {
export interface TenantDefaultUser {
username: string;
password: string;
role: string;
}
export interface CreateTenantResponse extends Tenant {
default_users: TenantDefaultUser[];
}
export async function createTenant(name: string, slug: string): Promise<CreateTenantResponse> {
return request<CreateTenantResponse>("/api/tenants", {
method: "POST",
body: JSON.stringify({ name, slug }),
});
@@ -754,6 +765,49 @@ export async function removeTenantDomain(
});
}
// ── Tenant Logo ─────────────────────────────────────────────────────────────
export function getTenantLogoUrl(tenantId: number): string {
return `${API_BASE}/api/tenants/${tenantId}/logo`;
}
export async function uploadTenantLogo(tenantId: number, file: File): Promise<void> {
const form = new FormData();
form.append("logo", file);
const res = await fetch(`${API_BASE}/api/tenants/${tenantId}/logo`, {
method: "POST",
body: form,
credentials: "include",
});
if (!res.ok) {
const body = await res.text();
throw new Error(body || `Upload failed: ${res.status}`);
}
}
export async function deleteTenantLogo(tenantId: number): Promise<void> {
await request<void>(`/api/tenants/${tenantId}/logo`, { method: "DELETE" });
}
// domain_admin: own tenant logo
export async function uploadMyTenantLogo(file: File): Promise<void> {
const form = new FormData();
form.append("logo", file);
const res = await fetch(`${API_BASE}/api/tenant/logo`, {
method: "POST",
body: form,
credentials: "include",
});
if (!res.ok) {
const body = await res.text();
throw new Error(body || `Upload failed: ${res.status}`);
}
}
export async function deleteMyTenantLogo(): Promise<void> {
await request<void>("/api/tenant/logo", { method: "DELETE" });
}
// ── PROJ-23: Pro-Mandant LDAP (tenant_ldap) ──────────────────────────────
export interface TenantLDAPConfig extends LDAPConfig {