use uv & make lint happy
This commit is contained in:
100
app/auth.py
100
app/auth.py
@@ -1,14 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timedelta
|
||||
import hashlib
|
||||
import secrets
|
||||
import string
|
||||
from typing import Optional
|
||||
|
||||
from app.config import settings
|
||||
from app.database import (
|
||||
OAuthToken,
|
||||
User as DBUser,
|
||||
)
|
||||
|
||||
import bcrypt
|
||||
from jose import JWTError, jwt
|
||||
from passlib.context import CryptContext
|
||||
from sqlalchemy.orm import Session
|
||||
from app.database import User as DBUser, OAuthToken
|
||||
from app.config import settings
|
||||
import secrets
|
||||
import string
|
||||
import hashlib
|
||||
import bcrypt
|
||||
|
||||
# 密码哈希上下文
|
||||
pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
@@ -16,6 +23,7 @@ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
|
||||
# bcrypt 缓存(模拟应用状态缓存)
|
||||
bcrypt_cache = {}
|
||||
|
||||
|
||||
def verify_password_legacy(plain_password: str, bcrypt_hash: str) -> bool:
|
||||
"""
|
||||
验证密码 - 使用 osu! 的验证方式
|
||||
@@ -24,34 +32,36 @@ def verify_password_legacy(plain_password: str, bcrypt_hash: str) -> bool:
|
||||
"""
|
||||
# 1. 明文密码转 MD5
|
||||
pw_md5 = hashlib.md5(plain_password.encode()).hexdigest().encode()
|
||||
|
||||
|
||||
# 2. 检查缓存
|
||||
if bcrypt_hash in bcrypt_cache:
|
||||
return bcrypt_cache[bcrypt_hash] == pw_md5
|
||||
|
||||
|
||||
# 3. 如果缓存中没有,进行 bcrypt 验证
|
||||
try:
|
||||
# 验证 MD5 哈希与 bcrypt 哈希
|
||||
is_valid = bcrypt.checkpw(pw_md5, bcrypt_hash.encode())
|
||||
|
||||
|
||||
# 如果验证成功,将结果缓存
|
||||
if is_valid:
|
||||
bcrypt_cache[bcrypt_hash] = pw_md5
|
||||
|
||||
|
||||
return is_valid
|
||||
except Exception as e:
|
||||
print(f"Password verification error: {e}")
|
||||
return False
|
||||
|
||||
|
||||
def verify_password(plain_password: str, hashed_password: str) -> bool:
|
||||
"""验证密码(向后兼容)"""
|
||||
# 首先尝试新的验证方式
|
||||
if verify_password_legacy(plain_password, hashed_password):
|
||||
return True
|
||||
|
||||
|
||||
# 如果失败,尝试标准 bcrypt 验证
|
||||
return pwd_context.verify(plain_password, hashed_password)
|
||||
|
||||
|
||||
def get_password_hash(password: str) -> str:
|
||||
"""生成密码哈希 - 使用 osu! 的方式"""
|
||||
# 1. 明文密码 -> MD5
|
||||
@@ -60,29 +70,30 @@ def get_password_hash(password: str) -> str:
|
||||
pw_bcrypt = bcrypt.hashpw(pw_md5, bcrypt.gensalt())
|
||||
return pw_bcrypt.decode()
|
||||
|
||||
|
||||
def authenticate_user_legacy(db: Session, name: str, password: str) -> Optional[DBUser]:
|
||||
"""
|
||||
验证用户身份 - 使用类似 from_login 的逻辑
|
||||
"""
|
||||
# 1. 明文密码转 MD5
|
||||
pw_md5 = hashlib.md5(password.encode()).hexdigest()
|
||||
|
||||
|
||||
# 2. 根据用户名查找用户
|
||||
user = db.query(DBUser).filter(DBUser.name == name).first()
|
||||
if not user:
|
||||
return None
|
||||
|
||||
|
||||
# 3. 验证密码
|
||||
if not user.pw_bcrypt:
|
||||
return None
|
||||
|
||||
|
||||
# 4. 检查缓存
|
||||
if user.pw_bcrypt in bcrypt_cache:
|
||||
if bcrypt_cache[user.pw_bcrypt] == pw_md5.encode():
|
||||
return user
|
||||
else:
|
||||
return None
|
||||
|
||||
|
||||
# 5. 验证 bcrypt
|
||||
try:
|
||||
is_valid = bcrypt.checkpw(pw_md5.encode(), user.pw_bcrypt.encode())
|
||||
@@ -92,68 +103,91 @@ def authenticate_user_legacy(db: Session, name: str, password: str) -> Optional[
|
||||
return user
|
||||
except Exception as e:
|
||||
print(f"Authentication error for user {name}: {e}")
|
||||
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def authenticate_user(db: Session, username: str, password: str) -> Optional[DBUser]:
|
||||
"""验证用户身份"""
|
||||
return authenticate_user_legacy(db, username, password)
|
||||
|
||||
|
||||
def create_access_token(data: dict, expires_delta: Optional[timedelta] = None) -> str:
|
||||
"""创建访问令牌"""
|
||||
to_encode = data.copy()
|
||||
if expires_delta:
|
||||
expire = datetime.utcnow() + expires_delta
|
||||
else:
|
||||
expire = datetime.utcnow() + timedelta(minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES)
|
||||
|
||||
expire = datetime.utcnow() + timedelta(
|
||||
minutes=settings.ACCESS_TOKEN_EXPIRE_MINUTES
|
||||
)
|
||||
|
||||
to_encode.update({"exp": expire})
|
||||
encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM)
|
||||
encoded_jwt = jwt.encode(
|
||||
to_encode, settings.SECRET_KEY, algorithm=settings.ALGORITHM
|
||||
)
|
||||
return encoded_jwt
|
||||
|
||||
|
||||
def generate_refresh_token() -> str:
|
||||
"""生成刷新令牌"""
|
||||
length = 64
|
||||
characters = string.ascii_letters + string.digits
|
||||
return ''.join(secrets.choice(characters) for _ in range(length))
|
||||
return "".join(secrets.choice(characters) for _ in range(length))
|
||||
|
||||
|
||||
def verify_token(token: str) -> Optional[dict]:
|
||||
"""验证访问令牌"""
|
||||
try:
|
||||
payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM])
|
||||
payload = jwt.decode(
|
||||
token, settings.SECRET_KEY, algorithms=[settings.ALGORITHM]
|
||||
)
|
||||
return payload
|
||||
except JWTError:
|
||||
return None
|
||||
|
||||
def store_token(db: Session, user_id: int, access_token: str, refresh_token: str, expires_in: int) -> OAuthToken:
|
||||
|
||||
def store_token(
|
||||
db: Session, user_id: int, access_token: str, refresh_token: str, expires_in: int
|
||||
) -> OAuthToken:
|
||||
"""存储令牌到数据库"""
|
||||
expires_at = datetime.utcnow() + timedelta(seconds=expires_in)
|
||||
|
||||
|
||||
# 删除用户的旧令牌
|
||||
db.query(OAuthToken).filter(OAuthToken.user_id == user_id).delete()
|
||||
|
||||
|
||||
# 创建新令牌记录
|
||||
token_record = OAuthToken(
|
||||
user_id=user_id,
|
||||
access_token=access_token,
|
||||
refresh_token=refresh_token,
|
||||
expires_at=expires_at
|
||||
expires_at=expires_at,
|
||||
)
|
||||
db.add(token_record)
|
||||
db.commit()
|
||||
db.refresh(token_record)
|
||||
return token_record
|
||||
|
||||
|
||||
def get_token_by_access_token(db: Session, access_token: str) -> Optional[OAuthToken]:
|
||||
"""根据访问令牌获取令牌记录"""
|
||||
return db.query(OAuthToken).filter(
|
||||
OAuthToken.access_token == access_token,
|
||||
OAuthToken.expires_at > datetime.utcnow()
|
||||
).first()
|
||||
return (
|
||||
db.query(OAuthToken)
|
||||
.filter(
|
||||
OAuthToken.access_token == access_token,
|
||||
OAuthToken.expires_at > datetime.utcnow(),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
|
||||
def get_token_by_refresh_token(db: Session, refresh_token: str) -> Optional[OAuthToken]:
|
||||
"""根据刷新令牌获取令牌记录"""
|
||||
return db.query(OAuthToken).filter(
|
||||
OAuthToken.refresh_token == refresh_token,
|
||||
OAuthToken.expires_at > datetime.utcnow()
|
||||
).first()
|
||||
return (
|
||||
db.query(OAuthToken)
|
||||
.filter(
|
||||
OAuthToken.refresh_token == refresh_token,
|
||||
OAuthToken.expires_at > datetime.utcnow(),
|
||||
)
|
||||
.first()
|
||||
)
|
||||
|
||||
@@ -1,25 +1,36 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from dotenv import load_dotenv
|
||||
|
||||
load_dotenv()
|
||||
|
||||
|
||||
class Settings:
|
||||
# 数据库设置
|
||||
DATABASE_URL: str = os.getenv("DATABASE_URL", "mysql+pymysql://root:password@localhost:3306/osu_api")
|
||||
DATABASE_URL: str = os.getenv(
|
||||
"DATABASE_URL", "mysql+pymysql://root:password@localhost:3306/osu_api"
|
||||
)
|
||||
REDIS_URL: str = os.getenv("REDIS_URL", "redis://localhost:6379/0")
|
||||
|
||||
|
||||
# JWT 设置
|
||||
SECRET_KEY: str = os.getenv("SECRET_KEY", "your-secret-key-here")
|
||||
ALGORITHM: str = os.getenv("ALGORITHM", "HS256")
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = int(os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "1440"))
|
||||
|
||||
ACCESS_TOKEN_EXPIRE_MINUTES: int = int(
|
||||
os.getenv("ACCESS_TOKEN_EXPIRE_MINUTES", "1440")
|
||||
)
|
||||
|
||||
# OAuth 设置
|
||||
OSU_CLIENT_ID: str = os.getenv("OSU_CLIENT_ID", "5")
|
||||
OSU_CLIENT_SECRET: str = os.getenv("OSU_CLIENT_SECRET", "FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk")
|
||||
|
||||
OSU_CLIENT_SECRET: str = os.getenv(
|
||||
"OSU_CLIENT_SECRET", "FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"
|
||||
)
|
||||
|
||||
# 服务器设置
|
||||
HOST: str = os.getenv("HOST", "0.0.0.0")
|
||||
PORT: int = int(os.getenv("PORT", "8000"))
|
||||
DEBUG: bool = os.getenv("DEBUG", "True").lower() == "true"
|
||||
|
||||
|
||||
settings = Settings()
|
||||
|
||||
@@ -1,35 +1,51 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Float, Text, JSON, ForeignKey, Date, DECIMAL
|
||||
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
|
||||
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) # 安全用户名
|
||||
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) # 国家代码
|
||||
|
||||
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)
|
||||
@@ -37,39 +53,57 @@ class User(Base):
|
||||
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()
|
||||
|
||||
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
|
||||
|
||||
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_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)
|
||||
@@ -80,14 +114,14 @@ class LazerUserProfile(Base):
|
||||
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')
|
||||
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))
|
||||
@@ -99,52 +133,52 @@ class LazerUserProfile(Base):
|
||||
interests = Column(Text)
|
||||
location = Column(String(100))
|
||||
occupation = Column(String(100))
|
||||
|
||||
|
||||
# 游戏相关字段
|
||||
playmode = Column(String(10), default='osu')
|
||||
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)
|
||||
@@ -163,32 +197,32 @@ class LazerUserCounts(Base):
|
||||
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')
|
||||
|
||||
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)
|
||||
@@ -197,32 +231,35 @@ class LazerUserStatistics(Base):
|
||||
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)
|
||||
@@ -231,13 +268,13 @@ class LegacyUserStatistics(Base):
|
||||
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)
|
||||
@@ -248,7 +285,7 @@ class LegacyUserStatistics(Base):
|
||||
|
||||
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)
|
||||
@@ -258,37 +295,50 @@ class OAuthToken(Base):
|
||||
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")
|
||||
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)
|
||||
@@ -297,72 +347,74 @@ class UserStatistics(Base):
|
||||
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")
|
||||
|
||||
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)
|
||||
@@ -372,25 +424,25 @@ class DailyChallengeStats(Base):
|
||||
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)
|
||||
@@ -399,5 +451,5 @@ class OAuthToken(Base):
|
||||
scope = Column(String(100), default="*")
|
||||
expires_at = Column(DateTime, nullable=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
user = relationship("User")
|
||||
|
||||
231
app/database.py
231
app/database.py
@@ -1,36 +1,52 @@
|
||||
from sqlalchemy import Column, Integer, String, Boolean, DateTime, Float, Text, JSON, ForeignKey, Date, DECIMAL
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
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
|
||||
from datetime import datetime
|
||||
from dataclasses import dataclass
|
||||
|
||||
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) # 安全用户名
|
||||
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) # 国家代码
|
||||
|
||||
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)
|
||||
@@ -38,46 +54,79 @@ class User(Base):
|
||||
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):
|
||||
creation_time = getattr(self, 'creation_time', 0)
|
||||
return datetime.fromtimestamp(creation_time) if creation_time > 0 else datetime.utcnow()
|
||||
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)
|
||||
latest_activity = getattr(self, "latest_activity", 0)
|
||||
return datetime.fromtimestamp(latest_activity) if 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")
|
||||
statistics = relationship("LegacyUserStatistics", back_populates="user", cascade="all, delete-orphan")
|
||||
achievements = relationship("LazerUserAchievement", 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")
|
||||
avatar = relationship("UserAvatar", back_populates="user", primaryjoin="and_(User.id==UserAvatar.user_id, UserAvatar.is_active==True)", uselist=False)
|
||||
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"
|
||||
)
|
||||
statistics = relationship(
|
||||
"LegacyUserStatistics", back_populates="user", cascade="all, delete-orphan"
|
||||
)
|
||||
achievements = relationship(
|
||||
"LazerUserAchievement", 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"
|
||||
)
|
||||
avatar = relationship(
|
||||
"UserAvatar",
|
||||
back_populates="user",
|
||||
primaryjoin="and_(User.id==UserAvatar.user_id, UserAvatar.is_active==True)",
|
||||
uselist=False,
|
||||
)
|
||||
|
||||
|
||||
# ============================================
|
||||
# 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)
|
||||
@@ -88,14 +137,14 @@ class LazerUserProfile(Base):
|
||||
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')
|
||||
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))
|
||||
@@ -108,52 +157,52 @@ class LazerUserProfile(Base):
|
||||
location = Column(String(100))
|
||||
|
||||
occupation = None # 职业字段,默认为 None
|
||||
|
||||
|
||||
# 游戏相关字段
|
||||
playmode = Column(String(10), default='osu')
|
||||
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)
|
||||
|
||||
|
||||
# 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)
|
||||
@@ -172,32 +221,32 @@ class LazerUserCounts(Base):
|
||||
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"), primary_key=True)
|
||||
mode = Column(String(10), nullable=False, default='osu', primary_key=True)
|
||||
|
||||
mode = Column(String(10), nullable=False, default="osu", primary_key=True)
|
||||
|
||||
# 基本命中统计
|
||||
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)
|
||||
@@ -206,70 +255,72 @@ class LazerUserStatistics(Base):
|
||||
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="lazer_statistics")
|
||||
|
||||
|
||||
class LazerUserAchievement(Base):
|
||||
__tablename__ = "lazer_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)
|
||||
|
||||
#created_at = Column(DateTime, default=datetime.utcnow)
|
||||
#updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
|
||||
# created_at = Column(DateTime, default=datetime.utcnow)
|
||||
# updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
|
||||
user = relationship("User", back_populates="lazer_achievements")
|
||||
|
||||
|
||||
# ============================================
|
||||
# 旧的兼容性表模型(保留以便向后兼容)
|
||||
# ============================================
|
||||
|
||||
|
||||
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)
|
||||
@@ -278,34 +329,34 @@ class LegacyUserStatistics(Base):
|
||||
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 LegacyOAuthToken(Base):
|
||||
__tablename__ = "legacy_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)
|
||||
@@ -315,19 +366,19 @@ class LegacyOAuthToken(Base):
|
||||
updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow)
|
||||
previous_usernames = Column(JSON, default=list)
|
||||
replays_watched_counts = Column(JSON, default=list)
|
||||
|
||||
|
||||
# 用户关系
|
||||
user = relationship("User")
|
||||
|
||||
|
||||
# class UserAchievement(Base):
|
||||
# __tablename__ = "lazer_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")
|
||||
|
||||
|
||||
@@ -340,34 +391,36 @@ class UserAchievement:
|
||||
|
||||
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")
|
||||
|
||||
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)
|
||||
@@ -377,25 +430,25 @@ class DailyChallengeStats(Base):
|
||||
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)
|
||||
@@ -404,13 +457,13 @@ class OAuthToken(Base):
|
||||
scope = Column(String(100), default="*")
|
||||
expires_at = Column(DateTime, nullable=False)
|
||||
created_at = Column(DateTime, default=datetime.utcnow)
|
||||
|
||||
|
||||
user = relationship("User")
|
||||
|
||||
|
||||
class UserAvatar(Base):
|
||||
__tablename__ = "user_avatars"
|
||||
|
||||
|
||||
id = Column(Integer, primary_key=True, index=True)
|
||||
user_id = Column(Integer, ForeignKey("users.id"), nullable=False)
|
||||
filename = Column(String(255), nullable=False)
|
||||
@@ -422,5 +475,5 @@ class UserAvatar(Base):
|
||||
updated_at = Column(Integer, default=lambda: int(datetime.now().timestamp()))
|
||||
r2_original_url = Column(String(500))
|
||||
r2_game_url = Column(String(500))
|
||||
|
||||
|
||||
user = relationship("User", back_populates="avatar")
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy.orm import sessionmaker
|
||||
|
||||
try:
|
||||
import redis
|
||||
except ImportError:
|
||||
@@ -16,6 +19,7 @@ if redis:
|
||||
else:
|
||||
redis_client = None
|
||||
|
||||
|
||||
# 数据库依赖
|
||||
def get_db():
|
||||
db = SessionLocal()
|
||||
@@ -24,6 +28,7 @@ def get_db():
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
|
||||
# Redis 依赖
|
||||
def get_redis():
|
||||
return redis_client
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
from pydantic import BaseModel, Field
|
||||
from typing import Optional, List
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from enum import Enum
|
||||
from typing import Optional
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class GameMode(str, Enum):
|
||||
@@ -88,7 +91,7 @@ class RankHighest(BaseModel):
|
||||
|
||||
class RankHistory(BaseModel):
|
||||
mode: str
|
||||
data: List[int]
|
||||
data: list[int]
|
||||
|
||||
|
||||
class DailyChallengeStats(BaseModel):
|
||||
@@ -132,7 +135,7 @@ class User(BaseModel):
|
||||
last_visit: Optional[datetime] = None
|
||||
pm_friends_only: bool = False
|
||||
profile_colour: Optional[str] = None
|
||||
|
||||
|
||||
# 个人资料
|
||||
cover_url: Optional[str] = None
|
||||
discord: Optional[str] = None
|
||||
@@ -144,24 +147,32 @@ class User(BaseModel):
|
||||
max_friends: int = 500
|
||||
occupation: Optional[str] = None
|
||||
playmode: GameMode = GameMode.OSU
|
||||
playstyle: List[PlayStyle] = []
|
||||
playstyle: list[PlayStyle] = []
|
||||
post_count: int = 0
|
||||
profile_hue: Optional[int] = None
|
||||
profile_order: List[str] = ["me", "recent_activity", "top_ranks", "medals", "historical", "beatmaps", "kudosu"]
|
||||
profile_order: list[str] = [
|
||||
"me",
|
||||
"recent_activity",
|
||||
"top_ranks",
|
||||
"medals",
|
||||
"historical",
|
||||
"beatmaps",
|
||||
"kudosu",
|
||||
]
|
||||
title: Optional[str] = None
|
||||
title_url: Optional[str] = None
|
||||
twitter: Optional[str] = None
|
||||
website: Optional[str] = None
|
||||
session_verified: bool = False
|
||||
support_level: int = 0
|
||||
|
||||
|
||||
# 关联对象
|
||||
country: Country
|
||||
cover: Cover
|
||||
kudosu: Kudosu
|
||||
statistics: Statistics
|
||||
statistics_rulesets: dict[str, Statistics]
|
||||
|
||||
|
||||
# 计数信息
|
||||
beatmap_playcounts_count: int = 0
|
||||
comments_count: int = 0
|
||||
@@ -180,24 +191,24 @@ class User(BaseModel):
|
||||
scores_first_count: int = 0
|
||||
scores_pinned_count: int = 0
|
||||
scores_recent_count: int = 0
|
||||
|
||||
|
||||
# 历史数据
|
||||
account_history: List[dict] = []
|
||||
account_history: list[dict] = []
|
||||
active_tournament_banner: Optional[dict] = None
|
||||
active_tournament_banners: List[dict] = []
|
||||
badges: List[dict] = []
|
||||
active_tournament_banners: list[dict] = []
|
||||
badges: list[dict] = []
|
||||
current_season_stats: Optional[dict] = None
|
||||
daily_challenge_user_stats: Optional[DailyChallengeStats] = None
|
||||
groups: List[dict] = []
|
||||
monthly_playcounts: List[MonthlyPlaycount] = []
|
||||
groups: list[dict] = []
|
||||
monthly_playcounts: list[MonthlyPlaycount] = []
|
||||
page: Page = Page()
|
||||
previous_usernames: List[str] = []
|
||||
previous_usernames: list[str] = []
|
||||
rank_highest: Optional[RankHighest] = None
|
||||
rank_history: Optional[RankHistory] = None
|
||||
rankHistory: Optional[RankHistory] = None # 兼容性别名
|
||||
replays_watched_counts: List[dict] = []
|
||||
replays_watched_counts: list[dict] = []
|
||||
team: Optional[Team] = None
|
||||
user_achievements: List[UserAchievement] = []
|
||||
user_achievements: list[UserAchievement] = []
|
||||
|
||||
|
||||
# OAuth 相关模型
|
||||
|
||||
191
app/utils.py
191
app/utils.py
@@ -1,48 +1,68 @@
|
||||
from typing import Dict, List, Optional
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime
|
||||
from app.models import *
|
||||
from app.database import User as DBUser, LazerUserStatistics, LazerUserProfile, LazerUserCountry, LazerUserKudosu, LazerUserCounts, LazerUserAchievement
|
||||
|
||||
from app.database import User as DBUser
|
||||
from app.models import (
|
||||
Country,
|
||||
Cover,
|
||||
DailyChallengeStats,
|
||||
GameMode,
|
||||
GradeCounts,
|
||||
Kudosu,
|
||||
Level,
|
||||
Page,
|
||||
PlayStyle,
|
||||
RankHighest,
|
||||
RankHistory,
|
||||
Statistics,
|
||||
Team,
|
||||
User,
|
||||
UserAchievement,
|
||||
)
|
||||
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
|
||||
def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_session: Session = None) -> User:
|
||||
def convert_db_user_to_api_user(
|
||||
db_user: DBUser, ruleset: str = "osu", db_session: Session | None = None
|
||||
) -> User:
|
||||
"""将数据库用户模型转换为API用户模型(使用 Lazer 表)"""
|
||||
|
||||
|
||||
# 从db_user获取基本字段值
|
||||
user_id = getattr(db_user, 'id')
|
||||
user_name = getattr(db_user, 'name')
|
||||
user_country = getattr(db_user, 'country')
|
||||
user_id = getattr(db_user, "id")
|
||||
user_name = getattr(db_user, "name")
|
||||
user_country = getattr(db_user, "country")
|
||||
user_country_code = user_country # 在User模型中,country字段就是country_code
|
||||
|
||||
|
||||
# 获取 Lazer 用户资料
|
||||
profile = db_user.lazer_profile
|
||||
if not profile:
|
||||
# 如果没有 lazer 资料,使用默认值
|
||||
profile = create_default_profile(db_user)
|
||||
|
||||
|
||||
# 获取指定模式的统计信息
|
||||
user_stats = None
|
||||
for stat in db_user.lazer_statistics:
|
||||
if stat.mode == ruleset:
|
||||
user_stats = stat
|
||||
break
|
||||
|
||||
|
||||
if not user_stats:
|
||||
# 如果没有找到指定模式的统计,创建默认统计
|
||||
user_stats = create_default_lazer_statistics(ruleset)
|
||||
|
||||
|
||||
# 获取国家信息
|
||||
country = Country(
|
||||
code=db_user.country_code,
|
||||
name=get_country_name(db_user.country_code)
|
||||
code=user_country_code, name=get_country_name(user_country_code)
|
||||
)
|
||||
|
||||
|
||||
# 获取 Kudosu 信息
|
||||
kudosu = Kudosu(available=0, total=0)
|
||||
|
||||
|
||||
# 获取计数信息
|
||||
counts = create_default_counts()
|
||||
|
||||
|
||||
# 转换统计信息
|
||||
statistics = Statistics(
|
||||
count_100=user_stats.count_100,
|
||||
@@ -50,8 +70,7 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
||||
count_50=user_stats.count_50,
|
||||
count_miss=user_stats.count_miss,
|
||||
level=Level(
|
||||
current=user_stats.level_current,
|
||||
progress=user_stats.level_progress
|
||||
current=user_stats.level_current, progress=user_stats.level_progress
|
||||
),
|
||||
global_rank=user_stats.global_rank,
|
||||
global_rank_exp=user_stats.global_rank_exp,
|
||||
@@ -71,12 +90,12 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
||||
ssh=user_stats.grade_ssh,
|
||||
s=user_stats.grade_s,
|
||||
sh=user_stats.grade_sh,
|
||||
a=user_stats.grade_a
|
||||
a=user_stats.grade_a,
|
||||
),
|
||||
country_rank=user_stats.country_rank,
|
||||
rank={"country": user_stats.country_rank} if user_stats.country_rank else None
|
||||
rank={"country": user_stats.country_rank} if user_stats.country_rank else None,
|
||||
)
|
||||
|
||||
|
||||
# 转换所有模式的统计信息
|
||||
statistics_rulesets = {}
|
||||
for stat in db_user.statistics:
|
||||
@@ -104,35 +123,36 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
||||
ssh=stat.grade_ssh,
|
||||
s=stat.grade_s,
|
||||
sh=stat.grade_sh,
|
||||
a=stat.grade_a
|
||||
)
|
||||
a=stat.grade_a,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
# 转换国家信息
|
||||
country = Country(
|
||||
code=user_country_code,
|
||||
name=get_country_name(user_country_code)
|
||||
)
|
||||
|
||||
country = Country(code=user_country_code, name=get_country_name(user_country_code))
|
||||
|
||||
# 转换封面信息
|
||||
cover_url = profile.cover_url if profile and profile.cover_url else "https://assets.ppy.sh/user-profile-covers/default.jpeg"
|
||||
cover = Cover(
|
||||
custom_url=profile.cover_url if profile else None,
|
||||
url=str(cover_url),
|
||||
id=None
|
||||
cover_url = (
|
||||
profile.cover_url
|
||||
if profile and profile.cover_url
|
||||
else "https://assets.ppy.sh/user-profile-covers/default.jpeg"
|
||||
)
|
||||
|
||||
cover = Cover(
|
||||
custom_url=profile.cover_url if profile else None, url=str(cover_url), id=None
|
||||
)
|
||||
|
||||
# 转换 Kudosu 信息
|
||||
kudosu = Kudosu(available=0, total=0)
|
||||
|
||||
|
||||
# 转换成就信息
|
||||
user_achievements = []
|
||||
if db_user.lazer_achievements:
|
||||
for achievement in db_user.lazer_achievements:
|
||||
user_achievements.append(UserAchievement(
|
||||
achieved_at=achievement.achieved_at,
|
||||
achievement_id=achievement.achievement_id
|
||||
))
|
||||
user_achievements.append(
|
||||
UserAchievement(
|
||||
achieved_at=achievement.achieved_at,
|
||||
achievement_id=achievement.achievement_id,
|
||||
)
|
||||
)
|
||||
|
||||
# 转换排名历史
|
||||
rank_history = None
|
||||
@@ -141,10 +161,10 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
||||
if rh.mode == ruleset:
|
||||
rank_history_data = rh.rank_data
|
||||
break
|
||||
|
||||
|
||||
if rank_history_data:
|
||||
rank_history = RankHistory(mode=ruleset, data=rank_history_data)
|
||||
|
||||
|
||||
# 转换每日挑战统计
|
||||
daily_challenge_stats = None
|
||||
if db_user.daily_challenge_stats:
|
||||
@@ -159,17 +179,17 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
||||
top_50p_placements=dcs.top_50p_placements,
|
||||
user_id=dcs.user_id,
|
||||
weekly_streak_best=dcs.weekly_streak_best,
|
||||
weekly_streak_current=dcs.weekly_streak_current
|
||||
weekly_streak_current=dcs.weekly_streak_current,
|
||||
)
|
||||
|
||||
|
||||
# 转换最高排名
|
||||
rank_highest = None
|
||||
if user_stats.rank_highest:
|
||||
rank_highest = RankHighest(
|
||||
rank=user_stats.rank_highest,
|
||||
updated_at=user_stats.rank_highest_updated_at or datetime.utcnow()
|
||||
updated_at=user_stats.rank_highest_updated_at or datetime.utcnow(),
|
||||
)
|
||||
|
||||
|
||||
# 转换团队信息
|
||||
team = None
|
||||
if db_user.team_membership:
|
||||
@@ -178,39 +198,43 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
||||
flag_url=team_member.team.flag_url or "",
|
||||
id=team_member.team.id,
|
||||
name=team_member.team.name,
|
||||
short_name=team_member.team.short_name
|
||||
short_name=team_member.team.short_name,
|
||||
)
|
||||
|
||||
|
||||
# 创建用户对象
|
||||
# 从db_user获取基本字段值
|
||||
user_id = getattr(db_user, 'id')
|
||||
user_name = getattr(db_user, 'name')
|
||||
user_country = getattr(db_user, 'country')
|
||||
|
||||
user_id = getattr(db_user, "id")
|
||||
user_name = getattr(db_user, "name")
|
||||
user_country = getattr(db_user, "country")
|
||||
|
||||
# 获取用户头像URL
|
||||
avatar_url = None
|
||||
|
||||
# 首先检查 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)
|
||||
|
||||
|
||||
# 然后检查是否有关联的头像记录
|
||||
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:
|
||||
# 优先使用游戏用的头像URL
|
||||
avatar_url = str(db_user.avatar.r2_game_url)
|
||||
elif db_user.avatar.r2_original_url:
|
||||
# 其次使用原始头像URL
|
||||
avatar_url = str(db_user.avatar.r2_original_url)
|
||||
|
||||
|
||||
# 如果还是没有找到,通过查询获取
|
||||
if db_session and avatar_url is None:
|
||||
try:
|
||||
# 导入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.r2_game_url is not None:
|
||||
# 优先使用游戏用的头像URL
|
||||
@@ -223,7 +247,7 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
||||
print(f"最终头像URL: {avatar_url}")
|
||||
# 如果仍然没有找到头像URL,则使用默认URL
|
||||
if avatar_url is None:
|
||||
avatar_url = f"https://a.gu-osu.gmoe.cc/api/users/avatar/1"
|
||||
avatar_url = "https://a.gu-osu.gmoe.cc/api/users/avatar/1"
|
||||
|
||||
user = User(
|
||||
id=user_id,
|
||||
@@ -248,21 +272,25 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
||||
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], #待修改
|
||||
|
||||
# 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_url=None,
|
||||
twitter=None,
|
||||
website='https://gmoe.cc',
|
||||
website="https://gmoe.cc",
|
||||
session_verified=True,
|
||||
support_level=0,
|
||||
country=country,
|
||||
@@ -295,7 +323,7 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
||||
daily_challenge_user_stats=None,
|
||||
groups=[],
|
||||
monthly_playcounts=[],
|
||||
#page=Page(html=db_user.page_html, raw=db_user.page_raw),
|
||||
# page=Page(html=db_user.page_html, raw=db_user.page_raw),
|
||||
page=Page(), # Provide a default Page object
|
||||
previous_usernames=[],
|
||||
rank_highest=rank_highest,
|
||||
@@ -303,9 +331,9 @@ def convert_db_user_to_api_user(db_user: DBUser, ruleset: str = "osu", db_sessio
|
||||
rankHistory=rank_history, # 兼容性别名
|
||||
replays_watched_counts=[],
|
||||
team=team,
|
||||
user_achievements=user_achievements
|
||||
user_achievements=user_achievements,
|
||||
)
|
||||
|
||||
|
||||
return user
|
||||
|
||||
|
||||
@@ -329,6 +357,7 @@ def get_country_name(country_code: str) -> str:
|
||||
|
||||
def create_default_profile(db_user: DBUser):
|
||||
"""创建默认的用户资料"""
|
||||
|
||||
class MockProfile:
|
||||
def __init__(self):
|
||||
self.is_active = True
|
||||
@@ -340,7 +369,7 @@ def create_default_profile(db_user: DBUser):
|
||||
self.session_verified = False
|
||||
self.has_supported = False
|
||||
self.pm_friends_only = False
|
||||
self.default_group = 'default'
|
||||
self.default_group = "default"
|
||||
self.last_visit = None
|
||||
self.join_date = db_user.join_date
|
||||
self.profile_colour = None
|
||||
@@ -355,19 +384,20 @@ def create_default_profile(db_user: DBUser):
|
||||
self.interests = None
|
||||
self.location = None
|
||||
self.occupation = None
|
||||
self.playmode = 'osu'
|
||||
self.playmode = "osu"
|
||||
self.support_level = 0
|
||||
self.max_blocks = 100
|
||||
self.max_friends = 500
|
||||
self.post_count = 0
|
||||
self.page_html = None
|
||||
self.page_raw = None
|
||||
|
||||
|
||||
return MockProfile()
|
||||
|
||||
|
||||
def create_default_lazer_statistics(mode: str):
|
||||
"""创建默认的 Lazer 统计信息"""
|
||||
|
||||
class MockLazerStatistics:
|
||||
def __init__(self, mode: str):
|
||||
self.mode = mode
|
||||
@@ -398,32 +428,35 @@ def create_default_lazer_statistics(mode: str):
|
||||
self.country_rank = None
|
||||
self.rank_highest = None
|
||||
self.rank_highest_updated_at = None
|
||||
|
||||
|
||||
return MockLazerStatistics(mode)
|
||||
|
||||
|
||||
def create_default_country(country_code: str):
|
||||
"""创建默认的国家信息"""
|
||||
|
||||
class MockCountry:
|
||||
def __init__(self, code: str):
|
||||
self.code = code
|
||||
self.name = get_country_name(code)
|
||||
|
||||
|
||||
return MockCountry(country_code)
|
||||
|
||||
|
||||
def create_default_kudosu():
|
||||
"""创建默认的 Kudosu 信息"""
|
||||
|
||||
class MockKudosu:
|
||||
def __init__(self):
|
||||
self.available = 0
|
||||
self.total = 0
|
||||
|
||||
|
||||
return MockKudosu()
|
||||
|
||||
|
||||
def create_default_counts():
|
||||
"""创建默认的计数信息"""
|
||||
|
||||
class MockCounts:
|
||||
def __init__(self):
|
||||
self.beatmap_playcounts_count = 0
|
||||
@@ -443,5 +476,5 @@ def create_default_counts():
|
||||
self.scores_first_count = 0
|
||||
self.scores_pinned_count = 0
|
||||
self.scores_recent_count = 0
|
||||
|
||||
|
||||
return MockCounts()
|
||||
|
||||
Reference in New Issue
Block a user