- 替换 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:
@@ -5,6 +5,7 @@ from utils.conf import appmeta
|
||||
from .admin import admin_router
|
||||
|
||||
from .callback import callback_router
|
||||
from .category import category_router
|
||||
from .directory import directory_router
|
||||
from .download import download_router
|
||||
from .file import router as file_router
|
||||
@@ -14,7 +15,6 @@ from .trash import trash_router
|
||||
from .site import site_router
|
||||
from .slave import slave_router
|
||||
from .user import user_router
|
||||
from .vas import vas_router
|
||||
from .webdav import webdav_router
|
||||
|
||||
router = APIRouter(prefix="/v1")
|
||||
@@ -24,6 +24,7 @@ router = APIRouter(prefix="/v1")
|
||||
if appmeta.mode == "master":
|
||||
router.include_router(admin_router)
|
||||
router.include_router(callback_router)
|
||||
router.include_router(category_router)
|
||||
router.include_router(directory_router)
|
||||
router.include_router(download_router)
|
||||
router.include_router(file_router)
|
||||
@@ -32,7 +33,6 @@ if appmeta.mode == "master":
|
||||
router.include_router(site_router)
|
||||
router.include_router(trash_router)
|
||||
router.include_router(user_router)
|
||||
router.include_router(vas_router)
|
||||
router.include_router(webdav_router)
|
||||
elif appmeta.mode == "slave":
|
||||
router.include_router(slave_router)
|
||||
|
||||
@@ -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} 个设置项")
|
||||
|
||||
@@ -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="对象不是文件")
|
||||
|
||||
@@ -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_url(Discovery 填充的值)
|
||||
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 类型应用支持自动发现")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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}")
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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}' 设为默认")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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="增值服务管理暂未实现")
|
||||
@@ -16,18 +16,12 @@ oauth_router = APIRouter(
|
||||
tags=["callback", "oauth"],
|
||||
)
|
||||
|
||||
pay_router = APIRouter(
|
||||
prefix='/callback/pay',
|
||||
tags=["callback", "pay"],
|
||||
)
|
||||
|
||||
upload_router = APIRouter(
|
||||
prefix='/callback/upload',
|
||||
tags=["callback", "upload"],
|
||||
)
|
||||
|
||||
callback_router.include_router(oauth_router)
|
||||
callback_router.include_router(pay_router)
|
||||
callback_router.include_router(upload_router)
|
||||
|
||||
@oauth_router.post(
|
||||
@@ -38,7 +32,7 @@ callback_router.include_router(upload_router)
|
||||
def router_callback_qq() -> ResponseBase:
|
||||
"""
|
||||
Handle QQ OAuth callback and return user information.
|
||||
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the QQ OAuth callback.
|
||||
"""
|
||||
@@ -55,11 +49,11 @@ async def router_callback_github(
|
||||
GitHub OAuth 回调处理
|
||||
- 错误响应示例:
|
||||
- {
|
||||
'error': 'bad_verification_code',
|
||||
'error_description': 'The code passed is incorrect or expired.',
|
||||
'error': 'bad_verification_code',
|
||||
'error_description': 'The code passed is incorrect or expired.',
|
||||
'error_uri': 'https://docs.github.com/apps/managing-oauth-apps/troubleshooting-oauth-app-access-token-request-errors/#bad-verification-code'
|
||||
}
|
||||
|
||||
|
||||
Returns:
|
||||
PlainTextResponse: A response containing the user information from GitHub.
|
||||
"""
|
||||
@@ -77,81 +71,6 @@ async def router_callback_github(
|
||||
l.error(f"GitHub OAuth 回调异常: {e}")
|
||||
return PlainTextResponse("认证过程中发生错误,请重试", status_code=500)
|
||||
|
||||
@pay_router.post(
|
||||
path='/alipay',
|
||||
summary='支付宝支付回调',
|
||||
description='Handle Alipay payment callback and return payment status.',
|
||||
)
|
||||
def router_callback_alipay() -> ResponseBase:
|
||||
"""
|
||||
Handle Alipay payment callback and return payment status.
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the Alipay payment callback.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
@pay_router.post(
|
||||
path='/wechat',
|
||||
summary='微信支付回调',
|
||||
description='Handle WeChat Pay payment callback and return payment status.',
|
||||
)
|
||||
def router_callback_wechat() -> ResponseBase:
|
||||
"""
|
||||
Handle WeChat Pay payment callback and return payment status.
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the WeChat Pay payment callback.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
@pay_router.post(
|
||||
path='/stripe',
|
||||
summary='Stripe支付回调',
|
||||
description='Handle Stripe payment callback and return payment status.',
|
||||
)
|
||||
def router_callback_stripe() -> ResponseBase:
|
||||
"""
|
||||
Handle Stripe payment callback and return payment status.
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the Stripe payment callback.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
@pay_router.get(
|
||||
path='/easypay',
|
||||
summary='易支付回调',
|
||||
description='Handle EasyPay payment callback and return payment status.',
|
||||
)
|
||||
def router_callback_easypay() -> PlainTextResponse:
|
||||
"""
|
||||
Handle EasyPay payment callback and return payment status.
|
||||
|
||||
Returns:
|
||||
PlainTextResponse: A response containing the payment status for the EasyPay payment callback.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
# return PlainTextResponse("success", status_code=200)
|
||||
|
||||
@pay_router.get(
|
||||
path='/custom/{order_no}/{id}',
|
||||
summary='自定义支付回调',
|
||||
description='Handle custom payment callback and return payment status.',
|
||||
)
|
||||
def router_callback_custom(order_no: str, id: str) -> ResponseBase:
|
||||
"""
|
||||
Handle custom payment callback and return payment status.
|
||||
|
||||
Args:
|
||||
order_no (str): The order number for the payment.
|
||||
id (str): The ID associated with the payment.
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the custom payment callback.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
@upload_router.post(
|
||||
path='/remote/{session_id}/{key}',
|
||||
summary='远程上传回调',
|
||||
@@ -160,11 +79,11 @@ def router_callback_custom(order_no: str, id: str) -> ResponseBase:
|
||||
def router_callback_remote(session_id: str, key: str) -> ResponseBase:
|
||||
"""
|
||||
Handle remote upload callback and return upload status.
|
||||
|
||||
|
||||
Args:
|
||||
session_id (str): The session ID for the upload.
|
||||
key (str): The key for the uploaded file.
|
||||
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the remote upload callback.
|
||||
"""
|
||||
@@ -178,15 +97,15 @@ def router_callback_remote(session_id: str, key: str) -> ResponseBase:
|
||||
def router_callback_qiniu(session_id: str) -> ResponseBase:
|
||||
"""
|
||||
Handle Qiniu Cloud upload callback and return upload status.
|
||||
|
||||
|
||||
Args:
|
||||
session_id (str): The session ID for the upload.
|
||||
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the Qiniu Cloud upload callback.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
|
||||
@upload_router.post(
|
||||
path='/tencent/{session_id}',
|
||||
summary='腾讯云上传回调',
|
||||
@@ -195,16 +114,16 @@ def router_callback_qiniu(session_id: str) -> ResponseBase:
|
||||
def router_callback_tencent(session_id: str) -> ResponseBase:
|
||||
"""
|
||||
Handle Tencent Cloud upload callback and return upload status.
|
||||
|
||||
|
||||
Args:
|
||||
session_id (str): The session ID for the upload.
|
||||
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the Tencent Cloud upload callback.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
@upload_router.post(
|
||||
@upload_router.post(
|
||||
path='/aliyun/{session_id}',
|
||||
summary='阿里云上传回调',
|
||||
description='Handle Aliyun upload callback and return upload status.',
|
||||
@@ -212,16 +131,16 @@ def router_callback_tencent(session_id: str) -> ResponseBase:
|
||||
def router_callback_aliyun(session_id: str) -> ResponseBase:
|
||||
"""
|
||||
Handle Aliyun upload callback and return upload status.
|
||||
|
||||
|
||||
Args:
|
||||
session_id (str): The session ID for the upload.
|
||||
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the Aliyun upload callback.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
@upload_router.post(
|
||||
@upload_router.post(
|
||||
path='/upyun/{session_id}',
|
||||
summary='又拍云上传回调',
|
||||
description='Handle Upyun upload callback and return upload status.',
|
||||
@@ -229,10 +148,10 @@ def router_callback_aliyun(session_id: str) -> ResponseBase:
|
||||
def router_callback_upyun(session_id: str) -> ResponseBase:
|
||||
"""
|
||||
Handle Upyun upload callback and return upload status.
|
||||
|
||||
|
||||
Args:
|
||||
session_id (str): The session ID for the upload.
|
||||
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the Upyun upload callback.
|
||||
"""
|
||||
@@ -246,10 +165,10 @@ def router_callback_upyun(session_id: str) -> ResponseBase:
|
||||
def router_callback_aws(session_id: str) -> ResponseBase:
|
||||
"""
|
||||
Handle AWS S3 upload callback and return upload status.
|
||||
|
||||
|
||||
Args:
|
||||
session_id (str): The session ID for the upload.
|
||||
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the AWS S3 upload callback.
|
||||
"""
|
||||
@@ -263,10 +182,10 @@ def router_callback_aws(session_id: str) -> ResponseBase:
|
||||
def router_callback_onedrive_finish(session_id: str) -> ResponseBase:
|
||||
"""
|
||||
Handle OneDrive upload completion callback and return upload status.
|
||||
|
||||
|
||||
Args:
|
||||
session_id (str): The session ID for the upload.
|
||||
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the OneDrive upload completion callback.
|
||||
"""
|
||||
@@ -280,7 +199,7 @@ def router_callback_onedrive_finish(session_id: str) -> ResponseBase:
|
||||
def router_callback_onedrive_auth() -> ResponseBase:
|
||||
"""
|
||||
Handle OneDrive authorization callback and return authorization status.
|
||||
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the OneDrive authorization callback.
|
||||
"""
|
||||
@@ -294,8 +213,8 @@ def router_callback_onedrive_auth() -> ResponseBase:
|
||||
def router_callback_google_auth() -> ResponseBase:
|
||||
"""
|
||||
Handle Google OAuth completion callback and return authorization status.
|
||||
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the Google OAuth completion callback.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
100
routers/api/v1/category/__init__.py
Normal file
100
routers/api/v1/category/__init__.py
Normal file
@@ -0,0 +1,100 @@
|
||||
"""
|
||||
文件分类筛选端点
|
||||
|
||||
按文件类型分类(图片/视频/音频/文档)查询用户的所有文件,
|
||||
跨目录搜索,支持分页。扩展名映射从数据库 Setting 表读取。
|
||||
"""
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from loguru import logger as l
|
||||
|
||||
from middleware.auth import auth_required
|
||||
from middleware.dependencies import SessionDep, TableViewRequestDep
|
||||
from sqlmodels import (
|
||||
FileCategory,
|
||||
ListResponse,
|
||||
Object,
|
||||
ObjectResponse,
|
||||
ObjectType,
|
||||
Setting,
|
||||
SettingsType,
|
||||
User,
|
||||
)
|
||||
|
||||
category_router = APIRouter(
|
||||
prefix="/category",
|
||||
tags=["category"],
|
||||
)
|
||||
|
||||
|
||||
@category_router.get(
|
||||
path="/{category}",
|
||||
summary="按分类获取文件列表",
|
||||
)
|
||||
async def router_category_list(
|
||||
session: SessionDep,
|
||||
user: Annotated[User, Depends(auth_required)],
|
||||
category: FileCategory,
|
||||
table_view: TableViewRequestDep,
|
||||
) -> ListResponse[ObjectResponse]:
|
||||
"""
|
||||
按文件类型分类查询用户的所有文件
|
||||
|
||||
跨所有目录搜索,返回分页结果。
|
||||
扩展名配置从数据库 Setting 表读取(type=file_category)。
|
||||
|
||||
认证:
|
||||
- JWT token in Authorization header
|
||||
|
||||
路径参数:
|
||||
- category: 文件分类(image / video / audio / document)
|
||||
|
||||
查询参数:
|
||||
- offset: 分页偏移量(默认0)
|
||||
- limit: 每页数量(默认20,最大100)
|
||||
- desc: 是否降序(默认true)
|
||||
- order: 排序字段(created_at / updated_at)
|
||||
|
||||
响应:
|
||||
- ListResponse[ObjectResponse]: 分页文件列表
|
||||
|
||||
错误处理:
|
||||
- HTTPException 422: category 参数无效
|
||||
- HTTPException 404: 该分类未配置扩展名
|
||||
"""
|
||||
# 从数据库读取该分类的扩展名配置
|
||||
setting = await Setting.get(
|
||||
session,
|
||||
(Setting.type == SettingsType.FILE_CATEGORY) & (Setting.name == category.value),
|
||||
)
|
||||
if not setting or not setting.value:
|
||||
raise HTTPException(status_code=404, detail=f"分类 {category.value} 未配置扩展名")
|
||||
|
||||
extensions = [ext.strip() for ext in setting.value.split(",") if ext.strip()]
|
||||
if not extensions:
|
||||
raise HTTPException(status_code=404, detail=f"分类 {category.value} 扩展名列表为空")
|
||||
|
||||
result = await Object.get_by_category(
|
||||
session,
|
||||
user.id,
|
||||
extensions,
|
||||
table_view=table_view,
|
||||
)
|
||||
|
||||
items = [
|
||||
ObjectResponse(
|
||||
id=obj.id,
|
||||
name=obj.name,
|
||||
type=ObjectType.FILE,
|
||||
size=obj.size,
|
||||
mime_type=obj.mime_type,
|
||||
thumb=False,
|
||||
created_at=obj.created_at,
|
||||
updated_at=obj.updated_at,
|
||||
source_enabled=False,
|
||||
)
|
||||
for obj in result.items
|
||||
]
|
||||
|
||||
return ListResponse(count=result.count, items=items)
|
||||
@@ -206,4 +206,4 @@ async def router_directory_create(
|
||||
parent_id=parent_id,
|
||||
policy_id=policy_id,
|
||||
)
|
||||
await new_folder.save(session)
|
||||
new_folder = await new_folder.save(session)
|
||||
|
||||
@@ -184,9 +184,7 @@ async def create_upload_session(
|
||||
|
||||
# 确定存储策略
|
||||
policy_id = request.policy_id or parent.policy_id
|
||||
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)
|
||||
|
||||
# 校验用户组是否有权使用该策略(仅当用户显式指定 policy_id 时)
|
||||
if request.policy_id:
|
||||
@@ -711,9 +709,7 @@ async def create_empty_file(
|
||||
|
||||
# 确定存储策略
|
||||
policy_id = request.policy_id or parent.policy_id
|
||||
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)
|
||||
|
||||
# 生成存储路径并创建空文件
|
||||
storage_path: str | None = None
|
||||
@@ -942,7 +938,7 @@ async def file_get(
|
||||
|
||||
# 递增下载次数
|
||||
link.downloads += 1
|
||||
await link.save(session)
|
||||
link = await link.save(session)
|
||||
|
||||
if policy.type == PolicyType.LOCAL:
|
||||
storage_service = LocalStorageService(policy)
|
||||
@@ -996,7 +992,7 @@ async def file_source_redirect(
|
||||
|
||||
# 递增下载次数
|
||||
link.downloads += 1
|
||||
await link.save(session)
|
||||
link = await link.save(session)
|
||||
|
||||
if policy.type == PolicyType.LOCAL:
|
||||
storage_service = LocalStorageService(policy)
|
||||
|
||||
@@ -112,9 +112,7 @@ async def router_object_create(
|
||||
|
||||
# 确定存储策略
|
||||
policy_id = request.policy_id or parent.policy_id
|
||||
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)
|
||||
|
||||
parent_id = parent.id
|
||||
|
||||
@@ -149,7 +147,7 @@ async def router_object_create(
|
||||
owner_id=user_id,
|
||||
policy_id=policy_id,
|
||||
)
|
||||
await file_object.save(session)
|
||||
file_object = await file_object.save(session)
|
||||
|
||||
l.info(f"创建空白文件: {request.name}")
|
||||
|
||||
@@ -474,7 +472,7 @@ async def router_object_rename(
|
||||
|
||||
# 更新名称
|
||||
obj.name = new_name
|
||||
await obj.save(session)
|
||||
obj = await obj.save(session)
|
||||
|
||||
l.info(f"用户 {user_id} 将对象 {obj.id} 重命名为 {new_name}")
|
||||
|
||||
@@ -682,7 +680,7 @@ async def router_object_switch_policy(
|
||||
dest_policy_id=dest_policy_id,
|
||||
object_id=obj_id,
|
||||
)
|
||||
await task_props.save(session)
|
||||
task_props = await task_props.save(session)
|
||||
|
||||
if obj_is_file:
|
||||
# 文件:后台迁移
|
||||
@@ -698,7 +696,7 @@ async def router_object_switch_policy(
|
||||
# 目录:先更新目录自身的 policy_id
|
||||
obj = await Object.get(session, Object.id == obj_id)
|
||||
obj.policy_id = dest_policy_id
|
||||
await obj.save(session)
|
||||
obj = await obj.save(session)
|
||||
|
||||
if request.is_migrate_existing:
|
||||
# 后台迁移所有已有文件
|
||||
@@ -715,7 +713,7 @@ async def router_object_switch_policy(
|
||||
task = await Task.get(session, Task.id == task_id)
|
||||
task.status = TaskStatus.COMPLETED
|
||||
task.progress = 100
|
||||
await task.save(session)
|
||||
task = await task.save(session)
|
||||
|
||||
# 重新获取 task 以读取最新状态
|
||||
task = await Task.get(session, Task.id == task_id)
|
||||
@@ -850,7 +848,7 @@ async def router_patch_object_metadata(
|
||||
)
|
||||
if existing:
|
||||
existing.value = patch.value
|
||||
await existing.save(session)
|
||||
existing = await existing.save(session)
|
||||
else:
|
||||
entry = ObjectMetadata(
|
||||
object_id=object_id,
|
||||
@@ -858,6 +856,6 @@ async def router_patch_object_metadata(
|
||||
value=patch.value,
|
||||
is_public=True,
|
||||
)
|
||||
await entry.save(session)
|
||||
entry = await entry.save(session)
|
||||
|
||||
l.info(f"用户 {user.id} 更新了对象 {object_id} 的 {len(request.patches)} 条元数据")
|
||||
|
||||
@@ -102,7 +102,7 @@ async def router_create_custom_property(
|
||||
options=request.options,
|
||||
default_value=request.default_value,
|
||||
)
|
||||
await definition.save(session)
|
||||
definition = await definition.save(session)
|
||||
|
||||
l.info(f"用户 {user.id} 创建了自定义属性: {request.name}")
|
||||
|
||||
@@ -128,12 +128,7 @@ async def router_update_custom_property(
|
||||
- 404: 属性定义不存在
|
||||
- 403: 无权操作此属性
|
||||
"""
|
||||
definition = await CustomPropertyDefinition.get(
|
||||
session,
|
||||
CustomPropertyDefinition.id == id,
|
||||
)
|
||||
if not definition:
|
||||
raise HTTPException(status_code=404, detail="自定义属性不存在")
|
||||
definition = await CustomPropertyDefinition.get_exist_one(session, id)
|
||||
|
||||
if definition.owner_id != user.id:
|
||||
raise HTTPException(status_code=403, detail="无权操作此属性")
|
||||
@@ -163,12 +158,7 @@ async def router_delete_custom_property(
|
||||
- 404: 属性定义不存在
|
||||
- 403: 无权操作此属性
|
||||
"""
|
||||
definition = await CustomPropertyDefinition.get(
|
||||
session,
|
||||
CustomPropertyDefinition.id == id,
|
||||
)
|
||||
if not definition:
|
||||
raise HTTPException(status_code=404, detail="自定义属性不存在")
|
||||
definition = await CustomPropertyDefinition.get_exist_one(session, id)
|
||||
|
||||
if definition.owner_id != user.id:
|
||||
raise HTTPException(status_code=403, detail="无权操作此属性")
|
||||
|
||||
@@ -45,12 +45,7 @@ async def router_share_get(
|
||||
4. 返回分享详情(含文件树和分享者信息)
|
||||
"""
|
||||
# 1. 查询分享(预加载 user 和 object)
|
||||
share = await Share.get(
|
||||
session, Share.id == id,
|
||||
load=[Share.user, Share.object],
|
||||
)
|
||||
if not share:
|
||||
http_exceptions.raise_not_found(detail="分享不存在或已被取消")
|
||||
share = await Share.get_exist_one(session, id, load=[Share.user, Share.object])
|
||||
|
||||
# 2. 检查过期
|
||||
now = datetime.now()
|
||||
@@ -474,16 +469,29 @@ def router_share_update(id: str) -> ResponseBase:
|
||||
path='/{id}',
|
||||
summary='删除分享',
|
||||
description='Delete a share by ID.',
|
||||
dependencies=[Depends(auth_required)]
|
||||
status_code=204,
|
||||
)
|
||||
def router_share_delete(id: str) -> ResponseBase:
|
||||
async def router_share_delete(
|
||||
session: SessionDep,
|
||||
user: Annotated[User, Depends(auth_required)],
|
||||
id: UUID,
|
||||
) -> None:
|
||||
"""
|
||||
Delete a share by ID.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the share to be deleted.
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the deleted share.
|
||||
删除分享
|
||||
|
||||
认证:需要 JWT token
|
||||
|
||||
流程:
|
||||
1. 通过分享ID查找分享
|
||||
2. 验证分享属于当前用户
|
||||
3. 删除分享记录
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
share = await Share.get_exist_one(session, id)
|
||||
if share.user_id != user.id:
|
||||
http_exceptions.raise_forbidden(detail="无权删除此分享")
|
||||
|
||||
user_id = user.id
|
||||
share_code = share.code
|
||||
await Share.delete(session, share)
|
||||
|
||||
l.info(f"用户 {user_id} 删除了分享: {share_code}")
|
||||
@@ -234,7 +234,7 @@ async def router_user_register(
|
||||
group_id=default_group.id,
|
||||
)
|
||||
new_user_id = new_user.id
|
||||
await new_user.save(session)
|
||||
new_user = await new_user.save(session)
|
||||
|
||||
# 7. 创建 AuthIdentity
|
||||
hashed_password = Password.hash(request.credential) if request.credential else None
|
||||
@@ -246,7 +246,7 @@ async def router_user_register(
|
||||
is_verified=False,
|
||||
user_id=new_user_id,
|
||||
)
|
||||
await identity.save(session)
|
||||
identity = await identity.save(session)
|
||||
|
||||
# 8. 创建用户根目录(使用用户组关联的第一个存储策略)
|
||||
await session.refresh(default_group, ['policies'])
|
||||
@@ -494,9 +494,24 @@ async def router_user_storage(
|
||||
if not group:
|
||||
raise HTTPException(status_code=404, detail="用户组不存在")
|
||||
|
||||
# [TODO] 总空间加上用户购买的额外空间
|
||||
# 查询用户所有未过期容量包的 size 总和
|
||||
from datetime import datetime
|
||||
from sqlalchemy import func, select, and_, or_
|
||||
|
||||
total: int = group.max_storage
|
||||
now = datetime.now()
|
||||
stmt = select(func.coalesce(func.sum(sqlmodels.StoragePack.size), 0)).where(
|
||||
and_(
|
||||
sqlmodels.StoragePack.user_id == user.id,
|
||||
or_(
|
||||
sqlmodels.StoragePack.expired_time.is_(None),
|
||||
sqlmodels.StoragePack.expired_time > now,
|
||||
),
|
||||
)
|
||||
)
|
||||
result = await session.exec(stmt)
|
||||
active_packs_total: int = result.scalar_one()
|
||||
|
||||
total: int = group.max_storage + active_packs_total
|
||||
used: int = user.storage
|
||||
free: int = max(0, total - used)
|
||||
|
||||
@@ -638,7 +653,7 @@ async def router_user_authn_finish(
|
||||
is_verified=True,
|
||||
user_id=user.id,
|
||||
)
|
||||
await identity.save(session)
|
||||
identity = await identity.save(session)
|
||||
|
||||
return authn.to_detail_response()
|
||||
|
||||
|
||||
@@ -218,7 +218,7 @@ async def router_user_settings_avatar(
|
||||
|
||||
# 更新用户头像字段
|
||||
user.avatar = "file"
|
||||
await user.save(session)
|
||||
user = await user.save(session)
|
||||
|
||||
|
||||
@user_settings_router.put(
|
||||
@@ -252,7 +252,7 @@ async def router_user_settings_avatar_gravatar(
|
||||
await delete_avatar_files(session, user.id)
|
||||
|
||||
user.avatar = "gravatar"
|
||||
await user.save(session)
|
||||
user = await user.save(session)
|
||||
|
||||
|
||||
@user_settings_router.delete(
|
||||
@@ -279,7 +279,7 @@ async def router_user_settings_avatar_delete(
|
||||
await delete_avatar_files(session, user.id)
|
||||
|
||||
user.avatar = "default"
|
||||
await user.save(session)
|
||||
user = await user.save(session)
|
||||
|
||||
|
||||
@user_settings_router.patch(
|
||||
@@ -321,7 +321,7 @@ async def router_user_settings_theme(
|
||||
user.color_error = request.theme_colors.error
|
||||
user.color_neutral = request.theme_colors.neutral
|
||||
|
||||
await user.save(session)
|
||||
user = await user.save(session)
|
||||
|
||||
|
||||
@user_settings_router.patch(
|
||||
@@ -358,7 +358,7 @@ async def router_user_settings_change_password(
|
||||
http_exceptions.raise_forbidden("当前密码错误")
|
||||
|
||||
email_identity.credential = Password.hash(request.new_password)
|
||||
await email_identity.save(session)
|
||||
email_identity = await email_identity.save(session)
|
||||
|
||||
|
||||
@user_settings_router.patch(
|
||||
@@ -392,7 +392,7 @@ async def router_user_settings_patch(
|
||||
http_exceptions.raise_bad_request(f"设置项 {option.value} 不允许为空")
|
||||
|
||||
setattr(user, option.value, value)
|
||||
await user.save(session)
|
||||
user = await user.save(session)
|
||||
|
||||
|
||||
@user_settings_router.get(
|
||||
@@ -454,7 +454,7 @@ async def router_user_settings_2fa_enable(
|
||||
extra: dict = orjson.loads(email_identity.extra_data) if email_identity.extra_data else {}
|
||||
extra["two_factor"] = secret
|
||||
email_identity.extra_data = orjson.dumps(extra).decode('utf-8')
|
||||
await email_identity.save(session)
|
||||
email_identity = await email_identity.save(session)
|
||||
|
||||
|
||||
# ==================== 认证身份管理 ====================
|
||||
|
||||
@@ -79,9 +79,7 @@ async def set_default_viewer(
|
||||
|
||||
if existing:
|
||||
existing.app_id = request.app_id
|
||||
existing = await existing.save(session)
|
||||
# 重新加载 app 关系
|
||||
await session.refresh(existing, attribute_names=["app"])
|
||||
existing = await existing.save(session, load=UserFileAppDefault.app)
|
||||
return existing.to_response()
|
||||
else:
|
||||
new_default = UserFileAppDefault(
|
||||
@@ -89,9 +87,7 @@ async def set_default_viewer(
|
||||
extension=normalized_ext,
|
||||
app_id=request.app_id,
|
||||
)
|
||||
new_default = await new_default.save(session)
|
||||
# 重新加载 app 关系
|
||||
await session.refresh(new_default, attribute_names=["app"])
|
||||
new_default = await new_default.save(session, load=UserFileAppDefault.app)
|
||||
return new_default.to_response()
|
||||
|
||||
|
||||
|
||||
@@ -1,106 +0,0 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
|
||||
from middleware.auth import auth_required
|
||||
from sqlmodels import ResponseBase
|
||||
from utils import http_exceptions
|
||||
|
||||
vas_router = APIRouter(
|
||||
prefix="/vas",
|
||||
tags=["vas"]
|
||||
)
|
||||
|
||||
@vas_router.get(
|
||||
path='/pack',
|
||||
summary='获取容量包及配额信息',
|
||||
description='Get information about storage packs and quotas.',
|
||||
dependencies=[Depends(auth_required)]
|
||||
)
|
||||
def router_vas_pack() -> ResponseBase:
|
||||
"""
|
||||
Get information about storage packs and quotas.
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for storage packs and quotas.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
@vas_router.get(
|
||||
path='/product',
|
||||
summary='获取商品信息,同时返回支付信息',
|
||||
description='Get product information along with payment details.',
|
||||
dependencies=[Depends(auth_required)]
|
||||
)
|
||||
def router_vas_product() -> ResponseBase:
|
||||
"""
|
||||
Get product information along with payment details.
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for products and payment information.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
@vas_router.post(
|
||||
path='/order',
|
||||
summary='新建支付订单',
|
||||
description='Create an order for a product.',
|
||||
dependencies=[Depends(auth_required)]
|
||||
)
|
||||
def router_vas_order() -> ResponseBase:
|
||||
"""
|
||||
Create an order for a product.
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the created order.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
@vas_router.get(
|
||||
path='/order/{id}',
|
||||
summary='查询订单状态',
|
||||
description='Get information about a specific payment order by ID.',
|
||||
dependencies=[Depends(auth_required)]
|
||||
)
|
||||
def router_vas_order_get(id: str) -> ResponseBase:
|
||||
"""
|
||||
Get information about a specific payment order by ID.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the order to retrieve information for.
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the specified order.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
@vas_router.get(
|
||||
path='/redeem',
|
||||
summary='获取兑换码信息',
|
||||
description='Get information about a specific redemption code.',
|
||||
dependencies=[Depends(auth_required)]
|
||||
)
|
||||
def router_vas_redeem(code: str) -> ResponseBase:
|
||||
"""
|
||||
Get information about a specific redemption code.
|
||||
|
||||
Args:
|
||||
code (str): The redemption code to retrieve information for.
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the specified redemption code.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
|
||||
@vas_router.post(
|
||||
path='/redeem',
|
||||
summary='执行兑换',
|
||||
description='Redeem a redemption code for a product or service.',
|
||||
dependencies=[Depends(auth_required)]
|
||||
)
|
||||
def router_vas_redeem_post() -> ResponseBase:
|
||||
"""
|
||||
Redeem a redemption code for a product or service.
|
||||
|
||||
Returns:
|
||||
ResponseBase: A model containing the response data for the redeemed code.
|
||||
"""
|
||||
http_exceptions.raise_not_implemented()
|
||||
Reference in New Issue
Block a user