Files
florian.wildeandClaude Opus 5 7a0f875f55
Prüfung / pruefung (push) Canceled after 0s
Prüfung / e2e (push) Canceled after 0s
feat(datenmodell): Schema, tagCodes und Geheimnisse (#20)
Vollständiges Datenmodell in deutscher Benennung: 20 Tabellen, 9 Aufzählungen,
eine Migration, dazu die framework-freien Helfer für tagCodes und Geheimnisse.

Regeln stehen zusätzlich in der Datenbank, nicht nur im Service: 12 CHECKs,
2 partielle Eindeutigkeitsindizes, 4 zusammengesetzte Fremdschlüssel. Die
Mandantengrenze ist damit strukturell erzwungen. Schrittergebnisse, Kommentare
und Protokolleinträge kaskadieren bewusst nicht — das Archiv ist das Gedächtnis
der Anwendung.

106 Tests, davon 42 gegen die echten Datenbankregeln.

Closes #6

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-24 23:11:58 +00:00

4.5 KiB
Raw Permalink Blame History

#006 M1 – Datenmodell

Status: erledigt Meilenstein: v0.1.0 Labels: phase-1, backend Geschätzt: 5 h Tatsächlich: 1 h 10 min

Ziel

Das vollständige Schema in deutscher Benennung, sodass alle folgenden Meilensteine nur noch Verhalten ergänzen und keine Strukturänderung mehr brauchen.

Aufgaben

  • Prisma 7: Generator prisma-client mit output = "../src/generated/prisma", datasource ohne url, @prisma/adapter-pg
  • prisma.config.ts mit migrations.seed = "tsx prisma/seed.ts"
  • src/lib/db.ts: Lazy-Singleton-Proxy, damit next build ohne DATABASE_URL läuft, Dev-Instanz an globalThis
  • Modelle: Benutzer, Kunde, Einstellung, Verzeichnisquelle, Mitarbeiter, Abteilung, Geraet, GeraetetypVorlage, Vorlage, VorlagenVersion, VorlagenVariable, VorlagenAbschnitt, VorlagenSchritt, Lauf, Schrittergebnis, Kommentar, Protokoll, AbgleichSperre
  • Enums: Rolle, Betriebsmodus, LaufStatus, SchrittZustand, SchrittTyp, GeraeteTyp, VersionStatus, Herkunft
  • Unique-Constraints: Geraet.tagCode, (laufId, vorlagenSchrittId) auf Schrittergebnis, (quelleId, externeId) auf Mitarbeiter, (kundeId, name) auf Abteilung, (kundeId) partiell für isteigeneOrganisation
  • pg_trgm-Erweiterung und GIN-Indizes für die Archivsuche über Lauf.titel, Schrittergebnis.notiz, Kommentar.rumpf
  • Jede kundenbezogene Zeile trägt kundeId als Pflichtfeld: Geraet, Mitarbeiter, Abteilung, Verzeichnisquelle, Lauf
  • src/lib/tagcode.ts: Alphabet ABCDEFGHJKLMNPQRSTUVWXYZ23456789, Präfix LZ-, 6 Zeichen, Kollisionsprüfung serverseitig
  • src/lib/geheimnisse.ts: AES-256-GCM aus APP_SECRET (scrypt-abgeleitet), für bindPasswortVerschluesselt
  • Erste Migration, prisma/seed.ts als idempotenter Rumpf: Admin aus SEED_ADMIN_EMAIL/SEED_ADMIN_PASSWORD mit @node-rs/argon2, eigene Organisation, Einstellung mit Betriebsmodus.INTERN
  • tests/setup-env.ts, tests/global-setup.ts, tests/helpers/db.ts nach ProxLedger-Muster

Akzeptanzkriterien

  • prisma migrate deploy läuft auf leerer Datenbank durch
  • pnpm db:seed zweimal hintereinander ohne Fehler und ohne Datenänderung
  • Tests: tagCode benutzt nur das vorgegebene Alphabet ABCDEFGHJKLMNPQRSTUVWXYZ23456789 und enthält damit nie 0, O, 1 oder I; eindeutig über 10 000 Ziehungen
  • Tests: verschlüsseln und entschlüsseln ist verlustfrei, zwei Verschlüsselungen desselben Klartexts unterscheiden sich (eigener IV), ein manipulierter Geheimtext wirft
  • next build gelingt ohne gesetzte DATABASE_URL

Beim Bauen aufgefallen

  • Widerspruch im Auftrag beim tagCode-Alphabet, aufgelöst. Das wörtlich vorgegebene Alphabet ABCDEFGHJKLMNPQRSTUVWXYZ23456789 enthält ein L, obwohl die Klammer daneben „ohne 0/O, 1/I/L" sagt. Beides zugleich geht nicht: ohne L blieben 31 Zeichen. Wir folgen dem wörtlichen Alphabet, weil die Klammer die Verwechslungsgruppen benennt und keine Ausschlussliste ist: aus {0, O} sind beide weg, aus {1, I, L} sind 1 und I weg — dadurch ist L eindeutig. Das Präfix LZ- enthält ohnehin in jedem Code ein L. Das Akzeptanzkriterium oben ist entsprechend richtiggestellt; ein grüner Test über einem unerfüllten Kriterium wäre schlimmer als ein korrigiertes Kriterium.
  • Der unabhängige Review hat drei Lücken gefunden, die alle behoben sind: der geforderten Suchspalte Schrittergebnis.wert fehlte der Trigramm-Index; die im Lauf gewählte Abteilung war nur ein Wert in variablen statt ein Bezug, wodurch die Druckansicht eines archivierten Laufs nicht reproduzierbar gewesen wäre; und für die Kundenbindung eines Benutzers (#008) gab es keine Struktur, sie hätte also nur im Token stehen können — gegen die eigene Regel, Rechte pro Anfrage aus der Datenbank zu lesen.
  • Ein DELETE auf einen Lauf hätte Schrittergebnisse und Kommentare stillschweigend mitgenommen. Jetzt Restrict; ein Lauf lässt sich nur noch bewusst und in der richtigen Reihenfolge entfernen. Dasselbe für das Protokoll, das seinen Urheber nicht mehr verlieren kann.
  • tagCode wäre nach dem Löschen eines Geräts wieder frei gewesen. Deshalb TagCodeVergabe als Vergabebuch, aus dem nie gelöscht wird.
  • prisma migrate reset --force hat den Bestätigungsdialog trotzdem gezeigt und die Advisory-Sperre gehalten. Schneller und verlässlicher: DROP SCHEMA public CASCADE per psql, dann migrate dev --create-only.