docs: document chat decisions and update roadmap
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
aa9b5e332a
commit
cc4f65b32d
4 files changed
+68
-11
No files matched your search
@@ -85,7 +85,7 @@ Alle Zeitpunkte werden als UTC (`DateTimeOffset`/`DateTime` UTC) gespeichert und
|
||||
| Essen ✅ | `MealPlanEntry` (Date als `DateOnly`, MealType, DishName, Note, RecipeUrl, Ingredients, WishId, RowVersion, Soft Delete; eindeutig je Tag + Mahlzeit), `Dish` (Name, RecipeUrl, Ingredients, UseCount – für Autovervollständigung), `MealWish` (DishName, Note, Wunschtag/-mahlzeit, Status offen/übernommen/abgelehnt, Soft Delete) |
|
||||
| Aufgaben ✅ | `TodoTask` (Title, Notes, DueDate + DueTime → DueAt, ReminderMinutesBefore → RemindAt, Priority, Status inkl. „wartet auf Bestätigung“, Category, IsPrivate, IsChore, Points, NeedsApproval, AssignedToFamily, RecurrenceRule + RecurrenceMode, SeriesId/SeriesStart/SeriesIndex, CompletedBy/At, ApprovedBy/At, RowVersion, Soft Delete), `TaskAssignment`, `TaskChecklistItem`, `TaskHistoryEntry` (Verlauf), `PointsLedgerEntry` (Punktebuch). Kategorien kommen aus `FamilySettings.TaskCategories`; „wer/wann/genehmigt“ steht an der Aufgabe und im Verlauf (ADR-0014) |
|
||||
| Kalender ✅ | `FamilyCalendar` (Name, Color, IsDefault, Soft Delete), `CalendarPermission` (Rolle **oder** Benutzer → None/Read/WriteOwn/Write), `CalendarEvent` (Title, Location, Notes, IsAllDay, StartUtc, EndUtc, TimeZoneId, RecurrenceRule, ExceptionDates, Uid, PersonIds, ForWholeFamily, Sequence, RowVersion, Soft Delete; Overrides als eigene Zeile mit `SeriesMasterId` + `RecurrenceId`), `IcsFeed` (Owner, Scope, CalendarId, TaskMode, TokenHash, TokenPrefix, LastAccessAt, RevokedAt) – ADR-0015/0016 |
|
||||
| Chat | `ChatChannel` (Family / Direct), `ChatMember` (LastReadAt), `ChatMessage` (Text, Attachment), `ChatReaction` |
|
||||
| Chat ✅ | `ChatChannel` (Family / Direct), `ChatMember` (LastReadAt), `ChatMessage` (Text, HasImage), `ChatAttachment`, `ChatReaction` |
|
||||
| Betrieb | `AuditLogEntry` ✅ (ActorName, Action, TargetType/Id/Name, Details, CreatedAt), `PushSubscription` ✅ (UserId, Endpoint + Hash, P256dh, Auth, LastSuccessAt, FailureCount) |
|
||||
|
||||
## 4. Rollen- und Berechtigungsmodell
|
||||
@@ -149,9 +149,9 @@ Das UI bekommt die wirksamen Berechtigungen über `/api/auth/me` und blendet anh
|
||||
| Kalender-Abos ✅ | `GET/POST /api/calendar/feeds`, `POST /api/calendar/feeds/{id}/regenerate`, `DELETE /api/calendar/feeds/{id}`; Admin: `GET/DELETE /api/admin/calendar-feeds` |
|
||||
| Admin – Kalender ✅ | `GET/POST /api/admin/calendars`, `PUT/DELETE /api/admin/calendars/{id}` |
|
||||
| ICS-Feed | `GET /ics/{token}.ics` (öffentlich, Token-gesichert, ETag/Last-Modified) |
|
||||
| Chat | `GET /api/chat/channels`, `GET /api/chat/channels/{id}/messages?before=`, `POST …/messages`, `POST …/read` |
|
||||
| Chat ✅ | `GET /api/chat/channels`, `POST /api/chat/direct`, `GET /api/chat/channels/{id}/messages?before=&take=`, `POST …/messages`, `POST …/images` (Multipart), `POST …/read`, `DELETE /api/chat/messages/{id}`, `PUT /api/chat/messages/{id}/reactions`, `GET /api/chat/attachments/{id}` (ADR-0019) |
|
||||
| Dashboard ✅ | `GET /api/dashboard` (alle Kacheln der Rolle in einem Request, ADR-0018) |
|
||||
| Hubs | `/hubs/live` (Live-Updates: Einkauf ✅, Essenplan ✅, Aufgaben ✅, Termine ✅; das Dashboard lädt bei diesen Ereignissen neu), `/hubs/chat` |
|
||||
| Hubs | `/hubs/live` (Live-Updates: Einkauf ✅, Essenplan ✅, Aufgaben ✅, Termine ✅, Chat ✅ inkl. Hub-Methode `Typing`; das Dashboard lädt bei diesen Ereignissen neu) |
|
||||
| Betrieb | `/health`, `/alive` |
|
||||
|
||||
## 7. Frontend
|
||||
@@ -167,7 +167,7 @@ Das UI bekommt die wirksamen Berechtigungen über `/api/auth/me` und blendet anh
|
||||
## 8. Echtzeit
|
||||
|
||||
SignalR-Hub `/hubs/live` in der Api, vom Web-Host per YARP (WebSockets) durchgereicht, Cookie-Auth. Gruppen: `shopping` und `meals` (alle Rollen),
|
||||
`family` (ohne Gäste), `user:{id}`; später `channel:{id}` für den Chat. Slices senden nach dem Speichern über `IHubContext<LiveHub>`
|
||||
`family` (ohne Gäste), `user:{id}`. Der Chat nutzt keine Kanal-Gruppen, sondern sendet an die `user:{id}` der jeweils aktuell Berechtigten (ADR-0019). Slices senden nach dem Speichern über `IHubContext<LiveHub>`
|
||||
Ereignisse mit dem vollständigen DTO (z. B. `ShoppingItemChanged`), die Clients aktualisieren gezielt ihre Ansicht.
|
||||
Aufgaben gehen wegen privater Aufgaben nur an `user:{id}` der Benutzer, die sie sehen dürfen (`TaskChanged`/`TaskRemoved`, ADR-0014).
|
||||
Der Client (`LiveConnection`) verbindet sich mit unbegrenzten Wiederholungen neu und lädt danach den aktuellen Stand.
|
||||
|
||||
+11
-6
@@ -98,11 +98,15 @@ Legende: `[x]` erledigt, `[ ]` offen. Nach jeder Phase: Build ohne Warnungen, Te
|
||||
- [ ] Schnellerfassung Einkauf offline (Outbox) – bewusst nicht umgesetzt, nur online (ADR-0018)
|
||||
|
||||
## Phase 8 – Chat
|
||||
- [ ] Familienchat + Direktnachrichten
|
||||
- [ ] Gelesen-Status, Ungelesen-Zähler, Tippindikator
|
||||
- [ ] Paging
|
||||
- [ ] Bilder (Größenlimit, Validierung), Emoji-Reaktionen
|
||||
- [ ] Rechte Kind/Gast
|
||||
- [x] Familienchat (ein geseedeter Kanal) + Direktnachrichten (ein Kanal je Personenpaar, nur Admin/Familie/Kind untereinander, nie mit Gästen); Rechte bei jedem Aufruf gegen die aktuellen Einstellungen/Rollen geprüft (ADR-0019)
|
||||
- [x] Gelesen-Stand (monoton, „bis zu dieser Nachricht“, nur bei sichtbarer Unterhaltung) mit Haken (Direkt) bzw. „Gelesen von N“ (Familie), Ungelesen-Zähler je Kanal (eine gruppierte Abfrage) in Kanalliste, Navigation (Drawer + Bottom-Navigation) und Dashboard, Tippindikator über Hub-Methode (gedrosselt, kein Echo)
|
||||
- [x] Paging (30 je Seite, Cursor auf Serverzeit + Id), „Ältere Nachrichten laden“ mit erhaltener Scrollposition, Tageswechsel-Trenner in Europe/Berlin
|
||||
- [x] Bilder (PNG/JPEG/WebP ≤ 3 MB, Typ aus der Signatur, nur für Kanalmitglieder abrufbar, Bytes nie über SignalR, beim Löschen entfernt), Emoji-Reaktionen (feste Auswahl, idempotent)
|
||||
- [x] Rechte Kind/Gast: Gast ohne Freigabe sieht nichts (404/403), mit Einstellung nur den Familienkanal; Kinder nur an Familienmitglieder; Löschen: eigene, Admin zusätzlich im Familienkanal; deaktivierte Personen: Verlauf lesbar, Senden gesperrt
|
||||
- [x] Live über `/hubs/live` an die jeweils berechtigten `user:{id}`-Gruppen, Idempotenz über Client-Ids, Migration `Phase8Chat`
|
||||
- [x] Tests: Unit (Rechte je Rolle/Einstellung, DM-Regeln, Text/Bild/Reaktion/Löschen, Paging-Grenzen, Drosselung), bUnit (Kanalliste, Verlauf, Paging, Senden/Fehler, Bild, Reaktionen, Löschen, Live-Nachricht sichtbar/versteckt, Tippen, Haken, Gast, nicht erreichbar, neue Unterhaltung, Reconnect), Integration (401, Familienkanal + Ungelesen, Gast-Einstellung, DM-Privatsphäre, Paging, Bilder inkl. gefälschter Typen, Reaktionen, Löschen, deaktivierter Partner, SignalR nur für Mitglieder + Tippen)
|
||||
- [ ] Chat offline (Outbox) – bewusst nicht umgesetzt, nur online (ADR-0019)
|
||||
- [ ] Nachrichten bearbeiten, Antworten/Zitate, Suche, frei wählbare Gruppen – bewusst nicht umgesetzt
|
||||
|
||||
## Phase 9 – Härtung
|
||||
- [ ] Content-Security-Policy (Basis-Header X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, HSTS sind seit Phase 1 aktiv)
|
||||
@@ -124,7 +128,8 @@ Legende: `[x]` erledigt, `[ ]` offen. Nach jeder Phase: Build ohne Warnungen, Te
|
||||
- Einkaufsliste: „Rückgängig“ nach dem Entfernen, Sortierung per Drag & Drop, eigene Kategorien je Familie
|
||||
- Offline-Cache beim Abmelden leeren (geteilte Geräte)
|
||||
- Belohnungskatalog für Ämtchen-Punkte
|
||||
- Web-Push für Termine und Chat
|
||||
- Web-Push für Termine und Chat (Infrastruktur aus Phase 5 vorhanden)
|
||||
- Chat: Nachrichten bearbeiten, Antworten, Suche, Aufräumen alter Nachrichten/Bilder
|
||||
- CalDAV-Server bzw. Microsoft-Graph-Synchronisation (ADR-0017)
|
||||
- Kalender: Stunden-Raster mit Drag & Drop, „dieser und folgende“, Erinnerungen per Web-Push für Termine
|
||||
- Rezeptdatenbank
|
||||
|
||||
@@ -23,7 +23,8 @@ Kacheln („Gast: eingeschränkte Kacheln“, Familien-/Kinderaufgaben nur für
|
||||
5. **Schnellerfassung Einkauf** nutzt den vorhandenen Endpunkt `POST /api/shopping/lists/{id}/items` der Standardliste mit Client-Id
|
||||
(idempotent). Sie funktioniert nur online; die Outbox (ADR-0011) bleibt der Einkaufsseite vorbehalten.
|
||||
6. **Offline**: Bei Fehlern bleibt der zuletzt geladene Stand mit Hinweis sichtbar; ohne Stand gibt es „Erneut versuchen“.
|
||||
7. **Chat** (Phase 8): Die Kachel erscheint für Rollen mit Chat-Rechten; der Zähler ist bis zur Chat-Umsetzung 0.
|
||||
7. **Chat** (Phase 8, ADR-0019): Die Kachel erscheint für Rollen mit Chat-Rechten und zeigt die Summe der ungelesenen Nachrichten über alle sichtbaren Kanäle
|
||||
(`ChatAccessService.TotalUnreadAsync`); die Seite lädt bei Chat-Ereignissen (neue Nachricht, gelöscht, gelesen) neu.
|
||||
|
||||
## Folgen
|
||||
* Ein Request je Seitenaufruf; Kosten wachsen mit der Familiengröße, nicht mit der Datenmenge (Aufgaben: nicht erledigte, Termine: zwei Tage).
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
# ADR-0019: Chat – Familienkanal, Direktnachrichten, Live über den Live-Hub
|
||||
|
||||
* Status: akzeptiert
|
||||
* Datum: 2026-10-10
|
||||
|
||||
## Kontext
|
||||
Phase 8 liefert einen Familienchat und Direktnachrichten mit Gelesen-Status, Ungelesen-Zähler, Tippindikator, Paging, Bildern und
|
||||
Emoji-Reaktionen. Die Rollenmatrix (ADR-0008) kennt `FamilyChat` (Gast nur mit Einstellung) und `DirectMessages` (Kind „eingeschränkt“).
|
||||
|
||||
## Entscheidung
|
||||
1. **Modell:** `ChatChannel` (Familie – genau einer, geseedet – oder Direkt mit eindeutigem `DirectKey` je Personenpaar),
|
||||
`ChatMember` (Gelesen-Stand `LastReadAt`), `ChatMessage` (Soft Delete, Text oder Bildunterschrift), `ChatAttachment` (Bild, Id = Nachrichten-Id),
|
||||
`ChatReaction` (eindeutig je Nachricht/Person/Emoji). Eindeutige, gefilterte Indizes erzwingen „ein Familienkanal“, „ein Kanal je Paar“ und
|
||||
„eine Reaktion je Person und Emoji“ auch bei gleichzeitigen Requests.
|
||||
2. **Rechte werden bei jedem Aufruf geprüft** (`ChatAccessService`), nicht über SignalR-Gruppen: Familienkanal nur mit `FamilyChat`
|
||||
(Gäste nur mit der Einstellung), Direktkanäle nur für Mitglieder mit `DirectMessages`. Ein Wechsel der Einstellung, Rolle oder ein Deaktivieren wirkt
|
||||
sofort; unberechtigte Zugriffe auf Kanäle liefern 404 (kein Hinweis auf die Existenz), das Starten einer Direktunterhaltung ohne Recht 403.
|
||||
3. **Direktnachrichten – „Kinder nur an Familienmitglieder“:** gelesen als „beide Seiten dürfen Direktnachrichten nutzen“, also
|
||||
Admin/Familie/Kind untereinander, nie mit Gästen (`ChatRules.CanDirectMessage`). Ein Direktkanal ohne Nachrichten ist für das Gegenüber verborgen.
|
||||
Wird die andere Person deaktiviert, gelöscht oder läuft sie ab, bleibt der Verlauf lesbar, Senden liefert 409 (`CanSend = false`).
|
||||
4. **Live über `/hubs/live`** (kein zweiter Hub `/hubs/chat`): eine Verbindung je Gerät, der BFF leitet `/hubs` bereits weiter. Ereignisse gehen an die
|
||||
Gruppen `user:{id}` der **jetzt** berechtigten Empfänger (wie bei Aufgaben, ADR-0014), nie an die Sammelgruppe `family`. Ereignisse:
|
||||
`ChatMessageReceived` (DTO), `ChatMessageRemoved`, `ChatReactionsChanged`, `ChatReadChanged` (auch an den Leser selbst, damit andere Geräte den Zähler
|
||||
aktualisieren), `ChatTyping`. **Bilddaten werden nie über SignalR verschickt**, nur die URL.
|
||||
5. **Tippindikator:** Hub-Methode `Typing(channelId)` – prüft den Kanalzugriff, sendet nicht an den Absender zurück und ist je Verbindung und Kanal auf ein
|
||||
Signal alle 2 s gedrosselt (`ChatTypingThrottle`). Der Client sendet höchstens alle 3 s und zeigt ein Signal 4 s an.
|
||||
6. **Gelesen-Stand:** monoton (nie zurück), „bis zu dieser Nachricht“ statt „jetzt“ – Nachrichten, die während des Ladens eintreffen, bleiben ungelesen.
|
||||
Der Client markiert nur, wenn die Unterhaltung wirklich sichtbar ist (Seite offen und Tab im Vordergrund). Ohne Eintrag gilt die Kontoerstellung als Basis
|
||||
(neue Mitglieder starten ohne Alt-Nachrichten als ungelesen). Eigene und gelöschte Nachrichten zählen nie. Alle Zähler kommen aus **einer** gruppierten Abfrage.
|
||||
Haken: bei Direktnachrichten „gelesen“, im Familienkanal „Gelesen von N“.
|
||||
7. **Paging:** neueste zuerst, Cursor ist eine Nachrichten-Id, Reihenfolge `(CreatedAt, Id)` auf Serverzeit – Client-Guids sind geräteübergreifend nicht zeitlich
|
||||
geordnet. 30 Nachrichten je Seite (höchstens 100).
|
||||
8. **Bilder:** PNG/JPEG/WebP bis 3 MB, Typ ausschließlich aus der Signatur (nicht Dateiname/Content-Type des Clients), in der Datenbank (ADR-0009).
|
||||
Upload als Multipart an `POST …/images` mit Größenlimit unterhalb des Standards, ohne Antiforgery-Token, aber mit dem CSRF-Header (ADR-0006).
|
||||
Abruf nur für Kanalmitglieder, `private` cachebar, `nosniff`. Listenabfragen projizieren auf DTOs und laden nie Bildbytes. Beim Löschen der Nachricht
|
||||
werden Bild und Reaktionen sofort physisch entfernt.
|
||||
9. **Reaktionen:** feste Auswahl (👍 ❤️ 😂 😮 😢 🎉), zustandsbasiert (`PUT … {emoji, active}`), damit idempotent.
|
||||
10. **Löschen:** eigene Nachrichten immer, Administratoren zusätzlich im Familienkanal (Moderation); Direktnachrichten moderiert niemand mit.
|
||||
11. **Idempotentes Senden:** Client-Id je Nachricht; erneutes Senden liefert die vorhandene Nachricht (200), gelöscht bleibt gelöscht (204),
|
||||
fremde Id → 409.
|
||||
12. **Gelöschte Benutzer:** Nachrichten bleiben, der Name wird aus dem anonymisierten Datensatz gelesen (ADR-0010); fehlt er, zeigt die Anzeige „Unbekannt“.
|
||||
13. **Zeit:** Trenner und Uhrzeiten in Europe/Berlin über die Familienzeitzone, nicht die Browserzone.
|
||||
|
||||
## Bewusst nicht umgesetzt
|
||||
* Offline-Senden/Outbox (Chat nur online, nach Wiederverbindung wird der Verlauf nachgeladen).
|
||||
* Nachrichten bearbeiten, Antworten/Zitate, Gruppen mit frei wählbaren Mitgliedern, Suche, Anhänge außer Bildern.
|
||||
* Web-Push für neue Nachrichten (Backlog; Infrastruktur aus ADR-0014 ist vorhanden).
|
||||
|
||||
## Folgen
|
||||
* Der Verlauf wächst unbegrenzt in der Datenbank; Bilder zählen zum Backup. Aufräumen alter Nachrichten ist ein Backlog-Thema.
|
||||
* Der Familienchat hat genau einen Kanal – mehrere Familienkanäle erfordern später ein Mitgliedschaftsmodell wie bei Direktkanälen.
|
||||
Reference in new issue
Block a user