import "dotenv/config"; import { PrismaClient } from "@prisma/client"; import { PrismaPg } from "@prisma/adapter-pg"; import { hashPassword } from "../src/server/password"; import { PERMISSIONS, type RoleKey } from "../src/server/rbac"; import { provisionTenant } from "../src/server/provision"; /** * Craftvia-Demo-Seed (Fundament): Plattform-Admin + Mandant `demo` mit je einem Nutzer * pro Standardrolle + zweiter Mandant `demo2` (Isolations-/Tenant-Switch-Tests) + * Multi-Membership-Fixture. Fachliche Demo-Daten (Teams, Kunden, Objekte, Aufträge, Berichte, * Notdienst) kommen aus scripts/lib/demo-seed.ts — nur mit SEED_DEMO=true oder außerhalb von * Production (SEED_DEMO=false schaltet sie ab). * * Idempotent (upserts) — beliebig oft ausführbar. Passwort: SEED_PASSWORD oder Default. */ const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: process.env.DATABASE_URL }), }); const DEMO_PASSWORD = process.env.SEED_PASSWORD || "Demo1234!"; async function ensureMember(tenantId: string, u: { email: string; name: string; roles: RoleKey[] }, passwordHash: string) { // Globale Identity (Anmeldung) + Mitgliedschaft (User) im Mandanten. const identity = await prisma.identity.upsert({ where: { email: u.email }, update: {}, create: { email: u.email, passwordHash }, }); const user = await prisma.user.upsert({ where: { tenantId_email: { tenantId, email: u.email } }, update: { identityId: identity.id, name: u.name }, create: { tenantId, identityId: identity.id, email: u.email, name: u.name }, }); const roles = await prisma.role.findMany({ where: { tenantId, key: { in: u.roles } } }); for (const r of roles) { await prisma.userRole.upsert({ where: { userId_roleId: { userId: user.id, roleId: r.id } }, update: {}, create: { userId: user.id, roleId: r.id }, }); } return user; } async function main() { const passwordHash = await hashPassword(DEMO_PASSWORD); // 1. Mandant „demo" (provisioniert Permissions, Rollen, Einstellungen, Module, Admin) const demo = await provisionTenant(prisma, { name: "Musterbau Haustechnik GmbH", slug: "demo", short: "Musterbau", sector: "Sanitär, Heizung, Klima", admin: { email: "admin@demo.example", name: "Anna Admin", password: DEMO_PASSWORD }, }); await prisma.tenantSettings.update({ where: { tenantId: demo.id }, data: { address: "Hafenstraße 12, 20457 Hamburg", phone: "+49 40 123456-0", email: "info@musterbau.example", }, }); const demoUsers: { email: string; name: string; roles: RoleKey[] }[] = [ { email: "admin@demo.example", name: "Anna Admin", roles: ["tenant-admin"] }, { email: "backoffice@demo.example", name: "Bernd Backoffice", roles: ["backoffice"] }, { email: "teamleiter@demo.example", name: "Tina Teamleiter", roles: ["team-lead"] }, { email: "monteur@demo.example", name: "Max Monteur", roles: ["technician"] }, // L10a Demo-Teams: Team Nord (Tina + Max + Nora), Team Süd (Sven + Paul) { email: "teamleiter2@demo.example", name: "Sven Südfeld", roles: ["team-lead"] }, { email: "monteur2@demo.example", name: "Nora Nordmann", roles: ["technician"] }, { email: "monteur3@demo.example", name: "Paul Petersen", roles: ["technician"] }, ]; const members: Record = {}; for (const u of demoUsers) members[u.email] = (await ensureMember(demo.id, u, passwordHash)).id; console.log(`✔ Mandant "demo" (${PERMISSIONS.length} Permissions) mit ${demoUsers.length} Nutzern (Passwort: ${DEMO_PASSWORD})`); // 2. Zweiter Mandant „demo2" — Grundlage für Isolations- und Tenant-Switch-Tests. const demo2 = await provisionTenant(prisma, { name: "Nordlicht Elektro GmbH", slug: "demo2", short: "Nordlicht", sector: "Elektro", admin: { email: "admin2@demo.example", name: "Zoe Zweitadmin", password: DEMO_PASSWORD }, }); console.log(`✔ Mandant "demo2" mit Admin admin2@demo.example`); // 3. Multi-Membership-Fixture: eine Identity in ZWEI Mandanten mit je anderer Rolle. const multi = { email: "multi@demo.example", name: "Mika Multi" }; await ensureMember(demo.id, { ...multi, roles: ["technician"] }, passwordHash); await ensureMember(demo2.id, { ...multi, roles: ["tenant-admin"] }, passwordHash); console.log(`✔ Multi-Membership-Fixture multi@demo.example (demo: Monteur, demo2: Mandantenadministrator)`); // 4. Plattform-Admin (getrennter Store, Login unter /platform/login). await prisma.platformAdmin.upsert({ where: { email: "platform@demo.example" }, update: { name: "Paula Plattform", status: "ACTIVE" }, create: { email: "platform@demo.example", name: "Paula Plattform", passwordHash, status: "ACTIVE" }, }); console.log(`✔ Plattform-Admin platform@demo.example (/platform/login)`); // 5. L10a: fachliche Demo-Daten (Teams, Kunden, Objekte, 20 Aufträge, Berichte, Notdienst). // Nur mit SEED_DEMO=true oder außerhalb von Production; SEED_DEMO=false schaltet sie ab. const demoData = process.env.SEED_DEMO === "true" || (process.env.NODE_ENV !== "production" && process.env.SEED_DEMO !== "false"); if (demoData) { const { seedDemoTenant, seedDemo2Tenant, closeDemoSeed } = await import("../scripts/lib/demo-seed"); const res = await seedDemoTenant(demo.id, { admin: members["admin@demo.example"], backoffice: members["backoffice@demo.example"], lead: members["teamleiter@demo.example"], lead2: members["teamleiter2@demo.example"], tech: members["monteur@demo.example"], tech2: members["monteur2@demo.example"], tech3: members["monteur3@demo.example"], }); const admin2 = await prisma.user.findUniqueOrThrow({ where: { tenantId_email: { tenantId: demo2.id, email: "admin2@demo.example" } } }); await seedDemo2Tenant(demo2.id, admin2.id); await closeDemoSeed(); console.log(`✔ Demo-Fachdaten: ${res.orders} Demo-Aufträge in "demo" (${res.created} neu angelegt), 1 Auftrag in "demo2"`); } else { console.log("• Demo-Fachdaten übersprungen (SEED_DEMO=false bzw. Production ohne SEED_DEMO=true)"); } } main() .then(async () => { await prisma.$disconnect(); // Services may leave queue/redis handles open (events, image jobs) — end the seed explicitly. process.exit(0); }) .catch(async (e) => { console.error(e); await prisma.$disconnect(); process.exit(1); });