Initial commit

This commit is contained in:
2026-07-19 13:09:04 +02:00
commit 8cf57d7878
215 changed files with 30417 additions and 0 deletions

121
docs/admin.md Normal file
View File

@@ -0,0 +1,121 @@
# Adminbereich
Der Adminbereich liegt im Frontend unter `/admin` und verwendet dieselbe
serverseitige Authentifizierung, CSRF-Pruefung und Permission-Logik wie die
restliche Anwendung. Angular blendet Navigation und Aktionen nur fuer passende
Permissions ein; verbindlich prueft immer das Backend.
## Permissions
Die administrativen Permissions sind fest in
`apps/backend/src/roles/permissions.ts` definiert:
- `users.read`: Benutzer anzeigen, suchen und filtern
- `users.manage`: Benutzer aktivieren, deaktivieren und Rollen zuweisen
- `roles.read`: Rollen und Permissions anzeigen
- `roles.manage`: Rollen anlegen, bearbeiten und loeschen
- `sessions.manage`: Sessions anderer Benutzer anzeigen und beenden
- `audit.read`: administratives Audit-Log anzeigen
Benutzer haben keine direkten Permissions. Effektive Permissions werden aus
allen Rollen abgeleitet. Rollen- und Benutzerverwaltung laufen ueber Services;
Controller greifen nicht direkt auf TypeORM-Repositories zu.
## Systemrollen
Es gibt mindestens die Systemrollen `admin` und `user`.
- `admin` ist geschuetzt, nicht loeschbar und muss administrative Permissions
behalten.
- `user` ist geschuetzt, nicht loeschbar und Standardrolle fuer neue Benutzer.
- Eigene Rollen koennen angelegt, umbenannt, geloescht und mit bekannten
Permissions versehen werden.
Eine Rolle kann nur geloescht werden, wenn sie keinem Benutzer mehr zugewiesen
ist. Andernfalls antwortet die API mit `ROLE_STILL_ASSIGNED`.
## Letzter aktiver Administrator
Die Anwendung darf nie ohne aktiven Administrator enden. Sicherheitskritische
Aktionen laufen transaktional und halten einen MySQL-Lock
`business_app_admin_integrity`:
- Benutzer deaktivieren
- Rollen eines Benutzers aendern
- Adminrolle entfernen
- Adminrolle in ihren Permissions veraendern
- Rolle loeschen
Wenn eine Aktion den letzten aktiven Administrator entfernen wuerde, antwortet
die API mit HTTP 409 und `LAST_ACTIVE_ADMIN_REQUIRED`.
## Benutzerstatus und Sessions
Benutzer werden weiterhin ausschliesslich ueber OIDC angelegt. Name und E-Mail
kommen vom Identity Provider und sind im Adminbereich nicht editierbar.
Beim Deaktivieren wird der Benutzer sofort inaktiv gesetzt und alle aktiven
Sessions des Benutzers werden widerrufen. Neue OIDC-Logins deaktivierter Benutzer
werden trotz erfolgreicher IdP-Authentifizierung abgewiesen. Beim erneuten
Aktivieren darf sich der Benutzer wieder anmelden; alte Sessions werden nicht
wiederhergestellt.
Admin-Session-Endpunkte geben nur eine sichere, gekuerzte Session-Referenz,
Zeitpunkte, User-Agent, IP-Annaeherung und Status zurueck. Tokens und rohe
Session-Secrets werden nie ausgegeben.
## API-Endpunkte
Benutzer:
- `GET /api/admin/users`
- `GET /api/admin/users/:id`
- `PATCH /api/admin/users/:id/deactivate`
- `PATCH /api/admin/users/:id/activate`
- `POST /api/admin/users/:id/roles/:roleId`
- `DELETE /api/admin/users/:id/roles/:roleId`
- `GET /api/admin/users/:id/sessions`
- `DELETE /api/admin/users/:userId/sessions/:sessionId`
- `DELETE /api/admin/users/:id/sessions`
Rollen:
- `GET /api/admin/roles`
- `GET /api/admin/roles/:id`
- `POST /api/admin/roles`
- `PUT /api/admin/roles/:id`
- `DELETE /api/admin/roles/:id`
Audit:
- `GET /api/audit-log`
## Audit-Log
Administrative Aenderungen werden auditierbar protokolliert, darunter:
- `USER_ACTIVATED`
- `USER_DEACTIVATED`
- `USER_ROLE_ASSIGNED`
- `USER_ROLE_REMOVED`
- `ROLE_CREATED`
- `ROLE_UPDATED`
- `ROLE_DELETED`
- `ROLE_PERMISSIONS_UPDATED`
- `SESSION_REVOKED`
- `ALL_USER_SESSIONS_REVOKED`
Gespeichert werden fachliche IDs, Request-ID und minimale Metadaten. Tokens,
Secrets und vollstaendige Sessiondaten werden nicht geloggt.
## Migration
Die Admin-Erweiterung fuegt `roles.description` hinzu. Vor dem Deployment muss
die Migration ausgefuehrt werden:
```bash
npm run migration:run
```
Der normale App-Start fuehrt Migrationen weiterhin nicht automatisch aus,
sondern meldet fehlende Migrationen ueber die Readiness-Pruefung.

129
docs/architecture.md Normal file
View File

