diff --git a/.idea/.gitignore b/.idea/.gitignore
deleted file mode 100644
index 50d9d22..0000000
--- a/.idea/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-# 默认忽略的文件
-/shelf/
-/workspace.xml
diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml
deleted file mode 100644
index 86f861d..0000000
--- a/.idea/inspectionProfiles/Project_Default.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml
deleted file mode 100644
index 20fc29e..0000000
--- a/.idea/inspectionProfiles/profiles_settings.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
deleted file mode 100644
index c311805..0000000
--- a/.idea/misc.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
deleted file mode 100644
index 5fd5691..0000000
--- a/.idea/modules.xml
+++ /dev/null
@@ -1,8 +0,0 @@
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/osu_lazer_api.iml b/.idea/osu_lazer_api.iml
deleted file mode 100644
index 32e115a..0000000
--- a/.idea/osu_lazer_api.iml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index c8397c9..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/DATA_SYNC_GUIDE.md b/DATA_SYNC_GUIDE.md
deleted file mode 100644
index f6ac41d..0000000
--- a/DATA_SYNC_GUIDE.md
+++ /dev/null
@@ -1,140 +0,0 @@
-# Lazer API 数据同步指南
-
-本指南将帮助您将现有的 bancho.py 数据库数据同步到新的 Lazer API 专用表中。
-
-## 文件说明
-
-1. **`migrations_old/add_missing_fields.sql`** - 创建 Lazer API 专用表结构
-2. **`migrations_old/sync_legacy_data.sql`** - 数据同步脚本
-3. **`sync_data.py`** - 交互式数据同步工具
-4. **`quick_sync.py`** - 快速同步脚本(使用项目配置)
-
-## 使用方法
-
-### 方法一:快速同步(推荐)
-
-如果您已经配置好了项目的数据库连接,可以直接使用快速同步脚本:
-
-```bash
-python quick_sync.py
-```
-
-此脚本会:
-1. 自动读取项目配置中的数据库连接信息
-2. 创建 Lazer API 专用表结构
-3. 同步现有数据到新表
-
-### 方法二:交互式同步
-
-如果需要使用不同的数据库连接配置:
-
-```bash
-python sync_data.py
-```
-
-此脚本会:
-1. 交互式地询问数据库连接信息
-2. 检查必要表是否存在
-3. 显示详细的同步过程和结果
-
-### 方法三:手动执行 SQL
-
-如果您熟悉 SQL 操作,可以手动执行:
-
-```bash
-# 1. 创建表结构
-mysql -u username -p database_name < migrations_old/add_missing_fields.sql
-
-# 2. 同步数据
-mysql -u username -p database_name < migrations_old/sync_legacy_data.sql
-```
-
-## 同步内容
-
-### 创建的新表
-
-- `lazer_user_profiles` - 用户扩展资料
-- `lazer_user_countries` - 用户国家信息
-- `lazer_user_kudosu` - 用户 Kudosu 统计
-- `lazer_user_counts` - 用户各项计数统计
-- `lazer_user_statistics` - 用户游戏统计(按模式)
-- `lazer_user_achievements` - 用户成就
-- `lazer_oauth_tokens` - OAuth 访问令牌
-- 其他相关表...
-
-### 同步的数据
-
-1. **用户基本信息**
- - 从 `users` 表同步基本资料
- - 自动转换时间戳格式
- - 设置合理的默认值
-
-2. **游戏统计**
- - 从 `stats` 表同步各模式的游戏数据
- - 计算命中精度和其他衍生统计
-
-3. **用户成就**
- - 从 `user_achievements` 表同步成就数据(如果存在)
-
-## 注意事项
-
-1. **安全性**
- - 脚本只会创建新表和插入数据
- - 不会修改或删除现有的原始表数据
- - 使用 `ON DUPLICATE KEY UPDATE` 避免重复插入
-
-2. **兼容性**
- - 兼容现有的 bancho.py 数据库结构
- - 支持标准的 osu! 数据格式
-
-3. **性能**
- - 大量数据可能需要较长时间
- - 建议在维护窗口期间执行
-
-## 故障排除
-
-### 常见错误
-
-1. **"Unknown column" 错误**
- ```
- ERROR 1054: Unknown column 'users.is_active' in 'field list'
- ```
- **解决方案**: 确保先执行了 `add_missing_fields.sql` 创建表结构
-
-2. **"Table doesn't exist" 错误**
- ```
- ERROR 1146: Table 'database.users' doesn't exist
- ```
- **解决方案**: 确认数据库中存在 bancho.py 的原始表
-
-3. **连接错误**
- ```
- ERROR 2003: Can't connect to MySQL server
- ```
- **解决方案**: 检查数据库连接配置和权限
-
-### 验证同步结果
-
-同步完成后,可以执行以下查询验证结果:
-
-```sql
--- 检查同步的用户数量
-SELECT COUNT(*) FROM lazer_user_profiles;
-
--- 查看样本数据
-SELECT
- u.id, u.name,
- lup.playmode, lup.is_supporter,
- lus.pp, lus.play_count
-FROM users u
-LEFT JOIN lazer_user_profiles lup ON u.id = lup.user_id
-LEFT JOIN lazer_user_statistics lus ON u.id = lus.user_id AND lus.mode = 'osu'
-LIMIT 5;
-```
-
-## 支持
-
-如果遇到问题,请:
-1. 检查日志文件 `data_sync.log`
-2. 确认数据库权限
-3. 验证原始表数据完整性
diff --git a/create_sample_data.py b/create_sample_data.py
deleted file mode 100644
index 5dcd79a..0000000
--- a/create_sample_data.py
+++ /dev/null
@@ -1,242 +0,0 @@
-#!/usr/bin/env python3
-"""
-osu! API 模拟服务器的示例数据填充脚本
-"""
-
-from __future__ import annotations
-
-import asyncio
-from datetime import datetime
-import random
-
-from app.auth import get_password_hash
-from app.database import (
- User,
-)
-from app.database.beatmap import Beatmap
-from app.database.beatmapset import Beatmapset
-from app.database.score import Score
-from app.dependencies.database import create_tables, engine
-from app.models.beatmap import BeatmapRankStatus, Genre, Language
-from app.models.mods import APIMod
-from app.models.score import GameMode, Rank
-
-from sqlmodel import select
-from sqlmodel.ext.asyncio.session import AsyncSession
-
-
-async def create_sample_user():
- """创建示例用户数据"""
- async with AsyncSession(engine) as session:
- async with session.begin():
- # 检查用户是否已存在
- result = await session.exec(select(User).where(User.name == "Googujiang"))
- result2 = await session.exec(
- select(User).where(User.name == "MingxuanGame")
- )
- existing_user = result.first()
- existing_user2 = result2.first()
- if existing_user is not None and existing_user2 is not None:
- print("示例用户已存在,跳过创建")
- return
-
- # 当前时间戳
- # current_timestamp = int(time.time())
- join_timestamp = int(datetime(2019, 11, 29, 17, 23, 13).timestamp())
- last_visit_timestamp = int(datetime(2025, 7, 18, 16, 31, 29).timestamp())
-
- # 创建用户
- user = User(
- name="Googujiang",
- safe_name="googujiang", # 安全用户名(小写)
- email="googujiang@example.com",
- priv=1, # 默认权限
- pw_bcrypt=get_password_hash("password123"), # 使用新的哈希方式
- country="JP",
- silence_end=0,
- donor_end=0,
- creation_time=join_timestamp,
- latest_activity=last_visit_timestamp,
- clan_id=0,
- clan_priv=0,
- preferred_mode=0, # 0 = osu!
- play_style=0,
- custom_badge_name=None,
- custom_badge_icon=None,
- userpage_content="「世界に忘れられた」",
- api_key=None,
- )
- user2 = User(
- name="MingxuanGame",
- safe_name="mingxuangame", # 安全用户名(小写)
- email="mingxuangame@example.com",
- priv=1, # 默认权限
- pw_bcrypt=get_password_hash("password123"), # 使用新的哈希方式
- country="US",
- silence_end=0,
- donor_end=0,
- creation_time=join_timestamp,
- latest_activity=last_visit_timestamp,
- clan_id=0,
- clan_priv=0,
- preferred_mode=0, # 0 = osu!
- play_style=0,
- custom_badge_name=None,
- custom_badge_icon=None,
- userpage_content="For love and fun!",
- api_key=None,
- )
-
- session.add(user)
- session.add(user2)
- print(f"成功创建示例用户: {user.name} (ID: {user.id})")
- print(f"安全用户名: {user.safe_name}")
- print(f"邮箱: {user.email}")
- print(f"国家: {user.country}")
- print(f"成功创建示例用户: {user2.name} (ID: {user2.id})")
- print(f"安全用户名: {user2.safe_name}")
- print(f"邮箱: {user2.email}")
- print(f"国家: {user2.country}")
-
-
-async def create_sample_beatmap_data():
- """创建示例谱面数据"""
- async with AsyncSession(engine) as session:
- async with session.begin():
- user_id = random.randint(1, 1000)
- # 检查谱面集是否已存在
- statement = select(Beatmapset).where(Beatmapset.id == 1)
- result = await session.exec(statement)
- existing_beatmapset = result.first()
- if existing_beatmapset:
- print("示例谱面集已存在,跳过创建")
- return existing_beatmapset
-
- # 创建谱面集
- beatmapset = Beatmapset(
- id=1,
- artist="Example Artist",
- artist_unicode="Example Artist",
- covers=None,
- creator="Googujiang",
- favourite_count=0,
- hype_current=0,
- hype_required=0,
- nsfw=False,
- play_count=0,
- preview_url="",
- source="",
- spotlight=False,
- title="Example Song",
- title_unicode="Example Song",
- user_id=user_id,
- video=False,
- availability_info=None,
- download_disabled=False,
- bpm=180.0,
- can_be_hyped=False,
- discussion_locked=False,
- last_updated=datetime.now(),
- ranked_date=datetime.now(),
- storyboard=False,
- submitted_date=datetime.now(),
- current_nominations=[],
- beatmap_status=BeatmapRankStatus.RANKED,
- beatmap_genre=Genre.ANY, # 使用整数表示Genre枚举
- beatmap_language=Language.ANY, # 使用整数表示Language枚举
- nominations_required=0,
- nominations_current=0,
- pack_tags=[],
- ratings=[],
- )
- session.add(beatmapset)
-
- # 创建谱面
- beatmap = Beatmap(
- id=1,
- url="",
- mode=GameMode.OSU,
- beatmapset_id=1,
- difficulty_rating=5.5,
- beatmap_status=BeatmapRankStatus.RANKED,
- total_length=195,
- user_id=user_id,
- version="Example Difficulty",
- checksum="example_checksum",
- current_user_playcount=0,
- max_combo=1200,
- ar=9.0,
- cs=4.0,
- drain=5.0,
- accuracy=8.0,
- bpm=180.0,
- count_circles=1000,
- count_sliders=200,
- count_spinners=1,
- deleted_at=None,
- hit_length=180,
- last_updated=datetime.now(),
- passcount=10,
- playcount=50,
- )
- session.add(beatmap)
-
- # 创建成绩
- score = Score(
- id=1,
- accuracy=0.9876,
- map_md5="example_checksum",
- user_id=1,
- best_id=1,
- build_id=None,
- classic_total_score=1234567,
- ended_at=datetime.now(),
- has_replay=True,
- max_combo=1100,
- mods=[
- APIMod(acronym="HD", settings={}),
- APIMod(acronym="DT", settings={}),
- ],
- passed=True,
- playlist_item_id=None,
- pp=250.5,
- preserve=True,
- rank=Rank.S,
- room_id=None,
- gamemode=GameMode.OSU,
- started_at=datetime.now(),
- total_score=1234567,
- type="solo_score",
- position=None,
- beatmap_id=1,
- n300=950,
- n100=30,
- n50=20,
- nmiss=5,
- ngeki=150,
- nkatu=50,
- nlarge_tick_miss=None,
- nslider_tail_hit=None,
- )
- session.add(score)
-
- print(f"成功创建示例谱面集: {beatmapset.title} (ID: {beatmapset.id})")
- print(f"成功创建示例谱面: {beatmap.version} (ID: {beatmap.id})")
- print(f"成功创建示例成绩: ID {score.id}")
- return beatmapset
-
-
-async def main():
- print("开始创建示例数据...")
- await create_tables()
- await create_sample_user()
- await create_sample_beatmap_data()
- print("示例数据创建完成!")
- # print(f"用户名: {user.name}")
- # print("密码: password123")
- # print("现在您可以使用这些凭据来测试API了。")
- await engine.dispose()
-
-
-if __name__ == "__main__":
- asyncio.run(main())
diff --git a/migrations_old/add_lazer_rank_fields.sql b/migrations_old/add_lazer_rank_fields.sql
deleted file mode 100644
index d811c90..0000000
--- a/migrations_old/add_lazer_rank_fields.sql
+++ /dev/null
@@ -1,16 +0,0 @@
--- 创建迁移日志表(如果不存在)
-CREATE TABLE IF NOT EXISTS `migration_logs` (
- `id` INT AUTO_INCREMENT PRIMARY KEY,
- `version` VARCHAR(50) NOT NULL,
- `description` VARCHAR(255) NOT NULL,
- `timestamp` DATETIME NOT NULL
-);
-
--- 向 lazer_user_statistics 表添加缺失的字段
-ALTER TABLE `lazer_user_statistics`
-ADD COLUMN IF NOT EXISTS `rank_highest` INT NULL COMMENT '最高排名' AFTER `grade_a`,
-ADD COLUMN IF NOT EXISTS `rank_highest_updated_at` DATETIME NULL COMMENT '最高排名更新时间' AFTER `rank_highest`;
-
--- 更新日志
-INSERT INTO `migration_logs` (`version`, `description`, `timestamp`)
-VALUES ('20250719', '向 lazer_user_statistics 表添加缺失的字段', NOW());
diff --git a/migrations_old/add_missing_fields.sql b/migrations_old/add_missing_fields.sql
deleted file mode 100644
index 464e5fa..0000000
--- a/migrations_old/add_missing_fields.sql
+++ /dev/null
@@ -1,421 +0,0 @@
--- Lazer API 专用数据表创建脚本
--- 基于真实 osu! API 返回数据设计的表结构
--- 完全不修改 bancho.py 原有表结构,创建全新的 lazer 专用表
-
--- ============================================
--- Lazer API 专用扩展表
--- ============================================
-
--- Lazer 用户扩展信息表
-CREATE TABLE IF NOT EXISTS lazer_user_profiles (
- user_id INT PRIMARY KEY COMMENT '关联 users.id',
-
- -- 基本状态字段
- is_active TINYINT(1) DEFAULT 1 COMMENT '用户是否激活',
- is_bot TINYINT(1) DEFAULT 0 COMMENT '是否为机器人账户',
- is_deleted TINYINT(1) DEFAULT 0 COMMENT '是否已删除',
- is_online TINYINT(1) DEFAULT 1 COMMENT '是否在线',
- is_supporter TINYINT(1) DEFAULT 0 COMMENT '是否为支持者',
- is_restricted TINYINT(1) DEFAULT 0 COMMENT '是否被限制',
- session_verified TINYINT(1) DEFAULT 0 COMMENT '会话是否已验证',
- has_supported TINYINT(1) DEFAULT 0 COMMENT '是否曾经支持过',
- pm_friends_only TINYINT(1) DEFAULT 0 COMMENT '是否只接受好友私信',
-
- -- 基本资料字段
- default_group VARCHAR(50) DEFAULT 'default' COMMENT '默认用户组',
- last_visit DATETIME NULL COMMENT '最后访问时间',
- join_date DATETIME NULL COMMENT '加入日期',
- profile_colour VARCHAR(7) NULL COMMENT '个人资料颜色',
- profile_hue INT NULL COMMENT '个人资料色调',
-
- -- 社交媒体和个人资料字段
- avatar_url VARCHAR(500) NULL COMMENT '头像URL',
- cover_url VARCHAR(500) NULL COMMENT '封面URL',
- discord VARCHAR(100) NULL COMMENT 'Discord用户名',
- twitter VARCHAR(100) NULL COMMENT 'Twitter用户名',
- website VARCHAR(500) NULL COMMENT '个人网站',
- title VARCHAR(100) NULL COMMENT '用户称号',
- title_url VARCHAR(500) NULL COMMENT '称号链接',
- interests TEXT NULL COMMENT '兴趣爱好',
- location VARCHAR(100) NULL COMMENT '地理位置',
- occupation VARCHAR(100) NULL COMMENT '职业',
-
- -- 游戏相关字段
- playmode VARCHAR(10) DEFAULT 'osu' COMMENT '主要游戏模式',
- support_level INT DEFAULT 0 COMMENT '支持者等级',
- max_blocks INT DEFAULT 100 COMMENT '最大屏蔽数量',
- max_friends INT DEFAULT 500 COMMENT '最大好友数量',
- post_count INT DEFAULT 0 COMMENT '帖子数量',
-
- -- 页面内容
- page_html TEXT NULL COMMENT '个人页面HTML',
- page_raw TEXT NULL COMMENT '个人页面原始内容',
-
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Lazer API 用户扩展资料表';
-
--- 用户封面信息表
-CREATE TABLE IF NOT EXISTS lazer_user_covers (
- user_id INT PRIMARY KEY COMMENT '关联 users.id',
- custom_url VARCHAR(500) NULL COMMENT '自定义封面URL',
- url VARCHAR(500) NULL COMMENT '封面URL',
- cover_id INT NULL COMMENT '封面ID',
-
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户封面信息表';
-
--- 用户国家信息表
-CREATE TABLE IF NOT EXISTS lazer_user_countries (
- user_id INT PRIMARY KEY COMMENT '关联 users.id',
- code VARCHAR(2) NOT NULL COMMENT '国家代码',
- name VARCHAR(100) NOT NULL COMMENT '国家名称',
-
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户国家信息表';
-
--- 用户 Kudosu 表
-CREATE TABLE IF NOT EXISTS lazer_user_kudosu (
- user_id INT PRIMARY KEY COMMENT '关联 users.id',
- available INT DEFAULT 0 COMMENT '可用 Kudosu',
- total INT DEFAULT 0 COMMENT '总 Kudosu',
-
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户 Kudosu 表';
-
--- 用户统计计数表
-CREATE TABLE IF NOT EXISTS lazer_user_counts (
- user_id INT PRIMARY KEY COMMENT '关联 users.id',
-
- -- 统计计数字段
- beatmap_playcounts_count INT DEFAULT 0 COMMENT '谱面游玩次数统计',
- comments_count INT DEFAULT 0 COMMENT '评论数量',
- favourite_beatmapset_count INT DEFAULT 0 COMMENT '收藏谱面集数量',
- follower_count INT DEFAULT 0 COMMENT '关注者数量',
- graveyard_beatmapset_count INT DEFAULT 0 COMMENT '坟场谱面集数量',
- guest_beatmapset_count INT DEFAULT 0 COMMENT '客串谱面集数量',
- loved_beatmapset_count INT DEFAULT 0 COMMENT '被喜爱谱面集数量',
- mapping_follower_count INT DEFAULT 0 COMMENT '作图关注者数量',
- nominated_beatmapset_count INT DEFAULT 0 COMMENT '提名谱面集数量',
- pending_beatmapset_count INT DEFAULT 0 COMMENT '待审核谱面集数量',
- ranked_beatmapset_count INT DEFAULT 0 COMMENT 'Ranked谱面集数量',
- ranked_and_approved_beatmapset_count INT DEFAULT 0 COMMENT 'Ranked+Approved谱面集数量',
- unranked_beatmapset_count INT DEFAULT 0 COMMENT '未Ranked谱面集数量',
- scores_best_count INT DEFAULT 0 COMMENT '最佳成绩数量',
- scores_first_count INT DEFAULT 0 COMMENT '第一名成绩数量',
- scores_pinned_count INT DEFAULT 0 COMMENT '置顶成绩数量',
- scores_recent_count INT DEFAULT 0 COMMENT '最近成绩数量',
-
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Lazer API 用户统计计数表';
-
--- 用户游戏风格表 (替代 playstyle JSON)
-CREATE TABLE IF NOT EXISTS lazer_user_playstyles (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- style VARCHAR(50) NOT NULL COMMENT '游戏风格: mouse, keyboard, tablet, touch',
-
- INDEX idx_user_id (user_id),
- UNIQUE KEY unique_user_style (user_id, style),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户游戏风格表';
-
--- 用户个人资料显示顺序表 (替代 profile_order JSON)
-CREATE TABLE IF NOT EXISTS lazer_user_profile_sections (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- section_name VARCHAR(50) NOT NULL COMMENT '部分名称',
- display_order INT DEFAULT 0 COMMENT '显示顺序',
-
- INDEX idx_user_id (user_id),
- INDEX idx_order (user_id, display_order),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户个人资料显示顺序表';
-
--- 用户账户历史表 (替代 account_history JSON)
-CREATE TABLE IF NOT EXISTS lazer_user_account_history (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- event_type VARCHAR(50) NOT NULL COMMENT '事件类型',
- description TEXT COMMENT '事件描述',
- length INT COMMENT '持续时间(秒)',
- permanent TINYINT(1) DEFAULT 0 COMMENT '是否永久',
- event_time DATETIME NOT NULL COMMENT '事件时间',
-
- INDEX idx_user_id (user_id),
- INDEX idx_event_time (event_time),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户账户历史表';
-
--- 用户历史用户名表 (替代 previous_usernames JSON)
-CREATE TABLE IF NOT EXISTS lazer_user_previous_usernames (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- username VARCHAR(32) NOT NULL COMMENT '历史用户名',
- changed_at DATETIME NOT NULL COMMENT '更改时间',
-
- INDEX idx_user_id (user_id),
- INDEX idx_changed_at (changed_at),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户历史用户名表';
-
--- 用户月度游戏次数表 (替代 monthly_playcounts JSON)
-CREATE TABLE IF NOT EXISTS lazer_user_monthly_playcounts (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- start_date DATE NOT NULL COMMENT '月份开始日期',
- play_count INT DEFAULT 0 COMMENT '游戏次数',
-
- INDEX idx_user_id (user_id),
- INDEX idx_start_date (start_date),
- UNIQUE KEY unique_user_month (user_id, start_date),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户月度游戏次数表';
-
--- 用户最高排名表 (rank_highest)
-CREATE TABLE IF NOT EXISTS lazer_user_rank_highest (
- user_id INT PRIMARY KEY COMMENT '关联 users.id',
- rank_position INT NOT NULL COMMENT '最高排名位置',
- updated_at DATETIME NOT NULL COMMENT '更新时间',
-
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户最高排名表';
-
--- ============================================
--- OAuth 令牌表 (Lazer API 专用)
--- ============================================
-CREATE TABLE IF NOT EXISTS lazer_oauth_tokens (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL,
- access_token VARCHAR(255) NOT NULL,
- refresh_token VARCHAR(255) NOT NULL,
- expires_at DATETIME NOT NULL,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- INDEX idx_user_id (user_id),
- INDEX idx_access_token (access_token),
- INDEX idx_refresh_token (refresh_token),
- INDEX idx_expires_at (expires_at),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Lazer API OAuth访问令牌表';
-
--- ============================================
--- 用户统计数据表 (基于真实 API 数据结构)
--- ============================================
-
--- 用户主要统计表 (statistics 字段)
-CREATE TABLE IF NOT EXISTS lazer_user_statistics (
- user_id INT NOT NULL,
- mode VARCHAR(10) NOT NULL DEFAULT 'osu' COMMENT '游戏模式: osu, taiko, fruits, mania',
-
- -- 基本命中统计
- count_100 INT DEFAULT 0 COMMENT '100分命中数',
- count_300 INT DEFAULT 0 COMMENT '300分命中数',
- count_50 INT DEFAULT 0 COMMENT '50分命中数',
- count_miss INT DEFAULT 0 COMMENT 'Miss数',
-
- -- 等级信息
- level_current INT DEFAULT 1 COMMENT '当前等级',
- level_progress INT DEFAULT 0 COMMENT '等级进度',
-
- -- 排名信息
- global_rank INT NULL COMMENT '全球排名',
- global_rank_exp INT NULL COMMENT '全球排名(实验性)',
- country_rank INT NULL COMMENT '国家/地区排名',
-
- -- PP 和分数
- pp DECIMAL(10,2) DEFAULT 0.00 COMMENT 'Performance Points',
- pp_exp DECIMAL(10,2) DEFAULT 0.00 COMMENT 'PP(实验性)',
- ranked_score BIGINT DEFAULT 0 COMMENT 'Ranked分数',
- hit_accuracy DECIMAL(5,2) DEFAULT 0.00 COMMENT '命中精度',
- total_score BIGINT DEFAULT 0 COMMENT '总分数',
- total_hits BIGINT DEFAULT 0 COMMENT '总命中数',
- maximum_combo INT DEFAULT 0 COMMENT '最大连击',
-
- -- 游戏统计
- play_count INT DEFAULT 0 COMMENT '游戏次数',
- play_time INT DEFAULT 0 COMMENT '游戏时间(秒)',
- replays_watched_by_others INT DEFAULT 0 COMMENT '被观看的Replay次数',
- is_ranked TINYINT(1) DEFAULT 0 COMMENT '是否有排名',
-
- -- 成绩等级计数 (grade_counts)
- grade_ss INT DEFAULT 0 COMMENT 'SS等级数',
- grade_ssh INT DEFAULT 0 COMMENT 'SSH等级数',
- grade_s INT DEFAULT 0 COMMENT 'S等级数',
- grade_sh INT DEFAULT 0 COMMENT 'SH等级数',
- grade_a INT DEFAULT 0 COMMENT 'A等级数',
-
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-
- PRIMARY KEY (user_id, mode),
- INDEX idx_mode (mode),
- INDEX idx_global_rank (global_rank),
- INDEX idx_country_rank (country_rank),
- INDEX idx_pp (pp),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='Lazer API 用户游戏统计表';
-
--- 每日挑战用户统计表 (daily_challenge_user_stats)
-CREATE TABLE IF NOT EXISTS lazer_daily_challenge_stats (
- user_id INT PRIMARY KEY COMMENT '关联 users.id',
- daily_streak_best INT DEFAULT 0 COMMENT '最佳每日连击',
- daily_streak_current INT DEFAULT 0 COMMENT '当前每日连击',
- last_update DATE NULL COMMENT '最后更新日期',
- last_weekly_streak DATE NULL COMMENT '最后周连击日期',
- playcount INT DEFAULT 0 COMMENT '游戏次数',
- top_10p_placements INT DEFAULT 0 COMMENT 'Top 10% 位置数',
- top_50p_placements INT DEFAULT 0 COMMENT 'Top 50% 位置数',
- weekly_streak_best INT DEFAULT 0 COMMENT '最佳周连击',
- weekly_streak_current INT DEFAULT 0 COMMENT '当前周连击',
-
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='每日挑战用户统计表';
-
--- 用户团队信息表 (team 字段)
-CREATE TABLE IF NOT EXISTS lazer_user_teams (
- user_id INT PRIMARY KEY COMMENT '关联 users.id',
- team_id INT NOT NULL COMMENT '团队ID',
- team_name VARCHAR(100) NOT NULL COMMENT '团队名称',
- team_short_name VARCHAR(10) NOT NULL COMMENT '团队简称',
- flag_url VARCHAR(500) NULL COMMENT '团队旗帜URL',
-
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户团队信息表';
-
--- 用户成就表 (user_achievements)
-CREATE TABLE IF NOT EXISTS lazer_user_achievements (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- achievement_id INT NOT NULL COMMENT '成就ID',
- achieved_at DATETIME NOT NULL COMMENT '获得时间',
-
- INDEX idx_user_id (user_id),
- INDEX idx_achievement_id (achievement_id),
- INDEX idx_achieved_at (achieved_at),
- UNIQUE KEY unique_user_achievement (user_id, achievement_id),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户成就表';
-
--- 用户排名历史表 (rank_history)
-CREATE TABLE IF NOT EXISTS lazer_user_rank_history (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- mode VARCHAR(10) NOT NULL DEFAULT 'osu' COMMENT '游戏模式',
- day_offset INT NOT NULL COMMENT '天数偏移量(从某个基准日期开始)',
- rank_position INT NOT NULL COMMENT '排名位置',
-
- INDEX idx_user_mode (user_id, mode),
- INDEX idx_day_offset (day_offset),
- UNIQUE KEY unique_user_mode_day (user_id, mode, day_offset),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户排名历史表';
-
--- Replay 观看次数表 (replays_watched_counts)
-CREATE TABLE IF NOT EXISTS lazer_user_replays_watched (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- start_date DATE NOT NULL COMMENT '开始日期',
- count INT DEFAULT 0 COMMENT '观看次数',
-
- INDEX idx_user_id (user_id),
- INDEX idx_start_date (start_date),
- UNIQUE KEY unique_user_date (user_id, start_date),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户Replay观看次数表';
-
--- 用户徽章表 (badges)
-CREATE TABLE IF NOT EXISTS lazer_user_badges (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- badge_id INT NOT NULL COMMENT '徽章ID',
- awarded_at DATETIME NULL COMMENT '授予时间',
- description TEXT NULL COMMENT '徽章描述',
- image_url VARCHAR(500) NULL COMMENT '徽章图片URL',
- url VARCHAR(500) NULL COMMENT '徽章链接',
-
- INDEX idx_user_id (user_id),
- INDEX idx_badge_id (badge_id),
- UNIQUE KEY unique_user_badge (user_id, badge_id),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户徽章表';
-
--- 用户组表 (groups)
-CREATE TABLE IF NOT EXISTS lazer_user_groups (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- group_id INT NOT NULL COMMENT '用户组ID',
- group_name VARCHAR(100) NOT NULL COMMENT '用户组名称',
- group_identifier VARCHAR(50) NULL COMMENT '用户组标识符',
- colour VARCHAR(7) NULL COMMENT '用户组颜色',
- is_probationary TINYINT(1) DEFAULT 0 COMMENT '是否为试用期',
- has_listing TINYINT(1) DEFAULT 1 COMMENT '是否显示在列表中',
- has_playmodes TINYINT(1) DEFAULT 0 COMMENT '是否有游戏模式',
-
- INDEX idx_user_id (user_id),
- INDEX idx_group_id (group_id),
- UNIQUE KEY unique_user_group (user_id, group_id),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户组表';
-
--- 锦标赛横幅表 (active_tournament_banners)
-CREATE TABLE IF NOT EXISTS lazer_user_tournament_banners (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- tournament_id INT NOT NULL COMMENT '锦标赛ID',
- image_url VARCHAR(500) NOT NULL COMMENT '横幅图片URL',
- is_active TINYINT(1) DEFAULT 1 COMMENT '是否为当前活跃横幅',
-
- INDEX idx_user_id (user_id),
- INDEX idx_tournament_id (tournament_id),
- INDEX idx_is_active (is_active),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='用户锦标赛横幅表';
-
--- ============================================
--- 占位表 (未来功能扩展用)
--- ============================================
-
--- 当前赛季统计占位表
-CREATE TABLE IF NOT EXISTS lazer_current_season_stats (
- id INT AUTO_INCREMENT PRIMARY KEY,
- user_id INT NOT NULL COMMENT '关联 users.id',
- season_id VARCHAR(50) NOT NULL COMMENT '赛季ID',
- data_placeholder TEXT COMMENT '赛季数据占位',
-
- INDEX idx_user_id (user_id),
- INDEX idx_season_id (season_id),
- UNIQUE KEY unique_user_season (user_id, season_id),
- FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='当前赛季统计占位表';
-
--- 其他功能占位表
-CREATE TABLE IF NOT EXISTS lazer_feature_placeholder (
- id INT AUTO_INCREMENT PRIMARY KEY,
- feature_type VARCHAR(50) NOT NULL COMMENT '功能类型',
- entity_id INT NOT NULL COMMENT '实体ID',
- data_placeholder TEXT COMMENT '功能数据占位',
-
- INDEX idx_feature_type (feature_type),
- INDEX idx_entity_id (entity_id)
-) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='功能扩展占位表';
diff --git a/migrations_old/base.sql b/migrations_old/base.sql
deleted file mode 100644
index 665723f..0000000
--- a/migrations_old/base.sql
+++ /dev/null
@@ -1,486 +0,0 @@
-create table achievements
-(
- id int auto_increment
- primary key,
- file varchar(128) not null,
- name varchar(128) charset utf8 not null,
- `desc` varchar(256) charset utf8 not null,
- cond varchar(64) not null,
- constraint achievements_desc_uindex
- unique (`desc`),
- constraint achievements_file_uindex
- unique (file),
- constraint achievements_name_uindex
- unique (name)
-);
-
-create table channels
-(
- id int auto_increment
- primary key,
- name varchar(32) not null,
- topic varchar(256) not null,
- read_priv int default 1 not null,
- write_priv int default 2 not null,
- auto_join tinyint(1) default 0 not null,
- constraint channels_name_uindex
- unique (name)
-);
-create index channels_auto_join_index
- on channels (auto_join);
-
-create table clans
-(
- id int auto_increment
- primary key,
- name varchar(16) charset utf8 not null,
- tag varchar(6) charset utf8 not null,
- owner int not null,
- created_at datetime not null,
- constraint clans_name_uindex
- unique (name),
- constraint clans_owner_uindex
- unique (owner),
- constraint clans_tag_uindex
- unique (tag)
-);
-
-create table client_hashes
-(
- userid int not null,
- osupath char(32) not null,
- adapters char(32) not null,
- uninstall_id char(32) not null,
- disk_serial char(32) not null,
- latest_time datetime not null,
- occurrences int default 0 not null,
- primary key (userid, osupath, adapters, uninstall_id, disk_serial)
-);
-
-create table comments
-(
- id int auto_increment
- primary key,
- target_id int not null comment 'replay, map, or set id',
- target_type enum('replay', 'map', 'song') not null,
- userid int not null,
- time int not null,
- comment varchar(80) charset utf8 not null,
- colour char(6) null comment 'rgb hex string'
-);
-
-create table favourites
-(
- userid int not null,
- setid int not null,
- created_at int default 0 not null,
- primary key (userid, setid)
-);
-
-create table ingame_logins
-(
- id int auto_increment
- primary key,
- userid int not null,
- ip varchar(45) not null comment 'maxlen for ipv6',
- osu_ver date not null,
- osu_stream varchar(11) not null,
- datetime datetime not null
-);
-
-create table relationships
-(
- user1 int not null,
- user2 int not null,
- type enum('friend', 'block') not null,
- primary key (user1, user2)
-);
-
-create table logs
-(
- id int auto_increment
- primary key,
- `from` int not null comment 'both from and to are playerids',
- `to` int not null,
- `action` varchar(32) not null,
- msg varchar(2048) charset utf8 null,
- time datetime not null on update CURRENT_TIMESTAMP
-);
-
-create table mail
-(
- id int auto_increment
- primary key,
- from_id int not null,
- to_id int not null,
- msg varchar(2048) charset utf8 not null,
- time int null,
- `read` tinyint(1) default 0 not null
-);
-
-create table maps
-(
- server enum('osu!', 'private') default 'osu!' not null,
- id int not null,
- set_id int not null,
- status int not null,
- md5 char(32) not null,
- artist varchar(128) charset utf8 not null,
- title varchar(128) charset utf8 not null,
- version varchar(128) charset utf8 not null,
- creator varchar(19) charset utf8 not null,
- filename varchar(256) charset utf8 not null,
- last_update datetime not null,
- total_length int not null,
- max_combo int not null,
- frozen tinyint(1) default 0 not null,
- plays int default 0 not null,
- passes int default 0 not null,
- mode tinyint(1) default 0 not null,
- bpm float(12,2) default 0.00 not null,
- cs float(4,2) default 0.00 not null,
- ar float(4,2) default 0.00 not null,
- od float(4,2) default 0.00 not null,
- hp float(4,2) default 0.00 not null,
- diff float(6,3) default 0.000 not null,
- primary key (server, id),
- constraint maps_id_uindex
- unique (id),
- constraint maps_md5_uindex
- unique (md5)
-);
-create index maps_set_id_index
- on maps (set_id);
-create index maps_status_index
- on maps (status);
-create index maps_filename_index
- on maps (filename);
-create index maps_plays_index
- on maps (plays);
-create index maps_mode_index
- on maps (mode);
-create index maps_frozen_index
- on maps (frozen);
-
-create table mapsets
-(
- server enum('osu!', 'private') default 'osu!' not null,
- id int not null,
- last_osuapi_check datetime default CURRENT_TIMESTAMP not null,
- primary key (server, id),
- constraint nmapsets_id_uindex
- unique (id)
-);
-
-create table map_requests
-(
- id int auto_increment
- primary key,
- map_id int not null,
- player_id int not null,
- datetime datetime not null,
- active tinyint(1) not null
-);
-
-create table performance_reports
-(
- scoreid bigint(20) unsigned not null,
- mod_mode enum('vanilla', 'relax', 'autopilot') default 'vanilla' not null,
- os varchar(64) not null,
- fullscreen tinyint(1) not null,
- fps_cap varchar(16) not null,
- compatibility tinyint(1) not null,
- version varchar(16) not null,
- start_time int not null,
- end_time int not null,
- frame_count int not null,
- spike_frames int not null,
- aim_rate int not null,
- completion tinyint(1) not null,
- identifier varchar(128) null comment 'really don''t know much about this yet',
- average_frametime int not null,
- primary key (scoreid, mod_mode)
-);
-
-create table ratings
-(
- userid int not null,
- map_md5 char(32) not null,
- rating tinyint(2) not null,
- primary key (userid, map_md5)
-);
-
-create table scores
-(
- id bigint unsigned auto_increment
- primary key,
- map_md5 char(32) not null,
- score int not null,
- pp float(7,3) not null,
- acc float(6,3) not null,
- max_combo int not null,
- mods int not null,
- n300 int not null,
- n100 int not null,
- n50 int not null,
- nmiss int not null,
- ngeki int not null,
- nkatu int not null,
- grade varchar(2) default 'N' not null,
- status tinyint not null,
- mode tinyint not null,
- play_time datetime not null,
- time_elapsed int not null,
- client_flags int not null,
- userid int not null,
- perfect tinyint(1) not null,
- online_checksum char(32) not null
-);
-create index scores_map_md5_index
- on scores (map_md5);
-create index scores_score_index
- on scores (score);
-create index scores_pp_index
- on scores (pp);
-create index scores_mods_index
- on scores (mods);
-create index scores_status_index
- on scores (status);
-create index scores_mode_index
- on scores (mode);
-create index scores_play_time_index
- on scores (play_time);
-create index scores_userid_index
- on scores (userid);
-create index scores_online_checksum_index
- on scores (online_checksum);
-create index scores_fetch_leaderboard_generic_index
- on scores (map_md5, status, mode);
-
-create table startups
-(
- id int auto_increment
- primary key,
- ver_major tinyint not null,
- ver_minor tinyint not null,
- ver_micro tinyint not null,
- datetime datetime not null
-);
-
-create table stats
-(
- id int auto_increment,
- mode tinyint(1) not null,
- tscore bigint unsigned default 0 not null,
- rscore bigint unsigned default 0 not null,
- pp int unsigned default 0 not null,
- plays int unsigned default 0 not null,
- playtime int unsigned default 0 not null,
- acc float(6,3) default 0.000 not null,
- max_combo int unsigned default 0 not null,
- total_hits int unsigned default 0 not null,
- replay_views int unsigned default 0 not null,
- xh_count int unsigned default 0 not null,
- x_count int unsigned default 0 not null,
- sh_count int unsigned default 0 not null,
- s_count int unsigned default 0 not null,
- a_count int unsigned default 0 not null,
- primary key (id, mode)
-);
-create index stats_mode_index
- on stats (mode);
-create index stats_pp_index
- on stats (pp);
-create index stats_tscore_index
- on stats (tscore);
-create index stats_rscore_index
- on stats (rscore);
-
-create table tourney_pool_maps
-(
- map_id int not null,
- pool_id int not null,
- mods int not null,
- slot tinyint not null,
- primary key (map_id, pool_id)
-);
-create index tourney_pool_maps_mods_slot_index
- on tourney_pool_maps (mods, slot);
-create index tourney_pool_maps_tourney_pools_id_fk
- on tourney_pool_maps (pool_id);
-
-create table tourney_pools
-(
- id int auto_increment
- primary key,
- name varchar(16) not null,
- created_at datetime not null,
- created_by int not null
-);
-
-create index tourney_pools_users_id_fk
- on tourney_pools (created_by);
-
-create table user_achievements
-(
- userid int not null,
- achid int not null,
- primary key (userid, achid)
-);
-create index user_achievements_achid_index
- on user_achievements (achid);
-create index user_achievements_userid_index
- on user_achievements (userid);
-
-create table users
-(
- id int auto_increment
- primary key,
- name varchar(32) charset utf8 not null,
- safe_name varchar(32) charset utf8 not null,
- email varchar(254) not null,
- priv int default 1 not null,
- pw_bcrypt char(60) not null,
- country char(2) default 'xx' not null,
- silence_end int default 0 not null,
- donor_end int default 0 not null,
- creation_time int default 0 not null,
- latest_activity int default 0 not null,
- clan_id int default 0 not null,
- clan_priv tinyint(1) default 0 not null,
- preferred_mode int default 0 not null,
- play_style int default 0 not null,
- custom_badge_name varchar(16) charset utf8 null,
- custom_badge_icon varchar(64) null,
- userpage_content varchar(2048) charset utf8 null,
- api_key char(36) null,
- constraint users_api_key_uindex
- unique (api_key),
- constraint users_email_uindex
- unique (email),
- constraint users_name_uindex
- unique (name),
- constraint users_safe_name_uindex
- unique (safe_name)
-);
-create index users_priv_index
- on users (priv);
-create index users_clan_id_index
- on users (clan_id);
-create index users_clan_priv_index
- on users (clan_priv);
-create index users_country_index
- on users (country);
-
-insert into users (id, name, safe_name, priv, country, silence_end, email, pw_bcrypt, creation_time, latest_activity)
-values (1, 'BanchoBot', 'banchobot', 1, 'ca', 0, 'bot@akatsuki.pw',
- '_______________________my_cool_bcrypt_______________________', UNIX_TIMESTAMP(), UNIX_TIMESTAMP());
-
-INSERT INTO stats (id, mode) VALUES (1, 0); # vn!std
-INSERT INTO stats (id, mode) VALUES (1, 1); # vn!taiko
-INSERT INTO stats (id, mode) VALUES (1, 2); # vn!catch
-INSERT INTO stats (id, mode) VALUES (1, 3); # vn!mania
-INSERT INTO stats (id, mode) VALUES (1, 4); # rx!std
-INSERT INTO stats (id, mode) VALUES (1, 5); # rx!taiko
-INSERT INTO stats (id, mode) VALUES (1, 6); # rx!catch
-INSERT INTO stats (id, mode) VALUES (1, 8); # ap!std
-
-
-# userid 2 is reserved for ppy in osu!, and the
-# client will not allow users to pm this id.
-# If you want this, simply remove these two lines.
-alter table users auto_increment = 3;
-alter table stats auto_increment = 3;
-
-insert into channels (name, topic, read_priv, write_priv, auto_join)
-values ('#osu', 'General discussion.', 1, 2, true),
- ('#announce', 'Exemplary performance and public announcements.', 1, 24576, true),
- ('#lobby', 'Multiplayer lobby discussion room.', 1, 2, false),
- ('#supporter', 'General discussion for supporters.', 48, 48, false),
- ('#staff', 'General discussion for staff members.', 28672, 28672, true),
- ('#admin', 'General discussion for administrators.', 24576, 24576, true),
- ('#dev', 'General discussion for developers.', 16384, 16384, true);
-
-insert into achievements (id, file, name, `desc`, cond) values (1, 'osu-skill-pass-1', 'Rising Star', 'Can''t go forward without the first steps.', '(score.mods & 1 == 0) and 1 <= score.sr < 2 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (2, 'osu-skill-pass-2', 'Constellation Prize', 'Definitely not a consolation prize. Now things start getting hard!', '(score.mods & 1 == 0) and 2 <= score.sr < 3 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (3, 'osu-skill-pass-3', 'Building Confidence', 'Oh, you''ve SO got this.', '(score.mods & 1 == 0) and 3 <= score.sr < 4 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (4, 'osu-skill-pass-4', 'Insanity Approaches', 'You''re not twitching, you''re just ready.', '(score.mods & 1 == 0) and 4 <= score.sr < 5 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (5, 'osu-skill-pass-5', 'These Clarion Skies', 'Everything seems so clear now.', '(score.mods & 1 == 0) and 5 <= score.sr < 6 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (6, 'osu-skill-pass-6', 'Above and Beyond', 'A cut above the rest.', '(score.mods & 1 == 0) and 6 <= score.sr < 7 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (7, 'osu-skill-pass-7', 'Supremacy', 'All marvel before your prowess.', '(score.mods & 1 == 0) and 7 <= score.sr < 8 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (8, 'osu-skill-pass-8', 'Absolution', 'My god, you''re full of stars!', '(score.mods & 1 == 0) and 8 <= score.sr < 9 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (9, 'osu-skill-pass-9', 'Event Horizon', 'No force dares to pull you under.', '(score.mods & 1 == 0) and 9 <= score.sr < 10 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (10, 'osu-skill-pass-10', 'Phantasm', 'Fevered is your passion, extraordinary is your skill.', '(score.mods & 1 == 0) and 10 <= score.sr < 11 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (11, 'osu-skill-fc-1', 'Totality', 'All the notes. Every single one.', 'score.perfect and 1 <= score.sr < 2 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (12, 'osu-skill-fc-2', 'Business As Usual', 'Two to go, please.', 'score.perfect and 2 <= score.sr < 3 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (13, 'osu-skill-fc-3', 'Building Steam', 'Hey, this isn''t so bad.', 'score.perfect and 3 <= score.sr < 4 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (14, 'osu-skill-fc-4', 'Moving Forward', 'Bet you feel good about that.', 'score.perfect and 4 <= score.sr < 5 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (15, 'osu-skill-fc-5', 'Paradigm Shift', 'Surprisingly difficult.', 'score.perfect and 5 <= score.sr < 6 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (16, 'osu-skill-fc-6', 'Anguish Quelled', 'Don''t choke.', 'score.perfect and 6 <= score.sr < 7 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (17, 'osu-skill-fc-7', 'Never Give Up', 'Excellence is its own reward.', 'score.perfect and 7 <= score.sr < 8 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (18, 'osu-skill-fc-8', 'Aberration', 'They said it couldn''t be done. They were wrong.', 'score.perfect and 8 <= score.sr < 9 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (19, 'osu-skill-fc-9', 'Chosen', 'Reign among the Prometheans, where you belong.', 'score.perfect and 9 <= score.sr < 10 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (20, 'osu-skill-fc-10', 'Unfathomable', 'You have no equal.', 'score.perfect and 10 <= score.sr < 11 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (21, 'osu-combo-500', '500 Combo', '500 big ones! You''re moving up in the world!', '500 <= score.max_combo < 750 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (22, 'osu-combo-750', '750 Combo', '750 notes back to back? Woah.', '750 <= score.max_combo < 1000 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (23, 'osu-combo-1000', '1000 Combo', 'A thousand reasons why you rock at this game.', '1000 <= score.max_combo < 2000 and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (24, 'osu-combo-2000', '2000 Combo', 'Nothing can stop you now.', '2000 <= score.max_combo and mode_vn == 0');
-insert into achievements (id, file, name, `desc`, cond) values (25, 'taiko-skill-pass-1', 'My First Don', 'Marching to the beat of your own drum. Literally.', '(score.mods & 1 == 0) and 1 <= score.sr < 2 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (26, 'taiko-skill-pass-2', 'Katsu Katsu Katsu', 'Hora! Izuko!', '(score.mods & 1 == 0) and 2 <= score.sr < 3 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (27, 'taiko-skill-pass-3', 'Not Even Trying', 'Muzukashii? Not even.', '(score.mods & 1 == 0) and 3 <= score.sr < 4 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (28, 'taiko-skill-pass-4', 'Face Your Demons', 'The first trials are now behind you, but are you a match for the Oni?', '(score.mods & 1 == 0) and 4 <= score.sr < 5 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (29, 'taiko-skill-pass-5', 'The Demon Within', 'No rest for the wicked.', '(score.mods & 1 == 0) and 5 <= score.sr < 6 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (30, 'taiko-skill-pass-6', 'Drumbreaker', 'Too strong.', '(score.mods & 1 == 0) and 6 <= score.sr < 7 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (31, 'taiko-skill-pass-7', 'The Godfather', 'You are the Don of Dons.', '(score.mods & 1 == 0) and 7 <= score.sr < 8 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (32, 'taiko-skill-pass-8', 'Rhythm Incarnate', 'Feel the beat. Become the beat.', '(score.mods & 1 == 0) and 8 <= score.sr < 9 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (33, 'taiko-skill-fc-1', 'Keeping Time', 'Don, then katsu. Don, then katsu..', 'score.perfect and 1 <= score.sr < 2 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (34, 'taiko-skill-fc-2', 'To Your Own Beat', 'Straight and steady.', 'score.perfect and 2 <= score.sr < 3 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (35, 'taiko-skill-fc-3', 'Big Drums', 'Bigger scores to match.', 'score.perfect and 3 <= score.sr < 4 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (36, 'taiko-skill-fc-4', 'Adversity Overcome', 'Difficult? Not for you.', 'score.perfect and 4 <= score.sr < 5 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (37, 'taiko-skill-fc-5', 'Demonslayer', 'An Oni felled forevermore.', 'score.perfect and 5 <= score.sr < 6 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (38, 'taiko-skill-fc-6', 'Rhythm''s Call', 'Heralding true skill.', 'score.perfect and 6 <= score.sr < 7 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (39, 'taiko-skill-fc-7', 'Time Everlasting', 'Not a single beat escapes you.', 'score.perfect and 7 <= score.sr < 8 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (40, 'taiko-skill-fc-8', 'The Drummer''s Throne', 'Percussive brilliance befitting royalty alone.', 'score.perfect and 8 <= score.sr < 9 and mode_vn == 1');
-insert into achievements (id, file, name, `desc`, cond) values (41, 'fruits-skill-pass-1', 'A Slice Of Life', 'Hey, this fruit catching business isn''t bad.', '(score.mods & 1 == 0) and 1 <= score.sr < 2 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (42, 'fruits-skill-pass-2', 'Dashing Ever Forward', 'Fast is how you do it.', '(score.mods & 1 == 0) and 2 <= score.sr < 3 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (43, 'fruits-skill-pass-3', 'Zesty Disposition', 'No scurvy for you, not with that much fruit.', '(score.mods & 1 == 0) and 3 <= score.sr < 4 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (44, 'fruits-skill-pass-4', 'Hyperdash ON!', 'Time and distance is no obstacle to you.', '(score.mods & 1 == 0) and 4 <= score.sr < 5 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (45, 'fruits-skill-pass-5', 'It''s Raining Fruit', 'And you can catch them all.', '(score.mods & 1 == 0) and 5 <= score.sr < 6 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (46, 'fruits-skill-pass-6', 'Fruit Ninja', 'Legendary techniques.', '(score.mods & 1 == 0) and 6 <= score.sr < 7 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (47, 'fruits-skill-pass-7', 'Dreamcatcher', 'No fruit, only dreams now.', '(score.mods & 1 == 0) and 7 <= score.sr < 8 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (48, 'fruits-skill-pass-8', 'Lord of the Catch', 'Your kingdom kneels before you.', '(score.mods & 1 == 0) and 8 <= score.sr < 9 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (49, 'fruits-skill-fc-1', 'Sweet And Sour', 'Apples and oranges, literally.', 'score.perfect and 1 <= score.sr < 2 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (50, 'fruits-skill-fc-2', 'Reaching The Core', 'The seeds of future success.', 'score.perfect and 2 <= score.sr < 3 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (51, 'fruits-skill-fc-3', 'Clean Platter', 'Clean only of failure. It is completely full, otherwise.', 'score.perfect and 3 <= score.sr < 4 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (52, 'fruits-skill-fc-4', 'Between The Rain', 'No umbrella needed.', 'score.perfect and 4 <= score.sr < 5 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (53, 'fruits-skill-fc-5', 'Addicted', 'That was an overdose?', 'score.perfect and 5 <= score.sr < 6 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (54, 'fruits-skill-fc-6', 'Quickening', 'A dash above normal limits.', 'score.perfect and 6 <= score.sr < 7 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (55, 'fruits-skill-fc-7', 'Supersonic', 'Faster than is reasonably necessary.', 'score.perfect and 7 <= score.sr < 8 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (56, 'fruits-skill-fc-8', 'Dashing Scarlet', 'Speed beyond mortal reckoning.', 'score.perfect and 8 <= score.sr < 9 and mode_vn == 2');
-insert into achievements (id, file, name, `desc`, cond) values (57, 'mania-skill-pass-1', 'First Steps', 'It isn''t 9-to-5, but 1-to-9. Keys, that is.', '(score.mods & 1 == 0) and 1 <= score.sr < 2 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (58, 'mania-skill-pass-2', 'No Normal Player', 'Not anymore, at least.', '(score.mods & 1 == 0) and 2 <= score.sr < 3 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (59, 'mania-skill-pass-3', 'Impulse Drive', 'Not quite hyperspeed, but getting close.', '(score.mods & 1 == 0) and 3 <= score.sr < 4 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (60, 'mania-skill-pass-4', 'Hyperspeed', 'Woah.', '(score.mods & 1 == 0) and 4 <= score.sr < 5 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (61, 'mania-skill-pass-5', 'Ever Onwards', 'Another challenge is just around the corner.', '(score.mods & 1 == 0) and 5 <= score.sr < 6 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (62, 'mania-skill-pass-6', 'Another Surpassed', 'Is there no limit to your skills?', '(score.mods & 1 == 0) and 6 <= score.sr < 7 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (63, 'mania-skill-pass-7', 'Extra Credit', 'See me after class.', '(score.mods & 1 == 0) and 7 <= score.sr < 8 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (64, 'mania-skill-pass-8', 'Maniac', 'There''s just no stopping you.', '(score.mods & 1 == 0) and 8 <= score.sr < 9 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (65, 'mania-skill-fc-1', 'Keystruck', 'The beginning of a new story', 'score.perfect and 1 <= score.sr < 2 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (66, 'mania-skill-fc-2', 'Keying In', 'Finding your groove.', 'score.perfect and 2 <= score.sr < 3 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (67, 'mania-skill-fc-3', 'Hyperflow', 'You can *feel* the rhythm.', 'score.perfect and 3 <= score.sr < 4 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (68, 'mania-skill-fc-4', 'Breakthrough', 'Many skills mastered, rolled into one.', 'score.perfect and 4 <= score.sr < 5 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (69, 'mania-skill-fc-5', 'Everything Extra', 'Giving your all is giving everything you have.', 'score.perfect and 5 <= score.sr < 6 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (70, 'mania-skill-fc-6', 'Level Breaker', 'Finesse beyond reason', 'score.perfect and 6 <= score.sr < 7 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (71, 'mania-skill-fc-7', 'Step Up', 'A precipice rarely seen.', 'score.perfect and 7 <= score.sr < 8 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (72, 'mania-skill-fc-8', 'Behind The Veil', 'Supernatural!', 'score.perfect and 8 <= score.sr < 9 and mode_vn == 3');
-insert into achievements (id, file, name, `desc`, cond) values (73, 'all-intro-suddendeath', 'Finality', 'High stakes, no regrets.', 'score.mods == 32');
-insert into achievements (id, file, name, `desc`, cond) values (74, 'all-intro-hidden', 'Blindsight', 'I can see just perfectly', 'score.mods & 8');
-insert into achievements (id, file, name, `desc`, cond) values (75, 'all-intro-perfect', 'Perfectionist', 'Accept nothing but the best.', 'score.mods & 16384');
-insert into achievements (id, file, name, `desc`, cond) values (76, 'all-intro-hardrock', 'Rock Around The Clock', "You can\'t stop the rock.", 'score.mods & 16');
-insert into achievements (id, file, name, `desc`, cond) values (77, 'all-intro-doubletime', 'Time And A Half', "Having a right ol\' time. One and a half of them, almost.", 'score.mods & 64');
-insert into achievements (id, file, name, `desc`, cond) values (78, 'all-intro-flashlight', 'Are You Afraid Of The Dark?', "Harder than it looks, probably because it\'s hard to look.", 'score.mods & 1024');
-insert into achievements (id, file, name, `desc`, cond) values (79, 'all-intro-easy', 'Dial It Right Back', 'Sometimes you just want to take it easy.', 'score.mods & 2');
-insert into achievements (id, file, name, `desc`, cond) values (80, 'all-intro-nofail', 'Risk Averse', 'Safety nets are fun!', 'score.mods & 1');
-insert into achievements (id, file, name, `desc`, cond) values (81, 'all-intro-nightcore', 'Sweet Rave Party', 'Founded in the fine tradition of changing things that were just fine as they were.', 'score.mods & 512');
-insert into achievements (id, file, name, `desc`, cond) values (82, 'all-intro-halftime', 'Slowboat', 'You got there. Eventually.', 'score.mods & 256');
-insert into achievements (id, file, name, `desc`, cond) values (83, 'all-intro-spunout', 'Burned Out', 'One cannot always spin to win.', 'score.mods & 4096');
diff --git a/migrations_old/custom_beatmaps.sql b/migrations_old/custom_beatmaps.sql
deleted file mode 100644
index b7cd122..0000000
--- a/migrations_old/custom_beatmaps.sql
+++ /dev/null
@@ -1,209 +0,0 @@
--- 自定义谱面系统迁移
--- 创建自定义谱面表,与官方谱面不冲突
-
--- 自定义谱面集表
-CREATE TABLE custom_mapsets (
- id BIGINT AUTO_INCREMENT PRIMARY KEY,
- creator_id INT NOT NULL,
- title VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
- artist VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
- source VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '',
- tags TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
- description TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
- status ENUM('pending', 'approved', 'rejected', 'loved') DEFAULT 'pending',
- upload_date DATETIME DEFAULT CURRENT_TIMESTAMP,
- last_update DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- osz_filename VARCHAR(255) NOT NULL,
- osz_hash CHAR(32) NOT NULL,
- download_count INT DEFAULT 0,
- favourite_count INT DEFAULT 0,
- UNIQUE KEY idx_custom_mapsets_id (id),
- KEY idx_custom_mapsets_creator (creator_id),
- KEY idx_custom_mapsets_status (status),
- KEY idx_custom_mapsets_upload_date (upload_date),
- UNIQUE KEY idx_custom_mapsets_osz_hash (osz_hash)
-);
-
--- 自定义谱面难度表
-CREATE TABLE custom_maps (
- id BIGINT AUTO_INCREMENT PRIMARY KEY,
- mapset_id BIGINT NOT NULL,
- md5 CHAR(32) NOT NULL,
- difficulty_name VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
- filename VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
- mode TINYINT DEFAULT 0 NOT NULL COMMENT '0=osu!, 1=taiko, 2=catch, 3=mania',
- status ENUM('pending', 'approved', 'rejected', 'loved') DEFAULT 'pending',
-
- -- osu!文件基本信息
- audio_filename VARCHAR(255) DEFAULT '',
- audio_lead_in INT DEFAULT 0,
- preview_time INT DEFAULT -1,
- countdown TINYINT DEFAULT 1,
- sample_set VARCHAR(16) DEFAULT 'Normal',
- stack_leniency DECIMAL(3,2) DEFAULT 0.70,
- letterbox_in_breaks BOOLEAN DEFAULT FALSE,
- story_fire_in_front BOOLEAN DEFAULT TRUE,
- use_skin_sprites BOOLEAN DEFAULT FALSE,
- always_show_playfield BOOLEAN DEFAULT FALSE,
- overlay_position VARCHAR(16) DEFAULT 'NoChange',
- skin_preference VARCHAR(255) DEFAULT '',
- epilepsy_warning BOOLEAN DEFAULT FALSE,
- countdown_offset INT DEFAULT 0,
- special_style BOOLEAN DEFAULT FALSE,
- widescreen_storyboard BOOLEAN DEFAULT FALSE,
- samples_match_playback_rate BOOLEAN DEFAULT FALSE,
-
- -- 编辑器信息
- distance_spacing DECIMAL(6,3) DEFAULT 1.000,
- beat_divisor TINYINT DEFAULT 4,
- grid_size TINYINT DEFAULT 4,
- timeline_zoom DECIMAL(6,3) DEFAULT 1.000,
-
- -- 谱面元数据
- title_unicode VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '',
- artist_unicode VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '',
- creator VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
- version VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL,
- source VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '',
- tags TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci,
- beatmap_id BIGINT DEFAULT 0,
- beatmapset_id BIGINT DEFAULT 0,
-
- -- 难度设定
- hp_drain_rate DECIMAL(3,1) DEFAULT 5.0,
- circle_size DECIMAL(3,1) DEFAULT 5.0,
- overall_difficulty DECIMAL(3,1) DEFAULT 5.0,
- approach_rate DECIMAL(3,1) DEFAULT 5.0,
- slider_multiplier DECIMAL(6,3) DEFAULT 1.400,
- slider_tick_rate DECIMAL(3,1) DEFAULT 1.0,
-
- -- 计算得出的信息
- total_length INT DEFAULT 0 COMMENT '总长度(秒)',
- hit_length INT DEFAULT 0 COMMENT '击打长度(秒)',
- max_combo INT DEFAULT 0,
- bpm DECIMAL(8,3) DEFAULT 0.000,
- star_rating DECIMAL(6,3) DEFAULT 0.000,
- aim_difficulty DECIMAL(6,3) DEFAULT 0.000,
- speed_difficulty DECIMAL(6,3) DEFAULT 0.000,
-
- -- 统计信息
- plays INT DEFAULT 0,
- passes INT DEFAULT 0,
-
- -- 时间戳
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
-
- UNIQUE KEY idx_custom_maps_id (id),
- UNIQUE KEY idx_custom_maps_md5 (md5),
- KEY idx_custom_maps_mapset (mapset_id),
- KEY idx_custom_maps_mode (mode),
- KEY idx_custom_maps_status (status),
- KEY idx_custom_maps_creator (creator),
- KEY idx_custom_maps_star_rating (star_rating),
- KEY idx_custom_maps_plays (plays),
-
- FOREIGN KEY (mapset_id) REFERENCES custom_mapsets(id) ON DELETE CASCADE
-);
-
--- 自定义谱面书签表
-CREATE TABLE custom_map_bookmarks (
- user_id INT NOT NULL,
- mapset_id BIGINT NOT NULL,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- PRIMARY KEY (user_id, mapset_id),
- FOREIGN KEY (mapset_id) REFERENCES custom_mapsets(id) ON DELETE CASCADE
-);
-
--- 自定义谱面评分表
-CREATE TABLE custom_map_ratings (
- user_id INT NOT NULL,
- map_id BIGINT NOT NULL,
- rating TINYINT NOT NULL CHECK (rating >= 1 AND rating <= 10),
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
- updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
- PRIMARY KEY (user_id, map_id),
- FOREIGN KEY (map_id) REFERENCES custom_maps(id) ON DELETE CASCADE
-);
-
--- 自定义谱面成绩表 (继承原scores表结构)
-CREATE TABLE custom_scores (
- id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
- map_id BIGINT NOT NULL,
- map_md5 CHAR(32) NOT NULL,
- user_id INT NOT NULL,
- score INT NOT NULL,
- pp FLOAT(7,3) NOT NULL,
- acc FLOAT(6,3) NOT NULL,
- max_combo INT NOT NULL,
- mods INT NOT NULL,
- n300 INT NOT NULL,
- n100 INT NOT NULL,
- n50 INT NOT NULL,
- nmiss INT NOT NULL,
- ngeki INT NOT NULL,
- nkatu INT NOT NULL,
- grade VARCHAR(2) DEFAULT 'N' NOT NULL,
- status TINYINT NOT NULL COMMENT '0=failed, 1=submitted, 2=best',
- mode TINYINT NOT NULL,
- play_time DATETIME NOT NULL,
- time_elapsed INT NOT NULL,
- client_flags INT NOT NULL,
- perfect BOOLEAN NOT NULL,
- online_checksum CHAR(32) NOT NULL,
-
- KEY idx_custom_scores_map_id (map_id),
- KEY idx_custom_scores_map_md5 (map_md5),
- KEY idx_custom_scores_user_id (user_id),
- KEY idx_custom_scores_score (score),
- KEY idx_custom_scores_pp (pp),
- KEY idx_custom_scores_mods (mods),
- KEY idx_custom_scores_status (status),
- KEY idx_custom_scores_mode (mode),
- KEY idx_custom_scores_play_time (play_time),
- KEY idx_custom_scores_online_checksum (online_checksum),
- KEY idx_custom_scores_leaderboard (map_md5, status, mode),
-
- FOREIGN KEY (map_id) REFERENCES custom_maps(id) ON DELETE CASCADE
-);
-
--- 自定义谱面文件存储表 (用于存储.osu文件内容等)
-CREATE TABLE custom_map_files (
- id BIGINT AUTO_INCREMENT PRIMARY KEY,
- map_id BIGINT NOT NULL,
- file_type ENUM('osu', 'audio', 'image', 'video', 'storyboard') NOT NULL,
- filename VARCHAR(255) NOT NULL,
- file_hash CHAR(32) NOT NULL,
- file_size INT NOT NULL,
- mime_type VARCHAR(100) DEFAULT '',
- storage_path VARCHAR(500) NOT NULL,
- created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
-
- UNIQUE KEY idx_custom_map_files_id (id),
- KEY idx_custom_map_files_map_id (map_id),
- KEY idx_custom_map_files_type (file_type),
- KEY idx_custom_map_files_hash (file_hash),
-
- FOREIGN KEY (map_id) REFERENCES custom_maps(id) ON DELETE CASCADE
-);
-
--- 为自定义谱面创建专门的ID生成器,避免与官方ID冲突
--- 自定义谱面ID从1000000开始
-ALTER TABLE custom_mapsets AUTO_INCREMENT = 3000000;
-ALTER TABLE custom_maps AUTO_INCREMENT = 3000000;
-
--- 创建触发器来同步mapset信息到maps表
-DELIMITER $$
-
-CREATE TRIGGER update_custom_mapset_on_map_change
-AFTER UPDATE ON custom_maps
-FOR EACH ROW
-BEGIN
- IF NEW.status != OLD.status THEN
- UPDATE custom_mapsets
- SET last_update = CURRENT_TIMESTAMP
- WHERE id = NEW.mapset_id;
- END IF;
-END$$
-
-DELIMITER ;
diff --git a/migrations_old/migrations.sql b/migrations_old/migrations.sql
deleted file mode 100644
index 8cb522d..0000000
--- a/migrations_old/migrations.sql
+++ /dev/null
@@ -1,477 +0,0 @@
-# This file contains any sql updates, along with the
-# version they are required from. Touching this without
-# at least reading utils/updater.py is certainly a bad idea :)
-
-# v3.0.6
-alter table users change name_safe safe_name varchar(32) not null;
-alter table users drop key users_name_safe_uindex;
-alter table users add constraint users_safe_name_uindex unique (safe_name);
-alter table users change pw_hash pw_bcrypt char(60) not null;
-insert into channels (name, topic, read_priv, write_priv, auto_join) values
- ('#supporter', 'General discussion for p2w gamers.', 48, 48, false),
- ('#staff', 'General discussion for the cool kids.', 28672, 28672, true),
- ('#admin', 'General discussion for the cool.', 24576, 24576, true),
- ('#dev', 'General discussion for the.', 16384, 16384, true);
-
-# v3.0.8
-alter table users modify safe_name varchar(32) charset utf8 not null;
-alter table users modify name varchar(32) charset utf8 not null;
-alter table mail modify msg varchar(2048) charset utf8 not null;
-alter table logs modify msg varchar(2048) charset utf8 not null;
-drop table if exists comments;
-create table comments
-(
- id int auto_increment
- primary key,
- target_id int not null comment 'replay, map, or set id',
- target_type enum('replay', 'map', 'song') not null,
- userid int not null,
- time int not null,
- comment varchar(80) charset utf8 not null,
- colour char(6) null comment 'rgb hex string'
-);
-
-# v3.0.9
-alter table stats modify tscore_vn_std int unsigned default 0 not null;
-alter table stats modify tscore_vn_taiko int unsigned default 0 not null;
-alter table stats modify tscore_vn_catch int unsigned default 0 not null;
-alter table stats modify tscore_vn_mania int unsigned default 0 not null;
-alter table stats modify tscore_rx_std int unsigned default 0 not null;
-alter table stats modify tscore_rx_taiko int unsigned default 0 not null;
-alter table stats modify tscore_rx_catch int unsigned default 0 not null;
-alter table stats modify tscore_ap_std int unsigned default 0 not null;
-alter table stats modify rscore_vn_std int unsigned default 0 not null;
-alter table stats modify rscore_vn_taiko int unsigned default 0 not null;
-alter table stats modify rscore_vn_catch int unsigned default 0 not null;
-alter table stats modify rscore_vn_mania int unsigned default 0 not null;
-alter table stats modify rscore_rx_std int unsigned default 0 not null;
-alter table stats modify rscore_rx_taiko int unsigned default 0 not null;
-alter table stats modify rscore_rx_catch int unsigned default 0 not null;
-alter table stats modify rscore_ap_std int unsigned default 0 not null;
-alter table stats modify pp_vn_std smallint unsigned default 0 not null;
-alter table stats modify pp_vn_taiko smallint unsigned default 0 not null;
-alter table stats modify pp_vn_catch smallint unsigned default 0 not null;
-alter table stats modify pp_vn_mania smallint unsigned default 0 not null;
-alter table stats modify pp_rx_std smallint unsigned default 0 not null;
-alter table stats modify pp_rx_taiko smallint unsigned default 0 not null;
-alter table stats modify pp_rx_catch smallint unsigned default 0 not null;
-alter table stats modify pp_ap_std smallint unsigned default 0 not null;
-alter table stats modify plays_vn_std int unsigned default 0 not null;
-alter table stats modify plays_vn_taiko int unsigned default 0 not null;
-alter table stats modify plays_vn_catch int unsigned default 0 not null;
-alter table stats modify plays_vn_mania int unsigned default 0 not null;
-alter table stats modify plays_rx_std int unsigned default 0 not null;
-alter table stats modify plays_rx_taiko int unsigned default 0 not null;
-alter table stats modify plays_rx_catch int unsigned default 0 not null;
-alter table stats modify plays_ap_std int unsigned default 0 not null;
-alter table stats modify playtime_vn_std int unsigned default 0 not null;
-alter table stats modify playtime_vn_taiko int unsigned default 0 not null;
-alter table stats modify playtime_vn_catch int unsigned default 0 not null;
-alter table stats modify playtime_vn_mania int unsigned default 0 not null;
-alter table stats modify playtime_rx_std int unsigned default 0 not null;
-alter table stats modify playtime_rx_taiko int unsigned default 0 not null;
-alter table stats modify playtime_rx_catch int unsigned default 0 not null;
-alter table stats modify playtime_ap_std int unsigned default 0 not null;
-alter table stats modify maxcombo_vn_std int unsigned default 0 not null;
-alter table stats modify maxcombo_vn_taiko int unsigned default 0 not null;
-alter table stats modify maxcombo_vn_catch int unsigned default 0 not null;
-alter table stats modify maxcombo_vn_mania int unsigned default 0 not null;
-alter table stats modify maxcombo_rx_std int unsigned default 0 not null;
-alter table stats modify maxcombo_rx_taiko int unsigned default 0 not null;
-alter table stats modify maxcombo_rx_catch int unsigned default 0 not null;
-alter table stats modify maxcombo_ap_std int unsigned default 0 not null;
-
-# v3.0.10
-update channels set write_priv = 24576 where name = '#announce';
-
-# v3.1.0
-alter table maps modify bpm float(12,2) default 0.00 not null;
-alter table stats modify tscore_vn_std bigint unsigned default 0 not null;
-alter table stats modify tscore_vn_taiko bigint unsigned default 0 not null;
-alter table stats modify tscore_vn_catch bigint unsigned default 0 not null;
-alter table stats modify tscore_vn_mania bigint unsigned default 0 not null;
-alter table stats modify tscore_rx_std bigint unsigned default 0 not null;
-alter table stats modify tscore_rx_taiko bigint unsigned default 0 not null;
-alter table stats modify tscore_rx_catch bigint unsigned default 0 not null;
-alter table stats modify tscore_ap_std bigint unsigned default 0 not null;
-alter table stats modify rscore_vn_std bigint unsigned default 0 not null;
-alter table stats modify rscore_vn_taiko bigint unsigned default 0 not null;
-alter table stats modify rscore_vn_catch bigint unsigned default 0 not null;
-alter table stats modify rscore_vn_mania bigint unsigned default 0 not null;
-alter table stats modify rscore_rx_std bigint unsigned default 0 not null;
-alter table stats modify rscore_rx_taiko bigint unsigned default 0 not null;
-alter table stats modify rscore_rx_catch bigint unsigned default 0 not null;
-alter table stats modify rscore_ap_std bigint unsigned default 0 not null;
-alter table stats modify pp_vn_std int unsigned default 0 not null;
-alter table stats modify pp_vn_taiko int unsigned default 0 not null;
-alter table stats modify pp_vn_catch int unsigned default 0 not null;
-alter table stats modify pp_vn_mania int unsigned default 0 not null;
-alter table stats modify pp_rx_std int unsigned default 0 not null;
-alter table stats modify pp_rx_taiko int unsigned default 0 not null;
-alter table stats modify pp_rx_catch int unsigned default 0 not null;
-alter table stats modify pp_ap_std int unsigned default 0 not null;
-
-# v3.1.2
-create table clans
-(
- id int auto_increment
- primary key,
- name varchar(16) not null,
- tag varchar(6) not null,
- owner int not null,
- created_at datetime not null,
- constraint clans_name_uindex
- unique (name),
- constraint clans_owner_uindex
- unique (owner),
- constraint clans_tag_uindex
- unique (tag)
-);
-alter table users add clan_id int default 0 not null;
-alter table users add clan_rank tinyint(1) default 0 not null;
-create table achievements
-(
- id int auto_increment
- primary key,
- file varchar(128) not null,
- name varchar(128) not null,
- `desc` varchar(256) not null,
- cond varchar(64) not null,
- mode tinyint(1) not null,
- constraint achievements_desc_uindex
- unique (`desc`),
- constraint achievements_file_uindex
- unique (file),
- constraint achievements_name_uindex
- unique (name)
-);
-create table user_achievements
-(
- userid int not null,
- achid int not null,
- primary key (userid, achid)
-);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (1, 'osu-skill-pass-1', 'Rising Star', 'Can''t go forward without the first steps.', '(score.mods & 259 == 0) and 2 >= score.sr > 1', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (2, 'osu-skill-pass-2', 'Constellation Prize', 'Definitely not a consolation prize. Now things start getting hard!', '(score.mods & 259 == 0) and 3 >= score.sr > 2', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (3, 'osu-skill-pass-3', 'Building Confidence', 'Oh, you''ve SO got this.', '(score.mods & 259 == 0) and 4 >= score.sr > 3', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (4, 'osu-skill-pass-4', 'Insanity Approaches', 'You''re not twitching, you''re just ready.', '(score.mods & 259 == 0) and 5 >= score.sr > 4', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (5, 'osu-skill-pass-5', 'These Clarion Skies', 'Everything seems so clear now.', '(score.mods & 259 == 0) and 6 >= score.sr > 5', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (6, 'osu-skill-pass-6', 'Above and Beyond', 'A cut above the rest.', '(score.mods & 259 == 0) and 7 >= score.sr > 6', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (7, 'osu-skill-pass-7', 'Supremacy', 'All marvel before your prowess.', '(score.mods & 259 == 0) and 8 >= score.sr > 7', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (8, 'osu-skill-pass-8', 'Absolution', 'My god, you''re full of stars!', '(score.mods & 259 == 0) and 9 >= score.sr > 8', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (9, 'osu-skill-pass-9', 'Event Horizon', 'No force dares to pull you under.', '(score.mods & 259 == 0) and 10 >= score.sr > 9', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (10, 'osu-skill-pass-10', 'Phantasm', 'Fevered is your passion, extraordinary is your skill.', '(score.mods & 259 == 0) and 11 >= score.sr > 10', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (11, 'osu-skill-fc-1', 'Totality', 'All the notes. Every single one.', 'score.perfect and 2 >= score.sr > 1', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (12, 'osu-skill-fc-2', 'Business As Usual', 'Two to go, please.', 'score.perfect and 3 >= score.sr > 2', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (13, 'osu-skill-fc-3', 'Building Steam', 'Hey, this isn''t so bad.', 'score.perfect and 4 >= score.sr > 3', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (14, 'osu-skill-fc-4', 'Moving Forward', 'Bet you feel good about that.', 'score.perfect and 5 >= score.sr > 4', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (15, 'osu-skill-fc-5', 'Paradigm Shift', 'Surprisingly difficult.', 'score.perfect and 6 >= score.sr > 5', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (16, 'osu-skill-fc-6', 'Anguish Quelled', 'Don''t choke.', 'score.perfect and 7 >= score.sr > 6', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (17, 'osu-skill-fc-7', 'Never Give Up', 'Excellence is its own reward.', 'score.perfect and 8 >= score.sr > 7', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (18, 'osu-skill-fc-8', 'Aberration', 'They said it couldn''t be done. They were wrong.', 'score.perfect and 9 >= score.sr > 8', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (19, 'osu-skill-fc-9', 'Chosen', 'Reign among the Prometheans, where you belong.', 'score.perfect and 10 >= score.sr > 9', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (20, 'osu-skill-fc-10', 'Unfathomable', 'You have no equal.', 'score.perfect and 11 >= score.sr > 10', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (21, 'osu-combo-500', '500 Combo', '500 big ones! You''re moving up in the world!', '750 >= score.max_combo > 500', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (22, 'osu-combo-750', '750 Combo', '750 notes back to back? Woah.', '1000 >= score.max_combo > 750', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (23, 'osu-combo-1000', '1000 Combo', 'A thousand reasons why you rock at this game.', '2000 >= score.max_combo > 1000', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (24, 'osu-combo-2000', '2000 Combo', 'Nothing can stop you now.', 'score.max_combo >= 2000', 0);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (25, 'taiko-skill-pass-1', 'My First Don', 'Marching to the beat of your own drum. Literally.', '(score.mods & 259 == 0) and 2 >= score.sr > 1', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (26, 'taiko-skill-pass-2', 'Katsu Katsu Katsu', 'Hora! Izuko!', '(score.mods & 259 == 0) and 3 >= score.sr > 2', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (27, 'taiko-skill-pass-3', 'Not Even Trying', 'Muzukashii? Not even.', '(score.mods & 259 == 0) and 4 >= score.sr > 3', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (28, 'taiko-skill-pass-4', 'Face Your Demons', 'The first trials are now behind you, but are you a match for the Oni?', '(score.mods & 259 == 0) and 5 >= score.sr > 4', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (29, 'taiko-skill-pass-5', 'The Demon Within', 'No rest for the wicked.', '(score.mods & 259 == 0) and 6 >= score.sr > 5', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (30, 'taiko-skill-pass-6', 'Drumbreaker', 'Too strong.', '(score.mods & 259 == 0) and 7 >= score.sr > 6', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (31, 'taiko-skill-pass-7', 'The Godfather', 'You are the Don of Dons.', '(score.mods & 259 == 0) and 8 >= score.sr > 7', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (32, 'taiko-skill-pass-8', 'Rhythm Incarnate', 'Feel the beat. Become the beat.', '(score.mods & 259 == 0) and 9 >= score.sr > 8', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (33, 'taiko-skill-fc-1', 'Keeping Time', 'Don, then katsu. Don, then katsu..', 'score.perfect and 2 >= score.sr > 1', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (34, 'taiko-skill-fc-2', 'To Your Own Beat', 'Straight and steady.', 'score.perfect and 3 >= score.sr > 2', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (35, 'taiko-skill-fc-3', 'Big Drums', 'Bigger scores to match.', 'score.perfect and 4 >= score.sr > 3', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (36, 'taiko-skill-fc-4', 'Adversity Overcome', 'Difficult? Not for you.', 'score.perfect and 5 >= score.sr > 4', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (37, 'taiko-skill-fc-5', 'Demonslayer', 'An Oni felled forevermore.', 'score.perfect and 6 >= score.sr > 5', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (38, 'taiko-skill-fc-6', 'Rhythm''s Call', 'Heralding true skill.', 'score.perfect and 7 >= score.sr > 6', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (39, 'taiko-skill-fc-7', 'Time Everlasting', 'Not a single beat escapes you.', 'score.perfect and 8 >= score.sr > 7', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (40, 'taiko-skill-fc-8', 'The Drummer''s Throne', 'Percussive brilliance befitting royalty alone.', 'score.perfect and 9 >= score.sr > 8', 1);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (41, 'fruits-skill-pass-1', 'A Slice Of Life', 'Hey, this fruit catching business isn''t bad.', '(score.mods & 259 == 0) and 2 >= score.sr > 1', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (42, 'fruits-skill-pass-2', 'Dashing Ever Forward', 'Fast is how you do it.', '(score.mods & 259 == 0) and 3 >= score.sr > 2', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (43, 'fruits-skill-pass-3', 'Zesty Disposition', 'No scurvy for you, not with that much fruit.', '(score.mods & 259 == 0) and 4 >= score.sr > 3', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (44, 'fruits-skill-pass-4', 'Hyperdash ON!', 'Time and distance is no obstacle to you.', '(score.mods & 259 == 0) and 5 >= score.sr > 4', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (45, 'fruits-skill-pass-5', 'It''s Raining Fruit', 'And you can catch them all.', '(score.mods & 259 == 0) and 6 >= score.sr > 5', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (46, 'fruits-skill-pass-6', 'Fruit Ninja', 'Legendary techniques.', '(score.mods & 259 == 0) and 7 >= score.sr > 6', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (47, 'fruits-skill-pass-7', 'Dreamcatcher', 'No fruit, only dreams now.', '(score.mods & 259 == 0) and 8 >= score.sr > 7', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (48, 'fruits-skill-pass-8', 'Lord of the Catch', 'Your kingdom kneels before you.', '(score.mods & 259 == 0) and 9 >= score.sr > 8', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (49, 'fruits-skill-fc-1', 'Sweet And Sour', 'Apples and oranges, literally.', 'score.perfect and 2 >= score.sr > 1', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (50, 'fruits-skill-fc-2', 'Reaching The Core', 'The seeds of future success.', 'score.perfect and 3 >= score.sr > 2', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (51, 'fruits-skill-fc-3', 'Clean Platter', 'Clean only of failure. It is completely full, otherwise.', 'score.perfect and 4 >= score.sr > 3', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (52, 'fruits-skill-fc-4', 'Between The Rain', 'No umbrella needed.', 'score.perfect and 5 >= score.sr > 4', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (53, 'fruits-skill-fc-5', 'Addicted', 'That was an overdose?', 'score.perfect and 6 >= score.sr > 5', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (54, 'fruits-skill-fc-6', 'Quickening', 'A dash above normal limits.', 'score.perfect and 7 >= score.sr > 6', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (55, 'fruits-skill-fc-7', 'Supersonic', 'Faster than is reasonably necessary.', 'score.perfect and 8 >= score.sr > 7', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (56, 'fruits-skill-fc-8', 'Dashing Scarlet', 'Speed beyond mortal reckoning.', 'score.perfect and 9 >= score.sr > 8', 2);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (57, 'mania-skill-pass-1', 'First Steps', 'It isn''t 9-to-5, but 1-to-9. Keys, that is.', '(score.mods & 259 == 0) and 2 >= score.sr > 1', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (58, 'mania-skill-pass-2', 'No Normal Player', 'Not anymore, at least.', '(score.mods & 259 == 0) and 3 >= score.sr > 2', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (59, 'mania-skill-pass-3', 'Impulse Drive', 'Not quite hyperspeed, but getting close.', '(score.mods & 259 == 0) and 4 >= score.sr > 3', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (60, 'mania-skill-pass-4', 'Hyperspeed', 'Woah.', '(score.mods & 259 == 0) and 5 >= score.sr > 4', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (61, 'mania-skill-pass-5', 'Ever Onwards', 'Another challenge is just around the corner.', '(score.mods & 259 == 0) and 6 >= score.sr > 5', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (62, 'mania-skill-pass-6', 'Another Surpassed', 'Is there no limit to your skills?', '(score.mods & 259 == 0) and 7 >= score.sr > 6', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (63, 'mania-skill-pass-7', 'Extra Credit', 'See me after class.', '(score.mods & 259 == 0) and 8 >= score.sr > 7', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (64, 'mania-skill-pass-8', 'Maniac', 'There''s just no stopping you.', '(score.mods & 259 == 0) and 9 >= score.sr > 8', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (65, 'mania-skill-fc-1', 'Keystruck', 'The beginning of a new story', 'score.perfect and (score.mods & 259 == 0) and 2 >= score.sr > 1', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (66, 'mania-skill-fc-2', 'Keying In', 'Finding your groove.', 'score.perfect and 3 >= score.sr > 2', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (67, 'mania-skill-fc-3', 'Hyperflow', 'You can *feel* the rhythm.', 'score.perfect and 4 >= score.sr > 3', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (68, 'mania-skill-fc-4', 'Breakthrough', 'Many skills mastered, rolled into one.', 'score.perfect and 5 >= score.sr > 4', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (69, 'mania-skill-fc-5', 'Everything Extra', 'Giving your all is giving everything you have.', 'score.perfect and 6 >= score.sr > 5', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (70, 'mania-skill-fc-6', 'Level Breaker', 'Finesse beyond reason', 'score.perfect and 7 >= score.sr > 6', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (71, 'mania-skill-fc-7', 'Step Up', 'A precipice rarely seen.', 'score.perfect and 8 >= score.sr > 7', 3);
-insert into achievements (`id`, `file`, `name`, `desc`, `cond`, `mode`) values (72, 'mania-skill-fc-8', 'Behind The Veil', 'Supernatural!', 'score.perfect and 9 >= score.sr > 8', 3);
-
-# v3.1.3
-alter table clans modify name varchar(16) charset utf8 not null;
-alter table clans modify tag varchar(6) charset utf8 not null;
-alter table achievements modify name varchar(128) charset utf8 not null;
-alter table achievements modify `desc` varchar(256) charset utf8 not null;
-alter table maps modify artist varchar(128) charset utf8 not null;
-alter table maps modify title varchar(128) charset utf8 not null;
-alter table maps modify version varchar(128) charset utf8 not null;
-alter table maps modify creator varchar(19) charset utf8 not null comment 'not 100%% certain on len';
-alter table tourney_pools drop foreign key tourney_pools_users_id_fk;
-alter table tourney_pool_maps drop foreign key tourney_pool_maps_tourney_pools_id_fk;
-alter table stats drop foreign key stats_users_id_fk;
-alter table ratings drop foreign key ratings_maps_md5_fk;
-alter table ratings drop foreign key ratings_users_id_fk;
-alter table logs modify `from` int not null comment 'both from and to are playerids';
-
-# v3.1.9
-alter table scores_rx modify id bigint(20) unsigned auto_increment;
-update scores_rx set id = id + (6148914691236517205 - 1);
-select @max_rx := MAX(id) + 1 from scores_rx;
-set @s = CONCAT('alter table scores_rx auto_increment = ', @max_rx);
-prepare stmt from @s;
-execute stmt;
-deallocate PREPARE stmt;
-alter table scores_ap modify id bigint(20) unsigned auto_increment;
-update scores_ap set id = id + (12297829382473034410 - 1);
-select @max_ap := MAX(id) + 1 from scores_ap;
-set @s = CONCAT('alter table scores_ap auto_increment = ', @max_ap);
-prepare stmt from @s;
-execute stmt;
-deallocate PREPARE stmt;
-alter table performance_reports modify scoreid bigint(20) unsigned auto_increment;
-
-# v3.2.0
-create table map_requests
-(
- id int auto_increment
- primary key,
- map_id int not null,
- player_id int not null,
- datetime datetime not null,
- active tinyint(1) not null
-);
-
-# v3.2.1
-update scores_rx set id = id - 3074457345618258603;
-update scores_ap set id = id - 6148914691236517206;
-
-# v3.2.2
-alter table maps add max_combo int not null after total_length;
-alter table users change clan_rank clan_priv tinyint(1) default 0 not null;
-
-# v3.2.3
-alter table users add api_key char(36) default NULL null;
-create unique index users_api_key_uindex on users (api_key);
-
-# v3.2.4
-update achievements set file = replace(file, 'ctb', 'fruits') where mode = 2;
-
-# v3.2.5
-update achievements set cond = '(score.mods & 1 == 0) and 1 <= score.sr < 2' where file in ('osu-skill-pass-1', 'taiko-skill-pass-1', 'fruits-skill-pass-1', 'mania-skill-pass-1');
-update achievements set cond = '(score.mods & 1 == 0) and 2 <= score.sr < 3' where file in ('osu-skill-pass-2', 'taiko-skill-pass-2', 'fruits-skill-pass-2', 'mania-skill-pass-2');
-update achievements set cond = '(score.mods & 1 == 0) and 3 <= score.sr < 4' where file in ('osu-skill-pass-3', 'taiko-skill-pass-3', 'fruits-skill-pass-3', 'mania-skill-pass-3');
-update achievements set cond = '(score.mods & 1 == 0) and 4 <= score.sr < 5' where file in ('osu-skill-pass-4', 'taiko-skill-pass-4', 'fruits-skill-pass-4', 'mania-skill-pass-4');
-update achievements set cond = '(score.mods & 1 == 0) and 5 <= score.sr < 6' where file in ('osu-skill-pass-5', 'taiko-skill-pass-5', 'fruits-skill-pass-5', 'mania-skill-pass-5');
-update achievements set cond = '(score.mods & 1 == 0) and 6 <= score.sr < 7' where file in ('osu-skill-pass-6', 'taiko-skill-pass-6', 'fruits-skill-pass-6', 'mania-skill-pass-6');
-update achievements set cond = '(score.mods & 1 == 0) and 7 <= score.sr < 8' where file in ('osu-skill-pass-7', 'taiko-skill-pass-7', 'fruits-skill-pass-7', 'mania-skill-pass-7');
-update achievements set cond = '(score.mods & 1 == 0) and 8 <= score.sr < 9' where file in ('osu-skill-pass-8', 'taiko-skill-pass-8', 'fruits-skill-pass-8', 'mania-skill-pass-8');
-update achievements set cond = '(score.mods & 1 == 0) and 9 <= score.sr < 10' where file = 'osu-skill-pass-9';
-update achievements set cond = '(score.mods & 1 == 0) and 10 <= score.sr < 11' where file = 'osu-skill-pass-10';
-
-update achievements set cond = 'score.perfect and 1 <= score.sr < 2' where file in ('osu-skill-fc-1', 'taiko-skill-fc-1', 'fruits-skill-fc-1', 'mania-skill-fc-1');
-update achievements set cond = 'score.perfect and 2 <= score.sr < 3' where file in ('osu-skill-fc-2', 'taiko-skill-fc-2', 'fruits-skill-fc-2', 'mania-skill-fc-2');
-update achievements set cond = 'score.perfect and 3 <= score.sr < 4' where file in ('osu-skill-fc-3', 'taiko-skill-fc-3', 'fruits-skill-fc-3', 'mania-skill-fc-3');
-update achievements set cond = 'score.perfect and 4 <= score.sr < 5' where file in ('osu-skill-fc-4', 'taiko-skill-fc-4', 'fruits-skill-fc-4', 'mania-skill-fc-4');
-update achievements set cond = 'score.perfect and 5 <= score.sr < 6' where file in ('osu-skill-fc-5', 'taiko-skill-fc-5', 'fruits-skill-fc-5', 'mania-skill-fc-5');
-update achievements set cond = 'score.perfect and 6 <= score.sr < 7' where file in ('osu-skill-fc-6', 'taiko-skill-fc-6', 'fruits-skill-fc-6', 'mania-skill-fc-6');
-update achievements set cond = 'score.perfect and 7 <= score.sr < 8' where file in ('osu-skill-fc-7', 'taiko-skill-fc-7', 'fruits-skill-fc-7', 'mania-skill-fc-7');
-update achievements set cond = 'score.perfect and 8 <= score.sr < 9' where file in ('osu-skill-fc-8', 'taiko-skill-fc-8', 'fruits-skill-fc-8', 'mania-skill-fc-8');
-update achievements set cond = 'score.perfect and 9 <= score.sr < 10' where file = 'osu-skill-fc-9';
-update achievements set cond = 'score.perfect and 10 <= score.sr < 11' where file = 'osu-skill-fc-10';
-
-update achievements set cond = '500 <= score.max_combo < 750' where file = 'osu-combo-500';
-update achievements set cond = '750 <= score.max_combo < 1000' where file = 'osu-combo-750';
-update achievements set cond = '1000 <= score.max_combo < 2000' where file = 'osu-combo-1000';
-update achievements set cond = '2000 <= score.max_combo' where file = 'osu-combo-2000';
-
-# v3.2.6
-alter table stats change maxcombo_vn_std max_combo_vn_std int unsigned default 0 not null;
-alter table stats change maxcombo_vn_taiko max_combo_vn_taiko int unsigned default 0 not null;
-alter table stats change maxcombo_vn_catch max_combo_vn_catch int unsigned default 0 not null;
-alter table stats change maxcombo_vn_mania max_combo_vn_mania int unsigned default 0 not null;
-alter table stats change maxcombo_rx_std max_combo_rx_std int unsigned default 0 not null;
-alter table stats change maxcombo_rx_taiko max_combo_rx_taiko int unsigned default 0 not null;
-alter table stats change maxcombo_rx_catch max_combo_rx_catch int unsigned default 0 not null;
-alter table stats change maxcombo_ap_std max_combo_ap_std int unsigned default 0 not null;
-
-# v3.2.7
-drop table if exists user_hashes;
-
-# v3.3.0
-rename table friendships to relationships;
-alter table relationships add type enum('friend', 'block') not null;
-
-# v3.3.1
-create table ingame_logins
-(
- id int auto_increment
- primary key,
- userid int not null,
- ip varchar(45) not null comment 'maxlen for ipv6',
- osu_ver date not null,
- osu_stream varchar(11) not null,
- datetime datetime not null
-);
-
-# v3.3.7
-update achievements set cond = CONCAT(cond, ' and mode_vn == 0') where mode = 0;
-update achievements set cond = CONCAT(cond, ' and mode_vn == 1') where mode = 1;
-update achievements set cond = CONCAT(cond, ' and mode_vn == 2') where mode = 2;
-update achievements set cond = CONCAT(cond, ' and mode_vn == 3') where mode = 3;
-alter table achievements drop column mode;
-
-# v3.3.8
-create table mapsets
-(
- server enum('osu!', 'gulag') default 'osu!' not null,
- id int not null,
- last_osuapi_check datetime default CURRENT_TIMESTAMP not null,
- primary key (server, id),
- constraint nmapsets_id_uindex
- unique (id)
-);
-
-# v3.4.1
-alter table maps add filename varchar(256) charset utf8 not null after creator;
-
-# v3.5.2
-alter table scores_vn add online_checksum char(32) not null;
-alter table scores_rx add online_checksum char(32) not null;
-alter table scores_ap add online_checksum char(32) not null;
-
-# v4.1.1
-alter table stats add total_hits int unsigned default 0 not null after max_combo;
-
-# v4.1.2
-alter table stats add replay_views int unsigned default 0 not null after total_hits;
-
-# v4.1.3
-alter table users add preferred_mode int default 0 not null after latest_activity;
-alter table users add play_style int default 0 not null after preferred_mode;
-alter table users add custom_badge_name varchar(16) charset utf8 null after play_style;
-alter table users add custom_badge_icon varchar(64) null after custom_badge_name;
-alter table users add userpage_content varchar(2048) charset utf8 null after custom_badge_icon;
-
-# v4.2.0
-# please refer to tools/migrate_v420 for further v4.2.0 migrations
-update stats set mode = 8 where mode = 7;
-
-# v4.3.1
-alter table maps change server server enum('osu!', 'private') default 'osu!' not null;
-alter table mapsets change server server enum('osu!', 'private') default 'osu!' not null;
-
-# v4.4.2
-insert into achievements (id, file, name, `desc`, cond) values (73, 'all-intro-suddendeath', 'Finality', 'High stakes, no regrets.', 'score.mods == 32');
-insert into achievements (id, file, name, `desc`, cond) values (74, 'all-intro-hidden', 'Blindsight', 'I can see just perfectly', 'score.mods & 8');
-insert into achievements (id, file, name, `desc`, cond) values (75, 'all-intro-perfect', 'Perfectionist', 'Accept nothing but the best.', 'score.mods & 16384');
-insert into achievements (id, file, name, `desc`, cond) values (76, 'all-intro-hardrock', 'Rock Around The Clock', "You can\'t stop the rock.", 'score.mods & 16');
-insert into achievements (id, file, name, `desc`, cond) values (77, 'all-intro-doubletime', 'Time And A Half', "Having a right ol\' time. One and a half of them, almost.", 'score.mods & 64');
-insert into achievements (id, file, name, `desc`, cond) values (78, 'all-intro-flashlight', 'Are You Afraid Of The Dark?', "Harder than it looks, probably because it\'s hard to look.", 'score.mods & 1024');
-insert into achievements (id, file, name, `desc`, cond) values (79, 'all-intro-easy', 'Dial It Right Back', 'Sometimes you just want to take it easy.', 'score.mods & 2');
-insert into achievements (id, file, name, `desc`, cond) values (80, 'all-intro-nofail', 'Risk Averse', 'Safety nets are fun!', 'score.mods & 1');
-insert into achievements (id, file, name, `desc`, cond) values (81, 'all-intro-nightcore', 'Sweet Rave Party', 'Founded in the fine tradition of changing things that were just fine as they were.', 'score.mods & 512');
-insert into achievements (id, file, name, `desc`, cond) values (82, 'all-intro-halftime', 'Slowboat', 'You got there. Eventually.', 'score.mods & 256');
-insert into achievements (id, file, name, `desc`, cond) values (83, 'all-intro-spunout', 'Burned Out', 'One cannot always spin to win.', 'score.mods & 4096');
-
-# v4.4.3
-alter table favourites add created_at int default 0 not null;
-
-# v4.7.1
-lock tables maps write;
-alter table maps drop primary key;
-alter table maps add primary key (id);
-alter table maps modify column server enum('osu!', 'private') not null default 'osu!' after id;
-unlock tables;
-
-# v5.0.1
-create index channels_auto_join_index
- on channels (auto_join);
-
-create index maps_set_id_index
- on maps (set_id);
-create index maps_status_index
- on maps (status);
-create index maps_filename_index
- on maps (filename);
-create index maps_plays_index
- on maps (plays);
-create index maps_mode_index
- on maps (mode);
-create index maps_frozen_index
- on maps (frozen);
-
-create index scores_map_md5_index
- on scores (map_md5);
-create index scores_score_index
- on scores (score);
-create index scores_pp_index
- on scores (pp);
-create index scores_mods_index
- on scores (mods);
-create index scores_status_index
- on scores (status);
-create index scores_mode_index
- on scores (mode);
-create index scores_play_time_index
- on scores (play_time);
-create index scores_userid_index
- on scores (userid);
-create index scores_online_checksum_index
- on scores (online_checksum);
-
-create index stats_mode_index
- on stats (mode);
-create index stats_pp_index
- on stats (pp);
-create index stats_tscore_index
- on stats (tscore);
-create index stats_rscore_index
- on stats (rscore);
-
-create index tourney_pool_maps_mods_slot_index
- on tourney_pool_maps (mods, slot);
-
-create index user_achievements_achid_index
- on user_achievements (achid);
-create index user_achievements_userid_index
- on user_achievements (userid);
-
-create index users_priv_index
- on users (priv);
-create index users_clan_id_index
- on users (clan_id);
-create index users_clan_priv_index
- on users (clan_priv);
-create index users_country_index
- on users (country);
-
-# v5.2.2
-create index scores_fetch_leaderboard_generic_index
- on scores (map_md5, status, mode);
diff --git a/migrations_old/sync_legacy_data.sql b/migrations_old/sync_legacy_data.sql
deleted file mode 100644
index fca085e..0000000
--- a/migrations_old/sync_legacy_data.sql
+++ /dev/null
@@ -1,337 +0,0 @@
--- Lazer API 数据同步脚本
--- 从现有的 bancho.py 表结构同步数据到新的 lazer 专用表
--- 执行此脚本前请确保已执行 add_missing_fields.sql
-
--- ============================================
--- 同步用户基本资料数据
--- ============================================
-
--- 同步用户扩展资料
-INSERT INTO lazer_user_profiles (
- user_id,
- is_active,
- is_bot,
- is_deleted,
- is_online,
- is_supporter,
- is_restricted,
- session_verified,
- has_supported,
- pm_friends_only,
- default_group,
- last_visit,
- join_date,
- profile_colour,
- profile_hue,
- avatar_url,
- cover_url,
- discord,
- twitter,
- website,
- title,
- title_url,
- interests,
- location,
- occupation,
- playmode,
- support_level,
- max_blocks,
- max_friends,
- post_count,
- page_html,
- page_raw
-)
-SELECT
- u.id as user_id,
- -- 基本状态字段 (使用默认值,因为原表没有这些字段)
- 1 as is_active,
- CASE WHEN u.name = 'BanchoBot' THEN 1 ELSE 0 END as is_bot,
- 0 as is_deleted,
- 1 as is_online,
- CASE WHEN u.donor_end > UNIX_TIMESTAMP() THEN 1 ELSE 0 END as is_supporter,
- CASE WHEN (u.priv & 1) = 0 THEN 1 ELSE 0 END as is_restricted,
- 0 as session_verified,
- CASE WHEN u.donor_end > 0 THEN 1 ELSE 0 END as has_supported,
- 0 as pm_friends_only,
-
- -- 基本资料字段
- 'default' as default_group,
- CASE WHEN u.latest_activity > 0 THEN FROM_UNIXTIME(u.latest_activity) ELSE NULL END as last_visit,
- CASE WHEN u.creation_time > 0 THEN FROM_UNIXTIME(u.creation_time) ELSE NULL END as join_date,
- NULL as profile_colour,
- NULL as profile_hue,
-
- -- 社交媒体和个人资料字段 (使用默认值)
- CONCAT('https://a.ppy.sh/', u.id) as avatar_url,
- CONCAT('https://assets.ppy.sh/user-profile-covers/banners/', u.id, '.jpg') as cover_url,
- NULL as discord,
- NULL as twitter,
- NULL as website,
- u.custom_badge_name as title,
- NULL as title_url,
- NULL as interests,
- CASE WHEN u.country != 'xx' THEN u.country ELSE NULL END as location,
- NULL as occupation,
-
- -- 游戏相关字段
- CASE u.preferred_mode
- WHEN 0 THEN 'osu'
- WHEN 1 THEN 'taiko'
- WHEN 2 THEN 'fruits'
- WHEN 3 THEN 'mania'
- ELSE 'osu'
- END as playmode,
- CASE WHEN u.donor_end > UNIX_TIMESTAMP() THEN 1 ELSE 0 END as support_level,
- 100 as max_blocks,
- 500 as max_friends,
- 0 as post_count,
-
- -- 页面内容
- u.userpage_content as page_html,
- u.userpage_content as page_raw
-
-FROM users u
-ON DUPLICATE KEY UPDATE
- last_visit = VALUES(last_visit),
- join_date = VALUES(join_date),
- is_supporter = VALUES(is_supporter),
- is_restricted = VALUES(is_restricted),
- has_supported = VALUES(has_supported),
- title = VALUES(title),
- location = VALUES(location),
- playmode = VALUES(playmode),
- support_level = VALUES(support_level),
- page_html = VALUES(page_html),
- page_raw = VALUES(page_raw);
-
--- 同步用户国家信息
-INSERT INTO lazer_user_countries (
- user_id,
- code,
- name
-)
-SELECT
- u.id as user_id,
- UPPER(u.country) as code,
- CASE UPPER(u.country)
- WHEN 'CN' THEN 'China'
- WHEN 'US' THEN 'United States'
- WHEN 'JP' THEN 'Japan'
- WHEN 'KR' THEN 'South Korea'
- WHEN 'CA' THEN 'Canada'
- WHEN 'GB' THEN 'United Kingdom'
- WHEN 'DE' THEN 'Germany'
- WHEN 'FR' THEN 'France'
- WHEN 'AU' THEN 'Australia'
- WHEN 'RU' THEN 'Russia'
- ELSE 'Unknown'
- END as name
-FROM users u
-WHERE u.country IS NOT NULL AND u.country != 'xx'
-ON DUPLICATE KEY UPDATE
- code = VALUES(code),
- name = VALUES(name);
-
--- 同步用户 Kudosu (使用默认值)
-INSERT INTO lazer_user_kudosu (
- user_id,
- available,
- total
-)
-SELECT
- u.id as user_id,
- 0 as available,
- 0 as total
-FROM users u
-ON DUPLICATE KEY UPDATE
- available = VALUES(available),
- total = VALUES(total);
-
--- 同步用户统计计数 (使用默认值)
-INSERT INTO lazer_user_counts (
- user_id,
- beatmap_playcounts_count,
- comments_count,
- favourite_beatmapset_count,
- follower_count,
- graveyard_beatmapset_count,
- guest_beatmapset_count,
- loved_beatmapset_count,
- mapping_follower_count,
- nominated_beatmapset_count,
- pending_beatmapset_count,
- ranked_beatmapset_count,
- ranked_and_approved_beatmapset_count,
- unranked_beatmapset_count,
- scores_best_count,
- scores_first_count,
- scores_pinned_count,
- scores_recent_count
-)
-SELECT
- u.id as user_id,
- 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
-FROM users u
-ON DUPLICATE KEY UPDATE
- user_id = VALUES(user_id);
-
--- ============================================
--- 同步游戏统计数据
--- ============================================
-
--- 从 stats 表同步用户统计数据到 lazer_user_statistics
-INSERT INTO lazer_user_statistics (
- user_id,
- mode,
- count_100,
- count_300,
- count_50,
- count_miss,
- level_current,
- level_progress,
- global_rank,
- country_rank,
- pp,
- ranked_score,
- hit_accuracy,
- total_score,
- total_hits,
- maximum_combo,
- play_count,
- play_time,
- replays_watched_by_others,
- is_ranked,
- grade_ss,
- grade_ssh,
- grade_s,
- grade_sh,
- grade_a
-)
-SELECT
- s.id as user_id,
- CASE s.mode
- WHEN 0 THEN 'osu'
- WHEN 1 THEN 'taiko'
- WHEN 2 THEN 'fruits'
- WHEN 3 THEN 'mania'
- ELSE 'osu'
- END as mode,
-
- -- 基本命中统计
- s.n100 as count_100,
- s.n300 as count_300,
- s.n50 as count_50,
- s.nmiss as count_miss,
-
- -- 等级信息
- 1 as level_current,
- 0 as level_progress,
-
- -- 排名信息
- NULL as global_rank,
- NULL as country_rank,
-
- -- PP 和分数
- s.pp as pp,
- s.rscore as ranked_score,
- CASE WHEN (s.n300 + s.n100 + s.n50 + s.nmiss) > 0
- THEN ROUND((s.n300 * 300 + s.n100 * 100 + s.n50 * 50) / ((s.n300 + s.n100 + s.n50 + s.nmiss) * 300) * 100, 2)
- ELSE 0.00
- END as hit_accuracy,
- s.tscore as total_score,
- (s.n300 + s.n100 + s.n50) as total_hits,
- s.max_combo as maximum_combo,
-
- -- 游戏统计
- s.plays as play_count,
- s.playtime as play_time,
- 0 as replays_watched_by_others,
- CASE WHEN s.pp > 0 THEN 1 ELSE 0 END as is_ranked,
-
- -- 成绩等级计数
- 0 as grade_ss,
- 0 as grade_ssh,
- 0 as grade_s,
- 0 as grade_sh,
- 0 as grade_a
-
-FROM stats s
-WHERE EXISTS (SELECT 1 FROM users u WHERE u.id = s.id)
-ON DUPLICATE KEY UPDATE
- count_100 = VALUES(count_100),
- count_300 = VALUES(count_300),
- count_50 = VALUES(count_50),
- count_miss = VALUES(count_miss),
- pp = VALUES(pp),
- ranked_score = VALUES(ranked_score),
- hit_accuracy = VALUES(hit_accuracy),
- total_score = VALUES(total_score),
- total_hits = VALUES(total_hits),
- maximum_combo = VALUES(maximum_combo),
- play_count = VALUES(play_count),
- play_time = VALUES(play_time),
- is_ranked = VALUES(is_ranked);
-
--- ============================================
--- 同步用户成就数据
--- ============================================
-
--- 从 user_achievements 表同步数据(如果存在的话)
-INSERT IGNORE INTO lazer_user_achievements (
- user_id,
- achievement_id,
- achieved_at
-)
-SELECT
- ua.userid as user_id,
- ua.achid as achievement_id,
- NOW() as achieved_at -- 使用当前时间作为获得时间
-FROM user_achievements ua
-WHERE EXISTS (SELECT 1 FROM users u WHERE u.id = ua.userid);
-
--- ============================================
--- 创建基础 OAuth 令牌记录(如果需要的话)
--- ============================================
-
--- 注意: OAuth 令牌通常在用户登录时动态创建,这里不需要预先填充
-
--- ============================================
--- 同步完成提示
--- ============================================
-
--- 显示同步统计信息
-SELECT
- 'lazer_user_profiles' as table_name,
- COUNT(*) as synced_records
-FROM lazer_user_profiles
-UNION ALL
-SELECT
- 'lazer_user_countries' as table_name,
- COUNT(*) as synced_records
-FROM lazer_user_countries
-UNION ALL
-SELECT
- 'lazer_user_statistics' as table_name,
- COUNT(*) as synced_records
-FROM lazer_user_statistics
-UNION ALL
-SELECT
- 'lazer_user_achievements' as table_name,
- COUNT(*) as synced_records
-FROM lazer_user_achievements;
-
--- 显示一些样本数据
-SELECT
- u.id,
- u.name,
- lup.is_supporter,
- lup.playmode,
- luc.code as country_code,
- lus.pp,
- lus.play_count
-FROM users u
-LEFT JOIN lazer_user_profiles lup ON u.id = lup.user_id
-LEFT JOIN lazer_user_countries luc ON u.id = luc.user_id
-LEFT JOIN lazer_user_statistics lus ON u.id = lus.user_id AND lus.mode = 'osu'
-ORDER BY u.id
-LIMIT 10;
diff --git a/osu_api_example.py b/osu_api_example.py
deleted file mode 100644
index 342d522..0000000
--- a/osu_api_example.py
+++ /dev/null
@@ -1,64 +0,0 @@
-from __future__ import annotations
-
-import os
-
-import requests
-
-CLIENT_ID = os.environ.get("OSU_CLIENT_ID", "5")
-CLIENT_SECRET = os.environ.get(
- "OSU_CLIENT_SECRET", "FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"
-)
-API_URL = os.environ.get("OSU_API_URL", "https://osu.ppy.sh")
-
-
-def authenticate(username: str, password: str):
- """Authenticate via OAuth password flow and return the token dict."""
- url = f"{API_URL}/oauth/token"
- data = {
- "grant_type": "password",
- "username": username,
- "password": password,
- "client_id": CLIENT_ID,
- "client_secret": CLIENT_SECRET,
- "scope": "*",
- }
- response = requests.post(url, data=data)
- response.raise_for_status()
- return response.json()
-
-
-def refresh_token(refresh: str):
- """Refresh the OAuth token."""
- url = f"{API_URL}/oauth/token"
- data = {
- "grant_type": "refresh_token",
- "refresh_token": refresh,
- "client_id": CLIENT_ID,
- "client_secret": CLIENT_SECRET,
- "scope": "*",
- }
- response = requests.post(url, data=data)
- response.raise_for_status()
- return response.json()
-
-
-def get_current_user(access_token: str, ruleset: str = "osu"):
- """Retrieve the authenticated user's data."""
- url = f"{API_URL}/api/v2/me/{ruleset}"
- headers = {"Authorization": f"Bearer {access_token}"}
- response = requests.get(url, headers=headers)
- response.raise_for_status()
- return response.json()
-
-
-if __name__ == "__main__":
- import getpass
-
- username = input("osu! username: ")
- password = getpass.getpass()
-
- token = authenticate(username, password)
- print("Access Token:", token["access_token"])
- user = get_current_user(token["access_token"])
-
- print(user)
diff --git a/quick_sync.py b/quick_sync.py
deleted file mode 100644
index 9a0221e..0000000
--- a/quick_sync.py
+++ /dev/null
@@ -1,128 +0,0 @@
-#!/usr/bin/env python3
-"""
-简化的数据同步执行脚本
-直接使用项目配置执行数据同步
-"""
-
-from __future__ import annotations
-
-import os
-import subprocess
-from urllib.parse import urlparse
-
-from app.config import settings
-
-
-def parse_database_url():
- """解析数据库 URL"""
- url = urlparse(settings.DATABASE_URL)
- return {
- "host": url.hostname or "localhost",
- "port": url.port or 3306,
- "user": url.username or "root",
- "password": url.password or "",
- "database": url.path.lstrip("/") if url.path else "osu_api",
- }
-
-
-def run_sql_script(script_path: str):
- """使用 mysql 命令行执行 SQL 脚本"""
- if not os.path.exists(script_path):
- print(f"错误: SQL 脚本不存在 - {script_path}")
- return False
-
- # 解析数据库配置
- db_config = parse_database_url()
-
- # 构建 mysql 命令
- cmd = [
- "mysql",
- f"--host={db_config['host']}",
- f"--port={db_config['port']}",
- f"--user={db_config['user']}",
- db_config["database"],
- ]
-
- # 添加密码(如果有的话)
- if db_config["password"]:
- cmd.insert(-1, f"--password={db_config['password']}")
-
- try:
- print(f"执行 SQL 脚本: {script_path}")
- with open(script_path, encoding="utf-8") as f:
- result = subprocess.run(
- cmd, stdin=f, capture_output=True, text=True, check=True
- )
-
- if result.stdout:
- print("执行结果:")
- print(result.stdout)
-
- print(f"✓ 成功执行: {script_path}")
- return True
-
- except subprocess.CalledProcessError as e:
- print(f"✗ 执行失败: {script_path}")
- print(f"错误信息: {e.stderr}")
- return False
- except FileNotFoundError:
- print("错误: 未找到 mysql 命令行工具")
- print("请确保 MySQL 客户端已安装并添加到 PATH 环境变量中")
- return False
-
-
-def main():
- """主函数"""
- print("Lazer API 快速数据同步")
- print("=" * 40)
-
- db_config = parse_database_url()
- print(f"数据库: {db_config['host']}:{db_config['port']}/{db_config['database']}")
- print()
-
- # 确认是否继续
- print("这将执行以下操作:")
- print("1. 创建 lazer 专用表结构")
- print("2. 同步现有用户数据到新表")
- print("3. 不会修改现有的原始表数据")
- print()
-
- confirm = input("是否继续? (y/N): ").strip().lower()
- if confirm != "y":
- print("操作已取消")
- return
-
- # 获取脚本路径
- script_dir = os.path.dirname(__file__)
- migrations_dir = os.path.join(script_dir, "migrations_old")
-
- # 第一步: 创建表结构
- print("\n步骤 1: 创建 lazer 专用表结构...")
- add_fields_script = os.path.join(migrations_dir, "add_missing_fields.sql")
- if not run_sql_script(add_fields_script):
- print("表结构创建失败,停止执行")
- return
-
- # 第二步: 同步数据
- print("\n步骤 2: 同步历史数据...")
- sync_script = os.path.join(migrations_dir, "sync_legacy_data.sql")
- if not run_sql_script(sync_script):
- print("数据同步失败")
- return
-
- # 第三步: 添加缺失的字段
- print("\n步骤 3: 添加缺失的字段...")
- add_rank_fields_script = os.path.join(migrations_dir, "add_lazer_rank_fields.sql")
- if not run_sql_script(add_rank_fields_script):
- print("添加字段失败")
- return
-
- print("\n🎉 数据同步完成!")
- print("\n现在您可以:")
- print("1. 启动 Lazer API 服务器")
- print("2. 使用现有用户账号登录")
- print("3. 查看同步后的用户数据")
-
-
-if __name__ == "__main__":
- main()
diff --git a/remove_ansi.py b/remove_ansi.py
deleted file mode 100644
index 1720888..0000000
--- a/remove_ansi.py
+++ /dev/null
@@ -1,52 +0,0 @@
-#!/usr/bin/env python3
-"""
-Script to remove ANSI escape codes from log files
-"""
-
-from __future__ import annotations
-
-import re
-import sys
-
-
-def remove_ansi_codes(text):
- """
- Remove ANSI escape codes from text
- """
- # Regular expression to match ANSI escape codes
- ansi_escape = re.compile(r"\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])")
- return ansi_escape.sub("", text)
-
-
-def process_log_file(input_file, output_file=None):
- """
- Process log file and remove ANSI escape codes
- """
- if output_file is None:
- output_file = (
- input_file.replace(".log", "_clean.log")
- if ".log" in input_file
- else input_file + "_clean"
- )
-
- with open(input_file, "r", encoding="utf-8") as infile:
- content = infile.read()
-
- # Remove ANSI escape codes
- clean_content = remove_ansi_codes(content)
-
- with open(output_file, "w", encoding="utf-8") as outfile:
- outfile.write(clean_content)
-
- print(f"Processed {input_file} -> {output_file}")
-
-
-if __name__ == "__main__":
- if len(sys.argv) < 2:
- print("Usage: python remove_ansi.py [output_file]")
- sys.exit(1)
-
- input_file = sys.argv[1]
- output_file = sys.argv[2] if len(sys.argv) > 2 else None
-
- process_log_file(input_file, output_file)
diff --git a/sync_data.py b/sync_data.py
deleted file mode 100644
index b69d7c8..0000000
--- a/sync_data.py
+++ /dev/null
@@ -1,236 +0,0 @@
-#!/usr/bin/env python3
-"""
-Lazer API 数据同步脚本
-用于将现有的 bancho.py 数据同步到新的 lazer 专用表中
-"""
-
-from __future__ import annotations
-
-import logging
-import os
-import sys
-
-import pymysql
-
-# 配置日志
-logging.basicConfig(
- level=logging.INFO,
- format="%(asctime)s - %(levelname)s - %(message)s",
- handlers=[logging.FileHandler("data_sync.log"), logging.StreamHandler(sys.stdout)],
-)
-
-logger = logging.getLogger(__name__)
-
-
-class DatabaseSyncer:
- def __init__(self, host: str, port: int, user: str, password: str, database: str):
- """初始化数据库连接配置"""
- self.host = host
- self.port = port
- self.user = user
- self.password = password
- self.database = database
- self.connection = None
-
- def connect(self):
- """连接到数据库"""
- try:
- self.connection = pymysql.connect(
- host=self.host,
- port=self.port,
- user=self.user,
- password=self.password,
- database=self.database,
- charset="utf8mb4",
- autocommit=False,
- )
- logger.info(f"成功连接到数据库 {self.database}")
- except Exception as e:
- logger.error(f"连接数据库失败: {e}")
- raise
-
- def disconnect(self):
- """断开数据库连接"""
- if self.connection:
- self.connection.close()
- logger.info("数据库连接已关闭")
-
- def execute_sql_file(self, file_path: str):
- """执行 SQL 文件"""
- if not os.path.exists(file_path):
- logger.error(f"SQL 文件不存在: {file_path}")
- return False
-
- try:
- with open(file_path, encoding="utf-8") as f:
- sql_content = f.read()
-
- # 分割SQL语句(简单实现,按分号分割)
- statements = [
- stmt.strip() for stmt in sql_content.split(";") if stmt.strip()
- ]
-
- cursor = self.connection.cursor()
-
- for i, statement in enumerate(statements):
- # 跳过注释和空语句
- if statement.startswith("--") or not statement:
- continue
-
- try:
- logger.info(f"执行第 {i + 1}/{len(statements)} 条SQL语句...")
- cursor.execute(statement)
-
- # 如果是SELECT语句,显示结果
- if statement.strip().upper().startswith("SELECT"):
- results = cursor.fetchall()
- if results:
- logger.info(f"查询结果: {results}")
-
- except Exception as e:
- logger.error(f"执行SQL语句失败: {statement[:100]}...")
- logger.error(f"错误信息: {e}")
- # 继续执行其他语句
- continue
-
- self.connection.commit()
- cursor.close()
- logger.info(f"成功执行SQL文件: {file_path}")
- return True
-
- except Exception as e:
- logger.error(f"执行SQL文件失败: {e}")
- if self.connection:
- self.connection.rollback()
- return False
-
- def check_tables_exist(self, tables: list) -> dict:
- """检查表是否存在"""
- results = {}
- cursor = self.connection.cursor()
-
- for table in tables:
- try:
- cursor.execute(f"SHOW TABLES LIKE '{table}'")
- exists = cursor.fetchone() is not None
- results[table] = exists
- logger.info(f"表 '{table}' {'存在' if exists else '不存在'}")
- except Exception as e:
- logger.error(f"检查表 '{table}' 时出错: {e}")
- results[table] = False
-
- cursor.close()
- return results
-
- def get_table_count(self, table: str) -> int:
- """获取表的记录数"""
- try:
- cursor = self.connection.cursor()
- cursor.execute(f"SELECT COUNT(*) FROM {table}")
- result = cursor.fetchone()
- count = result[0] if result else 0
- cursor.close()
- return count
- except Exception as e:
- logger.error(f"获取表 '{table}' 记录数失败: {e}")
- return -1
-
-
-def main():
- """主函数"""
- print("Lazer API 数据同步工具")
- print("=" * 50)
-
- # 数据库配置
- db_config = {
- "host": input("数据库主机 [localhost]: ").strip() or "localhost",
- "port": int(input("数据库端口 [3306]: ").strip() or "3306"),
- "user": input("数据库用户名: ").strip(),
- "password": input("数据库密码: ").strip(),
- "database": input("数据库名称: ").strip(),
- }
-
- syncer = DatabaseSyncer(**db_config)
-
- try:
- # 连接数据库
- syncer.connect()
-
- # 检查必要的原始表是否存在
- required_tables = ["users", "stats"]
- table_status = syncer.check_tables_exist(required_tables)
-
- missing_tables = [table for table, exists in table_status.items() if not exists]
- if missing_tables:
- logger.error(f"缺少必要的原始表: {missing_tables}")
- return
-
- # 显示原始表的记录数
- for table in required_tables:
- count = syncer.get_table_count(table)
- logger.info(f"表 '{table}' 当前有 {count} 条记录")
-
- # 确认是否执行同步
- print("\n准备执行数据同步...")
- print("这将会:")
- print("1. 创建 lazer 专用表结构 (如果不存在)")
- print("2. 从现有表同步数据到新表")
- print("3. 不会修改或删除现有数据")
-
- confirm = input("\n是否继续? (y/N): ").strip().lower()
- if confirm != "y":
- print("操作已取消")
- return
-
- # 执行表结构创建
- migrations_dir = os.path.join(os.path.dirname(__file__), "migrations_old")
-
- print("\n步骤 1: 创建表结构...")
- add_fields_sql = os.path.join(migrations_dir, "add_missing_fields.sql")
- if os.path.exists(add_fields_sql):
- success = syncer.execute_sql_file(add_fields_sql)
- if not success:
- logger.error("创建表结构失败")
- return
- else:
- logger.warning(f"表结构文件不存在: {add_fields_sql}")
-
- # 执行数据同步
- print("\n步骤 2: 同步数据...")
- sync_sql = os.path.join(migrations_dir, "sync_legacy_data.sql")
- if os.path.exists(sync_sql):
- success = syncer.execute_sql_file(sync_sql)
- if not success:
- logger.error("数据同步失败")
- return
- else:
- logger.error(f"同步脚本不存在: {sync_sql}")
- return
-
- # 显示同步后的统计信息
- print("\n步骤 3: 同步完成统计...")
- lazer_tables = [
- "lazer_user_profiles",
- "lazer_user_countries",
- "lazer_user_statistics",
- "lazer_user_kudosu",
- "lazer_user_counts",
- ]
-
- for table in lazer_tables:
- count = syncer.get_table_count(table)
- if count >= 0:
- logger.info(f"表 '{table}' 现在有 {count} 条记录")
-
- print("\n数据同步完成!")
-
- except KeyboardInterrupt:
- print("\n\n操作被用户中断")
- except Exception as e:
- logger.error(f"同步过程中发生错误: {e}")
- finally:
- syncer.disconnect()
-
-
-if __name__ == "__main__":
- main()
diff --git a/test_api.py b/test_api.py
deleted file mode 100644
index c87ef5b..0000000
--- a/test_api.py
+++ /dev/null
@@ -1,256 +0,0 @@
-#!/usr/bin/env python3
-"""
-测试 osu! API 模拟服务器的脚本
-"""
-
-from __future__ import annotations
-
-import os
-
-from dotenv import load_dotenv
-import httpx as requests
-
-# 加载 .env 文件
-load_dotenv()
-
-CLIENT_ID = os.environ.get("OSU_CLIENT_ID", "5")
-CLIENT_SECRET = os.environ.get(
- "OSU_CLIENT_SECRET", "FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"
-)
-API_URL = os.environ.get("OSU_API_URL", "http://localhost:8000")
-
-
-def test_server_health():
- """测试服务器健康状态"""
- try:
- response = requests.get(f"{API_URL}/health")
- if response.status_code == 200:
- print("✅ 服务器健康检查通过")
- return True
- else:
- print(f"❌ 服务器健康检查失败: {response.status_code}")
- return False
- except Exception as e:
- print(f"❌ 无法连接到服务器: {e}")
- return False
-
-
-def authenticate(username: str, password: str):
- """通过 OAuth 密码流进行身份验证并返回令牌字典"""
- url = f"{API_URL}/oauth/token"
- data = {
- "grant_type": "password",
- "username": username,
- "password": password,
- "client_id": CLIENT_ID,
- "client_secret": CLIENT_SECRET,
- "scope": "*",
- }
-
- try:
- response = requests.post(url, data=data)
- if response.status_code == 200:
- print("✅ 身份验证成功")
- return response.json()
- else:
- print(f"❌ 身份验证失败: {response.status_code}")
- print(f"响应内容: {response.text}")
- return None
- except Exception as e:
- print(f"❌ 身份验证请求失败: {e}")
- return None
-
-
-def refresh_token(refresh_token: str):
- """刷新 OAuth 令牌"""
- url = f"{API_URL}/oauth/token"
- data = {
- "grant_type": "refresh_token",
- "refresh_token": refresh_token,
- "client_id": CLIENT_ID,
- "client_secret": CLIENT_SECRET,
- "scope": "*",
- }
-
- try:
- response = requests.post(url, data=data)
- if response.status_code == 200:
- print("✅ 令牌刷新成功")
- return response.json()
- else:
- print(f"❌ 令牌刷新失败: {response.status_code}")
- print(f"响应内容: {response.text}")
- return None
- except Exception as e:
- print(f"❌ 令牌刷新请求失败: {e}")
- return None
-
-
-def get_current_user(access_token: str, ruleset: str = "osu"):
- """获取认证用户的数据"""
- url = f"{API_URL}/api/v2/me/{ruleset}"
- headers = {"Authorization": f"Bearer {access_token}"}
-
- try:
- response = requests.get(url, headers=headers)
- if response.status_code == 200:
- print(f"✅ 成功获取 {ruleset} 模式的用户数据")
- return response.json()
- else:
- print(f"❌ 获取用户数据失败: {response.status_code}")
- print(f"响应内容: {response.text}")
- return None
- except Exception as e:
- print(f"❌ 获取用户数据请求失败: {e}")
- return None
-
-
-def get_beatmap_scores(access_token: str, beatmap_id: int):
- """获取谱面成绩数据"""
- url = f"{API_URL}/api/v2/beatmaps/{beatmap_id}/scores"
- headers = {"Authorization": f"Bearer {access_token}"}
-
- try:
- response = requests.get(url, headers=headers)
- if response.status_code == 200:
- print(f"✅ 成功获取谱面 {beatmap_id} 的成绩数据")
- return response.json()
- else:
- print(f"❌ 获取谱面成绩失败: {response.status_code}")
- print(f"响应内容: {response.text}")
- return None
- except Exception as e:
- print(f"❌ 获取谱面成绩请求失败: {e}")
- return None
-
-
-def get_user_beatmap_score(access_token: str, beatmap_id: int, user_id: int):
- """获取玩家成绩"""
- url = f"{API_URL}/api/v2/beatmaps/{beatmap_id}/scores/users/{user_id}"
- headers = {"Authorization": f"Bearer {access_token}"}
- try:
- response = requests.get(url, headers=headers)
- if response.status_code == 200:
- print(f"✅ 成功获取谱面 {beatmap_id} 中用户 {user_id} 的成绩数据")
- return response.json()
- else:
- print(f"❌ 获取谱面成绩失败: {response.status_code}")
- print(f"响应内容: {response.text}")
- return None
- except Exception as e:
- print(f"❌ 获取谱面成绩请求失败: {e}")
- return None
-
-
-def get_user_beatmap_score_all(access_token: str, beatmap_id: int, user_id: int):
- """获取玩家成绩"""
- url = f"{API_URL}/api/v2/beatmaps/{beatmap_id}/scores/users/{user_id}/all"
- headers = {"Authorization": f"Bearer {access_token}"}
- try:
- response = requests.get(url, headers=headers)
- if response.status_code == 200:
- print(f"✅ 成功获取谱面 {beatmap_id} 中用户 {user_id} 的成绩数据")
- return response.json()
- else:
- print(f"❌ 获取谱面成绩失败: {response.status_code}")
- print(f"响应内容: {response.text}")
- return None
- except Exception as e:
- print(f"❌ 获取谱面成绩请求失败: {e}")
- return None
-
-
-def main():
- """主测试函数"""
- print("=== osu! API 模拟服务器测试 ===\n")
-
- # 1. 测试服务器健康状态
- print("1. 检查服务器状态...")
- if not test_server_health():
- print("请确保服务器正在运行: uvicorn main:app --reload")
- return
-
- print()
-
- # 2. 获取用户凭据
- print("2. 用户身份验证...")
- username = input("请输入用户名 (默认: Googujiang): ").strip() or "Googujiang"
-
- import getpass
-
- password = getpass.getpass("请输入密码 (默认: password123): ") or "password123"
-
- # 3. 身份验证
- print(f"\n3. 正在验证用户 '{username}'...")
- token_data = authenticate(username, password)
- if not token_data:
- print("身份验证失败,请检查用户名和密码")
- return
-
- print(f"访问令牌: {token_data['access_token']}")
- print(f"刷新令牌: {token_data['refresh_token']}")
- print(f"令牌有效期: {token_data['expires_in']} 秒")
-
- # 4. 获取用户数据
- print("\n4. 获取用户数据...")
- for ruleset in ["osu", "taiko", "fruits", "mania"]:
- print(f"\n--- {ruleset.upper()} 模式 ---")
- user_data = get_current_user(token_data["access_token"], ruleset)
- if user_data:
- print(f"用户名: {user_data['username']}")
- print(f"国家: {user_data['country']['name']} ({user_data['country_code']})")
- print(f"全球排名: {user_data['statistics']['global_rank']}")
- print(f"PP: {user_data['statistics']['pp']}")
- print(f"游戏次数: {user_data['statistics']['play_count']}")
- print(f"命中精度: {user_data['statistics']['hit_accuracy']:.2f}%")
-
- # 5. 测试获取谱面成绩
- print("\n5. 测试获取谱面成绩...")
- scores_data = get_beatmap_scores(token_data["access_token"], 1)
- if scores_data:
- print(f"谱面成绩总数: {len(scores_data['scores'])}")
- if scores_data["userScore"]:
- print("用户在该谱面有成绩记录")
- print(f"用户成绩 ID: {scores_data['userScore']['id']}")
- print(f"用户成绩分数: {scores_data['userScore']['total_score']}\n")
- else:
- print("用户在该谱面没有成绩记录\n")
-
- # 5a. 测试谱面指定用户成绩
- user_score = get_user_beatmap_score(token_data["access_token"], 1, 1)
- if user_score:
- print(f"用户成绩ID:{user_score['score']['id']}")
- print(f"此成绩acc:{user_score['score']['accuracy']}")
- print(f"总分:{user_score['score']['classic_total_score']}\n")
- else:
- print("该用户在此谱面没有记录\n")
-
- # 5b. 测试谱面指定用户成绩
- user_score_all = get_user_beatmap_score_all(token_data["access_token"], 1, 1)
- if user_score_all:
- index = 1
- for score in user_score_all:
- print(f"第{index}个成绩:")
- print(f"用户成绩ID:{score['id']}")
- print(f"此成绩acc:{score['accuracy']}")
- print(f"总分:{score['classic_total_score']}")
- else:
- print("该用户在此谱面没有记录")
-
- # 6. 测试令牌刷新
- print("\n6. 测试令牌刷新...")
- new_token_data = refresh_token(token_data["refresh_token"])
- if new_token_data:
- print(f"新访问令牌: {new_token_data['access_token']}")
-
- # 使用新令牌获取用户数据
- print("\n6. 使用新令牌获取用户数据...")
- user_data = get_current_user(new_token_data["access_token"])
- if user_data:
- print(f"✅ 新令牌有效,用户: {user_data['username']}")
-
- print("\n=== 测试完成 ===")
-
-
-if __name__ == "__main__":
- main()
diff --git a/test_lazer.py b/test_lazer.py
deleted file mode 100644
index 627325d..0000000
--- a/test_lazer.py
+++ /dev/null
@@ -1,133 +0,0 @@
-#!/usr/bin/env python3
-"""
-Lazer API 系统测试脚本
-验证新的 lazer 表支持是否正常工作
-"""
-
-from __future__ import annotations
-
-import os
-import sys
-
-sys.path.append(os.path.dirname(os.path.dirname(__file__)))
-
-from app.database import User
-from app.dependencies.database import engine
-from app.utils import convert_db_user_to_api_user
-
-from sqlmodel import select
-from sqlmodel.ext.asyncio.session import AsyncSession
-
-
-async def test_lazer_tables():
- """测试 lazer 表的基本功能"""
- print("测试 Lazer API 表支持...")
-
- async with AsyncSession(engine) as session:
- async with session.begin():
- try:
- # 测试查询用户
- statement = select(User)
- result = await session.execute(statement)
- user = result.scalars().first()
- if not user:
- print("❌ 没有找到用户,请先同步数据")
- return False
-
- print(f"✓ 找到用户: {user.name} (ID: {user.id})")
-
- # 测试 lazer 资料
- if user.lazer_profile:
- print(
- f"✓ 用户有 lazer 资料: 支持者={user.lazer_profile.is_supporter}"
- )
- else:
- print("⚠ 用户没有 lazer 资料,将使用默认值")
-
- # 测试 lazer 统计
- osu_stats = None
- for stat in user.lazer_statistics:
- if stat.mode == "osu":
- osu_stats = stat
- break
-
- if osu_stats:
- print(
- f"✓ 用户有 osu! 统计: PP={osu_stats.pp}, "
- f"游戏次数={osu_stats.play_count}"
- )
- else:
- print("⚠ 用户没有 osu! 统计,将使用默认值")
-
- # 测试转换为 API 格式
- api_user = convert_db_user_to_api_user(user, "osu")
- print("✓ 成功转换为 API 用户格式")
- print(f" - 用户名: {api_user.username}")
- print(f" - 国家: {api_user.country_code}")
- print(f" - PP: {api_user.statistics.pp}")
- print(f" - 是否支持者: {api_user.is_supporter}")
-
- return True
-
- except Exception as e:
- print(f"❌ 测试失败: {e}")
- import traceback
-
- traceback.print_exc()
- return False
-
-
-async def test_authentication():
- """测试认证功能"""
- print("\n测试认证功能...")
-
- async with AsyncSession(engine) as session:
- async with session.begin():
- try:
- # 尝试认证第一个用户
- statement = select(User)
- result = await session.execute(statement)
- user = result.scalars().first()
- if not user:
- print("❌ 没有用户进行认证测试")
- return False
-
- print(f"✓ 测试用户: {user.name}")
- print("⚠ 注意: 实际密码认证需要正确的密码")
-
- return True
-
- except Exception as e:
- print(f"❌ 认证测试失败: {e}")
- return False
-
-
-async def main():
- """主测试函数"""
- print("Lazer API 系统测试")
- print("=" * 40)
-
- # 测试表连接
- success1 = await test_lazer_tables()
-
- # 测试认证
- success2 = await test_authentication()
-
- print("\n" + "=" * 40)
- if success1 and success2:
- print("🎉 所有测试通过!")
- print("\n现在可以:")
- print("1. 启动 API 服务器: python main.py")
- print("2. 测试 OAuth 认证")
- print("3. 调用 /api/v2/me/osu 获取用户信息")
- else:
- print("❌ 测试失败,请检查:")
- print("1. 数据库连接是否正常")
- print("2. 是否已运行数据同步脚本")
- print("3. lazer 表是否正确创建")
-
-
-if __name__ == "__main__":
- import asyncio
-
- asyncio.run(main())
diff --git a/test_password.py b/test_password.py
deleted file mode 100644
index c0aa3cd..0000000
--- a/test_password.py
+++ /dev/null
@@ -1,55 +0,0 @@
-#!/usr/bin/env python3
-"""
-测试密码哈希和验证逻辑
-"""
-
-from __future__ import annotations
-
-import hashlib
-
-from app.auth import bcrypt_cache, get_password_hash, verify_password_legacy
-
-
-def test_password_logic():
- """测试密码逻辑"""
- print("=== 测试密码哈希和验证逻辑 ===\n")
-
- # 测试密码
- password = "password123"
- print(f"原始密码: {password}")
-
- # 1. 生成哈希
- print("\n1. 生成密码哈希...")
- hashed = get_password_hash(password)
- print(f"bcrypt 哈希: {hashed}")
-
- # 2. 验证密码
- print("\n2. 验证密码...")
- is_valid = verify_password_legacy(password, hashed)
- print(f"验证结果: {'✅ 成功' if is_valid else '❌ 失败'}")
-
- # 3. 测试错误密码
- print("\n3. 测试错误密码...")
- wrong_password = "wrongpassword"
- is_valid_wrong = verify_password_legacy(wrong_password, hashed)
- print(f"错误密码验证结果: {'❌ 不应该成功' if is_valid_wrong else '✅ 正确拒绝'}")
-
- # 4. 测试缓存
- print("\n4. 缓存状态:")
- print(f"缓存中的条目数: {len(bcrypt_cache)}")
- if hashed in bcrypt_cache:
- print(f"缓存的 MD5: {bcrypt_cache[hashed]}")
- expected_md5 = hashlib.md5(password.encode()).hexdigest().encode()
- print(f"期望的 MD5: {expected_md5}")
- print(f"缓存匹配: {'✅' if bcrypt_cache[hashed] == expected_md5 else '❌'}")
-
- # 5. 再次验证(应该使用缓存)
- print("\n5. 再次验证(使用缓存)...")
- is_valid_cached = verify_password_legacy(password, hashed)
- print(f"缓存验证结果: {'✅ 成功' if is_valid_cached else '❌ 失败'}")
-
- print("\n=== 测试完成 ===")
-
-
-if __name__ == "__main__":
- test_password_logic()