feat(chat): free-form GRUPPE channels with mutable participants
CybeDefend Security Scan / cybedefend_scan (push) Failing after 19s
CybeDefend Security Scan / cybedefend_scan (pull_request) Failing after 1s

- ChatChannelType.GRUPPE: created by Leitungsteam (any KC) or a Gemeinde
  Verantwortliche/r (own KC), mixing team users and guests/Konfis as
  explicit ChatParticipant rows (unlike GEMEINDE_GRUPPE, membership is
  not derived from Gemeinde)
- POST /chat/:kcId/gruppen to create, GET participant-candidates, and
  POST/DELETE /chat/gruppen/:channelId/participants to manage membership
  (creator, LT, or Verantwortliche/r of that KC)
- ChatGateway broadcasts chat:participants-changed on membership change
- PushService updated for nullable ChatParticipant.userId + new
  guestAccountId column
- SyncService now replicates ChatParticipant
- Prisma migration + 14 new unit tests (75/75 passing), tsc clean
- CI: add .gitea/workflows/cybedefend-scan.yml + .cybedefend project config
This commit is contained in:
2026-09-12 13:25:48 +02:00
parent 1467c8bdf6
commit 288628f20e
13 changed files with 790 additions and 54 deletions
+22 -7
View File
@@ -103,13 +103,28 @@ client's host - no separate web server is needed.
(`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.
greifende Kanäle, Broadcast (Konfis lesen nur), and free-form `GRUPPE`
chats. 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.
- `POST /chat/:kcId/gruppen` lets a Leitungsteam member (any KC) or a
Gemeinde Verantwortliche/r (their own KC — `RolesGuard`'s kcId scoping)
create a `GRUPPE` channel with any mix of team users and Konfis (guests)
from that KC as initial participants (`participantUserIds`,
`participantGuestIds`); the creator is always included. Unlike
`GEMEINDE_GRUPPE`, membership isn't derived from `Gemeinde` — every
participant is an explicit `ChatParticipant` row, so a Konfi (who always
belongs to exactly one Gemeinde) can be added regardless of which
Gemeinde the chat's creator manages.
- `POST` / `DELETE /chat/gruppen/:channelId/participants` (body
`{ userId }` or `{ guestId }`) add/remove a participant afterwards.
Allowed for the channel's creator, any Leitungsteam member, or a
Verantwortliche/r of that KC — not the participants themselves, and not
guests.
- `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