docs: add architecture, roadmap, ADRs and CLAUDE.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
JannisandClaude Opus 5.5 committed 2026-10-09 17:53:33 +02:00
commit 9c35a3c5b7
10 files changed
+439

No files matched your search

+46
View File
@@ -0,0 +1,46 @@
# CLAUDE.md – FamilyPlanner
Digitaler Familienplaner (PWA) auf .NET 10 + Aspire 13. Anforderungen: `docs/ARCHITECTURE.md`, Fortschritt: `docs/ROADMAP.md`, Entscheidungen: `docs/adr/`.
## Befehle
```bash
dotnet build FamilyPlanner.slnx # muss ohne Warnungen bauen (TreatWarningsAsErrors)
dotnet test FamilyPlanner.slnx # alle Tests; Integrationstests brauchen Docker
dotnet test tests/FamilyPlanner.UnitTests # schnell, ohne Docker
aspire run # bzw. dotnet run --project src/FamilyPlanner.AppHost
aspire publish -o artifacts/compose # Docker-Compose aus dem App-Modell erzeugen
# Neue EF-Migration (aus dem Repo-Root)
dotnet ef migrations add <Name> -p src/FamilyPlanner.Infrastructure -s src/FamilyPlanner.MigrationService -o Persistence/Migrations
# Container-Images bauen
dotnet publish src/FamilyPlanner.Api -c Release /t:PublishContainer
dotnet publish src/FamilyPlanner.Web -c Release /t:PublishContainer
dotnet publish src/FamilyPlanner.MigrationService -c Release /t:PublishContainer
```
Lokaler Seed-Admin (AppHost-Parameter, siehe `src/FamilyPlanner.AppHost/appsettings.Development.json`): `admin` / `Admin123!ChangeMe` – Passwortwechsel beim ersten Login.
## Struktur
* `src/FamilyPlanner.Domain` – Entities, Rollen (`Roles`), Policies (`Policies`), reine Logik. Keine Abhängigkeiten auf EF/ASP.NET.
* `src/FamilyPlanner.Infrastructure` – `AppDbContext` (Identity), Konfigurationen unter `Persistence/Configurations`, Migrations unter `Persistence/Migrations`, Seeding.
* `src/FamilyPlanner.Api` – Minimal APIs als Vertical Slices unter `Features/<Feature>/`; jeder Slice hat eine `Map<Feature>Endpoints`-Erweiterung.
* `src/FamilyPlanner.Shared` – DTOs (records) für Api und Client.
* `src/FamilyPlanner.Web` – BFF: statische WASM-Auslieferung + YARP (`/api`, `/hubs`, `/ics` → Api).
* `src/FamilyPlanner.Web.Client` – Blazor WASM + MudBlazor. Seiten unter `Pages/`, API-Clients unter `Services/`.
* `src/FamilyPlanner.MigrationService` – migriert + seedet, beendet sich.
## Konventionen
* UI-Texte auf **Deutsch**; Code, Bezeichner und Commit-Typen auf Englisch.
* Commits: Conventional Commits (`feat:`, `fix:`, `docs:`, `test:`, `chore:`, `refactor:`, `ci:`).
* Alle Zeiten in UTC speichern (`DateTimeOffset`/UTC), Anzeige in Europe/Berlin. `TimeProvider` injizieren statt `DateTime.UtcNow`.
* Neue Entitäten erben von `AuditableEntity`; löschbare implementieren `ISoftDeletable`; Einkauf/Aufgaben/Termine bekommen `RowVersion`.
* Berechtigungen immer serverseitig über Policies (`RequireAuthorization(Policies.X)`), nie nur im UI. Zu jeder Policy Unit-Tests.
* Schreibende Requests vom Client tragen `X-Requested-With: FamilyPlanner` (wird vom `HttpClient`-Handler im Client gesetzt, siehe ADR-0006).
* Validierung mit FluentValidation im jeweiligen Slice.
* Pakete nur über `Directory.Packages.props` (keine Versionen in `.csproj`), nur stabile Versionen.
* Keine Secrets im Repo: User Secrets / Aspire-Parameter lokal, `.env` in Docker.
* Nach jeder Roadmap-Phase: Build, Tests, Aspire-Start prüfen, Roadmap abhaken, committen.
+169
View File
@@ -0,0 +1,169 @@
# Architektur – Digitaler Familienplaner (FamilyPlanner)
## 1. Überblick
FamilyPlanner ist eine Progressive Web App für Familien (Einkaufsliste, Essenplan, Aufgaben/Ämtchen,
Kalender mit ICS-Abo, Chat). Lokal wird alles über **.NET Aspire** orchestriert, produktiv läuft es per
**Docker Compose** auf einem Linux-Host.
```
Browser / installierte PWA (Blazor WebAssembly)
│ HTTPS, same-origin, Auth-Cookie
▼
┌───────────────────────────────────────────────────────────────┐
│ FamilyPlanner.Web (BFF) │
│ • liefert WASM-App, Manifest, Service Worker (statisch) │
│ • YARP: /api/**, /hubs/**, /ics/** ──────────────┐ │
│ • Security-Header (CSP, HSTS …) │ │
└───────────────────────────────────────────────────┼───────────┘
▼
┌───────────────────────────────────────────────────────────────┐
│ FamilyPlanner.Api │
│ • Minimal APIs (Vertical Slices pro Feature) │
│ • ASP.NET Core Identity + Cookie-Auth, Policies │
│ • SignalR Hubs (Chat, Live-Updates) │
│ • ICS-Feeds (Token-gesichert), ICS-Import │
└───────────────────────────┬───────────────────────────────────┘
│ EF Core 10
▼
SQL Server (Volume)
▲
┌───────────────────────────┴───────────────────────────────────┐
│ FamilyPlanner.MigrationService (Worker, läuft einmal) │
│ • wendet EF-Migrations an, seedet Rollen + Initial-Admin │
└───────────────────────────────────────────────────────────────┘
```
Siehe ADRs in [`docs/adr/`](adr/) für die Begründungen.
## 2. Projektstruktur
| Projekt | Zweck |
|---|---|
| `src/FamilyPlanner.AppHost` | Aspire-Orchestrierung: SQL Server (persistentes Volume), Datenbank, MigrationService, Api, Web; Docker-Compose-Publishing |
| `src/FamilyPlanner.ServiceDefaults` | OpenTelemetry, Health Checks (`/health`, `/alive`), Service Discovery, Resilience |
| `src/FamilyPlanner.Domain` | Entities, Value Objects, Rollen-/Berechtigungsregeln, reine Domänenlogik (keine Abhängigkeiten) |
| `src/FamilyPlanner.Infrastructure` | EF Core `AppDbContext`, Identity, Migrations, Seeding, Interceptors (Audit/Soft Delete) |
| `src/FamilyPlanner.Shared` | DTOs/Contracts zwischen Api und WASM-Client |
| `src/FamilyPlanner.Api` | Minimal APIs (`Features/<Feature>/…`), Auth, SignalR, ICS |
| `src/FamilyPlanner.MigrationService` | Worker: Migrations + Seeding beim Start, beendet sich danach |
| `src/FamilyPlanner.Web` | Blazor-Host/BFF: statische Auslieferung der WASM-App + YARP-Proxy |
| `src/FamilyPlanner.Web.Client` | Blazor WebAssembly (MudBlazor), PWA |
| `tests/FamilyPlanner.UnitTests` | Domänenlogik, Rollen/Policies, Parser, ICS |
| `tests/FamilyPlanner.IntegrationTests` | End-to-End über `Aspire.Hosting.Testing` (echter SQL-Server-Container) |
| `tests/FamilyPlanner.Web.Tests` | bUnit-Komponententests |
### Vertical Slices in der Api
```
FamilyPlanner.Api/
Features/
Auth/AuthEndpoints.cs # /api/auth/*
Users/… # /api/admin/users/* (Phase 2)
Shopping/… # /api/shopping/* (Phase 3)
Meals/… Tasks/… Calendar/… Chat/… Dashboard/…
Hubs/ # SignalR
Infrastructure/ # Auth-Setup, Security, Rate Limiting
```
Jeder Slice enthält Endpunkte, Validatoren (FluentValidation) und ggf. Handler. Geteilte Regeln liegen im Domain-Projekt.
## 3. Datenmodell (Ziel, wird phasenweise umgesetzt)
Alle Entitäten erben von `AuditableEntity` (`Id: Guid`, `CreatedAt`, `UpdatedAt`, `CreatedBy`, `UpdatedBy`).
Löschbare Entitäten implementieren `ISoftDeletable` (`IsDeleted`, `DeletedAt`) – globaler Query-Filter.
Einkaufsliste, Aufgaben und Termine haben eine `RowVersion` (Optimistic Concurrency).
Alle Zeitpunkte werden als UTC (`DateTimeOffset`/`DateTime` UTC) gespeichert und in Europe/Berlin angezeigt.
| Bereich | Entitäten |
|---|---|
| Identity | `AppUser` (DisplayName, Color, AvatarUrl, MustChangePassword, IsActive, ExpiresAt, PinHash), `AppRole` (Admin, Familie, Kind, Gast) |
| Einstellungen | `FamilySettings` (Name, TimeZone, MealPlanDays, MealTypes, PointsEnabled, ChildTasksNeedApproval, GuestCanEditShopping, GuestChatEnabled) |
| Einkauf | `ShoppingList` (Name, IsDefault), `ShoppingItem` (Text, Quantity, Category, IsDone, DoneAt, SortOrder), `ShoppingSuggestion` (Text, UseCount) |
| Essen | `MealPlanEntry` (Date, MealType, Dish, Note, RecipeUrl, Ingredients), `Dish` (Name, Ingredients – für Autovervollständigung), `MealWish` (von Kindern) |
| Aufgaben | `TodoTask` (Title, Notes, DueAt, HasTime, Priority, Status, Category, IsPrivate, IsChore, Points, NeedsApproval, RecurrenceRule, SeriesId), `TaskAssignment`, `TaskChecklistItem`, `TaskCompletion` (wer/wann/genehmigt), `TaskCategory`, `PointsLedger` |
| Kalender | `Calendar` (Name, Color), `CalendarPermission` (Rolle/Benutzer → Read/Write), `CalendarEvent` (Title, StartUtc, EndUtc, AllDay, Location, Notes, RRule, ExDates, Uid), `EventPersonTag`, `EventException` (Overrides), `IcsFeedToken` (Token-Hash, Scope, Owner, RevokedAt) |
| Chat | `ChatChannel` (Family / Direct), `ChatMember` (LastReadAt), `ChatMessage` (Text, Attachment), `ChatReaction` |
| Betrieb | `AuditLogEntry` (Who, Action, Target, Details, At), `PushSubscription` (vorbereitet) |
## 4. Rollen- und Berechtigungsmodell
Rollen: **Admin, Familie, Kind, Gast** (Konstanten in `FamilyPlanner.Domain.Roles`).
Berechtigungen werden als **Policies** formuliert (`FamilyPlanner.Domain.Authorization.Policies`) und
serverseitig per `RequireAuthorization(policy)` erzwungen. Feingranulare Regeln, die vom Objekt abhängen
(„nur eigene Einträge löschen“, „nur eigene Termine“), laufen über ressourcenbasierte Autorisierung
(`IAuthorizationHandler` mit Ressource) bzw. reine Domänenfunktionen, die unit-getestet sind.
| Policy | Rollen |
|---|---|
| `Authenticated` | alle aktiven Benutzer |
| `FamilyMember` | Admin, Familie, Kind |
| `Parent` (Familie verwalten) | Admin, Familie |
| `Admin` | Admin |
| `ShoppingWrite` | Admin, Familie, Kind (+ Gast, falls in Einstellungen freigegeben) |
| `MealPlanEdit` | Admin, Familie |
| `MealWish` | Kind |
| `TaskAssign` | Admin, Familie |
| `ChatFamily` | Admin, Familie, Kind (+ Gast, falls freigegeben) |
| `DirectMessages` | Admin, Familie, Kind |
Das UI blendet Funktionen anhand derselben Policy-Namen aus (`AuthorizeView Policy=…`), ist aber nie die einzige Sperre.
Ein Benutzer mit `MustChangePassword = true` erhält nur Zugriff auf `/api/auth/*` (Policy `PasswordChanged` als Fallback-Policy).
## 5. Authentifizierung
* ASP.NET Core Identity (Api), Cookie `__Host-fp.auth` (HttpOnly, Secure, SameSite=Strict, Path=/).
* Der Browser spricht ausschließlich mit dem Web-Host (same-origin); YARP leitet `/api` und `/hubs` an die Api weiter.
Dadurch ist das Cookie First-Party, und es werden keine Tokens im Browser gespeichert (BFF-Prinzip, ADR-0002).
* Bei 401 liefert die Api JSON-Status statt Redirect (`OnRedirectToLogin` → 401/403).
* Anti-Forgery: SameSite=Strict + Pflicht-Header `X-Requested-With: FamilyPlanner` auf allen nicht-GET-Requests (Custom-Header-Check, kann von fremden Origins nicht ohne CORS-Preflight gesetzt werden).
* Login per Benutzername **oder** E-Mail, „Angemeldet bleiben“ (persistentes Cookie, 30 Tage gleitend), Lockout (5 Fehlversuche → 15 min), Rate Limiting (Fixed Window pro IP auf `/api/auth/login`).
* Seed-Admin aus Konfiguration (`Seed:AdminUserName`, `Seed:AdminPassword`, `Seed:AdminEmail`), `MustChangePassword = true`.
* Kalender-Abos (ICS) verwenden geheime Tokens in der URL (nur Hash in DB), da Kalender-Clients keine Cookies können (ADR-0005).
## 6. API-Übersicht (Ziel)
| Bereich | Endpunkte |
|---|---|
| Auth | `POST /api/auth/login`, `POST /api/auth/logout`, `GET /api/auth/me`, `POST /api/auth/change-password` |
| Admin – Benutzer | `GET/POST /api/admin/users`, `PUT/DELETE /api/admin/users/{id}`, `POST …/{id}/reset-password`, `POST …/{id}/unlock` |
| Admin – Einstellungen | `GET/PUT /api/admin/settings`, `GET /api/admin/audit` |
| Einkauf | `GET/POST /api/shopping/lists`, `GET/POST /api/shopping/lists/{id}/items`, `PATCH/DELETE /api/shopping/items/{id}`, `POST /api/shopping/lists/{id}/clear-done`, `GET /api/shopping/suggestions?q=` |
| Essen | `GET /api/meals?from=&to=`, `PUT /api/meals/{date}/{mealType}`, `POST /api/meals/{id}/move`, `POST /api/meals/{id}/to-shopping`, `GET/POST /api/meals/wishes` |
| Aufgaben | `GET/POST /api/tasks`, `PUT/DELETE /api/tasks/{id}`, `POST /api/tasks/{id}/complete`, `POST /api/tasks/{id}/approve`, `GET /api/tasks/points` |
| Kalender | `GET /api/calendars`, `GET /api/events?from=&to=`, `POST/PUT/DELETE /api/events…`, `POST /api/calendars/{id}/import`, `GET/POST/DELETE /api/feeds` |
| ICS-Feed | `GET /ics/{token}.ics` (öffentlich, Token-gesichert, ETag/Last-Modified) |
| Chat | `GET /api/chat/channels`, `GET /api/chat/channels/{id}/messages?before=`, `POST …/messages`, `POST …/read` |
| Dashboard | `GET /api/dashboard` |
| Hubs | `/hubs/live` (Live-Updates: Einkauf, Aufgaben, Termine, Dashboard), `/hubs/chat` |
| Betrieb | `/health`, `/alive` |
## 7. Frontend
* Blazor Web App, **Interactive WebAssembly global, Prerendering aus** (ADR-0002). Der Web-Host rendert nur die HTML-Hülle.
* MudBlazor als Komponentenbibliothek (lokal ausgeliefert, kein CDN), deutsches UI, Texte vorbereitet für Lokalisierung (`IStringLocalizer`, `Resources/`).
* Layout: MudLayout mit AppBar, Drawer (Desktop) bzw. Bottom-Navigation (Smartphone), Dark Mode (System-Default, umschaltbar, in `localStorage`).
* Eigener `AuthenticationStateProvider` (`ApiAuthenticationStateProvider`) holt `/api/auth/me` und liefert Claims inkl. Rollen.
* PWA: `manifest.webmanifest`, Icons 72–512 px + maskable, eigener Service Worker (App-Shell-Cache, Network-first für Navigation, keine Caches für `/api`/`/hubs`), Offline-Banner. Hintergrund-Sync für die Einkaufsliste über IndexedDB-Outbox ab Phase 3.
## 8. Echtzeit
SignalR-Hubs in der Api, vom Web-Host per YARP (WebSockets) durchgereicht. Gruppen: `family`, `user:{id}`, `channel:{id}`.
Änderungen in Slices publizieren Events (`ILiveNotifier`), die Clients aktualisieren gezielt ihre Ansichten.
## 9. Betrieb / Deployment
* **Lokal:** `aspire run` bzw. `dotnet run --project src/FamilyPlanner.AppHost`.
* **Produktion:** `deploy/docker-compose.yml` (SQL Server + Volume, migrations, api, web, optional Caddy für HTTPS).
Images werden über die SDK-Container-Unterstützung (`dotnet publish /t:PublishContainer`) bzw. Aspire-Publishing gebaut; alle Container laufen als Non-Root (`app`-User der Microsoft-Images).
* `aspire publish` mit `AddDockerComposeEnvironment("compose")` erzeugt alternativ eine Compose-Datei aus dem App-Modell.
* Konfiguration ausschließlich per Umgebungsvariablen/`.env` (siehe `deploy/.env.example`), lokal User Secrets / Aspire-Parameter.
* Health Checks: `/alive` (Liveness), `/health` (Readiness inkl. DB) – in Produktion nur vom Docker-Netz bzw. per Health-Check-Befehl erreichbar, der Web-Host proxyt sie nicht.
* Data Protection Keys werden in der Datenbank gespeichert (Api), damit Cookies Container-Neustarts überleben.
## 10. Querschnitt
* Nullable, `TreatWarningsAsErrors`, zentrales Paketmanagement (`Directory.Packages.props`), `.editorconfig`.
* Validierung: FluentValidation in jedem Slice.
* Logging/Tracing/Metriken: OpenTelemetry (OTLP → Aspire-Dashboard).
* Zeitzone: `TimeProvider` + `TimeZoneInfo.FindSystemTimeZoneById("Europe/Berlin")` (IANA-ID, funktioniert auf Linux und Windows mit ICU).
+98
View File
@@ -0,0 +1,98 @@
# Roadmap
Legende: `[x]` erledigt, `[ ]` offen. Nach jeder Phase: Build ohne Warnungen, Tests grün, Start über Aspire, Commit.
## Phase 1 – Fundament
- [ ] Solution (`FamilyPlanner.slnx`), alle Projekte laut Architektur inkl. MigrationService
- [ ] `Directory.Build.props` (Nullable, TreatWarningsAsErrors), `Directory.Packages.props`, `.editorconfig`, `global.json`, `.gitignore`
- [ ] Aspire AppHost: SQL Server mit persistentem Volume, Datenbank-Ressource, MigrationService, Api, Web (`WithReference`, `WaitFor`, `WaitForCompletion`)
- [ ] ServiceDefaults: OpenTelemetry, `/health`, `/alive`, Resilience, Service Discovery
- [ ] EF Core `AppDbContext` mit Identity, `AuditableEntity`, Soft Delete, Audit-Interceptor, Initial-Migration
- [ ] Data Protection Keys in der Datenbank
- [ ] Seeding: Rollen Admin/Familie/Kind/Gast, Initial-Admin aus Konfiguration mit erzwungener Passwortänderung, Familieneinstellungen
- [ ] Auth-API: Login (Benutzername/E-Mail, Angemeldet bleiben), Logout, Me, Passwort ändern; Lockout; Rate Limiting
- [ ] Web-Host als BFF mit YARP (`/api`, `/hubs`, `/ics`)
- [ ] Blazor-Shell mit MudBlazor: Login, Passwortwechsel, Navigation, Platzhalterseiten, Dark Mode, deutsche Texte
- [ ] PWA-Grundgerüst: Manifest, Icons, Service Worker, Offline-Hinweis
- [ ] Docker: Container-Images (non-root), `deploy/docker-compose.yml`, `deploy/.env.example`, Aspire-Docker-Compose-Publishing
- [ ] Tests: Unit (Rollen/Domain), Integration (Login-Flow über Aspire.Hosting.Testing), bUnit (Login-Seite)
- [ ] CI-Pipeline (GitHub Actions)
## Phase 2 – Rollen & Admin
- [ ] Policies vollständig (siehe ARCHITECTURE §4) inkl. Unit-Tests je Policy
- [ ] Benutzerverwaltung: anlegen, bearbeiten, deaktivieren, löschen, Rolle, Farbe, Avatar
- [ ] Passwort-Reset durch Admin, Konto entsperren, Gast-Ablaufdatum
- [ ] Schutz „letzter Admin“
- [ ] Allgemeine Einstellungen (Familienname, Zeitzone, Essenplan-Zeitraum, Mahlzeiten-Typen, Aufgaben-Kategorien, Punktesystem, Eltern-Bestätigung, Gast-Rechte)
- [ ] Audit-Log für administrative Aktionen + Ansicht
- [ ] Vorbereitung PIN-Login für Kinder (Datenmodell + Endpoint hinter Feature-Flag)
## Phase 3 – Einkaufsliste
- [ ] Mehrere Listen, Standardliste
- [ ] Schnellerfassung mit Mengen-Parser („2x Milch“, „500 g Mehl“) + Unit-Tests
- [ ] Abhaken, Entfernen (Wischgeste/Button), „Erledigte entfernen“
- [ ] Autovervollständigung aus häufig gekauften Artikeln
- [ ] Kategorien/Gänge zur Sortierung
- [ ] Rechte: Kind löscht nur eigene, Gast lesend (konfigurierbar)
- [ ] SignalR-Live-Updates, Optimistic Concurrency
- [ ] Offline-Outbox (IndexedDB) + Hintergrundsynchronisation
## Phase 4 – Essenplaner
- [ ] Zeitraum 7–10 Tage (konfigurierbar), Mahlzeiten konfigurierbar
- [ ] Mobile Liste / Desktop-Wochenraster
- [ ] Freitext + Autovervollständigung, Notiz, Rezeptlink, Zutaten
- [ ] „Zutaten auf Einkaufsliste“
- [ ] Essenswünsche von Kindern, Übernahme durch Familie/Admin
- [ ] Drag & Drop auf anderen Tag
## Phase 5 – Aufgaben
- [ ] Schnellerfassung + ausklappbare Details, alle Felder laut Anforderung
- [ ] Status offen/in Arbeit/erledigt, Verlauf (wer/wann)
- [ ] Zuweisung an Personen / ganze Familie, private Aufgaben
- [ ] Wiederholungen (RRULE), nächste Instanz nach Erledigung/Stichtag + Unit-Tests
- [ ] Ämtchen mit Punkten, Eltern-Bestätigung, Punktestand, Kinderansicht
- [ ] Ansichten Meine/Familie/Nach Person/Überfällig/Diese Woche, Filter, Sortierung
- [ ] Live-Updates, Offline-Erfassung
- [ ] Web-Push-Erinnerungen vorbereiten (VAPID, Subscriptions)
## Phase 6 – Kalender
- [ ] Kalender + Berechtigungen pro Rolle/Benutzer, Kalenderverwaltung
- [ ] Termine mit Personen-Tags, ganztägig, Ort, Notiz
- [ ] Wiederholungen (RRULE) + Ausnahmen (EXDATE/Overrides)
- [ ] Ansichten Tag/Woche/Monat/Agenda, Farben je Person, Filter
- [ ] Aufgaben-Ebene im Kalender (ein-/ausblendbar)
- [ ] ICS-Feeds (alle/meine/pro Kalender, optional Aufgaben als VTODO/ganztägig), VTIMEZONE Europe/Berlin, stabile UIDs, ETag/Last-Modified + Unit-Tests
- [ ] Feed-Token erzeugen/widerrufen, Seite „Kalender abonnieren“ mit QR-Code und Anleitungen (iPhone, Outlook)
- [ ] ICS-Import
- [ ] ADR CalDAV / Microsoft Graph
## Phase 7 – Dashboard
- [ ] Kacheln: Heute zu essen, Termine heute + morgen, Einkaufsliste (Anzahl + Schnellerfassung), Meine Aufgaben, Familien-/Kinderaufgaben (Eltern), ungelesene Nachrichten
- [ ] Begrüßung mit Datum, einspaltig auf dem Smartphone
- [ ] Live-Aktualisierung
## Phase 8 – Chat
- [ ] Familienchat + Direktnachrichten
- [ ] Gelesen-Status, Ungelesen-Zähler, Tippindikator
- [ ] Paging
- [ ] Bilder (Größenlimit, Validierung), Emoji-Reaktionen
- [ ] Rechte Kind/Gast
## Phase 9 – Härtung
- [ ] Security-Header (CSP, HSTS, X-Content-Type-Options, Referrer-Policy, Permissions-Policy)
- [ ] Rate Limiting vervollständigen
- [ ] Tests vervollständigen (Policies, ICS, Wiederholungen, Integration)
- [ ] Barrierefreiheit (WCAG 2.1 AA), Performance (AOT/Trimming prüfen, Lazy Loading)
## Phase 10 – Deployment
- [ ] Finale `docker-compose.yml` (inkl. optionalem Caddy), `.env.example`
- [ ] Backup-/Restore-Skripte
- [ ] README: Voraussetzungen, Entwicklung, Konfiguration, Deployment, Update, Backup/Restore, Kalender-Abos
## Backlog
- Belohnungskatalog für Ämtchen-Punkte
- Web-Push für Termine und Chat
- CalDAV-Server bzw. Microsoft-Graph-Synchronisation
- Rezeptdatenbank
- Geburtstage und Jahrestage
- Wand-Tablet-Modus (Kiosk-Dashboard)
+13
View File
@@ -0,0 +1,13 @@
# ADR-0001: Architekturentscheidungen dokumentieren
* Status: akzeptiert
* Datum: 2026-10-09
## Kontext
Das Projekt wird weitgehend eigenständig entwickelt. Entscheidungen müssen nachvollziehbar sein.
## Entscheidung
Jede wesentliche Architekturentscheidung wird als kurze ADR (Kontext, Entscheidung, Konsequenzen) in `docs/adr/NNNN-titel.md` abgelegt.
## Konsequenzen
Neue Mitwirkende finden Begründungen ohne Code-Archäologie. Überholte ADRs werden nicht gelöscht, sondern als „ersetzt durch ADR-XXXX“ markiert.
+27
View File
@@ -0,0 +1,27 @@
# ADR-0002: Web-Host als BFF mit YARP, Blazor WebAssembly ohne Prerendering
* Status: akzeptiert
* Datum: 2026-10-09
## Kontext
Vorgegeben sind eine getrennte Api (Minimal APIs, Identity, SignalR) und eine Blazor Web App mit Interactive WebAssembly als PWA.
Die Anmeldung soll per Cookie erfolgen (keine Tokens im Browser). Cookies funktionieren nur sauber und sicher (SameSite=Strict),
wenn Browser-App und Api dieselbe Origin haben.
## Entscheidung
* `FamilyPlanner.Web` ist der einzige öffentlich erreichbare Dienst. Er liefert die WASM-App aus und leitet `/api/**`, `/hubs/**` und `/ics/**`
per **YARP** (mit Aspire Service Discovery, `Microsoft.Extensions.ServiceDiscovery.Yarp`) an `FamilyPlanner.Api` weiter (inkl. WebSockets für SignalR).
* Die **Api besitzt Identity** und stellt das Cookie `__Host-fp.auth` aus (HttpOnly, Secure, SameSite=Strict). Durch den Proxy ist es aus Browsersicht ein First-Party-Cookie.
* Rendermodus: **InteractiveWebAssembly global mit `prerender: false`**. Der Web-Host kennt dadurch keinen Auth-Zustand und braucht weder Identity noch geteilte Data-Protection-Keys.
* Der WASM-Client nutzt einen eigenen `AuthenticationStateProvider`, der `/api/auth/me` abfragt.
* CSRF-Schutz: SameSite=Strict plus Pflicht-Header `X-Requested-With` für alle schreibenden API-Requests (ADR-0006).
## Alternativen
* Api und Web in einem Prozess: einfacher, widerspricht aber der vorgegebenen Projektstruktur.
* Prerendering mit geteilten Data-Protection-Keys: schnellere erste Darstellung, aber doppelte Auth-Logik. Für eine installierte PWA mit Service-Worker-Cache ist der Nutzen gering.
* JWT im Browser: widerspricht der Vorgabe Cookie-Auth/BFF, XSS-anfälliger.
## Konsequenzen
* Ein zusätzlicher Hop (Web → Api), vernachlässigbar.
* Die Api liefert bei fehlender Anmeldung 401/403 statt Redirects.
* Die Api ist im Produktivbetrieb nicht direkt exponiert (nur im Docker-Netz).
+19
View File
@@ -0,0 +1,19 @@
# ADR-0003: Migrations und Seeding über einen eigenen Worker-Service
* Status: akzeptiert
* Datum: 2026-10-09
## Kontext
Migrations sollen automatisch laufen, nicht manuell. Mehrere Api-Instanzen dürfen nicht parallel migrieren. Beim ersten Start muss ein Admin-Konto existieren.
## Entscheidung
`FamilyPlanner.MigrationService` ist ein .NET Worker, der beim Start `Database.MigrateAsync()` ausführt, Rollen (Admin, Familie, Kind, Gast),
die Familieneinstellungen und den Initial-Admin seedet und sich danach beendet. Im AppHost warten Api und Web per `WaitForCompletion(migrations)`.
Im Docker-Compose-Betrieb läuft er als einmaliger Container; `api` hängt mit `condition: service_completed_successfully` davon ab.
Initial-Admin: `Seed__AdminUserName`, `Seed__AdminEmail`, `Seed__AdminPassword` (Umgebungsvariablen / Aspire-Parameter). Das Konto wird nur angelegt,
wenn noch **kein** Benutzer in der Rolle Admin existiert, und erhält `MustChangePassword = true`.
## Konsequenzen
* Saubere Trennung, Migrations-Fehler blockieren den App-Start sichtbar im Aspire-Dashboard.
* Der Worker nutzt dieselbe Infrastructure-Bibliothek wie die Api.
+18
View File
@@ -0,0 +1,18 @@
# ADR-0004: Tooling, Versionen und Solution-Format
* Status: akzeptiert
* Datum: 2026-10-09
## Entscheidung
* .NET 10 (SDK 10.0.300, `global.json` mit `rollForward: latestFeature`), Aspire 13.6.1 (`Aspire.AppHost.Sdk`).
* Zentrales Paketmanagement über `Directory.Packages.props`; nur stabile Versionen (Stand 2026-10: EF Core/ASP.NET 10.0.12, MudBlazor 9.11.0,
YARP 2.3.0, OpenTelemetry 1.19, FluentValidation 12.1.1, Ical.Net 5.2.3, xunit.v3 4.0.1, bUnit 2.11.3).
* Solution im XML-Format **`FamilyPlanner.slnx`** (Default des .NET-10-SDK, von Visual Studio, Rider und `dotnet` unterstützt) statt `.sln`.
* `Directory.Build.props`: Nullable, ImplicitUsings, `TreatWarningsAsErrors`.
* Tests: xUnit v3 (VSTest-Adapter, damit `dotnet test` ohne Zusatzkonfiguration läuft), bUnit für Komponenten,
`Aspire.Hosting.Testing` für Integrationstests mit echtem SQL-Server-Container.
* Container-Images über die SDK-Container-Unterstützung (`dotnet publish /t:PublishContainer`), Basis `mcr.microsoft.com/dotnet/aspnet:10.0`,
Benutzer `app` (non-root, Standard der .NET-8+-Images). Keine handgeschriebenen Dockerfiles nötig.
## Konsequenzen
Werkzeuge, die nur `.sln` lesen, können über `dotnet sln` eine `.sln` erzeugen; die CI und lokale Entwicklung nutzen `.slnx` direkt.
+19
View File
@@ -0,0 +1,19 @@
# ADR-0005: ICS-Abo-Feeds mit geheimem Token, CalDAV/Graph später
* Status: akzeptiert (Umsetzung Phase 6)
* Datum: 2026-10-09
## Kontext
iOS und Outlook abonnieren Kalender per `webcal://`/`https://` und können dabei keine Cookie-Anmeldung durchführen.
## Entscheidung
* Lese-Feeds unter `/ics/{token}.ics`. Der Token ist 32 Byte kryptographisch zufällig (Base64Url), in der DB wird nur ein SHA-256-Hash gespeichert.
Tokens gibt es pro Benutzer und Feed-Variante (alle / nur meine / pro Kalender, optional inkl. Aufgaben); sie sind neu generierbar und widerrufbar (Benutzer und Admin).
* Erzeugung mit Ical.Net, `VTIMEZONE` Europe/Berlin, stabile `UID` (`{eventId}@familyplanner`), `RRULE`/`EXDATE`, ETag + Last-Modified, `Cache-Control: private, max-age=900`.
* Rate Limiting auf dem Feed-Endpunkt.
* **Bidirektionale Synchronisation** (CalDAV-Server für iOS, Microsoft Graph für Outlook) ist bewusst nicht Teil von Phase 1–6. Vorbereitung:
Termine haben stabile UIDs und `UpdatedAt`/RowVersion (für ETags/CTags), Wiederholungen werden RFC-5545-konform gespeichert. Ein späterer
CalDAV-Endpunkt (`/dav/`) kann auf demselben Modell aufsetzen.
## Konsequenzen
Wer den Link kennt, kann den Feed lesen – deshalb Widerruf und Hinweise im UI. Änderungen in iOS/Outlook fließen nicht zurück.
+16
View File
@@ -0,0 +1,16 @@
# ADR-0006: CSRF-Schutz für die Cookie-basierte Api
* Status: akzeptiert
* Datum: 2026-10-09
## Kontext
Die Api wird von einer WebAssembly-App per `fetch` mit Cookie aufgerufen. Klassische Antiforgery-Tokens aus Razor-Formularen passen nicht zu einem reinen JSON-API.
## Entscheidung
* Auth-Cookie `__Host-fp.auth`: HttpOnly, Secure, SameSite=Strict, Path=/ (das `__Host-`-Präfix erzwingt Secure und verbietet Domain).
* Alle schreibenden Requests (POST/PUT/PATCH/DELETE) unter `/api` müssen den Header `X-Requested-With: FamilyPlanner` tragen, sonst 400.
Fremde Origins können diesen Header nur mit CORS-Preflight setzen, und CORS ist nicht freigegeben.
* Keine CORS-Policy in der Api (same-origin via BFF).
## Konsequenzen
Zwei unabhängige Schutzschichten (SameSite + Custom Header). Clients außerhalb der App müssen den Header setzen.
+14
View File
@@ -0,0 +1,14 @@
# ADR-0007: MudBlazor als UI-Framework, eigener Service Worker
* Status: akzeptiert
* Datum: 2026-10-09
## Entscheidung
* **MudBlazor** (Material Design, touch-tauglich, Dark Mode, gute Mobile-Komponenten). Assets werden als Static Web Assets lokal ausgeliefert – kein CDN, keine Google Fonts (Systemschrift).
* Der Blazor-Web-App-Host hat keine eingebaute PWA-Vorlage. Deshalb eigener Service Worker (`wwwroot/service-worker.js`):
App-Shell und `_framework`-Dateien werden beim Laden gecacht (Cache-first, Dateinamen sind fingerprinted), Navigation Network-first mit Fallback auf die gecachte Shell,
`/api`, `/hubs`, `/ics` werden nie gecacht. Versionierung über eine Cache-Version im Skript.
* Mobil: Bottom-Navigation mit den wichtigsten Bereichen, Desktop: seitlicher Drawer.
## Konsequenzen
Updates des Service Workers erfordern ein Hochzählen der Cache-Version (Automatisierung im Build: Phase 9).