Idempotente Katalog-Seeds, die docs/reference/fahrzeug-katalog-noelfv.md als Code abbilden. Importiert ausschließlich das bestehende Drizzle-Schema (WS2), definiert keine Tabellen neu. - 34 Merkmale (+ Enum-Optionen: feuerloeschpumpe_typ=8, anzahl_achsen=3, stromerzeuger_bauart=3); Funkrufname ist Spalte, kein Merkmal. - 11 Fahrzeug-Vorlagen mit Pflichtmerkmalen, typisierten Vorgabewerten (vorgabewert_num/_text/_bool) und Aliassen mit `bestaetigt`. RLF/RLFA 2000 + 2000-4000 = true; kein HLFA-Alias (Laufzeitregel ist kanonisch); HLF 4-U als Alias auf HLF 4 mit Pulver-Pflichtmerkmalen. - 11 Geräte-Kategorien (Natural Key name). - upsert.ts: ausschließlich onConflictDoUpdate auf Natural Keys (slug/code/name + Verknüpfungs-PKs); index.ts seedet in EINER Transaktion (Merkmale -> Optionen -> Vorlagen -> Vorlagen-Merkmale -> Aliasse -> Kategorien) via Slug->ID-Map, sequenzielle Awaits. - Reiner Offline-Unit-Test (seed.test.ts) prüft alle fachlichen Invarianten ohne DB; package.json-Script db:seed ergänzt. Verifikation offline: tsc --noEmit (0), drizzle-kit check (0), next build (0), vitest run (191 passed, 7 DB-Tests skipped). Seed-Ausführung selbst deferred (kein Postgres im Sandbox). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
118 lines
3.8 KiB
TypeScript
118 lines
3.8 KiB
TypeScript
import { drizzle } from "drizzle-orm/node-postgres";
|
|
import { Pool } from "pg";
|
|
import * as schema from "@/db/schema";
|
|
import type { Tx } from "@/lib/audit";
|
|
import { MERKMALE } from "./data/merkmale";
|
|
import { VEHICLE_TEMPLATES } from "./data/vehicle-templates";
|
|
import { EQUIPMENT_CATEGORIES } from "./data/equipment-categories";
|
|
import {
|
|
upsertMerkmal,
|
|
upsertVehicleTemplate,
|
|
upsertTemplateMerkmal,
|
|
upsertTemplateAlias,
|
|
upsertEquipmentCategory,
|
|
upsertCategoryMerkmal,
|
|
pruneTemplateAliasse,
|
|
} from "./upsert";
|
|
|
|
/**
|
|
* Katalog-Seed (Workstream 9): füllt Merkmale, Enum-Optionen, Fahrzeug-Vorlagen,
|
|
* deren Pflichtmerkmale + Aliasse sowie Geräte-Kategorien aus dem NÖ-Katalog.
|
|
*
|
|
* Idempotent (Querschnittsstandard 7): ausschließlich Upserts auf Natural Keys;
|
|
* mehrfaches Ausführen ändert keine Counts. Läuft in EINER Transaktion in der
|
|
* Reihenfolge Merkmale → Optionen → Vorlagen → Vorlagen-Merkmale → Aliasse →
|
|
* Kategorien (sequenzielle Awaits, Slug→ID-Map).
|
|
*
|
|
* Liest `DATABASE_URL` direkt aus der Umgebung (keine Next.js-Env-Validierung,
|
|
* wie scripts/migrate.ts und scripts/seed-auth.ts).
|
|
*/
|
|
|
|
/** Reine Seed-Logik gegen eine bestehende Transaktion (für Tests injizierbar). */
|
|
export async function seedCatalog(tx: Tx): Promise<void> {
|
|
// 1. Merkmale (+ Optionen) → Slug→ID-Map.
|
|
const merkmalIdBySlug = new Map<string, string>();
|
|
for (const m of MERKMALE) {
|
|
const id = await upsertMerkmal(tx, m);
|
|
merkmalIdBySlug.set(m.slug, id);
|
|
}
|
|
|
|
// 2. Vorlagen → Pflichtmerkmale → Aliasse.
|
|
for (let i = 0; i < VEHICLE_TEMPLATES.length; i++) {
|
|
const t = VEHICLE_TEMPLATES[i]!;
|
|
const templateId = await upsertVehicleTemplate(tx, t, i);
|
|
|
|
for (let j = 0; j < t.merkmale.length; j++) {
|
|
const tm = t.merkmale[j]!;
|
|
const merkmalId = merkmalIdBySlug.get(tm.slug);
|
|
if (!merkmalId) {
|
|
throw new Error(
|
|
`Vorlage ${t.code} referenziert unbekanntes Merkmal '${tm.slug}'`,
|
|
);
|
|
}
|
|
await upsertTemplateMerkmal(tx, templateId, merkmalId, tm, j);
|
|
}
|
|
|
|
for (const a of t.aliasse) {
|
|
await upsertTemplateAlias(tx, templateId, a.alias, a.bestaetigt);
|
|
}
|
|
await pruneTemplateAliasse(
|
|
tx,
|
|
templateId,
|
|
t.aliasse.map((a) => a.alias),
|
|
);
|
|
}
|
|
|
|
// 3. Geräte-Kategorien (+ optionale Merkmal-Verknüpfungen).
|
|
for (const c of EQUIPMENT_CATEGORIES) {
|
|
const categoryId = await upsertEquipmentCategory(tx, c);
|
|
const slugs = c.merkmalSlugs ?? [];
|
|
for (let k = 0; k < slugs.length; k++) {
|
|
const merkmalId = merkmalIdBySlug.get(slugs[k]!);
|
|
if (!merkmalId) {
|
|
throw new Error(
|
|
`Kategorie ${c.name} referenziert unbekanntes Merkmal '${slugs[k]}'`,
|
|
);
|
|
}
|
|
await upsertCategoryMerkmal(tx, categoryId, merkmalId, k);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Standalone-Runner (npm run db:seed). */
|
|
export async function main(): Promise<void> {
|
|
const connectionString = process.env.DATABASE_URL;
|
|
if (!connectionString) {
|
|
throw new Error("DATABASE_URL ist nicht gesetzt.");
|
|
}
|
|
|
|
const pool = new Pool({ connectionString, max: 1 });
|
|
const db = drizzle(pool, { schema });
|
|
|
|
try {
|
|
await db.transaction(async (tx) => {
|
|
await seedCatalog(tx as Tx);
|
|
});
|
|
|
|
console.log("Katalog-Seed erfolgreich (idempotent).");
|
|
console.log(` Merkmale: ${MERKMALE.length}`);
|
|
console.log(` Fahrzeug-Vorlagen: ${VEHICLE_TEMPLATES.length}`);
|
|
console.log(` Geräte-Kategorien: ${EQUIPMENT_CATEGORIES.length}`);
|
|
} finally {
|
|
await pool.end();
|
|
}
|
|
}
|
|
|
|
// Nur ausführen, wenn direkt gestartet (nicht beim Import in Tests).
|
|
const isMain =
|
|
typeof process !== "undefined" &&
|
|
process.argv[1] !== undefined &&
|
|
import.meta.url === `file://${process.argv[1]}`;
|
|
|
|
if (isMain) {
|
|
main().catch((err: unknown) => {
|
|
console.error("Katalog-Seed fehlgeschlagen:", err);
|
|
process.exit(1);
|
|
});
|
|
}
|