@@ -0,0 +1,129 @@
# Architektur
Das Repository ist ein modularer Monolith mit getrennten Workspaces fuer
Frontend, Backend und API-Client. Ausgeliefert wird eine einzelne Node.js-App,
die die gebaute Angular-Anwendung statisch mitliefert.
## Workspaces
```text
apps/frontend
apps/backend
packages/api-client
```
Es wird bewusst kein Nx eingesetzt. Workspace-Grenzen bleiben durch npm-Scripts,
TypeScript-Projekte und klare Importpfade nachvollziehbar.
## Backend
Das Backend liegt in `apps/backend` und basiert auf NestJS. Es ist nach Features
geschnitten:
- `auth`: OIDC Login, Callback, Logout und Authentifizierung
- `sessions`: serverseitige Sessions, CSRF-Token und Session-Listen
- `roles`: Code-definierte Permissions und rollenbasierte Rechteverwaltung
- `users`: lokale Benutzerprofile und Einstellungen
- `admin`: Angular-Routen unter `/admin` fuer Benutzer-, Rollen-, Session- und Audit-Verwaltung
- `items`: Beispiel-Fachmodul
- `notifications`: persoenliche In-App-Benachrichtigungen, Admin-Erzeugung und interner Service
- `audit`: Audit-Log fuer administrative Nachvollziehbarkeit
- `health`: Liveness- und Readiness-Endpunkte
- `database`: TypeORM-Konfiguration, Entities und Migrationen
- `common`: Fehlerformat, Request Context, Validierung und HTTP-Helfer
Controller enthalten keine Datenbanklogik. Sie validieren den HTTP-Rand,
deklarieren Permissions und delegieren an Services. Services enthalten
Fachlogik. Datenbankzugriffe laufen ueber Repository-Klassen oder klar benannte
Persistence-Services.
Globale Backend-Bausteine:
- `ApiExceptionFilter` normalisiert Fehlerantworten.
- `ThrottlerGuard` setzt Rate Limits.
- `CsrfGuard` schuetzt schreibende Requests.
- `PermissionsGuard` prueft Rollen und Permissions.
- `RequestIdMiddleware` setzt Request-Korrelation.
- `helmet` setzt Security Header.
- `pino-http` loggt mit Redaction fuer Cookies, Tokens und Secrets.
## Frontend
Das Frontend liegt in `apps/frontend` und nutzt Angular Standalone Components.
Der Shell-Aufbau steckt in `src/app/layout/app-shell.ts`. Feature-Seiten liegen
unter `src/app/features`.
Design- und Implementierungsregeln:
- mobile-first HTML und SCSS
- keine UI-Komponentenbibliothek
- Signals fuer lokalen UI-State
- RxJS fuer HTTP- und API-Flows
- Angular Permissions nur fuer Darstellung und Navigation, nicht als
Sicherheitsgrenze
Der `permissionGuard` verhindert unpassende Navigation im Frontend. Die
verbindliche Autorisierung findet immer im Backend statt.
## API-Client
`packages/api-client` enthaelt den vom Frontend genutzten Angular-Service und die
DTO-Typen. Der Code ist als generiert markiert. Er wird mit folgendem Befehl neu
geschrieben:
```bash
npm run api:generate
```
Der Produktionsbuild ruft diesen Schritt vor Backend- und Frontend-Builds auf.
Bei API-Aenderungen muss der Generator synchron zur Backend-API angepasst werden.
## Runtime-Aufbau
Im Produktionsbuild entsteht:
```text
apps/backend/dist/main.js
apps/backend/dist/public/index.html
apps/backend/dist/public/assets...
```
NestJS liefert:
- `/api/*` als JSON API
- `/api/docs` als Swagger UI, wenn aktiviert
- `/health/live` fuer Liveness
- `/health/ready` fuer Readiness inklusive Datenbank- und Migrationspruefung
- alle anderen Pfade als Angular SPA Fallback
## Datenmodell und Migrationen
TypeORM laeuft mit:
- `synchronize: false`
- `migrationsRun: false`
- expliziten Migrationen unter `apps/backend/src/database/migrations`
Der normale App-Start fuehrt keine Migrationen aus. Stattdessen prueft
`MigrationHealthService`, ob Migrationen fehlen. Dadurch wird verhindert, dass
ein App-Rollout unbemerkt ein Schema veraendert.
## Autorisierung
Permissions werden in `apps/backend/src/roles/permissions.ts` definiert. Rollen
sind Datenbankdaten, Benutzer erhalten Permissions nur ueber Rollen.
Systemrollen:
- `admin`: alle Permissions
- `user`: Basisrechte fuer Items-Lesen und eigene Sessions
Die Standardrolle `user` enthaelt ausserdem
`notifications.readOwn` und `notifications.updateOwn`, damit angemeldete
Benutzer ihre eigenen In-App-Benachrichtigungen verwalten koennen.
Der erste erfolgreich angemeldete Benutzer wird automatisch Admin. Danach
erhalten neue Benutzer initial die Rolle `user`.
Details zu Admin-Endpunkten, Systemrollen und dem transaktionalen Schutz des
letzten aktiven Administrators stehen in [Adminbereich](admin.md).

118
docs/configuration.md Normal file
View File

