fix(solo-score): fix submit solo-score & Bigint
This commit is contained in:
@@ -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)
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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))
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}",
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
Reference in New Issue
Block a user