feat(playlist): support leaderboard

**UNTESTED**
This commit is contained in:
MingxuanGame
2025-08-07 14:52:02 +00:00
parent 18d16e2542
commit bc2961de10
6 changed files with 175 additions and 23 deletions

View File

@@ -1,10 +1,10 @@
from __future__ import annotations
from datetime import UTC, datetime
from time import timezone
from typing import Literal
from app.database.lazer_user import User
from app.database.playlist_attempts import ItemAttemptsCount, ItemAttemptsResp
from app.database.playlists import Playlist, PlaylistResp
from app.database.room import Room, RoomBase, RoomResp
from app.dependencies.database import get_db, get_redis
@@ -22,10 +22,10 @@ from app.signalr.hub import MultiplayerHubs
from .api_router import router
from fastapi import Depends, HTTPException, Query
from pydantic import BaseModel, Field
from redis.asyncio import Redis
from sqlmodel import select
from sqlmodel import col, select
from sqlmodel.ext.asyncio.session import AsyncSession
from starlette.status import HTTP_417_EXPECTATION_FAILED
@router.get("/rooms", tags=["rooms"], response_model=list[RoomResp])
@@ -144,3 +144,37 @@ async def add_user_to_room(room: int, user: int, db: AsyncSession = Depends(get_
return resp
else:
raise HTTPException(404, "room not found0")
class APILeaderboard(BaseModel):
leaderboard: list[ItemAttemptsResp] = Field(default_factory=list)
user_score: ItemAttemptsResp | None = None
@router.get("/rooms/{room}/leaderboard", tags=["room"], response_model=APILeaderboard)
async def get_room_leaderboard(
room: int,
db: AsyncSession = Depends(get_db),
current_user: User = Depends(get_current_user),
):
server_room = MultiplayerHubs.rooms[room]
if not server_room:
raise HTTPException(404, "Room not found")
aggs = await db.exec(
select(ItemAttemptsCount)
.where(ItemAttemptsCount.room_id == room)
.order_by(col(ItemAttemptsCount.total_score).desc())
)
aggs_resp = []
user_agg = None
for i, agg in enumerate(aggs):
resp = await ItemAttemptsResp.from_db(agg, db)
resp.position = i + 1
aggs_resp.append(resp)
if agg.user_id == current_user.id:
user_agg = resp
return APILeaderboard(
leaderboard=aggs_resp,
user_score=user_agg,
)

View File

@@ -1,17 +1,20 @@
from __future__ import annotations
from datetime import UTC, datetime
import time
from app.calculator import clamp
from app.database import (
Beatmap,
Playlist,
Room,
Score,
ScoreResp,
ScoreToken,
ScoreTokenResp,
User,
)
from app.database.playlist_attempts import ItemAttemptsCount
from app.database.playlist_best_score import (
PlaylistBestScore,
get_position,
@@ -36,7 +39,6 @@ from app.models.score import (
Rank,
SoloScoreSubmissionInfo,
)
from app.signalr.hub import MultiplayerHubs
from .api_router import router
@@ -278,9 +280,11 @@ async def create_playlist_score(
current_user: User = Depends(get_current_user),
session: AsyncSession = Depends(get_db),
):
room = MultiplayerHubs.rooms[room_id]
room = await session.get(Room, room_id)
if not room:
raise HTTPException(status_code=404, detail="Room not found")
if room.ended_at and room.ended_at < datetime.now(UTC):
raise HTTPException(status_code=400, detail="Room has ended")
item = (
await session.exec(
select(Playlist).where(
@@ -301,7 +305,18 @@ async def create_playlist_score(
raise HTTPException(
status_code=400, detail="Beatmap ID mismatch in playlist item"
)
# TODO: max attempts
agg = await session.exec(
select(ItemAttemptsCount).where(
ItemAttemptsCount.room_id == room_id,
ItemAttemptsCount.user_id == current_user.id,
)
)
agg = agg.first()
if agg and room.max_attempts and agg.attempts >= room.max_attempts:
raise HTTPException(
status_code=422,
detail="You have reached the maximum attempts for this room",
)
if item.expired:
raise HTTPException(status_code=400, detail="Playlist item has expired")
if item.played_at:
@@ -342,6 +357,8 @@ async def submit_playlist_score(
).first()
if not item:
raise HTTPException(status_code=404, detail="Playlist item not found")
user_id = current_user.id
score_resp = await submit_score(
info,
item.beatmap_id,
@@ -356,12 +373,13 @@ async def submit_playlist_score(
await process_playlist_best_score(
room_id,
playlist_id,
current_user.id,
user_id,
score_resp.id,
score_resp.total_score,
session,
redis,
)
await ItemAttemptsCount.get_or_create(room_id, user_id, session)
return score_resp