forked from fa/breadcrumb-the-shire
Move the entire audit subsystem (system audit, API audit, import audit, user lifecycle audit, frontend telemetry) from core into modules/audit/. Core decoupling via interface-based injection: - AuditRecorderInterface replaces SystemAuditService in 10+ core services - UserLifecycleAuditInterface / ImportAuditInterface for specialized flows - NullAuditRecorder fallback when audit module is disabled - ApiBootstrap/ApiResponse use null-safe callable resolvers Module structure (modules/audit/): - Manifest with routes, permissions, scheduler jobs, authorization policy - 9 services, 8 repositories, 6 domain enums, 4 job handlers - 33 page files, 4 JS files, 8 test files, migration scripts, i18n Core cleanup: - OperationsAuthorizationPolicy, UiCapabilityMap, PermissionService surgically cleaned of audit-specific constants - Sidebar template cleared of hardcoded audit navigation - AuditModuleIsolationContractTest ensures no future core→module coupling All quality gates pass: 1346 tests (19,276 assertions), PHPStan level 5 clean, architecture contracts verified. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
160 lines
4.9 KiB
Markdown
160 lines
4.9 KiB
Markdown
# Notifications Modul (V1)
|
|
|
|
Letzte Aktualisierung: 2026-03-25
|
|
|
|
## Ziel
|
|
|
|
Referenz fuer das Modul `notifications`: Event-Registrierung, Erzeugungsregeln, Guardrails, Frontend-Vertrag und Testbarkeit.
|
|
|
|
## Scope v1
|
|
|
|
- In-App Notifications (Bell + Dropdown), kein E-Mail-Channel.
|
|
- Core-5 Eventtypen:
|
|
- `user.created`
|
|
- `user.deleted`
|
|
- `user.activated`
|
|
- `user.deactivated`
|
|
- `user.assignment_changed`
|
|
- Dedupe fuer diese Typen mit 30 Minuten Fenster.
|
|
|
|
## Modul-Verkabelung
|
|
|
|
Kanonische Quelle: `modules/notifications/module.php`
|
|
|
|
- `event_listeners`
|
|
- mappt Eventtyp -> Listenerklasse + Methode `handle`.
|
|
- `ui_slots`
|
|
- `topbar.right_item`: Bell-Template
|
|
- `layout.head_style`: Modul-CSS fuer Topbar + Dropdown
|
|
- `runtime.component`: Bell-JS (`initNotificationBell`)
|
|
- `permissions`
|
|
- `notifications.view`
|
|
- `authorization_policies`
|
|
- `NotificationsAuthorizationPolicy`
|
|
- `layout_context_providers`
|
|
- liefert `notifications.nav.unread_count` in den Layout-Context
|
|
- `session_providers`
|
|
- synchronisiert `module.notifications.unread_count` aus DB
|
|
|
|
## Event -> Notification Fluss
|
|
|
|
1. Ein User-Lifecycle-Workflow dispatcht ein Domain-Event (z. B. `user.activated`).
|
|
2. Der in `module.php` registrierte Listener verarbeitet das Payload.
|
|
3. Listener ruft `NotificationService` auf.
|
|
4. `NotificationService` orchestriert Empfaenger/Fan-out und Dedupe-Metadaten.
|
|
5. `NotificationRepository` persistiert in `user_notifications`.
|
|
6. Bell-Endpunkte lesen tenant-scoped Daten und aktualisieren den Session-`unread_count`.
|
|
|
|
## Producer-Contract (fuer Core + andere Module)
|
|
|
|
Stabile Erzeugung erfolgt ueber:
|
|
|
|
- `MintyPHP\Module\Notifications\Service\NotificationMessage`
|
|
- `MintyPHP\Module\Notifications\Service\NotificationService::createFromMessage(...)`
|
|
- `NotificationService::createForTenantUsersFromMessage(...)`
|
|
- `NotificationService::createForTenantAdminUsersFromMessage(...)`
|
|
|
|
Beispiel (locale-neutral, mit i18n-Key):
|
|
|
|
```php
|
|
use MintyPHP\Module\Notifications\Service\NotificationMessage;
|
|
use MintyPHP\Module\Notifications\Service\NotificationService;
|
|
|
|
$message = NotificationMessage::localized(
|
|
'user.created',
|
|
'New user: %s',
|
|
[$displayName],
|
|
null,
|
|
[],
|
|
'admin/users/edit/' . $uuid,
|
|
['user_id' => $userId, 'uuid' => $uuid]
|
|
);
|
|
|
|
$notificationService->createForTenantAdminUsersFromMessage(
|
|
$tenantId,
|
|
$message,
|
|
$actorUserId,
|
|
$allowedAdminSet
|
|
);
|
|
```
|
|
|
|
Plain/Fallback-Erzeugung bleibt moeglich mit `NotificationMessage::plain(...)`.
|
|
|
|
## Datenvertrag Notification
|
|
|
|
Persistierter Kernvertrag:
|
|
|
|
- `type` (string)
|
|
- `title` (string)
|
|
- `body` (nullable string)
|
|
- `link` (nullable string)
|
|
- `data` (nullable JSON)
|
|
|
|
Erweiterung fuer locale-neutrale Texte:
|
|
|
|
- `data.__message.title_key` (string)
|
|
- `data.__message.title_params` (array)
|
|
- `data.__message.body_key` (string, optional)
|
|
- `data.__message.body_params` (array, optional)
|
|
|
|
Wenn `__message` vorhanden ist, wird der Anzeigetext beim Lesen in der aktuellen
|
|
Empfaenger-Locale gerendert (Fallback auf persistiertes `title`/`body` bleibt erhalten).
|
|
|
|
Dedupe-Metadaten (intern):
|
|
|
|
- `dedupe_fingerprint`
|
|
- `dedupe_bucket`
|
|
- `dedupe_until`
|
|
|
|
## Guardrails (Server)
|
|
|
|
- Alle Bell-Endpunkte erzwingen:
|
|
- Login
|
|
- Ability `notifications.view`
|
|
- POST-Endpunkte (`mark-read`, `delete`) pruefen CSRF.
|
|
- Tenant-Scope wird serverseitig aus `current_tenant` erzwungen.
|
|
- SQL ausschliesslich im Repository (`NotificationRepository`), Service nur Orchestrierung.
|
|
|
|
## Guardrails (Frontend)
|
|
|
|
- Endpunkte werden mit App-Base-URL gebaut (kein harter Root-Pfad).
|
|
- Fehlerpfade laufen ueber zentrale Telemetrie (`warn_once`).
|
|
- A11y-Klickzeilen im Dropdown sind lokal gestylt, damit globale `[role="button"]` Regeln nicht durchschlagen.
|
|
- Default-`details/summary`-Chevron wird pro Komponente ueber `data-summary-chevron="none"` deaktiviert.
|
|
|
|
## Template/Partial Best Practice
|
|
|
|
- Core-Partials immer ueber `templatePath('partials/...')` einbinden.
|
|
- Keine fragilen relativen Tiefenpfade auf Core-Templates verwenden.
|
|
- Modulinterne Templates bleiben lokal relativ zum Modul.
|
|
|
|
## CSS Token Best Practice (Module)
|
|
|
|
- Modul-CSS soll auf Core-Variablen aufsetzen (z. B. `--app-color`, `--app-muted-color`, `--app-dropdown-*`, `--app-action-danger-*`).
|
|
- Modul-spezifische Aliase sind ok (`--app-notification-*`), solange sie auf Core-Tokens mappen.
|
|
- Harte Hex-Fallbacks nur als Ausnahme; Standard ist tokenbasiert.
|
|
|
|
## Tests
|
|
|
|
### Unit
|
|
|
|
- Listener-Tests fuer alle Core-5 Typen:
|
|
- Empfaengerregeln
|
|
- Actor-Exclusion
|
|
- ungultige/leere Payloads
|
|
- Service-Tests fuer Dedupe (created vs suppressed)
|
|
- Policy-Tests fuer `notifications.view`
|
|
|
|
### Integration/Flow
|
|
|
|
- End-to-End fuer create/delete/activate/deactivate/assignment-change.
|
|
- Bell-Endpunkte mit Tenant-Scope und Permission-Pruefung.
|
|
- Bulk-Flows dispatchen konsistent.
|
|
|
|
### Manueller Smoke
|
|
|
|
1. Zwei Admins im selben Tenant, einer als Actor.
|
|
2. Actor fuehrt User-Aktion aus.
|
|
3. Zweiter Admin sieht Bell-Badge + Listeneintrag.
|
|
4. Wiederholung innerhalb 30 Minuten erzeugt kein Duplikat.
|