From a53c63a33abb62a6bb22ad2875798d488aaf6d21 Mon Sep 17 00:00:00 2001 From: MingxuanGame Date: Tue, 29 Jul 2025 07:57:59 +0000 Subject: [PATCH] feat(score): add `best_id` in response --- app/database/score.py | 28 +++++++++++--- ...c71791_score_remove_best_id_in_database.py | 38 +++++++++++++++++++ 2 files changed, 60 insertions(+), 6 deletions(-) create mode 100644 migrations/versions/78be13c71791_score_remove_best_id_in_database.py diff --git a/app/database/score.py b/app/database/score.py index ef54f41..694395b 100644 --- a/app/database/score.py +++ b/app/database/score.py @@ -58,7 +58,6 @@ class ScoreBase(SQLModel): # 基本字段 accuracy: float map_md5: str = Field(max_length=32, index=True) - best_id: int | None = Field(default=None) build_id: int | None = Field(default=None) classic_total_score: int | None = Field( default=0, sa_column=Column(BigInteger) @@ -130,7 +129,9 @@ class Score(ScoreBase, table=True): ), ) if with_user: - clause.options(joinedload(Score.user).options(*User.all_select_option())) # pyright: ignore[reportArgumentType] + return clause.options( + joinedload(Score.user).options(*User.all_select_option()) # pyright: ignore[reportArgumentType] + ) return clause @staticmethod @@ -168,6 +169,7 @@ class ScoreResp(ScoreBase): legacy_total_score: int = 0 # FIXME processed: bool = False # solo_score weight: float = 0.0 + best_id: int | None = None ruleset_id: int | None = None beatmap: BeatmapResp | None = None beatmapset: BeatmapsetResp | None = None @@ -190,8 +192,10 @@ class ScoreResp(ScoreBase): s.is_perfect_combo = s.max_combo == s.beatmap.max_combo s.legacy_perfect = s.max_combo == s.beatmap.max_combo s.ruleset_id = MODE_TO_INT[score.gamemode] - if score.best_id: - s.weight = calculate_pp_weight(score.best_id) + best_id = await get_best_id(session, score.id) + if best_id: + s.best_id = best_id + s.weight = calculate_pp_weight(best_id - 1) s.statistics = { HitResult.MISS: score.nmiss, HitResult.MEH: score.n50, @@ -224,7 +228,7 @@ class ScoreResp(ScoreBase): score.map_md5, score.id, mode=score.gamemode, - user=score.user, + user=user or score.user, ) or None ) @@ -234,13 +238,25 @@ class ScoreResp(ScoreBase): score.map_md5, score.id, score.gamemode, - score.user, + user or score.user, ) or None ) return s +async def get_best_id(session: AsyncSession, score_id: int) -> None: + rownum = ( + func.row_number() + .over(partition_by=col(BestScore.user_id), order_by=col(BestScore.pp).desc()) + .label("rn") + ) + subq = select(BestScore, rownum).subquery() + stmt = select(subq.c.rn).where(subq.c.score_id == score_id) + result = await session.exec(stmt) + return result.one_or_none() + + async def get_leaderboard( session: AsyncSession, beatmap_md5: str, diff --git a/migrations/versions/78be13c71791_score_remove_best_id_in_database.py b/migrations/versions/78be13c71791_score_remove_best_id_in_database.py new file mode 100644 index 0000000..d0cab2b --- /dev/null +++ b/migrations/versions/78be13c71791_score_remove_best_id_in_database.py @@ -0,0 +1,38 @@ +"""score: remove best_id in database + +Revision ID: 78be13c71791 +Revises: dc4d25c428c7 +Create Date: 2025-07-29 07:57:33.764517 + +""" + +from __future__ import annotations + +from collections.abc import Sequence + +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import mysql + +# revision identifiers, used by Alembic. +revision: str = "78be13c71791" +down_revision: str | Sequence[str] | None = "dc4d25c428c7" +branch_labels: str | Sequence[str] | None = None +depends_on: str | Sequence[str] | None = None + + +def upgrade() -> None: + """Upgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column("scores", "best_id") + # ### end Alembic commands ### + + +def downgrade() -> None: + """Downgrade schema.""" + # ### commands auto generated by Alembic - please adjust! ### + op.add_column( + "scores", + sa.Column("best_id", mysql.INTEGER(), autoincrement=False, nullable=True), + ) + # ### end Alembic commands ###