@@ -0,0 +1,118 @@
# Konfiguration
Die Runtime-Konfiguration wird ueber Umgebungsvariablen geladen und mit Zod in
`apps/backend/src/config/env.ts` validiert. `.env.example` ist die Referenz fuer
lokale Entwicklung und Deployment-Vorlagen.
## Allgemein
| Variable | Bedeutung |
| ------------------- | -------------------------------------------------------------------------- |
| `NODE_ENV` | `development`, `test` oder `production` |
| `PORT` | HTTP-Port des Backends |
| `APP_BASE_URL` | Externe Basis-URL der App; Grundlage fuer OIDC Callback |
| `FRONTEND_BASE_URL` | Frontend-Origin fuer Redirects und CORS; faellt auf `APP_BASE_URL` zurueck |
| `TRUST_PROXY` | Express Trust Proxy, wenn hinter Reverse Proxy |
## Datenbank
| Variable | Bedeutung |
| ------------------- | --------------------------- |
| `DATABASE_HOST` | MySQL Host |
| `DATABASE_PORT` | MySQL Port, Standard `3306` |
| `DATABASE_NAME` | Datenbankname |
| `DATABASE_USER` | Datenbankbenutzer |
| `DATABASE_PASSWORD` | Datenbankpasswort |
| `DATABASE_SSL` | TLS fuer MySQL-Verbindung |
Die Datenbank muss MySQL 8 und `utf8mb4` unterstuetzen. Migrationen werden
separat ausgefuehrt.
## OIDC
| Variable | Bedeutung |
| ------------------------- | ------------------------------------------------------- |
| `OIDC_ISSUER` | Exakter Issuer des Providers |
| `OIDC_CLIENT_ID` | Client ID |
| `OIDC_CLIENT_SECRET` | Client Secret |
| `OIDC_SCOPES` | Scopes, typischerweise `openid profile email` |
| `OIDC_LOGOUT_URL` | Optionale IdP-Logout-URL, falls Discovery keine liefert |
| `OIDC_ALLOWED_ALGORITHMS` | Erlaubte ID-Token-Signaturalgorithmen, z. B. `RS256` |
| `OIDC_HTTP_TIMEOUT_MS` | Timeout fuer IdP-HTTP-Requests |
Der Callback ist immer:
```text
<APP_BASE_URL>/api/auth/callback
```
Beim App-Logout wird zuerst die lokale Session widerrufen. Danach leitet die App
zum OIDC `end_session_endpoint` aus Discovery weiter. Wenn der Provider diesen
Endpoint nicht publiziert, kann `OIDC_LOGOUT_URL` gesetzt werden. Die App sendet
`client_id`, `post_logout_redirect_uri` und, falls vorhanden, `id_token_hint`.
## Sessions und CSRF
| Variable | Bedeutung |
| ---------------------------------- | --------------------------------------------------------- |
| `SESSION_COOKIE_NAME` | Name des signierten Session-Cookies |
| `SESSION_IDLE_TIMEOUT_SECONDS` | Inaktivitaetsablauf |
| `SESSION_ABSOLUTE_TIMEOUT_SECONDS` | Absoluter Ablauf unabhaengig von Aktivitaet |
| `SESSION_SECRET` | Secret fuer Cookie-Signatur, mindestens 32 Zeichen |
| `SESSION_ENCRYPTION_KEY` | Secret fuer Token-Verschluesselung, mindestens 32 Zeichen |
| `CSRF_HEADER_NAME` | Header fuer CSRF-Token, Standard `X-CSRF-Token` |
In Produktion duerfen diese Secrets keine Beispielwerte enthalten. Die
Konfiguration bricht bei offensichtlichen Platzhaltern ab.
## CORS, Logging, Swagger und Rate Limits
| Variable | Bedeutung |
| ------------------------------------- | ---------------------------------------- |
| `CORS_ORIGINS` | Kommagetrennte erlaubte Origins |
| `LOG_LEVEL` | Pino Log Level |
| `SWAGGER_ENABLED` | Swagger UI und JSON aktivieren |
| `RATE_LIMIT_WINDOW_SECONDS` | Globales IP-Zeitfenster |
| `RATE_LIMIT_MAX_REQUESTS` | Globale Requests pro IP und Zeitfenster |
| `RATE_LIMIT_SENSITIVE_WINDOW_SECONDS` | Zeitfenster fuer sensible Endpunkte |
| `RATE_LIMIT_SENSITIVE_MAX_REQUESTS` | Requests pro IP auf sensiblen Endpunkten |
`SWAGGER_ENABLED` sollte in Produktion nur bewusst aktiviert werden. Das
eingebaute Rate Limiting ist In-Memory, pro Prozess und nicht fuer horizontale
Skalierung koordiniert. Es ist fuer genau eine Containerinstanz ausgelegt.
## Beispiel fuer Produktion
```env
NODE_ENV=production
PORT=3000
APP_BASE_URL=https://business.example.com
FRONTEND_BASE_URL=https://business.example.com
TRUST_PROXY=true
DATABASE_HOST=mysql.internal
DATABASE_PORT=3306
DATABASE_NAME=business_app
DATABASE_USER=business_app
DATABASE_PASSWORD=<secret>
DATABASE_SSL=true
OIDC_ISSUER=https://idp.example.com/realms/internal
OIDC_CLIENT_ID=business-app
OIDC_CLIENT_SECRET=<secret>
OIDC_SCOPES=openid profile email
OIDC_LOGOUT_URL=https://idp.example.com/realms/internal/protocol/openid-connect/logout
OIDC_ALLOWED_ALGORITHMS=RS256
OIDC_HTTP_TIMEOUT_MS=5000
SESSION_COOKIE_NAME=app_session
SESSION_IDLE_TIMEOUT_SECONDS=28800
SESSION_ABSOLUTE_TIMEOUT_SECONDS=604800
SESSION_SECRET=<secret>
SESSION_ENCRYPTION_KEY=<secret>
CORS_ORIGINS=https://business.example.com
CSRF_HEADER_NAME=X-CSRF-Token
LOG_LEVEL=info
SWAGGER_ENABLED=false
RATE_LIMIT_WINDOW_SECONDS=60
RATE_LIMIT_MAX_REQUESTS=300
RATE_LIMIT_SENSITIVE_WINDOW_SECONDS=60
RATE_LIMIT_SENSITIVE_MAX_REQUESTS=10
```

139
docs/deployment.md Normal file
View File

@@ -0,0 +1,139 @@
# Deployment und Betrieb
Die Anwendung wird als ein Docker-Image ausgeliefert. Das Image enthaelt das
gebaute NestJS-Backend und das gebaute Angular-Frontend.
## Release-Build
Vor jedem Release:
```bash
npm ci
npm run lint
npm run format:check
npm run typecheck
npm test
npm run build
docker build -t registry.example.com/business-app:<tag> .
```
`npm run build` fuehrt aus:
1. API-Client generieren
2. API-Client bauen
3. Backend bauen
4. Frontend bauen
5. Frontend nach `apps/backend/dist/public` kopieren
Das Dockerfile baut erneut im Container und erzeugt anschliessend ein schlankes
Runtime-Image mit Non-Root-User.
## Runtime-Konfiguration
Konfiguration erfolgt ueber Umgebungsvariablen. Nutze `.env.example` nur als
Vorlage. Production-Secrets gehoeren in Secret Management, CI/CD Variables oder
eine geschuetzte Server-Konfiguration.
Wichtige Produktionswerte:
- `NODE_ENV=production`
- `APP_BASE_URL=https://app.example.com`
- `FRONTEND_BASE_URL=https://app.example.com`
- `TRUST_PROXY=true`, wenn hinter Reverse Proxy
- `DATABASE_SSL=true`, wenn die Datenbank TLS erzwingt
- `SWAGGER_ENABLED=false`, ausser bewusst anders entschieden
- starke Werte fuer `SESSION_SECRET`, `SESSION_ENCRYPTION_KEY`, `OIDC_CLIENT_SECRET`
## Migrationen
Migrationen werden nicht beim App-Start ausgefuehrt. Fuehre sie vor dem neuen
App-Container aus, mit demselben Image und derselben Konfiguration:
```bash
docker run --rm --env-file .env registry.example.com/business-app:<tag> node apps/backend/dist/database/run-migrations.js
```
Danach den App-Container starten oder aktualisieren. Wenn Migrationen fehlen,
schlaegt der App-Start fehl beziehungsweise `/health/ready` bleibt nicht bereit.
## Container starten
Ein einfaches Compose-Beispiel liegt in `compose.yml`:
```bash
docker compose up -d
```
Der Container:
- laeuft als Non-Root-User
- verwendet ein read-only Root Filesystem
- nutzt `/tmp` als tmpfs
- dropt Linux Capabilities
- prueft `/health/ready` als Healthcheck
Compose baut oder nutzt das Image `internal-business-app:latest`. Fuer echte
Deployments sollte ein versioniertes Registry-Image verwendet werden.
## Reverse Proxy
Nginx oder ein Load Balancer sollte TLS terminieren und alle Routen an den
App-Container weiterleiten. Die Angular-Dateien werden nicht separat durch Nginx
ausgeliefert. Ein Beispiel liegt in `docs/nginx-example.conf`.
Wichtig:
- WebSocket-Konfiguration ist fuer dieses Boilerplate nicht erforderlich.
- `X-Forwarded-Proto` und `X-Forwarded-For` sollten gesetzt werden.
- `APP_BASE_URL` muss zur externen HTTPS-URL passen.
- Wenn `TRUST_PROXY=true` gesetzt ist, muss der Proxy vertrauenswuerdig sein.
## Healthchecks
- `/health/live`: Prozess lebt.
- `/health/ready`: Datenbank erreichbar, Migration-Check initialisiert und keine
offenen Migrationen.
Readiness ist der richtige Check fuer Rolling Deployments und Container
Orchestrierung.
## TeamCity-Beispiel
1. Checkout
2. `npm ci`
3. `npm run lint`
4. `npm run format:check`
5. `npm run typecheck`
6. `npm test`
7. `npm run build`
8. `docker build -t <registry>/<image>:<build-number> .`
9. `docker push <registry>/<image>:<build-number>`
10. Migration-Job mit neuem Image ausfuehren
11. App-Service auf neues Image aktualisieren
12. `/health/ready` pruefen
## Rollback
Ein Rollback ist nur dann einfach, wenn die Datenbankmigrationen rueckwaerts
kompatibel geplant wurden. Fuer riskante Schema-Aenderungen sollte das Expand-
Contract-Muster verwendet werden:
1. Neue Spalten oder Tabellen hinzufuegen, alte weiter bedienen.
2. Anwendung umstellen.
3. Daten migrieren.
4. Alte Spalten oder Pfade in einem spaeteren Release entfernen.
## Betrieb
Beobachte mindestens:
- HTTP-Fehlerraten und Latenzen
- Healthcheck-Status
- Datenbankverbindungen
- Login-Fehler vom OIDC Provider
- `MIGRATION_MISSING`
- Rate-Limit-Treffer, insbesondere `RATE_LIMIT_EXCEEDED` auf Login- und Admin-Endpunkten
- Audit-Log fuer administrative Aktionen
Logs sollten zentral gesammelt werden. Request IDs helfen dabei, Frontend-Fehler,
Backend-Logs und Audit-Eintraege zusammenzufuehren.

