Private
Public Access
Add unread message indicators for rooms (#38)
Shows a dot on rooms with unread messages in the sidebar, updated live over WebSocket. Reuses the same offline-member audience computation already used for push notifications: a member gets the real-time signal whenever they aren't currently connected to that room's channel, which correctly covers both "room not open" and "room open but tab backgrounded" (the client leaves a room's channel while hidden). Persisted server-side via a new room_memberships.last_read_at column so state survives reload and stays consistent across devices, advanced by an explicit mark-read call the frontend makes on room-open and on each live message received while the room is genuinely visible -- gated on a live visibility check, not a cached ref, so a backgrounded-but-open room keeps accumulating unread instead of auto-marking-read the instant a message arrives somewhere it can't be seen. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -62,6 +62,7 @@ from app.services.room_service import (
|
||||
list_member_rooms,
|
||||
list_open_rooms,
|
||||
list_room_members,
|
||||
mark_room_read,
|
||||
remove_member,
|
||||
transfer_ownership,
|
||||
update_room,
|
||||
@@ -138,8 +139,9 @@ async def list_my_rooms_endpoint(
|
||||
owner_id=room.owner_id,
|
||||
created_at=room.created_at,
|
||||
role=role,
|
||||
has_unread=has_unread,
|
||||
)
|
||||
for room, role in rooms
|
||||
for room, role, has_unread in rooms
|
||||
]
|
||||
|
||||
|
||||
@@ -206,6 +208,16 @@ async def leave_room_endpoint(
|
||||
raise HTTPException(status_code=404, detail="Not a member of this room")
|
||||
|
||||
|
||||
@router.post("/{room_id}/read", status_code=204)
|
||||
async def mark_room_read_endpoint(
|
||||
room_id: uuid.UUID,
|
||||
current_user: User = Depends(get_current_user),
|
||||
db: AsyncSession = Depends(get_db),
|
||||
):
|
||||
await require_room_member(room_id, current_user, db)
|
||||
await mark_room_read(db, room_id, current_user.id)
|
||||
|
||||
|
||||
def _member_status(user: User, online_ids: set[uuid.UUID]) -> str:
|
||||
# appear_offline always wins, regardless of actual connection -- that's
|
||||
# the whole point of the override (lurking in a room undetected).
|
||||
|
||||
Reference in New Issue
Block a user