fix(database): keep migrations in alembic transactions (#6400)

This commit is contained in:
InfinityPacer
2026-08-23 00:27:55 +08:00
committed by GitHub
parent a59f1b928a
commit 176d9255e5
16 changed files with 659 additions and 154 deletions
+23 -6
View File
@@ -6,8 +6,8 @@ Create Date: 2024-09-11 08:07:02.753307
"""
from app.db.oper.systemconfig import SystemConfigOper
from app.schemas.types import SystemConfigKey
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '262735d025da'
@@ -19,9 +19,18 @@ depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 初始化消息通知范围
_systemconfig = SystemConfigOper()
if not _systemconfig.get(SystemConfigKey.NotificationSwitchs):
_systemconfig.set(SystemConfigKey.NotificationSwitchs, [
systemconfig = sa.table(
"systemconfig",
sa.column("key", sa.String()),
sa.column("value", sa.JSON()),
)
connection = op.get_bind()
key = "NotificationSwitchs"
row = connection.execute(
sa.select(systemconfig.c.value).where(systemconfig.c.key == key)
).first()
if not row or not row[0]:
value = [
{
'type': '资源下载',
'action': 'all',
@@ -54,7 +63,15 @@ def upgrade() -> None:
'type': '其它',
'action': 'admin',
},
])
]
if row:
connection.execute(
systemconfig.update().where(systemconfig.c.key == key).values(
value=value
)
)
else:
connection.execute(systemconfig.insert().values(key=key, value=value))
# ### end Alembic commands ###
+79 -43
View File
@@ -8,13 +8,11 @@ Create Date: 2024-07-20 08:43:40.741251
import secrets
from alembic import op
import sqlalchemy as sa
from app.runtime.config import settings
from app.application.security.token import get_password_hash
from app.db import SessionFactory
from app.db.models import *
from app.db.oper.systemconfig import SystemConfigOper
from app.runtime.log import logger
from app.schemas.types import SystemConfigKey
# revision identifiers, used by Alembic.
revision = '294b007932ef'
@@ -27,50 +25,88 @@ def upgrade() -> None:
"""
v2.0.0 数据库初始化
"""
with SessionFactory() as db:
# 初始化超级管理员
_user = User.get_by_name(db=db, name=settings.SUPERUSER)
if not _user:
if settings.SUPERUSER_PASSWORD:
init_password = settings.SUPERUSER_PASSWORD
else:
# 生成随机密码
init_password = secrets.token_urlsafe(16)
logger.info(
f"【超级管理员初始密码】{init_password} 请登录系统后在设定中修改。 注:该密码只会显示一次,请注意保存。")
_user = User(
connection = op.get_bind()
user = sa.table(
"user",
sa.column("name", sa.String()),
sa.column("email", sa.String()),
sa.column("hashed_password", sa.String()),
sa.column("is_active", sa.Boolean()),
sa.column("is_superuser", sa.Boolean()),
sa.column("avatar", sa.String()),
sa.column("is_otp", sa.Boolean()),
sa.column("otp_secret", sa.String()),
sa.column("permissions", sa.JSON()),
sa.column("settings", sa.JSON()),
)
# 初始化超级管理员
existing_user = connection.execute(
sa.select(user.c.name).where(user.c.name == settings.SUPERUSER)
).first()
if not existing_user:
if settings.SUPERUSER_PASSWORD:
init_password = settings.SUPERUSER_PASSWORD
else:
# 生成随机密码
init_password = secrets.token_urlsafe(16)
logger.info(
f"【超级管理员初始密码】{init_password} 请登录系统后在设定中修改。 注:该密码只会显示一次,请注意保存。")
connection.execute(
user.insert().values(
name=settings.SUPERUSER,
hashed_password=get_password_hash(init_password),
email="admin@movie-pilot.org",
is_active=True,
is_superuser=True,
avatar=""
avatar="",
is_otp=False,
otp_secret=None,
permissions={},
settings={},
)
_user.create(db)
# 初始化本地存储
_systemconfig = SystemConfigOper()
if not _systemconfig.get(SystemConfigKey.Storages):
_systemconfig.set(SystemConfigKey.Storages, [
{
"type": "local",
"name": "本地",
"config": {}
},
{
"type": "alipan",
"name": "阿里云盘",
"config": {}
},
{
"type": "u115",
"name": "115网盘",
"config": {}
},
{
"type": "rclone",
"name": "RClone",
"config": {}
}
])
)
# 初始化本地存储
systemconfig = sa.table(
"systemconfig",
sa.column("key", sa.String()),
sa.column("value", sa.JSON()),
)
key = "Storages"
row = connection.execute(
sa.select(systemconfig.c.value).where(systemconfig.c.key == key)
).first()
if not row or not row[0]:
value = [
{
"type": "local",
"name": "本地",
"config": {}
},
{
"type": "alipan",
"name": "阿里云盘",
"config": {}
},
{
"type": "u115",
"name": "115网盘",
"config": {}
},
{
"type": "rclone",
"name": "RClone",
"config": {}
}
]
if row:
connection.execute(
systemconfig.update().where(systemconfig.c.key == key).values(
value=value
)
)
else:
connection.execute(systemconfig.insert().values(key=key, value=value))
def downgrade() -> None:
+22 -6
View File
@@ -7,10 +7,9 @@ Create Date: 2025-06-28 08:40:14.516836
"""
from alembic import op
import sqlalchemy as sa
from sqlalchemy.dialects import sqlite
from app.db.oper.systemconfig import SystemConfigOper
from app.schemas.types import SystemConfigKey
# revision identifiers, used by Alembic.
revision = '3891a5e722a1'
@@ -22,14 +21,31 @@ depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# rename AList存储
_systemconfig = SystemConfigOper()
_storages = _systemconfig.get(SystemConfigKey.Storages)
systemconfig = sa.table(
"systemconfig",
sa.column("key", sa.String()),
sa.column("value", sa.JSON()),
)
connection = op.get_bind()
key = "Storages"
row = connection.execute(
sa.select(systemconfig.c.value).where(systemconfig.c.key == key)
).first()
_storages = row[0] if row else None
if _storages:
changed = False
for storage in _storages:
if storage["type"] == "alist":
storage["name"] = "OpenList"
if storage.get("name") != "OpenList":
storage["name"] = "OpenList"
changed = True
break
_systemconfig.set(SystemConfigKey.Storages, _storages)
if changed:
connection.execute(
systemconfig.update().where(systemconfig.c.key == key).values(
value=_storages
)
)
# ### end Alembic commands ###
+26 -22
View File
@@ -5,10 +5,8 @@ Revises: 486e56a62dcb
Create Date: 2025-06-11 19:52:57.185355
"""
import json
from app.db import SessionFactory
from app.db.models import User
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '3df653756eec'
@@ -18,24 +16,30 @@ depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
with SessionFactory() as db:
# 所有用户
users = User.list(db)
for user in users:
if user.is_superuser:
continue
if not user.permissions:
permissions = {
"discovery": True,
"search": True,
"subscribe": True,
"manage": False,
}
user.update(db, {
"permissions": permissions,
})
# ### end Alembic commands ###
connection = op.get_bind()
user = sa.table(
"user",
sa.column("id", sa.Integer()),
sa.column("is_superuser", sa.Boolean()),
sa.column("permissions", sa.JSON()),
)
users = connection.execute(
sa.select(user.c.id, user.c.is_superuser, user.c.permissions)
).mappings().all()
permissions = {
"discovery": True,
"search": True,
"subscribe": True,
"manage": False,
}
for item in users:
if item["is_superuser"] or item["permissions"]:
continue
connection.execute(
user.update()
.where(user.c.id == item["id"])
.values(permissions=permissions)
)
def downgrade() -> None:
+22 -5
View File
@@ -7,8 +7,8 @@ Create Date: 2025-05-13 19:49:51.271319
"""
import re
from app.db.oper.systemconfig import SystemConfigOper
from app.schemas.types import SystemConfigKey
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '486e56a62dcb'
@@ -20,16 +20,33 @@ depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
### 将消息模板中的 `season`(为单数字, 且重命名需要这个字段)替换为 `season_fmt`(Sxx格式字符串) ###
_systemconfig = SystemConfigOper()
templates = _systemconfig.get(SystemConfigKey.NotificationTemplates)
systemconfig = sa.table(
"systemconfig",
sa.column("key", sa.String()),
sa.column("value", sa.JSON()),
)
connection = op.get_bind()
key = "NotificationTemplates"
row = connection.execute(
sa.select(systemconfig.c.value).where(systemconfig.c.key == key)
).first()
templates = row[0] if row else None
if isinstance(templates, dict):
_re = r'(?<={{)(?![^}]*[%|])(\s*)season(\s*)(?=}})|(?<={%)if\s+(?![^%]*[%|])season\s*(?=%)'
changed = False
for k, v in templates.items():
# 替换season为season_fmt
result = re.sub(_re, r'\1season_fmt\2', v)
if result != v:
changed = True
templates[k] = result
# 将更新后的模板存回系统配置
_systemconfig.set(SystemConfigKey.NotificationTemplates, templates)
if changed:
connection.execute(
systemconfig.update().where(systemconfig.c.key == key).values(
value=templates
)
)
# ### end Alembic commands ###
+22 -5
View File
@@ -6,9 +6,9 @@ Revises: e8b1c4d7a2f9
Create Date: 2026-08-10
"""
from app.db.oper.systemconfig import SystemConfigOper
from alembic import op
import sqlalchemy as sa
from app.runtime.log import logger
from app.schemas.types import SystemConfigKey
# revision identifiers, used by Alembic.
revision = "4dadad1d161a"
@@ -21,7 +21,17 @@ def upgrade() -> None:
# V3 为大版本升级,通知模板直接覆盖用户旧设置,且迁移只执行一次;
# 默认模板同时兼容影视与音乐(音乐的下载、入库通知补齐艺术家/专辑/音质信息)。
# 覆盖前先将用户现有模板完整输出到日志,作为备份供用户恢复参考。
old_value = SystemConfigOper().get(SystemConfigKey.NotificationTemplates)
systemconfig = sa.table(
"systemconfig",
sa.column("key", sa.String()),
sa.column("value", sa.JSON()),
)
connection = op.get_bind()
key = "NotificationTemplates"
row = connection.execute(
sa.select(systemconfig.c.value).where(systemconfig.c.key == key)
).first()
old_value = row[0] if row else None
if old_value:
logger.info(f"即将使用 V3 默认通知模板覆盖用户现有通知模板,现有模板内容备份如下:\n{old_value}")
value = {
@@ -67,8 +77,15 @@ def upgrade() -> None:
'{% if overview %}\\n简介:{{ overview }}{% endif %}'
}"""
}
SystemConfigOper().set(SystemConfigKey.NotificationTemplates, value)
if row and row[0] != value:
connection.execute(
systemconfig.update().where(systemconfig.c.key == key).values(
value=value
)
)
elif not row:
connection.execute(systemconfig.insert().values(key=key, value=value))
def downgrade() -> None:
pass
pass
+21 -5
View File
@@ -6,8 +6,8 @@ Create Date: 2025-05-03 17:29:07.635618
"""
from app.db.oper.systemconfig import SystemConfigOper
from app.schemas.types import SystemConfigKey
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '89d24811e894'
@@ -57,9 +57,25 @@ def upgrade() -> None:
'{% if overview %}\\n简介:{{ overview }}{% endif %}'
}"""
}
_systemconfig = SystemConfigOper()
if not _systemconfig.get(SystemConfigKey.NotificationTemplates):
_systemconfig.set(SystemConfigKey.NotificationTemplates, value)
systemconfig = sa.table(
"systemconfig",
sa.column("key", sa.String()),
sa.column("value", sa.JSON()),
)
connection = op.get_bind()
key = "NotificationTemplates"
row = connection.execute(
sa.select(systemconfig.c.value).where(systemconfig.c.key == key)
).first()
if not row or not row[0]:
if row:
connection.execute(
systemconfig.update().where(systemconfig.c.key == key).values(
value=value
)
)
else:
connection.execute(systemconfig.insert().values(key=key, value=value))
# ### end Alembic commands ###
+20 -8
View File
@@ -6,8 +6,8 @@ Create Date: 2024-11-14 12:49:13.838120
"""
from app.db.oper.systemconfig import SystemConfigOper
from app.schemas.types import SystemConfigKey
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a295e41830a6'
@@ -19,16 +19,28 @@ depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 初始化AList存储
_systemconfig = SystemConfigOper()
_storages = _systemconfig.get(SystemConfigKey.Storages)
if _storages:
if "alist" not in [storage["type"] for storage in _storages]:
_storages.append({
systemconfig = sa.table(
"systemconfig",
sa.column("key", sa.String()),
sa.column("value", sa.JSON()),
)
connection = op.get_bind()
key = "Storages"
row = connection.execute(
sa.select(systemconfig.c.value).where(systemconfig.c.key == key)
).first()
_storages = row[0] if row else None
if _storages and "alist" not in [storage["type"] for storage in _storages]:
_storages.append({
"type": "alist",
"name": "AList",
"config": {}
})
_systemconfig.set(SystemConfigKey.Storages, _storages)
connection.execute(
systemconfig.update().where(systemconfig.c.key == key).values(
value=_storages
)
)
# ### end Alembic commands ###
+21 -3
View File
@@ -6,8 +6,8 @@ Create Date: 2024-10-16 15:05:01.775429
"""
from app.db.oper.systemconfig import SystemConfigOper
from app.schemas.types import SystemConfigKey
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = 'a73f2dbf5c09'
@@ -19,7 +19,25 @@ depends_on = None
def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
# 初始化下载优先规则
SystemConfigOper().set(SystemConfigKey.TorrentsPriority, ["torrent", "upload", "seeder"])
systemconfig = sa.table(
"systemconfig",
sa.column("key", sa.String()),
sa.column("value", sa.JSON()),
)
connection = op.get_bind()
key = "TorrentsPriority"
value = ["torrent", "upload", "seeder"]
row = connection.execute(
sa.select(systemconfig.c.value).where(systemconfig.c.key == key)
).first()
if row and row[0] != value:
connection.execute(
systemconfig.update().where(systemconfig.c.key == key).values(
value=value
)
)
elif not row:
connection.execute(systemconfig.insert().values(key=key, value=value))
# ### end Alembic commands ###
+1 -6
View File
@@ -5,14 +5,10 @@ Revises: 0fb94bf69b38
Create Date: 2024-10-09 13:44:13.926529
"""
import contextlib
from alembic import op
import sqlalchemy as sa
from app.runtime.log import logger
from app.db import SessionFactory
from app.db.models import UserConfig
# revision identifiers, used by Alembic.
revision = 'e2dbe1421fa4'
@@ -72,8 +68,7 @@ def upgrade() -> None:
except Exception as e:
logger.error(f"Could not alter column {column_name} in table {table}: {e}")
with SessionFactory() as db:
UserConfig.truncate(db)
conn.execute(sa.delete(sa.table("userconfig")))
def downgrade() -> None:
+19 -9
View File
@@ -63,9 +63,6 @@ def upgrade() -> None:
])
# 只升级系统旧默认模板;用户编辑过的模板保持原样。
from app.db.oper.systemconfig import SystemConfigOper
from app.schemas.types import SystemConfigKey
legacy_organize = """
{
'title': '{{ title_year }}'
@@ -123,18 +120,31 @@ def upgrade() -> None:
'{% if labels %}\\n标签:{{ labels }}{% endif %}'
'{% if description %}\\n描述:{{ description }}{% endif %}'
}"""
config_oper = SystemConfigOper()
templates = dict(config_oper.get(SystemConfigKey.NotificationTemplates) or {})
systemconfig = sa.table(
"systemconfig",
sa.column("key", sa.String()),
sa.column("value", sa.JSON()),
)
connection = op.get_bind()
config_key = "NotificationTemplates"
row = connection.execute(
sa.select(systemconfig.c.value).where(systemconfig.c.key == config_key)
).first()
templates = dict(row[0] or {}) if row else {}
changed = False
for key, legacy, replacement in (
for template_key, legacy, replacement in (
("organizeSuccess", legacy_organize, music_organize),
("downloadAdded", legacy_download, music_download),
):
if str(templates.get(key) or "").strip() == legacy.strip():
templates[key] = replacement
if str(templates.get(template_key) or "").strip() == legacy.strip():
templates[template_key] = replacement
changed = True
if changed:
config_oper.set(SystemConfigKey.NotificationTemplates, templates)
connection.execute(
systemconfig.update().where(systemconfig.c.key == config_key).values(
value=templates
)
)
def downgrade() -> None: