Maximilian Stubhan 730521ee79 Keep the environment's identifiers out of the repository
Projekt-Ref, Entra-Client- und Tenant-ID standen im Klartext in der
SSO-Anleitung. Geheimnisse sind das nicht — ohne Schlüssel gibt eine
Projekt-URL nichts her, und RLS greift unabhängig davon. Sie zeigen aber auf
die laufende Umgebung, und dieses Repository wandert weiter als sie: es geht
gleich auf einen eigenen Git-Server und später an den Kunden.

Jetzt Platzhalter; die Werte gehören in die Übergabedokumentation. In der
Historie stehen sie weiterhin — das sauber zu entfernen hiesse, die Historie
neu zu schreiben, und das passiert nicht nebenbei.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 18:39:20 +02:00

Manner HR Master

Interne HR-Stammdatenverwaltung: Mitarbeiter:innen, Organisationsstruktur (Bereich/Abteilung/Team), Planstellen, Neueinstellungen, Versetzungen/ Beförderungen/Karenz, Reorganisationen und der zugehörige Audit-Trail.

Zweck

Die App ersetzt Excel-basierte HR-Stammdatenpflege durch ein Werkzeug mit verbindlichen Regeln (z. B. wirksame Daten statt sofortiger Änderungen, eindeutige Positions-/Org-Nummern, verpflichtende Historie) und einem lückenlosen Audit-Trail für jede Änderung.

Alle Mitarbeiterdaten in diesem System sind vertraulich — Stammdaten, Verträge, Sozialversicherungsnummern, Angehörige und Audit-Daten. Zugriff ist auf explizit aktivierte HR-Benutzer:innen beschränkt (siehe Sicherheitsprinzipien).

Tech-Stack

  • Next.js 16 (App Router) — Achtung: Next.js 16 hat Breaking Changes gegenüber älteren Versionen (u. a. proxy.ts statt middleware.ts). Vor Änderungen an Framework-nahen Dateien die lokalen Docs unter node_modules/next/dist/docs/ konsultieren; sie sind maßgeblich, ältere Anleitungen im Netz beschreiben teils überholte APIs.
  • React 19, TypeScript
  • Supabase (Postgres, Auth, RLS) — Datenhaltung liegt vollständig in Supabase, nicht im Next.js-Prozess.
  • Tailwind CSS v4
  • Vitest — Unit- (Node), Komponenten- (jsdom) und Integrationstests (gegen ein lokales Supabase)

Setup

npm install
cp .env.example .env.local   # Werte eintragen, siehe unten
npm run dev

Für lokale Supabase-Entwicklung (statt gegen ein Cloud-Projekt):

supabase start   # startet lokalen Postgres/Auth/Studio-Stack

supabase/config.toml und .env.test.local sind bereits auf die Standard-Ports der lokalen Supabase-CLI abgestimmt.

Umgebungsvariablen

Siehe .env.example für die vollständige, kommentierte Liste. Kurzfassung:

Variable Sichtbarkeit Zweck
NEXT_PUBLIC_SUPABASE_URL Browser + Server Supabase-Projekt-URL
NEXT_PUBLIC_SUPABASE_ANON_KEY Browser + Server Anon-Key, RLS-gebunden
SUPABASE_SERVICE_ROLE_KEY Nur Server Umgeht RLS vollständig — niemals im Browser-Bundle, niemals loggen
CRON_SECRET Nur Server Schützt /api/cron/apply-pending-changes

NEXT_PUBLIC_*-Werte werden beim Build in das Client-Bundle eingebacken — eine Änderung erfordert einen Rebuild, nicht nur einen Neustart (relevant für Docker-Deployments, siehe unten).

Scripts

