feat(multiplayer): support leaderboard
This commit is contained in:
@@ -16,12 +16,15 @@ from .lazer_user import (
|
|||||||
UserResp,
|
UserResp,
|
||||||
)
|
)
|
||||||
from .playlist_attempts import ItemAttemptsCount
|
from .playlist_attempts import ItemAttemptsCount
|
||||||
|
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 .relationship import Relationship, RelationshipResp, RelationshipType
|
from .relationship import Relationship, RelationshipResp, RelationshipType
|
||||||
from .room import Room, RoomResp
|
from .room import Room, RoomResp
|
||||||
from .score import (
|
from .score import (
|
||||||
|
MultiplayerScores,
|
||||||
Score,
|
Score,
|
||||||
|
ScoreAround,
|
||||||
ScoreBase,
|
ScoreBase,
|
||||||
ScoreResp,
|
ScoreResp,
|
||||||
ScoreStatistics,
|
ScoreStatistics,
|
||||||
@@ -47,9 +50,11 @@ __all__ = [
|
|||||||
"DailyChallengeStatsResp",
|
"DailyChallengeStatsResp",
|
||||||
"FavouriteBeatmapset",
|
"FavouriteBeatmapset",
|
||||||
"ItemAttemptsCount",
|
"ItemAttemptsCount",
|
||||||
|
"MultiplayerScores",
|
||||||
"OAuthToken",
|
"OAuthToken",
|
||||||
"PPBestScore",
|
"PPBestScore",
|
||||||
"Playlist",
|
"Playlist",
|
||||||
|
"PlaylistBestScore",
|
||||||
"PlaylistResp",
|
"PlaylistResp",
|
||||||
"Relationship",
|
"Relationship",
|
||||||
"RelationshipResp",
|
"RelationshipResp",
|
||||||
@@ -57,6 +62,7 @@ __all__ = [
|
|||||||
"Room",
|
"Room",
|
||||||
"RoomResp",
|
"RoomResp",
|
||||||
"Score",
|
"Score",
|
||||||
|
"ScoreAround",
|
||||||
"ScoreBase",
|
"ScoreBase",
|
||||||
"ScoreResp",
|
"ScoreResp",
|
||||||
"ScoreStatistics",
|
"ScoreStatistics",
|
||||||
|
|||||||
107
app/database/playlist_best_score.py
Normal file
107
app/database/playlist_best_score.py
Normal file
@@ -0,0 +1,107 @@
|
|||||||
|
from typing import TYPE_CHECKING
|
||||||
|
|
||||||
|
from .lazer_user import User
|
||||||
|
|
||||||
|
from redis.asyncio import Redis
|
||||||
|
from sqlmodel import (
|
||||||
|
BigInteger,
|
||||||
|
Column,
|
||||||
|
Field,
|
||||||
|
ForeignKey,
|
||||||
|
Relationship,
|
||||||
|
SQLModel,
|
||||||
|
col,
|
||||||
|
func,
|
||||||
|
select,
|
||||||
|
)
|
||||||
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
|
if TYPE_CHECKING:
|
||||||
|
from .score import Score
|
||||||
|
|
||||||
|
|
||||||
|
class PlaylistBestScore(SQLModel, table=True):
|
||||||
|
__tablename__ = "playlist_best_scores" # pyright: ignore[reportAssignmentType]
|
||||||
|
|
||||||
|
user_id: int = Field(
|
||||||
|
sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), index=True)
|
||||||
|
)
|
||||||
|
score_id: int = Field(
|
||||||
|
sa_column=Column(BigInteger, ForeignKey("scores.id"), primary_key=True)
|
||||||
|
)
|
||||||
|
room_id: int = Field(foreign_key="rooms.id", index=True)
|
||||||
|
playlist_id: int = Field(foreign_key="room_playlists.id", index=True)
|
||||||
|
total_score: int = Field(default=0, sa_column=Column(BigInteger))
|
||||||
|
|
||||||
|
user: User = Relationship()
|
||||||
|
score: "Score" = Relationship(
|
||||||
|
sa_relationship_kwargs={
|
||||||
|
"foreign_keys": "[PlaylistBestScore.score_id]",
|
||||||
|
"lazy": "joined",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def process_playlist_best_score(
|
||||||
|
room_id: int,
|
||||||
|
playlist_id: int,
|
||||||
|
user_id: int,
|
||||||
|
score_id: int,
|
||||||
|
total_score: int,
|
||||||
|
session: AsyncSession,
|
||||||
|
redis: Redis,
|
||||||
|
):
|
||||||
|
previous = (
|
||||||
|
await session.exec(
|
||||||
|
select(PlaylistBestScore).where(
|
||||||
|
PlaylistBestScore.room_id == room_id,
|
||||||
|
PlaylistBestScore.playlist_id == playlist_id,
|
||||||
|
PlaylistBestScore.user_id == user_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
if previous is None:
|
||||||
|
score = PlaylistBestScore(
|
||||||
|
user_id=user_id,
|
||||||
|
score_id=score_id,
|
||||||
|
room_id=room_id,
|
||||||
|
playlist_id=playlist_id,
|
||||||
|
total_score=total_score,
|
||||||
|
)
|
||||||
|
session.add(score)
|
||||||
|
else:
|
||||||
|
previous.score_id = score_id
|
||||||
|
previous.total_score = total_score
|
||||||
|
await session.commit()
|
||||||
|
await redis.decr(f"multiplayer:{room_id}:gameplay:players")
|
||||||
|
|
||||||
|
|
||||||
|
async def get_position(
|
||||||
|
room_id: int,
|
||||||
|
playlist_id: int,
|
||||||
|
score_id: int,
|
||||||
|
session: AsyncSession,
|
||||||
|
) -> int:
|
||||||
|
rownum = (
|
||||||
|
func.row_number()
|
||||||
|
.over(
|
||||||
|
partition_by=(
|
||||||
|
col(PlaylistBestScore.playlist_id),
|
||||||
|
col(PlaylistBestScore.room_id),
|
||||||
|
),
|
||||||
|
order_by=col(PlaylistBestScore.total_score).desc(),
|
||||||
|
)
|
||||||
|
.label("row_number")
|
||||||
|
)
|
||||||
|
subq = (
|
||||||
|
select(PlaylistBestScore, rownum)
|
||||||
|
.where(
|
||||||
|
PlaylistBestScore.playlist_id == playlist_id,
|
||||||
|
PlaylistBestScore.room_id == room_id,
|
||||||
|
)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
stmt = select(subq.c.row_number).where(subq.c.score_id == score_id)
|
||||||
|
result = await session.exec(stmt)
|
||||||
|
s = result.one_or_none()
|
||||||
|
return s if s else 0
|
||||||
@@ -26,7 +26,7 @@ if TYPE_CHECKING:
|
|||||||
|
|
||||||
|
|
||||||
class PlaylistBase(SQLModel, UTCBaseModel):
|
class PlaylistBase(SQLModel, UTCBaseModel):
|
||||||
id: int = 0
|
id: int = Field(index=True)
|
||||||
owner_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id")))
|
owner_id: int = Field(sa_column=Column(BigInteger, ForeignKey("lazer_users.id")))
|
||||||
ruleset_id: int = Field(ge=0, le=3)
|
ruleset_id: int = Field(ge=0, le=3)
|
||||||
expired: bool = Field(default=False)
|
expired: bool = Field(default=False)
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from collections.abc import Sequence
|
|||||||
from datetime import UTC, date, datetime
|
from datetime import UTC, date, datetime
|
||||||
import json
|
import json
|
||||||
import math
|
import math
|
||||||
from typing import TYPE_CHECKING
|
from typing import TYPE_CHECKING, Any
|
||||||
|
|
||||||
from app.calculator import (
|
from app.calculator import (
|
||||||
calculate_pp,
|
calculate_pp,
|
||||||
@@ -14,7 +14,7 @@ from app.calculator import (
|
|||||||
clamp,
|
clamp,
|
||||||
)
|
)
|
||||||
from app.database.team import TeamMember
|
from app.database.team import TeamMember
|
||||||
from app.models.model import UTCBaseModel
|
from app.models.model import RespWithCursor, UTCBaseModel
|
||||||
from app.models.mods import APIMod, mods_can_get_pp
|
from app.models.mods import APIMod, mods_can_get_pp
|
||||||
from app.models.score import (
|
from app.models.score import (
|
||||||
INT_TO_MODE,
|
INT_TO_MODE,
|
||||||
@@ -88,6 +88,7 @@ class ScoreBase(AsyncAttrs, SQLModel, UTCBaseModel):
|
|||||||
default=0, sa_column=Column(BigInteger), exclude=True
|
default=0, sa_column=Column(BigInteger), exclude=True
|
||||||
)
|
)
|
||||||
type: str
|
type: str
|
||||||
|
beatmap_id: int = Field(index=True, foreign_key="beatmaps.id")
|
||||||
|
|
||||||
# optional
|
# optional
|
||||||
# TODO: current_user_attributes
|
# TODO: current_user_attributes
|
||||||
@@ -99,7 +100,6 @@ class Score(ScoreBase, table=True):
|
|||||||
id: int | None = Field(
|
id: int | None = Field(
|
||||||
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")
|
|
||||||
user_id: int = Field(
|
user_id: int = Field(
|
||||||
default=None,
|
default=None,
|
||||||
sa_column=Column(
|
sa_column=Column(
|
||||||
@@ -162,7 +162,8 @@ class ScoreResp(ScoreBase):
|
|||||||
maximum_statistics: ScoreStatistics | None = None
|
maximum_statistics: ScoreStatistics | None = None
|
||||||
rank_global: int | None = None
|
rank_global: int | None = None
|
||||||
rank_country: int | None = None
|
rank_country: int | None = None
|
||||||
position: int = 1 # TODO
|
position: int | None = None
|
||||||
|
scores_around: "ScoreAround | None" = None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_db(cls, session: AsyncSession, score: Score) -> "ScoreResp":
|
async def from_db(cls, session: AsyncSession, score: Score) -> "ScoreResp":
|
||||||
@@ -234,6 +235,16 @@ class ScoreResp(ScoreBase):
|
|||||||
return s
|
return s
|
||||||
|
|
||||||
|
|
||||||
|
class MultiplayerScores(RespWithCursor):
|
||||||
|
scores: list[ScoreResp] = Field(default_factory=list)
|
||||||
|
params: dict[str, Any] = Field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
class ScoreAround(SQLModel):
|
||||||
|
higher: MultiplayerScores | None = None
|
||||||
|
lower: MultiplayerScores | None = None
|
||||||
|
|
||||||
|
|
||||||
async def get_best_id(session: AsyncSession, score_id: int) -> None:
|
async def get_best_id(session: AsyncSession, score_id: int) -> None:
|
||||||
rownum = (
|
rownum = (
|
||||||
func.row_number()
|
func.row_number()
|
||||||
|
|||||||
@@ -13,3 +13,10 @@ class UTCBaseModel(BaseModel):
|
|||||||
v = v.replace(tzinfo=UTC)
|
v = v.replace(tzinfo=UTC)
|
||||||
return v.astimezone(UTC).isoformat()
|
return v.astimezone(UTC).isoformat()
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
|
||||||
|
Cursor = dict[str, int]
|
||||||
|
|
||||||
|
|
||||||
|
class RespWithCursor(BaseModel):
|
||||||
|
cursor: Cursor | None = None
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import time
|
||||||
|
|
||||||
|
from app.calculator import clamp
|
||||||
from app.database import (
|
from app.database import (
|
||||||
Beatmap,
|
Beatmap,
|
||||||
Playlist,
|
Playlist,
|
||||||
@@ -9,7 +12,18 @@ from app.database import (
|
|||||||
ScoreTokenResp,
|
ScoreTokenResp,
|
||||||
User,
|
User,
|
||||||
)
|
)
|
||||||
from app.database.score import get_leaderboard, process_score, process_user
|
from app.database.playlist_best_score import (
|
||||||
|
PlaylistBestScore,
|
||||||
|
get_position,
|
||||||
|
process_playlist_best_score,
|
||||||
|
)
|
||||||
|
from app.database.score import (
|
||||||
|
MultiplayerScores,
|
||||||
|
ScoreAround,
|
||||||
|
get_leaderboard,
|
||||||
|
process_score,
|
||||||
|
process_user,
|
||||||
|
)
|
||||||
from app.dependencies.database import get_db, get_redis
|
from app.dependencies.database import get_db, get_redis
|
||||||
from app.dependencies.fetcher import get_fetcher
|
from app.dependencies.fetcher import get_fetcher
|
||||||
from app.dependencies.user import get_current_user
|
from app.dependencies.user import get_current_user
|
||||||
@@ -33,6 +47,8 @@ from sqlalchemy.orm import joinedload
|
|||||||
from sqlmodel import col, select
|
from sqlmodel import col, select
|
||||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
|
READ_SCORE_TIMEOUT = 10
|
||||||
|
|
||||||
|
|
||||||
async def submit_score(
|
async def submit_score(
|
||||||
info: SoloScoreSubmissionInfo,
|
info: SoloScoreSubmissionInfo,
|
||||||
@@ -337,4 +353,163 @@ async def submit_playlist_score(
|
|||||||
item.id,
|
item.id,
|
||||||
room_id,
|
room_id,
|
||||||
)
|
)
|
||||||
|
await process_playlist_best_score(
|
||||||
|
room_id,
|
||||||
|
playlist_id,
|
||||||
|
current_user.id,
|
||||||
|
score_resp.id,
|
||||||
|
score_resp.total_score,
|
||||||
|
session,
|
||||||
|
redis,
|
||||||
|
)
|
||||||
return score_resp
|
return score_resp
|
||||||
|
|
||||||
|
|
||||||
|
class IndexedScoreResp(MultiplayerScores):
|
||||||
|
total: int
|
||||||
|
user_score: ScoreResp | None = None
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/rooms/{room_id}/playlist/{playlist_id}/scores", response_model=IndexedScoreResp
|
||||||
|
)
|
||||||
|
async def index_playlist_scores(
|
||||||
|
room_id: int,
|
||||||
|
playlist_id: int,
|
||||||
|
limit: int = 50,
|
||||||
|
cursor: int = Query(2000000, alias="cursor[total_score]"),
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
session: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
limit = clamp(limit, 1, 50)
|
||||||
|
|
||||||
|
scores = (
|
||||||
|
await session.exec(
|
||||||
|
select(PlaylistBestScore)
|
||||||
|
.where(
|
||||||
|
PlaylistBestScore.playlist_id == playlist_id,
|
||||||
|
PlaylistBestScore.room_id == room_id,
|
||||||
|
PlaylistBestScore.total_score < cursor,
|
||||||
|
)
|
||||||
|
.order_by(col(PlaylistBestScore.total_score).desc())
|
||||||
|
.limit(limit + 1)
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
has_more = len(scores) > limit
|
||||||
|
if has_more:
|
||||||
|
scores = scores[:-1]
|
||||||
|
|
||||||
|
user_score = None
|
||||||
|
score_resp = [await ScoreResp.from_db(session, score.score) for score in scores]
|
||||||
|
for score in score_resp:
|
||||||
|
score.position = await get_position(room_id, playlist_id, score.id, session)
|
||||||
|
if score.user_id == current_user.id:
|
||||||
|
user_score = score
|
||||||
|
resp = IndexedScoreResp(
|
||||||
|
scores=score_resp,
|
||||||
|
user_score=user_score,
|
||||||
|
total=len(scores),
|
||||||
|
params={
|
||||||
|
"limit": limit,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if has_more:
|
||||||
|
resp.cursor = {
|
||||||
|
"total_score": scores[-1].total_score,
|
||||||
|
}
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"/rooms/{room_id}/playlist/{playlist_id}/scores/{score_id}",
|
||||||
|
response_model=ScoreResp,
|
||||||
|
)
|
||||||
|
async def show_playlist_score(
|
||||||
|
room_id: int,
|
||||||
|
playlist_id: int,
|
||||||
|
score_id: int,
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
session: AsyncSession = Depends(get_db),
|
||||||
|
redis: Redis = Depends(get_redis),
|
||||||
|
):
|
||||||
|
start_time = time.time()
|
||||||
|
score_record = None
|
||||||
|
completed = False
|
||||||
|
while time.time() - start_time < READ_SCORE_TIMEOUT:
|
||||||
|
if score_record is None:
|
||||||
|
score_record = (
|
||||||
|
await session.exec(
|
||||||
|
select(PlaylistBestScore).where(
|
||||||
|
PlaylistBestScore.score_id == score_id,
|
||||||
|
PlaylistBestScore.playlist_id == playlist_id,
|
||||||
|
PlaylistBestScore.room_id == room_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
if completed_players := await redis.get(
|
||||||
|
f"multiplayer:{room_id}:gameplay:players"
|
||||||
|
):
|
||||||
|
completed = completed_players == "0"
|
||||||
|
if score_record and completed:
|
||||||
|
break
|
||||||
|
if not score_record:
|
||||||
|
raise HTTPException(status_code=404, detail="Score not found")
|
||||||
|
resp = await ScoreResp.from_db(session, score_record.score)
|
||||||
|
resp.position = await get_position(room_id, playlist_id, score_id, session)
|
||||||
|
if completed:
|
||||||
|
scores = (
|
||||||
|
await session.exec(
|
||||||
|
select(PlaylistBestScore).where(
|
||||||
|
PlaylistBestScore.playlist_id == playlist_id,
|
||||||
|
PlaylistBestScore.room_id == room_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).all()
|
||||||
|
higher_scores = []
|
||||||
|
lower_scores = []
|
||||||
|
for score in scores:
|
||||||
|
if score.total_score > resp.total_score:
|
||||||
|
higher_scores.append(await ScoreResp.from_db(session, score.score))
|
||||||
|
elif score.total_score < resp.total_score:
|
||||||
|
lower_scores.append(await ScoreResp.from_db(session, score.score))
|
||||||
|
resp.scores_around = ScoreAround(
|
||||||
|
higher=MultiplayerScores(scores=higher_scores),
|
||||||
|
lower=MultiplayerScores(scores=lower_scores),
|
||||||
|
)
|
||||||
|
|
||||||
|
return resp
|
||||||
|
|
||||||
|
|
||||||
|
@router.get(
|
||||||
|
"rooms/{room_id}/playlist/{playlist_id}/scores/users/{user_id}",
|
||||||
|
response_model=ScoreResp,
|
||||||
|
)
|
||||||
|
async def get_user_playlist_score(
|
||||||
|
room_id: int,
|
||||||
|
playlist_id: int,
|
||||||
|
user_id: int,
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
session: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
score_record = None
|
||||||
|
start_time = time.time()
|
||||||
|
while time.time() - start_time < READ_SCORE_TIMEOUT:
|
||||||
|
score_record = (
|
||||||
|
await session.exec(
|
||||||
|
select(PlaylistBestScore).where(
|
||||||
|
PlaylistBestScore.user_id == user_id,
|
||||||
|
PlaylistBestScore.playlist_id == playlist_id,
|
||||||
|
PlaylistBestScore.room_id == room_id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).first()
|
||||||
|
if score_record:
|
||||||
|
break
|
||||||
|
if not score_record:
|
||||||
|
raise HTTPException(status_code=404, detail="Score not found")
|
||||||
|
|
||||||
|
resp = await ScoreResp.from_db(session, score_record.score)
|
||||||
|
resp.position = await get_position(
|
||||||
|
room_id, playlist_id, score_record.score_id, session
|
||||||
|
)
|
||||||
|
return resp
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from app.database.beatmap import Beatmap
|
|||||||
from app.database.lazer_user import User
|
from app.database.lazer_user import User
|
||||||
from app.database.playlists import Playlist
|
from app.database.playlists import Playlist
|
||||||
from app.database.relationship import Relationship, RelationshipType
|
from app.database.relationship import Relationship, RelationshipType
|
||||||
from app.dependencies.database import engine
|
from app.dependencies.database import engine, get_redis
|
||||||
from app.exception import InvokeException
|
from app.exception import InvokeException
|
||||||
from app.log import logger
|
from app.log import logger
|
||||||
from app.models.mods import APIMod
|
from app.models.mods import APIMod
|
||||||
@@ -642,6 +642,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
if room.queue.current_item.expired:
|
if room.queue.current_item.expired:
|
||||||
raise InvokeException("Current playlist item is expired")
|
raise InvokeException("Current playlist item is expired")
|
||||||
playing = False
|
playing = False
|
||||||
|
played_user = 0
|
||||||
for user in room.room.users:
|
for user in room.room.users:
|
||||||
client = self.get_client_by_id(str(user.user_id))
|
client = self.get_client_by_id(str(user.user_id))
|
||||||
if client is None:
|
if client is None:
|
||||||
@@ -652,6 +653,7 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
MultiplayerUserState.LOADED,
|
MultiplayerUserState.LOADED,
|
||||||
):
|
):
|
||||||
playing = True
|
playing = True
|
||||||
|
played_user += 1
|
||||||
await self.change_user_state(room, user, MultiplayerUserState.PLAYING)
|
await self.change_user_state(room, user, MultiplayerUserState.PLAYING)
|
||||||
await self.call_noblock(client, "GameplayStarted")
|
await self.call_noblock(client, "GameplayStarted")
|
||||||
elif user.state == MultiplayerUserState.WAITING_FOR_LOAD:
|
elif user.state == MultiplayerUserState.WAITING_FOR_LOAD:
|
||||||
@@ -665,6 +667,13 @@ class MultiplayerHub(Hub[MultiplayerClientState]):
|
|||||||
room,
|
room,
|
||||||
(MultiplayerRoomState.PLAYING if playing else MultiplayerRoomState.OPEN),
|
(MultiplayerRoomState.PLAYING if playing else MultiplayerRoomState.OPEN),
|
||||||
)
|
)
|
||||||
|
if playing:
|
||||||
|
redis = get_redis()
|
||||||
|
await redis.set(
|
||||||
|
f"multiplayer:{room.room.room_id}:gameplay:players",
|
||||||
|
played_user,
|
||||||
|
ex=3600,
|
||||||
|
)
|
||||||
|
|
||||||
async def send_match_event(
|
async def send_match_event(
|
||||||
self, room: ServerMultiplayerRoom, event: MatchServerEvent
|
self, room: ServerMultiplayerRoom, event: MatchServerEvent
|
||||||
|
|||||||
@@ -0,0 +1,89 @@
|
|||||||
|
"""playlist: index playlist id
|
||||||
|
|
||||||
|
Revision ID: d0c1b2cefe91
|
||||||
|
Revises: 58a11441d302
|
||||||
|
Create Date: 2025-08-06 06:02:10.512616
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
from collections.abc import Sequence
|
||||||
|
|
||||||
|
from alembic import op
|
||||||
|
import sqlalchemy as sa
|
||||||
|
|
||||||
|
# revision identifiers, used by Alembic.
|
||||||
|
revision: str = "d0c1b2cefe91"
|
||||||
|
down_revision: str | Sequence[str] | None = "58a11441d302"
|
||||||
|
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_index(
|
||||||
|
op.f("ix_room_playlists_id"), "room_playlists", ["id"], unique=False
|
||||||
|
)
|
||||||
|
op.create_table(
|
||||||
|
"playlist_best_scores",
|
||||||
|
sa.Column("user_id", sa.BigInteger(), nullable=True),
|
||||||
|
sa.Column("score_id", sa.BigInteger(), nullable=False),
|
||||||
|
sa.Column("room_id", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("playlist_id", sa.Integer(), nullable=False),
|
||||||
|
sa.Column("total_score", sa.BigInteger(), nullable=True),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["playlist_id"],
|
||||||
|
["room_playlists.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["room_id"],
|
||||||
|
["rooms.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["score_id"],
|
||||||
|
["scores.id"],
|
||||||
|
),
|
||||||
|
sa.ForeignKeyConstraint(
|
||||||
|
["user_id"],
|
||||||
|
["lazer_users.id"],
|
||||||
|
),
|
||||||
|
sa.PrimaryKeyConstraint("score_id"),
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_playlist_best_scores_playlist_id"),
|
||||||
|
"playlist_best_scores",
|
||||||
|
["playlist_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_playlist_best_scores_room_id"),
|
||||||
|
"playlist_best_scores",
|
||||||
|
["room_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
op.create_index(
|
||||||
|
op.f("ix_playlist_best_scores_user_id"),
|
||||||
|
"playlist_best_scores",
|
||||||
|
["user_id"],
|
||||||
|
unique=False,
|
||||||
|
)
|
||||||
|
# ### end Alembic commands ###
|
||||||
|
|
||||||
|
|
||||||
|
def downgrade() -> None:
|
||||||
|
"""Downgrade schema."""
|
||||||
|
# ### commands auto generated by Alembic - please adjust! ###
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_playlist_best_scores_user_id"), table_name="playlist_best_scores"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_playlist_best_scores_room_id"), table_name="playlist_best_scores"
|
||||||
|
)
|
||||||
|
op.drop_index(
|
||||||
|
op.f("ix_playlist_best_scores_playlist_id"), table_name="playlist_best_scores"
|
||||||
|
)
|
||||||
|
op.drop_table("playlist_best_scores")
|
||||||
|
op.drop_index(op.f("ix_room_playlists_id"), table_name="room_playlists")
|
||||||
|
# ### end Alembic commands ###
|
||||||
Reference in New Issue
Block a user