docs: add architecture, roadmap, ADRs and CLAUDE.md
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
9c35a3c5b7
10 files changed
+439
No files matched your search
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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)
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
Reference in new issue
Block a user