Files
CapaKraken/plan.md
T
Hartmut 41eb722369 feat: user invite flow, deactivate/delete, favicon, dashboard loading fix, admin full-width
- Invite flow: admin can invite users by email with role selection; accept-invite page
  sets password and creates the account; 72-hour token expiry; E2E tests
- User deactivate/reactivate/delete: new tRPC procedures + UI buttons; deactivation
  revokes all active sessions immediately; delete cascades vacation/broadcast records;
  isActive field added via migration 20260402000000_user_isactive
- Auth: block login for inactive users with audit entry
- Favicon: SVG favicon + ICO/PNG fallbacks (16, 32, 180, 192, 512px); manifest updated
- Dashboard: GridLayout dynamic-import loading skeleton prevents blank dark area
  on first login before react-grid-layout chunk is cached
- Admin users: remove max-w-5xl constraint so table uses full page width
- Dev: docker container restart workflow documented in LEARNINGS.md; Prisma generate
  must run inside the container after schema changes (named node_modules volume)

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-04-02 20:19:26 +02:00

4.8 KiB

Plan: App Base URL — configurable via env, no localhost fallback in production

Stand: 2026-04-02


Anforderungsanalyse

Email-Links (Invite, Password Reset) werden aktuell mit process.env["NEXTAUTH_URL"] ?? "http://localhost:3100" gebaut. Das ist korrekt, wenn NEXTAUTH_URL gesetzt ist. Das Problem: wenn der Wert fehlt oder leer ist, enthalten Produktions-E-Mails localhost-Links. Außerdem gibt es keine .env.example, die dokumentiert, welche Variablen gesetzt werden müssen.

Was gebaut wird:

  1. Zentrale getAppBaseUrl() Funktion in packages/api — liest NEXTAUTH_URL, wirft in production einen Fehler wenn nicht gesetzt, fällt in dev auf localhost zurück.
  2. Beide Router (invite.ts, auth.ts) verwenden diese Funktion statt duplizierter Inline-Logik.
  3. .env.example mit allen benötigten Variablen.
  4. Health-Route zeigt ob NEXTAUTH_URL konfiguriert ist.

Betroffene Pakete: packages/api, apps/web

Audit-Ergebnis — intentionale localhost-Referenzen (NICHT ändern):

  • apps/web/e2e/dev-system/ — alle E2E-Helfer, Specs, global-setup.ts
  • apps/web/playwright.dev.config.ts
  • apps/web/src/middleware.test.ts
  • .github/workflows/deploy-test.yml

Betroffene Pakete & Dateien

Paket Datei Art
packages/api src/lib/app-base-url.ts create
packages/api src/router/invite.ts edit
packages/api src/router/auth.ts edit
apps/web src/app/api/health/route.ts edit
root .env.example create

Task-Liste

  • Task 1: getAppBaseUrl() in packages/api/src/lib/app-base-url.ts erstellen.

    • Liest process.env["NEXTAUTH_URL"] (trimmed).
    • Wenn gesetzt und nicht leer → gibt den Wert zurück (trailing slash entfernen).
    • Wenn leer/fehlend und NODE_ENV === "production" → wirft Error("NEXTAUTH_URL must be set in production — email links will be broken").
    • Sonst (development/test) → gibt "http://localhost:3100" zurück und loggt einmalig eine Warnung.
    • → Datei: packages/api/src/lib/app-base-url.ts
  • Task 2: invite.ts auf getAppBaseUrl() umstellen.

    • Ersetze const baseUrl = process.env["NEXTAUTH_URL"] ?? "http://localhost:3100"; durch const baseUrl = getAppBaseUrl();
    • → Datei: packages/api/src/router/invite.ts Zeile 53
  • Task 3: auth.ts auf getAppBaseUrl() umstellen.

    • Ersetze const baseUrl = process.env["NEXTAUTH_URL"] ?? "http://localhost:3100"; durch const baseUrl = getAppBaseUrl();
    • → Datei: packages/api/src/router/auth.ts Zeile 50
  • Task 4: .env.example anlegen mit allen required und optionalen Variablen.

    • Sections: App, Auth, Database, Redis, SMTP, AI, Dev-Tools (pgAdmin)
    • Jede Variable: Kommentar (required/optional, Beschreibung), Beispielwert.
    • NEXTAUTH_URL als REQUIRED mit Hinweis "must be the public URL (e.g. https://capakraken.example.com) — used in email links; do not use localhost in production"
    • → Datei: .env.example
  • Task 5: Health-Route um baseUrl-Check erweitern.

    • Liest NEXTAUTH_URL direkt (kein getAppBaseUrl() — soll nie werfen, nur reporten).
    • Fügt "baseUrl": { "configured": bool, "isLocalhost": bool } zum JSON-Response hinzu.
    • configured: false wenn Var fehlt/leer; isLocalhost: true wenn Wert mit http://localhost beginnt.
    • → Datei: apps/web/src/app/api/health/route.ts

Abhängigkeiten

  • Task 2 und Task 3 benötigen Task 1 (Funktion muss existieren).
  • Task 2 und Task 3 können parallel ausgeführt werden (unterschiedliche Dateien).
  • Task 4 und Task 5 sind unabhängig von allen anderen und können parallel ausgeführt werden.

Akzeptanzkriterien

  • pnpm test:unit läuft grün
  • pnpm --filter @capakraken/web exec tsc --noEmit — keine neuen TS-Errors
  • pnpm test:e2e:email — alle 3 E2E-Tests bestehen weiterhin
  • Wenn NEXTAUTH_URL=https://capakraken.hartmut-noerenberg.com: E-Mail-Links enthalten diese Domain
  • Wenn NEXTAUTH_URL fehlt und NODE_ENV=production: createInvite / requestPasswordReset werfen einen klaren Fehler beim Link-Bau
  • GET /api/health liefert baseUrl.configured: true/false und baseUrl.isLocalhost: bool
  • .env.example existiert und dokumentiert alle Pflichtfelder

Risiken & offene Fragen

  • Unit-Tests für Router: Bestehende Tests für invite.ts und auth.ts müssen NEXTAUTH_URL in der Testumgebung gesetzt haben (oder NODE_ENV=test → Dev-Fallback greift). Vorhandene Tests prüfen, ob process.env["NEXTAUTH_URL"] dort gesetzt wird.
  • docker-compose.prod.yml delegiert ENV-Vars an .env.production (gitignored). Das .env.example deckt ab, was dort stehen muss — kein Code-Change nötig.
  • Trailing Slash: NEXTAUTH_URL könnte mit oder ohne / enden. getAppBaseUrl() sollte trailing slashes normalisieren.