114
docs/design-system.md Normal file
View File

@@ -0,0 +1,114 @@
# Designsystem
Das Frontend verwendet ein eigenes, schlankes Designsystem ohne externe
UI-Library. Es besteht aus zentralen CSS Custom Properties, globalen
Grundklassen und wenigen Angular-UI-Komponenten unter
`apps/frontend/src/app/shared/ui`.
## Prinzipien
- sachliche Business-Oberflaeche statt Marketing-Optik
- mobile first, keine globale Mindestbreite
- klare Hierarchie durch Typografie, Abstand und Rahmen
- Farben immer semantisch ueber Tokens
- sichtbare Fokuszustaende und grosse Touch-Flaechen
- Komponenten nur dort, wo sie Verhalten oder Wiederverwendung bringen
## Tokens
Die Tokens liegen in `apps/frontend/src/styles/_tokens.scss` und werden ueber
`apps/frontend/src/styles.scss` eingebunden.
Wichtige Gruppen:
- Farben: `--color-primary`, `--color-danger`, `--color-surface`,
`--color-text-primary`, `--color-border`, `--color-focus`
- Abstaende: `--space-1` bis `--space-9`
- Typografie: `--font-size-xs` bis `--font-size-2xl`, `--line-height-*`,
`--font-weight-*`
- Layout: `--container-width`, `--sidebar-width`, `--header-height`,
`--touch-target`, `--input-height`, `--button-height`
- Oberflaeche: `--radius-*`, `--shadow-*`, `--z-*`, `--transition-*`
Feature-Komponenten duerfen keine direkten Hex-Farben enthalten. Neue Farben
werden zuerst als semantische Tokens angelegt.
## Globale Klassen
Globale Klassen sind bewusst begrenzt:
- Layout: `.ui-page`, `.ui-page-header`, `.ui-grid`, `.ui-card`,
`.ui-toolbar`, `.ui-actions`
- Formulare: `.ui-form`, `.ui-form-field`, `.ui-control`, `.ui-checkbox`,
`.ui-field-error`
- Buttons: `.ui-button`, `.ui-icon-button`
- Tabellen: `.ui-table-wrap`, `.ui-table`
- Status: `.ui-badge`, `.ui-badge--success`, `.ui-badge--warning`,
`.ui-badge--danger`, `.ui-badge--info`
- Utilities: `.visually-hidden`, `.truncate`, `.stack`, `.cluster`,
`.full-width`, `.text-muted`
Keine neuen Utility-Klassen einfuehren, wenn eine lokale Klasse oder bestehende
UI-Komponente ausreicht.
## Angular-Komponenten
Wiederverwendbare UI-Bausteine:
- `UiButtonComponent`
- `UiIconComponent`
- `UiIconButtonComponent`
- `UiFormFieldComponent`
- `UiStatusBadgeComponent`
- `UiConfirmDialogComponent`
- `ToastService` und `UiToastHostComponent`
- `UiEmptyStateComponent`
- `UiLoadingStateComponent`
- `UiPaginationComponent`
- `UiPageHeaderComponent`
Neue Feature-Seiten sollen diese Bausteine bevorzugen, wenn sie Button-, Badge-,
Dialog-, Toast-, Empty-, Loading- oder Pagination-Verhalten brauchen.
## Responsive Regeln
- Mobile Layouts sind einspaltig.
- Aktionen duerfen mobil untereinander stehen und volle Breite nutzen.
- Business-Listen werden mobil als Karten dargestellt.
- Tabellen liegen in `.ui-table-wrap`, wenn eine echte Tabelle sinnvoll bleibt.
- Touch-Ziele orientieren sich an `--touch-target`.
- Breakpoints werden in rem formuliert und nicht nach Geraetetyp benannt.
## Accessibility
- native HTML-Elemente vor ARIA verwenden
- interaktive Elemente sind Buttons oder Links
- sichtbare Fokuszustaende nicht entfernen
- Labels ersetzen Placeholder nicht
- Fehlertexte stehen direkt am Feld
- Status ist nicht nur Farbe, sondern auch Text/Marker
- Dialoge setzen Fokus, schliessen per Escape und geben Fokus zurueck
- Navigation und Drawer sind per Tastatur bedienbar
- Animationen respektieren `prefers-reduced-motion`
## Entwicklungsseite
Die interne Referenzseite liegt unter:
```text
/dev/design-system
```
Sie ist mit `devOnlyGuard` geschuetzt und im Production-Modus nicht matchbar.
Sie ersetzt kein Storybook, sondern zeigt die vorhandenen Tokens und Komponenten
innerhalb der echten Anwendung.
## Regeln fuer neue UI
1. Bestehende UI-Komponenten oder globale Klassen wiederverwenden.
2. Keine direkte Hex-Farbe in Feature-Komponenten.
3. Keine neue UI-Library einfuehren.
4. Keine tiefen Selektoren, kein `::ng-deep`, kein unkontrolliertes
`!important`.
5. Keine klickbaren `div`-Elemente als Ersatz fuer Buttons oder Links.
6. Verhalten mit Vitest testen, insbesondere Accessibility-relevante Zustaende.

