feat: client monorepo (Flutter app) + web fallback redesign #1

Merged
linus merged 37 commits from feat/backend-phases-0-6 into main 2026-09-12 11:27:27 +00:00
2 changed files with 34 additions and 19 deletions
Showing only changes of commit 138d782ca3 - Show all commits
+25 -12
View File
@@ -12,12 +12,13 @@ for the full architecture and phased roadmap.
sharing, chat, local/cloud sync). See [backend/README.md](backend/README.md) sharing, chat, local/cloud sync). See [backend/README.md](backend/README.md)
for setup. Also serves the web client (see below) directly, so it's the for setup. Also serves the web client (see below) directly, so it's the
single entry point for the web experience. single entry point for the web experience.
- `client/app/` — the Flutter client (single codebase; **web** target
enabled, mobile/desktop can be added later). Login (guest / local Teamer /
invite redemption), role-aware home, guest Workshop-Wahl, file list,
read-only chat. See [client/app/README.md](client/app/README.md).
- `client/web/` — minimal dependency-free HTML/CSS/JS placeholder web - `client/web/` — minimal dependency-free HTML/CSS/JS placeholder web
client (guest join, Wahl submission, file list, chat) exercising the real client, served by the backend at `/`. Superseded by the Flutter web build;
API, served by the backend at `/`. Will be replaced by the Flutter web kept for now as a zero-dependency fallback.
build once Flutter is available.
- `client/` (mobile/desktop) — planned Flutter app, not yet scaffolded
(Flutter is not installed in this environment).
## Status ## Status
@@ -50,12 +51,24 @@ cloud server's `/sync/ingest` + `/sync/export` endpoints (shared-secret
authenticated, not user auth). No conflict resolution needed by design - the authenticated, not user auth). No conflict resolution needed by design - the
local server is the sole source of truth while an event is live. local server is the sole source of truth while an event is live.
The backend now also serves the web client directly (static files from Since then: local (non-Authentik) Gemeinde Teamer accounts + invites
`client/web/`, API under `/api`), so the same process is the single entry (`teamer/`, `auth/team-login`), Gemeinde CRUD (`gemeinde/`), Gemeinde
point for the web experience. Verantwortliche self-registration with LT approval (`onboarding/`), JIT
`User` provisioning on first Authentik login, LEITUNGSTEAM derived from the
Authentik `groups` claim, and an email module (`mail/`, log-only by default,
SMTP opt-in) that sends personal Teamer invites. First Prisma migration is
in (`backend/prisma/migrations/`); the backend has been run end to end
against a local PostgreSQL 16. `npm test` covers the assignment algorithm
and the new auth/onboarding services (56 tests).
Backend builds and boots cleanly (`npm run build`, `node dist/main.js`) but Phase 7 (Flutter client) started: `client/app/` is a single Flutter
requires a real PostgreSQL database, Authentik instance, and Nextcloud/S3 codebase with the **web** target enabled — login (guest / local Teamer /
credentials (see `backend/.env.example`) to run end to end. Remaining: the invite redemption), role-aware home, guest Workshop-Wahl, file list,
Flutter clients (mobile/desktop; web has an interim plain-HTML client). read-only chat. `flutter build web` and `flutter test` pass. Mobile/desktop
targets, the Authentik Authorization-Code flow, WebSocket chat send, and LT
admin screens are still to come.
The backend also serves the interim `client/web/` placeholder at `/` (API
under `/api`). Running end to end still needs a real Authentik instance and
Nextcloud/S3 credentials (see `backend/.env.example`).
+9 -7
View File
@@ -2,7 +2,7 @@
Neuentwicklung, die das WordPress-Plugin **Workshop-Wahlen** (Wahlen/Workshops/Teilnehmer/Zuteilungslogik) ablöst und um Rollen-/Rechteverwaltung via Authentik, gestaffelte Dateifreigabe, mehrstufigen Chat und eine Hybrid-Server-Architektur (online + lokal mit Sync) erweitert. Backend: **NestJS 10 + PostgreSQL + Prisma 5**. Client: **Flutter** (eine Codebase für Mobile, Web, Desktop) — bis Flutter verfügbar ist, liefert das Backend selbst einen minimalen Platzhalter-Web-Client aus. Neuentwicklung, die das WordPress-Plugin **Workshop-Wahlen** (Wahlen/Workshops/Teilnehmer/Zuteilungslogik) ablöst und um Rollen-/Rechteverwaltung via Authentik, gestaffelte Dateifreigabe, mehrstufigen Chat und eine Hybrid-Server-Architektur (online + lokal mit Sync) erweitert. Backend: **NestJS 10 + PostgreSQL + Prisma 5**. Client: **Flutter** (eine Codebase für Mobile, Web, Desktop) — bis Flutter verfügbar ist, liefert das Backend selbst einen minimalen Platzhalter-Web-Client aus.
> Status (Stand dieser Session): **Alle geplanten Backend-Phasen (06) sind implementiert und verifiziert** (Typecheck, Build, Boot-Test). Offen ist ausschließlich der Flutter-Client (Mobile/Desktop), da Flutter in dieser Umgebung nicht installiert ist. > Status: **Backend-Phasen 06 komplett** plus lokale Teamer-Accounts/Invites, Gemeinde-CRUD, Verantwortlichen-Selbstregistrierung (`onboarding/`), Authentik-JIT + LT-aus-`groups`, `mail/`-Modul. Erste Prisma-Migration vorhanden, Backend lief Ende-zu-Ende gegen lokales PostgreSQL 16 (`npm test`: 56). **Phase 7 (Flutter) begonnen**: `client/app/` — eine Codebase, Web-Target aktiv, Login/Home/Wahl/Dateien/Chat(read-only); `flutter build web` + `flutter test` grün. Offen: Mobile/Desktop-Targets, Authentik-Auth-Code-Flow im Client, WS-Chat-Senden, LT-Admin-Screens, Push, echte Authentik/Nextcloud-Infra.
--- ---
@@ -37,7 +37,7 @@ Vollständiges Schema: [backend/prisma/schema.prisma](backend/prisma/schema.pris
| Bereich | Entscheidung | Begründung | | Bereich | Entscheidung | Begründung |
|---|---|---| |---|---|---|
| Backend | NestJS 10 + PostgreSQL + Prisma 5 | bestätigt vom Nutzer; Nest 10 statt CLI-Default (siehe unten) | | Backend | NestJS 10 + PostgreSQL + Prisma 5 | bestätigt vom Nutzer; Nest 10 statt CLI-Default (siehe unten) |
| Client | Flutter, eine Codebase Mobile/Web/Desktop | vom Nutzer delegiert; noch nicht scaffoldbar (Flutter fehlt lokal) | | Client | Flutter, eine Codebase Mobile/Web/Desktop `client/app/`, aktuell nur Web-Target aktiviert | vom Nutzer delegiert; Flutter-SDK lokal via Homebrew installiert (nur Web-Toolchain, Android/iOS wegen Speicher weggelassen); `lib/` ist plattformneutral, weitere Targets per `flutter create --platforms=…` nachrüstbar |
| Web-Interimslösung | Backend liefert `client/web/` (reines HTML/CSS/JS, kein Build-Schritt) über `ServeStaticModule` aus; REST-API liegt unter `/api/*` | Nutzerwunsch: "Server soll auch Web-Client bereitstellen"; vermeidet Kollision zwischen API-Routen und statischen Dateien | | Web-Interimslösung | Backend liefert `client/web/` (reines HTML/CSS/JS, kein Build-Schritt) über `ServeStaticModule` aus; REST-API liegt unter `/api/*` | Nutzerwunsch: "Server soll auch Web-Client bereitstellen"; vermeidet Kollision zwischen API-Routen und statischen Dateien |
| Auth (Team) | Authentik als OIDC Resource Server (JWKS-Verifikation), kein lokaler Autorisierungscode-Flow im Backend | Clients machen Authorization Code + PKCE direkt gegen Authentik; Backend validiert nur Access Token + löst lokale `Membership` auf | | Auth (Team) | Authentik als OIDC Resource Server (JWKS-Verifikation), kein lokaler Autorisierungscode-Flow im Backend | Clients machen Authorization Code + PKCE direkt gegen Authentik; Backend validiert nur Access Token + löst lokale `Membership` auf |
| Auth (Guest) | Rein lokale JWTs (`GUEST_JWT_SECRET`), kein Authentik | explizite Nutzervorgabe: Konfi-Accounts sind nie in Authentik | | Auth (Guest) | Rein lokale JWTs (`GUEST_JWT_SECRET`), kein Authentik | explizite Nutzervorgabe: Konfi-Accounts sind nie in Authentik |
@@ -97,7 +97,7 @@ Details, Setup-Anleitung und `.env`-Variablen: [backend/README.md](backend/READM
4. **Dateifreigabe** Storage-Abstraktion, Sichtbarkeitsstufen. ✅ 4. **Dateifreigabe** Storage-Abstraktion, Sichtbarkeitsstufen. ✅
5. **Kommunikation** Chat (Gruppen/DM/LT/Broadcast), WebSocket. ✅ (Push-Integration noch offen) 5. **Kommunikation** Chat (Gruppen/DM/LT/Broadcast), WebSocket. ✅ (Push-Integration noch offen)
6. **Hybrid Lokal/Cloud-Server & Sync** Replikationslog, Scheduler, Shared-Secret-Auth. ✅ 6. **Hybrid Lokal/Cloud-Server & Sync** Replikationslog, Scheduler, Shared-Secret-Auth. ✅
7. **Flutter-Clients** gemeinsame Codebase; Screens: Invite/Login, rollenspezifisches Dashboard, Wahl-Formular/Ergebnis, Dateien, Chat, Admin/Nutzerverwaltung. ❌ **offen** (Flutter nicht installiert); Web-Interimslösung siehe Abschnitt 3. 7. **Flutter-Clients** gemeinsame Codebase (`client/app/`, Web-Target). ✅ Grundgerüst: Login (Guest / lokaler Teamer / Invite-Redemption, Token in `shared_preferences`, `GET /auth/me` für rollenabhängige Startseite), Guest-Wahl-Formular (`/wahl/guest/overview` → geordnete Wunsch-Auswahl → Absenden), Datei-Liste, Chat (nur lesen). 🔜 Mobile/Desktop-Targets, Authentik-Auth-Code-Flow, Wahl-**Ergebnis**-Ansicht, WS-Chat-Senden, LT-/Verantwortlichen-Admin-Screens (KC/Gemeinde/Teamer/Onboarding-Freigaben).
--- ---
@@ -112,6 +112,8 @@ Details, Setup-Anleitung und `.env`-Variablen: [backend/README.md](backend/READM
1. Nach jeder Phase: `npx tsc -p tsconfig.build.json --noEmit`, `npx nest build`, sowie ein kurzer Boot-Test (`node dist/main.js`) zur Prüfung, dass der DI-Graph auflöst und alle Routen korrekt gemappt werden (ohne Live-DB/Authentik/Nextcloud). 1. Nach jeder Phase: `npx tsc -p tsconfig.build.json --noEmit`, `npx nest build`, sowie ein kurzer Boot-Test (`node dist/main.js`) zur Prüfung, dass der DI-Graph auflöst und alle Routen korrekt gemappt werden (ohne Live-DB/Authentik/Nextcloud).
2. Sync-Modul: manuell verifiziert, dass `SyncSecretGuard` Requests ohne `x-sync-secret` mit 403 ablehnt und mit korrektem Secret durchlässt (DB-Fehler in der Sandbox ist erwartet, da kein Postgres läuft). 2. Sync-Modul: manuell verifiziert, dass `SyncSecretGuard` Requests ohne `x-sync-secret` mit 403 ablehnt und mit korrektem Secret durchlässt (DB-Fehler in der Sandbox ist erwartet, da kein Postgres läuft).
3. Web-Client: `GET /` liefert die statische Seite (200), `GET /api/kc` trifft die echte, geschützte API (401 ohne Token). 3. Web-Client: `GET /` liefert die statische Seite (200), `GET /api/kc` trifft die echte, geschützte API (401 ohne Token).
3a. Gegen lokales **PostgreSQL 16** (Homebrew): `prisma migrate dev --name init` erzeugt/appliziert die erste Migration; Guest-Flow Ende-zu-Ende geprüft (`/auth/guest``/auth/me``/wahl/guest/overview``POST …/teilnehmer` → Re-Fetch zeigt `meinePrioritaeten`). Seed: `prisma/seed-dev.js`.
3b. Flutter-Client (`client/app/`): `flutter analyze` sauber, `flutter build web --release` erfolgreich, `flutter test` grün (Login-Screen-Smoke-Test); CORS-Preflight vom Browser-Origin ok.
4. Jest-Unit-Tests (Prisma/Sync/Mail gemockt, `npm test` grün, 56 Tests): 4. Jest-Unit-Tests (Prisma/Sync/Mail gemockt, `npm test` grün, 56 Tests):
- `src/wahl/zuteilung.service.spec.ts`: Force-Vorrang, Wunschrunden-Fallback bei voller Kapazität, Unzugeteilt-Fall, Konsolidierung unterbesetzter Workshops, Sync-Capture-Anzahl. - `src/wahl/zuteilung.service.spec.ts`: Force-Vorrang, Wunschrunden-Fallback bei voller Kapazität, Unzugeteilt-Fall, Konsolidierung unterbesetzter Workshops, Sync-Capture-Anzahl.
- `src/auth/team-auth.service.spec.ts`: Invite-Redemption (unbekannt/widerrufen/abgelaufen/aufgebraucht, Gruppen-Link ohne E-Mail, E-Mail-Mismatch, Dublette) + Passwort-Login. - `src/auth/team-auth.service.spec.ts`: Invite-Redemption (unbekannt/widerrufen/abgelaufen/aufgebraucht, Gruppen-Link ohne E-Mail, E-Mail-Mismatch, Dublette) + Passwort-Login.
@@ -124,8 +126,8 @@ Details, Setup-Anleitung und `.env`-Variablen: [backend/README.md](backend/READM
## 8. Nächste Schritte ## 8. Nächste Schritte
1. Sobald Flutter verfügbar ist: Client-Grundgerüst aufsetzen (Mobile + Web + Desktop, eine Codebase), beginnend mit Invite/Login-Flow gegen die bestehende API. 1. Flutter-Client ausbauen: Authentik-Authorization-Code-Flow (LT/Verantwortliche), Wahl-Ergebnis-Ansicht, LT-/Verantwortlichen-Admin-Screens (KC/Gemeinde/Teamer anlegen, Onboarding-Anfragen freigeben), WS-Chat-Senden, dann Mobile/Desktop-Targets aktivieren.
2. Authentik-Provider so konfigurieren, dass das Access-Token den `groups`-Claim trägt (Scope „groups"), und die LT-Gruppe auf `AUTHENTIK_LEITUNGSTEAM_GROUP` abstimmen — sonst greift der LT-Abgleich nicht. (Reine Ops-/Config-Aufgabe, Code ist fertig.) 2. Authentik-Provider so konfigurieren, dass das Access-Token den `groups`-Claim trägt (Scope „groups"), LT-Gruppe = `AUTHENTIK_LEITUNGSTEAM_GROUP`. (Ops/Config, Code ist fertig.)
3. SMTP konfigurieren (`MAIL_PROVIDER=smtp` + `SMTP_*`/`MAIL_FROM`/`APP_BASE_URL`) und die Invite-Mail-Templates finalisieren (aktuell nur Plain-Text); optional Onboarding-Benachrichtigungen an LT. 3. SMTP konfigurieren (`MAIL_PROVIDER=smtp` + `SMTP_*`/`MAIL_FROM`/`APP_BASE_URL`) und Invite-Mail-Templates finalisieren (aktuell Plain-Text); optional Onboarding-Benachrichtigungen an LT.
4. Push-Benachrichtigungen (FCM/APNs) für Chat/Ankündigungen. 4. Push-Benachrichtigungen (FCM/APNs) für Chat/Ankündigungen.
5. Echte Infrastruktur (Postgres, Authentik, Nextcloud/S3) aufsetzen, erste Prisma-Migration erzeugen (`prisma migrate dev`, bisher nur `schema.prisma`) und die in Abschnitt 7 offenen Verifikationsschritte durchführen. 5. Echte Authentik- + Nextcloud/S3-Infra anbinden und die in Abschnitt 7 offenen E2E-Verifikationsschritte durchführen (lokales Postgres + Migration sind erledigt).