95 lines
3.0 KiB
Python
95 lines
3.0 KiB
Python
from dataclasses import dataclass
|
|
|
|
from django.db import transaction
|
|
from django.db.models import Q
|
|
from django.utils import timezone
|
|
|
|
from communications.notifications.config import NotificationVerbs
|
|
from notifications.cache import invalidate_unread_summary
|
|
from notifications.models import Notification
|
|
from notifications.push_lifecycle import dismiss_chat_push
|
|
from notifications.realtime import send_notifications_read_all_event
|
|
|
|
|
|
@dataclass(frozen=True)
|
|
class ChatNotificationReadResult:
|
|
notification_ids: list[int]
|
|
count: int
|
|
|
|
|
|
class ChatNotificationReadService:
|
|
"""Use case for reading notifications related to a specific chat."""
|
|
|
|
chat_related_verbs = (
|
|
NotificationVerbs.NEW_MESSAGE,
|
|
NotificationVerbs.ANNOUNCEMENT,
|
|
NotificationVerbs.MENTION,
|
|
NotificationVerbs.REPLY,
|
|
NotificationVerbs.ADDED_TO_CHAT,
|
|
NotificationVerbs.COMMENTED,
|
|
)
|
|
|
|
def mark_chat_notifications_as_read(
|
|
self,
|
|
*,
|
|
user,
|
|
chat_id: int,
|
|
up_to_message_id: int | None = None,
|
|
) -> ChatNotificationReadResult:
|
|
queryset = self._get_unread_queryset(user_id=user.id, chat_id=chat_id)
|
|
candidates = list(queryset.only("id", "data"))
|
|
notification_ids = [
|
|
notification.id
|
|
for notification in candidates
|
|
if self._is_at_or_before_message(
|
|
notification.data,
|
|
up_to_message_id=up_to_message_id,
|
|
)
|
|
]
|
|
|
|
if not notification_ids:
|
|
return ChatNotificationReadResult(notification_ids=[], count=0)
|
|
|
|
read_at = timezone.now()
|
|
count = queryset.filter(id__in=notification_ids).update(
|
|
unread=False,
|
|
timestamp_read=read_at,
|
|
)
|
|
invalidate_unread_summary(user.id)
|
|
|
|
def notify_after_commit():
|
|
invalidate_unread_summary(user.id)
|
|
send_notifications_read_all_event(user.id, notification_ids)
|
|
dismiss_chat_push(
|
|
user_id=user.id,
|
|
chat_id=chat_id,
|
|
notification_ids=notification_ids,
|
|
)
|
|
|
|
transaction.on_commit(notify_after_commit)
|
|
|
|
return ChatNotificationReadResult(
|
|
notification_ids=notification_ids,
|
|
count=count,
|
|
)
|
|
|
|
@staticmethod
|
|
def _is_at_or_before_message(data, *, up_to_message_id: int | None) -> bool:
|
|
if up_to_message_id is None:
|
|
return True
|
|
if not isinstance(data, dict) or data.get("message_id") is None:
|
|
return True
|
|
try:
|
|
return int(data["message_id"]) <= up_to_message_id
|
|
except (TypeError, ValueError):
|
|
return False
|
|
|
|
def _get_unread_queryset(self, *, user_id: int, chat_id: int):
|
|
return Notification.objects.filter(
|
|
Q(data__chat_id=chat_id) | Q(data__chat_id=str(chat_id)),
|
|
recipient_id=user_id,
|
|
unread=True,
|
|
deleted=False,
|
|
verb__in=self.chat_related_verbs,
|
|
)
|