refactor: 统一 sqlmodel_ext 用法至官方推荐模式
Some checks failed
Test / test (push) Failing after 3m47s

- 替换 Field(max_length=X) 为 StrX/TextX 类型别名(21 个 sqlmodels 文件)
- 替换 get + 404 检查为 get_exist_one()(17 个路由文件,约 50 处)
- 替换 save + session.refresh 为 save(load=...)
- 替换 session.add + commit 为 save()(dav/provider.py)
- 更新所有依赖至最新版本

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-09 11:13:16 +08:00
parent 9185f26b83
commit 6c96c43bea
57 changed files with 1091 additions and 761 deletions

View File

@@ -16,6 +16,12 @@ from sqlmodels.setting import (
from sqlmodels.setting import SettingsType
from utils import http_exceptions
from utils.conf import appmeta
try:
from ee.service import get_cached_license
except ImportError:
get_cached_license = None
from .file import admin_file_router
from .file_app import admin_file_app_router
from .group import admin_group_router
@@ -24,7 +30,6 @@ from .share import admin_share_router
from .task import admin_task_router
from .user import admin_user_router
from .theme import admin_theme_router
from .vas import admin_vas_router
class Aria2TestRequest(SQLModelBase):
@@ -50,7 +55,6 @@ admin_router.include_router(admin_policy_router)
admin_router.include_router(admin_share_router)
admin_router.include_router(admin_task_router)
admin_router.include_router(admin_theme_router)
admin_router.include_router(admin_vas_router)
# 离线下载 /api/admin/aria2
admin_aria2_router = APIRouter(
@@ -159,14 +163,24 @@ async def router_admin_get_summary(session: SessionDep) -> AdminSummaryResponse:
if site_url_setting and site_url_setting.value:
site_urls.append(site_url_setting.value)
# 许可证信息(从设置读取或使用默认值
license_info = LicenseInfo(
expired_at=now + timedelta(days=365),
signed_at=now,
root_domains=[],
domains=[],
vol_domains=[],
)
# 许可证信息(Pro 版本从缓存读取CE 版本永不过期
if appmeta.IsPro and get_cached_license:
payload = get_cached_license()
license_info = LicenseInfo(
expired_at=payload.expires_at,
signed_at=payload.issued_at,
root_domains=[],
domains=[payload.domain],
vol_domains=[],
)
else:
license_info = LicenseInfo(
expired_at=datetime.max,
signed_at=now,
root_domains=[],
domains=[],
vol_domains=[],
)
# 版本信息
version_info = VersionInfo(
@@ -225,11 +239,11 @@ async def router_admin_update_settings(
if existing:
existing.value = item.value
await existing.save(session)
existing = await existing.save(session)
updated_count += 1
else:
new_setting = Setting(type=item.type, name=item.name, value=item.value)
await new_setting.save(session)
new_setting = await new_setting.save(session)
created_count += 1
l.info(f"管理员更新了 {updated_count} 个设置项,新建了 {created_count} 个设置项")

View File

@@ -54,7 +54,7 @@ async def _set_ban_recursive(
obj.banned_by = None
obj.ban_reason = None
await obj.save(session)
obj = await obj.save(session)
count += 1
return count
@@ -131,9 +131,7 @@ async def router_admin_preview_file(
:param file_id: 文件UUID
:return: 文件内容
"""
file_obj = await Object.get(session, Object.id == file_id)
if not file_obj:
raise HTTPException(status_code=404, detail="文件不存在")
file_obj = await Object.get_exist_one(session, file_id)
if not file_obj.is_file:
raise HTTPException(status_code=400, detail="对象不是文件")
@@ -182,9 +180,7 @@ async def router_admin_ban_file(
:param claims: 当前管理员 JWT claims
:return: 封禁结果
"""
file_obj = await Object.get(session, Object.id == file_id)
if not file_obj:
raise HTTPException(status_code=404, detail="文件不存在")
file_obj = await Object.get_exist_one(session, file_id)
count = await _set_ban_recursive(session, file_obj, request.ban, claims.sub, request.reason)
@@ -212,9 +208,7 @@ async def router_admin_delete_file(
:param delete_physical: 是否同时删除物理文件
:return: 删除结果
"""
file_obj = await Object.get(session, Object.id == file_id)
if not file_obj:
raise HTTPException(status_code=404, detail="文件不存在")
file_obj = await Object.get_exist_one(session, file_id)
if not file_obj.is_file:
raise HTTPException(status_code=400, detail="对象不是文件")

View File

@@ -151,9 +151,7 @@ async def get_file_app(
错误处理:
- 404: 应用不存在
"""
app: FileApp | None = await FileApp.get(session, FileApp.id == app_id)
if not app:
http_exceptions.raise_not_found("应用不存在")
app = await FileApp.get_exist_one(session, app_id)
extensions = await FileAppExtension.get(
session,
@@ -186,9 +184,7 @@ async def update_file_app(
- 404: 应用不存在
- 409: 新 app_key 已被其他应用使用
"""
app: FileApp | None = await FileApp.get(session, FileApp.id == app_id)
if not app:
http_exceptions.raise_not_found("应用不存在")
app = await FileApp.get_exist_one(session, app_id)
# 检查 app_key 唯一性
if request.app_key is not None and request.app_key != app.app_key:
@@ -235,9 +231,7 @@ async def delete_file_app(
错误处理:
- 404: 应用不存在
"""
app: FileApp | None = await FileApp.get(session, FileApp.id == app_id)
if not app:
http_exceptions.raise_not_found("应用不存在")
app = await FileApp.get_exist_one(session, app_id)
app_name = app.app_key
await FileApp.delete(session, app)
@@ -263,9 +257,7 @@ async def update_extensions(
错误处理:
- 404: 应用不存在
"""
app: FileApp | None = await FileApp.get(session, FileApp.id == app_id)
if not app:
http_exceptions.raise_not_found("应用不存在")
app = await FileApp.get_exist_one(session, app_id)
# 保留旧扩展名的 wopi_action_urlDiscovery 填充的值)
old_extensions: list[FileAppExtension] = await FileAppExtension.get(
@@ -330,9 +322,7 @@ async def update_group_access(
错误处理:
- 404: 应用不存在
"""
app: FileApp | None = await FileApp.get(session, FileApp.id == app_id)
if not app:
http_exceptions.raise_not_found("应用不存在")
await FileApp.get_exist_one(session, app_id)
# 删除旧的用户组关联
old_links_result = await session.exec(
@@ -387,9 +377,7 @@ async def discover_wopi(
- 400: 非 WOPI 类型 / discovery URL 未配置 / XML 解析失败
- 502: WOPI 服务端不可达或返回无效响应
"""
app: FileApp | None = await FileApp.get(session, FileApp.id == app_id)
if not app:
http_exceptions.raise_not_found("应用不存在")
app = await FileApp.get_exist_one(session, app_id)
if app.type != FileAppType.WOPI:
http_exceptions.raise_bad_request("仅 WOPI 类型应用支持自动发现")

View File

@@ -63,10 +63,7 @@ async def router_admin_get_group(
:param group_id: 用户组UUID
:return: 用户组详情
"""
group = await Group.get(session, Group.id == group_id, load=[Group.options, Group.policies])
if not group:
raise HTTPException(status_code=404, detail="用户组不存在")
group = await Group.get_exist_one(session, group_id, load=[Group.options, Group.policies])
# 直接访问已加载的关系,无需额外查询
policies = group.policies
@@ -94,9 +91,7 @@ async def router_admin_get_group_members(
:return: 分页成员列表
"""
# 验证组存在
group = await Group.get(session, Group.id == group_id)
if not group:
raise HTTPException(status_code=404, detail="用户组不存在")
await Group.get_exist_one(session, group_id)
result = await User.get_with_count(session, User.group_id == group_id, table_view=table_view)
@@ -138,10 +133,11 @@ async def router_admin_create_group(
speed_limit=request.speed_limit,
)
group = await group.save(session)
group_id_val: UUID = group.id
# 创建选项
options = GroupOptions(
group_id=group.id,
group_id=group_id_val,
share_download=request.share_download,
share_free=request.share_free,
relocate=request.relocate,
@@ -154,11 +150,11 @@ async def router_admin_create_group(
aria2=request.aria2,
redirected_source=request.redirected_source,
)
await options.save(session)
options = await options.save(session)
# 关联存储策略
for policy_id in request.policy_ids:
link = GroupPolicyLink(group_id=group.id, policy_id=policy_id)
link = GroupPolicyLink(group_id=group_id_val, policy_id=policy_id)
session.add(link)
await session.commit()
@@ -185,9 +181,7 @@ async def router_admin_update_group(
:param request: 更新请求
:return: 更新结果
"""
group = await Group.get(session, Group.id == group_id, load=Group.options)
if not group:
raise HTTPException(status_code=404, detail="用户组不存在")
group = await Group.get_exist_one(session, group_id, load=Group.options)
# 检查名称唯一性(如果要更新名称)
if request.name and request.name != group.name:
@@ -217,7 +211,7 @@ async def router_admin_update_group(
if options_data:
for key, value in options_data.items():
setattr(group.options, key, value)
await group.options.save(session)
group.options = await group.options.save(session)
# 更新策略关联
if request.policy_ids is not None:
@@ -255,9 +249,7 @@ async def router_admin_delete_group(
:param group_id: 用户组UUID
:return: 删除结果
"""
group = await Group.get(session, Group.id == group_id)
if not group:
raise HTTPException(status_code=404, detail="用户组不存在")
group = await Group.get_exist_one(session, group_id)
# 检查是否有用户属于该组
user_count = await User.count(session, User.group_id == group_id)

View File

@@ -332,7 +332,7 @@ async def router_policy_add_policy(
s3_path_style=request.s3_path_style,
s3_region=request.s3_region,
)
await options.save(session)
options = await options.save(session)
@admin_policy_router.post(
path='/cors',
@@ -383,9 +383,7 @@ async def router_policy_onddrive_oauth(
:param policy_id: 存储策略UUID
:return: OAuth URL
"""
policy = await Policy.get(session, Policy.id == policy_id)
if not policy:
raise HTTPException(status_code=404, detail="存储策略不存在")
policy = await Policy.get_exist_one(session, policy_id)
# TODO: 实现OneDrive OAuth
raise HTTPException(status_code=501, detail="OneDrive OAuth暂未实现")
@@ -408,9 +406,7 @@ async def router_policy_get_policy(
:param policy_id: 存储策略UUID
:return: 策略详情
"""
policy = await Policy.get(session, Policy.id == policy_id, load=Policy.options)
if not policy:
raise HTTPException(status_code=404, detail="存储策略不存在")
policy = await Policy.get_exist_one(session, policy_id, load=Policy.options)
# 获取使用此策略的用户组
groups = await policy.awaitable_attrs.groups
@@ -459,9 +455,7 @@ async def router_policy_delete_policy(
:param policy_id: 存储策略UUID
:return: 删除结果
"""
policy = await Policy.get(session, Policy.id == policy_id)
if not policy:
raise HTTPException(status_code=404, detail="存储策略不存在")
policy = await Policy.get_exist_one(session, policy_id)
# 检查是否有文件使用此策略
file_count = await Object.count(session, Object.policy_id == policy_id)
@@ -503,9 +497,7 @@ async def router_policy_update_policy(
:param policy_id: 存储策略UUID
:param request: 更新请求
"""
policy = await Policy.get(session, Policy.id == policy_id, load=Policy.options)
if not policy:
raise HTTPException(status_code=404, detail="存储策略不存在")
policy = await Policy.get_exist_one(session, policy_id, load=Policy.options)
# 检查名称唯一性(如果要更新名称)
if request.name and request.name != policy.name:
@@ -529,10 +521,10 @@ async def router_policy_update_policy(
if policy.options:
for key, value in options_data.items():
setattr(policy.options, key, value)
await policy.options.save(session)
policy.options = await policy.options.save(session)
else:
options = PolicyOptions(policy_id=policy.id, **options_data)
await options.save(session)
options = await options.save(session)
l.info(f"管理员更新了存储策略: {policy_id}")

View File

@@ -155,9 +155,7 @@ async def router_admin_delete_share(
:param share_id: 分享ID
:return: 删除结果
"""
share = await Share.get(session, Share.id == share_id)
if not share:
raise HTTPException(status_code=404, detail="分享不存在")
share = await Share.get_exist_one(session, share_id)
await Share.delete(session, share)

View File

@@ -150,9 +150,7 @@ async def router_admin_delete_task(
:param task_id: 任务ID
:return: 删除结果
"""
task = await Task.get(session, Task.id == task_id)
if not task:
raise HTTPException(status_code=404, detail="任务不存在")
task = await Task.get_exist_one(session, task_id)
await Task.delete(session, task)

View File

@@ -71,7 +71,7 @@ async def router_admin_theme_create(
name=request.name,
**request.colors.model_dump(),
)
await preset.save(session)
preset = await preset.save(session)
l.info(f"管理员创建了主题预设: {request.name}")
@@ -101,11 +101,7 @@ async def router_admin_theme_update(
- 404: 预设不存在
- 409: 名称已被其他预设使用
"""
preset: ThemePreset | None = await ThemePreset.get(
session, ThemePreset.id == preset_id
)
if not preset:
http_exceptions.raise_not_found("主题预设不存在")
preset = await ThemePreset.get_exist_one(session, preset_id)
# 检查名称唯一性(排除自身)
if request.name is not None and request.name != preset.name:
@@ -120,7 +116,7 @@ async def router_admin_theme_update(
for key, value in color_data.items():
setattr(preset, key, value)
await preset.save(session)
preset = await preset.save(session)
l.info(f"管理员更新了主题预设: {preset.name}")
@@ -147,11 +143,7 @@ async def router_admin_theme_delete(
副作用:
- 关联用户的 theme_preset_id 会被数据库 SET NULL
"""
preset: ThemePreset | None = await ThemePreset.get(
session, ThemePreset.id == preset_id
)
if not preset:
http_exceptions.raise_not_found("主题预设不存在")
preset = await ThemePreset.get_exist_one(session, preset_id)
await preset.delete(session)
l.info(f"管理员删除了主题预设: {preset.name}")
@@ -180,11 +172,7 @@ async def router_admin_theme_set_default(
逻辑:
- 事务中先清除所有旧默认,再设新默认
"""
preset: ThemePreset | None = await ThemePreset.get(
session, ThemePreset.id == preset_id
)
if not preset:
http_exceptions.raise_not_found("主题预设不存在")
preset = await ThemePreset.get_exist_one(session, preset_id)
# 清除所有旧默认
await session.execute(
@@ -195,5 +183,5 @@ async def router_admin_theme_set_default(
# 设新默认
preset.is_default = True
await preset.save(session)
preset = await preset.save(session)
l.info(f"管理员将主题预设 '{preset.name}' 设为默认")

View File

@@ -128,8 +128,9 @@ async def router_admin_create_user(
is_verified=True,
user_id=user.id,
)
await identity.save(session)
identity = await identity.save(session)
user = await User.get(session, User.id == user.id, load=User.group)
return user.to_public()
@@ -153,9 +154,7 @@ async def router_admin_update_user(
:param request: 更新请求
:return: 更新结果
"""
user = await User.get(session, User.id == user_id)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
user = await User.get_exist_one(session, user_id)
# 默认管理员不允许更改用户组(通过 Setting 中的 default_admin_id 识别)
default_admin_setting = await Setting.get(
@@ -252,9 +251,7 @@ async def router_admin_calibrate_storage(
:param user_id: 用户UUID
:return: 校准结果
"""
user = await User.get(session, User.id == user_id)
if not user:
raise HTTPException(status_code=404, detail="用户不存在")
user = await User.get_exist_one(session, user_id)
previous_storage = user.storage

View File

@@ -1,81 +0,0 @@
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException
from middleware.auth import admin_required
from middleware.dependencies import SessionDep
from sqlmodels import (
ResponseBase,
)
admin_vas_router = APIRouter(
prefix='/vas',
tags=['admin', 'admin_vas']
)
@admin_vas_router.get(
path='/list',
summary='获取增值服务列表',
description='Get VAS list (orders and storage packs)',
dependencies=[Depends(admin_required)]
)
async def router_admin_get_vas_list(
session: SessionDep,
user_id: UUID | None = None,
page: int = 1,
page_size: int = 20,
) -> ResponseBase:
"""
获取增值服务列表(订单和存储包)。
:param session: 数据库会话
:param user_id: 按用户筛选
:param page: 页码
:param page_size: 每页数量
:return: 增值服务列表
"""
# TODO: 实现增值服务列表
# 需要查询 Order 和 StoragePack 模型
raise HTTPException(status_code=501, detail="增值服务管理暂未实现")
@admin_vas_router.get(
path='/{vas_id}',
summary='获取增值服务详情',
description='Get VAS detail by ID',
dependencies=[Depends(admin_required)]
)
async def router_admin_get_vas(
session: SessionDep,
vas_id: UUID,
) -> ResponseBase:
"""
获取增值服务详情。
:param session: 数据库会话
:param vas_id: 增值服务UUID
:return: 增值服务详情
"""
# TODO: 实现增值服务详情
raise HTTPException(status_code=501, detail="增值服务管理暂未实现")
@admin_vas_router.delete(
path='/{vas_id}',
summary='删除增值服务',
description='Delete VAS by ID',
dependencies=[Depends(admin_required)]
)
async def router_admin_delete_vas(
session: SessionDep,
vas_id: UUID,
) -> ResponseBase:
"""
删除增值服务。
:param session: 数据库会话
:param vas_id: 增值服务UUID
:return: 删除结果
"""
# TODO: 实现增值服务删除
raise HTTPException(status_code=501, detail="增值服务管理暂未实现")