from fastapi import APIRouter, Depends from sqlmodel import Session, select, func from app.database import get_session from app.models.user import User from app.models.zone import Zone from app.models.score import Score from app.models.friendship import Friendship from app.schemas.score import LeaderboardEntry from app.auth.dependencies import get_current_user router = APIRouter(prefix="/leaderboard", tags=["leaderboard"]) @router.get("", response_model=list[LeaderboardEntry]) def get_leaderboard( current_user: User = Depends(get_current_user), session: Session = Depends(get_session), ): """Friend leaderboard sorted by total points. Includes the current user and all their friends. """ # Get friend IDs + self friend_ids = list( session.exec( select(Friendship.friend_id).where(Friendship.user_id == current_user.id) ).all() ) user_ids = friend_ids + [current_user.id] entries: list[LeaderboardEntry] = [] for uid in user_ids: user = session.get(User, uid) if not user: continue # Latest score latest_score = session.exec( select(Score).where(Score.user_id == uid).order_by(Score.date.desc()) # type: ignore[union-attr] ).first() total_pts = latest_score.total_pts if latest_score else 0 # Total area total_area = session.exec( select(func.coalesce(func.sum(Zone.area_m2), 0.0)).where( Zone.owner_id == uid ) ).one() # Zone count zone_count = session.exec( select(func.count()).select_from(Zone).where(Zone.owner_id == uid) ).one() entries.append( LeaderboardEntry( user_id=uid, username=user.username, avatar_url=user.avatar_url, total_pts=total_pts, total_area_m2=float(total_area), zone_count=zone_count, rank=0, # calculated below ) ) # Sort by total_pts descending, assign rank entries.sort(key=lambda e: e.total_pts, reverse=True) for i, entry in enumerate(entries): entry.rank = i + 1 return entries