From 7ec716d4deb0bc8f9574878994bbd5f846d44e0f Mon Sep 17 00:00:00 2001 From: MingxuanGame Date: Tue, 26 Aug 2025 16:42:57 +0000 Subject: [PATCH] feat(user): support get the user's first scores --- app/database/lazer_user.py | 7 +++-- app/database/score.py | 58 ++++++++++++++++++++++++++++++++++++++ app/router/v2/user.py | 17 ++++++----- 3 files changed, 72 insertions(+), 10 deletions(-) diff --git a/app/database/lazer_user.py b/app/database/lazer_user.py index 0c0a6cd..2ead3fd 100644 --- a/app/database/lazer_user.py +++ b/app/database/lazer_user.py @@ -2,6 +2,7 @@ from datetime import datetime, timedelta import json from typing import TYPE_CHECKING, NotRequired, TypedDict +from app.config import settings from app.models.model import UTCBaseModel from app.models.score import GameMode from app.models.user import Country, Page @@ -223,7 +224,7 @@ class UserResp(UserBase): follower_count: int = 0 friends: list["RelationshipResp"] | None = None scores_best_count: int = 0 - scores_first_count: int = 0 # TODO + scores_first_count: int = 0 scores_recent_count: int = 0 scores_pinned_count: int = 0 beatmap_playcounts_count: int = 0 @@ -261,7 +262,7 @@ class UserResp(UserBase): from .favourite_beatmapset import FavouriteBeatmapset from .pp_best_score import PPBestScore from .relationship import Relationship, RelationshipResp, RelationshipType - from .score import Score + from .score import Score, get_user_first_score_count ruleset = ruleset or obj.playmode @@ -409,6 +410,7 @@ class UserResp(UserBase): ) ) ).one() + u.scores_first_count = await get_user_first_score_count(session, obj.id, ruleset) u.beatmap_playcounts_count = ( await session.exec( select(func.count()) @@ -421,7 +423,6 @@ class UserResp(UserBase): # 检查会话验证状态 # 如果邮件验证功能被禁用,则始终设置 session_verified 为 true - from app.config import settings if not settings.enable_email_verification: u.session_verified = True diff --git a/app/database/score.py b/app/database/score.py index 53d5ff3..f0c1f90 100644 --- a/app/database/score.py +++ b/app/database/score.py @@ -570,6 +570,64 @@ async def get_user_best_score_with_mod_in_beatmap( ).first() +async def get_user_first_scores( + session: AsyncSession, user_id: int, mode: GameMode, limit: int = 5, offset: int = 0 +) -> list[BestScore]: + rownum = ( + func.row_number() + .over( + partition_by=(col(BestScore.beatmap_id), col(BestScore.gamemode)), + order_by=col(BestScore.total_score).desc(), + ) + .label("rn") + ) + + # Step 1: Fetch top score_ids in Python + subq = ( + select( + col(BestScore.score_id).label("score_id"), + col(BestScore.user_id).label("user_id"), + rownum, + ) + .where(col(BestScore.gamemode) == mode) + .subquery() + ) + + top_ids_stmt = select(subq.c.score_id).where(subq.c.rn == 1, subq.c.user_id == user_id).limit(limit).offset(offset) + + top_ids = await session.exec(top_ids_stmt) + top_ids = list(top_ids) + + stmt = select(BestScore).where(col(BestScore.score_id).in_(top_ids)).order_by(col(BestScore.total_score).desc()) + + result = await session.exec(stmt) + return list(result.all()) + + +async def get_user_first_score_count(session: AsyncSession, user_id: int, mode: GameMode) -> int: + rownum = ( + func.row_number() + .over( + partition_by=(col(BestScore.beatmap_id), col(BestScore.gamemode)), + order_by=col(BestScore.total_score).desc(), + ) + .label("rn") + ) + subq = ( + select( + col(BestScore.score_id).label("score_id"), + col(BestScore.user_id).label("user_id"), + rownum, + ) + .where(col(BestScore.gamemode) == mode) + .subquery() + ) + count_stmt = select(func.count()).where(subq.c.rn == 1, subq.c.user_id == user_id) + + result = await session.exec(count_stmt) + return result.one() + + async def get_user_best_pp_in_beatmap( session: AsyncSession, beatmap: int, diff --git a/app/router/v2/user.py b/app/router/v2/user.py index e9924e6..67ca97a 100644 --- a/app/router/v2/user.py +++ b/app/router/v2/user.py @@ -15,7 +15,7 @@ from app.database import ( from app.database.events import Event from app.database.lazer_user import SEARCH_INCLUDED from app.database.pp_best_score import PPBestScore -from app.database.score import Score, ScoreResp +from app.database.score import Score, ScoreResp, get_user_first_scores from app.dependencies.database import Database, get_redis from app.dependencies.user import get_current_user from app.log import logger @@ -360,14 +360,17 @@ async def get_user_scores( where_clause &= Score.ended_at > utcnow() - timedelta(hours=24) order_by = col(Score.ended_at).desc() elif type == "firsts": - # TODO where_clause &= false() - scores = ( - await session.exec(select(Score).where(where_clause).order_by(order_by).limit(limit).offset(offset)) - ).all() - if not scores: - return [] + if type != "firsts": + scores = ( + await session.exec(select(Score).where(where_clause).order_by(order_by).limit(limit).offset(offset)) + ).all() + if not scores: + return [] + else: + best_scores = await get_user_first_scores(session, db_user.id, gamemode, limit) + scores = [best_score.score for best_score in best_scores] score_responses = [ await ScoreResp.from_db(