It was never used. The repository lives on a self-hosted Gitea, which Vercel's git integration cannot connect to at all — so the documented route amounted to "mirror to GitHub first", and nobody did. vercel.json is gone, and with it the branch in next.config.ts that switched off `output: "standalone"` when the VERCEL variable was present. That branch was the only functional trace; everything else was documentation and comments describing a second deployment path that did not exist. DEPLOYMENT.md loses its "two supported ways" framing and the whole Vercel section — about fifty lines. Several statements next to it were stale for a different reason and are corrected in the same pass: the outbound-firewall table still listed Supabase's pooler (the database is a container now, nothing leaves the server), the prerequisites still demanded an existing Supabase project, and the .env table still asked for a pooler connection string instead of the two new passwords. The nightly job is described as what it is — a container in docker-compose.yml — rather than as a replacement for Vercel Cron. Migrations keep their references: two comments from July mention Vercel Cron, and they describe what was true when they were written. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
7.0 KiB
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.tsstattmiddleware.ts). Vor Änderungen an Framework-nahen Dateien die lokalen Docs unternode_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 |
|---|---|---|
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 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-Funktionis_hr_user(). Kein Sub-Rollensystem — siehedocs/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 alsSECURITY 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 vonpgausserhalb vonlib/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 siehedocs/security-review.md. - Historie ist append-only (
employee_history,audit_log) — RLS erlaubt keinupdate/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 ausdocker-compose.yml: täglich 03:00 Uhr, mitAuthorization: Bearer <CRON_SECRET>. - Fehlt
CRON_SECREToder stimmt der Header nicht, antwortet die Route mit401(nicht500— bewusst, siehetests/unit/security.test.ts).
Supabase-Hinweise
- Schema-Quelle der Wahrheit:
supabase/migrations/. Menschlich lesbare Fassung, aus der laufenden Datenbank erzeugt:docs/datenkatalog.md. - Migrationen einspielen:
supabase db push(gegen das verlinkte Projekt) bzw.supabase start+ automatische Anwendung für lokale Entwicklung. supabase/seed.tsund.env.test.localsind 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 (siehetests/integration/authorization.test.tsfür das HR-Only-Zugriffs- modell).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 fehlt noch (
next.config.tssetzt 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.gitignoreausgeschlossen; 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.