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}")