refactor(database): re-structure
This commit is contained in:
49
app/database/__init__.py
Normal file
49
app/database/__init__.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from .auth import OAuthToken
|
||||
from .legacy import LegacyOAuthToken, LegacyUserStatistics
|
||||
from .team import Team, TeamMember
|
||||
from .user import (
|
||||
DailyChallengeStats,
|
||||
LazerUserAchievement,
|
||||
LazerUserBadge,
|
||||
LazerUserBanners,
|
||||
LazerUserCountry,
|
||||
LazerUserCounts,
|
||||
LazerUserKudosu,
|
||||
LazerUserMonthlyPlaycounts,
|
||||
LazerUserPreviousUsername,
|
||||
LazerUserProfile,
|
||||
LazerUserProfileSections,
|
||||
LazerUserReplaysWatched,
|
||||
LazerUserStatistics,
|
||||
RankHistory,
|
||||
User,
|
||||
UserAchievement,
|
||||
UserAvatar,
|
||||
)
|
||||
|
||||
__all__ = [
|
||||
"DailyChallengeStats",
|
||||
"LazerUserAchievement",
|
||||
"LazerUserBadge",
|
||||
"LazerUserBanners",
|
||||
"LazerUserCountry",
|
||||
"LazerUserCounts",
|
||||
"LazerUserKudosu",
|
||||
"LazerUserMonthlyPlaycounts",
|
||||
"LazerUserPreviousUsername",
|
||||
"LazerUserProfile",
|
||||
"LazerUserProfileSections",
|
||||
"LazerUserReplaysWatched",
|
||||
"LazerUserStatistics",
|
||||
"LegacyOAuthToken",
|
||||
"LegacyUserStatistics",
|
||||
"OAuthToken",
|
||||
"RankHistory",
|
||||
"Team",
|
||||
"TeamMember",
|
||||
"User",
|
||||
"UserAchievement",
|
||||
"UserAvatar",
|
||||
]
|
||||
26
app/database/auth.py
Normal file
26
app/database/auth.py
Normal file
@@ -0,0 +1,26 @@
|
||||
# ruff: noqa: I002
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import Column, DateTime
|
||||
from sqlmodel import Field, Relationship, SQLModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .user import User
|
||||
|
||||
|
||||
class OAuthToken(SQLModel, table=True):
|
||||
__tablename__ = "oauth_tokens" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
access_token: str = Field(max_length=500, unique=True)
|
||||
refresh_token: str = Field(max_length=500, unique=True)
|
||||
token_type: str = Field(default="Bearer", max_length=20)
|
||||
scope: str = Field(default="*", max_length=100)
|
||||
expires_at: datetime = Field(sa_column=Column(DateTime))
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
user: "User" = Relationship()
|
||||
94
app/database/legacy.py
Normal file
94
app/database/legacy.py
Normal file
@@ -0,0 +1,94 @@
|
||||
# ruff: noqa: I002
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import JSON, Column, DateTime
|
||||
from sqlmodel import Field, Relationship, SQLModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .user import User
|
||||
# ============================================
|
||||
# 旧的兼容性表模型(保留以便向后兼容)
|
||||
# ============================================
|
||||
|
||||
|
||||
class LegacyUserStatistics(SQLModel, table=True):
|
||||
__tablename__ = "user_statistics" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
mode: str = Field(max_length=10) # osu, taiko, fruits, mania
|
||||
|
||||
# 基本统计
|
||||
count_100: int = Field(default=0)
|
||||
count_300: int = Field(default=0)
|
||||
count_50: int = Field(default=0)
|
||||
count_miss: int = Field(default=0)
|
||||
|
||||
# 等级信息
|
||||
level_current: int = Field(default=1)
|
||||
level_progress: int = Field(default=0)
|
||||
|
||||
# 排名信息
|
||||
global_rank: int | None = Field(default=None)
|
||||
global_rank_exp: int | None = Field(default=None)
|
||||
country_rank: int | None = Field(default=None)
|
||||
|
||||
# PP 和分数
|
||||
pp: float = Field(default=0.0)
|
||||
pp_exp: float = Field(default=0.0)
|
||||
ranked_score: int = Field(default=0)
|
||||
hit_accuracy: float = Field(default=0.0)
|
||||
total_score: int = Field(default=0)
|
||||
total_hits: int = Field(default=0)
|
||||
maximum_combo: int = Field(default=0)
|
||||
|
||||
# 游戏统计
|
||||
play_count: int = Field(default=0)
|
||||
play_time: int = Field(default=0)
|
||||
replays_watched_by_others: int = Field(default=0)
|
||||
is_ranked: bool = Field(default=False)
|
||||
|
||||
# 成绩等级计数
|
||||
grade_ss: int = Field(default=0)
|
||||
grade_ssh: int = Field(default=0)
|
||||
grade_s: int = Field(default=0)
|
||||
grade_sh: int = Field(default=0)
|
||||
grade_a: int = Field(default=0)
|
||||
|
||||
# 最高排名记录
|
||||
rank_highest: int | None = Field(default=None)
|
||||
rank_highest_updated_at: datetime | None = Field(
|
||||
default=None, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
# 关联关系
|
||||
user: "User" = Relationship(back_populates="statistics")
|
||||
|
||||
|
||||
class LegacyOAuthToken(SQLModel, table=True):
|
||||
__tablename__ = "legacy_oauth_tokens" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
access_token: str = Field(max_length=255, index=True)
|
||||
refresh_token: str = Field(max_length=255, index=True)
|
||||
expires_at: datetime = Field(sa_column=Column(DateTime))
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
previous_usernames: list = Field(default_factory=list, sa_column=Column(JSON))
|
||||
replays_watched_counts: list = Field(default_factory=list, sa_column=Column(JSON))
|
||||
|
||||
# 用户关系
|
||||
user: "User" = Relationship()
|
||||
35
app/database/team.py
Normal file
35
app/database/team.py
Normal file
@@ -0,0 +1,35 @@
|
||||
# ruff: noqa: I002
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
from sqlalchemy import Column, DateTime
|
||||
from sqlmodel import Field, Relationship, SQLModel
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from .user import User
|
||||
class Team(SQLModel, table=True):
|
||||
__tablename__ = "teams" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
name: str = Field(max_length=100)
|
||||
short_name: str = Field(max_length=10)
|
||||
flag_url: str | None = Field(default=None, max_length=500)
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
members: list["TeamMember"] = Relationship(back_populates="team")
|
||||
|
||||
|
||||
class TeamMember(SQLModel, table=True):
|
||||
__tablename__ = "team_members" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
team_id: int = Field(foreign_key="teams.id")
|
||||
joined_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
user: "User" = Relationship(back_populates="team_membership")
|
||||
team: "Team" = Relationship(back_populates="members")
|
||||
460
app/database/user.py
Normal file
460
app/database/user.py
Normal file
@@ -0,0 +1,460 @@
|
||||
# ruff: noqa: I002
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
|
||||
from .legacy import LegacyUserStatistics
|
||||
from .team import TeamMember
|
||||
|
||||
from sqlalchemy import DECIMAL, JSON, Column, Date, DateTime, Text
|
||||
from sqlalchemy.dialects.mysql import VARCHAR
|
||||
from sqlmodel import Field, Relationship, SQLModel
|
||||
|
||||
|
||||
class User(SQLModel, table=True):
|
||||
__tablename__ = "users" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
# 主键
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
|
||||
# 基本信息(匹配 migrations 中的结构)
|
||||
name: str = Field(max_length=32, unique=True, index=True) # 用户名
|
||||
safe_name: str = Field(max_length=32, unique=True, index=True) # 安全用户名
|
||||
email: str = Field(max_length=254, unique=True, index=True)
|
||||
priv: int = Field(default=1) # 权限
|
||||
pw_bcrypt: str = Field(max_length=60) # bcrypt 哈希密码
|
||||
country: str = Field(default="CN", max_length=2) # 国家代码
|
||||
|
||||
# 状态和时间
|
||||
silence_end: int = Field(default=0)
|
||||
donor_end: int = Field(default=0)
|
||||
creation_time: int = Field(default=0) # Unix 时间戳
|
||||
latest_activity: int = Field(default=0) # Unix 时间戳
|
||||
|
||||
# 游戏相关
|
||||
preferred_mode: int = Field(default=0) # 偏好游戏模式
|
||||
play_style: int = Field(default=0) # 游戏风格
|
||||
|
||||
# 扩展信息
|
||||
clan_id: int = Field(default=0)
|
||||
clan_priv: int = Field(default=0)
|
||||
custom_badge_name: str | None = Field(default=None, max_length=16)
|
||||
custom_badge_icon: str | None = Field(default=None, max_length=64)
|
||||
userpage_content: str | None = Field(default=None, max_length=2048)
|
||||
api_key: str | None = Field(default=None, max_length=36, unique=True)
|
||||
|
||||
# 虚拟字段用于兼容性
|
||||
@property
|
||||
def username(self):
|
||||
return self.name
|
||||
|
||||
@property
|
||||
def country_code(self):
|
||||
return self.country
|
||||
|
||||
@property
|
||||
def join_date(self):
|
||||
creation_time = getattr(self, "creation_time", 0)
|
||||
return (
|
||||
datetime.fromtimestamp(creation_time)
|
||||
if creation_time > 0
|
||||
else datetime.utcnow()
|
||||
)
|
||||
|
||||
@property
|
||||
def last_visit(self):
|
||||
latest_activity = getattr(self, "latest_activity", 0)
|
||||
return datetime.fromtimestamp(latest_activity) if latest_activity > 0 else None
|
||||
|
||||
# 关联关系
|
||||
lazer_profile: Optional["LazerUserProfile"] = Relationship(back_populates="user")
|
||||
lazer_statistics: list["LazerUserStatistics"] = Relationship(back_populates="user")
|
||||
lazer_counts: Optional["LazerUserCounts"] = Relationship(back_populates="user")
|
||||
lazer_achievements: list["LazerUserAchievement"] = Relationship(
|
||||
back_populates="user"
|
||||
)
|
||||
lazer_profile_sections: list["LazerUserProfileSections"] = Relationship(
|
||||
back_populates="user"
|
||||
)
|
||||
statistics: list[LegacyUserStatistics] = Relationship(back_populates="user")
|
||||
team_membership: list[TeamMember] = Relationship(back_populates="user")
|
||||
daily_challenge_stats: Optional["DailyChallengeStats"] = Relationship(
|
||||
back_populates="user"
|
||||
)
|
||||
rank_history: list["RankHistory"] = Relationship(back_populates="user")
|
||||
avatar: Optional["UserAvatar"] = Relationship(back_populates="user")
|
||||
active_banners: list["LazerUserBanners"] = Relationship(back_populates="user")
|
||||
lazer_badges: list["LazerUserBadge"] = Relationship(back_populates="user")
|
||||
lazer_monthly_playcounts: list["LazerUserMonthlyPlaycounts"] = Relationship(
|
||||
back_populates="user"
|
||||
)
|
||||
lazer_previous_usernames: list["LazerUserPreviousUsername"] = Relationship(
|
||||
back_populates="user"
|
||||
)
|
||||
lazer_replays_watched: list["LazerUserReplaysWatched"] = Relationship(
|
||||
back_populates="user"
|
||||
)
|
||||
|
||||
|
||||
# ============================================
|
||||
# Lazer API 专用表模型
|
||||
# ============================================
|
||||
|
||||
|
||||
class LazerUserProfile(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_profiles" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
user_id: int = Field(foreign_key="users.id", primary_key=True)
|
||||
|
||||
# 基本状态字段
|
||||
is_active: bool = Field(default=True)
|
||||
is_bot: bool = Field(default=False)
|
||||
is_deleted: bool = Field(default=False)
|
||||
is_online: bool = Field(default=True)
|
||||
is_supporter: bool = Field(default=False)
|
||||
is_restricted: bool = Field(default=False)
|
||||
session_verified: bool = Field(default=False)
|
||||
has_supported: bool = Field(default=False)
|
||||
pm_friends_only: bool = Field(default=False)
|
||||
|
||||
# 基本资料字段
|
||||
default_group: str = Field(default="default", max_length=50)
|
||||
last_visit: datetime | None = Field(default=None, sa_column=Column(DateTime))
|
||||
join_date: datetime | None = Field(default=None, sa_column=Column(DateTime))
|
||||
profile_colour: str | None = Field(default=None, max_length=7)
|
||||
profile_hue: int | None = Field(default=None)
|
||||
|
||||
# 社交媒体和个人资料字段
|
||||
avatar_url: str | None = Field(default=None, max_length=500)
|
||||
cover_url: str | None = Field(default=None, max_length=500)
|
||||
discord: str | None = Field(default=None, max_length=100)
|
||||
twitter: str | None = Field(default=None, max_length=100)
|
||||
website: str | None = Field(default=None, max_length=500)
|
||||
title: str | None = Field(default=None, max_length=100)
|
||||
title_url: str | None = Field(default=None, max_length=500)
|
||||
interests: str | None = Field(default=None, sa_column=Column(Text))
|
||||
location: str | None = Field(default=None, max_length=100)
|
||||
|
||||
occupation: str | None = Field(default=None) # 职业字段,默认为 None
|
||||
|
||||
# 游戏相关字段
|
||||
playmode: str = Field(default="osu", max_length=10)
|
||||
support_level: int = Field(default=0)
|
||||
max_blocks: int = Field(default=100)
|
||||
max_friends: int = Field(default=500)
|
||||
post_count: int = Field(default=0)
|
||||
|
||||
# 页面内容
|
||||
page_html: str | None = Field(default=None, sa_column=Column(Text))
|
||||
page_raw: str | None = Field(default=None, sa_column=Column(Text))
|
||||
|
||||
profile_order: str = Field(
|
||||
default="me,recent_activity,top_ranks,medals,historical,beatmaps,kudosu"
|
||||
)
|
||||
|
||||
# 关联关系
|
||||
user: "User" = Relationship(back_populates="lazer_profile")
|
||||
|
||||
|
||||
class LazerUserProfileSections(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_profile_sections" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
section_name: str = Field(sa_column=Column(VARCHAR(50)))
|
||||
display_order: int | None = Field(default=None)
|
||||
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
user: "User" = Relationship(back_populates="lazer_profile_sections")
|
||||
|
||||
|
||||
class LazerUserCountry(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_countries" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
user_id: int = Field(foreign_key="users.id", primary_key=True)
|
||||
code: str = Field(max_length=2)
|
||||
name: str = Field(max_length=100)
|
||||
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
|
||||
class LazerUserKudosu(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_kudosu" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
user_id: int = Field(foreign_key="users.id", primary_key=True)
|
||||
available: int = Field(default=0)
|
||||
total: int = Field(default=0)
|
||||
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
|
||||
class LazerUserCounts(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_counts" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
user_id: int = Field(foreign_key="users.id", primary_key=True)
|
||||
|
||||
# 统计计数字段
|
||||
beatmap_playcounts_count: int = Field(default=0)
|
||||
comments_count: int = Field(default=0)
|
||||
favourite_beatmapset_count: int = Field(default=0)
|
||||
follower_count: int = Field(default=0)
|
||||
graveyard_beatmapset_count: int = Field(default=0)
|
||||
guest_beatmapset_count: int = Field(default=0)
|
||||
loved_beatmapset_count: int = Field(default=0)
|
||||
mapping_follower_count: int = Field(default=0)
|
||||
nominated_beatmapset_count: int = Field(default=0)
|
||||
pending_beatmapset_count: int = Field(default=0)
|
||||
ranked_beatmapset_count: int = Field(default=0)
|
||||
ranked_and_approved_beatmapset_count: int = Field(default=0)
|
||||
unranked_beatmapset_count: int = Field(default=0)
|
||||
scores_best_count: int = Field(default=0)
|
||||
scores_first_count: int = Field(default=0)
|
||||
scores_pinned_count: int = Field(default=0)
|
||||
scores_recent_count: int = Field(default=0)
|
||||
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
# 关联关系
|
||||
user: "User" = Relationship(back_populates="lazer_counts")
|
||||
|
||||
|
||||
class LazerUserStatistics(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_statistics" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
user_id: int = Field(foreign_key="users.id", primary_key=True)
|
||||
mode: str = Field(default="osu", max_length=10, primary_key=True)
|
||||
|
||||
# 基本命中统计
|
||||
count_100: int = Field(default=0)
|
||||
count_300: int = Field(default=0)
|
||||
count_50: int = Field(default=0)
|
||||
count_miss: int = Field(default=0)
|
||||
|
||||
# 等级信息
|
||||
level_current: int = Field(default=1)
|
||||
level_progress: int = Field(default=0)
|
||||
|
||||
# 排名信息
|
||||
global_rank: int | None = Field(default=None)
|
||||
global_rank_exp: int | None = Field(default=None)
|
||||
country_rank: int | None = Field(default=None)
|
||||
|
||||
# PP 和分数
|
||||
pp: float = Field(default=0.00, sa_column=Column(DECIMAL(10, 2)))
|
||||
pp_exp: float = Field(default=0.00, sa_column=Column(DECIMAL(10, 2)))
|
||||
ranked_score: int = Field(default=0)
|
||||
hit_accuracy: float = Field(default=0.00, sa_column=Column(DECIMAL(5, 2)))
|
||||
total_score: int = Field(default=0)
|
||||
total_hits: int = Field(default=0)
|
||||
maximum_combo: int = Field(default=0)
|
||||
|
||||
# 游戏统计
|
||||
play_count: int = Field(default=0)
|
||||
play_time: int = Field(default=0) # 秒
|
||||
replays_watched_by_others: int = Field(default=0)
|
||||
is_ranked: bool = Field(default=False)
|
||||
|
||||
# 成绩等级计数
|
||||
grade_ss: int = Field(default=0)
|
||||
grade_ssh: int = Field(default=0)
|
||||
grade_s: int = Field(default=0)
|
||||
grade_sh: int = Field(default=0)
|
||||
grade_a: int = Field(default=0)
|
||||
|
||||
# 最高排名记录
|
||||
rank_highest: int | None = Field(default=None)
|
||||
rank_highest_updated_at: datetime | None = Field(
|
||||
default=None, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
# 关联关系
|
||||
user: "User" = Relationship(back_populates="lazer_statistics")
|
||||
|
||||
|
||||
class LazerUserBanners(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_tournament_banners" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
tournament_id: int
|
||||
image_url: str = Field(sa_column=Column(VARCHAR(500)))
|
||||
is_active: bool | None = Field(default=None)
|
||||
|
||||
# 修正user关系的back_populates值
|
||||
user: "User" = Relationship(back_populates="active_banners")
|
||||
|
||||
|
||||
class LazerUserAchievement(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_achievements" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
achievement_id: int
|
||||
achieved_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
user: "User" = Relationship(back_populates="lazer_achievements")
|
||||
|
||||
|
||||
class LazerUserBadge(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_badges" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
badge_id: int
|
||||
awarded_at: datetime | None = Field(default=None, sa_column=Column(DateTime))
|
||||
description: str | None = Field(default=None, sa_column=Column(Text))
|
||||
image_url: str | None = Field(default=None, max_length=500)
|
||||
url: str | None = Field(default=None, max_length=500)
|
||||
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
user: "User" = Relationship(back_populates="lazer_badges")
|
||||
|
||||
|
||||
class LazerUserMonthlyPlaycounts(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_monthly_playcounts" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
start_date: datetime = Field(sa_column=Column(Date))
|
||||
play_count: int = Field(default=0)
|
||||
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
user: "User" = Relationship(back_populates="lazer_monthly_playcounts")
|
||||
|
||||
|
||||
class LazerUserPreviousUsername(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_previous_usernames" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
username: str = Field(max_length=32)
|
||||
changed_at: datetime = Field(sa_column=Column(DateTime))
|
||||
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
user: "User" = Relationship(back_populates="lazer_previous_usernames")
|
||||
|
||||
|
||||
class LazerUserReplaysWatched(SQLModel, table=True):
|
||||
__tablename__ = "lazer_user_replays_watched" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
start_date: datetime = Field(sa_column=Column(Date))
|
||||
count: int = Field(default=0)
|
||||
|
||||
created_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
updated_at: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
user: "User" = Relationship(back_populates="lazer_replays_watched")
|
||||
|
||||
|
||||
# 类型转换用的 UserAchievement(不是 SQLAlchemy 模型)
|
||||
@dataclass
|
||||
class UserAchievement:
|
||||
achieved_at: datetime
|
||||
achievement_id: int
|
||||
|
||||
class DailyChallengeStats(SQLModel, table=True):
|
||||
__tablename__ = "daily_challenge_stats" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
user_id: int = Field(foreign_key="users.id", unique=True)
|
||||
|
||||
daily_streak_best: int = Field(default=0)
|
||||
daily_streak_current: int = Field(default=0)
|
||||
last_update: datetime | None = Field(default=None, sa_column=Column(DateTime))
|
||||
last_weekly_streak: datetime | None = Field(
|
||||
default=None, sa_column=Column(DateTime)
|
||||
)
|
||||
playcount: int = Field(default=0)
|
||||
top_10p_placements: int = Field(default=0)
|
||||
top_50p_placements: int = Field(default=0)
|
||||
weekly_streak_best: int = Field(default=0)
|
||||
weekly_streak_current: int = Field(default=0)
|
||||
|
||||
user: "User" = Relationship(back_populates="daily_challenge_stats")
|
||||
|
||||
|
||||
class RankHistory(SQLModel, table=True):
|
||||
__tablename__ = "rank_history" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
mode: str = Field(max_length=10)
|
||||
rank_data: list = Field(sa_column=Column(JSON)) # Array of ranks
|
||||
date_recorded: datetime = Field(
|
||||
default_factory=datetime.utcnow, sa_column=Column(DateTime)
|
||||
)
|
||||
|
||||
user: "User" = Relationship(back_populates="rank_history")
|
||||
|
||||
|
||||
|
||||
class UserAvatar(SQLModel, table=True):
|
||||
__tablename__ = "user_avatars" # pyright: ignore[reportAssignmentType]
|
||||
|
||||
id: int | None = Field(default=None, primary_key=True, index=True)
|
||||
user_id: int = Field(foreign_key="users.id")
|
||||
filename: str = Field(max_length=255)
|
||||
original_filename: str = Field(max_length=255)
|
||||
file_size: int
|
||||
mime_type: str = Field(max_length=100)
|
||||
is_active: bool = Field(default=True)
|
||||
created_at: int = Field(default_factory=lambda: int(datetime.now().timestamp()))
|
||||
updated_at: int = Field(default_factory=lambda: int(datetime.now().timestamp()))
|
||||
r2_original_url: str | None = Field(default=None, max_length=500)
|
||||
r2_game_url: str | None = Field(default=None, max_length=500)
|
||||
|
||||
user: "User" = Relationship(back_populates="avatar")
|
||||
Reference in New Issue
Block a user