swiftops-backend / src /app /tasks /notification_tasks.py
kamau1's picture
complete services
456b2e2
"""
Notification Background Tasks - Async notification sending
"""
import logging
import httpx
from typing import Dict, Any
from uuid import UUID
from fastapi import BackgroundTasks
from app.core.database import SessionLocal
from app.services.notification_service import NotificationService
from app.models.notification import Notification, NotificationChannel
logger = logging.getLogger(__name__)
notification_service = NotificationService()
async def send_notification_async(notification_id: UUID):
"""
Background task to send notification via external channel (email, SMS, WhatsApp)
Args:
notification_id: Notification ID to send
"""
db = SessionLocal()
try:
# Get notification
notification = db.query(Notification).filter(Notification.id == notification_id).first()
if not notification:
logger.error(f"Notification {notification_id} not found")
return
if notification.channel == NotificationChannel.IN_APP:
# In-app notifications don't need external sending
notification.mark_as_delivered()
db.commit()
return
# Get user details
user = notification.user
if not user:
logger.error(f"User not found for notification {notification_id}")
notification.mark_as_failed("User not found")
db.commit()
return
# Send based on channel
result = {'success': False, 'error': 'Unknown channel'}
if notification.channel == NotificationChannel.EMAIL:
result = await notification_service.send_email(
to_email=user.email,
subject=notification.title,
template_name='notification', # Generic notification template
template_data={
'title': notification.title,
'message': notification.message,
'user_name': user.full_name or user.email,
'action_url': notification.additional_metadata.get('action_url'),
'notification_type': notification.notification_type
}
)
elif notification.channel == NotificationChannel.WHATSAPP:
if user.phone:
# Send WhatsApp message
async with httpx.AsyncClient() as client:
response = await client.post(
f"{notification_service.wasender_api_url}/send-message",
headers={
'Authorization': f'Bearer {notification_service.wasender_api_key}',
'Content-Type': 'application/json'
},
json={
'to': user.phone,
'text': f"*{notification.title}*\n\n{notification.message}"
},
timeout=30.0
)
result = {'success': response.status_code == 200, 'error': response.text if response.status_code != 200 else None}
else:
result = {'success': False, 'error': 'User has no phone number'}
elif notification.channel == NotificationChannel.SMS:
# SMS not yet implemented
result = {'success': False, 'error': 'SMS delivery not yet implemented'}
elif notification.channel == NotificationChannel.PUSH:
# Push notifications not yet implemented
result = {'success': False, 'error': 'Push notifications not yet implemented'}
# Update notification status
if result['success']:
notification.mark_as_sent()
logger.info(f"Notification {notification_id} sent via {notification.channel.value}")
else:
notification.mark_as_failed(result.get('error', 'Unknown error'))
logger.error(f"Failed to send notification {notification_id}: {result.get('error')}")
db.commit()
except Exception as e:
logger.error(f"Error sending notification {notification_id}: {str(e)}")
if notification:
notification.mark_as_failed(str(e))
db.commit()
finally:
db.close()
def queue_notification_send(notification_id: UUID, background_tasks: BackgroundTasks):
"""
Queue a notification for background sending
Args:
notification_id: Notification ID
background_tasks: FastAPI BackgroundTasks instance
"""
background_tasks.add_task(send_notification_async, notification_id)
logger.info(f"Queued notification {notification_id} for sending")
# ============================================
# HELPER FUNCTIONS FOR COMMON NOTIFICATIONS
# ============================================
async def notify_ticket_assignment(
db,
ticket_id: UUID,
assignee_id: UUID,
assigner_name: str,
ticket_number: str
):
"""
Create notification for ticket assignment
Args:
db: Database session
ticket_id: Ticket ID
assignee_id: Assignee user ID
assigner_name: Name of person who assigned
ticket_number: Ticket number for display
"""
await notification_service.create_notification(
db=db,
user_id=assignee_id,
title=f"New Ticket Assigned: {ticket_number}",
message=f"{assigner_name} assigned you to ticket {ticket_number}",
source_type='ticket',
source_id=ticket_id,
notification_type='assignment',
channel='in_app',
additional_metadata={
'action_url': f'/tickets/{ticket_id}',
'ticket_number': ticket_number
}
)
async def notify_ticket_status_change(
db,
ticket_id: UUID,
user_ids: list,
ticket_number: str,
old_status: str,
new_status: str,
changed_by: str
):
"""
Notify users about ticket status change
Args:
db: Database session
ticket_id: Ticket ID
user_ids: List of user IDs to notify
ticket_number: Ticket number
old_status: Previous status
new_status: New status
changed_by: Who changed the status
"""
await notification_service.create_bulk_notifications(
db=db,
user_ids=user_ids,
title=f"Ticket {ticket_number} Status Changed",
message=f"{changed_by} changed ticket status from {old_status} to {new_status}",
source_type='ticket',
source_id=ticket_id,
notification_type='status_change',
channel='in_app',
additional_metadata={
'action_url': f'/tickets/{ticket_id}',
'ticket_number': ticket_number,
'old_status': old_status,
'new_status': new_status
}
)
async def notify_mention(
db,
mentioned_user_id: UUID,
mentioner_name: str,
context_type: str,
context_id: UUID,
context_title: str,
comment_text: str
):
"""
Notify user they were mentioned in a comment
Args:
db: Database session
mentioned_user_id: User who was mentioned
mentioner_name: User who mentioned
context_type: 'ticket', 'project', etc.
context_id: ID of the entity
context_title: Display title
comment_text: Comment excerpt
"""
await notification_service.create_notification(
db=db,
user_id=mentioned_user_id,
title=f"{mentioner_name} mentioned you",
message=f"You were mentioned in {context_type} {context_title}: \"{comment_text[:100]}...\"",
source_type=context_type,
source_id=context_id,
notification_type='mention',
channel='in_app',
additional_metadata={
'action_url': f'/{context_type}s/{context_id}',
'context_title': context_title
}
)