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

95 lines
4.8 KiB
Markdown

# 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.