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:
@@ -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'],
|
||||
})
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user