Spaces:
Sleeping
Sleeping
| """ | |
| 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 | |
| } | |
| ) | |