fix(solo-score): fix submit solo-score & Bigint

This commit is contained in:
MingxuanGame
2025-07-27 04:11:41 +00:00
parent 0b8beade5d
commit ec241ac200
11 changed files with 225 additions and 46 deletions

View File

@@ -2,7 +2,7 @@ from datetime import datetime
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import Column, DateTime from sqlalchemy import Column, DateTime
from sqlmodel import Field, Relationship, SQLModel from sqlmodel import BigInteger, Field, ForeignKey, Relationship, SQLModel
if TYPE_CHECKING: if TYPE_CHECKING:
from .user import User from .user import User
@@ -12,7 +12,9 @@ class OAuthToken(SQLModel, table=True):
__tablename__ = "oauth_tokens" # pyright: ignore[reportAssignmentType] __tablename__ = "oauth_tokens" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
user_id: int = Field(foreign_key="users.id") user_id: int = Field(
sa_column=Column(BigInteger, ForeignKey("users.id"), index=True)
)
access_token: str = Field(max_length=500, unique=True) access_token: str = Field(max_length=500, unique=True)
refresh_token: str = Field(max_length=500, unique=True) refresh_token: str = Field(max_length=500, unique=True)
token_type: str = Field(default="Bearer", max_length=20) token_type: str = Field(default="Bearer", max_length=20)

View File

