diff --git a/main.py b/main.py index b64ca99..b30eb45 100644 --- a/main.py +++ b/main.py @@ -16,32 +16,30 @@ async def start_up(): if MODE == "dev": if exists(DEV_SERVER_PID_FILE_NAME): await redis.connect() - return else: with open(DEV_SERVER_PID_FILE_NAME, "w", encoding="utf-8") as f: f.write(str(os.getpid())) else: await redis.connect() notification_service_task = asyncio.create_task(reactions_worker()) - print(f"[main] {notification_service_task}") + print(f"[main.start_up] {notification_service_task}") + try: + import sentry_sdk - try: - import sentry_sdk - - sentry_sdk.init( - SENTRY_DSN, - enable_tracing=True, - integrations=[ - StrawberryIntegration( - # Set async_execution to True if you have - # at least one async resolver - async_execution=True - ), - ], - ) - except Exception as e: - print("[sentry] init error") - print(e) + sentry_sdk.init( + SENTRY_DSN, + enable_tracing=True, + integrations=[ + StrawberryIntegration( + # Set async_execution to True if you have + # at least one async resolver + async_execution=True + ), + ], + ) + except Exception as e: + print("[sentry] init error") + print(e) async def shutdown(): diff --git a/orm/notification.py b/orm/notification.py index 0746ca6..dedd5b4 100644 --- a/orm/notification.py +++ b/orm/notification.py @@ -1,8 +1,9 @@ from enum import Enum as Enumeration import time -from sqlalchemy import Boolean, Column, Enum, Integer -from sqlalchemy.dialects.postgresql import JSONB - +from sqlalchemy import Boolean, Column, Enum, Integer, ForeignKey, JSON as JSONType +from sqlalchemy.orm import relationship +# from sqlalchemy.dialects.postgresql import JSONB +from orm.author import Author from services.db import Base @@ -21,12 +22,19 @@ class NotificationAction(Enumeration): UNFOLLOW = 6 +class NotificationSeen(Base): + __tablename__ = "notification_seen" + + viewer = Column(ForeignKey("author.id")) + notification = Column(ForeignKey("notification.id")) + + class Notification(Base): __tablename__ = "notification" created_at = Column(Integer, default=lambda: int(time.time())) - seen = Column(Boolean, nullable=False, default=False, index=True) entity = Column(Enum(NotificationEntity), nullable=False) action = Column(Enum(NotificationAction), nullable=False) - payload = Column(JSONB, nullable=True) - # occurrences = Column(Integer, default=1) + payload = Column(JSONType, nullable=True) + + seen = relationship(lambda: Author, secondary="notification_seen") diff --git a/pyproject.toml b/pyproject.toml index 1e18846..ea11fdd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -17,11 +17,13 @@ redis = {extras = ["hiredis"], version = "^5.0.1"} uvicorn = "^0.24.0.post1" strawberry-graphql = {extras = ["asgi", "debug-server"], version = "^0.215.1" } sentry-sdk = "^1.37.1" +strawberry-sqlalchemy-mapper = "^0.3.1" [tool.poetry.dev-dependencies] pytest = "^7.4.2" black = { version = "^23.9.1", python = ">=3.12" } mypy = { version = "^1.7", python = ">=3.12" } +setuptools = "^69.0.2" [tool.black] line-length = 120 diff --git a/resolvers/schema.py b/resolvers/schema.py index c9b4a69..a234b52 100644 --- a/resolvers/schema.py +++ b/resolvers/schema.py @@ -1,27 +1,25 @@ -from typing import List +from typing import List, Any -import strawberry from sqlalchemy import and_ +from sqlalchemy.orm import aliased from orm.author import Author +from orm.notification import Notification as NotificationMessage, NotificationSeen from services.auth import login_required from services.db import local_session +from strawberry_sqlalchemy_mapper import StrawberrySQLAlchemyMapper +import strawberry + +strawberry_sqlalchemy_mapper = StrawberrySQLAlchemyMapper() -@strawberry.type -class NotificationSeen: - notification: int # notification id - viewer: int # author id - - -@strawberry.type +@strawberry_sqlalchemy_mapper.type(NotificationMessage) class Notification: id: int action: str # create update delete join follow etc. entity: str # REACTION SHOUT created_at: int - seen: list[NotificationSeen] - data: str # JSON data - occurrences: int + payload: str # JSON data + seen: List[int] @strawberry.type @@ -35,14 +33,6 @@ class NotificationsResult: unread: int total: int -def notification_seen_by_viewer(viewer_id, notification_id, session): - seen = ( - session.query(NotificationSeen) - .filter(NotificationSeen.viewer == viewer_id, NotificationSeen.notification == notification_id) - .first() - ) - return seen is not None - @strawberry.type class Query: @@ -50,25 +40,32 @@ class Query: @login_required async def load_notifications(self, info, limit: int = 50, offset: int = 0) -> NotificationsResult: user_id = info.context["user_id"] - + nr = NotificationsResult() with local_session() as session: author = session.query(Author).filter(Author.user == user_id).first() + NotificationSeenAlias = aliased(NotificationSeen) - query = ( - session.query(Notification) - .outerjoin( - NotificationSeen, - and_(NotificationSeen.viewer == author.id, NotificationSeen.notification == Notification.id), - ) - .limit(limit) - .offset(offset) - ) - - nslist = query.all() - total = query.group_by(Notification.id).count() - unread = sum(1 for n in nslist if not n.seen_by_viewer) - - return NotificationsResult(notifications=nslist, unread=unread, total=total) + if author: + nnn = session.query( + NotificationMessage, + NotificationSeenAlias.viewer.label("seen") + ).outerjoin( + NotificationSeen, + and_(NotificationSeen.viewer == author.id, NotificationSeen.notification == NotificationMessage.id), + ).limit(limit).offset(offset).all() + nr.notifications = [] + for n in nnn: + ntf = Notification() + ntf.id = n.id + ntf.payload = n.payload + ntf.entity = n.entity + ntf.action = n.action + ntf.created_at = n.created_at + ntf.seen = n.seen + nr.notifications.append(ntf) + nr.unread = sum(1 for n in nr.notifications if author.id in n.seen) + nr.total = session.query(NotificationMessage).count() + return nr @strawberry.type @@ -77,16 +74,18 @@ class Mutation: @login_required async def mark_notification_as_read(self, info, notification_id: int) -> NotificationSeenResult: user_id = info.context["user_id"] - try: - with local_session() as session: + with local_session() as session: + try: author = session.query(Author).filter(Author.user == user_id).first() ns = NotificationSeen({"notification": notification_id, "viewer": author.id}) session.add(ns) session.commit() - except Exception as e: - session.rollback() - print(f"[mark_notification_as_read] error: {str(e)}") - return NotificationSeenResult(error="cant mark as read") + except Exception as e: + session.rollback() + print(f"[mark_notification_as_read] error: {str(e)}") + nsr = NotificationSeenResult() + nsr.error = "cant mark as read" + return nsr return NotificationSeenResult() @strawberry.mutation @@ -97,11 +96,14 @@ class Mutation: with local_session() as session: try: author = session.query(Author).filter(Author.user == user_id).first() - _nslist = session.quuery(NotificationSeen).filter(NotificationSeen.viewer == author.id).all() + if author: + _nslist = session.query(NotificationSeen).filter(NotificationSeen.viewer == author.id).all() except Exception as e: session.rollback() print(f"[mark_all_notifications_as_read] error: {str(e)}") - return NotificationSeenResult(error="cant mark as read") + nsr = NotificationSeenResult() + nsr.error = "cant mark as read" + return nsr return NotificationSeenResult()