feat: Implement API routers for user, tag, vas, webdav, and slave functionalities
- Added user authentication and registration endpoints with JWT support. - Created tag management routes for creating and deleting tags. - Implemented value-added service (VAS) endpoints for managing storage packs and orders. - Developed WebDAV account management routes for creating, updating, and deleting accounts. - Introduced slave router for handling file uploads, downloads, and aria2 task management. - Enhanced JWT utility functions for token creation and secret key management. - Established lifespan management for FastAPI application startup and shutdown processes. - Integrated password handling utilities with Argon2 hashing and two-factor authentication support.
This commit is contained in:
516
routers/api/v1/admin/__init__.py
Normal file
516
routers/api/v1/admin/__init__.py
Normal file
@@ -0,0 +1,516 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from loguru import logger
|
||||
|
||||
from middleware.auth import AdminRequired
|
||||
from middleware.dependencies import SessionDep
|
||||
from models import User
|
||||
from models.user import UserPublic
|
||||
from models.response import ResponseBase
|
||||
|
||||
# 管理员根目录 /api/admin
|
||||
admin_router = APIRouter(
|
||||
prefix="/admin",
|
||||
tags=["admin"],
|
||||
)
|
||||
|
||||
# 用户组 /api/admin/group
|
||||
admin_group_router = APIRouter(
|
||||
prefix="/admin/group",
|
||||
tags=["admin", "admin_group"],
|
||||
)
|
||||
|
||||
# 用户 /api/admin/user
|
||||
admin_user_router = APIRouter(
|
||||
prefix="/admin/user",
|
||||
tags=["admin", "admin_user"],
|
||||
)
|
||||
|
||||
# 文件 /api/admin/file
|
||||
admin_file_router = APIRouter(
|
||||
prefix="/admin/file",
|
||||
tags=["admin", "admin_file"],
|
||||
)
|
||||
|
||||
# 离线下载 /api/admin/aria2
|
||||
admin_aria2_router = APIRouter(
|
||||
prefix='/admin/aria2',
|
||||
tags=['admin', 'admin_aria2']
|
||||
)
|
||||
|
||||
# 存储策略管理 /api/admin/policy
|
||||
admin_policy_router = APIRouter(
|
||||
prefix='/admin/policy',
|
||||
tags=['admin', 'admin_policy']
|
||||
)
|
||||
|
||||
# 分享 /api/admin/share
|
||||
admin_share_router = APIRouter(
|
||||
prefix='/admin/share',
|
||||
tags=['admin', 'admin_share']
|
||||
)
|
||||
|
||||
# 任务 /api/admin/task
|
||||
admin_task_router = APIRouter(
|
||||
prefix='/admin/task',
|
||||
tags=['admin', 'admin_task']
|
||||
)
|
||||
|
||||
# 增值服务 /api/admin/vas
|
||||
admin_vas_router = APIRouter(
|
||||
prefix='/admin/vas',
|
||||
tags=['admin', 'admin_vas']
|
||||
)
|
||||
|
||||
|
||||
@admin_router.get(
|
||||
path='/summary',
|
||||
summary='获取站点概况',
|
||||
description='Get site summary information',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_get_summary() -> ResponseBase:
|
||||
"""
|
||||
获取站点概况信息,包括用户数、分享数、文件数等。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含站点概况信息的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_router.get(
|
||||
path='/news',
|
||||
summary='获取社区新闻',
|
||||
description='Get community news',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_get_news() -> ResponseBase:
|
||||
"""
|
||||
获取社区新闻信息,包括最新的动态和公告。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含社区新闻信息的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_router.patch(
|
||||
path='/settings',
|
||||
summary='更新设置',
|
||||
description='Update settings',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_update_settings() -> ResponseBase:
|
||||
"""
|
||||
更新站点设置,包括站点名称、描述等。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含更新结果的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_router.get(
|
||||
path='/settings',
|
||||
summary='获取设置',
|
||||
description='Get settings',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_get_settings() -> ResponseBase:
|
||||
"""
|
||||
获取站点设置,包括站点名称、描述等。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含站点设置的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_group_router.get(
|
||||
path='/',
|
||||
summary='获取用户组列表',
|
||||
description='Get user group list',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_get_groups() -> ResponseBase:
|
||||
"""
|
||||
获取用户组列表,包括每个用户组的名称和权限信息。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含用户组列表的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_group_router.get(
|
||||
path='/{group_id}',
|
||||
summary='获取用户组信息',
|
||||
description='Get user group information by ID',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_get_group(group_id: int) -> ResponseBase:
|
||||
"""
|
||||
根据用户组ID获取用户组信息,包括名称、权限等。
|
||||
|
||||
Args:
|
||||
group_id (int): 用户组ID。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含用户组信息的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_group_router.get(
|
||||
path='/list/{group_id}',
|
||||
summary='获取用户组成员列表',
|
||||
description='Get user group member list by group ID',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_get_group_members(
|
||||
group_id: int,
|
||||
page: int = 1,
|
||||
page_size: int = 20
|
||||
) -> ResponseBase:
|
||||
"""
|
||||
根据用户组ID获取用户组成员列表。
|
||||
|
||||
Args:
|
||||
group_id (int): 用户组ID。
|
||||
page (int): 页码,默认为1。
|
||||
page_size (int, optional): 每页显示的成员数量,默认为20。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含用户组成员列表的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_group_router.post(
|
||||
path='/',
|
||||
summary='创建用户组',
|
||||
description='Create a new user group',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_create_group() -> ResponseBase:
|
||||
"""
|
||||
创建一个新的用户组,设置名称和权限等信息。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含创建结果的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_group_router.patch(
|
||||
path='/{group_id}',
|
||||
summary='更新用户组信息',
|
||||
description='Update user group information by ID',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_update_group(group_id: int) -> ResponseBase:
|
||||
"""
|
||||
根据用户组ID更新用户组信息,包括名称、权限等。
|
||||
|
||||
Args:
|
||||
group_id (int): 用户组ID。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含更新结果的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_group_router.delete(
|
||||
path='/{group_id}',
|
||||
summary='删除用户组',
|
||||
description='Delete user group by ID',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_delete_group(group_id: int) -> ResponseBase:
|
||||
"""
|
||||
根据用户组ID删除用户组。
|
||||
|
||||
Args:
|
||||
group_id (int): 用户组ID。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含删除结果的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_user_router.get(
|
||||
path='/info/{user_id}',
|
||||
summary='获取用户信息',
|
||||
description='Get user information by ID',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
async def router_admin_get_user(session: SessionDep, user_id: int) -> ResponseBase:
|
||||
"""
|
||||
根据用户ID获取用户信息,包括用户名、邮箱、注册时间等。
|
||||
|
||||
Args:
|
||||
session(SessionDep): 数据库会话依赖项。
|
||||
user_id (int): 用户ID。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含用户信息的响应模型。
|
||||
"""
|
||||
user = await User.get_exist_one(session, user_id)
|
||||
return ResponseBase(data=user.to_public().model_dump())
|
||||
|
||||
@admin_user_router.get(
|
||||
path='/list',
|
||||
summary='获取用户列表',
|
||||
description='Get user list',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
async def router_admin_get_users(
|
||||
session: SessionDep,
|
||||
page: int = 1,
|
||||
page_size: int = 20
|
||||
) -> ResponseBase:
|
||||
"""
|
||||
获取用户列表,支持分页。
|
||||
|
||||
Args:
|
||||
session: 数据库会话依赖项。
|
||||
page (int): 页码,默认为1。
|
||||
page_size (int): 每页显示的用户数量,默认为20。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含用户列表的响应模型。
|
||||
"""
|
||||
offset = (page - 1) * page_size
|
||||
users: list[User] = await User.get(
|
||||
session,
|
||||
None,
|
||||
fetch_mode="all",
|
||||
offset=offset,
|
||||
limit=page_size
|
||||
)
|
||||
return ResponseBase(
|
||||
data=[user.to_public().model_dump() for user in users]
|
||||
)
|
||||
|
||||
@admin_user_router.post(
|
||||
path='/create',
|
||||
summary='创建用户',
|
||||
description='Create a new user',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
async def router_admin_create_user(
|
||||
session: SessionDep,
|
||||
user: User,
|
||||
) -> ResponseBase:
|
||||
"""
|
||||
创建一个新的用户,设置用户名、密码等信息。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含创建结果的响应模型。
|
||||
"""
|
||||
existing_user = await User.get(session, User.username == user.username)
|
||||
if existing_user:
|
||||
return ResponseBase(
|
||||
code=400,
|
||||
msg="User with this username already exists."
|
||||
)
|
||||
user = await user.save(session)
|
||||
return ResponseBase(data=user.to_public().model_dump())
|
||||
|
||||
@admin_user_router.patch(
|
||||
path='/{user_id}',
|
||||
summary='更新用户信息',
|
||||
description='Update user information by ID',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_update_user(user_id: int) -> ResponseBase:
|
||||
"""
|
||||
根据用户ID更新用户信息,包括用户名、邮箱等。
|
||||
|
||||
Args:
|
||||
user_id (int): 用户ID。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含更新结果的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_user_router.delete(
|
||||
path='/{user_id}',
|
||||
summary='删除用户',
|
||||
description='Delete user by ID',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_delete_user(user_id: int) -> ResponseBase:
|
||||
"""
|
||||
根据用户ID删除用户。
|
||||
|
||||
Args:
|
||||
user_id (int): 用户ID。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含删除结果的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_user_router.post(
|
||||
path='/calibrate/{user_id}',
|
||||
summary='校准用户存储容量',
|
||||
description='Calibrate the user storage.',
|
||||
dependencies=[Depends(AdminRequired)]
|
||||
)
|
||||
def router_admin_calibrate_storage():
|
||||
pass
|
||||
|
||||
@admin_file_router.get(
|
||||
path='/list',
|
||||
summary='获取文件',
|
||||
description='Get file list',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_get_file_list() -> ResponseBase:
|
||||
"""
|
||||
获取文件列表,包括文件名称、大小、上传时间等。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含文件列表的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_file_router.get(
|
||||
path='/preview/{file_id}',
|
||||
summary='预览文件',
|
||||
description='Preview file by ID',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_preview_file(file_id: int) -> ResponseBase:
|
||||
"""
|
||||
根据文件ID预览文件内容。
|
||||
|
||||
Args:
|
||||
file_id (int): 文件ID。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含文件预览内容的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_file_router.patch(
|
||||
path='/ban/{file_id}',
|
||||
summary='封禁文件',
|
||||
description='Ban the file, user can\'t open, copy, move, download or share this file if administrator ban.',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_ban_file(file_id: int) -> ResponseBase:
|
||||
"""
|
||||
根据文件ID封禁文件。
|
||||
|
||||
如果管理员封禁了某个文件,用户将无法打开、复制或移动、下载或分享此文件。
|
||||
|
||||
Args:
|
||||
file_id (int): 文件ID。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含删除结果的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_file_router.delete(
|
||||
path='/{file_id}',
|
||||
summary='删除文件',
|
||||
description='Delete file by ID',
|
||||
dependencies=[Depends(AdminRequired)],
|
||||
)
|
||||
def router_admin_delete_file(file_id: int) -> ResponseBase:
|
||||
"""
|
||||
根据文件ID删除文件。
|
||||
|
||||
Args:
|
||||
file_id (int): 文件ID。
|
||||
|
||||
Returns:
|
||||
ResponseModel: 包含删除结果的响应模型。
|
||||
"""
|
||||
pass
|
||||
|
||||
@admin_aria2_router.post(
|
||||
path='/test',
|
||||
summary='测试连接配置',
|
||||
description='',
|
||||
dependencies=[Depends(AdminRequired)]
|
||||
)
|
||||
def router_admin_aira2_test() -> ResponseBase:
|
||||
pass
|
||||
|
||||
@admin_policy_router.get(
|
||||
path='/list',
|
||||
summary='列出存储策略',
|
||||
description='',
|
||||
dependencies=[Depends(AdminRequired)]
|
||||
)
|
||||
def router_policy_list() -> ResponseBase:
|
||||
pass
|
||||
|
||||
@admin_policy_router.post(
|
||||
path='/test/path',
|
||||
summary='测试本地路径可用性',
|
||||
description='',
|
||||
dependencies=[Depends(AdminRequired)]
|
||||
)
|
||||
def router_policy_test_path() -> ResponseBase:
|
||||
pass
|
||||
|
||||
@admin_policy_router.post(
|
||||
path='/test/slave',
|
||||
summary='测试从机通信',
|
||||
description='',
|
||||
dependencies=[Depends(AdminRequired)]
|
||||
)
|
||||
def router_policy_test_slave() -> ResponseBase:
|
||||
pass
|
||||
|
||||
@admin_policy_router.post(
|
||||
path='/',
|
||||
summary='创建存储策略',
|
||||
description='',
|
||||
dependencies=[Depends(AdminRequired)]
|
||||
)
|
||||
def router_policy_add_policy() -> ResponseBase:
|
||||
pass
|
||||
|
||||
@admin_policy_router.post(
|
||||
path='/cors',
|
||||
summary='创建跨域策略',
|
||||
description='',
|
||||
dependencies=[Depends(AdminRequired)]
|
||||
)
|
||||
def router_policy_add_cors() -> ResponseBase:
|
||||
pass
|
||||
|
||||
@admin_policy_router.post(
|
||||
path='/scf',
|
||||
summary='创建COS回调函数',
|
||||
description='',
|
||||
dependencies=[Depends(AdminRequired)]
|
||||
)
|
||||
def router_policy_add_scf() -> ResponseBase:
|
||||
pass
|
||||
|
||||
@admin_policy_router.get(
|
||||
path='/{id}/oauth',
|
||||
summary='获取 OneDrive OAuth URL',
|
||||
description='',
|
||||
dependencies=[Depends(AdminRequired)]
|
||||
)
|
||||
def router_policy_onddrive_oauth() -> ResponseBase:
|
||||
pass
|
||||
|
||||
@admin_policy_router.get(
|
||||
path='/{id}',
|
||||
summary='获取存储策略',
|
||||
description='',
|
||||
dependencies=[Depends(AdminRequired)]
|
||||
)
|
||||
def router_policy_get_policy() -> ResponseBase:
|
||||
pass
|
||||
|
||||
@admin_policy_router.delete(
|
||||
path='/{id}',
|
||||
summary='删除存储策略',
|
||||
description='',
|
||||
dependencies=[Depends(AdminRequired)]
|
||||
)
|
||||
def router_policy_delete_policy() -> ResponseBase:
|
||||
pass
|
||||
298
routers/api/v1/callback/__init__.py
Normal file
298
routers/api/v1/callback/__init__.py
Normal file
@@ -0,0 +1,298 @@
|
||||
from fastapi import APIRouter, Depends, Query
|
||||
from fastapi.responses import PlainTextResponse, RedirectResponse
|
||||
from middleware.auth import SignRequired
|
||||
from models.response import ResponseBase
|
||||
import service.oauth
|
||||
|
||||
callback_router = APIRouter(
|
||||
prefix='/callback',
|
||||
tags=["callback"],
|
||||
)
|
||||
|
||||
oauth_router = APIRouter(
|
||||
prefix='/callback/oauth',
|
||||
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(
|
||||
path='/qq',
|
||||
summary='QQ互联回调',
|
||||
description='Handle QQ OAuth callback and return user information.',
|
||||
)
|
||||
def router_callback_qq() -> ResponseBase:
|
||||
"""
|
||||
Handle QQ OAuth callback and return user information.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the QQ OAuth callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@oauth_router.get(
|
||||
path='/github',
|
||||
summary='GitHub OAuth 回调',
|
||||
description='Handle GitHub OAuth callback and return user information.',
|
||||
)
|
||||
async def router_callback_github(
|
||||
code: str = Query(description="The token received from GitHub for authentication.")) -> PlainTextResponse:
|
||||
"""
|
||||
GitHub OAuth 回调处理
|
||||
- 错误响应示例:
|
||||
- {
|
||||
'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.
|
||||
"""
|
||||
try:
|
||||
access_token = await service.oauth.github.get_access_token(code)
|
||||
# [TODO] 把access_token写数据库里
|
||||
if not access_token:
|
||||
return PlainTextResponse("Failed to retrieve access token from GitHub.", status_code=400)
|
||||
|
||||
user_data = await service.oauth.github.get_user_info(access_token.access_token)
|
||||
# [TODO] 把user_data写数据库里
|
||||
|
||||
return PlainTextResponse(f"User information processed successfully, code: {code}, user_data: {user_data.json_dump()}", status_code=200)
|
||||
except Exception as e:
|
||||
return PlainTextResponse(f"An error occurred: {str(e)}", 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:
|
||||
ResponseModel: A model containing the response data for the Alipay payment callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@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:
|
||||
ResponseModel: A model containing the response data for the WeChat Pay payment callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@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:
|
||||
ResponseModel: A model containing the response data for the Stripe payment callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@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.
|
||||
"""
|
||||
pass
|
||||
# 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:
|
||||
ResponseModel: A model containing the response data for the custom payment callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@upload_router.post(
|
||||
path='/remote/{session_id}/{key}',
|
||||
summary='远程上传回调',
|
||||
description='Handle remote upload callback and return upload status.',
|
||||
)
|
||||
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:
|
||||
ResponseModel: A model containing the response data for the remote upload callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@upload_router.post(
|
||||
path='/qiniu/{session_id}',
|
||||
summary='七牛云上传回调',
|
||||
description='Handle Qiniu Cloud upload callback and return upload status.',
|
||||
)
|
||||
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:
|
||||
ResponseModel: A model containing the response data for the Qiniu Cloud upload callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@upload_router.post(
|
||||
path='/tencent/{session_id}',
|
||||
summary='腾讯云上传回调',
|
||||
description='Handle Tencent Cloud upload callback and return upload status.',
|
||||
)
|
||||
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:
|
||||
ResponseModel: A model containing the response data for the Tencent Cloud upload callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@upload_router.post(
|
||||
path='/aliyun/{session_id}',
|
||||
summary='阿里云上传回调',
|
||||
description='Handle Aliyun upload callback and return upload status.',
|
||||
)
|
||||
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:
|
||||
ResponseModel: A model containing the response data for the Aliyun upload callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@upload_router.post(
|
||||
path='/upyun/{session_id}',
|
||||
summary='又拍云上传回调',
|
||||
description='Handle Upyun upload callback and return upload status.',
|
||||
)
|
||||
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:
|
||||
ResponseModel: A model containing the response data for the Upyun upload callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@upload_router.post(
|
||||
path='/aws/{session_id}',
|
||||
summary='AWS S3上传回调',
|
||||
description='Handle AWS S3 upload callback and return upload status.',
|
||||
)
|
||||
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:
|
||||
ResponseModel: A model containing the response data for the AWS S3 upload callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@upload_router.post(
|
||||
path='/onedrive/finish/{session_id}',
|
||||
summary='OneDrive上传完成回调',
|
||||
description='Handle OneDrive upload completion callback and return upload status.',
|
||||
)
|
||||
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:
|
||||
ResponseModel: A model containing the response data for the OneDrive upload completion callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@upload_router.get(
|
||||
path='/ondrive/auth',
|
||||
summary='OneDrive授权回调',
|
||||
description='Handle OneDrive authorization callback and return authorization status.',
|
||||
)
|
||||
def router_callback_onedrive_auth() -> ResponseBase:
|
||||
"""
|
||||
Handle OneDrive authorization callback and return authorization status.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the OneDrive authorization callback.
|
||||
"""
|
||||
pass
|
||||
|
||||
@upload_router.get(
|
||||
path='/google/auth',
|
||||
summary='Google OAuth 完成',
|
||||
description='Handle Google OAuth completion callback and return authorization status.',
|
||||
)
|
||||
def router_callback_google_auth() -> ResponseBase:
|
||||
"""
|
||||
Handle Google OAuth completion callback and return authorization status.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the Google OAuth completion callback.
|
||||
"""
|
||||
pass
|
||||
155
routers/api/v1/directory/__init__.py
Normal file
155
routers/api/v1/directory/__init__.py
Normal file
@@ -0,0 +1,155 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from middleware.auth import AuthRequired
|
||||
from middleware.dependencies import SessionDep
|
||||
from models import (
|
||||
DirectoryCreateRequest,
|
||||
DirectoryResponse,
|
||||
Object,
|
||||
ObjectResponse,
|
||||
ObjectType,
|
||||
PolicyResponse,
|
||||
User,
|
||||
response,
|
||||
)
|
||||
|
||||
directory_router = APIRouter(
|
||||
prefix="/directory",
|
||||
tags=["directory"]
|
||||
)
|
||||
|
||||
@directory_router.get(
|
||||
path="/{path:path}",
|
||||
summary="获取目录内容",
|
||||
)
|
||||
async def router_directory_get(
|
||||
session: SessionDep,
|
||||
user: Annotated[User, Depends(AuthRequired)],
|
||||
path: str
|
||||
) -> DirectoryResponse:
|
||||
"""
|
||||
获取目录内容
|
||||
|
||||
路径必须以用户名开头,如 /api/directory/admin 或 /api/directory/admin/docs
|
||||
|
||||
:param session: 数据库会话
|
||||
:param user: 当前登录用户
|
||||
:param path: 目录路径(必须以用户名开头)
|
||||
:return: 目录内容
|
||||
"""
|
||||
# 路径必须以用户名开头
|
||||
path = path.strip("/")
|
||||
if not path:
|
||||
raise HTTPException(status_code=400, detail="路径不能为空,请使用 /{username} 格式")
|
||||
|
||||
path_parts = path.split("/")
|
||||
if path_parts[0] != user.username:
|
||||
raise HTTPException(status_code=403, detail="无权访问其他用户的目录")
|
||||
|
||||
folder = await Object.get_by_path(session, user.id, "/" + path, user.username)
|
||||
|
||||
if not folder:
|
||||
raise HTTPException(status_code=404, detail="目录不存在")
|
||||
|
||||
if not folder.is_folder:
|
||||
raise HTTPException(status_code=400, detail="指定路径不是目录")
|
||||
|
||||
children = await Object.get_children(session, user.id, folder.id)
|
||||
policy = await folder.awaitable_attrs.policy
|
||||
|
||||
objects = [
|
||||
ObjectResponse(
|
||||
id=child.id,
|
||||
name=child.name,
|
||||
path=f"/{child.name}", # TODO: 完整路径
|
||||
thumb=False,
|
||||
size=child.size,
|
||||
type=ObjectType.FOLDER if child.is_folder else ObjectType.FILE,
|
||||
date=child.updated_at,
|
||||
create_date=child.created_at,
|
||||
source_enabled=False,
|
||||
)
|
||||
for child in children
|
||||
]
|
||||
|
||||
policy_response = PolicyResponse(
|
||||
id=policy.id,
|
||||
name=policy.name,
|
||||
type=policy.type.value,
|
||||
max_size=policy.max_size,
|
||||
)
|
||||
|
||||
return DirectoryResponse(
|
||||
id=folder.id,
|
||||
parent=folder.parent_id,
|
||||
objects=objects,
|
||||
policy=policy_response,
|
||||
)
|
||||
|
||||
|
||||
@directory_router.put(
|
||||
path="/",
|
||||
summary="创建目录",
|
||||
)
|
||||
async def router_directory_create(
|
||||
session: SessionDep,
|
||||
user: Annotated[User, Depends(AuthRequired)],
|
||||
request: DirectoryCreateRequest
|
||||
) -> response.ResponseBase:
|
||||
"""
|
||||
创建目录
|
||||
|
||||
:param session: 数据库会话
|
||||
:param user: 当前登录用户
|
||||
:param request: 创建请求(包含 parent_id UUID 和 name)
|
||||
:return: 创建结果
|
||||
"""
|
||||
# 验证目录名称
|
||||
name = request.name.strip()
|
||||
if not name:
|
||||
raise HTTPException(status_code=400, detail="目录名称不能为空")
|
||||
|
||||
if "/" in name or "\\" in name:
|
||||
raise HTTPException(status_code=400, detail="目录名称不能包含斜杠")
|
||||
|
||||
# 通过 UUID 获取父目录
|
||||
parent = await Object.get(session, Object.id == request.parent_id)
|
||||
if not parent or parent.owner_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="父目录不存在")
|
||||
|
||||
if not parent.is_folder:
|
||||
raise HTTPException(status_code=400, detail="父路径不是目录")
|
||||
|
||||
# 检查是否已存在同名对象
|
||||
existing = await Object.get(
|
||||
session,
|
||||
(Object.owner_id == user.id) &
|
||||
(Object.parent_id == parent.id) &
|
||||
(Object.name == name)
|
||||
)
|
||||
if existing:
|
||||
raise HTTPException(status_code=409, detail="同名文件或目录已存在")
|
||||
|
||||
policy_id = request.policy_id if request.policy_id else parent.policy_id
|
||||
parent_id = parent.id # 在 save 前保存
|
||||
|
||||
new_folder = Object(
|
||||
name=name,
|
||||
type=ObjectType.FOLDER,
|
||||
owner_id=user.id,
|
||||
parent_id=parent_id,
|
||||
policy_id=policy_id,
|
||||
)
|
||||
new_folder_id = new_folder.id # 在 save 前保存 UUID
|
||||
new_folder_name = new_folder.name
|
||||
await new_folder.save(session)
|
||||
|
||||
return response.ResponseBase(
|
||||
data={
|
||||
"id": new_folder_id,
|
||||
"name": new_folder_name,
|
||||
"parent_id": parent_id,
|
||||
}
|
||||
)
|
||||
107
routers/api/v1/download/__init__.py
Normal file
107
routers/api/v1/download/__init__.py
Normal file
@@ -0,0 +1,107 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from middleware.auth import SignRequired
|
||||
from models.response import ResponseBase
|
||||
|
||||
aria2_router = APIRouter(
|
||||
prefix="/aria2",
|
||||
tags=["aria2"]
|
||||
)
|
||||
|
||||
@aria2_router.post(
|
||||
path='/url',
|
||||
summary='创建URL下载任务',
|
||||
description='Create a URL download task endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_aria2_url() -> ResponseBase:
|
||||
"""
|
||||
Create a URL download task endpoint.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the URL download task.
|
||||
"""
|
||||
pass
|
||||
|
||||
@aria2_router.post(
|
||||
path='/torrent/{id}',
|
||||
summary='创建种子下载任务',
|
||||
description='Create a torrent download task endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_aria2_torrent(id: str) -> ResponseBase:
|
||||
"""
|
||||
Create a torrent download task endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the torrent to download.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the torrent download task.
|
||||
"""
|
||||
pass
|
||||
|
||||
@aria2_router.put(
|
||||
path='/select/{gid}',
|
||||
summary='重新选择要下载的文件',
|
||||
description='Re-select files to download endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_aria2_select(gid: str) -> ResponseBase:
|
||||
"""
|
||||
Re-select files to download endpoint.
|
||||
|
||||
Args:
|
||||
gid (str): The GID of the download task.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the re-selection of files.
|
||||
"""
|
||||
pass
|
||||
|
||||
@aria2_router.delete(
|
||||
path='/task/{gid}',
|
||||
summary='取消或删除下载任务',
|
||||
description='Delete a download task endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_aria2_delete(gid: str) -> ResponseBase:
|
||||
"""
|
||||
Delete a download task endpoint.
|
||||
|
||||
Args:
|
||||
gid (str): The GID of the download task to delete.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the deletion of the download task.
|
||||
"""
|
||||
pass
|
||||
|
||||
@aria2_router.get(
|
||||
'/downloading',
|
||||
summary='获取正在下载中的任务',
|
||||
description='Get currently downloading tasks endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_aria2_downloading() -> ResponseBase:
|
||||
"""
|
||||
Get currently downloading tasks endpoint.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for currently downloading tasks.
|
||||
"""
|
||||
pass
|
||||
|
||||
@aria2_router.get(
|
||||
path='/finished',
|
||||
summary='获取已完成的任务',
|
||||
description='Get finished tasks endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_aria2_finished() -> ResponseBase:
|
||||
"""
|
||||
Get finished tasks endpoint.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for finished tasks.
|
||||
"""
|
||||
pass
|
||||
382
routers/api/v1/file/__init__.py
Normal file
382
routers/api/v1/file/__init__.py
Normal file
@@ -0,0 +1,382 @@
|
||||
from fastapi import APIRouter, Depends, UploadFile
|
||||
from fastapi.responses import FileResponse
|
||||
from middleware.auth import SignRequired
|
||||
from models.response import ResponseBase
|
||||
|
||||
file_router = APIRouter(
|
||||
prefix="/file",
|
||||
tags=["file"]
|
||||
)
|
||||
|
||||
file_upload_router = APIRouter(
|
||||
prefix="/file/upload",
|
||||
tags=["file"]
|
||||
)
|
||||
|
||||
@file_router.get(
|
||||
path='/get/{id}/{name}',
|
||||
summary='文件外链(直接输出文件数据)',
|
||||
description='Get file external link endpoint.',
|
||||
)
|
||||
def router_file_get(id: str, name: str) -> FileResponse:
|
||||
"""
|
||||
Get file external link endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file.
|
||||
name (str): The name of the file.
|
||||
|
||||
Returns:
|
||||
FileResponse: A response containing the file data.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.get(
|
||||
path='/source/{id}/{name}',
|
||||
summary='文件外链(301跳转)',
|
||||
description='Get file external link with 301 redirect endpoint.',
|
||||
)
|
||||
def router_file_source(id: str, name: str) -> ResponseBase:
|
||||
"""
|
||||
Get file external link with 301 redirect endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file.
|
||||
name (str): The name of the file.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file with a redirect.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_upload_router.get(
|
||||
path='/download/{id}',
|
||||
summary='下载文件',
|
||||
description='Download file endpoint.',
|
||||
)
|
||||
def router_file_download(id: str) -> ResponseBase:
|
||||
"""
|
||||
Download file endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file to download.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file download.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_upload_router.get(
|
||||
path='/archive/{sessionID}/archive.zip',
|
||||
summary='打包并下载文件',
|
||||
description='Archive and download files endpoint.',
|
||||
)
|
||||
def router_file_archive_download(sessionID: str) -> ResponseBase:
|
||||
"""
|
||||
Archive and download files endpoint.
|
||||
|
||||
Args:
|
||||
sessionID (str): The session ID for the archive.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the archived files download.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_upload_router.post(
|
||||
path='/{sessionID}/{index}',
|
||||
summary='文件上传',
|
||||
description='File upload endpoint.',
|
||||
)
|
||||
def router_file_upload(sessionID: str, index: int, file: UploadFile) -> ResponseBase:
|
||||
"""
|
||||
File upload endpoint.
|
||||
|
||||
Args:
|
||||
sessionID (str): The session ID for the upload.
|
||||
index (int): The index of the file being uploaded.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_upload_router.put(
|
||||
path='/',
|
||||
summary='创建上传会话',
|
||||
description='Create an upload session endpoint.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_file_upload_session() -> ResponseBase:
|
||||
"""
|
||||
Create an upload session endpoint.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the upload session.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_upload_router.delete(
|
||||
path='/{sessionID}',
|
||||
summary='删除上传会话',
|
||||
description='Delete an upload session endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_upload_session_delete(sessionID: str) -> ResponseBase:
|
||||
"""
|
||||
Delete an upload session endpoint.
|
||||
|
||||
Args:
|
||||
sessionID (str): The session ID to delete.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the deletion.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_upload_router.delete(
|
||||
path='/',
|
||||
summary='清除所有上传会话',
|
||||
description='Clear all upload sessions endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_upload_session_clear() -> ResponseBase:
|
||||
"""
|
||||
Clear all upload sessions endpoint.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for clearing all sessions.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.put(
|
||||
path='/update/{id}',
|
||||
summary='更新文件',
|
||||
description='Update file information endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_update(id: str) -> ResponseBase:
|
||||
"""
|
||||
Update file information endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file to update.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file update.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.post(
|
||||
path='/create',
|
||||
summary='创建空白文件',
|
||||
description='Create a blank file endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_create() -> ResponseBase:
|
||||
"""
|
||||
Create a blank file endpoint.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file creation.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.put(
|
||||
path='/download/{id}',
|
||||
summary='创建文件下载会话',
|
||||
description='Create a file download session endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_download(id: str) -> ResponseBase:
|
||||
"""
|
||||
Create a file download session endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file to download.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file download session.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.get(
|
||||
path='/preview/{id}',
|
||||
summary='预览文件',
|
||||
description='Preview file endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_preview(id: str) -> ResponseBase:
|
||||
"""
|
||||
Preview file endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file to preview.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file preview.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.get(
|
||||
path='/content/{id}',
|
||||
summary='获取文本文件内容',
|
||||
description='Get text file content endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_content(id: str) -> ResponseBase:
|
||||
"""
|
||||
Get text file content endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the text file.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the text file content.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.get(
|
||||
path='/doc/{id}',
|
||||
summary='获取Office文档预览地址',
|
||||
description='Get Office document preview URL endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_doc(id: str) -> ResponseBase:
|
||||
"""
|
||||
Get Office document preview URL endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the Office document.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the Office document preview URL.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.get(
|
||||
path='/thumb/{id}',
|
||||
summary='获取文件缩略图',
|
||||
description='Get file thumbnail endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_thumb(id: str) -> ResponseBase:
|
||||
"""
|
||||
Get file thumbnail endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file to get the thumbnail for.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file thumbnail.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.post(
|
||||
path='/source/{id}',
|
||||
summary='取得文件外链',
|
||||
description='Get file external link endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_source(id: str) -> ResponseBase:
|
||||
"""
|
||||
Get file external link endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file to get the external link for.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file external link.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.post(
|
||||
path='/archive',
|
||||
summary='打包要下载的文件',
|
||||
description='Archive files for download endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_archive(id: str) -> ResponseBase:
|
||||
"""
|
||||
Archive files for download endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file to archive.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the archived files.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.post(
|
||||
path='/compress',
|
||||
summary='创建文件压缩任务',
|
||||
description='Create file compression task endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_compress(id: str) -> ResponseBase:
|
||||
"""
|
||||
Create file compression task endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file to compress.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file compression task.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.post(
|
||||
path='/decompress',
|
||||
summary='创建文件解压任务',
|
||||
description='Create file extraction task endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_decompress(id: str) -> ResponseBase:
|
||||
"""
|
||||
Create file extraction task endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file to decompress.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file extraction task.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.post(
|
||||
path='/relocate',
|
||||
summary='创建文件转移任务',
|
||||
description='Create file relocation task endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_relocate(id: str) -> ResponseBase:
|
||||
"""
|
||||
Create file relocation task endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the file to relocate.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file relocation task.
|
||||
"""
|
||||
pass
|
||||
|
||||
@file_router.get(
|
||||
path='/search/{type}/{keyword}',
|
||||
summary='搜索文件',
|
||||
description='Search files by keyword endpoint.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_file_search(type: str, keyword: str) -> ResponseBase:
|
||||
"""
|
||||
Search files by keyword endpoint.
|
||||
|
||||
Args:
|
||||
type (str): The type of search (e.g., 'name', 'content').
|
||||
keyword (str): The keyword to search for.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the file search.
|
||||
"""
|
||||
pass
|
||||
156
routers/api/v1/object/__init__.py
Normal file
156
routers/api/v1/object/__init__.py
Normal file
@@ -0,0 +1,156 @@
|
||||
from typing import Annotated
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
|
||||
from middleware.auth import AuthRequired
|
||||
from middleware.dependencies import SessionDep
|
||||
from models import Object, ObjectDeleteRequest, ObjectMoveRequest, User
|
||||
from models.response import ResponseBase
|
||||
|
||||
object_router = APIRouter(
|
||||
prefix="/object",
|
||||
tags=["object"]
|
||||
)
|
||||
|
||||
|
||||
@object_router.delete(
|
||||
path='/',
|
||||
summary='删除对象',
|
||||
description='删除一个或多个对象(文件或目录)',
|
||||
)
|
||||
async def router_object_delete(
|
||||
session: SessionDep,
|
||||
user: Annotated[User, Depends(AuthRequired)],
|
||||
request: ObjectDeleteRequest,
|
||||
) -> ResponseBase:
|
||||
"""
|
||||
删除对象端点
|
||||
|
||||
:param session: 数据库会话
|
||||
:param user: 当前登录用户
|
||||
:param request: 删除请求(包含待删除对象的UUID列表)
|
||||
:return: 删除结果
|
||||
"""
|
||||
deleted_count = 0
|
||||
|
||||
for obj_id in request.ids:
|
||||
obj = await Object.get(session, Object.id == obj_id)
|
||||
if obj and obj.owner_id == user.id:
|
||||
# TODO: 递归删除子对象(如果是目录)
|
||||
# TODO: 更新用户存储空间
|
||||
await obj.delete(session)
|
||||
deleted_count += 1
|
||||
|
||||
return ResponseBase(
|
||||
data={
|
||||
"deleted": deleted_count,
|
||||
"total": len(request.ids),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@object_router.patch(
|
||||
path='/',
|
||||
summary='移动对象',
|
||||
description='移动一个或多个对象到目标目录',
|
||||
)
|
||||
async def router_object_move(
|
||||
session: SessionDep,
|
||||
user: Annotated[User, Depends(AuthRequired)],
|
||||
request: ObjectMoveRequest,
|
||||
) -> ResponseBase:
|
||||
"""
|
||||
移动对象端点
|
||||
|
||||
:param session: 数据库会话
|
||||
:param user: 当前登录用户
|
||||
:param request: 移动请求(包含源对象UUID列表和目标目录UUID)
|
||||
:return: 移动结果
|
||||
"""
|
||||
# 验证目标目录
|
||||
dst = await Object.get(session, Object.id == request.dst_id)
|
||||
if not dst or dst.owner_id != user.id:
|
||||
raise HTTPException(status_code=404, detail="目标目录不存在")
|
||||
|
||||
if not dst.is_folder:
|
||||
raise HTTPException(status_code=400, detail="目标不是有效文件夹")
|
||||
|
||||
moved_count = 0
|
||||
|
||||
for src_id in request.src_ids:
|
||||
src = await Object.get(session, Object.id == src_id)
|
||||
if not src or src.owner_id != user.id:
|
||||
continue
|
||||
|
||||
# 检查是否移动到自身或子目录(防止循环引用)
|
||||
if src.id == dst.id:
|
||||
continue
|
||||
|
||||
# 检查目标目录下是否存在同名对象
|
||||
existing = await Object.get(
|
||||
session,
|
||||
(Object.owner_id == user.id) &
|
||||
(Object.parent_id == dst.id) &
|
||||
(Object.name == src.name)
|
||||
)
|
||||
if existing:
|
||||
continue # 跳过重名对象
|
||||
|
||||
src.parent_id = dst.id
|
||||
await src.save(session)
|
||||
moved_count += 1
|
||||
|
||||
return ResponseBase(
|
||||
data={
|
||||
"moved": moved_count,
|
||||
"total": len(request.src_ids),
|
||||
}
|
||||
)
|
||||
|
||||
@object_router.post(
|
||||
path='/copy',
|
||||
summary='复制对象',
|
||||
description='Copy an object endpoint.',
|
||||
dependencies=[Depends(AuthRequired)]
|
||||
)
|
||||
def router_object_copy() -> ResponseBase:
|
||||
"""
|
||||
Copy an object endpoint.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the object copy.
|
||||
"""
|
||||
pass
|
||||
|
||||
@object_router.post(
|
||||
path='/rename',
|
||||
summary='重命名对象',
|
||||
description='Rename an object endpoint.',
|
||||
dependencies=[Depends(AuthRequired)]
|
||||
)
|
||||
def router_object_rename() -> ResponseBase:
|
||||
"""
|
||||
Rename an object endpoint.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the object rename.
|
||||
"""
|
||||
pass
|
||||
|
||||
@object_router.get(
|
||||
path='/property/{id}',
|
||||
summary='获取对象属性',
|
||||
description='Get object properties endpoint.',
|
||||
dependencies=[Depends(AuthRequired)]
|
||||
)
|
||||
def router_object_property(id: str) -> ResponseBase:
|
||||
"""
|
||||
Get object properties endpoint.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the object to retrieve properties for.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the object properties.
|
||||
"""
|
||||
pass
|
||||
105
routers/api/v1/site/__init__.py
Normal file
105
routers/api/v1/site/__init__.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from fastapi import APIRouter
|
||||
from sqlalchemy import and_
|
||||
import json
|
||||
|
||||
from middleware.dependencies import SessionDep
|
||||
from models.response import ResponseBase
|
||||
from models.setting import Setting
|
||||
|
||||
site_router = APIRouter(
|
||||
prefix="/site",
|
||||
tags=["site"],
|
||||
)
|
||||
|
||||
|
||||
async def _get_setting(session: SessionDep, type_: str, name: str) -> str | None:
|
||||
"""获取设置值"""
|
||||
setting = await Setting.get(session, and_(Setting.type == type_, Setting.name == name))
|
||||
return setting.value if setting else None
|
||||
|
||||
|
||||
async def _get_setting_bool(session: SessionDep, type_: str, name: str) -> bool:
|
||||
"""获取布尔类型设置值"""
|
||||
value = await _get_setting(session, type_, name)
|
||||
return value == "1" if value else False
|
||||
|
||||
async def _get_setting_json(session: SessionDep, type_: str, name: str) -> dict | list | None:
|
||||
"""获取 JSON 类型设置值"""
|
||||
value = await _get_setting(session, type_, name)
|
||||
return json.loads(value) if value else None
|
||||
|
||||
|
||||
@site_router.get(
|
||||
path="/ping",
|
||||
summary="测试用路由",
|
||||
description="A simple endpoint to check if the site is up and running.",
|
||||
response_model=ResponseBase,
|
||||
)
|
||||
def router_site_ping():
|
||||
"""
|
||||
Ping the site to check if it is up and running.
|
||||
|
||||
Returns:
|
||||
str: A message indicating the site is running.
|
||||
"""
|
||||
from utils.conf.appmeta import BackendVersion
|
||||
return ResponseBase(data=BackendVersion)
|
||||
|
||||
|
||||
@site_router.get(
|
||||
path='/captcha',
|
||||
summary='验证码',
|
||||
description='Get a Base64 captcha image.',
|
||||
response_model=ResponseBase,
|
||||
)
|
||||
def router_site_captcha():
|
||||
"""
|
||||
Get a Base64 captcha image.
|
||||
|
||||
Returns:
|
||||
str: A Base64 encoded string of the captcha image.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
@site_router.get(
|
||||
path='/config',
|
||||
summary='站点全局配置',
|
||||
description='Get the configuration file.',
|
||||
response_model=ResponseBase,
|
||||
)
|
||||
async def router_site_config(session: SessionDep):
|
||||
"""
|
||||
Get the configuration file.
|
||||
|
||||
Returns:
|
||||
dict: The site configuration.
|
||||
"""
|
||||
return ResponseBase(
|
||||
data={
|
||||
"title": await _get_setting(session, "basic", "siteName"),
|
||||
"loginCaptcha": await _get_setting_bool(session, "login", "login_captcha"),
|
||||
"regCaptcha": await _get_setting_bool(session, "login", "reg_captcha"),
|
||||
"forgetCaptcha": await _get_setting_bool(session, "login", "forget_captcha"),
|
||||
"emailActive": await _get_setting_bool(session, "login", "email_active"),
|
||||
"QQLogin": None,
|
||||
"themes": await _get_setting_json(session, "basic", "themes"),
|
||||
"defaultTheme": await _get_setting(session, "basic", "defaultTheme"),
|
||||
"score_enabled": None,
|
||||
"share_score_rate": None,
|
||||
"home_view_method": await _get_setting(session, "view", "home_view_method"),
|
||||
"share_view_method": await _get_setting(session, "view", "share_view_method"),
|
||||
"authn": await _get_setting_bool(session, "authn", "authn_enabled"),
|
||||
"user": {},
|
||||
"captcha_type": None,
|
||||
"captcha_ReCaptchaKey": await _get_setting(session, "captcha", "captcha_ReCaptchaKey"),
|
||||
"captcha_CloudflareKey": await _get_setting(session, "captcha", "captcha_CloudflareKey"),
|
||||
"captcha_tcaptcha_appid": None,
|
||||
"site_notice": None,
|
||||
"registerEnabled": await _get_setting_bool(session, "register", "register_enabled"),
|
||||
"app_promotion": None,
|
||||
"wopi_exts": None,
|
||||
"app_feedback": None,
|
||||
"app_forum": None,
|
||||
}
|
||||
)
|
||||
225
routers/api/v1/slave/__init__.py
Normal file
225
routers/api/v1/slave/__init__.py
Normal file
@@ -0,0 +1,225 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.responses import FileResponse
|
||||
from middleware.auth import SignRequired
|
||||
from models.response import ResponseBase
|
||||
|
||||
slave_router = APIRouter(
|
||||
prefix="/slave",
|
||||
tags=["slave"],
|
||||
)
|
||||
|
||||
slave_aria2_router = APIRouter(
|
||||
prefix="/aria2",
|
||||
tags=["slave_aria2"],
|
||||
)
|
||||
|
||||
@slave_router.get(
|
||||
path='/ping',
|
||||
summary='测试用路由',
|
||||
description='Test route for checking connectivity.',
|
||||
)
|
||||
def router_slave_ping() -> ResponseBase:
|
||||
"""
|
||||
Test route for checking connectivity.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A response model indicating success.
|
||||
"""
|
||||
from utils.conf.appmeta import BackendVersion
|
||||
return ResponseBase(data=BackendVersion)
|
||||
|
||||
@slave_router.post(
|
||||
path='/post',
|
||||
summary='上传',
|
||||
description='Upload data to the server.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_slave_post(data: str) -> ResponseBase:
|
||||
"""
|
||||
Upload data to the server.
|
||||
|
||||
Args:
|
||||
data (str): The data to be uploaded.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A response model indicating success.
|
||||
"""
|
||||
pass
|
||||
|
||||
@slave_router.get(
|
||||
path='/get/{speed}/{path}/{name}',
|
||||
summary='获取下载',
|
||||
)
|
||||
def router_slave_download(speed: int, path: str, name: str) -> ResponseBase:
|
||||
"""
|
||||
Get download information.
|
||||
|
||||
Args:
|
||||
speed (int): The speed of the download.
|
||||
path (str): The path where the file is located.
|
||||
name (str): The name of the file to be downloaded.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A response model containing download information.
|
||||
"""
|
||||
pass
|
||||
|
||||
@slave_router.get(
|
||||
path='/download/{sign}',
|
||||
summary='根据签名下载文件',
|
||||
description='Download a file based on its signature.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_slave_download_by_sign(sign: str) -> FileResponse:
|
||||
"""
|
||||
Download a file based on its signature.
|
||||
|
||||
Args:
|
||||
sign (str): The signature of the file to be downloaded.
|
||||
|
||||
Returns:
|
||||
FileResponse: A response containing the file to be downloaded.
|
||||
"""
|
||||
pass
|
||||
|
||||
@slave_router.get(
|
||||
path='/source/{speed}/{path}/{name}',
|
||||
summary='获取文件外链',
|
||||
description='Get the external link for a file based on its signature.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_slave_source(speed: int, path: str, name: str) -> ResponseBase:
|
||||
"""
|
||||
Get the external link for a file based on its signature.
|
||||
|
||||
Args:
|
||||
speed (int): The speed of the download.
|
||||
path (str): The path where the file is located.
|
||||
name (str): The name of the file to be linked.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A response model containing the external link for the file.
|
||||
"""
|
||||
pass
|
||||
|
||||
@slave_router.get(
|
||||
path='/source/{sign}',
|
||||
summary='根据签名获取文件',
|
||||
description='Get a file based on its signature.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_slave_source_by_sign(sign: str) -> FileResponse:
|
||||
"""
|
||||
Get a file based on its signature.
|
||||
|
||||
Args:
|
||||
sign (str): The signature of the file to be retrieved.
|
||||
|
||||
Returns:
|
||||
FileResponse: A response containing the file to be retrieved.
|
||||
"""
|
||||
pass
|
||||
|
||||
@slave_router.get(
|
||||
path='/thumb/{id}',
|
||||
summary='获取缩略图',
|
||||
description='Get a thumbnail image based on its ID.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_slave_thumb(id: str) -> ResponseBase:
|
||||
"""
|
||||
Get a thumbnail image based on its ID.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the thumbnail image.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A response model containing the Base64 encoded thumbnail image.
|
||||
"""
|
||||
pass
|
||||
|
||||
@slave_router.delete(
|
||||
path='/delete',
|
||||
summary='删除文件',
|
||||
description='Delete a file from the server.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_slave_delete(path: str) -> ResponseBase:
|
||||
"""
|
||||
Delete a file from the server.
|
||||
|
||||
Args:
|
||||
path (str): The path of the file to be deleted.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A response model indicating success or failure of the deletion.
|
||||
"""
|
||||
pass
|
||||
|
||||
@slave_aria2_router.post(
|
||||
path='/test',
|
||||
summary='测试从机连接Aria2服务',
|
||||
description='Test the connection to the Aria2 service from the slave.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_slave_aria2_test() -> ResponseBase:
|
||||
"""
|
||||
Test the connection to the Aria2 service from the slave.
|
||||
"""
|
||||
pass
|
||||
|
||||
@slave_aria2_router.get(
|
||||
path='/get/{gid}',
|
||||
summary='获取Aria2任务信息',
|
||||
description='Get information about an Aria2 task by its GID.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_slave_aria2_get(gid: str = None) -> ResponseBase:
|
||||
"""
|
||||
Get information about an Aria2 task by its GID.
|
||||
|
||||
Args:
|
||||
gid (str): The GID of the Aria2 task.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A response model containing the task information.
|
||||
"""
|
||||
pass
|
||||
|
||||
@slave_aria2_router.post(
|
||||
path='/add',
|
||||
summary='添加Aria2任务',
|
||||
description='Add a new Aria2 task.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_slave_aria2_add(gid: str, url: str, options: dict = None) -> ResponseBase:
|
||||
"""
|
||||
Add a new Aria2 task.
|
||||
|
||||
Args:
|
||||
gid (str): The GID for the new task.
|
||||
url (str): The URL of the file to be downloaded.
|
||||
options (dict, optional): Additional options for the task.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A response model indicating success or failure of the task addition.
|
||||
"""
|
||||
pass
|
||||
|
||||
@slave_aria2_router.delete(
|
||||
path='/remove/{gid}',
|
||||
summary='删除Aria2任务',
|
||||
description='Remove an Aria2 task by its GID.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_slave_aria2_remove(gid: str) -> ResponseBase:
|
||||
"""
|
||||
Remove an Aria2 task by its GID.
|
||||
|
||||
Args:
|
||||
gid (str): The GID of the Aria2 task to be removed.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A response model indicating success or failure of the task removal.
|
||||
"""
|
||||
pass
|
||||
56
routers/api/v1/tag/__init__.py
Normal file
56
routers/api/v1/tag/__init__.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from middleware.auth import SignRequired
|
||||
from models.response import ResponseBase
|
||||
|
||||
tag_router = APIRouter(
|
||||
prefix='/tag',
|
||||
tags=["tag"],
|
||||
)
|
||||
|
||||
@tag_router.post(
|
||||
path='/filter',
|
||||
summary='创建文件分类标签',
|
||||
description='Create a file classification tag.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_tag_create_filter() -> ResponseBase:
|
||||
"""
|
||||
Create a file classification tag.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the created tag.
|
||||
"""
|
||||
pass
|
||||
|
||||
@tag_router.post(
|
||||
path='/link',
|
||||
summary='创建目录快捷方式标签',
|
||||
description='Create a directory shortcut tag.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_tag_create_link() -> ResponseBase:
|
||||
"""
|
||||
Create a directory shortcut tag.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the created tag.
|
||||
"""
|
||||
pass
|
||||
|
||||
@tag_router.delete(
|
||||
path='/{id}',
|
||||
summary='删除标签',
|
||||
description='Delete a tag by its ID.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_tag_delete(id: str) -> ResponseBase:
|
||||
"""
|
||||
Delete a tag by its ID.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the tag to be deleted.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the deletion operation.
|
||||
"""
|
||||
pass
|
||||
564
routers/api/v1/user/__init__.py
Normal file
564
routers/api/v1/user/__init__.py
Normal file
@@ -0,0 +1,564 @@
|
||||
from typing import Annotated, Literal
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi.security import OAuth2PasswordRequestForm
|
||||
from sqlalchemy import and_
|
||||
from webauthn import generate_registration_options
|
||||
from webauthn.helpers import options_to_json_dict
|
||||
from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired
|
||||
|
||||
import models
|
||||
import service
|
||||
from middleware.auth import AuthRequired
|
||||
from middleware.dependencies import SessionDep
|
||||
from utils.JWT.JWT import SECRET_KEY
|
||||
from utils import Password
|
||||
|
||||
user_router = APIRouter(
|
||||
prefix="/user",
|
||||
tags=["user"],
|
||||
)
|
||||
|
||||
user_settings_router = APIRouter(
|
||||
prefix='/user/settings',
|
||||
tags=["user", "user_settings"],
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
|
||||
@user_router.post(
|
||||
path='/session',
|
||||
summary='用户登录',
|
||||
description='User login endpoint. 当用户启用两步验证时,需要传入 otp 参数。',
|
||||
)
|
||||
async def router_user_session(
|
||||
session: SessionDep,
|
||||
form_data: Annotated[OAuth2PasswordRequestForm, Depends()],
|
||||
) -> models.TokenResponse:
|
||||
"""
|
||||
用户登录端点。
|
||||
|
||||
根据 OAuth2.1 规范,使用 password grant type 进行登录。
|
||||
当用户启用两步验证时,需要在表单中传入 otp 参数(通过 scopes 字段传递)。
|
||||
|
||||
OAuth2 scopes 字段格式: "otp:123456" 或直接传入验证码
|
||||
|
||||
:raises HTTPException 401: 用户名或密码错误
|
||||
:raises HTTPException 403: 用户账号被封禁或未完成注册
|
||||
:raises HTTPException 428: 需要两步验证但未提供验证码
|
||||
:raises HTTPException 400: 两步验证码无效
|
||||
"""
|
||||
username = form_data.username
|
||||
password = form_data.password
|
||||
|
||||
# 从 scopes 中提取 OTP 验证码(OAuth2.1 扩展方式)
|
||||
# scopes 格式可以是 ["otp:123456"] 或 ["123456"]
|
||||
otp_code: str | None = None
|
||||
for scope in form_data.scopes:
|
||||
if scope.startswith("otp:"):
|
||||
otp_code = scope[4:]
|
||||
break
|
||||
elif scope.isdigit() and len(scope) == 6:
|
||||
otp_code = scope
|
||||
break
|
||||
|
||||
result = await service.user.Login(
|
||||
session,
|
||||
models.LoginRequest(
|
||||
username=username,
|
||||
password=password,
|
||||
two_fa_code=otp_code,
|
||||
),
|
||||
)
|
||||
|
||||
if isinstance(result, models.TokenResponse):
|
||||
return result
|
||||
elif result is None:
|
||||
raise HTTPException(status_code=401, detail="Invalid username or password")
|
||||
elif result is False:
|
||||
raise HTTPException(status_code=403, detail="User account is banned or not fully registered")
|
||||
elif result == "2fa_required":
|
||||
raise HTTPException(
|
||||
status_code=428,
|
||||
detail="Two-factor authentication required",
|
||||
headers={"X-2FA-Required": "true"},
|
||||
)
|
||||
elif result == "2fa_invalid":
|
||||
raise HTTPException(status_code=400, detail="Invalid two-factor authentication code")
|
||||
else:
|
||||
raise HTTPException(status_code=500, detail="Internal server error during login")
|
||||
|
||||
@user_router.post(
|
||||
path='/',
|
||||
summary='用户注册',
|
||||
description='User registration endpoint.',
|
||||
)
|
||||
async def router_user_register(
|
||||
session: SessionDep,
|
||||
request: models.RegisterRequest,
|
||||
) -> models.response.ResponseBase:
|
||||
"""
|
||||
用户注册端点
|
||||
|
||||
流程:
|
||||
1. 验证用户名唯一性
|
||||
2. 获取默认用户组
|
||||
3. 创建用户记录
|
||||
4. 创建以用户名命名的根目录
|
||||
|
||||
:param session: 数据库会话
|
||||
:param request: 注册请求
|
||||
:return: 注册结果
|
||||
:raises HTTPException 400: 用户名已存在
|
||||
:raises HTTPException 500: 默认用户组或存储策略不存在
|
||||
"""
|
||||
# 1. 验证用户名唯一性
|
||||
existing_user = await models.User.get(
|
||||
session,
|
||||
models.User.username == request.username
|
||||
)
|
||||
if existing_user:
|
||||
raise HTTPException(status_code=400, detail="用户名已存在")
|
||||
|
||||
# 2. 获取默认用户组(从设置中读取 UUID)
|
||||
default_group_setting: models.Setting | None = await models.Setting.get(
|
||||
session,
|
||||
and_(models.Setting.type == models.SettingsType.REGISTER, models.Setting.name == "default_group")
|
||||
)
|
||||
if default_group_setting is None or not default_group_setting.value:
|
||||
raise HTTPException(status_code=500, detail="默认用户组设置不存在")
|
||||
|
||||
default_group_id = UUID(default_group_setting.value)
|
||||
default_group = await models.Group.get(session, models.Group.id == default_group_id)
|
||||
if not default_group:
|
||||
raise HTTPException(status_code=500, detail="默认用户组不存在")
|
||||
|
||||
# 3. 创建用户
|
||||
hashed_password = Password.hash(request.password)
|
||||
new_user = models.User(
|
||||
username=request.username,
|
||||
password=hashed_password,
|
||||
group_id=default_group.id,
|
||||
)
|
||||
new_user_id = new_user.id # 在 save 前保存 UUID
|
||||
new_user_username = new_user.username
|
||||
await new_user.save(session)
|
||||
|
||||
# 4. 创建以用户名命名的根目录
|
||||
default_policy = await models.Policy.get(session, models.Policy.name == "本地存储")
|
||||
if not default_policy:
|
||||
raise HTTPException(status_code=500, detail="默认存储策略不存在")
|
||||
|
||||
await models.Object(
|
||||
name=new_user_username,
|
||||
type=models.ObjectType.FOLDER,
|
||||
owner_id=new_user_id,
|
||||
parent_id=None,
|
||||
policy_id=default_policy.id,
|
||||
).save(session)
|
||||
|
||||
return models.response.ResponseBase(
|
||||
data={
|
||||
"user_id": new_user_id,
|
||||
"username": new_user_username,
|
||||
},
|
||||
msg="注册成功",
|
||||
)
|
||||
|
||||
@user_router.post(
|
||||
path='/code',
|
||||
summary='发送验证码邮件',
|
||||
description='Send a verification code email.',
|
||||
)
|
||||
def router_user_email_code(
|
||||
reason: Literal['register', 'reset'] = 'register',
|
||||
) -> models.response.ResponseBase:
|
||||
"""
|
||||
Send a verification code email.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing information about the password reset email.
|
||||
"""
|
||||
pass
|
||||
|
||||
@user_router.get(
|
||||
path='/qq',
|
||||
summary='初始化QQ登录',
|
||||
description='Initialize QQ login for a user.',
|
||||
)
|
||||
def router_user_qq() -> models.response.ResponseBase:
|
||||
"""
|
||||
Initialize QQ login for a user.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing QQ login initialization information.
|
||||
"""
|
||||
pass
|
||||
|
||||
@user_router.get(
|
||||
path='authn/{username}',
|
||||
summary='WebAuthn登录初始化',
|
||||
description='Initialize WebAuthn login for a user.',
|
||||
)
|
||||
async def router_user_authn(username: str) -> models.response.ResponseBase:
|
||||
|
||||
pass
|
||||
|
||||
@user_router.post(
|
||||
path='authn/finish/{username}',
|
||||
summary='WebAuthn登录',
|
||||
description='Finish WebAuthn login for a user.',
|
||||
)
|
||||
def router_user_authn_finish(username: str) -> models.response.ResponseBase:
|
||||
"""
|
||||
Finish WebAuthn login for a user.
|
||||
|
||||
Args:
|
||||
username (str): The username of the user.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing WebAuthn login information.
|
||||
"""
|
||||
pass
|
||||
|
||||
@user_router.get(
|
||||
path='/profile/{id}',
|
||||
summary='获取用户主页展示用分享',
|
||||
description='Get user profile for display.',
|
||||
)
|
||||
def router_user_profile(id: str) -> models.response.ResponseBase:
|
||||
"""
|
||||
Get user profile for display.
|
||||
|
||||
Args:
|
||||
id (str): The user ID.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing user profile information.
|
||||
"""
|
||||
pass
|
||||
|
||||
@user_router.get(
|
||||
path='/avatar/{id}/{size}',
|
||||
summary='获取用户头像',
|
||||
description='Get user avatar by ID and size.',
|
||||
)
|
||||
def router_user_avatar(id: str, size: int = 128) -> models.response.ResponseBase:
|
||||
"""
|
||||
Get user avatar by ID and size.
|
||||
|
||||
Args:
|
||||
id (str): The user ID.
|
||||
size (int): The size of the avatar image.
|
||||
|
||||
Returns:
|
||||
str: A Base64 encoded string of the user avatar image.
|
||||
"""
|
||||
pass
|
||||
|
||||
#####################
|
||||
# 需要登录的接口
|
||||
#####################
|
||||
|
||||
@user_router.get(
|
||||
path='/me',
|
||||
summary='获取用户信息',
|
||||
description='Get user information.',
|
||||
dependencies=[Depends(dependency=AuthRequired)],
|
||||
response_model=models.response.ResponseBase,
|
||||
)
|
||||
async def router_user_me(
|
||||
session: SessionDep,
|
||||
user: Annotated[models.User, Depends(AuthRequired)],
|
||||
) -> models.response.ResponseBase:
|
||||
"""
|
||||
获取用户信息.
|
||||
|
||||
:return: response.ResponseModel containing user information.
|
||||
:rtype: response.ResponseModel
|
||||
"""
|
||||
# 加载 group 及其 options 关系
|
||||
group = await models.Group.get(
|
||||
session,
|
||||
models.Group.id == user.group_id,
|
||||
load=models.Group.options
|
||||
)
|
||||
|
||||
# 构建 GroupResponse
|
||||
group_response = group.to_response() if group else None
|
||||
|
||||
# 异步加载 tags 关系
|
||||
user_tags = await user.awaitable_attrs.tags
|
||||
|
||||
user_response = models.UserResponse(
|
||||
id=user.id,
|
||||
username=user.username,
|
||||
status=user.status,
|
||||
score=user.score,
|
||||
nickname=user.nickname,
|
||||
avatar=user.avatar,
|
||||
created_at=user.created_at,
|
||||
group=group_response,
|
||||
tags=[tag.name for tag in user_tags] if user_tags else [],
|
||||
)
|
||||
|
||||
return models.response.ResponseBase(data=user_response.model_dump())
|
||||
|
||||
@user_router.get(
|
||||
path='/storage',
|
||||
summary='存储信息',
|
||||
description='Get user storage information.',
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
async def router_user_storage(
|
||||
session: SessionDep,
|
||||
user: Annotated[models.user.User, Depends(AuthRequired)],
|
||||
) -> models.response.ResponseBase:
|
||||
"""
|
||||
获取用户存储空间信息。
|
||||
|
||||
返回值:
|
||||
- used: 已使用空间(字节)
|
||||
- free: 剩余空间(字节)
|
||||
- total: 总容量(字节)= 用户组容量
|
||||
"""
|
||||
# 获取用户组的基础存储容量
|
||||
group = await models.Group.get(session, models.Group.id == user.group_id)
|
||||
if not group:
|
||||
raise HTTPException(status_code=500, detail="用户组不存在")
|
||||
total: int = group.max_storage
|
||||
used: int = user.storage
|
||||
free: int = max(0, total - used)
|
||||
|
||||
return models.response.ResponseBase(
|
||||
data={
|
||||
"used": used,
|
||||
"free": free,
|
||||
"total": total,
|
||||
}
|
||||
)
|
||||
|
||||
@user_router.put(
|
||||
path='/authn/start',
|
||||
summary='WebAuthn登录初始化',
|
||||
description='Initialize WebAuthn login for a user.',
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
async def router_user_authn_start(
|
||||
session: SessionDep,
|
||||
user: Annotated[models.user.User, Depends(AuthRequired)],
|
||||
) -> models.response.ResponseBase:
|
||||
"""
|
||||
Initialize WebAuthn login for a user.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing WebAuthn initialization information.
|
||||
"""
|
||||
# TODO: 检查 WebAuthn 是否开启,用户是否有注册过 WebAuthn 设备等
|
||||
authn_setting = await models.Setting.get(
|
||||
session,
|
||||
and_(models.Setting.type == "authn", models.Setting.name == "authn_enabled")
|
||||
)
|
||||
if not authn_setting or authn_setting.value != "1":
|
||||
raise HTTPException(status_code=400, detail="WebAuthn is not enabled")
|
||||
|
||||
site_url_setting = await models.Setting.get(
|
||||
session,
|
||||
and_(models.Setting.type == "basic", models.Setting.name == "siteURL")
|
||||
)
|
||||
site_title_setting = await models.Setting.get(
|
||||
session,
|
||||
and_(models.Setting.type == "basic", models.Setting.name == "siteTitle")
|
||||
)
|
||||
|
||||
options = generate_registration_options(
|
||||
rp_id=site_url_setting.value if site_url_setting else "",
|
||||
rp_name=site_title_setting.value if site_title_setting else "",
|
||||
user_name=user.username,
|
||||
user_display_name=user.nick or user.username,
|
||||
)
|
||||
|
||||
return models.response.ResponseBase(data=options_to_json_dict(options))
|
||||
|
||||
@user_router.put(
|
||||
path='/authn/finish',
|
||||
summary='WebAuthn登录',
|
||||
description='Finish WebAuthn login for a user.',
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
def router_user_authn_finish() -> models.response.ResponseBase:
|
||||
"""
|
||||
Finish WebAuthn login for a user.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing WebAuthn login information.
|
||||
"""
|
||||
pass
|
||||
|
||||
@user_settings_router.get(
|
||||
path='/policies',
|
||||
summary='获取用户可选存储策略',
|
||||
description='Get user selectable storage policies.',
|
||||
)
|
||||
def router_user_settings_policies() -> models.response.ResponseBase:
|
||||
"""
|
||||
Get user selectable storage policies.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing available storage policies for the user.
|
||||
"""
|
||||
pass
|
||||
|
||||
@user_settings_router.get(
|
||||
path='/nodes',
|
||||
summary='获取用户可选节点',
|
||||
description='Get user selectable nodes.',
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
def router_user_settings_nodes() -> models.response.ResponseBase:
|
||||
"""
|
||||
Get user selectable nodes.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing available nodes for the user.
|
||||
"""
|
||||
pass
|
||||
|
||||
@user_settings_router.get(
|
||||
path='/tasks',
|
||||
summary='任务队列',
|
||||
description='Get user task queue.',
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
def router_user_settings_tasks() -> models.response.ResponseBase:
|
||||
"""
|
||||
Get user task queue.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the user's task queue information.
|
||||
"""
|
||||
pass
|
||||
|
||||
@user_settings_router.get(
|
||||
path='/',
|
||||
summary='获取当前用户设定',
|
||||
description='Get current user settings.',
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
def router_user_settings() -> models.response.ResponseBase:
|
||||
"""
|
||||
Get current user settings.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the current user settings.
|
||||
"""
|
||||
return models.response.ResponseBase(data=models.UserSettingResponse().model_dump())
|
||||
|
||||
@user_settings_router.post(
|
||||
path='/avatar',
|
||||
summary='从文件上传头像',
|
||||
description='Upload user avatar from file.',
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
def router_user_settings_avatar() -> models.response.ResponseBase:
|
||||
"""
|
||||
Upload user avatar from file.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the result of the avatar upload.
|
||||
"""
|
||||
pass
|
||||
|
||||
@user_settings_router.put(
|
||||
path='/avatar',
|
||||
summary='设定为Gravatar头像',
|
||||
description='Set user avatar to Gravatar.',
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
def router_user_settings_avatar_gravatar() -> models.response.ResponseBase:
|
||||
"""
|
||||
Set user avatar to Gravatar.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the result of setting the Gravatar avatar.
|
||||
"""
|
||||
pass
|
||||
|
||||
@user_settings_router.patch(
|
||||
path='/{option}',
|
||||
summary='更新用户设定',
|
||||
description='Update user settings.',
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
def router_user_settings_patch(option: str) -> models.response.ResponseBase:
|
||||
"""
|
||||
Update user settings.
|
||||
|
||||
Args:
|
||||
option (str): The setting option to update.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the result of the settings update.
|
||||
"""
|
||||
pass
|
||||
|
||||
@user_settings_router.get(
|
||||
path='/2fa',
|
||||
summary='获取两步验证初始化信息',
|
||||
description='Get two-factor authentication initialization information.',
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
async def router_user_settings_2fa(
|
||||
user: Annotated[models.user.User, Depends(AuthRequired)],
|
||||
) -> models.response.ResponseBase:
|
||||
"""
|
||||
Get two-factor authentication initialization information.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing two-factor authentication setup information.
|
||||
"""
|
||||
|
||||
return models.response.ResponseBase(
|
||||
data=await Password.generate_totp(user.username)
|
||||
)
|
||||
|
||||
@user_settings_router.post(
|
||||
path='/2fa',
|
||||
summary='启用两步验证',
|
||||
description='Enable two-factor authentication.',
|
||||
dependencies=[Depends(AuthRequired)],
|
||||
)
|
||||
async def router_user_settings_2fa_enable(
|
||||
session: SessionDep,
|
||||
user: Annotated[models.user.User, Depends(AuthRequired)],
|
||||
setup_token: str,
|
||||
code: str,
|
||||
) -> models.response.ResponseBase:
|
||||
"""
|
||||
Enable two-factor authentication for the user.
|
||||
|
||||
Returns:
|
||||
dict: A dictionary containing the result of enabling two-factor authentication.
|
||||
"""
|
||||
|
||||
serializer = URLSafeTimedSerializer(SECRET_KEY)
|
||||
|
||||
try:
|
||||
# 1. 解包 Token,设置有效期(例如 600秒)
|
||||
secret = serializer.loads(setup_token, salt="2fa-setup-salt", max_age=600)
|
||||
except SignatureExpired:
|
||||
raise HTTPException(status_code=400, detail="Setup session expired")
|
||||
except BadSignature:
|
||||
raise HTTPException(status_code=400, detail="Invalid token")
|
||||
|
||||
# 2. 验证用户输入的 6 位验证码
|
||||
if not Password.verify_totp(secret, code):
|
||||
raise HTTPException(status_code=400, detail="Invalid OTP code")
|
||||
|
||||
# 3. 将 secret 存储到用户的数据库记录中,启用 2FA
|
||||
user.two_factor = secret
|
||||
user = await user.save(session)
|
||||
|
||||
return models.response.ResponseBase(
|
||||
data={"message": "Two-factor authentication enabled successfully"}
|
||||
)
|
||||
104
routers/api/v1/vas/__init__.py
Normal file
104
routers/api/v1/vas/__init__.py
Normal file
@@ -0,0 +1,104 @@
|
||||
from fastapi import APIRouter, Depends
|
||||
from middleware.auth import SignRequired
|
||||
from models.response import ResponseBase
|
||||
|
||||
vas_router = APIRouter(
|
||||
prefix="/vas",
|
||||
tags=["vas"]
|
||||
)
|
||||
|
||||
@vas_router.get(
|
||||
path='/pack',
|
||||
summary='获取容量包及配额信息',
|
||||
description='Get information about storage packs and quotas.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_vas_pack() -> ResponseBase:
|
||||
"""
|
||||
Get information about storage packs and quotas.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for storage packs and quotas.
|
||||
"""
|
||||
pass
|
||||
|
||||
@vas_router.get(
|
||||
path='/product',
|
||||
summary='获取商品信息,同时返回支付信息',
|
||||
description='Get product information along with payment details.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_vas_product() -> ResponseBase:
|
||||
"""
|
||||
Get product information along with payment details.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for products and payment information.
|
||||
"""
|
||||
pass
|
||||
|
||||
@vas_router.post(
|
||||
path='/order',
|
||||
summary='新建支付订单',
|
||||
description='Create an order for a product.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_vas_order() -> ResponseBase:
|
||||
"""
|
||||
Create an order for a product.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the created order.
|
||||
"""
|
||||
pass
|
||||
|
||||
@vas_router.get(
|
||||
path='/order/{id}',
|
||||
summary='查询订单状态',
|
||||
description='Get information about a specific payment order by ID.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
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:
|
||||
ResponseModel: A model containing the response data for the specified order.
|
||||
"""
|
||||
pass
|
||||
|
||||
@vas_router.get(
|
||||
path='/redeem',
|
||||
summary='获取兑换码信息',
|
||||
description='Get information about a specific redemption code.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
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:
|
||||
ResponseModel: A model containing the response data for the specified redemption code.
|
||||
"""
|
||||
pass
|
||||
|
||||
@vas_router.post(
|
||||
path='/redeem',
|
||||
summary='执行兑换',
|
||||
description='Redeem a redemption code for a product or service.',
|
||||
dependencies=[Depends(SignRequired)]
|
||||
)
|
||||
def router_vas_redeem_post() -> ResponseBase:
|
||||
"""
|
||||
Redeem a redemption code for a product or service.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the redeemed code.
|
||||
"""
|
||||
pass
|
||||
108
routers/api/v1/webdav/__init__.py
Normal file
108
routers/api/v1/webdav/__init__.py
Normal file
@@ -0,0 +1,108 @@
|
||||
from fastapi import APIRouter, Depends, Request
|
||||
from middleware.auth import SignRequired
|
||||
from models.response import ResponseBase
|
||||
|
||||
# WebDAV 管理路由
|
||||
webdav_router = APIRouter(
|
||||
prefix='/webdav',
|
||||
tags=["webdav"],
|
||||
)
|
||||
|
||||
@webdav_router.get(
|
||||
path='/accounts',
|
||||
summary='获取账号信息',
|
||||
description='Get account information for WebDAV.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_webdav_accounts() -> ResponseBase:
|
||||
"""
|
||||
Get account information for WebDAV.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the account information.
|
||||
"""
|
||||
pass
|
||||
|
||||
@webdav_router.post(
|
||||
path='/accounts',
|
||||
summary='新建账号',
|
||||
description='Create a new WebDAV account.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_webdav_create_account() -> ResponseBase:
|
||||
"""
|
||||
Create a new WebDAV account.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the created account.
|
||||
"""
|
||||
pass
|
||||
|
||||
@webdav_router.delete(
|
||||
path='/accounts/{id}',
|
||||
summary='删除账号',
|
||||
description='Delete a WebDAV account by its ID.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_webdav_delete_account(id: str) -> ResponseBase:
|
||||
"""
|
||||
Delete a WebDAV account by its ID.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the account to be deleted.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the deletion operation.
|
||||
"""
|
||||
pass
|
||||
|
||||
@webdav_router.post(
|
||||
path='/mount',
|
||||
summary='新建目录挂载',
|
||||
description='Create a new WebDAV mount point.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_webdav_create_mount() -> ResponseBase:
|
||||
"""
|
||||
Create a new WebDAV mount point.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the created mount point.
|
||||
"""
|
||||
pass
|
||||
|
||||
@webdav_router.delete(
|
||||
path='/mount/{id}',
|
||||
summary='删除目录挂载',
|
||||
description='Delete a WebDAV mount point by its ID.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_webdav_delete_mount(id: str) -> ResponseBase:
|
||||
"""
|
||||
Delete a WebDAV mount point by its ID.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the mount point to be deleted.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the deletion operation.
|
||||
"""
|
||||
pass
|
||||
|
||||
@webdav_router.patch(
|
||||
path='accounts/{id}',
|
||||
summary='更新账号信息',
|
||||
description='Update WebDAV account information by ID.',
|
||||
dependencies=[Depends(SignRequired)],
|
||||
)
|
||||
def router_webdav_update_account(id: str) -> ResponseBase:
|
||||
"""
|
||||
Update WebDAV account information by ID.
|
||||
|
||||
Args:
|
||||
id (str): The ID of the account to be updated.
|
||||
|
||||
Returns:
|
||||
ResponseModel: A model containing the response data for the updated account.
|
||||
"""
|
||||
pass
|
||||
Reference in New Issue
Block a user