Files
KC-APP/backend/README.md
T
linusandClaude Sonnet 5 7aba87368d feat(backend): implement phases 0-6 (auth, kc, wahl, files, chat, sync)
Full NestJS backend for the KC-App platform:
- auth: Authentik OIDC resource-server strategy + guest invite-code JWT
  login, plus TokenVerificationService for the WS handshake path
- kc: Leitungsteam-only KC (event) creation/listing
- wahl: Wahl/Workshop admin, Force-Zuteilung overrides, ZuteilungService
  (port of the WP plugin's kc_run_zuteilung), CSV export
- files: LT-only upload with visibility tiers; list/download filtered by
  caller tier; StorageProvider abstraction (WebDAV/Nextcloud default, S3)
- chat: Gemeinde group / DM / LT-wide / broadcast channels; REST + raw ws
  gateway sharing ChatService access rules
- sync: append-only SyncLogEntry replication log + local<->cloud
  push/pull scheduler, shared-secret guarded
- common: Role enum, @Roles decorator, KC-scoped RolesGuard (LT global)
- serves client/web/ interim static web client under / (API under /api)

Typecheck, nest build and boot test pass; needs real Postgres/Authentik/
Nextcloud to run end to end.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-09 16:23:45 +02:00

77 lines
4.0 KiB
Markdown

# 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).