Befehl Zweck
npm run dev Lokaler Dev-Server
npm run build Produktions-Build
npm run start Produktions-Server (nach build)
npm run lint ESLint (eslint-config-next, Flat Config)
npm run typecheck tsc --noEmit
npm run test Vitest, Unit-Tests (tests/unit/**)
npm run test:integration Vitest gegen eine echte (lokale) Supabase-Instanz — braucht supabase start und .env.test.local
npm run test:e2e Playwright
npm run check lint + typecheck + test + build in Folge

Sicherheitsprinzipien

  • RLS ist die eigentliche Schranke, nicht die UI. Jede Tabelle hat Row Level Security aktiv; proxy.ts (App-Ebene) ist Defense-in-Depth, keine Ersatzkontrolle.
  • Ein Rollenmodell: profiles.role = 'hr' + profiles.is_active = true, geprüft über die SQL-Funktion is_hr_user(). Kein Sub-Rollensystem — siehe docs/data-model.md.
  • Service-Role-Key ist server-only. Einzige Verwendung: lib/supabase/admin.ts, geschützt durch import "server-only" (macht einen versehentlichen Client-Import zu einem Build-Fehler statt einem Laufzeitproblem).
  • Audit-Log ist transaktional in der Datenbank, nicht im App-Code: jede mutierende SQL-Funktion schreibt ihren audit_log-Eintrag in derselben Transaktion wie die Änderung selbst. Details und Prüfung siehe docs/security-review.md.
  • Historie ist append-only (employee_history, audit_log) — RLS erlaubt kein update/delete. Korrekturen sind kompensierende Einträge.

Cron-Konfiguration

/api/cron/apply-pending-changes wendet wirksam gewordene, zukunftsdatierte Änderungen an (pending_org_changesapply_due_pending_changes()).

  • Auf Vercel: vercel.json definiert den täglichen Schedule; Vercel Cron sendet Authorization: Bearer <CRON_SECRET> automatisch, wenn CRON_SECRET in den Projekt-Env-Vars gesetzt ist.
  • Außerhalb von Vercel (Docker): kein Vercel Cron verfügbar — siehe DEPLOYMENT.md für den Cron-Sidecar-Container, der denselben Endpoint mit demselben Schema aufruft.
  • Fehlt CRON_SECRET oder stimmt der Header nicht, antwortet die Route mit 401 (nicht 500 — bewusst, siehe tests/unit/security.test.ts).

Supabase-Hinweise

  • Schema-Quelle der Wahrheit: supabase/migrations/. Menschlich lesbare Zusammenfassung: docs/data-model.md.
  • Migrationen einspielen: supabase db push (gegen das verlinkte Projekt) bzw. supabase start + automatische Anwendung für lokale Entwicklung.
  • supabase/seed.ts und .env.test.local sind nur für lokale Entwicklung/Tests gedacht, nie für ein Produktivprojekt verwenden.

Testing

  • npm run test — schnell, keine externen Abhängigkeiten, läuft in CI.
  • npm run test:integration — braucht eine laufende lokale Supabase-Instanz (supabase start) und .env.test.local; prüft RLS-Verhalten end-to-end (siehe tests/integration/authorization.test.ts für das HR-Only-Zugriffs- modell).
  • npm run test:e2e — Playwright gegen einen laufenden Dev-/Preview-Server.

Deployment

Siehe DEPLOYMENT.md für Docker-basiertes Deployment (Dockerfile, docker-compose.yml, Reverse-Proxy/TLS, Cron-Ersatz, Updates). Für Vercel: vercel.json ist bereits vorhanden; Env-Vars im Vercel-Projekt setzen (siehe oben).

Known TODOs vor Produktivbetrieb

  • Content-Security-Policy fehlt noch (next.config.ts setzt bewusst keine CSP — Skript-/Style-/Connect-Quellen sind noch nicht vollständig inventarisiert; ungeprüft geraten zu setzen riskiert, Hydration oder den Supabase-Client stillschweigend zu brechen).
  • Lokale Scratch-Artefakte (.scratch_*, .scratch_shots/) enthalten Screenshots/Hilfsskripte aus einer früheren manuellen Verifikation und liegen noch im Arbeitsverzeichnis. Sie sind jetzt über .gitignore ausgeschlossen; vor einem Produktiv-Handover sollten sie durchgesehen und bei Bedarf gelöscht werden.
  • Kein granulareres Rollenmodell — aktuell HR-only (alles-oder-nichts). Falls z. B. eine reine Lese-Rolle künftig gebraucht wird, gehört die Erweiterung in eine neue Migration (is_hr_user()/RLS-Policies), nicht in App-seitigen Code.
Description
No description provided
Readme 938 KiB
Languages
TypeScript 67.3%
PLpgSQL 31.2%
JavaScript 0.8%
CSS 0.5%
Dockerfile 0.2%