/** * Idempotenter Permissions+Rollen-Sync für den Container-Start. * * Hintergrund: seed.ts läuft nur auf leeren DBs (USER_COUNT=0). Wer das * System schon installiert hat, bekommt nachträglich hinzugefügte * Permissions oder neue Rollenzuordnungen NICHT — die DSGVO-Rolle kann * dann z.B. ohne audit:read landen, obwohl Settings.tsx das voraussetzt. * * Dieses Skript synchronisiert ausschließlich: * - Permission-Katalog (resource/action-Paare aus dem Code) * - Roll-Zuordnungen (Admin, Developer, DSGVO, Mitarbeiter, * Mitarbeiter (Nur-Lesen), Kunde) * * KEINE Stammdaten, KEINE User, KEINE Verträge — das Skript ist auf * laufenden Prod-DBs sicher. */ import { PrismaClient } from '@prisma/client'; const prisma = new PrismaClient(); const RESOURCE_PERMISSIONS: Record = { customers: ['create', 'read', 'update', 'delete'], contracts: ['create', 'read', 'update', 'delete'], users: ['create', 'read', 'update', 'delete'], platforms: ['create', 'read', 'update', 'delete'], providers: ['create', 'read', 'update', 'delete'], tariffs: ['create', 'read', 'update', 'delete'], 'cancellation-periods': ['create', 'read', 'update', 'delete'], 'contract-durations': ['create', 'read', 'update', 'delete'], 'contract-categories': ['create', 'read', 'update', 'delete'], 'email-providers': ['create', 'read', 'update', 'delete'], settings: ['read', 'update'], developer: ['access'], emails: ['delete'], audit: ['read', 'export', 'admin'], gdpr: ['export', 'delete', 'admin'], }; async function syncRolePermissions(roleId: number, permissionIds: number[]) { const existing = await prisma.rolePermission.findMany({ where: { roleId }, select: { permissionId: true }, }); const existingIds = new Set(existing.map((e) => e.permissionId)); const targetIds = new Set(permissionIds); const missing = permissionIds.filter((id) => !existingIds.has(id)); if (missing.length > 0) { await prisma.rolePermission.createMany({ data: missing.map((permissionId) => ({ roleId, permissionId })), skipDuplicates: true, }); console.log(` → +${missing.length} Permissions an Rolle #${roleId}`); } const excess = existing .filter((e) => !targetIds.has(e.permissionId)) .map((e) => e.permissionId); if (excess.length > 0) { await prisma.rolePermission.deleteMany({ where: { roleId, permissionId: { in: excess } }, }); console.log(` → -${excess.length} Permissions von Rolle #${roleId}`); } } async function main() { console.log('[sync-roles] Permissions-Katalog upserten…'); for (const [resource, actions] of Object.entries(RESOURCE_PERMISSIONS)) { for (const action of actions) { await prisma.permission.upsert({ where: { resource_action: { resource, action } }, update: {}, create: { resource, action }, }); } } const allPermissions = await prisma.permission.findMany(); console.log(`[sync-roles] ${allPermissions.length} Permissions vorhanden`); // Admin: alles AUSSER developer:access und audit/gdpr (DSGVO + Developer // sind separate hidden roles, über Checkboxen zugewiesen) const adminPermIds = allPermissions .filter( (p) => !(p.resource === 'developer' && p.action === 'access') && p.resource !== 'audit' && p.resource !== 'gdpr' ) .map((p) => p.id); // Developer: alles const developerPermIds = allPermissions.map((p) => p.id); // DSGVO: audit + gdpr komplett const gdprPermIds = allPermissions .filter((p) => p.resource === 'audit' || p.resource === 'gdpr') .map((p) => p.id); // Mitarbeiter: customers + contracts + read auf Stammdaten const employeePermIds = allPermissions .filter( (p) => p.resource === 'customers' || p.resource === 'contracts' || (p.action === 'read' && [ 'platforms', 'providers', 'tariffs', 'cancellation-periods', 'contract-durations', 'contract-categories', ].includes(p.resource)) ) .map((p) => p.id); // Read-only Mitarbeiter + Kunde: nur read auf Haupt-Entities + Stammdaten const readOnlyResources = [ 'customers', 'contracts', 'platforms', 'providers', 'tariffs', 'cancellation-periods', 'contract-durations', 'contract-categories', ]; const readOnlyPermIds = allPermissions .filter((p) => p.action === 'read' && readOnlyResources.includes(p.resource)) .map((p) => p.id); const rolesSpec: Array<{ name: string; description: string; permIds: number[] }> = [ { name: 'Admin', description: 'Voller Zugriff auf alle Funktionen', permIds: adminPermIds }, { name: 'Developer', description: 'Voller Zugriff inkl. Entwickler-Tools', permIds: developerPermIds }, { name: 'DSGVO', description: 'DSGVO-Zugriff: Audit-Logs und Datenschutz-Verwaltung', permIds: gdprPermIds }, { name: 'Mitarbeiter', description: 'Kann Kunden und Verträge verwalten', permIds: employeePermIds }, { name: 'Mitarbeiter (Nur-Lesen)', description: 'Kann nur lesen, keine Änderungen', permIds: readOnlyPermIds }, { name: 'Kunde', description: 'Kann nur eigene Daten lesen', permIds: readOnlyPermIds }, ]; for (const r of rolesSpec) { const role = await prisma.role.upsert({ where: { name: r.name }, update: { description: r.description }, create: { name: r.name, description: r.description }, }); await syncRolePermissions(role.id, r.permIds); } console.log('[sync-roles] fertig.'); } main() .catch((e) => { console.error('[sync-roles] Fehler:', e); process.exit(1); }) .finally(async () => { await prisma.$disconnect(); });