- Symfony 7.4 LTS, PHP 8.4, FrankenPHP, SQLite (3-Layer src/UI|Logic|Data) - Auth: manueller OidcClient (Authorization-Code) im UI-Layer, kein Security-Bundle/league; Session-Gate via SessionAuthSubscriber - API-Guard via statischem X-Api-Token (ApiTokenSubscriber); ExceptionSubscriber - UserEntity (Auto-Provision) + Init-Migration - Docker (php:8.4-frankenphp) + compose (essen.ljh31.de, traefik-net external) - SmokeTests (7) grün: Redirect-Fälle, API-Token, Auth-Render, Logout Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
82 lines
3.8 KiB
Markdown
82 lines
3.8 KiB
Markdown
# Implementierungsplan
|
||
|
||
Status: **final** (2026-09-13, abgestimmt) · Basis: [`anforderungen.md`](anforderungen.md), [`architektur.md`](architektur.md), ADR 001–007
|
||
|
||
## Prinzipien
|
||
|
||
- **Vertikal geschnitten:** Jedes Milestone ist ein vollständiger UI→Logic→Data-Slice
|
||
und am Ende lauffähig/testbar (kein Layer-Reihenbau).
|
||
- **Ein Milestone = ein Review-Pausenpunkt.** Jeder MS wird einzeln abgenommen,
|
||
bevor der nächste startet.
|
||
- **Kein CI** – PHPUnit läuft manuell (`composer test`).
|
||
- **Auth:** Web-UI über **Keycloak-OIDC** (realm `family`, eigener Client
|
||
`essens`), Auto-Provision des lokalen Users (wie `svc-webui`). HA bezieht die
|
||
Tagesansicht über `GET /api/today` mit **statischem API-Token**
|
||
(`X-Api-Token` aus `.env`, kein Keycloak-Refresh für HA). Damit wird
|
||
ADR-003 in M0 entsprechend überarbeitet.
|
||
- **Deploy** (M5) erst nach expliziter Freigabe.
|
||
|
||
## Milestones
|
||
|
||
### M0 – Skelett + Auth-Grundgerüst
|
||
- Symfony 7.4 LTS, PHP 8.4, FrankenPHP: `composer.json`, `config/`
|
||
(framework, doctrine/SQLite, twig, routing), `public/index.php`
|
||
- Struktur laut `architektur.md` leergelegt (`src/UI`, `src/Logic`, `src/Data`)
|
||
- Keycloak-OIDC: manueller `OidcClient` (kein `league/oauth2-client`, kein
|
||
Security-Bundle; vgl. ADR-003), Login/Callback/Logout im UI-Layer,
|
||
`sub`/`email`-Claim → lokale User-Zeile (Auto-Provision)
|
||
- Docker: `Dockerfile` (php:8.4-frankenphp), `compose.yaml`
|
||
(Volume `data/` für SQLite, `.env`), Network `infra-traefik_traefik-net` (external),
|
||
Traefik-Labels → `essen.ljh31.de` + ACME + redirect-to-https
|
||
- `phpunit.xml`, `tests/`, htmx + SortableJS in `public/js/`
|
||
- ADR-003 überarbeitet (Keycloak statt „kein Login")
|
||
- **DoD:** Container läuft, `GET /` → Keycloak-Login, danach Hello-World-Seite
|
||
|
||
### M1 – CRUD-Slice
|
||
- Logic: `Dish`-Model, `CreateDish`/`UpdateDish`/`DeleteDish`, `ListDishes`, `DishDto`
|
||
- Data: `DishEntity`, Migration 0001, Provider/Processor-Impl, Mapper, Exception-Subscriber
|
||
- UI: `DishController` (htmx-Partials), Formular + Validierung im Controller, Liste
|
||
- Tests: UseCase-Unit (in-mem SQLite) + 1 Controller-Feature-Test
|
||
- **DoD:** Gericht anlegen/bearbeiten/löschen im Browser (als eingeloggter User)
|
||
|
||
### M2 – Status & Wochentage
|
||
- `CheckOffDish`/`UncheckDish` (Status `aktiv`/`gecheckt`), `ArchiveDish`
|
||
- Wochentag-Tags (Mo–So) setzen/ändern, Liste nach Tag gruppier-/filterbar
|
||
- **DoD:** Check-Off via htmx-Toggle, Tagesansicht, Archiv-Tab
|
||
|
||
### M3 – Reorder + Inspiration
|
||
- `ReorderDishes` (SortableJS-`change` → Position-Ints, persistiert)
|
||
- `SuggestDishes`-Query: Slow 0.5 / Rarität 0.3 / Hotness 0.2 (ADR-006),
|
||
5 Vorschläge + „Weitere 5", 1-Klick-Adopt → `CreateDish` mit Referenz
|
||
- **DoD:** Reorder bleibt nach Reload bestehen; Inspiration klickbar adoptierend
|
||
|
||
### M4 – Härtung & API
|
||
- `X-Api-Token`-Guard (optional, `.env`) für `GET /api/today` (HA-ready)
|
||
- Fehlerseiten, Logging, Backup-Hinweis (SQLite-Datei `data/`)
|
||
- **DoD:** `curl -H "X-Api-Token: …" /api/today` liefert Tagesgericht-JSON;
|
||
DoD-Punkte aus `anforderungen.md` abgehakt
|
||
|
||
### M5 – Deploy (erst nach Freigabe)
|
||
- Stack auf `docker1:/root/stacks/svc-essensplanung`, `git pull && docker compose up -d`
|
||
- Traefik: `essen.ljh31.de`, Let's-Encrypt
|
||
- Keycloak: Client `essens` in realm `family` anlegen (Redirect-URI `https://essen.ljh31.de`)
|
||
- **DoD:** Gesamt-DoD aus `anforderungen.md` erfüllt, erreichbar ohne LAN-IP
|
||
|
||
## Test-Strategie (quer)
|
||
|
||
- PHPUnit je Milestone: UseCase-Unit (in-mem SQLite, Logic-Dependencies als
|
||
Fakes) + Feature-Tests (HTTP gegen Test-DB)
|
||
- Manuell: `docker compose run app composer test` bzw. `vendor/bin/phpunit`
|
||
- Kein CI (bewusst, s. Prinzipien)
|
||
|
||
## Abnahme-Historie
|
||
|
||
| MS | Datum | Status |
|
||
|----|-------|--------|
|
||
| M0 | | offen |
|
||
| M1 | | offen |
|
||
| M2 | | offen |
|
||
| M3 | | offen |
|
||
| M4 | | offen |
|
||
| M5 | | offen |
|