import json from typing import List from datetime import datetime from app.models import User, Chat, Message, Notification, NotificationType, Mention from app.db.session import SessionLocal from app.websocket.connection_manager import connection_manager class NotificationService: def __init__(self): self.notification_queue = [] async def send_mention_notification( self, mentioned_user_id: int, sender_username: str, message_content: str, chat_id: int, message_id: int ): """Send notification for user mention""" db = SessionLocal() try: mentioned_user = db.query(User).filter(User.id == mentioned_user_id).first() chat = db.query(Chat).filter(Chat.id == chat_id).first() if not mentioned_user or not chat: return # Create notification record notification = Notification( user_id=mentioned_user_id, chat_id=chat_id, message_id=message_id, notification_type=NotificationType.MENTION, title=f"Mentioned by {sender_username}", body=f"You were mentioned: {message_content}", data=json.dumps({ "chat_id": chat_id, "message_id": message_id, "sender_username": sender_username, "chat_name": chat.name or f"Chat with {sender_username}" }) ) db.add(notification) db.commit() db.refresh(notification) # Send real-time notification via WebSocket await self._send_realtime_notification(mentioned_user_id, { "type": "mention_notification", "notification_id": notification.id, "title": notification.title, "body": notification.body, "chat_id": chat_id, "message_id": message_id, "sender_username": sender_username, "created_at": notification.created_at.isoformat() }) # Add to push notification queue if mentioned_user.device_token: await self._queue_push_notification(notification) finally: db.close() async def send_message_notification( self, recipient_user_id: int, sender_username: str, message_content: str, chat_id: int, message_id: int ): """Send notification for new message""" db = SessionLocal() try: recipient = db.query(User).filter(User.id == recipient_user_id).first() chat = db.query(Chat).filter(Chat.id == chat_id).first() if not recipient or not chat: return # Check if user is online and active in this chat is_online = recipient_user_id in connection_manager.active_connections if is_online: # Don't send push notification if user is online return # Create notification record notification = Notification( user_id=recipient_user_id, chat_id=chat_id, message_id=message_id, notification_type=NotificationType.MESSAGE, title=f"New message from {sender_username}", body=message_content[:100], data=json.dumps({ "chat_id": chat_id, "message_id": message_id, "sender_username": sender_username, "chat_name": chat.name or f"Chat with {sender_username}" }) ) db.add(notification) db.commit() db.refresh(notification) # Add to push notification queue if recipient.device_token: await self._queue_push_notification(notification) finally: db.close() async def send_group_invite_notification( self, invited_user_id: int, inviter_username: str, chat_id: int, group_name: str ): """Send notification for group chat invitation""" db = SessionLocal() try: invited_user = db.query(User).filter(User.id == invited_user_id).first() if not invited_user: return notification = Notification( user_id=invited_user_id, chat_id=chat_id, notification_type=NotificationType.GROUP_INVITE, title=f"Invited to {group_name}", body=f"{inviter_username} invited you to join {group_name}", data=json.dumps({ "chat_id": chat_id, "inviter_username": inviter_username, "group_name": group_name }) ) db.add(notification) db.commit() db.refresh(notification) # Send real-time notification await self._send_realtime_notification(invited_user_id, { "type": "group_invite_notification", "notification_id": notification.id, "title": notification.title, "body": notification.body, "chat_id": chat_id, "group_name": group_name, "inviter_username": inviter_username, "created_at": notification.created_at.isoformat() }) if invited_user.device_token: await self._queue_push_notification(notification) finally: db.close() async def mark_notification_read(self, notification_id: int, user_id: int): """Mark notification as read""" db = SessionLocal() try: notification = db.query(Notification).filter( Notification.id == notification_id, Notification.user_id == user_id ).first() if notification: notification.is_read = True notification.read_at = datetime.utcnow() db.commit() # Send real-time update await self._send_realtime_notification(user_id, { "type": "notification_read", "notification_id": notification_id }) finally: db.close() async def mark_mention_read(self, mention_id: int, user_id: int): """Mark mention as read""" db = SessionLocal() try: mention = db.query(Mention).filter( Mention.id == mention_id, Mention.mentioned_user_id == user_id ).first() if mention: mention.is_read = True mention.read_at = datetime.utcnow() db.commit() finally: db.close() def get_user_notifications( self, user_id: int, unread_only: bool = False, limit: int = 50, offset: int = 0 ) -> List[dict]: """Get user notifications""" db = SessionLocal() try: query = db.query(Notification).filter(Notification.user_id == user_id) if unread_only: query = query.filter(Notification.is_read.is_(False)) notifications = query.order_by( Notification.created_at.desc() ).offset(offset).limit(limit).all() result = [] for notif in notifications: data = json.loads(notif.data) if notif.data else {} result.append({ "id": notif.id, "type": notif.notification_type.value, "title": notif.title, "body": notif.body, "data": data, "is_read": notif.is_read, "created_at": notif.created_at.isoformat(), "read_at": notif.read_at.isoformat() if notif.read_at else None }) return result finally: db.close() def get_unread_mentions(self, user_id: int) -> List[dict]: """Get unread mentions for user""" db = SessionLocal() try: mentions = db.query(Mention).join(Message).join(Chat).filter( Mention.mentioned_user_id == user_id, Mention.is_read.is_(False) ).all() result = [] for mention in mentions: message = mention.message result.append({ "id": mention.id, "message_id": message.id, "chat_id": message.chat_id, "chat_name": message.chat.name, "sender_username": message.sender.username, "message_content": message.content[:100], "created_at": mention.created_at.isoformat() }) return result finally: db.close() async def _send_realtime_notification(self, user_id: int, notification_data: dict): """Send real-time notification via WebSocket""" await connection_manager.send_personal_message(notification_data, user_id) async def _queue_push_notification(self, notification: Notification): """Queue push notification for sending""" # In a real implementation, this would add to a task queue (like Celery) # For now, we'll just store in memory push_data = { "user_id": notification.user_id, "title": notification.title, "body": notification.body, "data": json.loads(notification.data) if notification.data else {} } self.notification_queue.append(push_data) # Mark as queued db = SessionLocal() try: notification.is_sent = True db.commit() finally: db.close() # Global notification service instance notification_service = NotificationService()