Merge branch 'main' of https://github.com/GooGuTeam/osu_lazer_api
This commit is contained in:
@@ -1,13 +1,13 @@
|
||||
from .achievement import UserAchievement, UserAchievementResp
|
||||
from .auth import OAuthClient, OAuthToken
|
||||
from .beatmap import (
|
||||
Beatmap as Beatmap,
|
||||
BeatmapResp as BeatmapResp,
|
||||
Beatmap,
|
||||
BeatmapResp,
|
||||
)
|
||||
from .beatmap_playcounts import BeatmapPlaycounts, BeatmapPlaycountsResp
|
||||
from .beatmapset import (
|
||||
Beatmapset as Beatmapset,
|
||||
BeatmapsetResp as BeatmapsetResp,
|
||||
Beatmapset,
|
||||
BeatmapsetResp,
|
||||
)
|
||||
from .best_score import BestScore
|
||||
from .counts import (
|
||||
@@ -30,6 +30,7 @@ from .playlist_attempts import (
|
||||
from .playlist_best_score import PlaylistBestScore
|
||||
from .playlists import Playlist, PlaylistResp
|
||||
from .pp_best_score import PPBestScore
|
||||
from .rank_history import RankHistory, RankHistoryResp, RankTop
|
||||
from .relationship import Relationship, RelationshipResp, RelationshipType
|
||||
from .room import APIUploadedRoom, Room, RoomResp
|
||||
from .room_participated_user import RoomParticipatedUser
|
||||
@@ -58,6 +59,7 @@ __all__ = [
|
||||
"Beatmap",
|
||||
"BeatmapPlaycounts",
|
||||
"BeatmapPlaycountsResp",
|
||||
"BeatmapResp",
|
||||
"Beatmapset",
|
||||
"BeatmapsetResp",
|
||||
"BestScore",
|
||||
@@ -78,6 +80,9 @@ __all__ = [
|
||||
"PlaylistAggregateScore",
|
||||
"PlaylistBestScore",
|
||||
"PlaylistResp",
|
||||
"RankHistory",
|
||||
"RankHistoryResp",
|
||||
"RankTop",
|
||||
"Relationship",
|
||||
"RelationshipResp",
|
||||
"RelationshipType",
|
||||
|
||||
@@ -4,12 +4,13 @@ from typing import TYPE_CHECKING, NotRequired, TypedDict
|
||||
from app.database.events import Event
|
||||
from app.models.model import UTCBaseModel
|
||||
from app.models.score import GameMode
|
||||
from app.models.user import Country, Page, RankHistory
|
||||
from app.models.user import Country, Page
|
||||
|
||||
from .achievement import UserAchievement, UserAchievementResp
|
||||
from .beatmap_playcounts import BeatmapPlaycounts
|
||||
from .counts import CountResp, MonthlyPlaycounts, ReplayWatchedCount
|
||||
from .daily_challenge import DailyChallengeStats, DailyChallengeStatsResp
|
||||
from .rank_history import RankHistory, RankHistoryResp, RankTop
|
||||
from .statistics import UserStatistics, UserStatisticsResp
|
||||
from .team import Team, TeamMember
|
||||
from .user_account_history import UserAccountHistory, UserAccountHistoryResp
|
||||
@@ -152,6 +153,10 @@ class User(AsyncAttrs, UserBase, table=True):
|
||||
favourite_beatmapsets: list["FavouriteBeatmapset"] = Relationship(
|
||||
back_populates="user"
|
||||
)
|
||||
rank_history: list[RankHistory] = Relationship(
|
||||
back_populates="user",
|
||||
)
|
||||
|
||||
events: list["Event"] = Relationship(back_populates="user")
|
||||
email: str = Field(max_length=254, unique=True, index=True, exclude=True)
|
||||
priv: int = Field(default=1, exclude=True)
|
||||
@@ -191,8 +196,8 @@ class UserResp(UserBase):
|
||||
monthly_playcounts: list[CountResp] = Field(default_factory=list)
|
||||
replay_watched_counts: list[CountResp] = Field(default_factory=list)
|
||||
unread_pm_count: int = 0 # TODO
|
||||
rank_history: RankHistory | None = None # TODO
|
||||
rank_highest: RankHighest | None = None # TODO
|
||||
rank_history: RankHistoryResp | None = None
|
||||
rank_highest: RankHighest | None = None
|
||||
statistics: UserStatisticsResp | None = None
|
||||
statistics_rulesets: dict[str, UserStatisticsResp] | None = None
|
||||
user_achievements: list[UserAchievementResp] = Field(default_factory=list)
|
||||
@@ -290,14 +295,18 @@ class UserResp(UserBase):
|
||||
current_stattistics = i
|
||||
break
|
||||
u.statistics = (
|
||||
UserStatisticsResp.from_db(current_stattistics)
|
||||
await UserStatisticsResp.from_db(
|
||||
current_stattistics, session, obj.country_code
|
||||
)
|
||||
if current_stattistics
|
||||
else None
|
||||
)
|
||||
|
||||
if "statistics_rulesets" in include:
|
||||
u.statistics_rulesets = {
|
||||
i.mode.value: UserStatisticsResp.from_db(i)
|
||||
i.mode.value: await UserStatisticsResp.from_db(
|
||||
i, session, obj.country_code
|
||||
)
|
||||
for i in await obj.awaitable_attrs.statistics
|
||||
}
|
||||
|
||||
@@ -318,6 +327,27 @@ class UserResp(UserBase):
|
||||
UserAchievementResp.from_db(ua)
|
||||
for ua in await obj.awaitable_attrs.achievement
|
||||
]
|
||||
if "rank_history" in include:
|
||||
rank_history = await RankHistoryResp.from_db(session, obj.id, ruleset)
|
||||
if len(rank_history.data) != 0:
|
||||
u.rank_history = rank_history
|
||||
|
||||
rank_top = (
|
||||
await session.exec(
|
||||
select(RankTop).where(
|
||||
RankTop.user_id == obj.id, RankTop.mode == ruleset
|
||||
)
|
||||
)
|
||||
).first()
|
||||
if rank_top:
|
||||
u.rank_highest = (
|
||||
RankHighest(
|
||||
rank=rank_top.rank,
|
||||
updated_at=datetime.combine(rank_top.date, datetime.min.time()),
|
||||
)
|
||||
if rank_top
|
||||
else None
|
||||
)
|
||||
|
||||
u.favourite_beatmapset_count = (
|
||||
await session.exec(
|
||||
@@ -384,6 +414,7 @@ ALL_INCLUDED = [
|
||||
"achievements",
|
||||
"monthly_playcounts",
|
||||
"replays_watched_counts",
|
||||
"rank_history",
|
||||
]
|
||||
|
||||
|
||||
@@ -395,6 +426,7 @@ SEARCH_INCLUDED = [
|
||||
"achievements",
|
||||
"monthly_playcounts",
|
||||
"replays_watched_counts",
|
||||
"rank_history",
|
||||
]
|
||||
|
||||
BASE_INCLUDES = [
|
||||
|
||||
78
app/database/rank_history.py
Normal file
78
app/database/rank_history.py
Normal file
@@ -0,0 +1,78 @@
|
||||
from datetime import (
|
||||
UTC,
|
||||
date as dt,
|
||||
datetime,
|
||||
)
|
||||
from typing import TYPE_CHECKING, Optional
|
||||
|
||||
from app.models.score import GameMode
|
||||
|
||||
from pydantic import BaseModel
|
||||
from sqlmodel import (
|
||||
BigInteger,
|
||||
Column,
|
||||
Date,
|
||||
Field,
|
||||
ForeignKey,
|
||||
Relationship,
|
||||
SQLModel,
|
||||
col,
|
||||
select,
|
||||
)
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .lazer_user import User
|
||||
|
||||
|
||||
class RankHistory(SQLModel, table=True):
|
||||
__tablename__ = "rank_history" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, sa_column=Column(BigInteger, primary_key=True))
|
||||
user_id: int = Field(
|
||||
sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), index=True)
|
||||
)
|
||||
mode: GameMode
|
||||
rank: int
|
||||
date: dt = Field(
|
||||
default_factory=lambda: datetime.now(UTC).date(),
|
||||
sa_column=Column(Date, index=True),
|
||||
)
|
||||
|
||||
user: Optional["User"] = Relationship(back_populates="rank_history")
|
||||
|
||||
|
||||
class RankTop(SQLModel, table=True):
|
||||
__tablename__ = "rank_top" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, sa_column=Column(BigInteger, primary_key=True))
|
||||
user_id: int = Field(
|
||||
sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), index=True)
|
||||
)
|
||||
mode: GameMode
|
||||
rank: int
|
||||
date: dt = Field(
|
||||
default_factory=lambda: datetime.now(UTC).date(),
|
||||
sa_column=Column(Date, index=True),
|
||||
)
|
||||
|
||||
|
||||
class RankHistoryResp(BaseModel):
|
||||
mode: GameMode
|
||||
data: list[int]
|
||||
|
||||
@classmethod
|
||||
async def from_db(
|
||||
cls, session: AsyncSession, user_id: int, mode: GameMode
|
||||
) -> "RankHistoryResp":
|
||||
results = (
|
||||
await session.exec(
|
||||
select(RankHistory)
|
||||
.where(RankHistory.user_id == user_id, RankHistory.mode == mode)
|
||||
.order_by(col(RankHistory.date).desc())
|
||||
.limit(90)
|
||||
)
|
||||
).all()
|
||||
data = [result.rank for result in results]
|
||||
data.reverse()
|
||||
return cls(mode=mode, data=data)
|
||||
@@ -1,7 +1,10 @@
|
||||
from datetime import UTC, datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from app.models.score import GameMode
|
||||
|
||||
from .rank_history import RankHistory
|
||||
|
||||
from sqlmodel import (
|
||||
BigInteger,
|
||||
Column,
|
||||
@@ -9,23 +12,24 @@ from sqlmodel import (
|
||||
ForeignKey,
|
||||
Relationship,
|
||||
SQLModel,
|
||||
col,
|
||||
func,
|
||||
select,
|
||||
)
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .lazer_user import User
|
||||
|
||||
|
||||
class UserStatisticsBase(SQLModel):
|
||||
mode: GameMode
|
||||
mode: GameMode = Field(index=True)
|
||||
count_100: int = Field(default=0, sa_column=Column(BigInteger))
|
||||
count_300: int = Field(default=0, sa_column=Column(BigInteger))
|
||||
count_50: int = Field(default=0, sa_column=Column(BigInteger))
|
||||
count_miss: int = Field(default=0, sa_column=Column(BigInteger))
|
||||
|
||||
global_rank: int | None = Field(default=None)
|
||||
country_rank: int | None = Field(default=None)
|
||||
|
||||
pp: float = Field(default=0.0)
|
||||
pp: float = Field(default=0.0, index=True)
|
||||
ranked_score: int = Field(default=0)
|
||||
hit_accuracy: float = Field(default=0.00)
|
||||
total_score: int = Field(default=0, sa_column=Column(BigInteger))
|
||||
@@ -62,6 +66,8 @@ class UserStatistics(UserStatisticsBase, table=True):
|
||||
|
||||
|
||||
class UserStatisticsResp(UserStatisticsBase):
|
||||
global_rank: int | None = Field(default=None)
|
||||
country_rank: int | None = Field(default=None)
|
||||
grade_counts: dict[str, int] = Field(
|
||||
default_factory=lambda: {
|
||||
"ss": 0,
|
||||
@@ -79,7 +85,9 @@ class UserStatisticsResp(UserStatisticsBase):
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def from_db(cls, obj: UserStatistics) -> "UserStatisticsResp":
|
||||
async def from_db(
|
||||
cls, obj: UserStatistics, session: AsyncSession, user_country: str
|
||||
) -> "UserStatisticsResp":
|
||||
s = cls.model_validate(obj)
|
||||
s.grade_counts = {
|
||||
"ss": obj.grade_ss,
|
||||
@@ -92,4 +100,56 @@ class UserStatisticsResp(UserStatisticsBase):
|
||||
"current": obj.level_current,
|
||||
"progress": obj.level_progress,
|
||||
}
|
||||
s.global_rank = await get_rank(session, obj)
|
||||
s.country_rank = await get_rank(session, obj, user_country)
|
||||
return s
|
||||
|
||||
|
||||
async def get_rank(
|
||||
session: AsyncSession, statistics: UserStatistics, country: str | None = None
|
||||
) -> int | None:
|
||||
from .lazer_user import User
|
||||
|
||||
query = select(
|
||||
UserStatistics.user_id,
|
||||
func.row_number().over(order_by=col(UserStatistics.pp).desc()).label("rank"),
|
||||
).where(
|
||||
UserStatistics.mode == statistics.mode,
|
||||
UserStatistics.pp > 0,
|
||||
col(UserStatistics.is_ranked).is_(True),
|
||||
)
|
||||
|
||||
if country is not None:
|
||||
query = query.join(User).where(User.country_code == country)
|
||||
|
||||
subq = query.subquery()
|
||||
|
||||
result = await session.exec(
|
||||
select(subq.c.rank).where(subq.c.user_id == statistics.user_id)
|
||||
)
|
||||
|
||||
rank = result.first()
|
||||
if rank is None:
|
||||
return None
|
||||
|
||||
today = datetime.now(UTC).date()
|
||||
rank_history = (
|
||||
await session.exec(
|
||||
select(RankHistory).where(
|
||||
RankHistory.user_id == statistics.user_id,
|
||||
RankHistory.mode == statistics.mode,
|
||||
RankHistory.date == today,
|
||||
)
|
||||
)
|
||||
).first()
|
||||
if rank_history is None:
|
||||
rank_history = RankHistory(
|
||||
user_id=statistics.user_id,
|
||||
mode=statistics.mode,
|
||||
date=today,
|
||||
rank=rank,
|
||||
)
|
||||
session.add(rank_history)
|
||||
else:
|
||||
rank_history.rank = rank
|
||||
return rank
|
||||
|
||||
Reference in New Issue
Block a user