freeleaps-service-hub/app/notification/backend/business/notification_manager.py

273 lines
9.7 KiB
Python

from typing import Dict
from app.notification.backend.services.sms_service import SmsService
from app.notification.backend.services.in_app_notif_service import InAppNotifService
from app.notification.backend.services.email_service import EmailService
from app.notification.backend.services.notification_publisher_service import (
NotificationPublisherService,
)
from app.notification.backend.models.constants import (
NotificationChannel,
NotificationMessage,
SystemNotifications,
)
import threading
from datetime import datetime, timezone
from typing import Optional, Type
from types import TracebackType
from infra.models.constants import UserRegion
from app.notification.common.config.app_settings import app_settings
from datetime import datetime, timezone
class NotificationManager:
notification_queues: dict[NotificationChannel, NotificationPublisherService] = None
instance_counter = 0
instance_counter_lock = threading.Lock()
def __init__(
self,
sender_id: str, # Require sender_id in the constructor
) -> None:
self.sms_service = SmsService()
self.in_app_notif_service = InAppNotifService()
self.email_service = EmailService()
self.sender_id = sender_id
self.notification_queues = NotificationManager.__create_notification_queues__()
@staticmethod
def __increment_instance_counter__() -> int:
with NotificationManager.instance_counter_lock:
NotificationManager.instance_counter += 1
return NotificationManager.instance_counter
@staticmethod
def __decrement_instance_counter__() -> int:
with NotificationManager.instance_counter_lock:
NotificationManager.instance_counter -= 1
return NotificationManager.instance_counter
@staticmethod
def __create_notification_queues__() -> None:
if not NotificationManager.notification_queues:
NotificationManager.notification_queues = {}
for channel in NotificationChannel:
NotificationManager.notification_queues[channel] = (
NotificationPublisherService(channel=channel)
)
return NotificationManager.notification_queues
async def __aenter__(self):
if NotificationManager.__increment_instance_counter__() == 1:
for channel in NotificationManager.notification_queues:
await NotificationManager.notification_queues[channel].bind()
async def __aexit__(
self,
exctype: Optional[Type[BaseException]],
excinst: Optional[BaseException],
exctb: Optional[TracebackType],
):
if NotificationManager.__decrement_instance_counter__() == 0:
for channel in NotificationManager.notification_queues:
await NotificationManager.notification_queues[channel].close()
async def __publish_notification__(
self,
channel: NotificationChannel,
message: NotificationMessage,
) -> None:
message.properties["publish_time"] = datetime.now(timezone.utc).isoformat()
await self.notification_queues[channel].publish(
message=message.model_dump_json()
)
async def __generate_message_from_subject_and_event__(
self,
subject: str,
event: str,
properties: dict,
region: Optional[UserRegion] = None,
) -> str:
# leverage the information in properties to enrich the message.
message_subject = None
message = None
if subject.lower() == "payment":
pass
# Default region to be international if not set
if region is None:
region = UserRegion.OTHER
message_subject = SystemNotifications[region][subject.lower()][event.lower()][
"message_subject"
]
message = SystemNotifications[region][subject.lower()][event.lower()]["message"]
if event.lower() == "authentication":
message = message.format(properties["auth_code"])
if not message:
raise RuntimeError("unsupported event:{}".format(event))
return message, message_subject
async def send_in_app_notification(
self, receiver_id: str, subject: str, event: str, properties: dict = None
) -> None:
await self.__publish_notification__(
channel=NotificationChannel.IN_APP,
message=NotificationMessage(
sender_id=app_settings.SYSTEM_USER_ID,
receiver_id=receiver_id,
subject=subject,
event=event,
properties=properties,
),
)
async def send_chat_message_notification(
self, receiver_id: str, subject: str, event: str, properties: dict = None
) -> None:
(
content_text,
content_subject,
) = await self.__generate_message_from_subject_and_event__(
subject=subject, event=event, properties=properties
)
properties_dict = {
"content_text": content_text,
"content_subject": content_subject,
"receiver_type": "user", # or 'conversation'
}
await self.__publish_notification__(
channel=NotificationChannel.CHAT_MESSAGE,
message=NotificationMessage(
sender_id=app_settings.SYSTEM_USER_ID,
receiver_id=receiver_id,
subject=subject,
event=event,
properties=(
{**properties_dict, **properties} if properties else properties_dict
),
),
)
async def send_email_notification(
self,
receiver_id: str,
subject: str,
event: str,
properties: dict = None,
region: Optional[UserRegion] = None,
) -> None:
(
content_text,
content_subject,
) = await self.__generate_message_from_subject_and_event__(
subject=subject, event=event, properties=properties, region=region
)
properties_dict = {
"content_text": content_text,
"content_subject": content_subject,
"sender_email": "qifei.lu1994@gmail.com",
"receiver_type": "email",
}
await self.__publish_notification__(
channel=NotificationChannel.EMAIL,
message=NotificationMessage(
sender_id="aaabbbcccddd", # app_settings.SYSTEM_USER_ID,
receiver_id=receiver_id,
subject=subject,
event=event,
properties=(
properties_dict.update(properties)
if not properties
else properties_dict
),
),
)
print(
"this is message",
NotificationMessage(
sender_id="aaabbbcccddd", # app_settings.SYSTEM_USER_ID,
receiver_id=receiver_id,
subject=subject,
event=event,
properties=(
properties_dict.update(properties)
if not properties
else properties_dict
),
),
)
async def send_sms_notification(
self, receiver_id: str, subject: str, event: str, properties: dict = None
) -> None:
(
content_text,
content_subject,
) = await self.__generate_message_from_subject_and_event__(
subject=subject, event=event, properties=properties
)
properties_dict = {
"content_text": content_text,
"content_subject": content_subject,
"sender_mobile": app_settings.SMS_FROM,
"receiver_type": "mobile",
}
await self.__publish_notification__(
channel=NotificationChannel.SMS,
message=NotificationMessage(
sender_id=app_settings.SYSTEM_USER_ID,
receiver_id=receiver_id,
subject=subject,
event=event,
properties=(
properties_dict.update(properties)
if not properties
else properties_dict
),
),
)
async def enqueue_notification(
self,
channels: list[NotificationChannel],
receiver_id: str,
subject: str,
event: str,
properties: dict = None,
) -> None:
for channel in channels:
if channel == NotificationChannel.CHAT_MESSAGE:
await self.send_chat_message_notification(
receiver_id=receiver_id,
subject=subject,
event=event,
properties=properties,
)
elif channel == NotificationChannel.IN_APP:
await self.send_in_app_notification(
receiver_id=receiver_id,
subject=subject,
event=event,
properties=properties,
)
elif channel == NotificationChannel.EMAIL:
print("should get here")
await self.send_email_notification(
receiver_id=receiver_id,
subject=subject,
event=event,
properties=properties,
)
elif channel == NotificationChannel.SMS:
await self.send_sms_notification(
receiver_id=receiver_id,
subject=subject,
event=event,
properties=properties,
)
else:
raise RuntimeError(f"Unsupported notification channel: {channel}")