Skip to content

Commit e2a9beb

Browse files
committed
feat: better database migration and config init
1 parent 080380f commit e2a9beb

162 files changed

Lines changed: 5046 additions & 2676 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

backend/.claude/claude.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
1: ruff 是 brew 安装的
2+
2: 项目是通过 uv 运行的

backend/.claude/mp.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# MoviePilot 版本管理参考
2+
3+
## 版本文件
4+
5+
位置:项目根目录 `version.py`
6+
7+
```python
8+
APP_VERSION = 'v1.9.19'
9+
```
10+
11+
## CI 工作流 (build.yml)
12+
13+
### 触发条件
14+
15+
```yaml
16+
on:
17+
workflow_dispatch: # 手动触发
18+
push:
19+
branches:
20+
- main
21+
paths:
22+
- 'version.py' # 只有 version.py 变化才触发
23+
```
24+
25+
### 读取版本号
26+
27+
```yaml
28+
- name: Get version
29+
id: get_version
30+
run: |
31+
app_version=$(cat version.py | sed -ne "s/APP_VERSION\s=\s'v\(.*\)'/\1/gp")
32+
echo "APP_VERSION=${app_version}" >> $GITHUB_OUTPUT
33+
```
34+
35+
### 使用版本号
36+
37+
```yaml
38+
# Docker 镜像标签
39+
- name: Build and push
40+
uses: docker/build-push-action@v5
41+
with:
42+
tags: |
43+
ghcr.io/${{ github.repository }}:${{ steps.get_version.outputs.APP_VERSION }}
44+
ghcr.io/${{ github.repository }}:latest
45+
46+
# 创建 Release
47+
- name: Create Release
48+
uses: softprops/action-gh-release@v1
49+
with:
50+
tag_name: v${{ steps.get_version.outputs.APP_VERSION }}
51+
name: v${{ steps.get_version.outputs.APP_VERSION }}
52+
```
53+
54+
## 完整工作流示例
55+
56+
```yaml
57+
name: Build
58+
59+
on:
60+
workflow_dispatch:
61+
push:
62+
branches:
63+
- main
64+
paths:
65+
- 'version.py'
66+
67+
jobs:
68+
build:
69+
runs-on: ubuntu-latest
70+
steps:
71+
- name: Checkout
72+
uses: actions/checkout@v4
73+
74+
- name: Get version
75+
id: get_version
76+
run: |
77+
app_version=$(cat version.py | sed -ne "s/APP_VERSION\s=\s'v\(.*\)'/\1/gp")
78+
echo "APP_VERSION=${app_version}" >> $GITHUB_OUTPUT
79+
80+
- name: Set up QEMU
81+
uses: docker/setup-qemu-action@v3
82+
83+
- name: Set up Docker Buildx
84+
uses: docker/setup-buildx-action@v3
85+
86+
- name: Login to GHCR
87+
uses: docker/login-action@v3
88+
with:
89+
registry: ghcr.io
90+
username: ${{ github.repository_owner }}
91+
password: ${{ secrets.GITHUB_TOKEN }}
92+
93+
- name: Build and push
94+
uses: docker/build-push-action@v5
95+
with:
96+
context: .
97+
push: true
98+
platforms: linux/amd64,linux/arm64
99+
tags: |
100+
ghcr.io/${{ github.repository }}:${{ steps.get_version.outputs.APP_VERSION }}
101+
ghcr.io/${{ github.repository }}:latest
102+
103+
- name: Create Release
104+
uses: softprops/action-gh-release@v1
105+
with:
106+
tag_name: v${{ steps.get_version.outputs.APP_VERSION }}
107+
name: v${{ steps.get_version.outputs.APP_VERSION }}
108+
generate_release_notes: true
109+
env:
110+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
111+
```
112+
113+
## 发版流程
114+
115+
```
116+
1. 修改 version.py: APP_VERSION = 'v1.9.20'
117+
2. git add version.py
118+
3. git commit -m "release: v1.9.20"
119+
4. git push origin main
120+
5. GitHub Actions 自动:
121+
- 检测 version.py 变化
122+
- 读取版本号 1.9.20
123+
- 构建 Docker 镜像 (tag: 1.9.20, latest)
124+
- 创建 GitHub Release (tag: v1.9.20)
125+
```
126+
127+
## sed 命令解析
128+
129+
```bash
130+
cat version.py | sed -ne "s/APP_VERSION\s=\s'v\(.*\)'/\1/gp"
131+
```
132+
133+
- `-n`: 不自动打印
134+
- `-e`: 执行脚本
135+
- `s/pattern/replacement/gp`: 替换并打印
136+
- `\s`: 匹配空白字符
137+
- `\(.*\)`: 捕获组,匹配版本号
138+
- `\1`: 引用捕获组
139+
140+
输入: `APP_VERSION = 'v1.9.19'`
141+
输出: `1.9.19`

