feat(beatmap): support playcount & passcount
This commit is contained in:
@@ -4,6 +4,7 @@ from .beatmap import (
|
||||
Beatmap as Beatmap,
|
||||
BeatmapResp as BeatmapResp,
|
||||
)
|
||||
from .beatmap_playcounts import BeatmapPlaycounts, BeatmapPlaycountsResp
|
||||
from .beatmapset import (
|
||||
Beatmapset as Beatmapset,
|
||||
BeatmapsetResp as BeatmapsetResp,
|
||||
@@ -37,6 +38,8 @@ from .user_account_history import (
|
||||
|
||||
__all__ = [
|
||||
"Beatmap",
|
||||
"BeatmapPlaycounts",
|
||||
"BeatmapPlaycountsResp",
|
||||
"Beatmapset",
|
||||
"BeatmapsetResp",
|
||||
"BestScore",
|
||||
|
||||
@@ -5,10 +5,11 @@ from app.models.beatmap import BeatmapRankStatus
|
||||
from app.models.model import UTCBaseModel
|
||||
from app.models.score import MODE_TO_INT, GameMode
|
||||
|
||||
from .beatmap_playcounts import BeatmapPlaycounts
|
||||
from .beatmapset import Beatmapset, BeatmapsetResp
|
||||
|
||||
from sqlalchemy import DECIMAL, Column, DateTime
|
||||
from sqlmodel import VARCHAR, Field, Relationship, SQLModel, select
|
||||
from sqlmodel import VARCHAR, Field, Relationship, SQLModel, col, func, select
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@@ -58,8 +59,6 @@ class BeatmapBase(SQLModel, UTCBaseModel):
|
||||
deleted_at: datetime | None = Field(default=None, sa_column=Column(DateTime))
|
||||
hit_length: int = Field(default=0)
|
||||
last_updated: datetime = Field(sa_column=Column(DateTime))
|
||||
passcount: int = Field(default=0)
|
||||
playcount: int = Field(default=0)
|
||||
|
||||
|
||||
class Beatmap(BeatmapBase, table=True):
|
||||
@@ -156,6 +155,8 @@ class BeatmapResp(BeatmapBase):
|
||||
mode_int: int
|
||||
ranked: int
|
||||
url: str = ""
|
||||
playcount: int = 0
|
||||
passcount: int = 0
|
||||
|
||||
@classmethod
|
||||
async def from_db(
|
||||
@@ -166,6 +167,8 @@ class BeatmapResp(BeatmapBase):
|
||||
session: AsyncSession | None = None,
|
||||
user: "User | None" = None,
|
||||
) -> "BeatmapResp":
|
||||
from .score import Score
|
||||
|
||||
beatmap_ = beatmap.model_dump()
|
||||
if query_mode is not None and beatmap.mode != query_mode:
|
||||
beatmap_["convert"] = True
|
||||
@@ -177,4 +180,22 @@ class BeatmapResp(BeatmapBase):
|
||||
beatmap_["beatmapset"] = await BeatmapsetResp.from_db(
|
||||
beatmap.beatmapset, session=session, user=user
|
||||
)
|
||||
if session:
|
||||
beatmap_["playcount"] = (
|
||||
await session.exec(
|
||||
select(func.count())
|
||||
.select_from(BeatmapPlaycounts)
|
||||
.where(BeatmapPlaycounts.beatmap_id == beatmap.id)
|
||||
)
|
||||
).one()
|
||||
beatmap_["passcount"] = (
|
||||
await session.exec(
|
||||
select(func.count())
|
||||
.select_from(Score)
|
||||
.where(
|
||||
Score.beatmap_id == beatmap.id,
|
||||
col(Score.passed).is_(True),
|
||||
)
|
||||
)
|
||||
).one()
|
||||
return cls.model_validate(beatmap_)
|
||||
|
||||
77
app/database/beatmap_playcounts.py
Normal file
77
app/database/beatmap_playcounts.py
Normal file
@@ -0,0 +1,77 @@
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from pydantic import BaseModel
|
||||
from sqlalchemy.ext.asyncio import AsyncAttrs
|
||||
from sqlmodel import (
|
||||
BigInteger,
|
||||
Column,
|
||||
Field,
|
||||
ForeignKey,
|
||||
Relationship,
|
||||
SQLModel,
|
||||
select,
|
||||
)
|
||||
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .beatmap import Beatmap, BeatmapResp
|
||||
from .beatmapset import BeatmapsetResp
|
||||
from .lazer_user import User
|
||||
|
||||
|
||||
class BeatmapPlaycounts(AsyncAttrs, SQLModel, table=True):
|
||||
__tablename__ = "beatmap_playcounts" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(
|
||||
default=None,
|
||||
sa_column=Column(BigInteger, primary_key=True, autoincrement=True),
|
||||
)
|
||||
user_id: int = Field(
|
||||
sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), index=True)
|
||||
)
|
||||
beatmap_id: int = Field(foreign_key="beatmaps.id", index=True)
|
||||
playcount: int = Field(default=0)
|
||||
|
||||
user: "User" = Relationship()
|
||||
beatmap: "Beatmap" = Relationship()
|
||||
|
||||
|
||||
class BeatmapPlaycountsResp(BaseModel):
|
||||
beatmap_id: int
|
||||
beatmap: "BeatmapResp | None" = None
|
||||
beatmapset: "BeatmapsetResp | None" = None
|
||||
count: int
|
||||
|
||||
@classmethod
|
||||
async def from_db(cls, db_model: BeatmapPlaycounts) -> "BeatmapPlaycountsResp":
|
||||
from .beatmap import BeatmapResp
|
||||
from .beatmapset import BeatmapsetResp
|
||||
|
||||
await db_model.awaitable_attrs.beatmap
|
||||
return cls(
|
||||
beatmap_id=db_model.beatmap_id,
|
||||
count=db_model.playcount,
|
||||
beatmap=await BeatmapResp.from_db(db_model.beatmap),
|
||||
beatmapset=await BeatmapsetResp.from_db(db_model.beatmap.beatmapset),
|
||||
)
|
||||
|
||||
|
||||
async def process_beatmap_playcount(
|
||||
session: AsyncSession, user_id: int, beatmap_id: int
|
||||
) -> None:
|
||||
existing_playcount = (
|
||||
await session.exec(
|
||||
select(BeatmapPlaycounts).where(
|
||||
BeatmapPlaycounts.user_id == user_id,
|
||||
BeatmapPlaycounts.beatmap_id == beatmap_id,
|
||||
)
|
||||
)
|
||||
).first()
|
||||
|
||||
if existing_playcount:
|
||||
existing_playcount.playcount += 1
|
||||
else:
|
||||
new_playcount = BeatmapPlaycounts(
|
||||
user_id=user_id, beatmap_id=beatmap_id, playcount=1
|
||||
)
|
||||
session.add(new_playcount)
|
||||
@@ -217,7 +217,7 @@ class BeatmapsetResp(BeatmapsetBase):
|
||||
|
||||
update = {
|
||||
"beatmaps": [
|
||||
await BeatmapResp.from_db(beatmap, from_set=True)
|
||||
await BeatmapResp.from_db(beatmap, from_set=True, session=session)
|
||||
for beatmap in await beatmapset.awaitable_attrs.beatmaps
|
||||
],
|
||||
"hype": BeatmapHype(
|
||||
|
||||
@@ -28,6 +28,7 @@ from app.models.score import (
|
||||
)
|
||||
|
||||
from .beatmap import Beatmap, BeatmapResp
|
||||
from .beatmap_playcounts import process_beatmap_playcount
|
||||
from .beatmapset import BeatmapsetResp
|
||||
from .best_score import BestScore
|
||||
from .lazer_user import User, UserResp
|
||||
@@ -601,6 +602,7 @@ async def process_user(
|
||||
statistics.hit_accuracy = acc_sum
|
||||
if add_to_db:
|
||||
session.add(mouthly_playcount)
|
||||
await process_beatmap_playcount(session, user.id, score.beatmap_id)
|
||||
await session.commit()
|
||||
await session.refresh(user)
|
||||
|
||||
|
||||
@@ -71,10 +71,10 @@ async def favourite_beatmapset(
|
||||
)
|
||||
).first()
|
||||
|
||||
if action == "favourite" and existing_favourite:
|
||||
raise HTTPException(status_code=400, detail="Already favourited")
|
||||
elif action == "unfavourite" and not existing_favourite:
|
||||
raise HTTPException(status_code=400, detail="Not favourited")
|
||||
if (action == "favourite" and existing_favourite) or (
|
||||
action == "unfavourite" and not existing_favourite
|
||||
):
|
||||
return
|
||||
|
||||
if action == "favourite":
|
||||
favourite = FavouriteBeatmapset(
|
||||
|
||||
Reference in New Issue
Block a user