674 lines
25 KiB
Markdown
674 lines
25 KiB
Markdown
# Messaging Service Konzept
|
|
|
|
## Übersicht
|
|
|
|
Das Messaging-System ermöglicht es, Nachrichten über verschiedene Kanäle (E-Mail, SMS, WhatsApp, Teams Chat, etc.) an registrierte Benutzer zu senden, basierend auf Subscriptions. Es ist mandantenbasiert und unterstützt mehrere Kanäle pro Subscription.
|
|
|
|
## Architektur-Überlegungen
|
|
|
|
### Kernkonzept
|
|
|
|
Das System besteht aus zwei Hauptkomponenten:
|
|
|
|
1. **Subscription-Management**: Users können sich für Subscriptions registrieren und ihre bevorzugten Kanäle wählen
|
|
2. **Subscription-Funktionen**: Jede Subscription hat eine eigene Funktion (`subSubscriptionXxxx.py`), die komplett flexibel ist und die Nachrichten vorbereitet
|
|
|
|
### Setup-Architektur
|
|
|
|
**Wichtig**: Die Datenbank mit den Subscriptions ist die **stabile Basis-Referenz** und die Grundlage des Systems.
|
|
|
|
Es gibt zwei Seiten, die an die Datenbank andocken:
|
|
|
|
1. **User-Seite**: Users können sich für Subscriptions registrieren (subscribe)
|
|
- Dies ist unabhängig davon, ob bereits eine Subscription-Funktion existiert
|
|
- Users können sich bereits subscriben, bevor eine Funktion implementiert ist
|
|
|
|
2. **Funktions-Seite**: Subscription-Funktionen können später hinzugefügt werden
|
|
- Eine Subscription-Funktion ist **optional** und kann nachträglich implementiert werden
|
|
- Wenn eine Subscription-Funktion fehlt, wird beim Trigger ein Fehler geloggt, aber das System bleibt stabil
|
|
- Die Datenbank-Struktur ist unabhängig von der Existenz der Funktionen
|
|
|
|
**Workflow**:
|
|
1. Admin erstellt Subscription in der Datenbank (z.B. "SystemErrors")
|
|
2. Users können sich sofort für diese Subscription registrieren
|
|
3. Später kann die Subscription-Funktion (`subSubscriptionSystemErrors.py`) hinzugefügt werden
|
|
4. Erst dann können Trigger die Subscription ausführen
|
|
|
|
### Trigger-Mechanismus
|
|
|
|
Subscriptions können über verschiedene Trigger ausgeführt werden:
|
|
- **Trigger-Route-Endpunkt**: API-Endpunkt, der eine Subscription triggert
|
|
- **Workflow-Action**: Automatischer Workflow, der eine Subscription als Event auslöst
|
|
- **Scheduled Job**: Zeitgesteuerte Ausführung
|
|
- **Event-basiert**: System-Events (z.B. Audit-Log-Events)
|
|
|
|
**Wichtig**: Eine Subscription kann über **alle** Trigger-Typen ausgeführt werden. Es gibt keine Einschränkung pro Subscription.
|
|
|
|
### Subscription-Funktionen
|
|
|
|
Jede Subscription hat eine eigene Funktion im Format `subSubscriptionXxxx.py` (z.B. `subSubscriptionSystemErrors.py`, `subSubscriptionAuditLogin.py`).
|
|
|
|
**Naming-Regel**: Die `subscriptionId` muss nur Buchstaben und Unterstriche (`_`) enthalten. Sie wird direkt als Dateiname verwendet:
|
|
- `subscriptionId`: "SystemErrors" → Datei: `subSubscriptionSystemErrors.py`
|
|
- `subscriptionId`: "audit_login" → Datei: `subSubscriptionAuditLogin.py`
|
|
|
|
**Validierung**: Bei der Erstellung einer Subscription wird geprüft, dass `subscriptionId` nur Buchstaben und `_` enthält.
|
|
|
|
Diese Funktionen:
|
|
- Erhalten Event-Parameter vom Trigger (als Pydantic Model)
|
|
- Erhalten bereits die Registrierungen (werden vor dem Funktionsaufruf geholt)
|
|
- Bereiten die Nachrichten vor (können pro Kanal unterschiedlich sein)
|
|
- Rufen `sendMessage` für jeden Channel auf
|
|
- Haben vollständige Flexibilität bei der Nachrichtenerstellung
|
|
|
|
**Beispiel-Struktur:**
|
|
```python
|
|
# modules/services/serviceMessaging/subscriptions/subSubscriptionSystemErrors.py
|
|
from typing import List
|
|
from modules.datamodels.datamodelMessaging import (
|
|
MessagingEventParameters,
|
|
MessagingSubscriptionExecutionResult,
|
|
MessagingSubscriptionRegistration,
|
|
MessagingChannel
|
|
)
|
|
|
|
def execute(
|
|
eventParameters: MessagingEventParameters,
|
|
registrations: List[MessagingSubscriptionRegistration],
|
|
messagingService
|
|
) -> MessagingSubscriptionExecutionResult:
|
|
"""
|
|
Subscription-Funktion für System-Errors.
|
|
Erhält eventParameters vom Trigger und registrations bereits geholt.
|
|
"""
|
|
# Gruppiere nach Channel
|
|
emailRegistrations = [r for r in registrations if r.channel == MessagingChannel.EMAIL]
|
|
smsRegistrations = [r for r in registrations if r.channel == MessagingChannel.SMS]
|
|
|
|
# Bereite Nachrichten vor (können pro Channel unterschiedlich sein)
|
|
emailSubject = "System Error Report"
|
|
errors = eventParameters.triggerData.get('errors', [])
|
|
emailMessage = f"System errors detected: {errors}"
|
|
|
|
smsMessage = f"System Error: {len(errors)} errors detected"
|
|
|
|
messagesSent = 0
|
|
|
|
# Versende über sendMessage
|
|
for reg in emailRegistrations:
|
|
sendResult = messagingService.sendMessage(
|
|
subject=emailSubject,
|
|
message=emailMessage,
|
|
registration=reg
|
|
)
|
|
if sendResult.success:
|
|
messagesSent += 1
|
|
|
|
for reg in smsRegistrations:
|
|
sendResult = messagingService.sendMessage(
|
|
subject="", # SMS hat kein Subject
|
|
message=smsMessage,
|
|
registration=reg
|
|
)
|
|
if sendResult.success:
|
|
messagesSent += 1
|
|
|
|
return MessagingSubscriptionExecutionResult(
|
|
success=True,
|
|
messagesSent=messagesSent
|
|
)
|
|
```
|
|
|
|
## Datenmodell
|
|
|
|
### 1. MessagingChannel (Enum)
|
|
```python
|
|
class MessagingChannel(str, Enum):
|
|
EMAIL = "email"
|
|
SMS = "sms"
|
|
WHATSAPP = "whatsapp"
|
|
TEAMS_CHAT = "teams_chat"
|
|
# Weitere Kanäle können hier hinzugefügt werden
|
|
```
|
|
|
|
### 2. MessagingSubscription
|
|
- `id`: UUID
|
|
- `subscriptionId`: String (eindeutiger Identifier, z.B. "SystemErrors", "audit_login")
|
|
- **Validierung**: Nur Buchstaben und `_` erlaubt
|
|
- `subscriptionLabel`: String (Anzeigename)
|
|
- `mandateId`: String (Mandanten-ID - wird automatisch vom Interface gesetzt)
|
|
- `description`: Optional[String]
|
|
- `isSystemSubscription`: Boolean (nur Admin kann System-Subscriptions erstellen)
|
|
- `enabled`: Boolean
|
|
- System-Felder: `creationDate`, `lastModified`, `createdBy`, `modifiedBy`
|
|
|
|
### 3. MessagingSubscriptionRegistration
|
|
- `id`: UUID
|
|
- `subscriptionId`: String (Referenz zur Subscription)
|
|
- `userId`: String (Referenz zum User)
|
|
- `channel`: MessagingChannel
|
|
- `channelConfig`: String (z.B. E-Mail-Adresse, Telefonnummer, Teams User ID)
|
|
- `enabled`: Boolean (User kann sich temporär deaktivieren)
|
|
- System-Felder: `creationDate`, `lastModified`
|
|
|
|
### 4. MessagingDelivery
|
|
- `id`: UUID
|
|
- `subscriptionId`: String (Referenz zur Subscription)
|
|
- `userId`: String (Referenz zum User)
|
|
- `channel`: MessagingChannel
|
|
- `status`: Enum (PENDING, SENT, FAILED)
|
|
- `errorMessage`: Optional[String]
|
|
- `sentAt`: Optional[Float] (Timestamp wenn gesendet)
|
|
- System-Felder: `creationDate`
|
|
|
|
### 5. MessagingEventParameters (Pydantic Model)
|
|
- `triggerData`: dict - Event-Daten vom Trigger als Dictionary/JSON
|
|
|
|
### 6. MessagingSendResult (Pydantic Model)
|
|
- `success`: Boolean
|
|
- `deliveryId`: Optional[String] (ID des MessagingDelivery Records)
|
|
- `errorMessage`: Optional[String]
|
|
|
|
### 7. MessagingSubscriptionExecutionResult (Pydantic Model)
|
|
- `success`: Boolean
|
|
- `messagesSent`: Integer
|
|
- `errorMessage`: Optional[String]
|
|
- `extra="allow"` für zusätzliche Felder
|
|
|
|
## RBAC-Berechtigungsmodell
|
|
|
|
### Access Rules für MessagingSubscription
|
|
- **Context**: `DATA`
|
|
- **Item**: `MessagingSubscription`
|
|
- **Permissions**:
|
|
- **Admin**: Alle CRUD-Operationen auf alle Subscriptions
|
|
- **Mandate-Admin**: CRUD auf Subscriptions des eigenen Mandanten (außer System-Subscriptions)
|
|
- **User**: Read auf Subscriptions des eigenen Mandanten, Create/Update/Delete nur auf eigene Registrierungen
|
|
|
|
### Access Rules für MessagingSubscriptionRegistration
|
|
- **Context**: `DATA`
|
|
- **Item**: `MessagingSubscriptionRegistration`
|
|
- **Permissions**:
|
|
- **User**: CRUD auf eigene Registrierungen
|
|
- **Admin/Mandate-Admin**: Read auf alle Registrierungen des Mandanten
|
|
|
|
### Access Rules für MessagingDelivery
|
|
- **Context**: `DATA`
|
|
- **Item**: `MessagingDelivery`
|
|
- **Permissions**:
|
|
- **Admin/Mandate-Admin**: Read auf alle Deliveries
|
|
- **User**: Read auf eigene Deliveries
|
|
|
|
## Service-Architektur
|
|
|
|
### serviceMessaging/mainServiceMessaging.py
|
|
|
|
```python
|
|
class MessagingService:
|
|
def __init__(self, services):
|
|
self.services = services
|
|
self._messagingInterface = None # interfaceMessaging
|
|
|
|
# Core Messaging
|
|
def sendMessage(
|
|
self,
|
|
subject: str,
|
|
message: str,
|
|
registration: MessagingSubscriptionRegistration
|
|
) -> MessagingSendResult:
|
|
"""
|
|
Sendet eine Nachricht über einen Channel an einen User.
|
|
Erstellt MessagingDelivery Record.
|
|
|
|
Args:
|
|
subject: Subject der Nachricht (für E-Mail, leer für SMS)
|
|
message: Nachrichtentext
|
|
registration: MessagingSubscriptionRegistration mit Channel-Info und userId
|
|
|
|
Returns:
|
|
MessagingSendResult mit Status und Delivery-ID
|
|
"""
|
|
# Erstelle Delivery Record
|
|
delivery = MessagingDelivery(
|
|
subscriptionId=registration.subscriptionId,
|
|
userId=registration.userId,
|
|
channel=registration.channel,
|
|
status=DeliveryStatus.PENDING
|
|
)
|
|
|
|
# Speichere Delivery Record
|
|
deliveryRecord = self.services.interfaceDbComponent.createDelivery(delivery)
|
|
|
|
try:
|
|
# Versende über interfaceMessaging
|
|
success = self._getMessagingInterface().send(
|
|
channel=registration.channel,
|
|
recipient=registration.channelConfig,
|
|
subject=subject,
|
|
message=message
|
|
)
|
|
|
|
if success:
|
|
# Update Delivery Record
|
|
self.services.interfaceDbComponent.updateDelivery(
|
|
deliveryRecord["id"],
|
|
{
|
|
"status": DeliveryStatus.SENT,
|
|
"sentAt": getUtcTimestamp()
|
|
}
|
|
)
|
|
return MessagingSendResult(
|
|
success=True,
|
|
deliveryId=deliveryRecord["id"]
|
|
)
|
|
else:
|
|
# Update Delivery Record mit Fehler
|
|
self.services.interfaceDbComponent.updateDelivery(
|
|
deliveryRecord["id"],
|
|
{
|
|
"status": DeliveryStatus.FAILED,
|
|
"errorMessage": "Failed to send message"
|
|
}
|
|
)
|
|
return MessagingSendResult(
|
|
success=False,
|
|
deliveryId=deliveryRecord["id"],
|
|
errorMessage="Failed to send message"
|
|
)
|
|
except Exception as e:
|
|
logger.error(f"Error sending message: {str(e)}")
|
|
# Update Delivery Record mit Fehler
|
|
self.services.interfaceDbComponent.updateDelivery(
|
|
deliveryRecord["id"],
|
|
{
|
|
"status": DeliveryStatus.FAILED,
|
|
"errorMessage": str(e)
|
|
}
|
|
)
|
|
return MessagingSendResult(
|
|
success=False,
|
|
deliveryId=deliveryRecord["id"],
|
|
errorMessage=str(e)
|
|
)
|
|
|
|
def executeSubscription(
|
|
self,
|
|
subscriptionId: str,
|
|
eventParameters: MessagingEventParameters
|
|
) -> MessagingSubscriptionExecutionResult:
|
|
"""
|
|
Führt eine Subscription-Funktion aus.
|
|
|
|
Args:
|
|
subscriptionId: ID der Subscription
|
|
eventParameters: Parameter vom Trigger (als Pydantic Model)
|
|
|
|
Returns:
|
|
MessagingSubscriptionExecutionResult
|
|
|
|
Raises:
|
|
ValueError: Wenn Subscription nicht existiert oder nicht enabled ist
|
|
FileNotFoundError: Wenn Subscription-Funktion nicht gefunden wird
|
|
"""
|
|
# Prüfe ob Subscription existiert und enabled ist
|
|
subscription = self.services.interfaceDbComponent.getSubscription(subscriptionId)
|
|
if not subscription:
|
|
raise ValueError(f"Subscription {subscriptionId} not found")
|
|
if not subscription.enabled:
|
|
logger.warning(f"Subscription {subscriptionId} is disabled, skipping execution")
|
|
return MessagingSubscriptionExecutionResult(
|
|
success=False,
|
|
messagesSent=0,
|
|
errorMessage="Subscription is disabled"
|
|
)
|
|
|
|
# Hole alle aktiven Registrierungen für diese Subscription
|
|
registrations = self._getSubscribers(subscriptionId)
|
|
|
|
if not registrations:
|
|
logger.info(f"No active registrations for subscription {subscriptionId}")
|
|
return MessagingSubscriptionExecutionResult(
|
|
success=True,
|
|
messagesSent=0
|
|
)
|
|
|
|
# Lade Subscription-Funktion dynamisch
|
|
subscriptionFunction = self._loadSubscriptionFunction(subscriptionId)
|
|
if not subscriptionFunction:
|
|
errorMsg = f"Subscription function not found for {subscriptionId}"
|
|
logger.error(errorMsg)
|
|
raise FileNotFoundError(errorMsg)
|
|
|
|
# Führe Funktion aus mit Registrierungen
|
|
try:
|
|
return subscriptionFunction.execute(eventParameters, registrations, self)
|
|
except Exception as e:
|
|
logger.error(f"Error executing subscription {subscriptionId}: {str(e)}", exc_info=True)
|
|
return MessagingSubscriptionExecutionResult(
|
|
success=False,
|
|
messagesSent=0,
|
|
errorMessage=str(e)
|
|
)
|
|
|
|
# Helper Methods
|
|
def _getSubscribers(
|
|
self,
|
|
subscriptionId: str,
|
|
channel: Optional[MessagingChannel] = None
|
|
) -> List[MessagingSubscriptionRegistration]:
|
|
"""Holt alle aktiven Subscriber einer Subscription"""
|
|
return self.services.interfaceDbComponent.getAllRegistrations(
|
|
subscriptionId=subscriptionId,
|
|
filters={"enabled": True} if not channel else {"enabled": True, "channel": channel.value}
|
|
)
|
|
|
|
def _loadSubscriptionFunction(self, subscriptionId: str) -> Optional[Callable]:
|
|
"""
|
|
Lädt die Subscription-Funktion dynamisch.
|
|
|
|
Returns:
|
|
Callable mit execute-Methode oder None wenn nicht gefunden
|
|
|
|
Note:
|
|
subscriptionId wird direkt als Dateiname verwendet (z.B. "SystemErrors" -> subSubscriptionSystemErrors.py)
|
|
"""
|
|
# Format: subSubscription{subscriptionId}.py
|
|
functionName = f"subSubscription{subscriptionId}"
|
|
moduleName = f"modules.services.serviceMessaging.subscriptions.{functionName}"
|
|
|
|
try:
|
|
# Dynamisches Import
|
|
import importlib
|
|
subscriptionModule = importlib.import_module(moduleName)
|
|
return subscriptionModule
|
|
except ImportError:
|
|
# Funktion existiert noch nicht - das ist OK
|
|
logger.debug(f"Subscription function {moduleName} not found (this is OK if not yet implemented)")
|
|
return None
|
|
|
|
def _getMessagingInterface(self):
|
|
"""Holt das Messaging-Interface (interfaceMessaging)"""
|
|
if not self._messagingInterface:
|
|
from modules.interfaces.interfaceMessaging import getInterface
|
|
self._messagingInterface = getInterface()
|
|
return self._messagingInterface
|
|
```
|
|
|
|
## Connector-Architektur
|
|
|
|
### Überblick
|
|
|
|
Für jeden Channel gibt es einen separaten Connector in `modules/connectors/`:
|
|
- `connectorMessagingEmail.py` - Azure Communication Services
|
|
- `connectorMessagingSms.py` - Twilio
|
|
- `connectorMessagingWhatsapp.py` - WhatsApp API (zukünftig)
|
|
- `connectorMessagingTeams.py` - Microsoft Teams API (zukünftig)
|
|
|
|
### Interface: interfaceMessaging.py
|
|
|
|
Das Interface `modules/interfaces/interfaceMessaging.py` stellt eine einheitliche Schnittstelle bereit, die alle Connectors nach dem gleichen Schema verwendet:
|
|
|
|
```python
|
|
class MessagingInterface:
|
|
def send(
|
|
self,
|
|
channel: MessagingChannel,
|
|
recipient: str,
|
|
subject: str,
|
|
message: str
|
|
) -> bool:
|
|
"""
|
|
Sendet eine Nachricht über den angegebenen Channel.
|
|
|
|
Args:
|
|
channel: MessagingChannel Enum
|
|
recipient: Empfänger-Adresse (E-Mail, Telefonnummer, etc.)
|
|
subject: Betreff (für E-Mail, leer für SMS)
|
|
message: Nachrichtentext
|
|
|
|
Returns:
|
|
bool: True wenn erfolgreich, False bei Fehler
|
|
"""
|
|
# Wähle Connector basierend auf Channel
|
|
if channel == MessagingChannel.EMAIL:
|
|
connector = ConnectorMessagingEmail()
|
|
elif channel == MessagingChannel.SMS:
|
|
connector = ConnectorMessagingSms()
|
|
elif channel == MessagingChannel.WHATSAPP:
|
|
connector = ConnectorMessagingWhatsapp()
|
|
elif channel == MessagingChannel.TEAMS_CHAT:
|
|
connector = ConnectorMessagingTeams()
|
|
else:
|
|
logger.error(f"Unknown channel: {channel}")
|
|
return False
|
|
|
|
# Rufe Connector mit einheitlichem Schema auf
|
|
return connector.send(recipient=recipient, subject=subject, message=message)
|
|
```
|
|
|
|
### Connector-Struktur
|
|
|
|
Jeder Connector implementiert die gleiche Schnittstelle:
|
|
|
|
```python
|
|
# modules/connectors/connectorMessagingEmail.py
|
|
class ConnectorMessagingEmail:
|
|
def __init__(self):
|
|
# Initialisiere Azure Communication Services Client
|
|
pass
|
|
|
|
def send(self, recipient: str, subject: str, message: str) -> bool:
|
|
"""
|
|
Sendet E-Mail über Azure Communication Services.
|
|
|
|
Args:
|
|
recipient: E-Mail-Adresse
|
|
subject: Betreff
|
|
message: Nachrichtentext (kann HTML enthalten)
|
|
|
|
Returns:
|
|
bool: True wenn erfolgreich
|
|
"""
|
|
# Implementierung hier
|
|
pass
|
|
```
|
|
|
|
```python
|
|
# modules/connectors/connectorMessagingSms.py
|
|
class ConnectorMessagingSms:
|
|
def __init__(self):
|
|
# Initialisiere Twilio Client
|
|
pass
|
|
|
|
def send(self, recipient: str, subject: str, message: str) -> bool:
|
|
"""
|
|
Sendet SMS über Twilio.
|
|
|
|
Args:
|
|
recipient: Telefonnummer (mit Ländercode)
|
|
subject: Wird ignoriert (SMS hat kein Subject)
|
|
message: Nachrichtentext
|
|
|
|
Returns:
|
|
bool: True wenn erfolgreich
|
|
"""
|
|
# Implementierung hier
|
|
pass
|
|
```
|
|
|
|
**Vorteile**:
|
|
- Einheitliches Schema für alle Channels
|
|
- Einfache Erweiterung um neue Channels
|
|
- Klare Trennung zwischen Service-Logik und Channel-Implementierung
|
|
- Connectors können unabhängig getestet werden
|
|
|
|
## Interface-Methoden (interfaceDbComponentObjects.py)
|
|
|
|
```python
|
|
# Subscription Management
|
|
def getAllSubscriptions(self, pagination: Optional[PaginationParams] = None) -> Union[List[MessagingSubscription], PaginatedResult]
|
|
def getSubscription(self, subscriptionId: str) -> Optional[MessagingSubscription]
|
|
def getSubscriptionById(self, id: str) -> Optional[MessagingSubscription] # By UUID
|
|
def createSubscription(self, subscriptionData: Dict[str, Any]) -> Dict[str, Any]
|
|
def updateSubscription(self, subscriptionId: str, updateData: Dict[str, Any]) -> Dict[str, Any]
|
|
def deleteSubscription(self, subscriptionId: str) -> bool
|
|
|
|
# Registration Management
|
|
def getAllRegistrations(self, subscriptionId: Optional[str] = None, userId: Optional[str] = None,
|
|
pagination: Optional[PaginationParams] = None) -> Union[List[MessagingSubscriptionRegistration], PaginatedResult]
|
|
def getRegistration(self, registrationId: str) -> Optional[MessagingSubscriptionRegistration]
|
|
def createRegistration(self, registrationData: Dict[str, Any]) -> Dict[str, Any]
|
|
def updateRegistration(self, registrationId: str, updateData: Dict[str, Any]) -> Dict[str, Any]
|
|
def deleteRegistration(self, registrationId: str) -> bool
|
|
def subscribeUser(self, subscriptionId: str, userId: str, channel: MessagingChannel, channelConfig: str) -> Dict[str, Any]
|
|
def unsubscribeUser(self, subscriptionId: str, userId: str, channel: MessagingChannel) -> bool
|
|
|
|
# Delivery Management
|
|
def createDelivery(self, delivery: MessagingDelivery) -> Dict[str, Any]
|
|
def updateDelivery(self, deliveryId: str, updateData: Dict[str, Any]) -> Dict[str, Any]
|
|
def getDeliveries(self, subscriptionId: Optional[str] = None, userId: Optional[str] = None,
|
|
pagination: Optional[PaginationParams] = None) -> Union[List[MessagingDelivery], PaginatedResult]
|
|
def getDelivery(self, deliveryId: str) -> Optional[MessagingDelivery]
|
|
```
|
|
|
|
## Route-Struktur (routeMessaging.py)
|
|
|
|
### Rate Limits
|
|
- **Subscription Endpoints**: 60 requests/minute pro Session
|
|
- **Registration Endpoints**: 60 requests/minute pro Session
|
|
- **Trigger Endpoints**: 60 requests/minute pro `subscriptionId`
|
|
|
|
### Subscription Endpoints
|
|
- `GET /api/messaging/subscriptions` - Liste aller Subscriptions
|
|
- `POST /api/messaging/subscriptions` - Neue Subscription erstellen
|
|
- `GET /api/messaging/subscriptions/{subscriptionId}` - Subscription abrufen
|
|
- `PUT /api/messaging/subscriptions/{subscriptionId}` - Subscription aktualisieren
|
|
- `DELETE /api/messaging/subscriptions/{subscriptionId}` - Subscription löschen
|
|
|
|
### Registration Endpoints
|
|
- `GET /api/messaging/subscriptions/{subscriptionId}/registrations` - Registrierungen einer Subscription
|
|
- `POST /api/messaging/subscriptions/{subscriptionId}/subscribe` - User zu Subscription hinzufügen
|
|
- `DELETE /api/messaging/subscriptions/{subscriptionId}/unsubscribe` - User von Subscription entfernen
|
|
- `GET /api/messaging/registrations` - Eigene Registrierungen des Users
|
|
- `PUT /api/messaging/registrations/{registrationId}` - Registrierung aktualisieren (z.B. enabled/disabled)
|
|
- `DELETE /api/messaging/registrations/{registrationId}` - Registrierung löschen
|
|
|
|
### Trigger Endpoints
|
|
- `POST /api/messaging/trigger/{subscriptionId}` - Trigger-Endpunkt für externe Systeme/Workflows
|
|
- Body: `{"eventParameters": {...}}`
|
|
- Führt `executeSubscription` aus
|
|
- Rate Limit: 60 requests/minute pro `subscriptionId`
|
|
|
|
### Delivery Endpoints
|
|
- `GET /api/messaging/deliveries` - Delivery-Historie
|
|
- `GET /api/messaging/deliveries/{deliveryId}` - Delivery abrufen
|
|
|
|
## Use Cases
|
|
|
|
### 1. Trigger-Route-Endpunkt
|
|
```python
|
|
@router.post("/api/messaging/trigger/{subscriptionId}")
|
|
@limiter.limit("60/minute", key_func=lambda: f"{request.path_params['subscriptionId']}")
|
|
async def trigger_subscription(
|
|
request: Request,
|
|
subscriptionId: str,
|
|
eventParameters: Dict[str, Any] = Body(...),
|
|
currentUser: User = Depends(getCurrentUser)
|
|
):
|
|
"""Trigger-Endpunkt für externe Systeme"""
|
|
# RBAC-Check: Nur Admin/Mandate-Admin kann triggern
|
|
messagingService = request.app.state.services.messaging
|
|
|
|
# Konvertiere Dict zu Pydantic Model
|
|
eventParams = MessagingEventParameters(triggerData=eventParameters)
|
|
|
|
executionResult = messagingService.executeSubscription(subscriptionId, eventParams)
|
|
return executionResult
|
|
```
|
|
|
|
### 2. Workflow-Action
|
|
Workflow kann `messaging.executeSubscription` Action aufrufen mit:
|
|
- `subscriptionId`: String
|
|
- `eventParameters`: Dict (wird zu MessagingEventParameters konvertiert)
|
|
|
|
### 3. Scheduled Job (System Errors)
|
|
```python
|
|
def _sendSystemErrorsJob(self):
|
|
"""Tägliches Mail an Admin mit Log-Errors"""
|
|
# Sammle Errors aus Log
|
|
errors = self._collectLogErrors()
|
|
if errors:
|
|
messagingService = self.services.messaging
|
|
eventParams = MessagingEventParameters(triggerData={"errors": errors, "timestamp": getUtcTimestamp()})
|
|
messagingService.executeSubscription(
|
|
subscriptionId="SystemErrors",
|
|
eventParameters=eventParams
|
|
)
|
|
```
|
|
|
|
### 4. Audit Log Events
|
|
```python
|
|
# In audit_logger.py
|
|
def logAuditEvent(eventType: str, userId: str, details: Dict):
|
|
# ... existing audit logging ...
|
|
|
|
# Trigger messaging if subscription exists
|
|
if eventType == "login":
|
|
messagingService = getMessagingService(getAdminUser())
|
|
eventParams = MessagingEventParameters(
|
|
triggerData={
|
|
"eventType": eventType,
|
|
"userId": userId,
|
|
"details": details,
|
|
"timestamp": getUtcTimestamp()
|
|
}
|
|
)
|
|
messagingService.executeSubscription(
|
|
subscriptionId="audit_login",
|
|
eventParameters=eventParams
|
|
)
|
|
```
|
|
|
|
## Error Handling
|
|
|
|
Fehler werden wie in anderen Modulen behandelt:
|
|
- Normale Logger-Ausgabe mit `logger.error()`, `logger.warning()`, `logger.info()`
|
|
- Exceptions werden geloggt mit `exc_info=True` für Stack-Traces
|
|
- Keine speziellen Error-Handler, Standard-Python-Exception-Handling
|
|
|
|
## Konfiguration
|
|
|
|
### Environment Variables
|
|
```env
|
|
# Email (Azure Communication Services)
|
|
MESSAGING_ACS_CONNECTION_STRING=...
|
|
MESSAGING_ACS_SENDER_EMAIL=...
|
|
|
|
# SMS (Twilio)
|
|
MESSAGING_TWILIO_ACCOUNT_SID=...
|
|
MESSAGING_TWILIO_AUTH_TOKEN=...
|
|
MESSAGING_TWILIO_FROM_NUMBER=...
|
|
|
|
# WhatsApp (zukünftig)
|
|
MESSAGING_WHATSAPP_API_KEY=...
|
|
|
|
# Teams Chat (zukünftig)
|
|
MESSAGING_TEAMS_APP_ID=...
|
|
MESSAGING_TEAMS_APP_SECRET=...
|
|
```
|
|
|
|
## Implementierungsreihenfolge
|
|
|
|
1. **Datenmodelle** (`datamodelMessaging.py`) ✅
|
|
2. **Connectors** (`connectorMessagingEmail.py`, `connectorMessagingSms.py`)
|
|
3. **Interface** (`interfaceMessaging.py`)
|
|
4. **Interface-Methoden** (`interfaceDbComponentObjects.py`)
|
|
5. **Service-Implementierung** (`serviceMessaging/mainServiceMessaging.py`)
|
|
6. **Routes** (`routeMessaging.py`)
|
|
7. **Integration** (Service in `__init__.py` registrieren, Routes registrieren)
|
|
8. **Subscription-Funktionen** (`serviceMessaging/subscriptions/`)
|
|
- Können nachträglich hinzugefügt werden
|
|
- System funktioniert auch ohne Funktionen (Users können sich subscriben)
|
|
9. **Tests** (Unit-Tests für Service, Integration-Tests für Routes)
|
|
|
|
**Wichtig**: Die Datenbank-Struktur ist die Basis. Subscription-Funktionen sind optional und können später hinzugefügt werden.
|