From 680c7525b89a57514ed172bbb1f86deedcf26bb8 Mon Sep 17 00:00:00 2001 From: MingxuanGame Date: Mon, 11 Aug 2025 07:32:41 +0000 Subject: [PATCH] feat(user): support view recent & best scores --- app/database/lazer_user.py | 67 +++++++++++++++++++++++++++++++++++--- app/router/user.py | 18 ++++++---- 2 files changed, 75 insertions(+), 10 deletions(-) diff --git a/app/database/lazer_user.py b/app/database/lazer_user.py index 2717c3a..2397323 100644 --- a/app/database/lazer_user.py +++ b/app/database/lazer_user.py @@ -1,4 +1,4 @@ -from datetime import UTC, datetime +from datetime import UTC, datetime, timedelta from typing import TYPE_CHECKING, NotRequired, TypedDict from app.models.model import UTCBaseModel @@ -6,6 +6,7 @@ from app.models.score import GameMode from app.models.user import Country, Page, RankHistory from .achievement import UserAchievement, UserAchievementResp +from .beatmap_playcounts import BeatmapPlaycounts from .daily_challenge import DailyChallengeStats, DailyChallengeStatsResp from .monthly_playcounts import MonthlyPlaycounts, MonthlyPlaycountsResp from .statistics import UserStatistics, UserStatisticsResp @@ -21,6 +22,7 @@ from sqlmodel import ( Field, Relationship, SQLModel, + col, func, select, ) @@ -164,7 +166,7 @@ class UserResp(UserBase): is_online: bool = False groups: list = [] # TODO country: Country = Field(default_factory=lambda: Country(code="CN", name="China")) - favourite_beatmapset_count: int = 0 # TODO + favourite_beatmapset_count: int = 0 graveyard_beatmapset_count: int = 0 # TODO guest_beatmapset_count: int = 0 # TODO loved_beatmapset_count: int = 0 # TODO @@ -176,9 +178,10 @@ class UserResp(UserBase): follower_count: int = 0 friends: list["RelationshipResp"] | None = None scores_best_count: int = 0 - scores_first_count: int = 0 + scores_first_count: int = 0 # TODO scores_recent_count: int = 0 scores_pinned_count: int = 0 + beatmap_playcounts_count: int = 0 account_history: list[UserAccountHistoryResp] = [] active_tournament_banners: list[dict] = [] # TODO kudosu: Kudosu = Field(default_factory=lambda: Kudosu(available=0, total=0)) # TODO @@ -207,7 +210,11 @@ class UserResp(UserBase): from app.dependencies.database import get_redis from .best_score import BestScore + from .favourite_beatmapset import FavouriteBeatmapset from .relationship import Relationship, RelationshipResp, RelationshipType + from .score import Score + + ruleset = ruleset or obj.playmode u = cls.model_validate(obj.model_dump()) u.id = obj.id @@ -275,7 +282,7 @@ class UserResp(UserBase): if "statistics" in include: current_stattistics = None for i in await obj.awaitable_attrs.statistics: - if i.mode == (ruleset or obj.playmode): + if i.mode == ruleset: current_stattistics = i break u.statistics = ( @@ -302,6 +309,58 @@ class UserResp(UserBase): for ua in await obj.awaitable_attrs.achievement ] + u.favourite_beatmapset_count = ( + await session.exec( + select(func.count()) + .select_from(FavouriteBeatmapset) + .where(FavouriteBeatmapset.user_id == obj.id) + ) + ).one() + u.scores_pinned_count = ( + await session.exec( + select(func.count()) + .select_from(Score) + .where( + Score.user_id == obj.id, + Score.pinned_order > 0, + Score.gamemode == ruleset, + col(Score.passed).is_(True), + ) + ) + ).one() + u.scores_best_count = ( + await session.exec( + select(func.count()) + .select_from(BestScore) + .where( + BestScore.user_id == obj.id, + BestScore.gamemode == ruleset, + ) + .limit(200) + ) + ).one() + u.scores_recent_count = ( + await session.exec( + select(func.count()) + .select_from(Score) + .where( + Score.user_id == obj.id, + Score.gamemode == ruleset, + col(Score.passed).is_(True), + Score.ended_at > datetime.now(UTC) - timedelta(hours=24), + ) + ) + ).one() + u.beatmap_playcounts_count = ( + await session.exec( + select(func.count()) + .select_from(BeatmapPlaycounts) + .where( + BeatmapPlaycounts.user_id == obj.id, + ) + ) + ).one() + return u diff --git a/app/router/user.py b/app/router/user.py index c22fc85..e8d0a45 100644 --- a/app/router/user.py +++ b/app/router/user.py @@ -1,5 +1,6 @@ from __future__ import annotations +from datetime import UTC, datetime, timedelta from typing import Literal from app.database import ( @@ -10,6 +11,7 @@ from app.database import ( UserResp, ) 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.dependencies.database import get_db from app.dependencies.user import get_current_user @@ -20,7 +22,7 @@ from .api_router import router from fastapi import Depends, HTTPException, Query from pydantic import BaseModel -from sqlmodel import false, select +from sqlmodel import exists, false, select from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.sql.expression import col @@ -152,15 +154,19 @@ async def get_user_scores( gamemode = mode or db_user.playmode order_by = None - where_clause = ( - (col(Score.user_id) == db_user.id) - & (col(Score.gamemode) == gamemode) - & (col(Score.passed).is_(True)) + where_clause = (col(Score.user_id) == db_user.id) & ( + col(Score.gamemode) == gamemode ) + if not include_fails: + where_clause &= col(Score.passed).is_(True) if type == "pinned": where_clause &= Score.pinned_order > 0 order_by = col(Score.pinned_order).asc() - else: + elif type == "best": + where_clause &= exists().where(col(PPBestScore.score_id) == Score.id) + elif type == "recent": + where_clause &= Score.ended_at > datetime.now(UTC) - timedelta(hours=24) + elif type == "firsts": # TODO where_clause &= false()