Files
bancho.py/app/repositories/scores.py
2025-04-04 21:30:31 +09:00

247 lines
7.2 KiB
Python

from __future__ import annotations
from datetime import datetime
from typing import TypedDict
from typing import cast
from sqlalchemy import Column
from sqlalchemy import DateTime
from sqlalchemy import Index
from sqlalchemy import Integer
from sqlalchemy import String
from sqlalchemy import func
from sqlalchemy import insert
from sqlalchemy import select
from sqlalchemy import update
from sqlalchemy.dialects.mysql import FLOAT
from sqlalchemy.dialects.mysql import TINYINT
import app.state.services
from app._typing import UNSET
from app._typing import _UnsetSentinel
from app.repositories import Base
class ScoresTable(Base):
__tablename__ = "scores"
id = Column("id", Integer, nullable=False, primary_key=True, autoincrement=True)
map_md5 = Column("map_md5", String(32), nullable=False)
score = Column("score", Integer, nullable=False)
pp = Column("pp", FLOAT(precision=6, scale=3), nullable=False)
acc = Column("acc", FLOAT(precision=6, scale=3), nullable=False)
max_combo = Column("max_combo", Integer, nullable=False)
mods = Column("mods", Integer, nullable=False)
n300 = Column("n300", Integer, nullable=False)
n100 = Column("n100", Integer, nullable=False)
n50 = Column("n50", Integer, nullable=False)
nmiss = Column("nmiss", Integer, nullable=False)
ngeki = Column("ngeki", Integer, nullable=False)
nkatu = Column("nkatu", Integer, nullable=False)
grade = Column("grade", String(2), nullable=False, server_default="N")
status = Column("status", Integer, nullable=False)
mode = Column("mode", Integer, nullable=False)
play_time = Column("play_time", DateTime, nullable=False)
time_elapsed = Column("time_elapsed", Integer, nullable=False)
client_flags = Column("client_flags", Integer, nullable=False)
userid = Column("userid", Integer, nullable=False)
perfect = Column("perfect", TINYINT(1), nullable=False)
online_checksum = Column("online_checksum", String(32), nullable=False)
__table_args__ = (
Index("scores_map_md5_index", map_md5),
Index("scores_score_index", score),
Index("scores_pp_index", pp),
Index("scores_mods_index", mods),
Index("scores_status_index", status),
Index("scores_mode_index", mode),
Index("scores_play_time_index", play_time),
Index("scores_userid_index", userid),
Index("scores_online_checksum_index", online_checksum),
)
READ_PARAMS = (
ScoresTable.id,
ScoresTable.map_md5,
ScoresTable.score,
ScoresTable.pp,
ScoresTable.acc,
ScoresTable.max_combo,
ScoresTable.mods,
ScoresTable.n300,
ScoresTable.n100,
ScoresTable.n50,
ScoresTable.nmiss,
ScoresTable.ngeki,
ScoresTable.nkatu,
ScoresTable.grade,
ScoresTable.status,
ScoresTable.mode,
ScoresTable.play_time,
ScoresTable.time_elapsed,
ScoresTable.client_flags,
ScoresTable.userid,
ScoresTable.perfect,
ScoresTable.online_checksum,
)
class Score(TypedDict):
id: int
map_md5: str
score: int
pp: float
acc: float
max_combo: int
mods: int
n300: int
n100: int
n50: int
nmiss: int
ngeki: int
nkatu: int
grade: str
status: int
mode: int
play_time: datetime
time_elapsed: int
client_flags: int
userid: int
perfect: int
online_checksum: str
async def create(
map_md5: str,
score: int,
pp: float,
acc: float,
max_combo: int,
mods: int,
n300: int,
n100: int,
n50: int,
nmiss: int,
ngeki: int,
nkatu: int,
grade: str,
status: int,
mode: int,
play_time: datetime,
time_elapsed: int,
client_flags: int,
user_id: int,
perfect: int,
online_checksum: str,
) -> Score:
insert_stmt = insert(ScoresTable).values(
map_md5=map_md5,
score=score,
pp=pp,
acc=acc,
max_combo=max_combo,
mods=mods,
n300=n300,
n100=n100,
n50=n50,
nmiss=nmiss,
ngeki=ngeki,
nkatu=nkatu,
grade=grade,
status=status,
mode=mode,
play_time=play_time,
time_elapsed=time_elapsed,
client_flags=client_flags,
userid=user_id,
perfect=perfect,
online_checksum=online_checksum,
)
rec_id = await app.state.services.database.execute(insert_stmt)
select_stmt = select(*READ_PARAMS).where(ScoresTable.id == rec_id)
_score = await app.state.services.database.fetch_one(select_stmt)
assert _score is not None
return cast(Score, _score)
async def fetch_one(id: int) -> Score | None:
select_stmt = select(*READ_PARAMS).where(ScoresTable.id == id)
_score = await app.state.services.database.fetch_one(select_stmt)
return cast(Score | None, _score)
async def fetch_count(
map_md5: str | None = None,
mods: int | None = None,
status: int | None = None,
mode: int | None = None,
user_id: int | None = None,
) -> int:
select_stmt = select(func.count().label("count")).select_from(ScoresTable)
if map_md5 is not None:
select_stmt = select_stmt.where(ScoresTable.map_md5 == map_md5)
if mods is not None:
select_stmt = select_stmt.where(ScoresTable.mods == mods)
if status is not None:
select_stmt = select_stmt.where(ScoresTable.status == status)
if mode is not None:
select_stmt = select_stmt.where(ScoresTable.mode == mode)
if user_id is not None:
select_stmt = select_stmt.where(ScoresTable.userid == user_id)
rec = await app.state.services.database.fetch_one(select_stmt)
assert rec is not None
return cast(int, rec["count"])
async def fetch_many(
map_md5: str | None = None,
mods: int | None = None,
status: int | None = None,
mode: int | None = None,
user_id: int | None = None,
page: int | None = None,
page_size: int | None = None,
) -> list[Score]:
select_stmt = select(*READ_PARAMS)
if map_md5 is not None:
select_stmt = select_stmt.where(ScoresTable.map_md5 == map_md5)
if mods is not None:
select_stmt = select_stmt.where(ScoresTable.mods == mods)
if status is not None:
select_stmt = select_stmt.where(ScoresTable.status == status)
if mode is not None:
select_stmt = select_stmt.where(ScoresTable.mode == mode)
if user_id is not None:
select_stmt = select_stmt.where(ScoresTable.userid == user_id)
if page is not None and page_size is not None:
select_stmt = select_stmt.limit(page_size).offset((page - 1) * page_size)
scores = await app.state.services.database.fetch_all(select_stmt)
return cast(list[Score], scores)
async def partial_update(
id: int,
pp: float | _UnsetSentinel = UNSET,
status: int | _UnsetSentinel = UNSET,
) -> Score | None:
"""Update an existing score."""
update_stmt = update(ScoresTable).where(ScoresTable.id == id)
if not isinstance(pp, _UnsetSentinel):
update_stmt = update_stmt.values(pp=pp)
if not isinstance(status, _UnsetSentinel):
update_stmt = update_stmt.values(status=status)
await app.state.services.database.execute(update_stmt)
select_stmt = select(*READ_PARAMS).where(ScoresTable.id == id)
_score = await app.state.services.database.fetch_one(select_stmt)
return cast(Score | None, _score)
# TODO: delete