138 lines
6.3 KiB
TypeScript
138 lines
6.3 KiB
TypeScript
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<string, string> = {};
|
|
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);
|
|
});
|