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 .achievement import UserAchievement, UserAchievementResp
|
||||||
from .auth import OAuthClient, OAuthToken
|
from .auth import OAuthClient, OAuthToken
|
||||||
from .beatmap import (
|
from .beatmap import (
|
||||||
Beatmap as Beatmap,
|
Beatmap,
|
||||||
BeatmapResp as BeatmapResp,
|
BeatmapResp,
|
||||||
)
|
)
|
||||||
from .beatmap_playcounts import BeatmapPlaycounts, BeatmapPlaycountsResp
|
from .beatmap_playcounts import BeatmapPlaycounts, BeatmapPlaycountsResp
|
||||||
from .beatmapset import (
|
from .beatmapset import (
|
||||||
Beatmapset as Beatmapset,
|
Beatmapset,
|
||||||
BeatmapsetResp as BeatmapsetResp,
|
BeatmapsetResp,
|
||||||
)
|
)
|
||||||
from .best_score import BestScore
|
from .best_score import BestScore
|
||||||
from .counts import (
|
from .counts import (
|
||||||
@@ -30,6 +30,7 @@ from .playlist_attempts import (
|
|||||||
from .playlist_best_score import PlaylistBestScore
|
from .playlist_best_score import PlaylistBestScore
|
||||||
from .playlists import Playlist, PlaylistResp
|
from .playlists import Playlist, PlaylistResp
|
||||||
from .pp_best_score import PPBestScore
|
from .pp_best_score import PPBestScore
|
||||||
|
from .rank_history import RankHistory, RankHistoryResp, RankTop
|
||||||
from .relationship import Relationship, RelationshipResp, RelationshipType
|
from .relationship import Relationship, RelationshipResp, RelationshipType
|
||||||
from .room import APIUploadedRoom, Room, RoomResp
|
from .room import APIUploadedRoom, Room, RoomResp
|
||||||
from .room_participated_user import RoomParticipatedUser
|
from .room_participated_user import RoomParticipatedUser
|
||||||
@@ -58,6 +59,7 @@ __all__ = [
|
|||||||
"Beatmap",
|
"Beatmap",
|
||||||
"BeatmapPlaycounts",
|
"BeatmapPlaycounts",
|
||||||
"BeatmapPlaycountsResp",
|
"BeatmapPlaycountsResp",
|
||||||
|
"BeatmapResp",
|
||||||
"Beatmapset",
|
"Beatmapset",
|
||||||
"BeatmapsetResp",
|
"BeatmapsetResp",
|
||||||
"BestScore",
|
"BestScore",
|
||||||
@@ -78,6 +80,9 @@ __all__ = [
|
|||||||
"PlaylistAggregateScore",
|
"PlaylistAggregateScore",
|
||||||
"PlaylistBestScore",
|
"PlaylistBestScore",
|
||||||
"PlaylistResp",
|
"PlaylistResp",
|
||||||
|
"RankHistory",
|
||||||
|
"RankHistoryResp",
|
||||||
|
"RankTop",
|
||||||
"Relationship",
|
"Relationship",
|
||||||
"RelationshipResp",
|
"RelationshipResp",
|
||||||
"RelationshipType",
|
"RelationshipType",
|
||||||
|
|||||||
@@ -4,12 +4,13 @@ from typing import TYPE_CHECKING, NotRequired, TypedDict
|
|||||||
from app.database.events import Event
|
from app.database.events import Event
|
||||||
from app.models.model import UTCBaseModel
|
from app.models.model import UTCBaseModel
|
||||||
from app.models.score import GameMode
|
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 .achievement import UserAchievement, UserAchievementResp
|
||||||
from .beatmap_playcounts import BeatmapPlaycounts
|
from .beatmap_playcounts import BeatmapPlaycounts
|
||||||
from .counts import CountResp, MonthlyPlaycounts, ReplayWatchedCount
|
from .counts import CountResp, MonthlyPlaycounts, ReplayWatchedCount
|
||||||
from .daily_challenge import DailyChallengeStats, DailyChallengeStatsResp
|
from .daily_challenge import DailyChallengeStats, DailyChallengeStatsResp
|
||||||
|
from .rank_history import RankHistory, RankHistoryResp, RankTop
|
||||||
from .statistics import UserStatistics, UserStatisticsResp
|
from .statistics import UserStatistics, UserStatisticsResp
|
||||||
from .team import Team, TeamMember
|
from .team import Team, TeamMember
|
||||||
from .user_account_history import UserAccountHistory, UserAccountHistoryResp
|
from .user_account_history import UserAccountHistory, UserAccountHistoryResp
|
||||||
@@ -152,6 +153,10 @@ class User(AsyncAttrs, UserBase, table=True):
|
|||||||
favourite_beatmapsets: list["FavouriteBeatmapset"] = Relationship(
|
favourite_beatmapsets: list["FavouriteBeatmapset"] = Relationship(
|
||||||
back_populates="user"
|
back_populates="user"
|
||||||
)
|
)
|
||||||
|
rank_history: list[RankHistory] = Relationship(
|
||||||
|
back_populates="user",
|
||||||
|
)
|
||||||
|
|
||||||
events: list["Event"] = Relationship(back_populates="user")
|
events: list["Event"] = Relationship(back_populates="user")
|
||||||
email: str = Field(max_length=254, unique=True, index=True, exclude=True)
|
email: str = Field(max_length=254, unique=True, index=True, exclude=True)
|
||||||
priv: int = Field(default=1, exclude=True)
|
priv: int = Field(default=1, exclude=True)
|
||||||
@@ -191,8 +196,8 @@ class UserResp(UserBase):
|
|||||||
monthly_playcounts: list[CountResp] = Field(default_factory=list)
|
monthly_playcounts: list[CountResp] = Field(default_factory=list)
|
||||||
replay_watched_counts: list[CountResp] = Field(default_factory=list)
|
replay_watched_counts: list[CountResp] = Field(default_factory=list)
|
||||||
unread_pm_count: int = 0 # TODO
|
unread_pm_count: int = 0 # TODO
|
||||||
rank_history: RankHistory | None = None # TODO
|
rank_history: RankHistoryResp | None = None
|
||||||
rank_highest: RankHighest | None = None # TODO
|
rank_highest: RankHighest | None = None
|
||||||
statistics: UserStatisticsResp | None = None
|
statistics: UserStatisticsResp | None = None
|
||||||
statistics_rulesets: dict[str, UserStatisticsResp] | None = None
|
statistics_rulesets: dict[str, UserStatisticsResp] | None = None
|
||||||
user_achievements: list[UserAchievementResp] = Field(default_factory=list)
|
user_achievements: list[UserAchievementResp] = Field(default_factory=list)
|
||||||
@@ -290,14 +295,18 @@ class UserResp(UserBase):
|
|||||||
current_stattistics = i
|
current_stattistics = i
|
||||||
break
|
break
|
||||||
u.statistics = (
|
u.statistics = (
|
||||||
UserStatisticsResp.from_db(current_stattistics)
|
await UserStatisticsResp.from_db(
|
||||||
|
current_stattistics, session, obj.country_code
|
||||||
|
)
|
||||||
if current_stattistics
|
if current_stattistics
|
||||||
else None
|
else None
|
||||||
)
|
)
|
||||||
|
|
||||||
if "statistics_rulesets" in include:
|
if "statistics_rulesets" in include:
|
||||||
u.statistics_rulesets = {
|
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
|
for i in await obj.awaitable_attrs.statistics
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -318,6 +327,27 @@ class UserResp(UserBase):
|
|||||||
UserAchievementResp.from_db(ua)
|
UserAchievementResp.from_db(ua)
|
||||||
for ua in await obj.awaitable_attrs.achievement
|
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 = (
|
u.favourite_beatmapset_count = (
|
||||||
await session.exec(
|
await session.exec(
|
||||||
@@ -384,6 +414,7 @@ ALL_INCLUDED = [
|
|||||||
"achievements",
|
"achievements",
|
||||||
"monthly_playcounts",
|
"monthly_playcounts",
|
||||||
"replays_watched_counts",
|
"replays_watched_counts",
|
||||||
|
"rank_history",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -395,6 +426,7 @@ SEARCH_INCLUDED = [
|
|||||||
"achievements",
|
"achievements",
|
||||||
"monthly_playcounts",
|
"monthly_playcounts",
|
||||||
"replays_watched_counts",
|
"replays_watched_counts",
|
||||||
|
"rank_history",
|
||||||
]
|
]
|
||||||
|
|
||||||
BASE_INCLUDES = [
|
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 typing import TYPE_CHECKING
|
||||||
|
|
||||||
from app.models.score import GameMode
|
from app.models.score import GameMode
|
||||||
|
|
||||||
|
from .rank_history import RankHistory
|
||||||
|
|
||||||
from sqlmodel import (
|
from sqlmodel import (
|
||||||
BigInteger,
|
BigInteger,
|
||||||
Column,
|
Column,
|
||||||
@@ -9,23 +12,24 @@ from sqlmodel import (
|
|||||||
ForeignKey,
|
ForeignKey,
|
||||||
Relationship,
|
Relationship,
|
||||||
SQLModel,
|
SQLModel,
|
||||||
|
col,
|
||||||
|
func,
|
||||||
|
select,
|
||||||
)
|
)
|
||||||
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from .lazer_user import User
|
from .lazer_user import User
|
||||||
|
|
||||||
|
|
||||||
class UserStatisticsBase(SQLModel):
|
class UserStatisticsBase(SQLModel):
|
||||||
mode: GameMode
|
mode: GameMode = Field(index=True)
|
||||||
count_100: int = Field(default=0, sa_column=Column(BigInteger))
|
count_100: int = Field(default=0, sa_column=Column(BigInteger))
|
||||||
count_300: 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_50: int = Field(default=0, sa_column=Column(BigInteger))
|
||||||
count_miss: 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)
|
pp: float = Field(default=0.0, index=True)
|
||||||
country_rank: int | None = Field(default=None)
|
|
||||||
|
|
||||||
pp: float = Field(default=0.0)
|
|
||||||
ranked_score: int = Field(default=0)
|
ranked_score: int = Field(default=0)
|
||||||
hit_accuracy: float = Field(default=0.00)
|
hit_accuracy: float = Field(default=0.00)
|
||||||
total_score: int = Field(default=0, sa_column=Column(BigInteger))
|
total_score: int = Field(default=0, sa_column=Column(BigInteger))
|
||||||
@@ -62,6 +66,8 @@ class UserStatistics(UserStatisticsBase, table=True):
|
|||||||
|
|
||||||
|
|
||||||
class UserStatisticsResp(UserStatisticsBase):
|
class UserStatisticsResp(UserStatisticsBase):
|
||||||
|
global_rank: int | None = Field(default=None)
|
||||||
|
country_rank: int | None = Field(default=None)
|
||||||
grade_counts: dict[str, int] = Field(
|
grade_counts: dict[str, int] = Field(
|
||||||
default_factory=lambda: {
|
default_factory=lambda: {
|
||||||
"ss": 0,
|
"ss": 0,
|
||||||
@@ -79,7 +85,9 @@ class UserStatisticsResp(UserStatisticsBase):
|
|||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@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 = cls.model_validate(obj)
|
||||||
s.grade_counts = {
|
s.grade_counts = {
|
||||||
"ss": obj.grade_ss,
|
"ss": obj.grade_ss,
|
||||||
@@ -92,4 +100,56 @@ class UserStatisticsResp(UserStatisticsBase):
|
|||||||
"current": obj.level_current,
|
"current": obj.level_current,
|
||||||
"progress": obj.level_progress,
|
"progress": obj.level_progress,
|
||||||
}
|
}
|
||||||
|
s.global_rank = await get_rank(session, obj)
|
||||||
|
s.country_rank = await get_rank(session, obj, user_country)
|
||||||
return s
|
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
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ class BatchUserResponse(BaseModel):
|
|||||||
|
|
||||||
|
|
||||||
@router.get(
|
@router.get(
|
||||||
"/users",
|
"/users/",
|
||||||
response_model=BatchUserResponse,
|
response_model=BatchUserResponse,
|
||||||
name="批量获取用户信息",
|
name="批量获取用户信息",
|
||||||
description="通过用户 ID 列表批量获取用户信息。",
|
description="通过用户 ID 列表批量获取用户信息。",
|
||||||
|
|||||||
87
app/service/calculate_all_user_rank.py
Normal file
87
app/service/calculate_all_user_rank.py
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from datetime import UTC, datetime, timedelta
|
||||||
|
|
||||||
|
from app.database import RankHistory, UserStatistics
|
||||||
|
from app.database.rank_history import RankTop
|
||||||
|
from app.dependencies.database import engine
|
||||||
|
from app.dependencies.scheduler import get_scheduler
|
||||||
|
from app.models.score import GameMode
|
||||||
|
|
||||||
|
from sqlmodel import col, exists, select, update
|
||||||
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
|
|
||||||
|
@get_scheduler().scheduled_job(
|
||||||
|
"cron", hour=0, minute=0, second=0, id="calculate_user_rank"
|
||||||
|
)
|
||||||
|
async def calculate_user_rank(is_today: bool = False):
|
||||||
|
today = datetime.now(UTC).date()
|
||||||
|
target_date = today if is_today else today - timedelta(days=1)
|
||||||
|
async with AsyncSession(engine) as session:
|
||||||
|
for gamemode in GameMode:
|
||||||
|
users = await session.exec(
|
||||||
|
select(UserStatistics)
|
||||||
|
.where(
|
||||||
|
UserStatistics.mode == gamemode,
|
||||||
|
UserStatistics.pp > 0,
|
||||||
|
col(UserStatistics.is_ranked).is_(True),
|
||||||
|
)
|
||||||
|
.order_by(
|
||||||
|
col(UserStatistics.pp).desc(),
|
||||||
|
col(UserStatistics.total_score).desc(),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
rank = 1
|
||||||
|
for user in users:
|
||||||
|
is_exist = (
|
||||||
|
await session.exec(
|
||||||
|
select(exists()).where(
|
||||||
|
RankHistory.user_id == user.user_id,
|
||||||
|
RankHistory.mode == gamemode,
|
||||||
|
RankHistory.date == target_date,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
if not is_exist:
|
||||||
|
rank_history = RankHistory(
|
||||||
|
user_id=user.user_id,
|
||||||
|
mode=gamemode,
|
||||||
|
rank=rank,
|
||||||
|
date=today,
|
||||||
|
)
|
||||||
|
session.add(rank_history)
|
||||||
|
else:
|
||||||
|
await session.execute(
|
||||||
|
update(RankHistory)
|
||||||
|
.where(
|
||||||
|
col(RankHistory.user_id) == user.user_id,
|
||||||
|
col(RankHistory.mode) == gamemode,
|
||||||
|
col(RankHistory.date) == target_date,
|
||||||
|
)
|
||||||
|
.values(rank=rank)
|
||||||
|
)
|
||||||
|
|
||||||
|
rank_top = (
|
||||||
|
await session.exec(
|
||||||
|
select(RankTop).where(
|
||||||
|
RankTop.user_id == user.user_id,
|
||||||
|
RankTop.mode == gamemode,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
if not rank_top:
|
||||||
|
rank_top = RankTop(
|
||||||
|
user_id=user.user_id,
|
||||||
|
mode=gamemode,
|
||||||
|
rank=rank,
|
||||||
|
date=today,
|
||||||
|
)
|
||||||
|
session.add(rank_top)
|
||||||
|
else:
|
||||||
|
if rank_top.rank > rank:
|
||||||
|
rank_top.rank = rank
|
||||||
|
rank_top.date = today
|
||||||
|
|
||||||
|
rank += 1
|
||||||
|
await session.commit()
|
||||||
@@ -848,6 +848,8 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
played_user,
|
played_user,
|
||||||
ex=3600,
|
ex=3600,
|
||||||
)
|
)
|
||||||
|
else:
|
||||||
|
await room.queue.finish_current_item()
|
||||||
|
|
||||||
async def send_match_event(
|
async def send_match_event(
|
||||||
self, room: ServerMultiplayerRoom, event: MatchServerEvent
|
self, room: ServerMultiplayerRoom, event: MatchServerEvent
|
||||||
|
|||||||
2
main.py
2
main.py
@@ -17,6 +17,7 @@ from app.router import (
|
|||||||
signalr_router,
|
signalr_router,
|
||||||
)
|
)
|
||||||
from app.router.redirect import redirect_router
|
from app.router.redirect import redirect_router
|
||||||
|
from app.service.calculate_all_user_rank import calculate_user_rank
|
||||||
from app.service.daily_challenge import daily_challenge_job
|
from app.service.daily_challenge import daily_challenge_job
|
||||||
from app.service.osu_rx_statistics import create_rx_statistics
|
from app.service.osu_rx_statistics import create_rx_statistics
|
||||||
|
|
||||||
@@ -29,6 +30,7 @@ from fastapi.responses import JSONResponse
|
|||||||
async def lifespan(app: FastAPI):
|
async def lifespan(app: FastAPI):
|
||||||
# on startup
|
# on startup
|
||||||
await create_rx_statistics()
|
await create_rx_statistics()
|
||||||
|
await calculate_user_rank(True)
|
||||||
await get_fetcher() # 初始化 fetcher
|
await get_fetcher() # 初始化 fetcher
|
||||||
init_scheduler()
|
init_scheduler()
|
||||||
await daily_challenge_job()
|
await daily_challenge_job()
|
||||||
|
|||||||
113
migrations/versions/b6a304d96a2d_user_support_rank.py
Normal file
113
migrations/versions/b6a304d96a2d_user_support_rank.py
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
"""user: support rank
|
||||||
|
|
||||||
|
Revision ID: b6a304d96a2d
|
||||||
|
Revises: 749bb2c2c33a
|
||||||
|
Create Date: 2025-08-12 13:31:45.315844
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
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 = "b6a304d96a2d"
|
||||||
|
down_revision: str | Sequence[str] | None = "749bb2c2c33a"
|
||||||
|
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.create_table(
|
||||||
|
"rank_history",
|
||||||
|
sa.Column("id", sa.BigInteger(), nullable=False),
|
||||||
|
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"mode",
|
||||||
|
sa.Enum(
|
||||||
|
"OSU", "TAIKO", "FRUITS", "MANIA", "OSURX", "OSUAP", name="gamemode"
|
||||||
|
),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
sa.Column("rank", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("date", sa.Date(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["user_id"],
|
||||||
|
["lazer_users.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_rank_history_date"), "rank_history", ["date"], unique=False
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_rank_history_user_id"), "rank_history", ["user_id"], unique=False
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"rank_top",
|
||||||
|
sa.Column("id", sa.BigInteger(), nullable=False),
|
||||||
|
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column(
|
||||||
|
"mode",
|
||||||
|
sa.Enum(
|
||||||
|
"OSU", "TAIKO", "FRUITS", "MANIA", "OSURX", "OSUAP", name="gamemode"
|
||||||
|
),
|
||||||
|
nullable=False,
|
||||||
|
),
|
||||||
|
sa.Column("rank", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("date", sa.Date(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["user_id"],
|
||||||
|
["lazer_users.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("id"),
|
||||||
|
)
|
||||||
|
op.create_index(op.f("ix_rank_top_date"), "rank_top", ["date"], unique=False)
|
||||||
|
op.create_index(op.f("ix_rank_top_user_id"), "rank_top", ["user_id"], unique=False)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_lazer_user_statistics_mode"),
|
||||||
|
"lazer_user_statistics",
|
||||||
|
["mode"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_lazer_user_statistics_pp"),
|
||||||
|
"lazer_user_statistics",
|
||||||
|
["pp"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.drop_column("lazer_user_statistics", "country_rank")
|
||||||
|
op.drop_column("lazer_user_statistics", "global_rank")
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_oauth_clients_name"), "oauth_clients", ["name"], unique=False
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(op.f("ix_oauth_clients_name"), table_name="oauth_clients")
|
||||||
|
op.add_column(
|
||||||
|
"lazer_user_statistics",
|
||||||
|
sa.Column("global_rank", mysql.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.add_column(
|
||||||
|
"lazer_user_statistics",
|
||||||
|
sa.Column("country_rank", mysql.INTEGER(), autoincrement=False, nullable=True),
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_lazer_user_statistics_pp"), table_name="lazer_user_statistics"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_lazer_user_statistics_mode"), table_name="lazer_user_statistics"
|
||||||
|
)
|
||||||
|
op.drop_table("rank_top")
|
||||||
|
op.drop_table("rank_history")
|
||||||
|
# ### end Alembic commands ###
|
||||||
Reference in New Issue
Block a user