chore(dev): update develop environment

This commit is contained in:
MingxuanGame
2025-07-26 15:03:56 +00:00
parent 9ceacd9771
commit ced315be9e
27 changed files with 3555 additions and 3414 deletions

View File

@@ -0,0 +1,56 @@
{
"name": "OSU Lazer API",
"dockerComposeFile": "docker-compose.yml",
"service": "devcontainer",
"shutdownAction": "stopCompose",
"workspaceFolder": "/workspaces/osu_lazer_api",
"customizations": {
"vscode": {
"extensions": [
"ms-python.python",
"ms-python.vscode-pylance",
"detachhead.basedpyright",
"charliermarsh.ruff",
"ms-python.debugpy",
"ms-vscode.vscode-json",
"redhat.vscode-yaml",
"ms-vscode.docker"
],
"settings": {
"python.defaultInterpreterPath": "/usr/local/bin/python",
"python.terminal.activateEnvironment": true,
"python.linting.enabled": true,
"python.linting.pylintEnabled": false,
"python.linting.flake8Enabled": false,
"python.formatting.provider": "none",
"[python]": {
"editor.defaultFormatter": "charliermarsh.ruff",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit"
}
},
"ruff.enable": true,
"ruff.lint.enable": true,
"ruff.format.enable": true,
"ruff.importStrategy": "fromEnvironment",
"files.exclude": {
"**/__pycache__": true,
"**/*.pyc": true
},
"python.testing.pytestEnabled": true,
"python.testing.unittestEnabled": false,
"python.testing.pytestArgs": [
"."
],
"terminal.integrated.defaultProfile.linux": "bash"
}
}
},
"features": {
"ghcr.io/va-h/devcontainers-features/uv:1": {}
},
"forwardPorts": [8000, 3306, 6379],
"postCreateCommand": "uv sync --dev && uv run pre-commit install",
"remoteUser": "vscode"
}

View File

@@ -0,0 +1,56 @@
version: '3.8'
services:
devcontainer:
image: mcr.microsoft.com/devcontainers/python:3.13
volumes:
- ../..:/workspaces:cached
- ~/.ssh:/home/vscode/.ssh:ro
command: sleep infinity
networks:
- devcontainer-network
depends_on:
- mysql
- redis
environment:
DATABASE_URL: mysql+aiomysql://osu_user:osu_password@mysql:3306/osu_api
REDIS_URL: redis://redis:6379/0
SECRET_KEY: dev-secret-key-change-in-production
OSU_CLIENT_ID: "5"
OSU_CLIENT_SECRET: "FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"
mysql:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: osu_api
MYSQL_USER: osu_user
MYSQL_PASSWORD: osu_password
ports:
- "3306:3306"
volumes:
- mysql-data:/var/lib/mysql
- ../mysql-init:/docker-entrypoint-initdb.d:cached
networks:
- devcontainer-network
command: --default-authentication-plugin=mysql_native_password
redis:
image: redis:7-alpine
restart: unless-stopped
ports:
- "6379:6379"
volumes:
- redis-data:/data
networks:
- devcontainer-network
command: redis-server --appendonly yes
networks:
devcontainer-network:
driver: bridge
volumes:
mysql-data:
redis-data:

28
.editorconfig Normal file
View File

@@ -0,0 +1,28 @@
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
# Makefiles always use tabs for indentation
[Makefile]
indent_style = tab
# Batch files use tabs for indentation
[*.bat]
indent_style = tab
[*.md]
trim_trailing_whitespace = false
# Matches the exact files either package.json or .travis.yml
[{package.json,.travis.yml}]
indent_size = 2
[{*.py,*.pyi}]
indent_size = 4

View File

@@ -1,4 +1,4 @@
# osu! API 客户端配置
OSU_CLIENT_ID=5
OSU_CLIENT_SECRET=FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk
OSU_API_URL=http://localhost:8000
# osu! API 客户端配置
OSU_CLIENT_ID=5
OSU_CLIENT_SECRET=FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk
OSU_API_URL=http://localhost:8000

418
.gitignore vendored
View File

