4.8 KiB
Benachrichtigungen
Das Modul apps/backend/src/notifications stellt persoenliche In-App-
Benachrichtigungen bereit. Es gibt bewusst keinen E-Mail-Versand, keine Push
Notifications, keine WebSockets, keine Server-Sent Events, keine Queue, kein
Redis und keine Hintergrundjobs. Neue Benachrichtigungen werden beim Laden der
Anwendung und per Frontend-Polling abgerufen.
Datenmodell
Die Entity NotificationEntity wird in der Tabelle notifications gespeichert.
Jede Benachrichtigung gehoert genau einem Benutzer.
Felder:
id: UUIDuserId: Foreign Key aufusers.idtype: technischer Typ ausNotificationTypetitle: kurzer Plain-Text-Titel, maximal 150 Zeichenmessage: Plain-Text-Nachricht, maximal 1000 Zeichenlink: optionale interne Angular-Route, maximal 500 Zeichenmetadata: optionale JSON-Metadaten, maximal 4096 Bytes serialisiertreadAt:null, solange ungelesencreatedAt: UTC-ErstellungszeitdeletedAt: Soft Delete
Indizes:
user_id, created_atuser_id, read_atuser_id, deleted_at
Die Migration liegt unter
apps/backend/src/database/migrations/1720000001000-AddNotifications.ts und wird
nicht automatisch beim App-Start ausgefuehrt.
Typen
Benachrichtigungstypen sind Code, keine Datenbankdaten:
systemitem.createditem.updateduser.role-changed
Neue Module erweitern apps/backend/src/notifications/notification-types.ts
und nutzen anschliessend NotificationsService.
Permissions
notifications.readOwn: eigene Benachrichtigungen lesennotifications.updateOwn: eigene Benachrichtigungen markieren oder loeschennotifications.manage: administrativ Benachrichtigungen erzeugen
Die Systemrolle user erhaelt notifications.readOwn und
notifications.updateOwn. Die Rolle admin erhaelt ueber allPermissions
zusaetzlich notifications.manage.
API
Alle Endpunkte liegen unter dem bestehenden API-Praefix /api.
GET /notifications: eigene Benachrichtigungen, mitpage,pageSizeundstatus=all|read|unreadGET /notifications/unread-count: Anzahl ungelesener eigener BenachrichtigungenPATCH /notifications/:id/read: idempotent als gelesen markierenPATCH /notifications/:id/unread: idempotent als ungelesen markierenPATCH /notifications/read-all: alle eigenen Benachrichtigungen als gelesen markierenDELETE /notifications/:id: Soft Delete einer eigenen BenachrichtigungPOST /admin/notifications: administrative Erzeugung mitnotifications.manage
Normale Benutzer koennen keine fremde userId uebergeben. Der aktuelle Benutzer
wird serverseitig aus der Session bestimmt.
Interner Service
Andere Backend-Module erzeugen Benachrichtigungen ueber
NotificationsService, nicht direkt ueber ein Repository:
await notifications.createForUser({
userId,
type: NotificationType.ItemCreated,
title: 'Neuer Eintrag',
message: 'Der Eintrag "Beispiel" wurde erstellt.',
link: '/items/123',
metadata: { itemId: '123' },
});
Verfuegbare Methoden:
createForUsercreateForUsersgetForCurrentUsergetUnreadCountmarkAsReadmarkAsUnreadmarkAllAsReadsoftDelete
createForUsers begrenzt Bulk-Erzeugung auf 100 Zielbenutzer.
Integrationen
ItemsService benachrichtigt beim Erstellen eines Items den ersten aktiven
Administrator, sofern dieser nicht der Ersteller ist. Das ist eine bewusst kleine
Beispielregel und keine vollstaendige fachliche Eskalationslogik.
UsersService benachrichtigt betroffene Benutzer nach erfolgreicher
Rollenaenderung. Fehler beim Erzeugen werden geloggt; die bereits erfolgreiche
sicherheitsrelevante Rollenaenderung wird dadurch nicht unkontrolliert
zurueckgerollt.
Administrative Erzeugung wird im Audit-Log als NOTIFICATION_CREATED
protokolliert. Geloggt werden Actor, Zielbenutzer, Notification-ID, Typ und
Request-ID, nicht die vollstaendige Nachricht oder Metadata.
Frontend
NotificationStore verwendet Angular Signals fuer lokalen State und RxJS fuer
HTTP und Polling. Header-Panel und Seite /notifications verwenden denselben
Store, damit keine doppelten Requests fuer dieselben Daten entstehen.
Polling:
- Standardintervall: 60 Sekunden ueber
NOTIFICATION_POLL_INTERVAL_MS - nur bei angemeldetem Benutzer
- pausiert bei unsichtbarem Tab
- aktualisiert sofort beim Sichtbarwerden
- verhindert ueberlappende Count-Requests
- stoppt und leert State beim Logout
Links werden im Frontend nur navigiert, wenn sie interne relative Routen sind.
Titel und Nachricht werden normal interpoliert und nicht per innerHTML
gerendert.
Spaetere Echtzeitkommunikation
Wenn spaeter echte Echtzeitkommunikation noetig wird, sollte das als separate Architekturentscheidung erfolgen. Dann waeren Transport, Skalierung, Authentifizierung, Backpressure und Betrieb gemeinsam zu entscheiden, statt WebSockets oder Queues nebenbei in das In-App-Modul einzubauen.