Files
breadcrumb-the-shire/docs/reference-notifications-module.md
fs 0c351f6aff refactor(audit): extract audit domain into self-contained module
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>
2026-03-25 21:12:49 +01:00

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.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):

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.