backend/alembic.ini

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
1+
# A generic, single database configuration.
2+
3+
[alembic]
4+
# path to migration scripts.
5+
# this is typically a path given in POSIX (e.g. forward slashes)
6+
# format, relative to the token %(here)s which refers to the location of this
7+
# ini file
8+
script_location = %(here)s/src/alembic
9+
10+
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
11+
# Uncomment the line below if you want the files to be prepended with date and time
12+
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
13+
# for all available tokens
14+
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
15+
16+
# sys.path path, will be prepended to sys.path if present.
17+
# defaults to the current working directory. for multiple paths, the path separator
18+
# is defined by "path_separator" below.
19+
prepend_sys_path = src
20+
21+
22+
# timezone to use when rendering the date within the migration file
23+
# as well as the filename.
24+
# If specified, requires the tzdata library which can be installed by adding
25+
# `alembic[tz]` to the pip requirements.
26+
# string value is passed to ZoneInfo()
27+
# leave blank for localtime
28+
# timezone =
29+
30+
# max length of characters to apply to the "slug" field
31+
# truncate_slug_length = 40
32+
33+
# set to 'true' to run the environment during
34+
# the 'revision' command, regardless of autogenerate
35+
# revision_environment = false
36+
37+
# set to 'true' to allow .pyc and .pyo files without
38+
# a source .py file to be detected as revisions in the
39+
# versions/ directory
40+
# sourceless = false
41+
42+
# version location specification; This defaults
43+
# to <script_location>/versions. When using multiple version
44+
# directories, initial revisions must be specified with --version-path.
45+
# The path separator used here should be the separator specified by "path_separator"
46+
# below.
47+
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
48+
49+
# path_separator; This indicates what character is used to split lists of file
50+
# paths, including version_locations and prepend_sys_path within configparser
51+
# files such as alembic.ini.
52+
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
53+
# to provide os-dependent path splitting.
54+
#
55+
# Note that in order to support legacy alembic.ini files, this default does NOT
56+
# take place if path_separator is not present in alembic.ini. If this
57+
# option is omitted entirely, fallback logic is as follows:
58+
#
59+
# 1. Parsing of the version_locations option falls back to using the legacy
60+
# "version_path_separator" key, which if absent then falls back to the legacy
61+
# behavior of splitting on spaces and/or commas.
62+
# 2. Parsing of the prepend_sys_path option falls back to the legacy
63+
# behavior of splitting on spaces, commas, or colons.
64+
#
65+
# Valid values for path_separator are:
66+
#
67+
# path_separator = :
68+
# path_separator = ;
69+
# path_separator = space
70+
# path_separator = newline
71+
#
72+
# Use os.pathsep. Default configuration used for new projects.
73+
path_separator = os
74+
75+
# set to 'true' to search source files recursively
76+
# in each "version_locations" directory
77+
# new in Alembic version 1.10
78+
# recursive_version_locations = false
79+
80+
# the output encoding used when revision files
81+
# are written from script.py.mako
82+
# output_encoding = utf-8
83+
84+
# database URL. This is consumed by the user-maintained env.py script only.
85+
# other means of configuring database URLs may be customized within the env.py
86+
# file.
87+
sqlalchemy.url = sqlite:///data/data.db
88+
89+
90+
[post_write_hooks]
91+
# post_write_hooks defines scripts or Python functions that are run
92+
# on newly generated revision scripts. See the documentation for further
93+
# detail and examples
94+
95+
# format using "black" - use the console_scripts runner, against the "black" entrypoint
96+
# hooks = black
97+
# black.type = console_scripts
98+
# black.entrypoint = black
99+
# black.options = -l 79 REVISION_SCRIPT_FILENAME
100+
101+
# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
102+
# hooks = ruff
103+
# ruff.type = module
104+
# ruff.module = ruff
105+
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
106+
107+
# Alternatively, use the exec runner to execute a binary found on your PATH
108+
# hooks = ruff
109+
# ruff.type = exec
110+
# ruff.executable = ruff
111+
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
112+
113+
# Logging configuration. This is also consumed by the user-maintained
114+
# env.py script only.
115+
[loggers]
116+
keys = root,sqlalchemy,alembic
117+
118+
[handlers]
119+
keys = console
120+
121+
[formatters]
122+
keys = generic
123+
124+
[logger_root]
125+
level = WARNING
126+
handlers = console
127+
qualname =
128+
129+
[logger_sqlalchemy]
130+
level = WARNING
131+
handlers =
132+
qualname = sqlalchemy.engine
133+
134+
[logger_alembic]
135+
level = INFO
136+
handlers =
137+
qualname = alembic
138+
139+
[handler_console]
140+
class = StreamHandler
141+
args = (sys.stderr,)
142+
level = NOTSET
143+
formatter = generic
144+
145+
[formatter_generic]
146+
format = %(levelname)-5.5s [%(name)s] %(message)s
147+
datefmt = %H:%M:%S

