fix(recent_activity): fix rank & achievement event

运行 tools/fix_user_rank_event.py 修复现存的 event
This commit is contained in:
MingxuanGame
2025-08-22 11:00:36 +00:00
parent e3cbd31312
commit 545fc9e0c6
7 changed files with 259 additions and 34 deletions

View File

@@ -24,6 +24,7 @@ from .counts import (
)
from .daily_challenge import DailyChallengeStats, DailyChallengeStatsResp
from .email_verification import EmailVerification, LoginSession
from .events import Event
from .favourite_beatmapset import FavouriteBeatmapset
from .lazer_user import (
User,
@@ -83,6 +84,7 @@ __all__ = [
"DailyChallengeStats",
"DailyChallengeStatsResp",
"EmailVerification",
"Event",
"FavouriteBeatmapset",
"ItemAttemptsCount",
"ItemAttemptsResp",

View File

@@ -77,7 +77,7 @@ async def process_achievements(session: AsyncSession, redis: Redis, score_id: in
type=EventType.ACHIEVEMENT,
user_id=score.user_id,
event_payload={
"achievement": {"achievement_id": r.id, "achieved_at": now.isoformat()},
"achievement": {"slug": r.assets_id, "name": r.name},
"user": {
"username": score.user.username,
"url": settings.web_url + "users/" + str(score.user.id),

View File

@@ -2,6 +2,9 @@ from datetime import UTC, datetime
from enum import Enum
from typing import TYPE_CHECKING
from app.models.model import UTCBaseModel
from pydantic import model_serializer
from sqlmodel import (
JSON,
BigInteger,
@@ -34,26 +37,25 @@ class EventType(str, Enum):
USERNAME_CHANGE = "username_change"
class EventBase(SQLModel):
class Event(UTCBaseModel, SQLModel, table=True):
__tablename__: str = "user_events"
id: int = Field(default=None, primary_key=True)
created_at: datetime = Field(sa_column=Column(DateTime(timezone=True), default=datetime.now(UTC)))
type: EventType
event_payload: dict = Field(exclude=True, default_factory=dict, sa_column=Column(JSON))
class Event(EventBase, table=True):
__tablename__: str = "user_events"
user_id: int | None = Field(
default=None,
sa_column=Column(BigInteger, ForeignKey("lazer_users.id"), index=True),
)
user: "User" = Relationship(back_populates="events")
class EventResp(EventBase):
def merge_payload(self) -> "EventResp":
for key, value in self.event_payload.items():
setattr(self, key, value)
return self
pass
@model_serializer
def serialize(self) -> dict:
d = {
"id": self.id,
"createdAt": self.created_at.replace(tzinfo=UTC).isoformat(),
"type": self.type.value,
}
for k, v in self.event_payload.items():
d[k] = v
return d

View File

@@ -71,6 +71,18 @@ class GameMode(str, Enum):
7: GameMode.FRUITSRX,
}[v]
def readable(self) -> str:
return {
GameMode.OSU: "osu!",
GameMode.TAIKO: "osu!taiko",
GameMode.FRUITS: "osu!catch",
GameMode.MANIA: "osu!mania",
GameMode.OSURX: "osu!relax",
GameMode.OSUAP: "osu!autopilot",
GameMode.TAIKORX: "taiko relax",
GameMode.FRUITSRX: "catch relax",
}[self]
def to_special_mode(self, mods: list[APIMod] | list[str]) -> "GameMode":
if self not in (GameMode.OSU, GameMode.TAIKO, GameMode.FRUITS):
return self

View File

@@ -172,10 +172,13 @@ async def submit_score(
user=score.user,
)
rank_event.event_payload = {
"scorerank": str(score.rank),
"scorerank": score.rank.value,
"rank": resp.rank_global,
"mode": str(resp.beatmap.mode), # pyright: ignore[reportOptionalMemberAccess]
"beatmap": {"title": resp.beatmap.version, "url": resp.beatmap.url}, # pyright: ignore[reportOptionalMemberAccess]
"mode": resp.beatmap.mode.readable(), # pyright: ignore[reportOptionalMemberAccess]
"beatmap": {
"title": f"{resp.beatmap.beatmapset.artist} - {resp.beatmap.beatmapset.title} [{resp.beatmap.version}]", # pyright: ignore[reportOptionalMemberAccess]
"url": resp.beatmap.url, # pyright: ignore[reportOptionalMemberAccess]
},
"user": {
"username": score.user.username,
"url": settings.web_url + "users/" + str(score.user.id),

View File

@@ -12,7 +12,7 @@ from app.database import (
User,
UserResp,
)
from app.database.events import EventResp
from app.database.events import Event
from app.database.lazer_user import SEARCH_INCLUDED
from app.database.pp_best_score import PPBestScore
from app.database.score import Score, ScoreResp
@@ -100,6 +100,29 @@ async def get_users(
return BatchUserResponse(users=users)
@router.get("/users/{user}/recent_activity", tags=["用户"], response_model=list[Event])
async def get_user_events(
session: Database,
user: int,
limit: int | None = Query(None),
offset: int | None = Query(None), # TODO: 搞清楚并且添加这个奇怪的分页偏移
):
db_user = await session.get(User, user)
if db_user is None or db_user.id == BANCHOBOT_ID:
raise HTTPException(404, "User Not found")
events = (
await session.exec(
select(Event)
.where(Event.user_id == db_user.id)
.order_by(col(Event.created_at).desc())
.limit(limit)
.offset(offset)
)
).all()
print(events)
return events
@router.get(
"/users/{user_id}/{ruleset}",
response_model=UserResp,
@@ -341,19 +364,3 @@ async def get_user_scores(
)
return score_responses
@router.get("/users/{user}/recent_activity", tags=["用户"], response_model=list[EventResp])
async def get_user_events(
session: Database,
user: int,
limit: int | None = Query(None),
offset: str | None = Query(None), # TODO: 搞清楚并且添加这个奇怪的分页偏移
):
db_user = await session.get(User, user)
if db_user is None or db_user.id == BANCHOBOT_ID:
raise HTTPException(404, "User Not found")
events = await db_user.awaitable_attrs.events
if limit is not None:
events = events[:limit]
return [EventResp(**event.model_dump()).merge_payload() for event in events]