119
docs/development.md Normal file
View File

@@ -0,0 +1,119 @@
# Entwicklung
Diese Datei beschreibt die normalen Entwicklungsablaeufe im Monorepo.
## Installation
```bash
npm ci
```
Abhaengigkeiten werden nur im Root installiert. Workspace-spezifische Befehle
werden ueber Root-Scripts oder `npm --workspace ...` ausgefuehrt.
## Lokaler Start
```bash
npm run dev
```
Alternativ getrennt:
```bash
npm run dev:frontend
npm run dev:backend
```
Das Frontend nutzt den Proxy in `apps/frontend/proxy.conf.json`, damit `/api`
lokal an das Backend weitergereicht wird.
## Feature-Workflow
1. Backend-DTOs und Entities modellieren.
2. Repository fuer Datenzugriff erstellen oder erweitern.
3. Service mit Fachlogik implementieren.
4. Controller nur als HTTP-Rand und Permission-Deklaration verwenden.
5. Permission in `roles/permissions.ts` ergaenzen, falls noetig.
6. Migration erzeugen und kontrollieren.
7. API-Client-Generator aktualisieren.
8. Frontend-Route, Navigation und Page bauen.
9. Tests fuer Verhalten schreiben.
10. Qualitaetsbefehle ausfuehren.
## Migrationen
Status anzeigen:
```bash
npm run migration:status
```
Migration erzeugen:
```bash
npm run migration:generate
```
Migration ausfuehren:
```bash
npm run migration:run
```
Generierte Migrationen muessen reviewed werden. Sie duerfen keine
versehentlichen Datenverluste, falsche Defaults oder umgebungsspezifische Namen
enthalten.
## API-Client
Nach API-Aenderungen:
```bash
npm run api:generate
```
Der Client unter `packages/api-client/src` ist generiert. Aendere stattdessen den
Generator oder ersetze ihn spaeter bewusst durch einen OpenAPI-basierten
Generator.
## Tests
```bash
npm test
npm run test:backend
npm run test:frontend
```
Tests sollen Verhalten pruefen, nicht nur Existenz. Backend-Tests fuer Services
sollen fachliche Regeln, Fehlerfaelle, Permissions und Datenbankinteraktionen
abdecken. Frontend-Tests sollen sichtbares Verhalten, Guards, Interaktionen und
API-Fehlerpfade abdecken.
Integrationstests gegen MySQL muessen eine separate Testdatenbank verwenden,
deren Name eindeutig `test` enthaelt. Tests duerfen niemals gegen
Produktionsdatenbanken laufen.
## Qualitaet vor Abschluss
```bash
npm run lint
npm run format:check
npm run typecheck
npm test
npm run build
docker build .
```
Diese Befehle sind die Mindestpruefung fuer Merge und Release.
## Fehlerformat
Backend-Fehler werden ueber `ApiExceptionFilter` in ein konsistentes Format
gebracht. Neue fachliche Fehler sollten `ApiError` und `ErrorCode` verwenden,
damit Frontend und Logs stabil bleiben.
## Logging
`pino-http` redigiert sensible Header und Token-Felder. Neue Logs duerfen keine
Secrets, Session-IDs, Cookies, Access Tokens, Refresh Tokens oder ID Tokens
enthalten.

93
docs/getting-started.md Normal file
View File

@@ -0,0 +1,93 @@
# Getting Started
Diese Anleitung bringt eine lokale Entwicklungsumgebung fuer das Boilerplate zum
Laufen. Sie setzt voraus, dass MySQL 8 und ein OIDC Provider bereits verfuegbar
sind.
## Voraussetzungen
- Node.js `24.18.0` aus `.nvmrc`
- npm `11.x`
- Docker fuer Image-Builds und spaetere Auslieferung
- MySQL 8 mit `utf8mb4`
- OIDC Client mit Authorization Code Flow, PKCE und Discovery Endpoint
Das Repository ist ein npm-Workspace-Monorepo. Abhaengigkeiten werden immer aus
dem Root installiert.
```bash
npm ci
cp .env.example .env
```
## Lokale Konfiguration
Trage in `.env` mindestens folgende Werte ein:
- `DATABASE_HOST`, `DATABASE_PORT`, `DATABASE_NAME`, `DATABASE_USER`, `DATABASE_PASSWORD`
- `OIDC_ISSUER`, `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`
- `SESSION_SECRET` mit mindestens 32 zufaelligen Zeichen
- `SESSION_ENCRYPTION_KEY` mit mindestens 32 zufaelligen Zeichen
- `APP_BASE_URL=http://localhost:3000`
- `FRONTEND_BASE_URL=http://localhost:4200`
- `CORS_ORIGINS=http://localhost:4200,http://localhost:3000`
Der OIDC Provider muss als Redirect URI diese URL erlauben:
```text
http://localhost:3000/api/auth/callback
```
Falls der Provider RP-Initiated Logout validiert, muss ausserdem
`http://localhost:4200` beziehungsweise die konfigurierte `FRONTEND_BASE_URL` als
Post-Logout-Redirect erlaubt sein. Wenn Discovery keinen `end_session_endpoint`
liefert, setze `OIDC_LOGOUT_URL`.
## Datenbank vorbereiten
Die Anwendung fuehrt Migrationen beim normalen Start nicht automatisch aus.
Fuehre sie bewusst aus:
```bash
npm run migration:status
npm run migration:run
```
Wenn Migrationen fehlen, verweigert das Backend den Start beziehungsweise
`/health/ready` bleibt nicht bereit.
## Entwicklung starten
```bash
npm run dev
```
Das startet:
- Frontend: `http://localhost:4200`
- Backend: `http://localhost:3000`
- API: `http://localhost:3000/api`
- Health: `http://localhost:3000/health/live` und `/health/ready`
- Swagger, falls `SWAGGER_ENABLED=true`: `http://localhost:3000/api/docs`
Das Frontend proxyt `/api` ueber `apps/frontend/proxy.conf.json` an das Backend.
Dadurch kann lokal mit Cookie-basierter Authentifizierung gearbeitet werden.
## Erster Login
1. Oeffne `http://localhost:4200`.
2. Melde dich ueber den OIDC Provider an.
3. Der erste lokal angelegte Benutzer erhaelt automatisch die Rollen `user` und
`admin`.
4. Weitere Benutzer erhalten initial die Rolle `user`.
Systemrollen und Permissions werden beim Login synchronisiert. Permissions sind
im Code definiert und werden nicht frei in der UI angelegt.
## Haefige Probleme
- `Ungueltige Konfiguration`: `.env` verletzt das Schema in `apps/backend/src/config/env.ts`.
- `MIGRATION_MISSING`: `npm run migration:run` ausfuehren.
- `UNAUTHORIZED`: Session abgelaufen, Benutzer deaktiviert oder OIDC-Konfiguration falsch.
- `CSRF_INVALID`: Schreibender Request ohne `X-CSRF-Token`; im Angular-Client erledigt das der Interceptor.
- OIDC Callback schlaegt fehl: Redirect URI, Issuer, Client Secret und erlaubte Algorithmen pruefen.

