notifier/resolvers/load.py

243 lines
8.0 KiB
Python
Raw Normal View History

2024-02-16 23:56:15 +00:00
import json
import logging
import time
from typing import Dict, List, Tuple, Union
import strawberry
from sqlalchemy import and_, select
from sqlalchemy.orm import aliased
2024-01-23 13:40:53 +00:00
from sqlalchemy.sql import not_
2024-02-16 23:56:15 +00:00
2024-01-26 00:40:49 +00:00
from orm.notification import (
Notification,
NotificationAction,
NotificationEntity,
NotificationSeen,
)
2023-12-22 09:09:03 +00:00
from resolvers.model import (
2024-02-16 23:56:15 +00:00
NotificationAuthor,
2023-12-22 09:09:03 +00:00
NotificationGroup,
2024-02-16 23:56:15 +00:00
NotificationReaction,
2023-12-22 09:09:03 +00:00
NotificationShout,
NotificationsResult,
)
2024-02-16 23:56:15 +00:00
from services.db import local_session
2024-01-22 19:05:19 +00:00
2024-02-18 07:47:07 +00:00
logger = logging.getLogger("[resolvers.schema]")
2024-01-22 19:05:19 +00:00
logger.setLevel(logging.DEBUG)
2023-12-22 09:09:03 +00:00
2024-02-18 07:47:07 +00:00
def query_notifications(
author_id: int, after: int = 0
) -> Tuple[int, int, List[Tuple[Notification, bool]]]:
2024-02-16 23:56:15 +00:00
notification_seen_alias = aliased(NotificationSeen)
2024-02-18 07:47:07 +00:00
query = select(
Notification, notification_seen_alias.viewer.label("seen")
).outerjoin(
2023-12-22 09:09:03 +00:00
NotificationSeen,
2024-01-26 00:40:49 +00:00
and_(
NotificationSeen.viewer == author_id,
NotificationSeen.notification == Notification.id,
),
2023-12-22 09:09:03 +00:00
)
if after:
2023-12-22 14:52:55 +00:00
query = query.filter(Notification.created_at > after)
2024-01-23 18:39:20 +00:00
query = query.group_by(NotificationSeen.notification, Notification.created_at)
2023-12-22 09:09:03 +00:00
with local_session() as session:
2024-02-04 04:58:44 +00:00
total = (
session.query(Notification)
2024-01-26 00:40:49 +00:00
.filter(
and_(
Notification.action == NotificationAction.CREATE.value,
Notification.created_at > after,
)
2024-01-23 09:15:10 +00:00
)
2024-02-04 04:58:44 +00:00
.count()
)
2024-02-16 23:56:15 +00:00
2024-02-04 04:58:44 +00:00
unread = (
session.query(Notification)
.filter(
and_(
Notification.action == NotificationAction.CREATE.value,
Notification.created_at > after,
not_(Notification.seen),
)
2024-01-23 09:15:10 +00:00
)
2024-02-04 04:58:44 +00:00
.count()
)
2024-02-16 23:56:15 +00:00
2023-12-22 13:42:37 +00:00
notifications_result = session.execute(query)
2024-02-16 23:56:15 +00:00
notifications = []
2023-12-22 13:42:37 +00:00
for n, seen in notifications_result:
2024-02-16 23:56:15 +00:00
notifications.append((n, seen))
return total, unread, notifications
def process_shout_notification(
notification: Notification, seen: bool
) -> Union[Tuple[str, NotificationGroup], None] | None:
2024-02-18 07:47:07 +00:00
if not isinstance(notification.payload, str) or not isinstance(
notification.entity, str
):
2024-02-16 23:56:15 +00:00
return
payload = json.loads(notification.payload)
shout: NotificationShout = payload
thread_id = str(shout.id)
group = NotificationGroup(
id=thread_id,
entity=notification.entity,
shout=shout,
authors=shout.authors,
updated_at=shout.created_at,
reactions=[],
2024-02-18 07:47:07 +00:00
action="create",
2024-02-16 23:56:15 +00:00
seen=seen,
)
return thread_id, group
def process_reaction_notification(
notification: Notification, seen: bool
) -> Union[Tuple[str, NotificationGroup], None] | None:
if (
not isinstance(notification, Notification)
or not isinstance(notification.payload, str)
or not isinstance(notification.entity, str)
):
return
payload = json.loads(notification.payload)
reaction: NotificationReaction = payload
shout: NotificationShout = reaction.shout
thread_id = str(reaction.shout)
2024-02-18 07:47:07 +00:00
if reaction.kind == "COMMENT" and reaction.reply_to:
thread_id += f"::{reaction.reply_to}"
2024-02-16 23:56:15 +00:00
group = NotificationGroup(
id=thread_id,
action=str(notification.action),
entity=notification.entity,
updated_at=reaction.created_at,
reactions=[reaction.id],
shout=shout,
authors=[reaction.created_by],
seen=seen,
)
return thread_id, group
def process_follower_notification(
notification: Notification, seen: bool
) -> Union[Tuple[str, NotificationGroup], None] | None:
if not isinstance(notification.payload, str):
return
payload = json.loads(notification.payload)
follower: NotificationAuthor = payload
2024-02-18 07:47:07 +00:00
thread_id = "followers"
2024-02-16 23:56:15 +00:00
group = NotificationGroup(
id=thread_id,
authors=[follower],
updated_at=int(time.time()),
shout=None,
reactions=[],
2024-02-18 07:47:07 +00:00
entity="follower",
action="follow",
2024-02-16 23:56:15 +00:00
seen=seen,
)
return thread_id, group
async def get_notifications_grouped(
author_id: int, after: int = 0, limit: int = 10
) -> Tuple[Dict[str, NotificationGroup], int, int]:
2024-02-18 07:47:07 +00:00
"""
Retrieves notifications for a given author.
Args:
author_id (int): The ID of the author for whom notifications are retrieved.
after (int, optional): If provided, selects only notifications created after this timestamp will be considered.
limit (int, optional): The maximum number of groupa to retrieve.
offset (int, optional): Offset for pagination
Returns:
Dict[str, NotificationGroup], int, int: A dictionary where keys are thread IDs and values are NotificationGroup objects, unread and total amounts.
This function queries the database to retrieve notifications for the specified author, considering optional filters.
The result is a dictionary where each key is a thread ID, and the corresponding value is a NotificationGroup
containing information about the notifications within that thread.
NotificationGroup structure:
{
entity: str, # Type of entity (e.g., 'reaction', 'shout', 'follower').
updated_at: int, # Timestamp of the latest update in the thread.
shout: Optional[NotificationShout]
reactions: List[int], # List of reaction ids within the thread.
authors: List[NotificationAuthor], # List of authors involved in the thread.
}
"""
2024-02-16 23:56:15 +00:00
total, unread, notifications = query_notifications(author_id, after)
groups_by_thread: Dict[str, NotificationGroup] = {}
groups_amount = 0
for notification, seen in notifications:
if groups_amount >= limit:
break
2024-02-18 07:47:07 +00:00
if str(notification.entity) == "shout" and str(notification.action) == "create":
2024-02-16 23:56:15 +00:00
result = process_shout_notification(notification, seen)
if result:
thread_id, group = result
2023-12-22 14:52:55 +00:00
groups_by_thread[thread_id] = group
groups_amount += 1
2024-02-16 23:56:15 +00:00
elif (
str(notification.entity) == NotificationEntity.REACTION.value
and str(notification.action) == NotificationAction.CREATE.value
):
result = process_reaction_notification(notification, seen)
if result:
thread_id, group = result
existing_group = groups_by_thread.get(thread_id)
if existing_group:
existing_group.seen = False
existing_group.shout = group.shout
existing_group.authors.append(group.authors[0])
if not existing_group.reactions:
existing_group.reactions = []
existing_group.reactions.extend(group.reactions or [])
groups_by_thread[thread_id] = existing_group
else:
groups_by_thread[thread_id] = group
groups_amount += 1
2024-02-18 07:47:07 +00:00
elif str(notification.entity) == "follower":
2024-02-16 23:56:15 +00:00
result = process_follower_notification(notification, seen)
if result:
thread_id, group = result
2023-12-22 14:52:55 +00:00
groups_by_thread[thread_id] = group
groups_amount += 1
2023-12-22 09:09:03 +00:00
2024-02-16 23:56:15 +00:00
return groups_by_thread, unread, total
2023-12-22 09:09:03 +00:00
@strawberry.type
class Query:
@strawberry.field
2024-02-18 07:47:07 +00:00
async def load_notifications(
self, info, after: int, limit: int = 50, offset: int = 0
) -> NotificationsResult:
author_id = info.context.get("author_id")
2023-12-22 09:09:03 +00:00
if author_id:
2024-02-18 07:47:07 +00:00
groups, unread, total = await get_notifications_grouped(
author_id, after, limit
)
notifications = sorted(
groups.values(), key=lambda group: group.updated_at, reverse=True
)
return NotificationsResult(
notifications=notifications, total=total, unread=unread, error=None
)
2024-02-16 23:56:15 +00:00
return NotificationsResult(notifications=[], total=0, unread=0, error=None)