@@ -1,6 +1,6 @@
from datetime import datetime from datetime import datetime
from typing import TYPE_CHECKING
from app.fetcher import Fetcher
from app.models.beatmap import BeatmapRankStatus from app.models.beatmap import BeatmapRankStatus
from app.models.score import MODE_TO_INT, GameMode from app.models.score import MODE_TO_INT, GameMode
@@ -11,6 +11,9 @@ from sqlalchemy.orm import joinedload
from sqlmodel import VARCHAR, Field, Relationship, SQLModel, select from sqlmodel import VARCHAR, Field, Relationship, SQLModel, select
from sqlmodel.ext.asyncio.session import AsyncSession from sqlmodel.ext.asyncio.session import AsyncSession
if TYPE_CHECKING:
from app.fetcher import Fetcher
class BeatmapOwner(SQLModel): class BeatmapOwner(SQLModel):
id: int id: int
@@ -111,7 +114,7 @@ class Beatmap(BeatmapBase, table=True):
@classmethod @classmethod
async def get_or_fetch( async def get_or_fetch(
cls, session: AsyncSession, bid: int, fetcher: Fetcher cls, session: AsyncSession, bid: int, fetcher: "Fetcher"
) -> "Beatmap": ) -> "Beatmap":
beatmap = ( beatmap = (
await session.exec( await session.exec(

View File

@@ -3,7 +3,7 @@ from typing import TYPE_CHECKING
from sqlalchemy import JSON, Column, DateTime from sqlalchemy import JSON, Column, DateTime
from sqlalchemy.orm import Mapped from sqlalchemy.orm import Mapped
from sqlmodel import Field, Relationship, SQLModel from sqlmodel import BigInteger, Field, ForeignKey, Relationship, SQLModel
if TYPE_CHECKING: if TYPE_CHECKING:
from .user import User from .user import User
@@ -16,7 +16,7 @@ class LegacyUserStatistics(SQLModel, table=True):
__tablename__ = "user_statistics" # pyright: ignore[reportAssignmentType] __tablename__ = "user_statistics" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
user_id: int = Field(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
mode: str = Field(max_length=10) # osu, taiko, fruits, mania mode: str = Field(max_length=10) # osu, taiko, fruits, mania
# 基本统计 # 基本统计
@@ -77,7 +77,7 @@ class LegacyOAuthToken(SQLModel, table=True):
__tablename__ = "legacy_oauth_tokens" # pyright: ignore[reportAssignmentType] __tablename__ = "legacy_oauth_tokens" # 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(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
access_token: str = Field(max_length=255, index=True) access_token: str = Field(max_length=255, index=True)
refresh_token: str = Field(max_length=255, index=True) refresh_token: str = Field(max_length=255, index=True)
expires_at: datetime = Field(sa_column=Column(DateTime)) expires_at: datetime = Field(sa_column=Column(DateTime))

View File

@@ -4,7 +4,10 @@ from .user import User
from pydantic import BaseModel from pydantic import BaseModel
from sqlmodel import ( from sqlmodel import (
BigInteger,
Column,
Field, Field,
ForeignKey,
Relationship as SQLRelationship, Relationship as SQLRelationship,
SQLModel, SQLModel,
select, select,
@@ -20,10 +23,22 @@ class RelationshipType(str, Enum):
class Relationship(SQLModel, table=True): class Relationship(SQLModel, table=True):
__tablename__ = "relationship" # pyright: ignore[reportAssignmentType] __tablename__ = "relationship" # pyright: ignore[reportAssignmentType]
user_id: int = Field( user_id: int = Field(
default=None, foreign_key="users.id", primary_key=True, index=True default=None,
sa_column=Column(
BigInteger,
ForeignKey("users.id"),
primary_key=True,
index=True,
),
) )
target_id: int = Field( target_id: int = Field(
default=None, foreign_key="users.id", primary_key=True, index=True default=None,
sa_column=Column(
BigInteger,
ForeignKey("users.id"),
primary_key=True,
index=True,
),
) )
type: RelationshipType = Field(default=RelationshipType.FOLLOW, nullable=False) type: RelationshipType = Field(default=RelationshipType.FOLLOW, nullable=False)
target: "User" = SQLRelationship( target: "User" = SQLRelationship(

View File

@@ -22,6 +22,7 @@ from sqlmodel import (
JSON, JSON,
BigInteger, BigInteger,
Field, Field,
ForeignKey,
Relationship, Relationship,
SQLModel, SQLModel,
col, col,
@@ -69,7 +70,14 @@ class Score(ScoreBase, table=True):
default=None, sa_column=Column(BigInteger, autoincrement=True, primary_key=True) default=None, sa_column=Column(BigInteger, autoincrement=True, primary_key=True)
) )
beatmap_id: int = Field(index=True, foreign_key="beatmaps.id") beatmap_id: int = Field(index=True, foreign_key="beatmaps.id")
user_id: int = Field(foreign_key="users.id", index=True) user_id: int = Field(
default=None,
sa_column=Column(
BigInteger,
ForeignKey("users.id"),
index=True,
),
)
# ScoreStatistics # ScoreStatistics
n300: int = Field(exclude=True) n300: int = Field(exclude=True)
n100: int = Field(exclude=True) n100: int = Field(exclude=True)
@@ -92,6 +100,7 @@ class Score(ScoreBase, table=True):
class ScoreResp(ScoreBase): class ScoreResp(ScoreBase):
id: int id: int
user_id: int
is_perfect_combo: bool = False is_perfect_combo: bool = False
legacy_perfect: bool = False legacy_perfect: bool = False
legacy_total_score: int = 0 # FIXME legacy_total_score: int = 0 # FIXME

View File

@@ -27,12 +27,15 @@ class ScoreToken(ScoreTokenBase, table=True):
id: int | None = Field( id: int | None = Field(
default=None, default=None,
primary_key=True, sa_column=Column(
index=True, BigInteger,
sa_column_kwargs={"autoincrement": True}, primary_key=True,
index=True,
autoincrement=True,
),
) )
user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id"))) user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
beatmap_id: int = Field(sa_column=Column(BigInteger, ForeignKey("beatmaps.id"))) beatmap_id: int = Field(foreign_key="beatmaps.id")
user: "User" = Relationship() user: "User" = Relationship()
beatmap: "Beatmap" = Relationship() beatmap: "Beatmap" = Relationship()

View File

@@ -2,7 +2,7 @@ from datetime import datetime
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
from sqlalchemy import Column, DateTime from sqlalchemy import Column, DateTime
from sqlmodel import Field, Relationship, SQLModel from sqlmodel import BigInteger, Field, ForeignKey, Relationship, SQLModel
if TYPE_CHECKING: if TYPE_CHECKING:
from .user import User from .user import User
@@ -26,7 +26,7 @@ class TeamMember(SQLModel, table=True):
__tablename__ = "team_members" # pyright: ignore[reportAssignmentType] __tablename__ = "team_members" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
user_id: int = Field(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
team_id: int = Field(foreign_key="teams.id") team_id: int = Field(foreign_key="teams.id")
joined_at: datetime = Field( joined_at: datetime = Field(
default_factory=datetime.utcnow, sa_column=Column(DateTime) default_factory=datetime.utcnow, sa_column=Column(DateTime)

View File

@@ -7,7 +7,7 @@ from .team import TeamMember
from sqlalchemy import DECIMAL, JSON, Column, Date, DateTime, Text from sqlalchemy import DECIMAL, JSON, Column, Date, DateTime, Text
from sqlalchemy.dialects.mysql import VARCHAR from sqlalchemy.dialects.mysql import VARCHAR
from sqlmodel import BigInteger, Field, Relationship, SQLModel from sqlmodel import BigInteger, Field, ForeignKey, Relationship, SQLModel
class User(SQLModel, table=True): class User(SQLModel, table=True):
@@ -109,7 +109,14 @@ class User(SQLModel, table=True):
class LazerUserProfile(SQLModel, table=True): class LazerUserProfile(SQLModel, table=True):
__tablename__ = "lazer_user_profiles" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_profiles" # pyright: ignore[reportAssignmentType]
user_id: int = Field(foreign_key="users.id", primary_key=True) user_id: int = Field(
default=None,
sa_column=Column(
BigInteger,
ForeignKey("users.id"),
primary_key=True,
),
)
# 基本状态字段 # 基本状态字段
is_active: bool = Field(default=True) is_active: bool = Field(default=True)
@@ -165,7 +172,7 @@ class LazerUserProfileSections(SQLModel, table=True):
__tablename__ = "lazer_user_profile_sections" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_profile_sections" # 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(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
section_name: str = Field(sa_column=Column(VARCHAR(50))) section_name: str = Field(sa_column=Column(VARCHAR(50)))
display_order: int | None = Field(default=None) display_order: int | None = Field(default=None)
@@ -182,7 +189,14 @@ class LazerUserProfileSections(SQLModel, table=True):
class LazerUserCountry(SQLModel, table=True): class LazerUserCountry(SQLModel, table=True):
__tablename__ = "lazer_user_countries" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_countries" # pyright: ignore[reportAssignmentType]
user_id: int = Field(foreign_key="users.id", primary_key=True) user_id: int = Field(
default=None,
sa_column=Column(
BigInteger,
ForeignKey("users.id"),
primary_key=True,
),
)
code: str = Field(max_length=2) code: str = Field(max_length=2)
name: str = Field(max_length=100) name: str = Field(max_length=100)
@@ -197,7 +211,14 @@ class LazerUserCountry(SQLModel, table=True):
class LazerUserKudosu(SQLModel, table=True): class LazerUserKudosu(SQLModel, table=True):
__tablename__ = "lazer_user_kudosu" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_kudosu" # pyright: ignore[reportAssignmentType]
user_id: int = Field(foreign_key="users.id", primary_key=True) user_id: int = Field(
default=None,
sa_column=Column(
BigInteger,
ForeignKey("users.id"),
primary_key=True,
),
)
available: int = Field(default=0) available: int = Field(default=0)
total: int = Field(default=0) total: int = Field(default=0)
@@ -212,7 +233,14 @@ class LazerUserKudosu(SQLModel, table=True):
class LazerUserCounts(SQLModel, table=True): class LazerUserCounts(SQLModel, table=True):
__tablename__ = "lazer_user_counts" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_counts" # pyright: ignore[reportAssignmentType]
user_id: int = Field(foreign_key="users.id", primary_key=True) user_id: int = Field(
default=None,
sa_column=Column(
BigInteger,
ForeignKey("users.id"),
primary_key=True,
),
)
# 统计计数字段 # 统计计数字段
beatmap_playcounts_count: int = Field(default=0) beatmap_playcounts_count: int = Field(default=0)
@@ -247,7 +275,14 @@ class LazerUserCounts(SQLModel, table=True):
class LazerUserStatistics(SQLModel, table=True): class LazerUserStatistics(SQLModel, table=True):
__tablename__ = "lazer_user_statistics" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_statistics" # pyright: ignore[reportAssignmentType]
user_id: int = Field(foreign_key="users.id", primary_key=True) user_id: int = Field(
default=None,
sa_column=Column(
BigInteger,
ForeignKey("users.id"),
primary_key=True,
),
)
mode: str = Field(default="osu", max_length=10, primary_key=True) mode: str = Field(default="osu", max_length=10, primary_key=True)
# 基本命中统计 # 基本命中统计
@@ -308,7 +343,7 @@ class LazerUserBanners(SQLModel, table=True):
__tablename__ = "lazer_user_tournament_banners" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_tournament_banners" # 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(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
tournament_id: int tournament_id: int
image_url: str = Field(sa_column=Column(VARCHAR(500))) image_url: str = Field(sa_column=Column(VARCHAR(500)))
is_active: bool | None = Field(default=None) is_active: bool | None = Field(default=None)
@@ -321,7 +356,7 @@ class LazerUserAchievement(SQLModel, table=True):
__tablename__ = "lazer_user_achievements" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_achievements" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
user_id: int = Field(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
achievement_id: int achievement_id: int
achieved_at: datetime = Field( achieved_at: datetime = Field(
default_factory=datetime.utcnow, sa_column=Column(DateTime) default_factory=datetime.utcnow, sa_column=Column(DateTime)
@@ -334,7 +369,7 @@ class LazerUserBadge(SQLModel, table=True):
__tablename__ = "lazer_user_badges" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_badges" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
user_id: int = Field(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
badge_id: int badge_id: int
awarded_at: datetime | None = Field(default=None, sa_column=Column(DateTime)) awarded_at: datetime | None = Field(default=None, sa_column=Column(DateTime))
description: str | None = Field(default=None, sa_column=Column(Text)) description: str | None = Field(default=None, sa_column=Column(Text))
@@ -355,7 +390,7 @@ class LazerUserMonthlyPlaycounts(SQLModel, table=True):
__tablename__ = "lazer_user_monthly_playcounts" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_monthly_playcounts" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
user_id: int = Field(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
start_date: datetime = Field(sa_column=Column(Date)) start_date: datetime = Field(sa_column=Column(Date))
play_count: int = Field(default=0) play_count: int = Field(default=0)
@@ -373,7 +408,7 @@ class LazerUserPreviousUsername(SQLModel, table=True):
__tablename__ = "lazer_user_previous_usernames" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_previous_usernames" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
user_id: int = Field(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
username: str = Field(max_length=32) username: str = Field(max_length=32)
changed_at: datetime = Field(sa_column=Column(DateTime)) changed_at: datetime = Field(sa_column=Column(DateTime))
@@ -391,7 +426,7 @@ class LazerUserReplaysWatched(SQLModel, table=True):
__tablename__ = "lazer_user_replays_watched" # pyright: ignore[reportAssignmentType] __tablename__ = "lazer_user_replays_watched" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
user_id: int = Field(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
start_date: datetime = Field(sa_column=Column(Date)) start_date: datetime = Field(sa_column=Column(Date))
count: int = Field(default=0) count: int = Field(default=0)
@@ -416,7 +451,9 @@ class DailyChallengeStats(SQLModel, table=True):
__tablename__ = "daily_challenge_stats" # pyright: ignore[reportAssignmentType] __tablename__ = "daily_challenge_stats" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
user_id: int = Field(foreign_key="users.id", unique=True) user_id: int = Field(
sa_column=Column(BigInteger, ForeignKey("users.id"), unique=True)
)
daily_streak_best: int = Field(default=0) daily_streak_best: int = Field(default=0)
daily_streak_current: int = Field(default=0) daily_streak_current: int = Field(default=0)
@@ -437,7 +474,7 @@ class RankHistory(SQLModel, table=True):
__tablename__ = "rank_history" # pyright: ignore[reportAssignmentType] __tablename__ = "rank_history" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
user_id: int = Field(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
mode: str = Field(max_length=10) mode: str = Field(max_length=10)
rank_data: list = Field(sa_column=Column(JSON)) # Array of ranks rank_data: list = Field(sa_column=Column(JSON)) # Array of ranks
date_recorded: datetime = Field( date_recorded: datetime = Field(
@@ -451,7 +488,7 @@ class UserAvatar(SQLModel, table=True):
__tablename__ = "user_avatars" # pyright: ignore[reportAssignmentType] __tablename__ = "user_avatars" # pyright: ignore[reportAssignmentType]
id: int | None = Field(default=None, primary_key=True, index=True) id: int | None = Field(default=None, primary_key=True, index=True)
user_id: int = Field(foreign_key="users.id") user_id: int = Field(sa_column=Column(BigInteger, ForeignKey("users.id")))
filename: str = Field(max_length=255) filename: str = Field(max_length=255)
original_filename: str = Field(max_length=255) original_filename: str = Field(max_length=255)
file_size: int file_size: int

View File

@@ -1,19 +1,14 @@
from __future__ import annotations from __future__ import annotations
from typing import TYPE_CHECKING from app.database.beatmap import BeatmapResp
from ._base import BaseFetcher from ._base import BaseFetcher
from httpx import AsyncClient from httpx import AsyncClient
if TYPE_CHECKING:
from app.database.beatmap import BeatmapResp
class BeatmapFetcher(BaseFetcher): class BeatmapFetcher(BaseFetcher):
async def get_beatmap(self, beatmap_id: int) -> "BeatmapResp": async def get_beatmap(self, beatmap_id: int) -> BeatmapResp:
from app.database.beatmap import BeatmapResp
async with AsyncClient() as client: async with AsyncClient() as client:
response = await client.get( response = await client.get(
f"https://osu.ppy.sh/api/v2/beatmaps/{beatmap_id}", f"https://osu.ppy.sh/api/v2/beatmaps/{beatmap_id}",

View File

@@ -3,7 +3,7 @@ from __future__ import annotations
from enum import Enum from enum import Enum
from typing import Literal, TypedDict from typing import Literal, TypedDict
from .mods import API_MOD_TO_LEGACY, API_MODS, APIMod, init_mods from .mods import API_MODS, APIMod, init_mods
from pydantic import BaseModel, Field, ValidationInfo, field_validator from pydantic import BaseModel, Field, ValidationInfo, field_validator
import rosu_pp_py as rosu import rosu_pp_py as rosu
@@ -109,7 +109,7 @@ class SoloScoreSubmissionInfo(BaseModel):
@field_validator("mods", mode="after") @field_validator("mods", mode="after")
@classmethod @classmethod
def validate_mods(cls, mods: list[APIMod], info: ValidationInfo): def validate_mods(cls, mods: list[APIMod], info: ValidationInfo):
if not API_MOD_TO_LEGACY: if not API_MODS:
init_mods() init_mods()
incompatible_mods = set() incompatible_mods = set()
# check incompatible mods # check incompatible mods
@@ -122,6 +122,7 @@ class SoloScoreSubmissionInfo(BaseModel):
if not setting_mods: if not setting_mods:
raise ValueError(f"Invalid mod: {mod['acronym']}") raise ValueError(f"Invalid mod: {mod['acronym']}")
incompatible_mods.update(setting_mods["IncompatibleMods"]) incompatible_mods.update(setting_mods["IncompatibleMods"])
return mods
class LegacyReplaySoloScoreInfo(TypedDict): class LegacyReplaySoloScoreInfo(TypedDict):

View File

@@ -1,17 +1,22 @@
from __future__ import annotations from __future__ import annotations
import datetime
from app.database import ( from app.database import (
Beatmap, Beatmap,
User as DBUser, User as DBUser,
) )
from app.database.beatmapset import Beatmapset from app.database.beatmapset import Beatmapset
from app.database.score import Score, ScoreResp from app.database.score import Score, ScoreResp
from app.database.score_token import ScoreToken, ScoreTokenResp
from app.database.user import User
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
from app.models.score import INT_TO_MODE, HitResult, Rank, SoloScoreSubmissionInfo
from .api_router import router from .api_router import router
from fastapi import Depends, HTTPException, Query from fastapi import Depends, Form, HTTPException, Query
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy.orm import joinedload from sqlalchemy.orm import joinedload
from sqlmodel import col, select from sqlmodel import col, select
@@ -63,8 +68,8 @@ async def get_beatmap_scores(
).first() ).first()
return BeatmapScores( return BeatmapScores(
scores=[ScoreResp.from_db(score) for score in all_scores], scores=[await ScoreResp.from_db(db, score) for score in all_scores],
userScore=ScoreResp.from_db(user_score) if user_score else None, userScore=await ScoreResp.from_db(db, user_score) if user_score else None,
) )
@@ -115,7 +120,7 @@ async def get_user_beatmap_score(
else: else:
return BeatmapUserScore( return BeatmapUserScore(
position=user_score.position if user_score.position is not None else 0, position=user_score.position if user_score.position is not None else 0,
score=ScoreResp.from_db(user_score), score=await ScoreResp.from_db(db, user_score),
) )
@@ -153,4 +158,113 @@ async def get_user_all_beatmap_scores(
) )
).all() ).all()
return [ScoreResp.from_db(score) for score in all_user_scores] return [await ScoreResp.from_db(db, score) for score in all_user_scores]
@router.post(
"/beatmaps/{beatmap}/solo/scores", tags=["beatmap"], response_model=ScoreTokenResp
)
async def create_solo_score(
beatmap: int,
version_hash: str = Form(""),
beatmap_hash: str = Form(),
ruleset_id: int = Form(..., ge=0, le=3),
current_user: DBUser = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
async with db:
score_token = ScoreToken(
user_id=current_user.id,
beatmap_id=beatmap,
ruleset_id=INT_TO_MODE[ruleset_id],
)
db.add(score_token)
await db.commit()
await db.refresh(score_token)
return ScoreTokenResp.from_db(score_token)
@router.put(
"/beatmaps/{beatmap}/solo/scores/{token}",
tags=["beatmap"],
response_model=ScoreResp,
)
async def submit_solo_score(
beatmap: int,
token: int,
info: SoloScoreSubmissionInfo,
current_user: DBUser = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
):
if not info.passed:
info.rank = Rank.F
async with db:
score_token = (
await db.exec(
select(ScoreToken)
.options(joinedload(ScoreToken.beatmap)) # pyright: ignore[reportArgumentType]
.where(ScoreToken.id == token, ScoreToken.user_id == current_user.id)
)
).first()
if not score_token or score_token.user_id != current_user.id:
raise HTTPException(status_code=404, detail="Score token not found")
if score_token.score_id:
score = (
await db.exec(
select(Score)
.options(joinedload(Score.beatmap)) # pyright: ignore[reportArgumentType]
.where(
Score.id == score_token.score_id,
Score.user_id == current_user.id,
)
)
).first()
if not score:
raise HTTPException(status_code=404, detail="Score not found")
else:
score = Score(
accuracy=info.accuracy,
max_combo=info.max_combo,
# maximum_statistics=info.maximum_statistics,
mods=info.mods,
passed=info.passed,
rank=info.rank,
total_score=info.total_score,
total_score_without_mods=info.total_score_without_mods,
beatmap_id=beatmap,
ended_at=datetime.datetime.now(datetime.UTC),
gamemode=INT_TO_MODE[info.ruleset_id],
started_at=score_token.created_at,
user_id=current_user.id,
preserve=info.passed,
map_md5=score_token.beatmap.checksum,
has_replay=False,
pp=info.pp,
type="solo",
n300=info.statistics.get(HitResult.GREAT, 0),
n100=info.statistics.get(HitResult.OK, 0),
n50=info.statistics.get(HitResult.MEH, 0),
nmiss=info.statistics.get(HitResult.MISS, 0),
ngeki=info.statistics.get(HitResult.PERFECT, 0),
nkatu=info.statistics.get(HitResult.GOOD, 0),
)
db.add(score)
await db.commit()
await db.refresh(score)
score_id = score.id
score_token.score_id = score_id
await db.commit()
score = (
await db.exec(
select(Score)
.options(
joinedload(Score.beatmap) # pyright: ignore[reportArgumentType]
.joinedload(Beatmap.beatmapset) # pyright: ignore[reportArgumentType]
.selectinload(Beatmapset.beatmaps), # pyright: ignore[reportArgumentType]
joinedload(Score.user).joinedload(User.lazer_profile), # pyright: ignore[reportArgumentType]
)
.where(Score.id == score_id)
)
).first()
assert score is not None
return await ScoreResp.from_db(db, score)