feat(user): support view recent & best scores

This commit is contained in:
MingxuanGame
2025-08-11 07:32:41 +00:00
parent bc782cda01
commit 680c7525b8
2 changed files with 75 additions and 10 deletions

View File

@@ -1,4 +1,4 @@
from datetime import UTC, datetime from datetime import UTC, datetime, timedelta
from typing import TYPE_CHECKING, NotRequired, TypedDict from typing import TYPE_CHECKING, NotRequired, TypedDict
from app.models.model import UTCBaseModel 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 app.models.user import Country, Page, RankHistory
from .achievement import UserAchievement, UserAchievementResp from .achievement import UserAchievement, UserAchievementResp
from .beatmap_playcounts import BeatmapPlaycounts
from .daily_challenge import DailyChallengeStats, DailyChallengeStatsResp from .daily_challenge import DailyChallengeStats, DailyChallengeStatsResp
from .monthly_playcounts import MonthlyPlaycounts, MonthlyPlaycountsResp from .monthly_playcounts import MonthlyPlaycounts, MonthlyPlaycountsResp
from .statistics import UserStatistics, UserStatisticsResp from .statistics import UserStatistics, UserStatisticsResp
@@ -21,6 +22,7 @@ from sqlmodel import (
Field, Field,
Relationship, Relationship,
SQLModel, SQLModel,
col,
func, func,
select, select,
) )
@@ -164,7 +166,7 @@ class UserResp(UserBase):
is_online: bool = False is_online: bool = False
groups: list = [] # TODO groups: list = [] # TODO
country: Country = Field(default_factory=lambda: Country(code="CN", name="China")) 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 graveyard_beatmapset_count: int = 0 # TODO
guest_beatmapset_count: int = 0 # TODO guest_beatmapset_count: int = 0 # TODO
loved_beatmapset_count: int = 0 # TODO loved_beatmapset_count: int = 0 # TODO
@@ -176,9 +178,10 @@ class UserResp(UserBase):
follower_count: int = 0 follower_count: int = 0
friends: list["RelationshipResp"] | None = None friends: list["RelationshipResp"] | None = None
scores_best_count: int = 0 scores_best_count: int = 0
scores_first_count: int = 0 scores_first_count: int = 0 # TODO
scores_recent_count: int = 0 scores_recent_count: int = 0
scores_pinned_count: int = 0 scores_pinned_count: int = 0
beatmap_playcounts_count: int = 0
account_history: list[UserAccountHistoryResp] = [] account_history: list[UserAccountHistoryResp] = []
active_tournament_banners: list[dict] = [] # TODO active_tournament_banners: list[dict] = [] # TODO
kudosu: Kudosu = Field(default_factory=lambda: Kudosu(available=0, total=0)) # 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 app.dependencies.database import get_redis
from .best_score import BestScore from .best_score import BestScore
from .favourite_beatmapset import FavouriteBeatmapset
from .relationship import Relationship, RelationshipResp, RelationshipType from .relationship import Relationship, RelationshipResp, RelationshipType
from .score import Score
ruleset = ruleset or obj.playmode
u = cls.model_validate(obj.model_dump()) u = cls.model_validate(obj.model_dump())
u.id = obj.id u.id = obj.id
@@ -275,7 +282,7 @@ class UserResp(UserBase):
if "statistics" in include: if "statistics" in include:
current_stattistics = None current_stattistics = None
for i in await obj.awaitable_attrs.statistics: for i in await obj.awaitable_attrs.statistics:
if i.mode == (ruleset or obj.playmode): if i.mode == ruleset:
current_stattistics = i current_stattistics = i
break break
u.statistics = ( u.statistics = (
@@ -302,6 +309,58 @@ class UserResp(UserBase):
for ua in await obj.awaitable_attrs.achievement 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 return u

View File

@@ -1,5 +1,6 @@
from __future__ import annotations from __future__ import annotations
from datetime import UTC, datetime, timedelta
from typing import Literal from typing import Literal
from app.database import ( from app.database import (
@@ -10,6 +11,7 @@ from app.database import (
UserResp, UserResp,
) )
from app.database.lazer_user import SEARCH_INCLUDED 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
from app.dependencies.database import get_db from app.dependencies.database import get_db
from app.dependencies.user import get_current_user from app.dependencies.user import get_current_user
@@ -20,7 +22,7 @@ from .api_router import router
from fastapi import Depends, HTTPException, Query from fastapi import Depends, HTTPException, Query
from pydantic import BaseModel from pydantic import BaseModel
from sqlmodel import false, select from sqlmodel import exists, false, select
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
from sqlmodel.sql.expression import col from sqlmodel.sql.expression import col
@@ -152,15 +154,19 @@ async def get_user_scores(
gamemode = mode or db_user.playmode gamemode = mode or db_user.playmode
order_by = None order_by = None
where_clause = ( where_clause = (col(Score.user_id) == db_user.id) & (
(col(Score.user_id) == db_user.id) col(Score.gamemode) == gamemode
& (col(Score.gamemode) == gamemode)
& (col(Score.passed).is_(True))
) )
if not include_fails:
where_clause &= col(Score.passed).is_(True)
if type == "pinned": if type == "pinned":
where_clause &= Score.pinned_order > 0 where_clause &= Score.pinned_order > 0
order_by = col(Score.pinned_order).asc() 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 # TODO
where_clause &= false() where_clause &= false()