243 lines
8.8 KiB
Python
243 lines
8.8 KiB
Python
# Copyright (c) 2025 Patrick Motsch
|
|
# All rights reserved.
|
|
"""
|
|
Messaging service for sending messages across different channels.
|
|
Provides subscription-based messaging functionality.
|
|
"""
|
|
|
|
import logging
|
|
import re
|
|
from typing import List, Optional, Callable
|
|
from modules.datamodels.datamodelMessaging import (
|
|
MessagingSubscription,
|
|
MessagingSubscriptionRegistration,
|
|
MessagingDelivery,
|
|
MessagingChannel,
|
|
MessagingEventParameters,
|
|
MessagingSendResult,
|
|
MessagingSubscriptionExecutionResult,
|
|
DeliveryStatus
|
|
)
|
|
from modules.interfaces.interfaceMessaging import getInterface as getMessagingInterface
|
|
from modules.shared.timeUtils import getUtcTimestamp
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class MessagingService:
|
|
"""
|
|
Messaging service providing subscription-based messaging functionality.
|
|
"""
|
|
|
|
def __init__(self, services):
|
|
"""Initialize messaging service with service center access.
|
|
|
|
Args:
|
|
services: Service center instance providing access to interfaces
|
|
"""
|
|
self.services = services
|
|
self._messagingInterface = None
|
|
|
|
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
|
|
try:
|
|
deliveryRecord = self.services.interfaceDbComponent.createDelivery(delivery)
|
|
except Exception as e:
|
|
logger.error(f"Failed to create delivery record: {str(e)}")
|
|
return MessagingSendResult(
|
|
success=False,
|
|
errorMessage=f"Failed to create delivery record: {str(e)}"
|
|
)
|
|
|
|
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
|
|
try:
|
|
self.services.interfaceDbComponent.updateDelivery(
|
|
deliveryRecord["id"],
|
|
{
|
|
"status": DeliveryStatus.FAILED,
|
|
"errorMessage": str(e)
|
|
}
|
|
)
|
|
except Exception as updateError:
|
|
logger.error(f"Failed to update delivery record: {str(updateError)}")
|
|
|
|
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)
|
|
)
|
|
|
|
def _getSubscribers(
|
|
self,
|
|
subscriptionId: str,
|
|
channel: Optional[MessagingChannel] = None
|
|
) -> List[MessagingSubscriptionRegistration]:
|
|
"""Holt alle aktiven Subscriber einer Subscription"""
|
|
filters = {"enabled": True}
|
|
if channel:
|
|
filters["channel"] = channel.value
|
|
|
|
registrations = self.services.interfaceDbComponent.getAllRegistrations(
|
|
subscriptionId=subscriptionId
|
|
)
|
|
|
|
# Filter nach enabled und channel
|
|
filteredRegistrations = []
|
|
for reg in registrations:
|
|
if reg.enabled and (not channel or reg.channel == channel):
|
|
filteredRegistrations.append(reg)
|
|
|
|
return filteredRegistrations
|
|
|
|
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:
|
|
self._messagingInterface = getMessagingInterface()
|
|
return self._messagingInterface
|
|
|