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>
4.9 KiB
4.9 KiB
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.createduser.deleteduser.activateduser.deactivateduser.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.
- mappt Eventtyp -> Listenerklasse + Methode
ui_slotstopbar.right_item: Bell-Templatelayout.head_style: Modul-CSS fuer Topbar + Dropdownruntime.component: Bell-JS (initNotificationBell)
permissionsnotifications.view
authorization_policiesNotificationsAuthorizationPolicy
layout_context_providers- liefert
notifications.nav.unread_countin den Layout-Context
- liefert
session_providers- synchronisiert
module.notifications.unread_countaus DB
- synchronisiert
Event -> Notification Fluss
- Ein User-Lifecycle-Workflow dispatcht ein Domain-Event (z. B.
user.activated). - Der in
module.phpregistrierte Listener verarbeitet das Payload. - Listener ruft
NotificationServiceauf. NotificationServiceorchestriert Empfaenger/Fan-out und Dedupe-Metadaten.NotificationRepositorypersistiert inuser_notifications.- 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\NotificationMessageMintyPHP\Module\Notifications\Service\NotificationService::createFromMessage(...)NotificationService::createForTenantUsersFromMessage(...)NotificationService::createForTenantAdminUsersFromMessage(...)
Beispiel (locale-neutral, mit i18n-Key):
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_fingerprintdedupe_bucketdedupe_until
Guardrails (Server)
- Alle Bell-Endpunkte erzwingen:
- Login
- Ability
notifications.view
- POST-Endpunkte (
mark-read,delete) pruefen CSRF. - Tenant-Scope wird serverseitig aus
current_tenanterzwungen. - 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 ueberdata-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
- Zwei Admins im selben Tenant, einer als Actor.
- Actor fuehrt User-Aktion aus.
- Zweiter Admin sieht Bell-Badge + Listeneintrag.
- Wiederholung innerhalb 30 Minuten erzeugt kein Duplikat.