diff --git a/.idea/.gitignore b/.idea/.gitignore
new file mode 100644
index 0000000..359bb53
--- /dev/null
+++ b/.idea/.gitignore
@@ -0,0 +1,3 @@
+# 默认忽略的文件
+/shelf/
+/workspace.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
new file mode 100644
index 0000000..d4add55
--- /dev/null
+++ b/.idea/inspectionProfiles/Project_Default.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 0000000..105ce2d
--- /dev/null
+++ b/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..e5787b9
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..92ff71d
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/osu_lazer_api.iml b/.idea/osu_lazer_api.iml
new file mode 100644
index 0000000..e2e520d
--- /dev/null
+++ b/.idea/osu_lazer_api.iml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/database copy.py b/app/database copy.py
deleted file mode 100644
index 457e349..0000000
--- a/app/database copy.py
+++ /dev/null
@@ -1,455 +0,0 @@
-from __future__ import annotations
-
-from datetime import datetime
-
-from sqlalchemy import (
- DECIMAL,
- JSON,
- Boolean,
- Column,
- DateTime,
- Float,
- ForeignKey,
- Integer,
- String,
- Text,
-)
-from sqlalchemy.ext.declarative import declarative_base
-from sqlalchemy.orm import relationship
-
-Base = declarative_base()
-
-
-class User(Base):
- __tablename__ = "users"
-
- # 主键
- id = Column(Integer, primary_key=True, index=True)
-
- # 基本信息(匹配 migrations 中的结构)
- name = Column(String(32), unique=True, index=True, nullable=False) # 用户名
- safe_name = Column(
- String(32), unique=True, index=True, nullable=False
- ) # 安全用户名
- email = Column(String(254), unique=True, index=True, nullable=False)
- priv = Column(Integer, default=1, nullable=False) # 权限
- pw_bcrypt = Column(String(60), nullable=False) # bcrypt 哈希密码
- country = Column(String(2), default="CN", nullable=False) # 国家代码
-
- # 状态和时间
- silence_end = Column(Integer, default=0, nullable=False)
- donor_end = Column(Integer, default=0, nullable=False)
- creation_time = Column(Integer, default=0, nullable=False) # Unix 时间戳
- latest_activity = Column(Integer, default=0, nullable=False) # Unix 时间戳
-
- # 游戏相关
- preferred_mode = Column(Integer, default=0, nullable=False) # 偏好游戏模式
- play_style = Column(Integer, default=0, nullable=False) # 游戏风格
-
- # 扩展信息
- clan_id = Column(Integer, default=0, nullable=False)
- clan_priv = Column(Integer, default=0, nullable=False)
- custom_badge_name = Column(String(16))
- custom_badge_icon = Column(String(64))
- userpage_content = Column(String(2048))
- api_key = Column(String(36), unique=True)
-
- # 虚拟字段用于兼容性
- @property
- def username(self):
- return self.name
-
- @property
- def country_code(self):
- return self.country
-
- @property
- def join_date(self):
- return (
- datetime.fromtimestamp(self.creation_time)
- if self.creation_time > 0
- else datetime.utcnow()
- )
-
- @property
- def last_visit(self):
- return (
- datetime.fromtimestamp(self.latest_activity)
- if self.latest_activity > 0
- else None
- )
-
- # 关联关系
- lazer_profile = relationship(
- "LazerUserProfile",
- back_populates="user",
- uselist=False,
- cascade="all, delete-orphan",
- )
- lazer_statistics = relationship(
- "LazerUserStatistics", back_populates="user", cascade="all, delete-orphan"
- )
- lazer_achievements = relationship(
- "LazerUserAchievement", back_populates="user", cascade="all, delete-orphan"
- )
-
-
-# ============================================
-# Lazer API 专用表模型
-# ============================================
-
-
-class LazerUserProfile(Base):
- __tablename__ = "lazer_user_profiles"
-
- user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
-
- # 基本状态字段
- is_active = Column(Boolean, default=True)
- is_bot = Column(Boolean, default=False)
- is_deleted = Column(Boolean, default=False)
- is_online = Column(Boolean, default=True)
- is_supporter = Column(Boolean, default=False)
- is_restricted = Column(Boolean, default=False)
- session_verified = Column(Boolean, default=False)
- has_supported = Column(Boolean, default=False)
- pm_friends_only = Column(Boolean, default=False)
-
- # 基本资料字段
- default_group = Column(String(50), default="default")
- last_visit = Column(DateTime)
- join_date = Column(DateTime)
- profile_colour = Column(String(7))
- profile_hue = Column(Integer)
-
- # 社交媒体和个人资料字段
- avatar_url = Column(String(500))
- cover_url = Column(String(500))
- discord = Column(String(100))
- twitter = Column(String(100))
- website = Column(String(500))
- title = Column(String(100))
- title_url = Column(String(500))
- interests = Column(Text)
- location = Column(String(100))
- occupation = Column(String(100))
-
- # 游戏相关字段
- playmode = Column(String(10), default="osu")
- support_level = Column(Integer, default=0)
- max_blocks = Column(Integer, default=100)
- max_friends = Column(Integer, default=500)
- post_count = Column(Integer, default=0)
-
- # 页面内容
- page_html = Column(Text)
- page_raw = Column(Text)
-
- created_at = Column(DateTime, default=datetime.utcnow)
- updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
-
- # 关联关系
- user = relationship("User", back_populates="lazer_profile")
-
-
-class LazerUserCountry(Base):
- __tablename__ = "lazer_user_countries"
-
- user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
- code = Column(String(2), nullable=False)
- name = Column(String(100), nullable=False)
-
- created_at = Column(DateTime, default=datetime.utcnow)
- updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
-
-
-class LazerUserKudosu(Base):
- __tablename__ = "lazer_user_kudosu"
-
- user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
- available = Column(Integer, default=0)
- total = Column(Integer, default=0)
-
- created_at = Column(DateTime, default=datetime.utcnow)
- updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
-
-
-class LazerUserCounts(Base):
- __tablename__ = "lazer_user_counts"
-
- user_id = Column(Integer, ForeignKey("users.id"), primary_key=True)
-
- # 统计计数字段
- beatmap_playcounts_count = Column(Integer, default=0)
- comments_count = Column(Integer, default=0)
- favourite_beatmapset_count = Column(Integer, default=0)
- follower_count = Column(Integer, default=0)
- graveyard_beatmapset_count = Column(Integer, default=0)
- guest_beatmapset_count = Column(Integer, default=0)
- loved_beatmapset_count = Column(Integer, default=0)
- mapping_follower_count = Column(Integer, default=0)
- nominated_beatmapset_count = Column(Integer, default=0)
- pending_beatmapset_count = Column(Integer, default=0)
- ranked_beatmapset_count = Column(Integer, default=0)
- ranked_and_approved_beatmapset_count = Column(Integer, default=0)
- unranked_beatmapset_count = Column(Integer, default=0)
- scores_best_count = Column(Integer, default=0)
- scores_first_count = Column(Integer, default=0)
- scores_pinned_count = Column(Integer, default=0)
- scores_recent_count = Column(Integer, default=0)
-
- created_at = Column(DateTime, default=datetime.utcnow)
- updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
-
-
-class LazerUserStatistics(Base):
- __tablename__ = "lazer_user_statistics"
-
- user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
- mode = Column(String(10), nullable=False, default="osu")
-
- # 基本命中统计
- count_100 = Column(Integer, default=0)
- count_300 = Column(Integer, default=0)
- count_50 = Column(Integer, default=0)
- count_miss = Column(Integer, default=0)
-
- # 等级信息
- level_current = Column(Integer, default=1)
- level_progress = Column(Integer, default=0)
-
- # 排名信息
- global_rank = Column(Integer)
- global_rank_exp = Column(Integer)
- country_rank = Column(Integer)
-
- # PP 和分数
- pp = Column(DECIMAL(10, 2), default=0.00)
- pp_exp = Column(DECIMAL(10, 2), default=0.00)
- ranked_score = Column(Integer, default=0)
- hit_accuracy = Column(DECIMAL(5, 2), default=0.00)
- total_score = Column(Integer, default=0)
- total_hits = Column(Integer, default=0)
- maximum_combo = Column(Integer, default=0)
-
-
-# ============================================
-# 旧的兼容性表模型(保留以便向后兼容)
-# ============================================
-
-
-class LegacyUserStatistics(Base):
- __tablename__ = "user_statistics"
-
- id = Column(Integer, primary_key=True, index=True)
- user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
- mode = Column(String(10), nullable=False) # osu, taiko, fruits, mania
-
- # 基本统计
- count_100 = Column(Integer, default=0)
- count_300 = Column(Integer, default=0)
- count_50 = Column(Integer, default=0)
- count_miss = Column(Integer, default=0)
-
- # 等级信息
- level_current = Column(Integer, default=1)
- level_progress = Column(Integer, default=0)
-
- # 排名信息
- global_rank = Column(Integer)
- global_rank_exp = Column(Integer)
- country_rank = Column(Integer)
-
- # PP 和分数
- pp = Column(Float, default=0.0)
- pp_exp = Column(Float, default=0.0)
- ranked_score = Column(Integer, default=0)
- hit_accuracy = Column(Float, default=0.0)
- total_score = Column(Integer, default=0)
- total_hits = Column(Integer, default=0)
- maximum_combo = Column(Integer, default=0)
-
- # 游戏统计
- play_count = Column(Integer, default=0)
- play_time = Column(Integer, default=0)
- replays_watched_by_others = Column(Integer, default=0)
- is_ranked = Column(Boolean, default=False)
-
- # 成绩等级计数
- grade_ss = Column(Integer, default=0)
- grade_ssh = Column(Integer, default=0)
- grade_s = Column(Integer, default=0)
- grade_sh = Column(Integer, default=0)
- grade_a = Column(Integer, default=0)
-
-
-class OAuthToken(Base):
- __tablename__ = "oauth_tokens"
-
- id = Column(Integer, primary_key=True, autoincrement=True)
- user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
- access_token = Column(String(255), nullable=False, index=True)
- refresh_token = Column(String(255), nullable=False, index=True)
- expires_at = Column(DateTime, nullable=False)
- created_at = Column(DateTime, default=datetime.utcnow)
- updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
- previous_usernames = Column(JSON, default=list)
- replays_watched_counts = Column(JSON, default=list)
-
- # 关联关系
- statistics = relationship(
- "UserStatistics", back_populates="user", cascade="all, delete-orphan"
- )
- achievements = relationship(
- "UserAchievement", back_populates="user", cascade="all, delete-orphan"
- )
- team_membership = relationship(
- "TeamMember", back_populates="user", cascade="all, delete-orphan"
- )
- daily_challenge_stats = relationship(
- "DailyChallengeStats",
- back_populates="user",
- uselist=False,
- cascade="all, delete-orphan",
- )
- rank_history = relationship(
- "RankHistory", back_populates="user", cascade="all, delete-orphan"
- )
-
-
-class UserStatistics(Base):
- __tablename__ = "user_statistics"
-
- id = Column(Integer, primary_key=True, index=True)
- user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
- mode = Column(String(10), nullable=False) # osu, taiko, fruits, mania
-
- # 基本统计
- count_100 = Column(Integer, default=0)
- count_300 = Column(Integer, default=0)
- count_50 = Column(Integer, default=0)
- count_miss = Column(Integer, default=0)
-
- # 等级信息
- level_current = Column(Integer, default=1)
- level_progress = Column(Integer, default=0)
-
- # 排名信息
- global_rank = Column(Integer)
- global_rank_exp = Column(Integer)
- country_rank = Column(Integer)
-
- # PP 和分数
- pp = Column(Float, default=0.0)
- pp_exp = Column(Float, default=0.0)
- ranked_score = Column(Integer, default=0)
- hit_accuracy = Column(Float, default=0.0)
- total_score = Column(Integer, default=0)
- total_hits = Column(Integer, default=0)
- maximum_combo = Column(Integer, default=0)
-
- # 游戏统计
- play_count = Column(Integer, default=0)
- play_time = Column(Integer, default=0) # 秒
- replays_watched_by_others = Column(Integer, default=0)
- is_ranked = Column(Boolean, default=False)
-
- # 成绩等级计数
- grade_ss = Column(Integer, default=0)
- grade_ssh = Column(Integer, default=0)
- grade_s = Column(Integer, default=0)
- grade_sh = Column(Integer, default=0)
- grade_a = Column(Integer, default=0)
-
- # 最高排名记录
- rank_highest = Column(Integer)
- rank_highest_updated_at = Column(DateTime)
-
- created_at = Column(DateTime, default=datetime.utcnow)
- updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
-
- # 关联关系
- user = relationship("User", back_populates="statistics")
-
-
-class UserAchievement(Base):
- __tablename__ = "user_achievements"
-
- id = Column(Integer, primary_key=True, index=True)
- user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
- achievement_id = Column(Integer, nullable=False)
- achieved_at = Column(DateTime, default=datetime.utcnow)
-
- user = relationship("User", back_populates="achievements")
-
-
-class Team(Base):
- __tablename__ = "teams"
-
- id = Column(Integer, primary_key=True, index=True)
- name = Column(String(100), nullable=False)
- short_name = Column(String(10), nullable=False)
- flag_url = Column(String(500))
- created_at = Column(DateTime, default=datetime.utcnow)
-
- members = relationship(
- "TeamMember", back_populates="team", cascade="all, delete-orphan"
- )
-
-
-class TeamMember(Base):
- __tablename__ = "team_members"
-
- id = Column(Integer, primary_key=True, index=True)
- user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
- team_id = Column(Integer, ForeignKey("teams.id"), nullable=False)
- joined_at = Column(DateTime, default=datetime.utcnow)
-
- user = relationship("User", back_populates="team_membership")
- team = relationship("Team", back_populates="members")
-
-
-class DailyChallengeStats(Base):
- __tablename__ = "daily_challenge_stats"
-
- id = Column(Integer, primary_key=True, index=True)
- user_id = Column(Integer, ForeignKey("users.id"), nullable=False, unique=True)
-
- daily_streak_best = Column(Integer, default=0)
- daily_streak_current = Column(Integer, default=0)
- last_update = Column(DateTime)
- last_weekly_streak = Column(DateTime)
- playcount = Column(Integer, default=0)
- top_10p_placements = Column(Integer, default=0)
- top_50p_placements = Column(Integer, default=0)
- weekly_streak_best = Column(Integer, default=0)
- weekly_streak_current = Column(Integer, default=0)
-
- user = relationship("User", back_populates="daily_challenge_stats")
-
-
-class RankHistory(Base):
- __tablename__ = "rank_history"
-
- id = Column(Integer, primary_key=True, index=True)
- user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
- mode = Column(String(10), nullable=False)
- rank_data = Column(JSON, nullable=False) # Array of ranks
- date_recorded = Column(DateTime, default=datetime.utcnow)
-
- user = relationship("User", back_populates="rank_history")
-
-
-class OAuthToken(Base):
- __tablename__ = "oauth_tokens"
-
- id = Column(Integer, primary_key=True, index=True)
- user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
- access_token = Column(String(500), unique=True, nullable=False)
- refresh_token = Column(String(500), unique=True, nullable=False)
- token_type = Column(String(20), default="Bearer")
- scope = Column(String(100), default="*")
- expires_at = Column(DateTime, nullable=False)
- created_at = Column(DateTime, default=datetime.utcnow)
-
- user = relationship("User")
diff --git a/app/database.py b/app/database.py
index 449ca98..31d3d6a 100644
--- a/app/database.py
+++ b/app/database.py
@@ -8,6 +8,7 @@ from sqlalchemy import (
JSON,
Boolean,
Column,
+ Date,
DateTime,
Float,
ForeignKey,
@@ -15,6 +16,7 @@ from sqlalchemy import (
String,
Text,
)
+from sqlalchemy.dialects.mysql import TINYINT, VARCHAR
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
@@ -91,11 +93,19 @@ class User(Base):
lazer_achievements = relationship(
"LazerUserAchievement", back_populates="user", cascade="all, delete-orphan"
)
+ lazer_profile_sections = relationship(
+ "LazerUserProfileSections", # 修正类名拼写(添加s)
+ back_populates="user",
+ cascade="all, delete-orphan",
+ )
statistics = relationship(
"LegacyUserStatistics", back_populates="user", cascade="all, delete-orphan"
)
achievements = relationship(
- "LazerUserAchievement", back_populates="user", cascade="all, delete-orphan"
+ "LazerUserAchievement",
+ back_populates="user",
+ cascade="all, delete-orphan",
+ overlaps="lazer_achievements",
)
team_membership = relationship(
"TeamMember", back_populates="user", cascade="all, delete-orphan"
@@ -115,6 +125,28 @@ class User(Base):
primaryjoin="and_(User.id==UserAvatar.user_id, UserAvatar.is_active==True)",
uselist=False,
)
+ active_banners = relationship(
+ "LazerUserBanners", # 原定义指向LazerUserBanners,实际应为UserAvatar
+ back_populates="user",
+ primaryjoin=(
+ "and_(User.id==LazerUserBanners.user_id, LazerUserBanners.is_active==True)"
+ ),
+ uselist=False,
+ )
+ lazer_badges = relationship(
+ "LazerUserBadge", back_populates="user", cascade="all, delete-orphan"
+ )
+ lazer_monthly_playcounts = relationship(
+ "LazerUserMonthlyPlaycounts",
+ back_populates="user",
+ cascade="all, delete-orphan",
+ )
+ lazer_previous_usernames = relationship(
+ "LazerUserPreviousUsername", back_populates="user", cascade="all, delete-orphan"
+ )
+ lazer_replays_watched = relationship(
+ "LazerUserReplaysWatched", back_populates="user", cascade="all, delete-orphan"
+ )
# ============================================
@@ -176,6 +208,20 @@ class LazerUserProfile(Base):
user = relationship("User", back_populates="lazer_profile")
+class LazerUserProfileSections(Base):
+ __tablename__ = "lazer_user_profile_sections"
+
+ id = Column(Integer, primary_key=True)
+ user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
+ section_name = Column(VARCHAR(50), nullable=False)
+ display_order = Column(Integer)
+
+ created_at = Column(DateTime, default=datetime.utcnow)
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
+
+ user = relationship("User", back_populates="lazer_profile_sections")
+
+
class LazerUserCountry(Base):
__tablename__ = "lazer_user_countries"
@@ -280,6 +326,22 @@ class LazerUserStatistics(Base):
user = relationship("User", back_populates="lazer_statistics")
+class LazerUserBanners(Base):
+ __tablename__ = "lazer_user_tournament_banners"
+
+ id = Column(Integer, primary_key=True)
+ user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
+ tournament_id = Column(Integer, nullable=False)
+ image_url = Column(VARCHAR(500), nullable=False)
+ is_active = Column(TINYINT(1))
+
+ # 修正user关系的back_populates值
+ user = relationship(
+ "User",
+ back_populates="active_banners", # 改为实际存在的属性名
+ )
+
+
class LazerUserAchievement(Base):
__tablename__ = "lazer_user_achievements"
@@ -294,6 +356,65 @@ class LazerUserAchievement(Base):
user = relationship("User", back_populates="lazer_achievements")
+class LazerUserBadge(Base):
+ __tablename__ = "lazer_user_badges"
+
+ id = Column(Integer, primary_key=True, index=True)
+ user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
+ badge_id = Column(Integer, nullable=False)
+ awarded_at = Column(DateTime)
+ description = Column(Text)
+ image_url = Column(String(500))
+ url = Column(String(500))
+
+ created_at = Column(DateTime, default=datetime.utcnow)
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
+
+ user = relationship("User", back_populates="lazer_badges")
+
+
+class LazerUserMonthlyPlaycounts(Base):
+ __tablename__ = "lazer_user_monthly_playcounts"
+
+ id = Column(Integer, primary_key=True, index=True)
+ user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
+ start_date = Column(Date, nullable=False)
+ play_count = Column(Integer, default=0)
+
+ created_at = Column(DateTime, default=datetime.utcnow)
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
+
+ user = relationship("User", back_populates="lazer_monthly_playcounts")
+
+
+class LazerUserPreviousUsername(Base):
+ __tablename__ = "lazer_user_previous_usernames"
+
+ id = Column(Integer, primary_key=True, index=True)
+ user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
+ username = Column(String(32), nullable=False)
+ changed_at = Column(DateTime, nullable=False)
+
+ created_at = Column(DateTime, default=datetime.utcnow)
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
+
+ user = relationship("User", back_populates="lazer_previous_usernames")
+
+
+class LazerUserReplaysWatched(Base):
+ __tablename__ = "lazer_user_replays_watched"
+
+ id = Column(Integer, primary_key=True, index=True)
+ user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
+ start_date = Column(Date, nullable=False)
+ count = Column(Integer, default=0)
+
+ created_at = Column(DateTime, default=datetime.utcnow)
+ updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
+
+ user = relationship("User", back_populates="lazer_replays_watched")
+
+
# ============================================
# 旧的兼容性表模型(保留以便向后兼容)
# ============================================
diff --git a/app/utils.py b/app/utils.py
index ec7e6aa..75021a8 100644
--- a/app/utils.py
+++ b/app/utils.py
@@ -7,12 +7,10 @@ from app.models import (
Country,
Cover,
DailyChallengeStats,
- GameMode,
GradeCounts,
Kudosu,
Level,
Page,
- PlayStyle,
RankHighest,
RankHistory,
Statistics,
@@ -41,6 +39,13 @@ def convert_db_user_to_api_user(
# 如果没有 lazer 资料,使用默认值
profile = create_default_profile(db_user)
+ # 获取 Lazer 用户计数
+
+ lzrcnt = db_user.lazer_statistics
+ if not lzrcnt:
+ # 如果没有 lazer 计数,使用默认值
+ lzrcnt = create_default_counts()
+
# 获取指定模式的统计信息
user_stats = None
for stat in db_user.lazer_statistics:
@@ -53,9 +58,9 @@ def convert_db_user_to_api_user(
user_stats = create_default_lazer_statistics(ruleset)
# 获取国家信息
- country = Country(
- code=user_country_code, name=get_country_name(user_country_code)
- )
+ country_code = db_user.country_code if db_user.country_code else "XX"
+
+ country = Country(code=country_code, name=get_country_name(country_code))
# 获取 Kudosu 信息
kudosu = Kudosu(available=0, total=0)
@@ -249,11 +254,99 @@ def convert_db_user_to_api_user(
if avatar_url is None:
avatar_url = "https://a.gu-osu.gmoe.cc/api/users/avatar/1"
+ # 处理 profile_order 列表排序
+ profile_order = [
+ "me",
+ "recent_activity",
+ "top_ranks",
+ "medals",
+ "historical",
+ "beatmaps",
+ "kudosu",
+ ]
+ if profile and profile.profile_order:
+ profile_order = profile.profile_order
+
+ # 在convert_db_user_to_api_user函数中添加active_tournament_banners处理
+ active_tournament_banners = []
+ if (
+ hasattr(db_user, "lazer_tournament_banners")
+ and db_user.lazer_tournament_banners
+ ):
+ for banner in db_user.lazer_tournament_banners:
+ active_tournament_banners.append(
+ {
+ "tournament_id": banner.tournament_id,
+ "image_url": banner.image_url,
+ "is_active": banner.is_active,
+ }
+ )
+
+ # 在convert_db_user_to_api_user函数中添加badges处理
+ badges = []
+ if hasattr(db_user, "lazer_badges") and db_user.lazer_badges:
+ for badge in db_user.lazer_badges:
+ badges.append(
+ {
+ "badge_id": badge.badge_id,
+ "awarded_at": badge.awarded_at,
+ "description": badge.description,
+ "image_url": badge.image_url,
+ "url": badge.url,
+ }
+ )
+
+ # 在convert_db_user_to_api_user函数中添加monthly_playcounts处理
+ monthly_playcounts = []
+ if (
+ hasattr(db_user, "lazer_monthly_playcounts")
+ and db_user.lazer_monthly_playcounts
+ ):
+ for playcount in db_user.lazer_monthly_playcounts:
+ monthly_playcounts.append(
+ {
+ "start_date": playcount.start_date.isoformat()
+ if playcount.start_date
+ else None,
+ "play_count": playcount.play_count,
+ }
+ )
+
+ # 在convert_db_user_to_api_user函数中添加previous_usernames处理
+ previous_usernames = []
+ if (
+ hasattr(db_user, "lazer_previous_usernames")
+ and db_user.lazer_previous_usernames
+ ):
+ for username in db_user.lazer_previous_usernames:
+ previous_usernames.append(
+ {
+ "username": username.username,
+ "changed_at": username.changed_at.isoformat()
+ if username.changed_at
+ else None,
+ }
+ )
+
+ # 在convert_db_user_to_api_user函数中添加replays_watched_counts处理
+ replays_watched_counts = []
+ if hasattr(db_user, "lazer_replays_watched") and db_user.lazer_replays_watched:
+ for replay in db_user.lazer_replays_watched:
+ replays_watched_counts.append(
+ {
+ "start_date": replay.start_date.isoformat()
+ if replay.start_date
+ else None,
+ "count": replay.count,
+ }
+ )
+
+ # 创建用户对象
user = User(
id=user_id,
username=user_name,
- avatar_url=avatar_url, # 使用我们上面获取的头像URL
- country_code=user_country,
+ avatar_url=avatar_url,
+ country_code=country_code,
default_group=profile.default_group if profile else "default",
is_active=profile.is_active if profile else True,
is_bot=profile.is_bot if profile else False,
@@ -261,75 +354,68 @@ def convert_db_user_to_api_user(
is_online=profile.is_online if profile else True,
is_supporter=profile.is_supporter if profile else False,
is_restricted=profile.is_restricted if profile else False,
- last_visit=db_user.last_visit,
+ last_visit=db_user.last_visit if db_user.last_visit else None,
pm_friends_only=profile.pm_friends_only if profile else False,
profile_colour=profile.profile_colour if profile else None,
- cover_url=cover_url,
+ cover_url=profile.cover_url
+ if profile and profile.cover_url
+ else "https://assets.ppy.sh/user-profile-covers/default.jpeg",
discord=profile.discord if profile else None,
has_supported=profile.has_supported if profile else False,
interests=profile.interests if profile else None,
- join_date=db_user.join_date,
+ join_date=profile.join_date,
location=profile.location if profile else None,
- max_blocks=profile.max_blocks if profile else 100,
- max_friends=profile.max_friends if profile else 500,
- occupation=None, # 职业字段,默认为 None #待修改
- # playmode=GameMode(db_user.playmode),
- playmode=GameMode("osu"), # 待修改
- playstyle=[PlayStyle.MOUSE, PlayStyle.KEYBOARD, PlayStyle.TABLET], # 待修改
- post_count=0,
- profile_hue=None,
- profile_order=[
- "me",
- "recent_activity",
- "top_ranks",
- "medals",
- "historical",
- "beatmaps",
- "kudosu",
- ],
- title=None,
- title_url=None,
- twitter=None,
- website="https://gmoe.cc",
+ max_blocks=profile.max_blocks if profile and profile.max_blocks else 100,
+ max_friends=profile.max_friends if profile and profile.max_friends else 500,
+ post_count=profile.post_count if profile and profile.post_count else 0,
+ profile_hue=profile.profile_hue if profile and profile.profile_hue else None,
+ profile_order=profile_order, # 使用排序后的 profile_order
+ title=profile.title if profile else None,
+ title_url=profile.title_url if profile else None,
+ twitter=profile.twitter if profile else None,
+ website=profile.website if profile else None,
session_verified=True,
- support_level=0,
+ support_level=profile.support_level if profile else 0,
country=country,
cover=cover,
kudosu=kudosu,
statistics=statistics,
statistics_rulesets=statistics_rulesets,
- beatmap_playcounts_count=3306,
- comments_count=0,
- favourite_beatmapset_count=0,
- follower_count=0,
- graveyard_beatmapset_count=0,
- guest_beatmapset_count=0,
- loved_beatmapset_count=0,
- mapping_follower_count=0,
- nominated_beatmapset_count=0,
- pending_beatmapset_count=0,
- ranked_beatmapset_count=0,
- ranked_and_approved_beatmapset_count=0,
- unranked_beatmapset_count=0,
- scores_best_count=0,
- scores_first_count=0,
- scores_pinned_count=0,
- scores_recent_count=0,
- account_history=[],
- active_tournament_banner=None,
- active_tournament_banners=[],
- badges=[],
+ beatmap_playcounts_count=lzrcnt.beatmap_playcounts_count if lzrcnt else 0,
+ comments_count=lzrcnt.comments_count if lzrcnt else 0,
+ favourite_beatmapset_count=lzrcnt.favourite_beatmapset_count if lzrcnt else 0,
+ follower_count=lzrcnt.follower_count if lzrcnt else 0,
+ graveyard_beatmapset_count=lzrcnt.graveyard_beatmapset_count if lzrcnt else 0,
+ guest_beatmapset_count=lzrcnt.guest_beatmapset_count if lzrcnt else 0,
+ loved_beatmapset_count=lzrcnt.loved_beatmapset_count if lzrcnt else 0,
+ mapping_follower_count=lzrcnt.mapping_follower_count if lzrcnt else 0,
+ nominated_beatmapset_count=lzrcnt.nominated_beatmapset_count if lzrcnt else 0,
+ pending_beatmapset_count=lzrcnt.pending_beatmapset_count if lzrcnt else 0,
+ ranked_beatmapset_count=lzrcnt.ranked_beatmapset_count if lzrcnt else 0,
+ ranked_and_approved_beatmapset_count=lzrcnt.ranked_and_approved_beatmapset_count
+ if lzrcnt
+ else 0,
+ unranked_beatmapset_count=lzrcnt.unranked_beatmapset_count if lzrcnt else 0,
+ scores_best_count=lzrcnt.scores_best_count if lzrcnt else 0,
+ scores_first_count=lzrcnt.scores_first_count if lzrcnt else 0,
+ scores_pinned_count=lzrcnt.scores_pinned_count,
+ scores_recent_count=lzrcnt.scores_recent_count if lzrcnt else 0,
+ account_history=[], # TODO: 获取用户历史账户信息
+ # active_tournament_banner=len(active_tournament_banners),
+ active_tournament_banners=active_tournament_banners,
+ badges=badges,
current_season_stats=None,
daily_challenge_user_stats=None,
groups=[],
- monthly_playcounts=[],
- # page=Page(html=db_user.page_html, raw=db_user.page_raw),
- page=Page(), # Provide a default Page object
- previous_usernames=[],
+ monthly_playcounts=monthly_playcounts,
+ page=Page(html=profile.page_html, raw=profile.page_raw)
+ if profile.page_html or profile.page_raw
+ else Page(),
+ previous_usernames=previous_usernames,
rank_highest=rank_highest,
rank_history=rank_history,
- rankHistory=rank_history, # 兼容性别名
- replays_watched_counts=[],
+ rankHistory=rank_history,
+ replays_watched_counts=replays_watched_counts,
team=team,
user_achievements=user_achievements,
)
@@ -358,6 +444,7 @@ def get_country_name(country_code: str) -> str:
def create_default_profile(db_user: DBUser):
"""创建默认的用户资料"""
+ # 完善 MockProfile 类定义
class MockProfile:
def __init__(self):
self.is_active = True
@@ -371,7 +458,7 @@ def create_default_profile(db_user: DBUser):
self.pm_friends_only = False
self.default_group = "default"
self.last_visit = None
- self.join_date = db_user.join_date
+ self.join_date = db_user.join_date if db_user else datetime.utcnow()
self.profile_colour = None
self.profile_hue = None
self.avatar_url = None
@@ -389,8 +476,23 @@ def create_default_profile(db_user: DBUser):
self.max_blocks = 100
self.max_friends = 500
self.post_count = 0
- self.page_html = None
- self.page_raw = None
+ # 添加profile_order字段
+ self.profile_order = [
+ "me",
+ "recent_activity",
+ "top_ranks",
+ "medals",
+ "historical",
+ "beatmaps",
+ "kudosu",
+ ]
+ self.page_html = ""
+ self.page_raw = ""
+ # 在MockProfile类中添加active_tournament_banners字段
+ self.active_tournament_banners = (
+ MockLazerTournamentBanner.create_default_banners()
+ )
+ self.active_tournament_banners = [] # 默认空列表
return MockProfile()
@@ -413,11 +515,11 @@ def create_default_lazer_statistics(mode: str):
self.pp_exp = 0.0
self.ranked_score = 0
self.hit_accuracy = 0.0
- self.play_count = 0
- self.play_time = 0
self.total_score = 0
self.total_hits = 0
self.maximum_combo = 0
+ self.play_count = 0
+ self.play_time = 0
self.replays_watched_by_others = 0
self.is_ranked = False
self.grade_ss = 0
@@ -459,7 +561,10 @@ def create_default_counts():
class MockCounts:
def __init__(self):
+ self.recent_scores_count = None
self.beatmap_playcounts_count = 0
+ self.scores_first_count = 0
+ self.scores_pinned_count = 0
self.comments_count = 0
self.favourite_beatmapset_count = 0
self.follower_count = 0
@@ -478,3 +583,18 @@ def create_default_counts():
self.scores_recent_count = 0
return MockCounts()
+
+
+class MockLazerTournamentBanner:
+ def __init__(self, tournament_id: int, image_url: str, is_active: bool = True):
+ self.tournament_id = tournament_id
+ self.image_url = image_url
+ self.is_active = is_active
+
+ @staticmethod
+ def create_default_banners():
+ """创建默认的锦标赛横幅配置"""
+ return [
+ MockLazerTournamentBanner(1, "https://example.com/banner1.jpg", True),
+ MockLazerTournamentBanner(2, "https://example.com/banner2.jpg", False),
+ ]
diff --git a/create_sample_data.py b/create_sample_data.py
index ce8695b..d2d7ba8 100644
--- a/create_sample_data.py
+++ b/create_sample_data.py
@@ -12,12 +12,12 @@ from app.auth import get_password_hash
from app.database import (
Base,
DailyChallengeStats,
- LazerUserStatistics as UserStatistics,
+ LazerUserStatistics,
RankHistory,
User,
UserAchievement,
)
-from app.dependencies import engine, get_db
+from app.dependencies.database import engine, get_db
# 创建所有表
Base.metadata.create_all(bind=engine)
@@ -58,92 +58,92 @@ def create_sample_user():
custom_badge_icon=None,
userpage_content="「世界に忘れられた」",
api_key=None,
- # # 兼容性字段
- # avatar_url="https://a.ppy.sh/15651670?1732362658.jpeg",
- # cover_url="https://assets.ppy.sh/user-profile-covers/15651670/0fc7b77adef39765a570e7f535bc383e5a848850d41a8943f8857984330b8bc6.jpeg",
- # has_supported=True,
- # interests="「世界に忘れられた」",
- # location="咕谷国",
- # website="https://gmoe.cc",
- # playstyle=["mouse", "keyboard", "tablet"],
- # profile_order=[
- # "me",
- # "recent_activity",
- # "top_ranks",
- # "medals",
- # "historical",
- # "beatmaps",
- # "kudosu",
- # ],
- # beatmap_playcounts_count=3306,
- # favourite_beatmapset_count=15,
- # follower_count=98,
- # graveyard_beatmapset_count=7,
- # mapping_follower_count=1,
- # previous_usernames=["hehejun"],
- # monthly_playcounts=[
- # {"start_date": "2019-11-01", "count": 43},
- # {"start_date": "2020-04-01", "count": 216},
- # {"start_date": "2020-05-01", "count": 656},
- # {"start_date": "2020-07-01", "count": 158},
- # {"start_date": "2020-08-01", "count": 174},
- # {"start_date": "2020-10-01", "count": 13},
- # {"start_date": "2020-11-01", "count": 52},
- # {"start_date": "2020-12-01", "count": 140},
- # {"start_date": "2021-01-01", "count": 359},
- # {"start_date": "2021-02-01", "count": 452},
- # {"start_date": "2021-03-01", "count": 77},
- # {"start_date": "2021-04-01", "count": 114},
- # {"start_date": "2021-05-01", "count": 270},
- # {"start_date": "2021-06-01", "count": 148},
- # {"start_date": "2021-07-01", "count": 246},
- # {"start_date": "2021-08-01", "count": 56},
- # {"start_date": "2021-09-01", "count": 136},
- # {"start_date": "2021-10-01", "count": 45},
- # {"start_date": "2021-11-01", "count": 98},
- # {"start_date": "2021-12-01", "count": 54},
- # {"start_date": "2022-01-01", "count": 88},
- # {"start_date": "2022-02-01", "count": 45},
- # {"start_date": "2022-03-01", "count": 6},
- # {"start_date": "2022-04-01", "count": 54},
- # {"start_date": "2022-05-01", "count": 105},
- # {"start_date": "2022-06-01", "count": 37},
- # {"start_date": "2022-07-01", "count": 88},
- # {"start_date": "2022-08-01", "count": 7},
- # {"start_date": "2022-09-01", "count": 9},
- # {"start_date": "2022-10-01", "count": 6},
- # {"start_date": "2022-11-01", "count": 2},
- # {"start_date": "2022-12-01", "count": 16},
- # {"start_date": "2023-01-01", "count": 7},
- # {"start_date": "2023-04-01", "count": 16},
- # {"start_date": "2023-05-01", "count": 3},
- # {"start_date": "2023-06-01", "count": 8},
- # {"start_date": "2023-07-01", "count": 23},
- # {"start_date": "2023-08-01", "count": 3},
- # {"start_date": "2023-09-01", "count": 1},
- # {"start_date": "2023-10-01", "count": 25},
- # {"start_date": "2023-11-01", "count": 160},
- # {"start_date": "2023-12-01", "count": 306},
- # {"start_date": "2024-01-01", "count": 735},
- # {"start_date": "2024-02-01", "count": 420},
- # {"start_date": "2024-03-01", "count": 549},
- # {"start_date": "2024-04-01", "count": 466},
- # {"start_date": "2024-05-01", "count": 333},
- # {"start_date": "2024-06-01", "count": 1126},
- # {"start_date": "2024-07-01", "count": 534},
- # {"start_date": "2024-08-01", "count": 280},
- # {"start_date": "2024-09-01", "count": 116},
- # {"start_date": "2024-10-01", "count": 120},
- # {"start_date": "2024-11-01", "count": 332},
- # {"start_date": "2024-12-01", "count": 243},
- # {"start_date": "2025-01-01", "count": 122},
- # {"start_date": "2025-02-01", "count": 379},
- # {"start_date": "2025-03-01", "count": 278},
- # {"start_date": "2025-04-01", "count": 296},
- # {"start_date": "2025-05-01", "count": 964},
- # {"start_date": "2025-06-01", "count": 821},
- # {"start_date": "2025-07-01", "count": 230},
- # ],
+ # # 兼容性字段
+ # avatar_url="https://a.ppy.sh/15651670?1732362658.jpeg",
+ # cover_url="https://assets.ppy.sh/user-profile-covers/15651670/0fc7b77adef39765a570e7f535bc383e5a848850d41a8943f8857984330b8bc6.jpeg",
+ # has_supported=True,
+ # interests="「世界に忘れられた」",
+ # location="咕谷国",
+ # website="https://gmoe.cc",
+ # playstyle=["mouse", "keyboard", "tablet"],
+ # profile_order=[
+ # "me",
+ # "recent_activity",
+ # "top_ranks",
+ # "medals",
+ # "historical",
+ # "beatmaps",
+ # "kudosu",
+ # ],
+ # beatmap_playcounts_count=3306,
+ # favourite_beatmapset_count=15,
+ # follower_count=98,
+ # graveyard_beatmapset_count=7,
+ # mapping_follower_count=1,
+ # previous_usernames=["hehejun"],
+ # monthly_playcounts=[
+ # {"start_date": "2019-11-01", "count": 43},
+ # {"start_date": "2020-04-01", "count": 216},
+ # {"start_date": "2020-05-01", "count": 656},
+ # {"start_date": "2020-07-01", "count": 158},
+ # {"start_date": "2020-08-01", "count": 174},
+ # {"start_date": "2020-10-01", "count": 13},
+ # {"start_date": "2020-11-01", "count": 52},
+ # {"start_date": "2020-12-01", "count": 140},
+ # {"start_date": "2021-01-01", "count": 359},
+ # {"start_date": "2021-02-01", "count": 452},
+ # {"start_date": "2021-03-01", "count": 77},
+ # {"start_date": "2021-04-01", "count": 114},
+ # {"start_date": "2021-05-01", "count": 270},
+ # {"start_date": "2021-06-01", "count": 148},
+ # {"start_date": "2021-07-01", "count": 246},
+ # {"start_date": "2021-08-01", "count": 56},
+ # {"start_date": "2021-09-01", "count": 136},
+ # {"start_date": "2021-10-01", "count": 45},
+ # {"start_date": "2021-11-01", "count": 98},
+ # {"start_date": "2021-12-01", "count": 54},
+ # {"start_date": "2022-01-01", "count": 88},
+ # {"start_date": "2022-02-01", "count": 45},
+ # {"start_date": "2022-03-01", "count": 6},
+ # {"start_date": "2022-04-01", "count": 54},
+ # {"start_date": "2022-05-01", "count": 105},
+ # {"start_date": "2022-06-01", "count": 37},
+ # {"start_date": "2022-07-01", "count": 88},
+ # {"start_date": "2022-08-01", "count": 7},
+ # {"start_date": "2022-09-01", "count": 9},
+ # {"start_date": "2022-10-01", "count": 6},
+ # {"start_date": "2022-11-01", "count": 2},
+ # {"start_date": "2022-12-01", "count": 16},
+ # {"start_date": "2023-01-01", "count": 7},
+ # {"start_date": "2023-04-01", "count": 16},
+ # {"start_date": "2023-05-01", "count": 3},
+ # {"start_date": "2023-06-01", "count": 8},
+ # {"start_date": "2023-07-01", "count": 23},
+ # {"start_date": "2023-08-01", "count": 3},
+ # {"start_date": "2023-09-01", "count": 1},
+ # {"start_date": "2023-10-01", "count": 25},
+ # {"start_date": "2023-11-01", "count": 160},
+ # {"start_date": "2023-12-01", "count": 306},
+ # {"start_date": "2024-01-01", "count": 735},
+ # {"start_date": "2024-02-01", "count": 420},
+ # {"start_date": "2024-03-01", "count": 549},
+ # {"start_date": "2024-04-01", "count": 466},
+ # {"start_date": "2024-05-01", "count": 333},
+ # {"start_date": "2024-06-01", "count": 1126},
+ # {"start_date": "2024-07-01", "count": 534},
+ # {"start_date": "2024-08-01", "count": 280},
+ # {"start_date": "2024-09-01", "count": 116},
+ # {"start_date": "2024-10-01", "count": 120},
+ # {"start_date": "2024-11-01", "count": 332},
+ # {"start_date": "2024-12-01", "count": 243},
+ # {"start_date": "2025-01-01", "count": 122},
+ # {"start_date": "2025-02-01", "count": 379},
+ # {"start_date": "2025-03-01", "count": 278},
+ # {"start_date": "2025-04-01", "count": 296},
+ # {"start_date": "2025-05-01", "count": 964},
+ # {"start_date": "2025-06-01", "count": 821},
+ # {"start_date": "2025-07-01", "count": 230},
+ # ],
)
db.add(user)
@@ -151,7 +151,7 @@ def create_sample_user():
db.refresh(user)
# 创建 osu! 模式统计
- osu_stats = UserStatistics(
+ osu_stats = LazerUserStatistics(
user_id=user.id,
mode="osu",
count_100=276274,
@@ -182,7 +182,7 @@ def create_sample_user():
)
# 创建 taiko 模式统计
- taiko_stats = UserStatistics(
+ taiko_stats = LazerUserStatistics(
user_id=user.id,
mode="taiko",
count_100=160,
@@ -205,7 +205,7 @@ def create_sample_user():
)
# 创建 fruits 模式统计
- fruits_stats = UserStatistics(
+ fruits_stats = LazerUserStatistics(
user_id=user.id,
mode="fruits",
count_100=109,
@@ -229,7 +229,7 @@ def create_sample_user():
)
# 创建 mania 模式统计
- mania_stats = UserStatistics(
+ mania_stats = LazerUserStatistics(
user_id=user.id,
mode="mania",
count_100=7867,