from typing import Optional import structlog from notifications.signals import notify from sendgrid import Mail, SendGridAPIClient from storages.utils import setting from vbv_lernwelt.core.models import User from vbv_lernwelt.notify.models import Notification, NotificationType logger = structlog.get_logger(__name__) class EmailService: _sendgrid_client = SendGridAPIClient(setting("SENDGRID_API_KEY")) @classmethod def send_email(cls, recipient: User, verb: str, target_url) -> bool: message = Mail( from_email="info@iterativ.ch", to_emails=recipient.email, subject=f"myVBV - {verb}", ## TODO: Add HTML content. html_content=f"{verb}: Link", ) try: cls._sendgrid_client.send(message) logger.info(f"Successfully sent email to {recipient}", label="email") return True except Exception as e: logger.error( f"Failed to send email to {recipient}: {e}", exc_info=True, label="email", ) return False class NotificationService: @classmethod def send_user_interaction_notification( cls, recipient: User, verb: str, sender: User, course: str, target_url: str ) -> None: cls._send_notification( recipient=recipient, verb=verb, sender=sender, course=course, target_url=target_url, notification_type=NotificationType.USER_INTERACTION, ) @classmethod def send_progress_notification( cls, recipient: User, verb: str, course: str, target_url: str ) -> None: cls._send_notification( recipient=recipient, verb=verb, course=course, target_url=target_url, notification_type=NotificationType.PROGRESS, ) @classmethod def send_information_notification( cls, recipient: User, verb: str, target_url: str ) -> None: cls._send_notification( recipient=recipient, verb=verb, target_url=target_url, notification_type=NotificationType.INFORMATION, ) @classmethod def _send_notification( cls, recipient: User, verb: str, notification_type: NotificationType, sender: Optional[User] = None, course: Optional[str] = None, target_url: Optional[str] = None, ) -> None: actor_avatar_url: Optional[str] = None if not sender: sender = User.objects.get(email="admin") else: actor_avatar_url = sender.avatar_url emailed = False if cls._should_send_email(notification_type, recipient): emailed = cls._send_email(recipient, verb, target_url) log = logger.bind( verb=verb, notification_type=notification_type, sender=sender.get_full_name(), recipient=recipient.get_full_name(), course=course, target_url=target_url, ) try: response = notify.send( sender=sender, recipient=recipient, verb=verb, ) sent_notification: Notification = response[0][1][0] sent_notification.target_url = target_url sent_notification.notification_type = notification_type sent_notification.course = course sent_notification.actor_avatar_url = actor_avatar_url sent_notification.emailed = emailed sent_notification.save() except Exception as e: log.bind(exception=e) log.error("Failed to send notification") else: log.info("Notification sent successfully") @staticmethod def _should_send_email( notification_type: NotificationType, recipient: User ) -> bool: return str(notification_type) in recipient.additional_json_data.get( "email_notification_types", [] ) @staticmethod def _send_email(recipient: User, verb: str, target_url: Optional[str]) -> bool: try: return EmailService.send_email( recipient=recipient, verb=verb, target_url=target_url, ) except Exception as e: logger.error(f"Failed to send email to {recipient}: {e}") return False