feat(beatmap): support playcount & passcount
This commit is contained in:
@@ -4,6 +4,7 @@ from .beatmap import (
|
|||||||
Beatmap as Beatmap,
|
Beatmap as Beatmap,
|
||||||
BeatmapResp as BeatmapResp,
|
BeatmapResp as BeatmapResp,
|
||||||
)
|
)
|
||||||
|
from .beatmap_playcounts import BeatmapPlaycounts, BeatmapPlaycountsResp
|
||||||
from .beatmapset import (
|
from .beatmapset import (
|
||||||
Beatmapset as Beatmapset,
|
Beatmapset as Beatmapset,
|
||||||
BeatmapsetResp as BeatmapsetResp,
|
BeatmapsetResp as BeatmapsetResp,
|
||||||
@@ -37,6 +38,8 @@ from .user_account_history import (
|
|||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"Beatmap",
|
"Beatmap",
|
||||||
|
"BeatmapPlaycounts",
|
||||||
|
"BeatmapPlaycountsResp",
|
||||||
"Beatmapset",
|
"Beatmapset",
|
||||||
"BeatmapsetResp",
|
"BeatmapsetResp",
|
||||||
"BestScore",
|
"BestScore",
|
||||||
|
|||||||
@@ -5,10 +5,11 @@ from app.models.beatmap import BeatmapRankStatus
|
|||||||
from app.models.model import UTCBaseModel
|
from app.models.model import UTCBaseModel
|
||||||
from app.models.score import MODE_TO_INT, GameMode
|
from app.models.score import MODE_TO_INT, GameMode
|
||||||
|
|
||||||
|
from .beatmap_playcounts import BeatmapPlaycounts
|
||||||
from .beatmapset import Beatmapset, BeatmapsetResp
|
from .beatmapset import Beatmapset, BeatmapsetResp
|
||||||
|
|
||||||
from sqlalchemy import DECIMAL, Column, DateTime
|
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
|
from sqlmodel.ext.asyncio.session import AsyncSession
|
||||||
|
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
@@ -58,8 +59,6 @@ class BeatmapBase(SQLModel, UTCBaseModel):
|
|||||||
deleted_at: datetime | None = Field(default=None, sa_column=Column(DateTime))
|
deleted_at: datetime | None = Field(default=None, sa_column=Column(DateTime))
|
||||||
hit_length: int = Field(default=0)
|
hit_length: int = Field(default=0)
|
||||||
last_updated: datetime = Field(sa_column=Column(DateTime))
|
last_updated: datetime = Field(sa_column=Column(DateTime))
|
||||||
passcount: int = Field(default=0)
|
|
||||||
playcount: int = Field(default=0)
|
|
||||||
|
|
||||||
|
|
||||||
class Beatmap(BeatmapBase, table=True):
|
class Beatmap(BeatmapBase, table=True):
|
||||||
@@ -156,6 +155,8 @@ class BeatmapResp(BeatmapBase):
|
|||||||
mode_int: int
|
mode_int: int
|
||||||
ranked: int
|
ranked: int
|
||||||
url: str = ""
|
url: str = ""
|
||||||
|
playcount: int = 0
|
||||||
|
passcount: int = 0
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
async def from_db(
|
async def from_db(
|
||||||
@@ -166,6 +167,8 @@ class BeatmapResp(BeatmapBase):
|
|||||||
session: AsyncSession | None = None,
|
session: AsyncSession | None = None,
|
||||||
user: "User | None" = None,
|
user: "User | None" = None,
|
||||||
) -> "BeatmapResp":
|
) -> "BeatmapResp":
|
||||||
|
from .score import Score
|
||||||
|
|
||||||
beatmap_ = beatmap.model_dump()
|
beatmap_ = beatmap.model_dump()
|
||||||
if query_mode is not None and beatmap.mode != query_mode:
|
if query_mode is not None and beatmap.mode != query_mode:
|
||||||
beatmap_["convert"] = True
|
beatmap_["convert"] = True
|
||||||
@@ -177,4 +180,22 @@ class BeatmapResp(BeatmapBase):
|
|||||||
beatmap_["beatmapset"] = await BeatmapsetResp.from_db(
|
beatmap_["beatmapset"] = await BeatmapsetResp.from_db(
|
||||||
beatmap.beatmapset, session=session, user=user
|
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_)
|
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 = {
|
update = {
|
||||||
"beatmaps": [
|
"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
|
for beatmap in await beatmapset.awaitable_attrs.beatmaps
|
||||||
],
|
],
|
||||||
"hype": BeatmapHype(
|
"hype": BeatmapHype(
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ from app.models.score import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from .beatmap import Beatmap, BeatmapResp
|
from .beatmap import Beatmap, BeatmapResp
|
||||||
|
from .beatmap_playcounts import process_beatmap_playcount
|
||||||
from .beatmapset import BeatmapsetResp
|
from .beatmapset import BeatmapsetResp
|
||||||
from .best_score import BestScore
|
from .best_score import BestScore
|
||||||
from .lazer_user import User, UserResp
|
from .lazer_user import User, UserResp
|
||||||
@@ -601,6 +602,7 @@ async def process_user(
|
|||||||
statistics.hit_accuracy = acc_sum
|
statistics.hit_accuracy = acc_sum
|
||||||
if add_to_db:
|
if add_to_db:
|
||||||
session.add(mouthly_playcount)
|
session.add(mouthly_playcount)
|
||||||
|
await process_beatmap_playcount(session, user.id, score.beatmap_id)
|
||||||
await session.commit()
|
await session.commit()
|
||||||
await session.refresh(user)
|
await session.refresh(user)
|
||||||
|
|
||||||
|
|||||||
@@ -71,10 +71,10 @@ async def favourite_beatmapset(
|
|||||||
)
|
)
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if action == "favourite" and existing_favourite:
|
if (action == "favourite" and existing_favourite) or (
|
||||||
raise HTTPException(status_code=400, detail="Already favourited")
|
action == "unfavourite" and not existing_favourite
|
||||||
elif action == "unfavourite" and not existing_favourite:
|
):
|
||||||
raise HTTPException(status_code=400, detail="Not favourited")
|
return
|
||||||
|
|
||||||
if action == "favourite":
|
if action == "favourite":
|
||||||
favourite = FavouriteBeatmapset(
|
favourite = FavouriteBeatmapset(
|
||||||
|
|||||||
Reference in New Issue
Block a user