diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/api/notifications.py')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/api/notifications.py | 68 |
1 files changed, 65 insertions, 3 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/api/notifications.py b/packages/meshbay-hub/src/meshbay_hub/api/notifications.py index e7d36d2..ca56620 100644 --- a/packages/meshbay-hub/src/meshbay_hub/api/notifications.py +++ b/packages/meshbay-hub/src/meshbay_hub/api/notifications.py @@ -1,12 +1,14 @@ """Notification endpoints — /v1/notifications/*""" from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy import select, func, update +from datetime import datetime, timezone + +from sqlalchemy import delete, func, select, update from sqlalchemy.ext.asyncio import AsyncSession from meshbay_hub.api.deps import get_current_user from meshbay_hub.db.engine import get_db -from meshbay_hub.db.models import Notification, User +from meshbay_hub.db.models import GroupMember, Notification, User router = APIRouter(prefix="/v1/notifications", tags=["notifications"]) @@ -40,6 +42,7 @@ async def list_notifications( "title": n.title, "detail": n.detail, "link": n.link, + "group_id": n.group_id, "read": n.read, "created_at": n.created_at.isoformat(), } @@ -63,6 +66,24 @@ async def mark_read( return {"status": "ok"} +@router.delete("") +async def purge_notifications( + current_user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + """ + Throw them all away. + + These are signals, not a record: the group is still there, the message is + still in the chat, the invitation is still an invitation. Nothing is lost by + clearing the list, so it clears rather than marking a hundred rows read. + """ + result = await db.execute( + delete(Notification).where(Notification.user_id == current_user.id)) + await db.commit() + return {"status": "ok", "removed": result.rowcount} + + @router.post("/read-all") async def mark_all_read( current_user: User = Depends(get_current_user), @@ -84,9 +105,50 @@ async def create_notification( title: str, detail: str | None = None, link: str | None = None, -) -> Notification: + group_id: str | None = None, + aggregate: bool = False, +) -> Notification | None: + """ + Create a notification, or refresh the one already standing for this group. + + `aggregate` is for anything that repeats — chat, above all. One row per + (person, kind, group) whose date moves and whose read flag clears, so a busy + conversation is a single line saying when it last spoke rather than forty + saying that it spoke. + + Returns None when the person muted this group: the point of muting is that + nothing is created, not that something is created and hidden. + """ + if group_id is not None: + muted = await db.execute( + select(GroupMember.muted).where( + GroupMember.group_id == group_id, + GroupMember.user_id == user_id, + ) + ) + if muted.scalar() is True: + return None + + if aggregate and group_id is not None: + existing = (await db.execute( + select(Notification).where( + Notification.user_id == user_id, + Notification.kind == kind, + Notification.group_id == group_id, + ).order_by(Notification.created_at.desc()).limit(1) + )).scalar_one_or_none() + if existing is not None: + existing.title = title + existing.detail = detail + existing.link = link + existing.read = False + existing.created_at = datetime.now(timezone.utc) + await db.flush() + return existing + notif = Notification( user_id=user_id, kind=kind, title=title, detail=detail, link=link, + group_id=group_id, ) db.add(notif) await db.flush() |