Andrei Laas b23ee17608
Some checks failed
CI / Migrationen auf leerer Datenbank (push) Has been cancelled
CI / Lint, Typen, Tests, Build (push) Has been cancelled
Logo.tsx wieder vollstaendig machen
Zwei Handaenderungen hatten das Bauteil halb umgebaut: `aufRosa` war aus der
Signatur von MannerLogo verschwunden, wurde im Rumpf aber weiter benutzt
(erster Build: "Cannot find name"), und nach dem Nachbessern reichte
AppWortmarke die Eigenschaft weiter, die es nicht mehr gab (zweiter Build:
"Property 'aufRosa' does not exist"). Beide Male scheiterte der Typcheck im
Container, und damit blieb der Stand von d2d5e1d unausgeliefert.

Wiederhergestellt ist genau dieser Stand. `aufRosa` wird gebraucht: auf einer
rosa Flaeche darf das Logo kein eigenes Feld bekommen, sonst liegt wieder ein
Rechteck darauf — auf dem Panel der Anmeldeseite waere es das Raster, das
unter dem Feld endet.

Lint, Typen, Schemaabgleich, 562 Tests und der Build sind sauber.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-15 21:30:03 +02:00
2026-09-07 10:43:22 +02:00
2026-09-07 10:43:22 +02:00
2026-09-07 10:43:22 +02:00
2026-09-07 10:43:22 +02:00
2026-09-07 10:43:22 +02:00
2026-09-07 10:43:22 +02:00

Alpenwerk 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
  • PostgreSQL, angesprochen über Kysely und pg — die Datenhaltung liegt in der Datenbank, nicht im Next.js-Prozess. Der Zugriff läuft ausschliesslich über withUser() (lib/db/), das den Sitzungskontext setzt.
  • Auth.js gegen Microsoft Entra ID — keine eigenen Passwörter.
  • Tailwind CSS v4
  • Vitest — Unit- (Node), Komponenten- (jsdom) und Integrationstests (gegen eine erreichbare Datenbank)

Setup

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

Für eine lokale Datenbank genügt der Container aus docker-compose.yml:

docker compose up -d db
docker compose run --rm migrate     # spielt db/migrations/ ein

Danach zeigt DATABASE_URL in .env.local auf diese Datenbank.

Umgebungsvariablen

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

Variable Sichtbarkeit Zweck
DATABASE_URL Nur Server PostgreSQL-Verbindung. Die Rolle darf kein BYPASSRLS haben
DATABASE_SSL Nur Server false für lokal/CI ohne TLS
AUTH_SECRET Nur Server Signiert und verschlüsselt das Sitzungscookie
AUTH_MICROSOFT_ENTRA_ID_ID Nur Server Anwendungs-ID der Entra-Registrierung
AUTH_MICROSOFT_ENTRA_ID_SECRET Nur Server Client-Geheimnis dazu
AUTH_MICROSOFT_ENTRA_ID_ISSUER Nur Server Aussteller mit Mandanten-ID — nicht common
CRON_SECRET Nur Server Schützt /api/cron/apply-pending-changes

Es gibt keine NEXT_PUBLIC_*-Variablen mehr. Nichts wird in das Browser-Bundle eingebacken, weil der Browser mit nichts ausser der Anwendung selbst spricht. Ein Docker-Abbild ist damit umgebungsneutral: einmal gebaut, überall dasselbe — vorher brauchte jede Umgebung ihr eigenes.

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 erreichbare Datenbank; überspringt sich ohne DATABASE_URL
npm run test:e2e Playwright
npm run migrate Ausstehende Migrationen einspielen (--dry-run, --baseline)
npm run check lint + typecheck + types:check + test + build in Folge — dieselben Schritte wie der CI-Job „check"

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/datenkatalog.md.
  • Es gibt keinen privilegierten Zugang mehr. Der Dienstschlüssel, der RLS aushebelte, ist ersatzlos entfallen; auch der nächtliche Lauf benutzt dieselbe Rolle ohne BYPASSRLS. Was ohne angemeldete Person laufen muss, steht als SECURITY DEFINER-Funktion in der Datenbank und prüft dort selbst, was es tut.
  • Jede Abfrage läuft in einer Transaktion mit gesetztem Sitzungskontext. Die Kysely-Instanz wird nicht exportiert — der einzige Weg an die Datenbank ist withUser() (lib/db/index.ts), und eine ESLint-Regel verbietet den Import von pg ausserhalb von lib/db/.
  • 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_changes → apply_due_pending_changes()).

  • Den Zeitplan hält der cron-Container aus docker-compose.yml: täglich 03:00 Uhr, mit Authorization: Bearer <CRON_SECRET>.
  • Fehlt CRON_SECRET oder stimmt der Header nicht, antwortet die Route mit 401 (nicht 500 — bewusst, siehe tests/unit/security.test.ts).

Datenbank

  • Schema-Quelle der Wahrheit: db/migrations/. Menschlich lesbare Fassung, aus der laufenden Datenbank erzeugt: docs/datenkatalog.md.
  • Migrationen einspielen: npm run migrate. Der Läufer wendet nur die fehlenden Dateien an, jede in ihrer eigenen Transaktion, und führt darüber Buch in migrationen.schema_migrations.
  • lib/types.ts wird von Hand gepflegt. npm run types:check hält sie Spalte für Spalte gegen die Migrationen.

Testing

  • npm run test — schnell, keine externen Abhängigkeiten, läuft in CI.
  • npm run test:integration — braucht eine erreichbare Datenbank (DATABASE_URL). Geprüft werden Regeln, die es zweimal gibt: einmal als SQL, einmal als TypeScript. Ohne DATABASE_URL überspringen sich die Dateien, statt mit einem Verbindungsfehler abzubrechen.
  • npm run test:e2e — Playwright gegen einen laufenden Dev-/Preview-Server.

Deployment

Siehe DEPLOYMENT.md: Anwendung und Datenbank als Container, Reverse-Proxy/TLS, der nächtliche Lauf, Migrationen und Updates.

Known TODOs vor Produktivbetrieb

  • Content-Security-Policy läuft im Nur-Bericht-Modus (next.config.ts). Erzwungen wird sie erst, wenn die Meldungen sauber sind — eine geratene, erzwungene Richtlinie blendet die Anwendung für alle aus.
  • Seed für die Integrationstests fehlt. Er lag in der abgelösten Umgebung. Zwei der drei Integrationstests vergleichen Regeln über den gesamten Bestand und brauchen dafür Daten; bis der Seed nachgezogen ist, laufen sie nur gegen eine bereits befüllte Datenbank, nicht in der CI.
  • Sicherheitsprüfung des heutigen Aufbaus steht aus — siehe docs/security-review.md.
  • Bildschirmfotos unter .scratch_shots/ stammen aus einer früheren Handprüfung. Sie sind über .gitignore ausgeschlossen; vor der Übergabe durchsehen und löschen.
  • 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 3.8 MiB
Languages
TypeScript 57.2%
PLpgSQL 41.7%
JavaScript 0.7%
CSS 0.3%