Phase 0: Fundament der Feuerwehr-Verwaltungsplattform

Legt den kompletten Unterbau an, auf dem die Fachmodule aufsetzen.

Betrieb:
- docker-compose.yml mit Traefik, App, Worker, PostgreSQL 17 + PostGIS,
  Redis, MinIO und Gotenberg; Entwicklungs-Override ohne Traefik, dafuer
  mit Mailpit und Adminer
- Mehrstufiges Dockerfile: eigener Benutzer, tini als PID 1, Healthcheck
- .env.example mit einem Kommentar zu jeder Variable

Mandantentrennung:
- Row Level Security auf allen mandantenpflichtigen Tabellen
- Zwei getrennte Datenbankverbindungen: florian_app mit aktiver RLS fuer
  die Anwendung, Eigentuemer-Zugang nur fuer Migration, Seed und Anmeldung
- fuerOrganisation() als einziger Weg zu Fachdaten

Nachvollziehbarkeit:
- AuditLog append-only per Trigger; UPDATE ist dauerhaft gesperrt, DELETE
  nur im gekennzeichneten Aufbewahrungslauf
- Geheimnisse werden vor dem Schreiben entfernt

Anmeldung:
- OIDC gegen ein extern betriebenes authentik, Rollen aus Gruppen nur als
  Vorbelegung beim ersten Login
- Lokaler E-Mail/Passwort-Login als Rueckfallweg
- Terminal per PIN und RFID mit zwei Schranken (Geraet, dann Person)

Weiteres:
- lib/authz.ts mit 9 Rollen und 38 Einzelrechten
- lib/storage.ts (MinIO), lib/audit.ts, lib/warteschlange.ts (BullMQ)
- Integrationen fuer Mail, Telegram und LLM hinter abschaltbaren Interfaces
- tRPC v11, Basis-Layout mit Seitenleiste und dunkler Darstellung
- Seed mit einer Beispielwehr: 16 Personen, 3 Fahrzeuge, 40 Geraete
- 32 Vitest-Tests (davon 8 gegen eine echte Datenbank), 10 Playwright-Tests
- README, Anleitung mit Screenshots, Roadmap, Aufwandsschaetzung, Devlog,
  Versionierungsregeln und Changelog

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Florian Wilde
2026-09-03 00:13:53 +02:00
co-authored by Claude Opus 5
commit 845942f36c
84 changed files with 21433 additions and 0 deletions
@@ -0,0 +1,60 @@
-- =============================================================================
-- Kontrollierte Ausnahme vom Änderungsverbot des Audit-Logs.
--
-- Ausgangslage: Der Trigger aus 20260902000100_rls_policies verbietet UPDATE
-- und DELETE auf audit_log. Das ist richtig — nachträglich verändern soll den
-- Log niemand.
--
-- Es gibt aber genau einen Vorgang, der löschen können muss: der
-- Aufbewahrungsjob des Workers. Nach AUDIT_AUFBEWAHRUNG_TAGE exportiert er die
-- alten Einträge nach MinIO und räumt sie danach ab. Ohne diese Möglichkeit
-- wächst die Tabelle unbegrenzt, und das Löschkonzept nach DSGVO ließe sich
-- nicht umsetzen.
--
-- Der Ausweg ist ein ausdrücklicher Schalter, den nur der Eigentümer-Zugang
-- setzen kann und der nur für die laufende Transaktion gilt:
--
-- BEGIN;
-- SELECT set_config('app.audit_bereinigung', 'an', true);
-- DELETE FROM audit_log WHERE zeitpunkt < now() - interval '10 years';
-- COMMIT;
--
-- UPDATE bleibt unter allen Umständen verboten. Ein Eintrag kann verschwinden,
-- nachdem er gesichert wurde — verfälscht werden kann er nie.
-- =============================================================================
CREATE OR REPLACE FUNCTION audit_log_unveraenderlich()
RETURNS trigger
LANGUAGE plpgsql
AS $$
BEGIN
-- Änderungen sind ausnahmslos gesperrt.
IF TG_OP = 'UPDATE' THEN
RAISE EXCEPTION
'Der Audit-Log ist unveraenderlich. UPDATE ist auf audit_log nicht zulaessig.'
USING ERRCODE = 'insufficient_privilege';
END IF;
-- Löschen nur im ausdrücklich gekennzeichneten Aufbewahrungslauf.
IF TG_OP = 'DELETE' THEN
IF coalesce(current_setting('app.audit_bereinigung', true), '') = 'an' THEN
RETURN OLD;
END IF;
RAISE EXCEPTION
'Der Audit-Log ist unveraenderlich. DELETE ist nur im Aufbewahrungslauf zulaessig '
'(SET LOCAL app.audit_bereinigung = ''an'').'
USING ERRCODE = 'insufficient_privilege';
END IF;
RETURN NULL;
END;
$$;
COMMENT ON FUNCTION audit_log_unveraenderlich() IS
'Sperrt UPDATE auf audit_log dauerhaft und DELETE ausserhalb des Aufbewahrungslaufs.';
-- Die Anwendungsrolle darf den Schalter gar nicht erst setzen: sie hat kein
-- DELETE-Recht auf audit_log (siehe vorherige Migration). Der Schalter wirkt
-- also nur fuer den Eigentuemer-Zugang, den ausschliesslich Worker und
-- Migrationen benutzen.