import "dotenv/config"; import { hash } from "@node-rs/argon2"; import { PrismaPg } from "@prisma/adapter-pg"; import { PrismaClient } from "../src/generated/prisma/client.js"; import { Betriebsmodus, Rolle } from "../src/generated/prisma/enums.js"; /** * Grundbestand, ohne den die Anwendung nicht bedienbar waere: der Betriebsmodus, * die eigene Organisation und ein Administrator. * * Der Seed ist idempotent – zweimal hintereinander aufgerufen aendert der zweite * Lauf nichts. Er laeuft im Compose-Stand bewusst NICHT automatisch mit, sondern * ist ein dokumentierter Handgriff (siehe README). * * Die sieben Vorlagen, die Beispiel-Mitarbeiter und die Demo-Kunden kommen mit * #017 dazu. */ const url = process.env["DATABASE_URL"]; if (!url) { throw new Error("DATABASE_URL ist nicht gesetzt. Siehe .env.example."); } const prisma = new PrismaClient({ adapter: new PrismaPg({ connectionString: url }) }); /** Akzeptiert den im Auftrag dokumentierten Wert SERVICE als Alias. */ function modusLesen(): Betriebsmodus { const roh = (process.env["SEED_MODE"] ?? "INTERN").trim().toUpperCase(); if (roh === "DIENSTLEISTUNG" || roh === "SERVICE") return Betriebsmodus.DIENSTLEISTUNG; if (roh === "INTERN") return Betriebsmodus.INTERN; throw new Error(`SEED_MODE kennt nur INTERN und DIENSTLEISTUNG (Alias SERVICE), nicht "${roh}".`); } async function einstellungAnlegen(modus: Betriebsmodus): Promise { // Nur anlegen, nicht ueberschreiben: ein spaeterer Seed-Lauf darf einen im // Betrieb umgeschalteten Modus nicht zuruecksetzen. const vorhanden = await prisma.einstellung.findUnique({ where: { id: "global" } }); if (vorhanden) { console.log(` Einstellung: vorhanden, Modus ${vorhanden.modus} bleibt`); return; } await prisma.einstellung.create({ data: { id: "global", modus } }); console.log(` Einstellung: angelegt, Modus ${modus}`); } async function eigeneOrganisationAnlegen(): Promise { const name = process.env["SEED_ORG_NAME"]?.trim() || "Eigene Organisation"; const kuerzel = (process.env["SEED_ORG_KUERZEL"]?.trim() || "EIGEN").toUpperCase(); if (!/^[A-Z0-9]{2,6}$/.test(kuerzel)) { throw new Error(`SEED_ORG_KUERZEL muss 2 bis 6 Grossbuchstaben oder Ziffern sein, nicht "${kuerzel}".`); } // Bewusst nicht aktualisieren: wer Name oder Kuerzel im Betrieb geaendert hat, // soll das nicht beim naechsten Seed-Lauf zurueckbekommen. Geaenderte Werte in // der Umgebung bleiben deshalb wirkungslos, sobald die Zeile existiert. const vorhanden = await prisma.kunde.findFirst({ where: { istEigeneOrganisation: true } }); if (vorhanden) { console.log(` Eigene Organisation: vorhanden (${vorhanden.name})`); return vorhanden.id; } const kunde = await prisma.kunde.create({ data: { name, kuerzel, istEigeneOrganisation: true }, }); console.log(` Eigene Organisation: angelegt (${kunde.name}, ${kunde.kuerzel})`); return kunde.id; } async function adminAnlegen(): Promise { const email = process.env["SEED_ADMIN_EMAIL"]?.trim().toLowerCase(); const passwort = process.env["SEED_ADMIN_PASSWORD"]; if (!email || !passwort) { console.log(" Administrator: uebersprungen (SEED_ADMIN_EMAIL oder SEED_ADMIN_PASSWORD fehlt)"); return; } // Gesucht wird ueber die Adresse: eine geaenderte SEED_ADMIN_EMAIL legt also // einen zweiten Administrator an, statt den ersten umzubenennen. Das ist // gewollt – umbenennen waere eine Aenderung an einem Konto, das laengst benutzt // sein kann. const vorhanden = await prisma.benutzer.findUnique({ where: { email } }); if (vorhanden) { console.log(` Administrator: vorhanden (${email})`); return; } // Das Passwort wird nur als argon2-Hash gespeichert, nie im Klartext. await prisma.benutzer.create({ data: { email, name: "Administrator", rolle: Rolle.ADMIN, passwortHash: await hash(passwort), aktiv: true, }, }); console.log(` Administrator: angelegt (${email})`); } async function main(): Promise { const modus = modusLesen(); console.log("Seed laeuft:"); await einstellungAnlegen(modus); await eigeneOrganisationAnlegen(); await adminAnlegen(); console.log("Seed fertig."); } try { await main(); } finally { await prisma.$disconnect(); }