fix(ranking): fix missing user causing client crash

This commit is contained in:
MingxuanGame
2025-08-15 06:34:35 +00:00
parent 1251ba31a2
commit d87839f86e
4 changed files with 55 additions and 11 deletions

View File

@@ -444,3 +444,8 @@ BASE_INCLUDES = [
"daily_challenge_user_stats",
"statistics",
]
RANKING_INCLUDES = [
"team",
"statistics",
]

View File

@@ -1,4 +1,4 @@
from datetime import UTC, datetime
from datetime import UTC, datetime, timedelta
import math
from typing import TYPE_CHECKING
@@ -6,6 +6,7 @@ from app.models.score import GameMode
from .rank_history import RankHistory
from sqlalchemy.ext.asyncio import AsyncAttrs
from sqlmodel import (
BigInteger,
Column,
@@ -20,7 +21,7 @@ from sqlmodel import (
from sqlmodel.ext.asyncio.session import AsyncSession
if TYPE_CHECKING:
from .lazer_user import User
from .lazer_user import User, UserResp
class UserStatisticsBase(SQLModel):
@@ -43,7 +44,7 @@ class UserStatisticsBase(SQLModel):
is_ranked: bool = Field(default=True)
class UserStatistics(UserStatisticsBase, table=True):
class UserStatistics(AsyncAttrs, UserStatisticsBase, table=True):
__tablename__ = "lazer_user_statistics" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True)
user_id: int = Field(
@@ -66,6 +67,8 @@ class UserStatistics(UserStatisticsBase, table=True):
class UserStatisticsResp(UserStatisticsBase):
user: "UserResp | None" = None
rank_change_since_30_days: int | None = 0
global_rank: int | None = Field(default=None)
country_rank: int | None = Field(default=None)
grade_counts: dict[str, int] = Field(
@@ -86,9 +89,13 @@ class UserStatisticsResp(UserStatisticsBase):
@classmethod
async def from_db(
cls, obj: UserStatistics, session: AsyncSession, user_country: str | None = None
cls,
obj: UserStatistics,
session: AsyncSession,
user_country: str | None = None,
include: list[str] = [],
) -> "UserStatisticsResp":
s = cls.model_validate(obj)
s = cls.model_validate(obj.model_dump())
s.grade_counts = {
"ss": obj.grade_ss,
"ssh": obj.grade_ssh,
@@ -100,9 +107,32 @@ class UserStatisticsResp(UserStatisticsBase):
"current": int(obj.level_current),
"progress": int(math.fmod(obj.level_current, 1) * 100),
}
if "user" in include:
from .lazer_user import RANKING_INCLUDES, UserResp
user = await UserResp.from_db(
await obj.awaitable_attrs.user, session, include=RANKING_INCLUDES
)
s.user = user
user_country = user.country_code
s.global_rank = await get_rank(session, obj)
s.country_rank = await get_rank(session, obj, user_country)
if "rank_change_since_30_days" in include:
rank_best = (
await session.exec(
select(func.max(RankHistory.rank)).where(
RankHistory.date > datetime.now(UTC) - timedelta(days=30),
RankHistory.user_id == obj.user_id,
)
)
).first()
if rank_best is None or s.global_rank is None:
s.rank_change_since_30_days = 0
else:
s.rank_change_since_30_days = rank_best - s.global_rank
return s

View File

@@ -86,7 +86,7 @@ class GameMode(str, Enum):
GameMode.TAIKO: GameMode.TAIKORX,
GameMode.FRUITS: GameMode.FRUITSRX,
}[self]
raise ValueError(f"Unknown game mode: {self}")
return self
class Rank(str, Enum):

View File

@@ -17,7 +17,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
class CountryStatistics(BaseModel):
country_code: str
code: str
active_users: int
play_count: int
ranked_score: int
@@ -48,13 +48,15 @@ async def get_country_ranking(
await session.exec(
select(UserStatistics).where(
UserStatistics.mode == ruleset,
UserStatistics.pp > 0,
col(UserStatistics.user).has(country_code=country),
col(UserStatistics.user).has(is_active=True),
)
)
).all()
pp = 0
country_stats = CountryStatistics(
country_code=country,
code=country,
active_users=0,
play_count=0,
ranked_score=0,
@@ -92,9 +94,15 @@ async def get_user_ranking(
current_user: User = Security(get_current_user, scopes=["public"]),
session: AsyncSession = Depends(get_db),
):
wheres = [col(UserStatistics.mode) == ruleset]
wheres = [
col(UserStatistics.mode) == ruleset,
col(UserStatistics.pp) > 0,
col(UserStatistics.is_ranked).is_(True),
]
include = ["user"]
if type == "performance":
order_by = col(UserStatistics.pp).desc()
include.append("rank_change_since_30_days")
else:
order_by = col(UserStatistics.ranked_score).desc()
if country:
@@ -106,9 +114,10 @@ async def get_user_ranking(
.limit(50)
.offset(50 * (page - 1))
)
return TopUsersResponse(
resp = TopUsersResponse(
ranking=[
await UserStatisticsResp.from_db(statistics, session, None)
await UserStatisticsResp.from_db(statistics, session, None, include)
for statistics in statistics_list
]
)
return resp