32
docs/nginx-example.conf Normal file
View File

@@ -0,0 +1,32 @@
server {
listen 443 ssl http2;
server_name app.example.com;
ssl_certificate /etc/letsencrypt/live/app.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/app.example.com/privkey.pem;
client_max_body_size 1m;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Request-ID $request_id;
}
}
server {
listen 80;
server_name app.example.com;
return 301 https://$host$request_uri;
}

146
docs/notifications.md Normal file
View File

@@ -0,0 +1,146 @@
# Benachrichtigungen
Das Modul `apps/backend/src/notifications` stellt persoenliche In-App-
Benachrichtigungen bereit. Es gibt bewusst keinen E-Mail-Versand, keine Push
Notifications, keine WebSockets, keine Server-Sent Events, keine Queue, kein
Redis und keine Hintergrundjobs. Neue Benachrichtigungen werden beim Laden der
Anwendung und per Frontend-Polling abgerufen.
## Datenmodell
Die Entity `NotificationEntity` wird in der Tabelle `notifications` gespeichert.
Jede Benachrichtigung gehoert genau einem Benutzer.
Felder:
- `id`: UUID
- `userId`: Foreign Key auf `users.id`
- `type`: technischer Typ aus `NotificationType`
- `title`: kurzer Plain-Text-Titel, maximal 150 Zeichen
- `message`: Plain-Text-Nachricht, maximal 1000 Zeichen
- `link`: optionale interne Angular-Route, maximal 500 Zeichen
- `metadata`: optionale JSON-Metadaten, maximal 4096 Bytes serialisiert
- `readAt`: `null`, solange ungelesen
- `createdAt`: UTC-Erstellungszeit
- `deletedAt`: Soft Delete
Indizes:
- `user_id, created_at`
- `user_id, read_at`
- `user_id, deleted_at`
Die Migration liegt unter
`apps/backend/src/database/migrations/1720000001000-AddNotifications.ts` und wird
nicht automatisch beim App-Start ausgefuehrt.
## Typen
Benachrichtigungstypen sind Code, keine Datenbankdaten:
- `system`
- `item.created`
- `item.updated`
- `user.role-changed`
Neue Module erweitern `apps/backend/src/notifications/notification-types.ts`
und nutzen anschliessend `NotificationsService`.
## Permissions
- `notifications.readOwn`: eigene Benachrichtigungen lesen
- `notifications.updateOwn`: eigene Benachrichtigungen markieren oder loeschen
- `notifications.manage`: administrativ Benachrichtigungen erzeugen
Die Systemrolle `user` erhaelt `notifications.readOwn` und
`notifications.updateOwn`. Die Rolle `admin` erhaelt ueber `allPermissions`
zusaetzlich `notifications.manage`.
## API
Alle Endpunkte liegen unter dem bestehenden API-Praefix `/api`.
- `GET /notifications`: eigene Benachrichtigungen, mit `page`, `pageSize` und
`status=all|read|unread`
- `GET /notifications/unread-count`: Anzahl ungelesener eigener
Benachrichtigungen
- `PATCH /notifications/:id/read`: idempotent als gelesen markieren
- `PATCH /notifications/:id/unread`: idempotent als ungelesen markieren
- `PATCH /notifications/read-all`: alle eigenen Benachrichtigungen als gelesen
markieren
- `DELETE /notifications/:id`: Soft Delete einer eigenen Benachrichtigung
- `POST /admin/notifications`: administrative Erzeugung mit
`notifications.manage`
Normale Benutzer koennen keine fremde `userId` uebergeben. Der aktuelle Benutzer
wird serverseitig aus der Session bestimmt.
## Interner Service
Andere Backend-Module erzeugen Benachrichtigungen ueber
`NotificationsService`, nicht direkt ueber ein Repository:
```ts
await notifications.createForUser({
userId,
type: NotificationType.ItemCreated,
title: 'Neuer Eintrag',
message: 'Der Eintrag "Beispiel" wurde erstellt.',
link: '/items/123',
metadata: { itemId: '123' },
});
```
Verfuegbare Methoden:
- `createForUser`
- `createForUsers`
- `getForCurrentUser`
- `getUnreadCount`
- `markAsRead`
- `markAsUnread`
- `markAllAsRead`
- `softDelete`
`createForUsers` begrenzt Bulk-Erzeugung auf 100 Zielbenutzer.
## Integrationen
`ItemsService` benachrichtigt beim Erstellen eines Items den ersten aktiven
Administrator, sofern dieser nicht der Ersteller ist. Das ist eine bewusst kleine
Beispielregel und keine vollstaendige fachliche Eskalationslogik.
`UsersService` benachrichtigt betroffene Benutzer nach erfolgreicher
Rollenaenderung. Fehler beim Erzeugen werden geloggt; die bereits erfolgreiche
sicherheitsrelevante Rollenaenderung wird dadurch nicht unkontrolliert
zurueckgerollt.
Administrative Erzeugung wird im Audit-Log als `NOTIFICATION_CREATED`
protokolliert. Geloggt werden Actor, Zielbenutzer, Notification-ID, Typ und
Request-ID, nicht die vollstaendige Nachricht oder Metadata.
## Frontend
`NotificationStore` verwendet Angular Signals fuer lokalen State und RxJS fuer
HTTP und Polling. Header-Panel und Seite `/notifications` verwenden denselben
Store, damit keine doppelten Requests fuer dieselben Daten entstehen.
Polling:
- Standardintervall: 60 Sekunden ueber `NOTIFICATION_POLL_INTERVAL_MS`
- nur bei angemeldetem Benutzer
- pausiert bei unsichtbarem Tab
- aktualisiert sofort beim Sichtbarwerden
- verhindert ueberlappende Count-Requests
- stoppt und leert State beim Logout
Links werden im Frontend nur navigiert, wenn sie interne relative Routen sind.
Titel und Nachricht werden normal interpoliert und nicht per `innerHTML`
gerendert.
## Spaetere Echtzeitkommunikation
Wenn spaeter echte Echtzeitkommunikation noetig wird, sollte das als separate
Architekturentscheidung erfolgen. Dann waeren Transport, Skalierung,
Authentifizierung, Backpressure und Betrieb gemeinsam zu entscheiden, statt
WebSockets oder Queues nebenbei in das In-App-Modul einzubauen.

