fix(ranking): fix missing user causing client crash
This commit is contained in:
@@ -444,3 +444,8 @@ BASE_INCLUDES = [
|
|||||||
"daily_challenge_user_stats",
|
"daily_challenge_user_stats",
|
||||||
"statistics",
|
"statistics",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
RANKING_INCLUDES = [
|
||||||
|
"team",
|
||||||
|
"statistics",
|
||||||
|
]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from datetime import UTC, datetime
|
from datetime import UTC, datetime, timedelta
|
||||||
import math
|
import math
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
@@ -6,6 +6,7 @@ from app.models.score import GameMode
|
|||||||
|
|
||||||
from .rank_history import RankHistory
|
from .rank_history import RankHistory
|
||||||
|
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncAttrs
|
||||||
from sqlmodel import (
|
from sqlmodel import (
|
||||||
BigInteger,
|
BigInteger,
|
||||||
Column,
|
Column,
|
||||||
@@ -20,7 +21,7 @@ from sqlmodel import (
|
|||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .lazer_user import User
|
from .lazer_user import User, UserResp
|
||||||
|
|
||||||
|
|
||||||
class UserStatisticsBase(SQLModel):
|
class UserStatisticsBase(SQLModel):
|
||||||
@@ -43,7 +44,7 @@ class UserStatisticsBase(SQLModel):
|
|||||||
is_ranked: bool = Field(default=True)
|
is_ranked: bool = Field(default=True)
|
||||||
|
|
||||||
|
|
||||||
class UserStatistics(UserStatisticsBase, table=True):
|
class UserStatistics(AsyncAttrs, UserStatisticsBase, table=True):
|
||||||
__tablename__ = "lazer_user_statistics" # pyright: ignore[reportAssignmentType]
|
__tablename__ = "lazer_user_statistics" # pyright: ignore[reportAssignmentType]
|
||||||
id: int | None = Field(default=None, primary_key=True)
|
id: int | None = Field(default=None, primary_key=True)
|
||||||
user_id: int = Field(
|
user_id: int = Field(
|
||||||
@@ -66,6 +67,8 @@ class UserStatistics(UserStatisticsBase, table=True):
|
|||||||
|
|
||||||
|
|
||||||
class UserStatisticsResp(UserStatisticsBase):
|
class UserStatisticsResp(UserStatisticsBase):
|
||||||
|
user: "UserResp | None" = None
|
||||||
|
rank_change_since_30_days: int | None = 0
|
||||||
global_rank: int | None = Field(default=None)
|
global_rank: int | None = Field(default=None)
|
||||||
country_rank: int | None = Field(default=None)
|
country_rank: int | None = Field(default=None)
|
||||||
grade_counts: dict[str, int] = Field(
|
grade_counts: dict[str, int] = Field(
|
||||||
@@ -86,9 +89,13 @@ class UserStatisticsResp(UserStatisticsBase):
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_db(
|
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":
|
) -> "UserStatisticsResp":
|
||||||
s = cls.model_validate(obj)
|
s = cls.model_validate(obj.model_dump())
|
||||||
s.grade_counts = {
|
s.grade_counts = {
|
||||||
"ss": obj.grade_ss,
|
"ss": obj.grade_ss,
|
||||||
"ssh": obj.grade_ssh,
|
"ssh": obj.grade_ssh,
|
||||||
@@ -100,9 +107,32 @@ class UserStatisticsResp(UserStatisticsBase):
|
|||||||
"current": int(obj.level_current),
|
"current": int(obj.level_current),
|
||||||
"progress": int(math.fmod(obj.level_current, 1) * 100),
|
"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.global_rank = await get_rank(session, obj)
|
||||||
s.country_rank = await get_rank(session, obj, user_country)
|
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
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ class GameMode(str, Enum):
|
|||||||
GameMode.TAIKO: GameMode.TAIKORX,
|
GameMode.TAIKO: GameMode.TAIKORX,
|
||||||
GameMode.FRUITS: GameMode.FRUITSRX,
|
GameMode.FRUITS: GameMode.FRUITSRX,
|
||||||
}[self]
|
}[self]
|
||||||
raise ValueError(f"Unknown game mode: {self}")
|
return self
|
||||||
|
|
||||||
|
|
||||||
class Rank(str, Enum):
|
class Rank(str, Enum):
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
|
|||||||
|
|
||||||
|
|
||||||
class CountryStatistics(BaseModel):
|
class CountryStatistics(BaseModel):
|
||||||
country_code: str
|
code: str
|
||||||
active_users: int
|
active_users: int
|
||||||
play_count: int
|
play_count: int
|
||||||
ranked_score: int
|
ranked_score: int
|
||||||
@@ -48,13 +48,15 @@ async def get_country_ranking(
|
|||||||
await session.exec(
|
await session.exec(
|
||||||
select(UserStatistics).where(
|
select(UserStatistics).where(
|
||||||
UserStatistics.mode == ruleset,
|
UserStatistics.mode == ruleset,
|
||||||
|
UserStatistics.pp > 0,
|
||||||
col(UserStatistics.user).has(country_code=country),
|
col(UserStatistics.user).has(country_code=country),
|
||||||
|
col(UserStatistics.user).has(is_active=True),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
).all()
|
).all()
|
||||||
pp = 0
|
pp = 0
|
||||||
country_stats = CountryStatistics(
|
country_stats = CountryStatistics(
|
||||||
country_code=country,
|
code=country,
|
||||||
active_users=0,
|
active_users=0,
|
||||||
play_count=0,
|
play_count=0,
|
||||||
ranked_score=0,
|
ranked_score=0,
|
||||||
@@ -92,9 +94,15 @@ async def get_user_ranking(
|
|||||||
current_user: User = Security(get_current_user, scopes=["public"]),
|
current_user: User = Security(get_current_user, scopes=["public"]),
|
||||||
session: AsyncSession = Depends(get_db),
|
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":
|
if type == "performance":
|
||||||
order_by = col(UserStatistics.pp).desc()
|
order_by = col(UserStatistics.pp).desc()
|
||||||
|
include.append("rank_change_since_30_days")
|
||||||
else:
|
else:
|
||||||
order_by = col(UserStatistics.ranked_score).desc()
|
order_by = col(UserStatistics.ranked_score).desc()
|
||||||
if country:
|
if country:
|
||||||
@@ -106,9 +114,10 @@ async def get_user_ranking(
|
|||||||
.limit(50)
|
.limit(50)
|
||||||
.offset(50 * (page - 1))
|
.offset(50 * (page - 1))
|
||||||
)
|
)
|
||||||
return TopUsersResponse(
|
resp = TopUsersResponse(
|
||||||
ranking=[
|
ranking=[
|
||||||
await UserStatisticsResp.from_db(statistics, session, None)
|
await UserStatisticsResp.from_db(statistics, session, None, include)
|
||||||
for statistics in statistics_list
|
for statistics in statistics_list
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
return resp
|
||||||
|
|||||||
Reference in New Issue
Block a user