backend/pyproject.toml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ description = "Add your description here"
55
readme = "README.md"
66
requires-python = ">=3.11"
77
dependencies = [
8+
"aiolimiter>=1.2.1",
9+
"alembic>=1.17.2",
810
"bcrypt==4.2.0",
911
"beautifulsoup4>=4.13.4",
1012
"bencode-py>=4.0.0",
@@ -14,6 +16,7 @@ dependencies = [
1416
"jinja2>=3.1.6",
1517
"packaging>=25.0",
1618
"passlib>=1.7.4",
19+
"pydantic-settings>=2.0.0",
1720
"python-jose>=3.5.0",
1821
"python-multipart>=0.0.20",
1922
"sqlmodel>=0.0.24",

backend/src/alembic/README

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Generic single-database configuration.

backend/src/alembic/env.py

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import sys
2+
from pathlib import Path
3+
4+
from alembic import context
5+
from sqlalchemy import engine_from_config, pool
6+
from sqlmodel import SQLModel
7+
8+
# 添加路径
9+
src_dir = Path(__file__).resolve().parent.parent # backend/src
10+
sys.path.insert(0, str(src_dir))
11+
12+
# 导入所有模型(确保它们被注册到 metadata)
13+
from models import Bangumi, RSSItem, Torrent, User
14+
15+
# Alembic Config 对象
16+
config = context.config
17+
18+
# 设置日志 - 注意:在程序内部调用时不要重置日志配置
19+
# fileConfig 会破坏 uvicorn 的日志处理器,导致启动失败
20+
# if config.config_file_name is not None:
21+
# fileConfig(config.config_file_name)
22+
23+
# SQLModel 的 metadata
24+
target_metadata = SQLModel.metadata
25+
26+
27+
def run_migrations_offline() -> None:
28+
"""离线模式运行迁移"""
29+
url = config.get_main_option("sqlalchemy.url")
30+
context.configure(
31+
url=url,
32+
target_metadata=target_metadata,
33+
literal_binds=True,
34+
dialect_opts={"paramstyle": "named"},
35+
render_as_batch=True, # SQLite 必需
36+
)
37+
38+
with context.begin_transaction():
39+
context.run_migrations()
40+
41+
42+
def run_migrations_online() -> None:
43+
"""在线模式运行迁移"""
44+
# 创建 data 目录
45+
data_dir = src_dir.parent / "data" # backend/data
46+
data_dir.mkdir(exist_ok=True)
47+
48+
connectable = engine_from_config(
49+
config.get_section(config.config_ini_section, {}),
50+
prefix="sqlalchemy.",
51+
poolclass=pool.NullPool,
52+
)
53+
54+
with connectable.connect() as connection:
55+
context.configure(
56+
connection=connection,
57+
target_metadata=target_metadata,
58+
render_as_batch=True, # SQLite 必需
59+
compare_type=True,
60+
)
61+
62+
with context.begin_transaction():
63+
context.run_migrations()
64+
65+
66+
if context.is_offline_mode():
67+
run_migrations_offline()
68+
else:
69+
run_migrations_online()

0 commit comments

Comments
 (0)