135
docs/security.md Normal file
View File

@@ -0,0 +1,135 @@
# Security-Modell
Das Boilerplate trennt Browser, Backend und Identity Provider strikt. Der Browser
bekommt keine OIDC-Tokens. Das Backend ist fuer Authentifizierung,
Autorisierung, CSRF und Session-Verwaltung verbindlich.
## OIDC
Der Login nutzt Authorization Code Flow mit PKCE:
1. Browser ruft `/api/auth/login` auf.
2. Backend erzeugt `state`, `nonce`, `code_verifier` und leitet zum IdP weiter.
3. IdP ruft `/api/auth/callback` mit `code` und `state` auf.
4. Backend tauscht den Code gegen Tokens.
5. Backend validiert ID Token, Issuer, Audience, Nonce und Algorithmus.
6. Backend laedt optional UserInfo.
7. Backend legt oder aktualisiert den lokalen Benutzer.
8. Backend erzeugt eine serverseitige Session.
Erlaubte Signaturalgorithmen werden ueber `OIDC_ALLOWED_ALGORITHMS` gesetzt.
`none` ist explizit verboten.
Beim Logout wird zuerst die lokale Session widerrufen und das Session-/CSRF-Cookie
geloescht. Anschliessend redirectet das Backend zum OIDC
`end_session_endpoint` aus Discovery oder zur optionalen `OIDC_LOGOUT_URL`. Wenn
die Session ein ID-Token enthaelt, wird es nur als `id_token_hint` an den IdP
gegeben und nicht an das Frontend ausgeliefert.
## Sessions
Sessions liegen in MySQL. Das Session-Cookie enthaelt keine Tokens oder
Benutzerdaten, sondern nur eine signierte Session-ID.
Gespeichert werden unter anderem:
- verschluesselte OIDC-Tokens
- Hash des CSRF-Tokens
- Ablaufzeiten fuer Idle Timeout und absolutes Timeout
- User-Agent und IP fuer Anzeige und Audit-Kontext
- Revocation-Zeitpunkt
Die Token-Verschluesselung nutzt `SESSION_ENCRYPTION_KEY`. Cookie-Signaturen
nutzen `SESSION_SECRET`. Beide Werte muessen in Produktion echte Secrets sein.
## CSRF
Schreibende Methoden `POST`, `PUT`, `PATCH` und `DELETE` brauchen ein gueltiges
CSRF-Token. Das Frontend liest das CSRF-Cookie und sendet es als
`X-CSRF-Token`. Das Backend vergleicht nur den Hash gegen die Session.
Public Routes sind vom CSRF-Guard ausgenommen, sofern sie mit `@Public()`
markiert sind.
## Rollen und Permissions
Permissions werden in `apps/backend/src/roles/permissions.ts` definiert. Rollen
sind Datenbankdaten und referenzieren diese Permissions. Benutzer haben keine
direkten Permissions.
Backend-Controller schuetzen Endpunkte mit:
```ts
@RequirePermissions(Permission.ItemsRead)
```
Angular nutzt Permissions nur fuer Navigation und Darstellung. Eine versteckte
Schaltflaeche ist keine Sicherheitsgrenze.
Administrative Benutzer-, Rollen- und Session-Aktionen sind unter `/api/admin/*`
mit `users.read`, `users.manage`, `roles.read`, `roles.manage`,
`sessions.manage` und `audit.read` geschuetzt. Aktionen, die den letzten aktiven
Administrator entfernen koennten, laufen transaktional und antworten bei Verstoss
mit `LAST_ACTIVE_ADMIN_REQUIRED`.
Benachrichtigungen sind benutzerbezogene Daten. Normale Notification-Endpunkte
bestimmen den Benutzer ausschliesslich aus der serverseitig aufgeloesten Session.
Benutzer-IDs werden fuer eigene Benachrichtigungen nicht als Query-Parameter oder
Body-Feld akzeptiert. Fremde oder geloeschte Notification-IDs werden als nicht
gefunden behandelt.
## Benutzerstatus
Deaktivierte Benutzer werden trotz erfolgreichem IdP-Login abgewiesen. Aktive
Sessions deaktivierter Benutzer werden beim Session-Resolve unbrauchbar gemacht.
## Cookies und Proxy
In Produktion sollte TLS vor der Anwendung terminiert werden, zum Beispiel mit
Nginx. Setze `TRUST_PROXY=true`, wenn die Anwendung hinter einem vertrauenswuerdigen
Reverse Proxy laeuft und korrekte Forwarded Header benoetigt.
`APP_BASE_URL` muss die externe URL der Anwendung enthalten, weil daraus die OIDC
Callback URL gebaut wird.
## Logging und Redaction
Das Backend redigiert sensible Werte in Standard-HTTP-Logs:
- `Authorization`
- `Cookie`
- `X-CSRF-Token`
- `Set-Cookie`
- Access-, Refresh- und ID-Tokens
Neue Logs duerfen keine Secrets, Tokens, Session-IDs oder vollstaendige Cookies
enthalten. Fuer Auditing sollten fachliche IDs und Request IDs verwendet werden.
## Swagger
`SWAGGER_ENABLED` ist in Produktion standardmaessig `false`. Wenn Swagger in
Produktion aktiviert wird, sollte der Zugriff ueber Netzwerkregeln oder Reverse
Proxy zusaetzlich eingeschraenkt werden.
## Rate Limiting
Das konfigurierte Rate Limiting ist fuer eine einzelne Containerinstanz ohne
Redis ausgelegt. Die Zaehler liegen im Prozessspeicher. Bei horizontaler
Skalierung muss ein gemeinsamer Store eingefuehrt oder der Schutz an den Reverse
Proxy verlagert werden.
Es gibt zwei fruehe Ebenen:
- Globales IP-Limit fuer alle Requests, bevor Guards und Controller laufen.
- Strengeres IP-Limit fuer sensible Endpunkte, ebenfalls bevor Session-Resolve
und fachliche Datenbankzugriffe laufen.
Sensible Endpunkte werden mit `@SensitiveRateLimit()` markiert. Dazu gehoeren
Login, OIDC Callback, Logout, Session-Revoke sowie Benutzer- und
Rollenverwaltung. Bei Ueberschreitung antwortet die API mit HTTP 429,
`RATE_LIMIT_EXCEEDED`, `Retry-After` und `X-RateLimit-*` Headern.
Ein zusaetzliches Benutzer-Limit waere technisch moeglich, wuerde in diesem
Projekt aber erst nach dem serverseitigen Session-Resolve greifen. Fuer die
geforderte fruehe Lastreduktion ist deshalb das globale IP-Limit die verbindliche
Schutzschicht.

