from django.core.mail import send_mail from django.conf import settings from django.template.loader import render_to_string from django.utils import timezone from django.contrib.auth import get_user_model import requests import json from datetime import timedelta from celery import shared_task User = get_user_model() class NotificationDispatcher: """Handles comment notifications and escalations""" def __init__(self): self.email_enabled = hasattr(settings, 'EMAIL_HOST') self.slack_enabled = hasattr(settings, 'SLACK_WEBHOOK_URL') self.sms_enabled = hasattr(settings, 'SMS_API_KEY') def notify_new_comment(self, comment, thread): """Handle notification for a new comment""" # Queue immediate notifications self.send_in_app_notification.delay( user_ids=self._get_thread_participants(thread), title="New Comment", message=f"New comment on {thread.content_object}", link=self._get_thread_url(thread) ) # Queue email notifications self.send_email_notification.delay( user_ids=self._get_thread_participants(thread), subject=f"New comment on {thread.content_object}", template="notifications/new_comment.html", context={ 'comment': comment, 'thread': thread, 'url': self._get_thread_url(thread) } ) # Schedule Slack escalation if needed if self.slack_enabled: self.schedule_slack_escalation.apply_async( args=[comment.id], countdown=24 * 3600 # 24 hours ) def notify_mention(self, comment, mentioned_users): """Handle notification for @mentions""" user_ids = [user.id for user in mentioned_users] # Queue immediate notifications self.send_in_app_notification.delay( user_ids=user_ids, title="Mentioned in Comment", message=f"{comment.author} mentioned you in a comment", link=self._get_comment_url(comment) ) # Queue email notifications self.send_email_notification.delay( user_ids=user_ids, subject="You were mentioned in a comment", template="notifications/mention.html", context={ 'comment': comment, 'url': self._get_comment_url(comment) } ) # Queue mobile push notifications self.send_push_notification.delay( user_ids=user_ids, title="New Mention", message=f"{comment.author} mentioned you: {comment.content[:100]}..." ) # Schedule SMS escalation if needed if self.sms_enabled: self.schedule_sms_escalation.apply_async( args=[comment.id, user_ids], countdown=12 * 3600 # 12 hours ) def notify_resolution(self, thread, resolver): """Handle notification for thread resolution""" self.send_in_app_notification.delay( user_ids=self._get_thread_participants(thread), title="Thread Resolved", message=f"Thread resolved by {resolver}", link=self._get_thread_url(thread) ) @shared_task def send_in_app_notification(user_ids, title, message, link): """Send in-app notification to users""" from .models import InAppNotification for user_id in user_ids: InAppNotification.objects.create( user_id=user_id, title=title, message=message, link=link ) @shared_task def send_email_notification(user_ids, subject, template, context): """Send email notification to users""" if not settings.EMAIL_HOST: return users = User.objects.filter(id__in=user_ids) for user in users: if not user.email: continue html_content = render_to_string(template, { 'user': user, **context }) send_mail( subject=subject, message='', html_message=html_content, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[user.email] ) @shared_task def send_push_notification(user_ids, title, message): """Send mobile push notification""" from .models import PushToken tokens = PushToken.objects.filter( user_id__in=user_ids, active=True ) if not tokens: return # Implementation depends on push notification service # Example using Firebase: try: requests.post( settings.FIREBASE_FCM_URL, headers={ 'Authorization': f'key={settings.FIREBASE_SERVER_KEY}', 'Content-Type': 'application/json' }, json={ 'registration_ids': [t.token for t in tokens], 'notification': { 'title': title, 'body': message } } ) except Exception as e: print(f"Push notification failed: {e}") @shared_task def schedule_slack_escalation(comment_id): """Send Slack DM escalation for unread comments""" from .models import Comment try: comment = Comment.objects.get(id=comment_id) if not comment.read_by.exists(): # Send Slack message requests.post( settings.SLACK_WEBHOOK_URL, json={ 'text': ( f"Unread comment needs attention:\n" f"{comment.content}\n" f"View: {self._get_comment_url(comment)}" ) } ) except Exception as e: print(f"Slack escalation failed: {e}") @shared_task def schedule_sms_escalation(comment_id, user_ids): """Send SMS escalation for unread mentions""" from .models import Comment try: comment = Comment.objects.get(id=comment_id) users = User.objects.filter(id__in=user_ids) for user in users: if not user.phone_number: continue if not comment.read_by.filter(id=user.id).exists(): # Send SMS using Twilio or similar service requests.post( settings.SMS_API_URL, headers={'Authorization': f'Bearer {settings.SMS_API_KEY}'}, json={ 'to': user.phone_number, 'message': ( f"You were mentioned in a comment that needs attention. " f"View: {self._get_comment_url(comment)}" ) } ) except Exception as e: print(f"SMS escalation failed: {e}") def _get_thread_participants(self, thread): """Get IDs of all participants in a thread""" return list(set( [thread.created_by_id] + list(thread.comments.values_list('author_id', flat=True)) )) def _get_thread_url(self, thread): """Generate URL for thread""" return f"/version-control/comments/thread/{thread.id}/" def _get_comment_url(self, comment): """Generate URL for specific comment""" return f"{self._get_thread_url(comment.thread)}#comment-{comment.id}"