Files
Laufzettel/prisma/seed-daten/grundbestand.ts
T
florian.wilde 9bd198a531
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(seed): sieben Vorlagen, Beispielbestand und Demo-Kunden (#33)
2026-09-26 03:26:58 +00:00

146 lines
6.0 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { hash } from "@node-rs/argon2";
import { Betriebsmodus, Rolle } from "../../src/generated/prisma/enums.js";
import type { PrismaClient } from "../../src/generated/prisma/client.js";
/**
* Der Grundbestand, ohne den die Anwendung nicht bedienbar waere (#006, #017):
* Betriebsmodus samt Namensregeln, die eigene Organisation und ein Administrator.
*
* Steht neben dem Seed-Skript und nicht darin, damit Tests diese Schritte einzeln
* aufrufen koennen. `prisma/seed.ts` fuehrt beim Import den ganzen Seed aus — ein
* Test, der es importierte, schriebe in die Datenbank aus `DATABASE_URL`.
*
* Die Funktionen nehmen den Client als Argument. Sie schreiben nur, was fehlt.
*/
/** Akzeptiert den im Auftrag dokumentierten Wert SERVICE als Alias. */
export 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}".`);
}
/**
* Namensregeln, die zu den Beispieldaten passen.
*
* `{v}.{nachname}@firma.example` ergibt fuer Maria Huber genau
* `m.huber@firma.example` und `mhuber` — dieselben Werte, die auch im
* Beispielbestand stehen. Ohne diese Regeln antwortet „Namen aus den Regeln
* vorschlagen“ im Onboarding mit „Für diesen Kunden sind keine Namensregeln
* hinterlegt“, und das Abnahmekriterium „Vorname und Nachname eingeben → UPN,
* sam und Mail werden vorgeschlagen“ liesse sich nach einem frischen Seed nicht
* vorfuehren (Reviewbefund zu #017).
*
* Die Hostname-Regel haengt am Modus: im Modus DIENSTLEISTUNG traegt der Name
* das Kundenkuerzel, wie der Auftrag es vorgibt (`{kundenkuerzel}-PC-{nnn}`).
*/
export function namensregelnFuerModus(modus: Betriebsmodus): Record<string, string> {
return {
upn: "{v}.{nachname}@firma.example",
mail: "{v}.{nachname}@firma.example",
sam: "{v}{nachname}",
hostname: modus === Betriebsmodus.DIENSTLEISTUNG ? "{kundenkuerzel}-PC-{nnn}" : "PC-{nnn}",
};
}
/** Ziel-OU fuer deaktivierte Konten – Vorbelegung von `{{ou_gesperrt}}` im Offboarding. */
export const GESPERRTE_OU = "OU=Gesperrt,OU=Benutzer,DC=firma,DC=local";
export async function einstellungAnlegen(db: PrismaClient, modus: Betriebsmodus): Promise<void> {
// Nur anlegen, nicht ueberschreiben: ein spaeterer Seed-Lauf darf einen im
// Betrieb umgeschalteten Modus nicht zuruecksetzen.
const vorhanden = await db.einstellung.findUnique({ where: { id: "global" } });
if (!vorhanden) {
await db.einstellung.create({
data: {
id: "global",
modus,
namensregeln: namensregelnFuerModus(modus),
gesperrteOu: GESPERRTE_OU,
},
});
console.log(` Einstellung: angelegt, Modus ${modus}, Namensregeln und Sperr-OU gesetzt`);
return;
}
/*
* Die Zeile steht schon. Ergaenzt wird nur, was **leer** ist: eine gepflegte
* Namensregel oder eine eigene Sperr-OU zurueckzusetzen waere eine Aenderung an
* Betriebsdaten, und dafuer ist der Seed nicht da.
*/
const ohneRegeln =
typeof vorhanden.namensregeln !== "object" ||
vorhanden.namensregeln === null ||
Object.keys(vorhanden.namensregeln as Record<string, unknown>).length === 0;
const ohneSperrOu = (vorhanden.gesperrteOu ?? "").trim() === "";
if (ohneRegeln || ohneSperrOu) {
await db.einstellung.update({
where: { id: "global" },
data: {
...(ohneRegeln ? { namensregeln: namensregelnFuerModus(vorhanden.modus) } : {}),
...(ohneSperrOu ? { gesperrteOu: GESPERRTE_OU } : {}),
},
});
}
const ergaenzt = [ohneRegeln ? "Namensregeln" : null, ohneSperrOu ? "Sperr-OU" : null].filter(
(teil) => teil !== null,
);
console.log(
` Einstellung: vorhanden, Modus ${vorhanden.modus} bleibt` +
(ergaenzt.length > 0 ? ` (${ergaenzt.join(" und ")} ergänzt)` : ""),
);
}
export async function eigeneOrganisationAnlegen(db: PrismaClient): Promise<string> {
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 db.kunde.findFirst({ where: { istEigeneOrganisation: true } });
if (vorhanden) {
console.log(` Eigene Organisation: vorhanden (${vorhanden.name})`);
return vorhanden.id;
}
const kunde = await db.kunde.create({
data: { name, kuerzel, istEigeneOrganisation: true },
});
console.log(` Eigene Organisation: angelegt (${kunde.name}, ${kunde.kuerzel})`);
return kunde.id;
}
export async function adminAnlegen(db: PrismaClient): Promise<void> {
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 db.benutzer.findUnique({ where: { email } });
if (vorhanden) {
console.log(` Administrator: vorhanden (${email})`);
return;
}
// Das Passwort wird nur als argon2-Hash gespeichert, nie im Klartext.
await db.benutzer.create({
data: {
email,
name: "Administrator",
rolle: Rolle.ADMIN,
passwortHash: await hash(passwort),
aktiv: true,
},
});
console.log(` Administrator: angelegt (${email})`);
}