97
docs/using-as-template.md Normal file
View File

@@ -0,0 +1,97 @@
# Als Projektvorlage Verwenden
Dieses Boilerplate ist bewusst nah an einer realen internen Business-Anwendung.
Beim Start eines neuen Projekts solltest du zuerst Namen, Branding,
Runtime-Konfiguration und Demo-Fachlichkeit ersetzen, aber die Sicherheits- und
Betriebsentscheidungen beibehalten.
## Empfohlener Start
1. Repository kopieren oder als Template verwenden.
2. Paketnamen und Beschreibungen in `package.json`, `apps/*/package.json` und
`packages/api-client/package.json` anpassen.
3. UI-Name `Business App` in `apps/frontend/src/app/layout/app-shell.ts` ersetzen.
4. Swagger-Titel und Beschreibung in `apps/backend/src/main.ts` ersetzen.
5. Docker-Image-Namen in README, CI und Compose auf den Projektnamen aendern.
6. `.env.example` auf die Zielumgebung zuschneiden, aber keine Secrets eintragen.
7. Lizenz, Repository-URL, Ownership und Open-Source-Metadaten bewusst setzen.
## Was du behalten solltest
- Backend-for-Frontend Authentifizierung: OIDC-Tokens bleiben im Backend.
- Server-seitige Sessions in MySQL.
- CSRF-Schutz fuer alle schreibenden Requests.
- TypeORM-Migrationen statt `synchronize`.
- Controller delegieren an Services; Datenbankzugriff liegt in Repositories oder
klar benannten Persistence-Services.
- Permissions sind Code, Rollen sind Daten.
- Keine UI-Library als versteckte Projektbindung.
- Striktes TypeScript ohne `any`.
## Demo-Fachlichkeit entfernen oder ersetzen
Das Modul `items` ist ein Beispiel fuer ein einfaches fachliches CRUD-Modul mit
optimistischer Versionierung und Soft Delete. Fuer ein neues Projekt gibt es zwei
saubere Optionen:
- Behalten und in das erste echte Fachmodul umbenennen.
- Entfernen und anhand der Struktur ein neues Modul erstellen.
Wenn du `items` entfernst, pruefe mindestens:
- Backend-Import in `apps/backend/src/app.module.ts`
- Entity-Export in `apps/backend/src/database/entities.ts`
- Initiale Migration und neue Migration fuer Schema-Aenderungen
- Permissions in `apps/backend/src/roles/permissions.ts`
- Routen in `apps/frontend/src/app/app.routes.ts`
- Navigation in `apps/frontend/src/app/layout/app-shell.ts`
- API-Client-Generator in `scripts/generate-api-client.mjs`
- Frontend-Pages und Tests unter `apps/frontend/src/app/features/items`
## Ein neues Feature anlegen
Backend:
1. Feature-Ordner unter `apps/backend/src/<feature>` anlegen.
2. Entity, DTOs, Repository, Service, Controller und Module erstellen.
3. Controller nur mit Services verdrahten.
4. Datenbankzugriff im Repository kapseln.
5. Permissions in `roles/permissions.ts` ergaenzen.
6. Controller-Methoden mit `@RequirePermissions(...)` schuetzen.
7. Entity in `database/entities.ts` aufnehmen.
8. Migration erzeugen und pruefen.
9. Verhaltenstests fuer Service oder Controller schreiben.
Frontend:
1. Page unter `apps/frontend/src/app/features/<feature>` erstellen.
2. Route in `app.routes.ts` anlegen.
3. Navigation in `app-shell.ts` ergaenzen.
4. Permission im Route-`data` und in der Navigation konsistent setzen.
5. API-Client ueber `packages/api-client` verwenden.
6. Verhaltenstests fuer relevante UI-Logik schreiben.
API-Client:
Aktuell schreibt `scripts/generate-api-client.mjs` den Client reproduzierbar aus
einer gepflegten Vorlage. Wenn die API waechst, muss der Generator erweitert oder
durch eine echte OpenAPI-Codegenerierung ersetzt werden. Der Client selbst ist
generierter Code und sollte nicht manuell editiert werden.
## Open-Source-Veroeffentlichung
Vor einer Veroeffentlichung als Open-Source-Projekt:
- `license` in `package.json` bewusst setzen.
- `LICENSE` und optional `NOTICE` hinzufuegen.
- Namen, Screenshots, Domaenen, Beispiel-Registry und interne Hinweise entfernen.
- Beispiel-Secrets in `.env.example` nur als Platzhalter belassen.
- Pruefen, ob alle Dependencies und generierten Artefakte lizenzkompatibel sind.
- Sicherheitsmodell in README und `docs/security.md` aktuell halten.
## Projektgrenzen
Dieses Boilerplate ist fuer eine einzelne deploybare Business-Anwendung gebaut.
Es ist kein Microservice-Framework, kein Nx-Workspace und keine Multi-Tenant
Plattform. Wenn ein Projekt diese Grenzen braucht, sollte die Architektur zuerst
bewusst erweitert werden, statt sie indirekt ueber Feature-Code einzuschleppen.