- ISMS-Routen, Actions, Server-/Lib-Code, Komponenten, Prisma-Modelle, Seeds, Importer, Skripte und ISMS-Tests entfernt (Fundament bleibt: Auth, Identity, MFA/WebAuthn, RBAC, Audit, Mail, Storage, Backup/DSGVO, Plattform-Admin) - Schema auf Fundament-Modelle reduziert; TenantSettings generisch (+phone/email) - TENANT_MODELS (db.ts, backup/topology.ts) und PII-Felder ausgedünnt - RBAC: Rollen tenant-admin/backoffice/team-lead/technician + Craftvia-Permissions - Modul-Katalog (customers, sites, teams, work_orders, imports, field, reports, emergency, documents, notifications, lotse) + Navigation aus src/lib/nav.ts - Modul-Routen mit requireModule-Layout und Platzhalterseite - Message-Katalog je Namespace (messages/<locale>/<namespace>.json), fs-Loader - check-module-guards: Modul-Key aus src/server/actions/<moduleKey>/ - Provisionierung, Admin-Konsole, Einstellungen, Files-Route, Mail entkoppelt - Seed minimal (demo/demo2, Nutzer je Rolle); Fundament-Tests auf Role/ NotificationPreference-Fixtures umgestellt Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
102 lines
3.8 KiB
TypeScript
102 lines
3.8 KiB
TypeScript
import type { PrismaClient } from "@prisma/client";
|
|
import { hashPassword } from "@/server/password";
|
|
import { PERMISSIONS, ROLE_DEFS } from "@/server/rbac";
|
|
import { MODULES } from "@/lib/modules";
|
|
|
|
export interface ProvisionOpts {
|
|
name: string;
|
|
slug: string;
|
|
short?: string;
|
|
sector?: string;
|
|
admin: { email: string; name: string; password: string };
|
|
actorId?: string | null;
|
|
}
|
|
|
|
/**
|
|
* Legt einen Mandanten idempotent an bzw. aktualisiert ihn: globaler Permission-Katalog,
|
|
* Standard-Rollen (ROLE_DEFS) inkl. Rechte, erster Mandantenadministrator (Identity +
|
|
* Mitgliedschaft), Mandanten-Einstellungen, alle Module aktiv, Audit-Eintrag.
|
|
*
|
|
* Andockpunkt Fachmodule: mandantenspezifische Stammdaten-Defaults (z. B. Auftragsarten,
|
|
* Checklisten-Vorlagen) werden künftig hier nach Schritt 6 angelegt.
|
|
*/
|
|
export async function provisionTenant(prisma: PrismaClient, opts: ProvisionOpts) {
|
|
// 1. Globaler Permission-Katalog
|
|
for (const key of PERMISSIONS) {
|
|
await prisma.permission.upsert({ where: { key }, update: {}, create: { key } });
|
|
}
|
|
|
|
// 2. Mandant
|
|
const tenant = await prisma.tenant.upsert({
|
|
where: { slug: opts.slug },
|
|
update: { name: opts.name, short: opts.short ?? null, sector: opts.sector ?? null },
|
|
create: { name: opts.name, slug: opts.slug, short: opts.short ?? null, sector: opts.sector ?? null },
|
|
});
|
|
|
|
// 3. Rollen + Permissions
|
|
for (const [key, def] of Object.entries(ROLE_DEFS)) {
|
|
const role = await prisma.role.upsert({
|
|
where: { tenantId_key: { tenantId: tenant.id, key } },
|
|
update: { name: def.name },
|
|
create: { tenantId: tenant.id, key, name: def.name },
|
|
});
|
|
const perms = await prisma.permission.findMany({ where: { key: { in: [...def.permissions] } } });
|
|
for (const p of perms) {
|
|
await prisma.rolePermission.upsert({
|
|
where: { roleId_permissionId: { roleId: role.id, permissionId: p.id } },
|
|
update: {},
|
|
create: { roleId: role.id, permissionId: p.id },
|
|
});
|
|
}
|
|
}
|
|
|
|
// 4. Erster Mandantenadministrator: globale Identity (Anmeldung) + Mitgliedschaft.
|
|
// Idempotent: eine bestehende Identity/ihr Passwort wird NICHT überschrieben.
|
|
const passwordHash = await hashPassword(opts.admin.password);
|
|
const identity = await prisma.identity.upsert({
|
|
where: { email: opts.admin.email },
|
|
update: {},
|
|
create: { email: opts.admin.email, passwordHash },
|
|
});
|
|
const admin = await prisma.user.upsert({
|
|
where: { tenantId_email: { tenantId: tenant.id, email: opts.admin.email } },
|
|
update: { name: opts.admin.name, identityId: identity.id },
|
|
create: { tenantId: tenant.id, identityId: identity.id, email: opts.admin.email, name: opts.admin.name },
|
|
});
|
|
const adminRole = await prisma.role.findUnique({ where: { tenantId_key: { tenantId: tenant.id, key: "tenant-admin" } } });
|
|
if (adminRole) {
|
|
await prisma.userRole.upsert({
|
|
where: { userId_roleId: { userId: admin.id, roleId: adminRole.id } },
|
|
update: {},
|
|
create: { userId: admin.id, roleId: adminRole.id },
|
|
});
|
|
}
|
|
|
|
// 5. Mandanten-Einstellungen (Unternehmensdaten)
|
|
await prisma.tenantSettings.upsert({
|
|
where: { tenantId: tenant.id },
|
|
update: {},
|
|
create: {
|
|
tenantId: tenant.id,
|
|
orgName: opts.name,
|
|
orgShort: opts.short ?? null,
|
|
sector: opts.sector ?? null,
|
|
},
|
|
});
|
|
|
|
// 6. Alle Module aktivieren
|
|
for (const m of MODULES) {
|
|
await prisma.tenantModule.upsert({
|
|
where: { tenantId_moduleKey: { tenantId: tenant.id, moduleKey: m.key } },
|
|
update: {},
|
|
create: { tenantId: tenant.id, moduleKey: m.key, enabled: true },
|
|
});
|
|
}
|
|
|
|
await prisma.auditLog.create({
|
|
data: { tenantId: tenant.id, scope: "platform", actorId: opts.actorId ?? null, action: "provision", entity: "tenant", entityId: tenant.id, after: { name: opts.name } },
|
|
});
|
|
|
|
return tenant;
|
|
}
|