@@ -1,209 +1,209 @@
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
bancho.py-master/*
.vscode/settings.json
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[codz]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py.cover
.hypothesis/
.pytest_cache/
cover/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
.pybuilder/
target/
# Jupyter Notebook
.ipynb_checkpoints
# IPython
profile_default/
ipython_config.py
# pyenv
# For a library or package, you might want to ignore these files since the code is
# intended to run in multiple environments; otherwise, check them in:
# .python-version
# pipenv
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
# However, in case of collaboration, if having platform-specific dependencies or dependencies
# having no cross-platform support, pipenv may install dependencies that don't work, or not
# install all needed dependencies.
#Pipfile.lock
# UV
# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
#uv.lock
# poetry
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
# This is especially recommended for binary packages to ensure reproducibility, and is more
# commonly ignored for libraries.
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
#poetry.lock
#poetry.toml
# pdm
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python.
# https://pdm-project.org/en/latest/usage/project/#working-with-version-control
#pdm.lock
#pdm.toml
.pdm-python
.pdm-build/
# pixi
# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control.
#pixi.lock
# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one
# in the .venv directory. It is recommended not to include this directory in version control.
.pixi
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/
# Celery stuff
celerybeat-schedule
celerybeat.pid
# SageMath parsed files
*.sage.py
# Environments
.env
.envrc
.venv
env/
venv/
ENV/
env.bak/
venv.bak/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
.dmypy.json
dmypy.json
# Pyre type checker
.pyre/
# pytype static type analyzer
.pytype/
# Cython debug symbols
cython_debug/
# PyCharm
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
# Abstra
# Abstra is an AI-powered process automation framework.
# Ignore directories containing user credentials, local state, and settings.
# Learn more at https://abstra.io/docs
.abstra/
# Visual Studio Code
# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore
# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore
# and can be added to the global gitignore or merged into this file. However, if you prefer,
# you could uncomment the following to ignore the entire vscode folder
# .vscode/
# Ruff stuff:
.ruff_cache/
# PyPI configuration file
.pypirc
# Cursor
# Cursor is an AI-powered code editor. `.cursorignore` specifies files/directories to
# exclude from AI features like autocomplete and code analysis. Recommended for sensitive data
# refer to https://docs.cursor.com/context/ignore-files
.cursorignore
.cursorindexingignore
# Marimo
marimo/_static/
marimo/_lsp/
__marimo__/
bancho.py-master/*
.vscode/settings.json

6
.idea/.gitignore generated vendored
View File

@@ -1,3 +1,3 @@
# 默认忽略的文件
/shelf/
/workspace.xml
# 默认忽略的文件
/shelf/
/workspace.xml

View File

@@ -1,17 +1,17 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="3.7" />
<item index="1" class="java.lang.String" itemvalue="3.11" />
<item index="2" class="java.lang.String" itemvalue="3.12" />
<item index="3" class="java.lang.String" itemvalue="3.13" />
</list>
</value>
</option>
</inspection_tool>
</profile>
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="PyCompatibilityInspection" enabled="true" level="WARNING" enabled_by_default="true">
<option name="ourVersions">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="3.7" />
<item index="1" class="java.lang.String" itemvalue="3.11" />
<item index="2" class="java.lang.String" itemvalue="3.12" />
<item index="3" class="java.lang.String" itemvalue="3.13" />
</list>
</value>
</option>
</inspection_tool>
</profile>
</component>

View File

@@ -1,6 +1,6 @@
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
<component name="InspectionProjectProfileManager">
<settings>
<option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" />
</settings>
</component>

18
.idea/misc.xml generated
View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="osu_lazer_api" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="uv (osu_lazer_api)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="Black">
<option name="sdkName" value="osu_lazer_api" />
</component>
<component name="ProjectRootManager" version="2" project-jdk-name="uv (osu_lazer_api)" project-jdk-type="Python SDK" />
<component name="PythonCompatibilityInspectionAdvertiser">
<option name="version" value="3" />
</component>
</project>

14
.idea/modules.xml generated
View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/osu_lazer_api.iml" filepath="$PROJECT_DIR$/.idea/osu_lazer_api.iml" />
</modules>
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/osu_lazer_api.iml" filepath="$PROJECT_DIR$/.idea/osu_lazer_api.iml" />
</modules>
</component>
</project>

View File

@@ -1,14 +1,14 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="uv (osu_lazer_api)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
<?xml version="1.0" encoding="UTF-8"?>
<module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/.venv" />
</content>
<orderEntry type="jdk" jdkName="uv (osu_lazer_api)" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
<component name="PyDocumentationSettings">
<option name="format" value="PLAIN" />
<option name="myDocStringFormat" value="Plain" />
</component>
</module>

10
.idea/vcs.xml generated
View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

View File

@@ -1,16 +1,16 @@
default_install_hook_types: [pre-commit, prepare-commit-msg]
ci:
autofix_commit_msg: "chore(deps): auto fix by pre-commit hooks"
autofix_prs: true
autoupdate_branch: master
autoupdate_schedule: monthly
autoupdate_commit_msg: "chore(deps): auto update by pre-commit hooks"
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.2
hooks:
- id: ruff-check
args: [--fix]
stages: [pre-commit]
- id: ruff-format
stages: [pre-commit]
default_install_hook_types: [pre-commit, prepare-commit-msg]
ci:
autofix_commit_msg: "chore(deps): auto fix by pre-commit hooks"
autofix_prs: true
autoupdate_branch: master
autoupdate_schedule: monthly
autoupdate_commit_msg: "chore(deps): auto update by pre-commit hooks"
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.12.2
hooks:
- id: ruff-check
args: [--fix]
stages: [pre-commit]
- id: ruff-format
stages: [pre-commit]

25
.vscode/launch.json vendored
View File

@@ -1,14 +1,13 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Server",
"type": "debugpy",
"request": "launch",
"program": "main.py",
"console": "integratedTerminal",
"justMyCode": true
}
]
{
"version": "0.2.0",
"configurations": [
{
"name": "Server",
"type": "debugpy",
"request": "launch",
"program": "main.py",
"console": "integratedTerminal",
"justMyCode": true
}
]
}

View File

@@ -1,140 +1,140 @@
# Lazer API 数据同步指南
本指南将帮助您将现有的 bancho.py 数据库数据同步到新的 Lazer API 专用表中。
## 文件说明
1. **`migrations/add_missing_fields.sql`** - 创建 Lazer API 专用表结构
2. **`migrations/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/add_missing_fields.sql
# 2. 同步数据
mysql -u username -p database_name < migrations/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. 验证原始表数据完整性
# Lazer API 数据同步指南
本指南将帮助您将现有的 bancho.py 数据库数据同步到新的 Lazer API 专用表中。
## 文件说明
1. **`migrations/add_missing_fields.sql`** - 创建 Lazer API 专用表结构
2. **`migrations/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/add_missing_fields.sql
# 2. 同步数据
mysql -u username -p database_name < migrations/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. 验证原始表数据完整性

View File

@@ -1,28 +1,28 @@
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
WORKDIR /app
ENV UV_PROJECT_ENVIRONMENT=syncvenv
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
pkg-config \
default-libmysqlclient-dev \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY uv.lock .
COPY pyproject.toml .
COPY requirements.txt .
# 安装Python依赖
RUN pip install -r requirements.txt
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
WORKDIR /app
ENV UV_PROJECT_ENVIRONMENT=syncvenv
# 安装系统依赖
RUN apt-get update && apt-get install -y \
gcc \
pkg-config \
default-libmysqlclient-dev \
&& rm -rf /var/lib/apt/lists/*
# 复制依赖文件
COPY uv.lock .
COPY pyproject.toml .
COPY requirements.txt .
# 安装Python依赖
RUN pip install -r requirements.txt
# 复制应用代码
COPY . .
# 暴露端口
EXPOSE 8000
# 启动命令
CMD ["uv", "run", "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]

428
README.md
View File

@@ -1,214 +1,214 @@
# osu! API 模拟服务器
这是一个使用 FastAPI + MySQL + Redis 实现的 osu! API 模拟服务器,提供了完整的用户认证和数据管理功能。
## 功能特性
- **OAuth 2.0 认证**: 支持密码流和刷新令牌流
- **用户数据管理**: 完整的用户信息、统计数据、成就等
- **多游戏模式支持**: osu!, taiko, fruits, mania
- **数据库持久化**: MySQL 存储用户数据
- **缓存支持**: Redis 缓存令牌和会话信息
- **容器化部署**: Docker 和 Docker Compose 支持
## API 端点
### 认证端点
- `POST /oauth/token` - OAuth 令牌获取/刷新
### 用户端点
- `GET /api/v2/me/{ruleset}` - 获取当前用户信息
### 其他端点
- `GET /` - 根端点
- `GET /health` - 健康检查
## 快速开始
### 使用 Docker Compose (推荐)
1. 克隆项目
```bash
git clone <repository-url>
cd osu_lazer_api
```
2. 启动服务
```bash
docker-compose up -d
```
3. 创建示例数据
```bash
docker-compose exec api python create_sample_data.py
```
4. 测试 API
```bash
# 获取访问令牌
curl -X POST http://localhost:8000/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=Googujiang&password=password123&client_id=5&client_secret=FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk&scope=*"
# 使用令牌获取用户信息
curl -X GET http://localhost:8000/api/v2/me/osu \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
### 本地开发
1. 安装依赖
```bash
pip install -r requirements.txt
```
2. 配置环境变量
```bash
# 复制服务器配置文件
cp .env .env.local
# 复制客户端配置文件(用于测试脚本)
cp .env.client .env.client.local
```
3. 启动 MySQL 和 Redis
```bash
# 使用 Docker
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=osu_api -p 3306:3306 mysql:8.0
docker run -d --name redis -p 6379:6379 redis:7-alpine
```
4. 创建示例数据
```bash
python create_sample_data.py
```
5. 启动应用
```bash
uvicorn main:app --reload
```
6. 测试 API
```bash
# 使用测试脚本(会自动加载 .env 文件)
python test_api.py
# 或使用原始示例脚本
python osu_api_example.py
```
## 项目结构
```
osu_lazer_api/
├── app/
│ ├── __init__.py
│ ├── models.py # Pydantic 数据模型
│ ├── database.py # SQLAlchemy 数据库模型
│ ├── config.py # 配置设置
│ ├── dependencies.py # 依赖注入
│ ├── auth.py # 认证和令牌管理
│ └── utils.py # 工具函数
├── main.py # FastAPI 应用主文件
├── create_sample_data.py # 示例数据创建脚本
├── requirements.txt # Python 依赖
├── .env # 环境变量配置
├── docker-compose.yml # Docker Compose 配置
├── Dockerfile # Docker 镜像配置
└── README.md # 项目说明
```
## 示例用户
创建示例数据后,您可以使用以下凭据进行测试:
- **用户名**: `Googujiang`
- **密码**: `password123`
- **用户ID**: `15651670`
## 环境变量配置
项目包含两个环境配置文件:
### 服务器配置 (`.env`)
用于配置 FastAPI 服务器的运行参数:
| 变量名 | 描述 | 默认值 |
|--------|------|--------|
| `DATABASE_URL` | MySQL 数据库连接字符串 | `mysql+pymysql://root:password@localhost:3306/osu_api` |
| `REDIS_URL` | Redis 连接字符串 | `redis://localhost:6379/0` |
| `SECRET_KEY` | JWT 签名密钥 | `your-secret-key-here` |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | 访问令牌过期时间(分钟) | `1440` |
| `OSU_CLIENT_ID` | OAuth 客户端 ID | `5` |
| `OSU_CLIENT_SECRET` | OAuth 客户端密钥 | `FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk` |
| `HOST` | 服务器监听地址 | `0.0.0.0` |
| `PORT` | 服务器监听端口 | `8000` |
| `DEBUG` | 调试模式 | `True` |
### 客户端配置 (`.env.client`)
用于配置客户端脚本的 API 连接参数:
| 变量名 | 描述 | 默认值 |
|--------|------|--------|
| `OSU_CLIENT_ID` | OAuth 客户端 ID | `5` |
| `OSU_CLIENT_SECRET` | OAuth 客户端密钥 | `FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk` |
| `OSU_API_URL` | API 服务器地址 | `http://localhost:8000` |
> **注意**: 在生产环境中,请务必更改默认的密钥和密码!
## API 使用示例
### 获取访问令牌
```bash
curl -X POST http://localhost:8000/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=Googujiang&password=password123&client_id=5&client_secret=FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk&scope=*"
```
响应:
```json
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "abc123...",
"scope": "*"
}
```
### 获取用户信息
```bash
curl -X GET http://localhost:8000/api/v2/me/osu \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
### 刷新令牌
```bash
curl -X POST http://localhost:8000/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN&client_id=5&client_secret=FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"
```
## 开发
### 添加新用户
您可以通过修改 `create_sample_data.py` 文件来添加更多示例用户,或者扩展 API 来支持用户注册功能。
### 扩展功能
- 添加更多 API 端点(排行榜、谱面信息等)
- 实现实时功能WebSocket
- 添加管理面板
- 实现数据导入/导出功能
## 许可证
MIT License
## 贡献
欢迎提交 Issue 和 Pull Request
# osu! API 模拟服务器
这是一个使用 FastAPI + MySQL + Redis 实现的 osu! API 模拟服务器,提供了完整的用户认证和数据管理功能。
## 功能特性
- **OAuth 2.0 认证**: 支持密码流和刷新令牌流
- **用户数据管理**: 完整的用户信息、统计数据、成就等
- **多游戏模式支持**: osu!, taiko, fruits, mania
- **数据库持久化**: MySQL 存储用户数据
- **缓存支持**: Redis 缓存令牌和会话信息
- **容器化部署**: Docker 和 Docker Compose 支持
## API 端点
### 认证端点
- `POST /oauth/token` - OAuth 令牌获取/刷新
### 用户端点
- `GET /api/v2/me/{ruleset}` - 获取当前用户信息
### 其他端点
- `GET /` - 根端点
- `GET /health` - 健康检查
## 快速开始
### 使用 Docker Compose (推荐)
1. 克隆项目
```bash
git clone <repository-url>
cd osu_lazer_api
```
2. 启动服务
```bash
docker-compose up -d
```
3. 创建示例数据
```bash
docker-compose exec api python create_sample_data.py
```
4. 测试 API
```bash
# 获取访问令牌
curl -X POST http://localhost:8000/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=Googujiang&password=password123&client_id=5&client_secret=FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk&scope=*"
# 使用令牌获取用户信息
curl -X GET http://localhost:8000/api/v2/me/osu \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
### 本地开发
1. 安装依赖
```bash
pip install -r requirements.txt
```
2. 配置环境变量
```bash
# 复制服务器配置文件
cp .env .env.local
# 复制客户端配置文件(用于测试脚本)
cp .env.client .env.client.local
```
3. 启动 MySQL 和 Redis
```bash
# 使用 Docker
docker run -d --name mysql -e MYSQL_ROOT_PASSWORD=password -e MYSQL_DATABASE=osu_api -p 3306:3306 mysql:8.0
docker run -d --name redis -p 6379:6379 redis:7-alpine
```
4. 创建示例数据
```bash
python create_sample_data.py
```
5. 启动应用
```bash
uvicorn main:app --reload
```
6. 测试 API
```bash
# 使用测试脚本(会自动加载 .env 文件)
python test_api.py
# 或使用原始示例脚本
python osu_api_example.py
```
## 项目结构
```
osu_lazer_api/
├── app/
│ ├── __init__.py
│ ├── models.py # Pydantic 数据模型
│ ├── database.py # SQLAlchemy 数据库模型
│ ├── config.py # 配置设置
│ ├── dependencies.py # 依赖注入
│ ├── auth.py # 认证和令牌管理
│ └── utils.py # 工具函数
├── main.py # FastAPI 应用主文件
├── create_sample_data.py # 示例数据创建脚本
├── requirements.txt # Python 依赖
├── .env # 环境变量配置
├── docker-compose.yml # Docker Compose 配置
├── Dockerfile # Docker 镜像配置
└── README.md # 项目说明
```
## 示例用户
创建示例数据后,您可以使用以下凭据进行测试:
- **用户名**: `Googujiang`
- **密码**: `password123`
- **用户ID**: `15651670`
## 环境变量配置
项目包含两个环境配置文件:
### 服务器配置 (`.env`)
用于配置 FastAPI 服务器的运行参数:
| 变量名 | 描述 | 默认值 |
|--------|------|--------|
| `DATABASE_URL` | MySQL 数据库连接字符串 | `mysql+pymysql://root:password@localhost:3306/osu_api` |
| `REDIS_URL` | Redis 连接字符串 | `redis://localhost:6379/0` |
| `SECRET_KEY` | JWT 签名密钥 | `your-secret-key-here` |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | 访问令牌过期时间(分钟) | `1440` |
| `OSU_CLIENT_ID` | OAuth 客户端 ID | `5` |
| `OSU_CLIENT_SECRET` | OAuth 客户端密钥 | `FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk` |
| `HOST` | 服务器监听地址 | `0.0.0.0` |
| `PORT` | 服务器监听端口 | `8000` |
| `DEBUG` | 调试模式 | `True` |
### 客户端配置 (`.env.client`)
用于配置客户端脚本的 API 连接参数:
| 变量名 | 描述 | 默认值 |
|--------|------|--------|
| `OSU_CLIENT_ID` | OAuth 客户端 ID | `5` |
| `OSU_CLIENT_SECRET` | OAuth 客户端密钥 | `FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk` |
| `OSU_API_URL` | API 服务器地址 | `http://localhost:8000` |
> **注意**: 在生产环境中,请务必更改默认的密钥和密码!
## API 使用示例
### 获取访问令牌
```bash
curl -X POST http://localhost:8000/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=password&username=Googujiang&password=password123&client_id=5&client_secret=FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk&scope=*"
```
响应:
```json
{
"access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...",
"token_type": "Bearer",
"expires_in": 86400,
"refresh_token": "abc123...",
"scope": "*"
}
```
### 获取用户信息
```bash
curl -X GET http://localhost:8000/api/v2/me/osu \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"
```
### 刷新令牌
```bash
curl -X POST http://localhost:8000/oauth/token \
-H "Content-Type: application/x-www-form-urlencoded" \
-d "grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN&client_id=5&client_secret=FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"
```
## 开发
### 添加新用户
您可以通过修改 `create_sample_data.py` 文件来添加更多示例用户,或者扩展 API 来支持用户注册功能。
### 扩展功能
- 添加更多 API 端点(排行榜、谱面信息等)
- 实现实时功能WebSocket
- 添加管理面板
- 实现数据导入/导出功能
## 许可证
MIT License
## 贡献
欢迎提交 Issue 和 Pull Request

View File

@@ -1,3 +1,5 @@
from __future__ import annotations
from .router import router as signalr_router
__all__ = ["signalr_router"]

View File

@@ -127,7 +127,7 @@ class Hub:
args: list[Any] | None = packet[3]
if args is None:
args = []
streams: list[str] | None = packet[4] # TODO: stream support
# streams: list[str] | None = packet[4] # TODO: stream support
code = ResultKind.VOID
result = None
try:

View File

@@ -1,50 +1,50 @@
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: osu_api_mysql
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: osu_api
MYSQL_USER: osu_user
MYSQL_PASSWORD: osu_password
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./mysql-init:/docker-entrypoint-initdb.d
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: osu_api_redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
command: redis-server --appendonly yes
api:
build: .
container_name: osu_api_server
ports:
- "8000:8000"
environment:
DATABASE_URL: mysql+aiomysql://osu_user:osu_password@mysql:3306/osu_api
REDIS_URL: redis://redis:6379/0
SECRET_KEY: your-production-secret-key-here
OSU_CLIENT_ID: "5"
OSU_CLIENT_SECRET: "FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"
depends_on:
- mysql
- redis
restart: unless-stopped
volumes:
- ./:/app
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
volumes:
mysql_data:
redis_data:
version: '3.8'
services:
mysql:
image: mysql:8.0
container_name: osu_api_mysql
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: osu_api
MYSQL_USER: osu_user
MYSQL_PASSWORD: osu_password
ports:
- "3306:3306"
volumes:
- mysql_data:/var/lib/mysql
- ./mysql-init:/docker-entrypoint-initdb.d
restart: unless-stopped
redis:
image: redis:7-alpine
container_name: osu_api_redis
ports:
- "6379:6379"
volumes:
- redis_data:/data
restart: unless-stopped
command: redis-server --appendonly yes
api:
build: .
container_name: osu_api_server
ports:
- "8000:8000"
environment:
DATABASE_URL: mysql+aiomysql://osu_user:osu_password@mysql:3306/osu_api
REDIS_URL: redis://redis:6379/0
SECRET_KEY: your-production-secret-key-here
OSU_CLIENT_ID: "5"
OSU_CLIENT_SECRET: "FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"
depends_on:
- mysql
- redis
restart: unless-stopped
volumes:
- ./:/app
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
volumes:
mysql_data:
redis_data:

View File

@@ -1,16 +1,16 @@
-- 创建迁移日志表(如果不存在)
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());
-- 创建迁移日志表(如果不存在)
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());

View File

@@ -1,421 +1,421 @@
-- 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='功能扩展占位表';
-- 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='功能扩展占位表';

View File

@@ -1,486 +1,486 @@
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');
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');

View File

@@ -1,209 +1,209 @@
-- 自定义谱面系统迁移
-- 创建自定义谱面表,与官方谱面不冲突
-- 自定义谱面集表
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 ;
-- 自定义谱面系统迁移
-- 创建自定义谱面表,与官方谱面不冲突
-- 自定义谱面集表
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 ;

View File

@@ -1,477 +1,477 @@
# 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);
# 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);

View File

@@ -1,337 +1,337 @@
-- 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;
-- 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;

File diff suppressed because it is too large Load Diff