Workspace und Datenmodell

pnpm-Workspace mit web/, worker/, pdf/ und packages/db. Das Datenbankpaket ist
eigenständig, weil Web-App und Worker denselben Prisma-Client brauchen.

Schema nach docs/04-datenmodell.md, mit drei Abweichungen, die dort begründet
sind: CaseParticipant bekommt einen eigenen Schlüssel statt eines
zusammengesetzten mit NULL-Spalten, die Aktenzeichen laufen über CaseCounter
statt über eine Sequenz pro Jahr, und ServiceHeartbeat kommt dazu, damit
/api/health den Gateway-Zustand kennt, ohne Discord zu fragen.

Die zweite Migration enthält, was Prisma nicht ausdrücken kann:
- kein DELETE auf CaseEntry, CaseEntryVersion und AuditLog
- authorId, createdAt, caseId und type eines Eintrags sind unveränderlich
- an einer Eintragsversion darf nur minClearance angehoben werden
- Volltextsuche mit deutscher Konfiguration; geschwärzte Bereiche werden vor
  dem Indizieren entfernt, sonst verrät ein Treffer den Inhalt
- pg_notify bei Rollen- und Mapping-Änderungen

Acht Tests prüfen diese Regeln gegen eine echte Postgres-Instanz, nicht gegen
die Anwendungslogik.
This commit is contained in:
Harlekin
2026-09-01 00:01:29 +02:00
parent 72c47e74a6
commit 8f0644e053
20 changed files with 9085 additions and 0 deletions
+39
View File
@@ -0,0 +1,39 @@
import { PrismaClient } from './generated/client.js'
import { PrismaPg } from '@prisma/adapter-pg'
import { loadEnvFromRepoRoot } from './loadEnv.js'
/**
* Der Client wird von Prisma als ECMAScript-Module nach `src/generated`
* erzeugt (siehe prisma/schema.prisma) und von dort mitübersetzt. Dadurch
* lässt er sich sauber weiterreichen, Enums inklusive, und er überlebt das
* Bündeln der Container-Images.
*/
export * from './generated/client.js'
export { loadEnvFromRepoRoot }
/**
* Erzeugt einen Prisma-Client.
*
* Prisma 7 spricht über einen Treiber-Adapter statt über eine eigene
* Rust-Engine. Das passt hier doppelt: der `pg`-Treiber wird für
* LISTEN/NOTIFY ohnehin gebraucht (siehe docs/06-chat-und-realtime.md), und
* die Container-Images kommen ohne nachzuladende Binärdatei aus.
*
* Im Betrieb zeigt DATABASE_URL auf pgbouncer im Transaktionsmodus, deshalb
* gehören `pgbouncer=true` und ein kleines Pool-Limit in die URL.
* Siehe docs/09-performance-und-ux.md und docs/11-betrieb.md.
*/
export function createPrismaClient(url?: string): PrismaClient {
if (!url) {
loadEnvFromRepoRoot()
url = process.env.DATABASE_URL
}
if (!url) throw new Error('Konfiguration unvollständig: DATABASE_URL fehlt')
const adapter = new PrismaPg({ connectionString: url })
return new PrismaClient({
adapter,
log: ['warn', 'error'],
})
}
+23
View File
@@ -0,0 +1,23 @@
import { existsSync } from 'node:fs'
import path from 'node:path'
import { fileURLToPath } from 'node:url'
/**
* Prisma 7 liest die .env nicht mehr von selbst, und der Seed läuft außerhalb
* von Next.js. Im Betrieb kommen die Werte aus dem .env-Reiter von Dockge und
* stehen längst in der Umgebung, dann passiert hier nichts.
* Node 22 bringt loadEnvFile mit, dafür braucht es kein Paket.
*/
export function loadEnvFromRepoRoot(): void {
const here = path.dirname(fileURLToPath(import.meta.url))
for (const candidate of [
path.resolve(here, '../../../.env'),
path.resolve(here, '../../.env'),
path.resolve(process.cwd(), '.env'),
]) {
if (existsSync(candidate)) {
process.loadEnvFile(candidate)
return
}
}
}