# KC-App Backend NestJS API for the KC-App platform (see repo root README + plan for architecture context). ## Setup ```bash npm install cp .env.example .env # then fill in DATABASE_URL / AUTHENTIK_ISSUER_URL / GUEST_JWT_SECRET npx prisma generate npx prisma migrate dev --name init # requires a running PostgreSQL instance npm run start:dev ``` The API is served under `/api` (see `app.setGlobalPrefix('api')` in `main.ts`); everything else (`/`, `/app.js`, ...) is served statically from `../client/web` via `ServeStaticModule`, so the backend doubles as the web client's host - no separate web server is needed. ## Auth model - Team members (Leitungsteam, Gemeinde Verantwortliche, Gemeinde Teamer) are provisioned in Authentik; this API acts as an OIDC **resource server**, verifying access tokens against Authentik's JWKS (`AuthentikStrategy`) and then resolving local `Membership` rows to determine role + KC/Gemeinde scope. Clients perform the actual Authorization Code + PKCE flow against Authentik directly. - Guests/Konfis get a temporary local account (first/last name required, no Authentik) created via `POST /auth/guest` with a KC invite code, returning a JWT signed with `GUEST_JWT_SECRET`. ## Modules implemented so far - `prisma/` — shared `PrismaClient` provider. - `auth/` — Authentik resource-server strategy (`AuthGuard('authentik')`) + guest invite-code login issuing a locally-signed JWT (`AuthGuard('guest')`). - `kc/` — KC (event) creation/listing, Leitungsteam-only. - `wahl/` — Wahl/Workshop administration (Leitungsteam-only), guest Teilnehmer submission, Force-Zuteilung overrides, and `ZuteilungService`: a faithful port of the WP plugin's `kc_run_zuteilung` (force-assignments → up to 3 wish rounds → random fill → consolidation of workshops that stay below `minTeilnehmer`), plus CSV export (`GET /wahl/:id/zuteilung/csv`). - `files/` — Leitungsteam-only upload (`POST /files/:kcId`, multipart) tagged with a `FileVisibility` tier; list/download (`GET /files/:kcId`, `GET /files/download/:fileId`) accept either an Authentik or a guest token and filter by the caller's allowed visibility tiers. Storage is behind a `StorageProvider` abstraction: defaults to Nextcloud via WebDAV (`WEBDAV_*` env vars), switchable to S3-compatible storage with `STORAGE_PROVIDER=s3` (`S3_*` env vars). - `chat/` — Gemeinde-Gruppenchat, 1:1 Direktnachrichten, Leitungsteam-über- greifende Kanäle und Broadcast (Konfis lesen nur). Channel administration and message history are plain REST (`ChatController`); real-time send/ receive is a raw `ws` gateway (`ChatGateway`, path `/chat`) since passport guards don't apply to WS upgrades — auth happens once via `?token=` at connect time (`TokenVerificationService` tries Authentik JWKS, then falls back to a guest token). Access rules live in `ChatService` and are shared between the REST and WS entry points. - `sync/` — replicates mutations between the local (on-site) and cloud server. `SyncService.capture()` is called by feature services right after a write, appending an entry to the append-only `SyncLogEntry` log tagged with this server's `SERVER_ID`. The local server (set `SYNC_ENABLED=true`, `SYNC_PEER_URL`) periodically pushes its new entries to the cloud's `POST /sync/ingest` and pulls the cloud's via `GET /sync/export` (`SyncSchedulerService`, every 30s), both guarded by `SYNC_SHARED_SECRET` (`SyncSecretGuard`) rather than user auth. No conflict resolution is implemented by design — the local server is the sole source of truth while an event is live. `POST /sync/trigger` lets a Leitungsteam member force an immediate push+pull. Known gap: only entity metadata is replicated; uploaded file bytes only resolve on both sides if local and cloud share the same Nextcloud/S3 backend. - `common/` — `Role` enum, `@Roles()` decorator, `RolesGuard` (KC-scoped, Leitungsteam roles are global across all KCs). All planned backend phases are implemented; remaining work is the Flutter clients (see repo root README).