feat(beatmap,score): update beatmaps from Bancho & deleting scores (#50)

New API:

- DELETE /api/private/score/{score_id}: delete a score
- POST /api/private/beatmapsets/{beatmapset_id}/sync: request for syncing a beatmapset

New configuration:

- OLD_SCORE_PROCESSING_MODE
This commit is contained in:
MingxuanGame
2025-10-02 13:36:09 +08:00
committed by GitHub
parent 860ebe9fa9
commit 3f6776847e
22 changed files with 888 additions and 84 deletions

View File

@@ -32,6 +32,7 @@ from app.models.score import (
ScoreStatistics,
SoloScoreSubmissionInfo,
)
from app.storage import StorageService
from app.utils import utcnow
from .beatmap import Beatmap, BeatmapResp
@@ -40,6 +41,7 @@ from .beatmapset import BeatmapsetResp
from .best_score import BestScore
from .counts import MonthlyPlaycounts
from .lazer_user import User, UserResp
from .playlist_best_score import PlaylistBestScore
from .pp_best_score import PPBestScore
from .relationship import (
Relationship as DBRelationship,
@@ -95,6 +97,7 @@ class ScoreBase(AsyncAttrs, SQLModel, UTCBaseModel):
beatmap_id: int = Field(index=True, foreign_key="beatmaps.id")
maximum_statistics: ScoreStatistics = Field(sa_column=Column(JSON), default_factory=dict)
processed: bool = False # solo_score
ranked: bool = False
@field_validator("maximum_statistics", mode="before")
@classmethod
@@ -189,16 +192,57 @@ class Score(ScoreBase, table=True):
# optional
beatmap: Mapped[Beatmap] = Relationship()
user: Mapped[User] = Relationship(sa_relationship_kwargs={"lazy": "joined"})
best_score: Mapped[BestScore | None] = Relationship(
back_populates="score",
sa_relationship_kwargs={
"cascade": "all, delete-orphan",
},
)
ranked_score: Mapped[PPBestScore | None] = Relationship(
back_populates="score",
sa_relationship_kwargs={
"cascade": "all, delete-orphan",
},
)
playlist_item_score: Mapped[PlaylistBestScore | None] = Relationship(
back_populates="score",
sa_relationship_kwargs={
"cascade": "all, delete-orphan",
},
)
@property
def is_perfect_combo(self) -> bool:
return self.max_combo == self.beatmap.max_combo
@property
def replay_filename(self) -> str:
return f"replays/{self.id}_{self.beatmap_id}_{self.user_id}_lazer_replay.osr"
async def to_resp(self, session: AsyncSession, api_version: int) -> "ScoreResp | LegacyScoreResp":
if api_version >= 20220705:
return await ScoreResp.from_db(session, self)
return await LegacyScoreResp.from_db(session, self)
async def delete(
self,
session: AsyncSession,
storage_service: StorageService,
):
if await self.awaitable_attrs.best_score:
assert self.best_score is not None
await self.best_score.delete(session)
await session.refresh(self)
if await self.awaitable_attrs.ranked_score:
assert self.ranked_score is not None
await self.ranked_score.delete(session)
await session.refresh(self)
if await self.awaitable_attrs.playlist_item_score:
await session.delete(self.playlist_item_score)
await storage_service.delete_file(self.replay_filename)
await session.delete(self)
class ScoreResp(ScoreBase):
id: int
@@ -218,7 +262,6 @@ class ScoreResp(ScoreBase):
rank_country: int | None = None
position: int | None = None
scores_around: "ScoreAround | None" = None
ranked: bool = False
current_user_attributes: CurrentUserAttributes | None = None
@field_validator(
@@ -335,7 +378,6 @@ class ScoreResp(ScoreBase):
s.current_user_attributes = CurrentUserAttributes(
pin=PinAttributes(is_pinned=bool(score.pinned_order), score_id=score.id)
)
s.ranked = s.pp > 0
return s
@@ -977,6 +1019,7 @@ async def process_score(
room_id=room_id,
maximum_statistics=info.maximum_statistics,
processed=True,
ranked=ranked,
)
successed = True
if can_get_pp: