from fastapi import APIRouter, Depends, HTTPException, status from sqlmodel import Session, select from datetime import datetime from typing import Optional from app.database import get_session from app.auth import get_current_user from app.models import User, Channel, LastSeen from app.websocket import manager router = APIRouter(prefix="/me/last-seen", tags=["LastSeen"]) @router.post("/") async def set_last_seen( channel_id: Optional[int] = None, dm_user_id: Optional[int] = None, last_seen: Optional[str] = None, session: Session = Depends(get_session), current_user: User = Depends(get_current_user), ): if not channel_id and not dm_user_id: raise HTTPException(status_code=400, detail="channel_id or dm_user_id required") # Validate target exists if channel_id: channel = session.get(Channel, channel_id) if not channel: raise HTTPException(status_code=404, detail="Channel not found") # Basic access check: user must belong to the channel's department user_dept_ids = [d.id for d in current_user.departments] if current_user.departments else [] if channel.department_id not in user_dept_ids: raise HTTPException(status_code=403, detail="No access to channel") if dm_user_id: other = session.get(User, dm_user_id) if not other: raise HTTPException(status_code=404, detail="Target user not found") ts = datetime.utcnow() if not last_seen else datetime.fromisoformat(last_seen) # Upsert last seen if channel_id: statement = select(LastSeen).where(LastSeen.user_id == current_user.id, LastSeen.channel_id == channel_id) else: statement = select(LastSeen).where(LastSeen.user_id == current_user.id, LastSeen.dm_user_id == dm_user_id) exists = session.exec(statement).first() if exists: exists.last_seen = ts session.add(exists) session.commit() session.refresh(exists) result = exists else: payload = LastSeen(user_id=current_user.id, channel_id=channel_id, dm_user_id=dm_user_id, last_seen=ts) session.add(payload) session.commit() session.refresh(payload) result = payload # Broadcast read_marker to relevant channel or DM partner try: message = { "type": "read_marker", "user_id": current_user.id, "last_seen": result.last_seen.isoformat(), } if channel_id: message["channel_id"] = channel_id await manager.broadcast_to_channel(message, channel_id) elif dm_user_id: message["dm_user_id"] = dm_user_id # partner listens on channel id = -their_user_id await manager.broadcast_to_channel(message, -dm_user_id) # also broadcast to presence channel 0 await manager.broadcast_to_channel({**message, "type": "read_marker"}, 0) except Exception: pass return {"id": result.id, "user_id": result.user_id, "channel_id": result.channel_id, "dm_user_id": result.dm_user_id, "last_seen": result.last_seen.isoformat()} @router.get("/") def get_last_seen( channel_id: Optional[int] = None, dm_user_id: Optional[int] = None, session: Session = Depends(get_session), current_user: User = Depends(get_current_user), ): if not channel_id and not dm_user_id: raise HTTPException(status_code=400, detail="channel_id or dm_user_id required") if channel_id: stmt = select(LastSeen).where(LastSeen.user_id == current_user.id, LastSeen.channel_id == channel_id) else: stmt = select(LastSeen).where(LastSeen.user_id == current_user.id, LastSeen.dm_user_id == dm_user_id) found = session.exec(stmt).first() if not found: raise HTTPException(status_code=404, detail="LastSeen not found") return {"id": found.id, "user_id": found.user_id, "channel_id": found.channel_id, "dm_user_id": found.dm_user_id, "last_seen": found.last_seen.isoformat()}