feat: 添加从数据库中读取部分lazer资料的功能
This commit is contained in:
3
.idea/.gitignore
generated
vendored
Normal file
3
.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# 默认忽略的文件
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
17
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
17
.idea/inspectionProfiles/Project_Default.xml
generated
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<profile version="1.0">
|
||||||
|
<option name="myName" value="Project Default" />
|
||||||
|
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
|
||||||
|
<option name="ourVersions">
|
||||||
|
<value>
|
||||||
|
<list size="4">
|
||||||
|
<item index="0" class="java.lang.String" itemvalue="3.7" />
|
||||||
|
<item index="1" class="java.lang.String" itemvalue="3.11" />
|
||||||
|
<item index="2" class="java.lang.String" itemvalue="3.12" />
|
||||||
|
<item index="3" class="java.lang.String" itemvalue="3.13" />
|
||||||
|
</list>
|
||||||
|
</value>
|
||||||
|
</option>
|
||||||
|
</inspection_tool>
|
||||||
|
</profile>
|
||||||
|
</component>
|
||||||
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
6
.idea/inspectionProfiles/profiles_settings.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<component name="InspectionProjectProfileManager">
|
||||||
|
<settings>
|
||||||
|
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||||
|
<version value="1.0" />
|
||||||
|
</settings>
|
||||||
|
</component>
|
||||||
10
.idea/misc.xml
generated
Normal file
10
.idea/misc.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="Black">
|
||||||
|
<option name="sdkName" value="osu_lazer_api" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectRootManager" version="2" project-jdk-name="osu_lazer_api" project-jdk-type="Python SDK" />
|
||||||
|
<component name="PythonCompatibilityInspectionAdvertiser">
|
||||||
|
<option name="version" value="3" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
8
.idea/modules.xml
generated
Normal file
8
.idea/modules.xml
generated
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/osu_lazer_api.iml" filepath="$PROJECT_DIR$/.idea/osu_lazer_api.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
14
.idea/osu_lazer_api.iml
generated
Normal file
14
.idea/osu_lazer_api.iml
generated
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="PYTHON_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$">
|
||||||
|
<excludeFolder url="file://$MODULE_DIR$/.venv" />
|
||||||
|
</content>
|
||||||
|
<orderEntry type="jdk" jdkName="osu_lazer_api" jdkType="Python SDK" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
<component name="PyDocumentationSettings">
|
||||||
|
<option name="format" value="PLAIN" />
|
||||||
|
<option name="myDocStringFormat" value="Plain" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -1,403 +0,0 @@
|
|||||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Float, Text, JSON, ForeignKey, Date, DECIMAL
|
|
||||||
from sqlalchemy.ext.declarative import declarative_base
|
|
||||||
from sqlalchemy.orm import relationship
|
|
||||||
from datetime import datetime
|
|
||||||
|
|
||||||
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")
|
|
||||||
49
app/utils.py
49
app/utils.py
@@ -1,7 +1,7 @@
|
|||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
from datetime import datetime
|
from datetime import datetime, UTC
|
||||||
from app.models import *
|
from app.models import User, Statistics, Level, GradeCounts, Country, Cover, Kudosu, GameMode, PlayStyle, Team, UserAchievement, RankHistory, DailyChallengeStats, RankHighest, Page
|
||||||
from app.database import User as DBUser, LazerUserStatistics, LazerUserProfile, LazerUserCountry, LazerUserKudosu, LazerUserCounts, LazerUserAchievement
|
from app.database import User as DBUser, LazerUserProfile, LazerUserStatistics, LazerUserCountry, LazerUserKudosu, LazerUserCounts, LazerUserAchievement
|
||||||
from sqlalchemy.orm import Session
|
from sqlalchemy.orm import Session
|
||||||
|
|
||||||
|
|
||||||
@@ -32,9 +32,11 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
|||||||
user_stats = create_default_lazer_statistics(ruleset)
|
user_stats = create_default_lazer_statistics(ruleset)
|
||||||
|
|
||||||
# 获取国家信息
|
# 获取国家信息
|
||||||
|
country_code = db_user.country_code if db_user.country_code else 'XX'
|
||||||
|
|
||||||
country = Country(
|
country = Country(
|
||||||
code=db_user.country_code,
|
code=country_code,
|
||||||
name=get_country_name(db_user.country_code)
|
name=get_country_name(country_code)
|
||||||
)
|
)
|
||||||
|
|
||||||
# 获取 Kudosu 信息
|
# 获取 Kudosu 信息
|
||||||
@@ -186,14 +188,14 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
|||||||
user_id = getattr(db_user, 'id')
|
user_id = getattr(db_user, 'id')
|
||||||
user_name = getattr(db_user, 'name')
|
user_name = getattr(db_user, 'name')
|
||||||
user_country = getattr(db_user, 'country')
|
user_country = getattr(db_user, 'country')
|
||||||
|
|
||||||
# 获取用户头像URL
|
# 获取用户头像URL
|
||||||
avatar_url = None
|
avatar_url = None
|
||||||
|
|
||||||
# 首先检查 profile 中的 avatar_url
|
# 首先检查 profile 中的 avatar_url
|
||||||
if profile and hasattr(profile, 'avatar_url') and profile.avatar_url:
|
if profile and hasattr(profile, 'avatar_url') and profile.avatar_url:
|
||||||
avatar_url = str(profile.avatar_url)
|
avatar_url = str(profile.avatar_url)
|
||||||
|
|
||||||
# 然后检查是否有关联的头像记录
|
# 然后检查是否有关联的头像记录
|
||||||
if avatar_url is None and hasattr(db_user, 'avatar') and db_user.avatar is not None:
|
if avatar_url is None and hasattr(db_user, 'avatar') and db_user.avatar is not None:
|
||||||
if db_user.avatar.r2_game_url:
|
if db_user.avatar.r2_game_url:
|
||||||
@@ -202,13 +204,13 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
|||||||
elif db_user.avatar.r2_original_url:
|
elif db_user.avatar.r2_original_url:
|
||||||
# 其次使用原始头像URL
|
# 其次使用原始头像URL
|
||||||
avatar_url = str(db_user.avatar.r2_original_url)
|
avatar_url = str(db_user.avatar.r2_original_url)
|
||||||
|
|
||||||
# 如果还是没有找到,通过查询获取
|
# 如果还是没有找到,通过查询获取
|
||||||
if db_session and avatar_url is None:
|
if db_session and avatar_url is None:
|
||||||
try:
|
try:
|
||||||
# 导入UserAvatar模型
|
# 导入UserAvatar模型
|
||||||
from app.database import UserAvatar
|
from app.database import UserAvatar
|
||||||
|
|
||||||
# 尝试查找用户的头像记录
|
# 尝试查找用户的头像记录
|
||||||
avatar_record = db_session.query(UserAvatar).filter_by(user_id=user_id, is_active=True).first()
|
avatar_record = db_session.query(UserAvatar).filter_by(user_id=user_id, is_active=True).first()
|
||||||
if avatar_record is not None:
|
if avatar_record is not None:
|
||||||
@@ -229,7 +231,7 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
|||||||
id=user_id,
|
id=user_id,
|
||||||
username=user_name,
|
username=user_name,
|
||||||
avatar_url=avatar_url, # 使用我们上面获取的头像URL
|
avatar_url=avatar_url, # 使用我们上面获取的头像URL
|
||||||
country_code=user_country,
|
country_code=country_code,
|
||||||
default_group=profile.default_group if profile else "default",
|
default_group=profile.default_group if profile else "default",
|
||||||
is_active=profile.is_active if profile else True,
|
is_active=profile.is_active if profile else True,
|
||||||
is_bot=profile.is_bot if profile else False,
|
is_bot=profile.is_bot if profile else False,
|
||||||
@@ -237,27 +239,19 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
|||||||
is_online=profile.is_online if profile else True,
|
is_online=profile.is_online if profile else True,
|
||||||
is_supporter=profile.is_supporter if profile else False,
|
is_supporter=profile.is_supporter if profile else False,
|
||||||
is_restricted=profile.is_restricted 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,
|
pm_friends_only=profile.pm_friends_only if profile else False,
|
||||||
profile_colour=profile.profile_colour if profile else None,
|
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,
|
discord=profile.discord if profile else None,
|
||||||
has_supported=profile.has_supported if profile else False,
|
has_supported=profile.has_supported if profile else False,
|
||||||
interests=profile.interests if profile else None,
|
interests=profile.interests if profile else None,
|
||||||
join_date=db_user.join_date,
|
join_date=db_user.join_date if db_user.join_date else None,
|
||||||
location=profile.location if profile else None,
|
location=profile.location if profile else None,
|
||||||
max_blocks=profile.max_blocks if profile else 100,
|
max_blocks=profile.max_blocks if profile and profile.max_blocks else 100,
|
||||||
max_friends=profile.max_friends if profile else 500,
|
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,
|
||||||
occupation=None, # 职业字段,默认为 None #待修改
|
profile_hue=profile.profile_hue if profile and profile.profile_hue else 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'],
|
profile_order= ['me', 'recent_activity', 'top_ranks', 'medals', 'historical', 'beatmaps', 'kudosu'],
|
||||||
title=None,
|
title=None,
|
||||||
title_url=None,
|
title_url=None,
|
||||||
@@ -295,12 +289,11 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
|||||||
daily_challenge_user_stats=None,
|
daily_challenge_user_stats=None,
|
||||||
groups=[],
|
groups=[],
|
||||||
monthly_playcounts=[],
|
monthly_playcounts=[],
|
||||||
#page=Page(html=db_user.page_html, raw=db_user.page_raw),
|
page=Page(html=profile.page_html, raw=profile.page_raw) if profile.page_html or profile.page_raw else Page(),
|
||||||
page=Page(), # Provide a default Page object
|
|
||||||
previous_usernames=[],
|
previous_usernames=[],
|
||||||
rank_highest=rank_highest,
|
rank_highest=rank_highest,
|
||||||
rank_history=rank_history,
|
rank_history=rank_history,
|
||||||
rankHistory=rank_history, # 兼容性别名
|
rankHistory=rank_history, # 保留旧API兼容性字段
|
||||||
replays_watched_counts=[],
|
replays_watched_counts=[],
|
||||||
team=team,
|
team=team,
|
||||||
user_achievements=user_achievements
|
user_achievements=user_achievements
|
||||||
|
|||||||
3
main.py
3
main.py
@@ -20,7 +20,6 @@ app = FastAPI(title="osu! API 模拟服务器", version="1.0.0")
|
|||||||
|
|
||||||
security = HTTPBearer()
|
security = HTTPBearer()
|
||||||
|
|
||||||
|
|
||||||
@app.post("/oauth/token", response_model=TokenResponse)
|
@app.post("/oauth/token", response_model=TokenResponse)
|
||||||
async def oauth_token(
|
async def oauth_token(
|
||||||
grant_type: str = Form(...),
|
grant_type: str = Form(...),
|
||||||
@@ -104,7 +103,7 @@ async def oauth_token(
|
|||||||
refresh_token=new_refresh_token,
|
refresh_token=new_refresh_token,
|
||||||
scope=scope
|
scope=scope
|
||||||
)
|
)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise HTTPException(status_code=400, detail="Unsupported grant type")
|
raise HTTPException(status_code=400, detail="Unsupported grant type")
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user