M1 – Datenmodell #6

Closed
opened 2026-09-24 20:44:39 +00:00 by florian.wilde · 0 comments
Owner

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.

Quelle: docs/issues/006-datenmodell.md — dort gepflegt, hierher gespiegelt.

**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 - [x] Prisma 7: Generator `prisma-client` mit `output = "../src/generated/prisma"`, `datasource` ohne `url`, `@prisma/adapter-pg` - [x] `prisma.config.ts` mit `migrations.seed = "tsx prisma/seed.ts"` - [x] `src/lib/db.ts`: Lazy-Singleton-Proxy, damit `next build` ohne `DATABASE_URL` läuft, Dev-Instanz an `globalThis` - [x] Modelle: `Benutzer`, `Kunde`, `Einstellung`, `Verzeichnisquelle`, `Mitarbeiter`, `Abteilung`, `Geraet`, `GeraetetypVorlage`, `Vorlage`, `VorlagenVersion`, `VorlagenVariable`, `VorlagenAbschnitt`, `VorlagenSchritt`, `Lauf`, `Schrittergebnis`, `Kommentar`, `Protokoll`, `AbgleichSperre` - [x] Enums: `Rolle`, `Betriebsmodus`, `LaufStatus`, `SchrittZustand`, `SchrittTyp`, `GeraeteTyp`, `VersionStatus`, `Herkunft` - [x] Unique-Constraints: `Geraet.tagCode`, `(laufId, vorlagenSchrittId)` auf `Schrittergebnis`, `(quelleId, externeId)` auf `Mitarbeiter`, `(kundeId, name)` auf `Abteilung`, `(kundeId)` partiell für `isteigeneOrganisation` - [x] `pg_trgm`-Erweiterung und GIN-Indizes für die Archivsuche über `Lauf.titel`, `Schrittergebnis.notiz`, `Kommentar.rumpf` - [x] Jede kundenbezogene Zeile trägt `kundeId` als Pflichtfeld: `Geraet`, `Mitarbeiter`, `Abteilung`, `Verzeichnisquelle`, `Lauf` - [x] `src/lib/tagcode.ts`: Alphabet `ABCDEFGHJKLMNPQRSTUVWXYZ23456789`, Präfix `LZ-`, 6 Zeichen, Kollisionsprüfung serverseitig - [x] `src/lib/geheimnisse.ts`: AES-256-GCM aus `APP_SECRET` (scrypt-abgeleitet), für `bindPasswortVerschluesselt` - [x] 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` - [x] `tests/setup-env.ts`, `tests/global-setup.ts`, `tests/helpers/db.ts` nach ProxLedger-Muster ## Akzeptanzkriterien - [x] `prisma migrate deploy` läuft auf leerer Datenbank durch - [x] `pnpm db:seed` zweimal hintereinander ohne Fehler und ohne Datenänderung - [x] Tests: `tagCode` benutzt nur das vorgegebene Alphabet `ABCDEFGHJKLMNPQRSTUVWXYZ23456789` und enthält damit nie `0`, `O`, `1` oder `I`; eindeutig über 10 000 Ziehungen - [x] Tests: verschlüsseln und entschlüsseln ist verlustfrei, zwei Verschlüsselungen desselben Klartexts unterscheiden sich (eigener IV), ein manipulierter Geheimtext wirft - [x] `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`. --- Quelle: `docs/issues/006-datenmodell.md` — dort gepflegt, hierher gespiegelt.
florian.wilde added this to the v0.1.0 milestone 2026-09-24 20:44:39 +00:00
florian.wilde added the phase-1backend labels 2026-09-24 20:44:39 +00:00
Sign in to join this conversation.