单元测试:新建用户与用户组

This commit is contained in:
2025-07-14 15:13:05 +08:00
parent e011a1ea0e
commit 557a50f539
26 changed files with 396 additions and 242 deletions

View File

@@ -69,7 +69,7 @@ def get_excluded_dirs(exclude_arg: str) -> Set[str]:
def clean_pycache(root_dir: str, exclude_dirs: Set[str], dry_run: bool = False) -> List[str]:
"""清理 __pycache__ 目录"""
log.info("开始清理 __pycache__ 目录...")
log.info("开始清理 __pycache__ 目录pass")
cleaned_paths = []
for dirpath, dirnames, _ in os.walk(root_dir):
@@ -90,7 +90,7 @@ def clean_pycache(root_dir: str, exclude_dirs: Set[str], dry_run: bool = False)
def clean_pyc_files(root_dir: str, exclude_dirs: Set[str], dry_run: bool = False) -> List[str]:
"""清理 .pyc 文件"""
log.info("开始清理 .pyc 文件...")
log.info("开始清理 .pyc 文件pass")
cleaned_files = []
for dirpath, dirnames, filenames in os.walk(root_dir):
@@ -112,7 +112,7 @@ def clean_pyc_files(root_dir: str, exclude_dirs: Set[str], dry_run: bool = False
def clean_pytest_cache(root_dir: str, exclude_dirs: Set[str], dry_run: bool = False) -> List[str]:
"""清理 .pytest_cache 目录"""
log.info("开始清理 .pytest_cache 目录...")
log.info("开始清理 .pytest_cache 目录pass")
cleaned_paths = []
for dirpath, dirnames, _ in os.walk(root_dir):
@@ -133,7 +133,7 @@ def clean_pytest_cache(root_dir: str, exclude_dirs: Set[str], dry_run: bool = Fa
def clean_nicegui(root_dir: str, dry_run: bool = False) -> Tuple[bool, str]:
"""清理 .nicegui 目录"""
log.info("开始清理 .nicegui 目录...")
log.info("开始清理 .nicegui 目录pass")
nicegui_dir = os.path.join(root_dir, ".nicegui")
if os.path.exists(nicegui_dir) and os.path.isdir(nicegui_dir):
success, error = safe_remove(nicegui_dir, dry_run)
@@ -146,7 +146,7 @@ def clean_nicegui(root_dir: str, dry_run: bool = False) -> Tuple[bool, str]:
def clean_testdb(root_dir: str, dry_run: bool = False) -> Tuple[bool, str, str]:
"""清理测试数据库文件"""
log.info("开始清理 test.db 文件...")
log.info("开始清理 test.db 文件pass")
test_db = os.path.join(root_dir, "test.db")
if os.path.exists(test_db) and os.path.isfile(test_db):
success, error = safe_remove(test_db, dry_run)

10
main.py
View File

@@ -4,10 +4,14 @@ from pkg.conf import appmeta
from models.database import init_db
from models.migration import init_default_settings
from pkg.lifespan import lifespan
from pkg.JWT import jwt
# 添加初始化数据库启动项
lifespan.add_startup(init_db)
lifespan.add_startup(init_default_settings)
lifespan.add_startup(jwt.load_secret_key)
# 创建应用实例并设置元数据
app = FastAPI(
title=appmeta.APP_NAME,
summary=appmeta.summary,
@@ -18,6 +22,7 @@ app = FastAPI(
lifespan=lifespan.lifespan,
)
# 挂载路由
for router in routers.Router:
app.include_router(
router,
@@ -30,7 +35,8 @@ for router in routers.Router:
500: {"description": "内部服务器错误 Internal server error"}
},)
# 启动时打印欢迎信息
if __name__ == "__main__":
import uvicorn
uvicorn.run(app=app, host="0.0.0.0", port=5213)
# uvicorn.run(app='main:app', host="0.0.0.0", port=5213, reload=True)
# uvicorn.run(app=app, host="0.0.0.0", port=5213) # 生产环境
uvicorn.run(app='main:app', host="0.0.0.0", port=5213, reload=True) # 开发环境

View File

@@ -8,7 +8,7 @@ async def AuthRequired(
'''
AuthRequired 需要登录
'''
return True
from models.user import User
async def SignRequired(
token: Annotated[str, Depends(jwt.oauth2_scheme)]
@@ -27,4 +27,4 @@ async def AdminRequired(
使用方法:
>>> APIRouter(dependencies=[Depends(is_admin)])
'''
...
pass

View File

@@ -6,7 +6,7 @@ from sqlmodel.ext.asyncio.session import AsyncSession
from sqlalchemy.orm import sessionmaker
from typing import AsyncGenerator
ASYNC_DATABASE_URL = "sqlite+aiosqlite:///database.db"
ASYNC_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
engine = create_async_engine(
ASYNC_DATABASE_URL,

View File

@@ -49,43 +49,58 @@ class Group(BaseModel, table=True):
sa_relationship_kwargs={"foreign_keys": "User.previous_group_id"}
)
async def add_group(self, name: str, policies: Optional[str] = None, max_storage: int = 0,
share_enabled: bool = False, web_dav_enabled: bool = False,
speed_limit: int = 0, options: Optional[str] = None) -> "Group":
@staticmethod
async def create(
group: "Group"
) -> "Group":
"""
向数据库内添加用户组。
:param name: 用户组
:type name: str
:param policies: 允许的策略ID列表逗号分隔默认为 None
:type policies: Optional[str]
:param max_storage: 最大存储空间(字节),默认为 0
:type max_storage: int
:param share_enabled: 是否允许创建分享,默认为 False
:type share_enabled: bool
:param web_dav_enabled: 是否允许使用WebDAV默认为 False
:type web_dav_enabled: bool
:param speed_limit: 速度限制 (KB/s), 0为不限制默认为 0
:type speed_limit: int
:param options: 其他选项 (JSON格式),默认为 None
:type options: Optional[str]
:param group: 用户组对象
:type group: Group
:return: 新创建的用户组对象
:rtype: Group
"""
from .database import get_session
async for session in get_session():
try:
session.add(group)
await session.commit()
await session.refresh(group)
except Exception as e:
await session.rollback()
raise e
return group
@staticmethod
async def get(
id: int = None
) -> Optional["Group"]:
"""
获取用户组信息。
:param id: 用户组ID默认为 None
:type id: int
:return: 用户组对象或 None
:rtype: Optional[Group]
"""
from .database import get_session
from sqlmodel import select
session = get_session()
new_group = Group(
name=name,
policies=policies,
max_storage=max_storage,
share_enabled=share_enabled,
web_dav_enabled=web_dav_enabled,
speed_limit=speed_limit,
options=options
)
session.add(new_group)
if id is None:
return None
session.commit()
session.refresh(new_group)
async for session in get_session():
statement = select(Group).where(Group.id == id)
result = await session.exec(statement)
group = result.one_or_none()
if group:
return group
else:
return None

View File

@@ -113,4 +113,55 @@ async def init_default_settings() -> None:
type=setting.type,
name=setting.name,
value=setting.value
)
)
async def init_default_group() -> None:
from .group import Group
try:
# 未找到初始管理组时,则创建
if not Group.get(id=1):
Group.add(
name="管理员",
max_storage=1 * 1024 * 1024 * 1024, # 1GB
share_enabled=True,
web_dav_enabled=True,
options={
"ArchiveDownload": True,
"ArchiveTask": True,
"ShareDownload": True,
"Aria2": True,
}
)
except Exception as e:
raise RuntimeError(f"无法创建管理员用户组: {e}") from e
try:
# 未找到初始注册会员时,则创建
if not Group.get(id=2):
Group.add(
name="注册会员",
max_storage=1 * 1024 * 1024 * 1024, # 1GB
share_enabled=True,
web_dav_enabled=True,
options={
"ShareDownload": True,
}
)
except Exception as e:
raise RuntimeError(f"无法创建初始注册会员用户组: {e}") from e
try:
# 未找到初始游客组时,则创建
if not Group.get(id=3):
Group.add(
name="游客",
policies="[]",
share_enabled=False,
web_dav_enabled=False,
options={
"ShareDownload": True,
}
)
except Exception as e:
raise RuntimeError(f"无法创建初始游客用户组: {e}") from e

View File

@@ -71,6 +71,7 @@ class Setting(BaseModel, table=True):
),
)
@staticmethod
async def add(
type: SETTINGS_TYPE = None,
name: str = None,
@@ -97,6 +98,7 @@ class Setting(BaseModel, table=True):
await session.commit()
@staticmethod
async def get(
type: SETTINGS_TYPE,
name: str,
@@ -138,6 +140,7 @@ class Setting(BaseModel, table=True):
else:
raise ValueError(f"Unsupported format: {format}")
@staticmethod
async def set(
type: SETTINGS_TYPE,
name: str,
@@ -177,6 +180,7 @@ class Setting(BaseModel, table=True):
setting.value = value
await session.commit()
@staticmethod
async def delete(
type: SETTINGS_TYPE,
name: str

View File

@@ -86,4 +86,25 @@ class User(BaseModel, table=True):
storage_packs: list["StoragePack"] = Relationship(back_populates="user")
tags: list["Tag"] = Relationship(back_populates="user")
tasks: list["Task"] = Relationship(back_populates="user")
webdavs: list["WebDAV"] = Relationship(back_populates="user")
webdavs: list["WebDAV"] = Relationship(back_populates="user")
async def create(
user: "User"
):
"""
向数据库内添加用户。
:param user: User 实例
:type user: User
"""
from .database import get_session
async for session in get_session():
try:
session.add(user)
await session.commit()
await session.refresh(user)
except Exception as e:
await session.rollback()
raise e
return user

View File

@@ -1,4 +1,5 @@
from fastapi.security import OAuth2PasswordBearer
from models.setting import Setting
oauth2_scheme = OAuth2PasswordBearer(
scheme_name='获取 JWT Bearer 令牌',
@@ -6,4 +7,14 @@ oauth2_scheme = OAuth2PasswordBearer(
tokenUrl="/api/user/session",
)
SECRET_KEY = ''
SECRET_KEY = ''
async def load_secret_key() -> None:
"""
从数据库读取 JWT 的密钥。
:param key: 用于加密和解密 JWT 的密钥
:type key: str
"""
global SECRET_KEY
SECRET_KEY = await Setting.get(type='auth', name='secret_key')

View File

@@ -70,7 +70,7 @@ def router_admin_get_summary() -> ResponseModel:
Returns:
ResponseModel: 包含站点概况信息的响应模型。
"""
...
pass
@admin_router.get(
path='/news',
@@ -85,7 +85,7 @@ def router_admin_get_news() -> ResponseModel:
Returns:
ResponseModel: 包含社区新闻信息的响应模型。
"""
...
pass
@admin_router.patch(
path='/settings',
@@ -100,7 +100,7 @@ def router_admin_update_settings() -> ResponseModel:
Returns:
ResponseModel: 包含更新结果的响应模型。
"""
...
pass
@admin_router.get(
path='/settings',
@@ -115,7 +115,7 @@ def router_admin_get_settings() -> ResponseModel:
Returns:
ResponseModel: 包含站点设置的响应模型。
"""
...
pass
@admin_group_router.get(
path='/',
@@ -130,7 +130,7 @@ def router_admin_get_groups() -> ResponseModel:
Returns:
ResponseModel: 包含用户组列表的响应模型。
"""
...
pass
@admin_group_router.get(
path='/{group_id}',
@@ -148,7 +148,7 @@ def router_admin_get_group(group_id: int) -> ResponseModel:
Returns:
ResponseModel: 包含用户组信息的响应模型。
"""
...
pass
@admin_group_router.get(
path='/list/{group_id}',
@@ -172,7 +172,7 @@ def router_admin_get_group_members(
Returns:
ResponseModel: 包含用户组成员列表的响应模型。
"""
...
pass
@admin_group_router.post(
path='/',
@@ -187,7 +187,7 @@ def router_admin_create_group() -> ResponseModel:
Returns:
ResponseModel: 包含创建结果的响应模型。
"""
...
pass
@admin_group_router.patch(
path='/{group_id}',
@@ -205,7 +205,7 @@ def router_admin_update_group(group_id: int) -> ResponseModel:
Returns:
ResponseModel: 包含更新结果的响应模型。
"""
...
pass
@admin_group_router.delete(
path='/{group_id}',
@@ -223,7 +223,7 @@ def router_admin_delete_group(group_id: int) -> ResponseModel:
Returns:
ResponseModel: 包含删除结果的响应模型。
"""
...
pass
@admin_user_router.get(
path='/info/{user_id}',
@@ -241,7 +241,7 @@ def router_admin_get_user(user_id: int) -> ResponseModel:
Returns:
ResponseModel: 包含用户信息的响应模型。
"""
...
pass
@admin_user_router.get(
path='/list',
@@ -263,7 +263,7 @@ def router_admin_get_users(
Returns:
ResponseModel: 包含用户列表的响应模型。
"""
...
pass
@admin_user_router.post(
path='/create',
@@ -278,7 +278,7 @@ def router_admin_create_user() -> ResponseModel:
Returns:
ResponseModel: 包含创建结果的响应模型。
"""
...
pass
@admin_user_router.patch(
path='/{user_id}',
@@ -296,7 +296,7 @@ def router_admin_update_user(user_id: int) -> ResponseModel:
Returns:
ResponseModel: 包含更新结果的响应模型。
"""
...
pass
@admin_user_router.delete(
path='/{user_id}',
@@ -314,7 +314,7 @@ def router_admin_delete_user(user_id: int) -> ResponseModel:
Returns:
ResponseModel: 包含删除结果的响应模型。
"""
...
pass
@admin_user_router.post(
path='/calibrate/{user_id}',
@@ -323,7 +323,7 @@ def router_admin_delete_user(user_id: int) -> ResponseModel:
dependencies=[Depends(AdminRequired)]
)
def router_admin_calibrate_storage():
...
pass
@admin_file_router.get(
path='/list',
@@ -338,7 +338,7 @@ def router_admin_get_file_list() -> ResponseModel:
Returns:
ResponseModel: 包含文件列表的响应模型。
"""
...
pass
@admin_file_router.get(
path='/preview/{file_id}',
@@ -356,7 +356,7 @@ def router_admin_preview_file(file_id: int) -> ResponseModel:
Returns:
ResponseModel: 包含文件预览内容的响应模型。
"""
...
pass
@admin_file_router.patch(
path='/ban/{file_id}',
@@ -376,7 +376,7 @@ def router_admin_ban_file(file_id: int) -> ResponseModel:
Returns:
ResponseModel: 包含删除结果的响应模型。
"""
...
pass
@admin_file_router.delete(
path='/{file_id}',
@@ -394,7 +394,7 @@ def router_admin_delete_file(file_id: int) -> ResponseModel:
Returns:
ResponseModel: 包含删除结果的响应模型。
"""
...
pass
@admin_aria2_router.post(
path='/test',
@@ -403,7 +403,7 @@ def router_admin_delete_file(file_id: int) -> ResponseModel:
dependencies=[Depends(AdminRequired)]
)
def router_admin_aira2_test() -> ResponseModel:
...
pass
@admin_policy_router.get(
path='/list',
@@ -412,7 +412,7 @@ def router_admin_aira2_test() -> ResponseModel:
dependencies=[Depends(AdminRequired)]
)
def router_policy_list() -> ResponseModel:
...
pass
@admin_policy_router.post(
path='/test/path',
@@ -421,7 +421,7 @@ def router_policy_list() -> ResponseModel:
dependencies=[Depends(AdminRequired)]
)
def router_policy_test_path() -> ResponseModel:
...
pass
@admin_policy_router.post(
path='/test/slave',
@@ -430,7 +430,7 @@ def router_policy_test_path() -> ResponseModel:
dependencies=[Depends(AdminRequired)]
)
def router_policy_test_slave() -> ResponseModel:
...
pass
@admin_policy_router.post(
path='/',
@@ -439,7 +439,7 @@ def router_policy_test_slave() -> ResponseModel:
dependencies=[Depends(AdminRequired)]
)
def router_policy_add_policy() -> ResponseModel:
...
pass
@admin_policy_router.post(
path='/cors',
@@ -448,7 +448,7 @@ def router_policy_add_policy() -> ResponseModel:
dependencies=[Depends(AdminRequired)]
)
def router_policy_add_cors() -> ResponseModel:
...
pass
@admin_policy_router.post(
path='/scf',
@@ -457,7 +457,7 @@ def router_policy_add_cors() -> ResponseModel:
dependencies=[Depends(AdminRequired)]
)
def router_policy_add_scf() -> ResponseModel:
...
pass
@admin_policy_router.get(
path='/{id}/oauth',
@@ -466,7 +466,7 @@ def router_policy_add_scf() -> ResponseModel:
dependencies=[Depends(AdminRequired)]
)
def router_policy_onddrive_oauth() -> ResponseModel:
...
pass
@admin_policy_router.get(
path='/{id}',
@@ -475,7 +475,7 @@ def router_policy_onddrive_oauth() -> ResponseModel:
dependencies=[Depends(AdminRequired)]
)
def router_policy_get_policy() -> ResponseModel:
...
pass
@admin_policy_router.delete(
path='/{id}',
@@ -484,4 +484,4 @@ def router_policy_get_policy() -> ResponseModel:
dependencies=[Depends(AdminRequired)]
)
def router_policy_delete_policy() -> ResponseModel:
...
pass

View File

@@ -20,7 +20,7 @@ def router_aria2_url() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the URL download task.
"""
...
pass
@aria2_router.post(
path='/torrent/{id}',
@@ -38,7 +38,7 @@ def router_aria2_torrent(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the torrent download task.
"""
...
pass
@aria2_router.put(
path='/select/{gid}',
@@ -56,7 +56,7 @@ def router_aria2_select(gid: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the re-selection of files.
"""
...
pass
@aria2_router.delete(
path='/task/{gid}',
@@ -74,7 +74,7 @@ def router_aria2_delete(gid: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the deletion of the download task.
"""
...
pass
@aria2_router.get(
'/downloading',
@@ -89,7 +89,7 @@ def router_aria2_downloading() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for currently downloading tasks.
"""
...
pass
@aria2_router.get(
path='/finished',
@@ -104,4 +104,4 @@ def router_aria2_finished() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for finished tasks.
"""
...
pass

View File

@@ -39,7 +39,7 @@ def router_callback_qq() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the QQ OAuth callback.
"""
...
pass
@oauth_router.post(
path='/github',
@@ -53,7 +53,7 @@ def router_callback_github() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the GitHub OAuth callback.
"""
...
pass
@pay_router.post(
path='/alipay',
@@ -67,7 +67,7 @@ def router_callback_alipay() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the Alipay payment callback.
"""
...
pass
@pay_router.post(
path='/wechat',
@@ -81,7 +81,7 @@ def router_callback_wechat() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the WeChat Pay payment callback.
"""
...
pass
@pay_router.post(
path='/stripe',
@@ -95,7 +95,7 @@ def router_callback_stripe() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the Stripe payment callback.
"""
...
pass
@pay_router.get(
path='/easypay',
@@ -109,7 +109,7 @@ def router_callback_easypay() -> PlainTextResponse:
Returns:
PlainTextResponse: A response containing the payment status for the EasyPay payment callback.
"""
...
pass
# return PlainTextResponse("success", status_code=200)
@pay_router.get(
@@ -128,7 +128,7 @@ def router_callback_custom(order_no: str, id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the custom payment callback.
"""
...
pass
@upload_router.post(
path='/remote/{session_id}/{key}',
@@ -146,7 +146,7 @@ def router_callback_remote(session_id: str, key: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the remote upload callback.
"""
...
pass
@upload_router.post(
path='/qiniu/{session_id}',
@@ -163,7 +163,7 @@ def router_callback_qiniu(session_id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the Qiniu Cloud upload callback.
"""
...
pass
@upload_router.post(
path='/tencent/{session_id}',
@@ -180,7 +180,7 @@ def router_callback_tencent(session_id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the Tencent Cloud upload callback.
"""
...
pass
@upload_router.post(
path='/aliyun/{session_id}',
@@ -197,7 +197,7 @@ def router_callback_aliyun(session_id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the Aliyun upload callback.
"""
...
pass
@upload_router.post(
path='/upyun/{session_id}',
@@ -214,7 +214,7 @@ def router_callback_upyun(session_id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the Upyun upload callback.
"""
...
pass
@upload_router.post(
path='/aws/{session_id}',
@@ -231,7 +231,7 @@ def router_callback_aws(session_id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the AWS S3 upload callback.
"""
...
pass
@upload_router.post(
path='/onedrive/finish/{session_id}',
@@ -248,7 +248,7 @@ def router_callback_onedrive_finish(session_id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the OneDrive upload completion callback.
"""
...
pass
@upload_router.get(
path='/ondrive/auth',
@@ -262,7 +262,7 @@ def router_callback_onedrive_auth() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the OneDrive authorization callback.
"""
...
pass
@upload_router.get(
path='/google/auth',
@@ -276,4 +276,4 @@ def router_callback_google_auth() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the Google OAuth completion callback.
"""
...
pass

View File

@@ -20,7 +20,7 @@ def router_directory_create() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the directory creation.
"""
...
pass
@directory_router.get(
path='/{path:path}',
@@ -38,4 +38,4 @@ def router_directory_get(path: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the directory contents.
"""
...
pass

View File

@@ -1,4 +1,5 @@
from fastapi import APIRouter, Depends
from fastapi import APIRouter, Depends, UploadFile
from fastapi.responses import FileResponse
from middleware.auth import SignRequired
from models.response import ResponseModel
@@ -17,7 +18,7 @@ file_upload_router = APIRouter(
summary='文件外链(直接输出文件数据)',
description='Get file external link endpoint.',
)
def router_file_get(id: str, name: str) -> ResponseModel:
def router_file_get(id: str, name: str) -> FileResponse:
"""
Get file external link endpoint.
@@ -26,9 +27,9 @@ def router_file_get(id: str, name: str) -> ResponseModel:
name (str): The name of the file.
Returns:
ResponseModel: A model containing the response data for the file.
FileResponse: A response containing the file data.
"""
...
pass
@file_router.get(
path='/source/{id}/{name}',
@@ -46,7 +47,7 @@ def router_file_source(id: str, name: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file with a redirect.
"""
...
pass
@file_upload_router.get(
path='/download/{id}',
@@ -63,7 +64,7 @@ def router_file_download(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file download.
"""
...
pass
@file_upload_router.get(
path='/archive/{sessionID}/archive.zip',
@@ -80,14 +81,14 @@ def router_file_archive_download(sessionID: str) -> ResponseModel:
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) -> ResponseModel:
def router_file_upload(sessionID: str, index: int, file: UploadFile) -> ResponseModel:
"""
File upload endpoint.
@@ -98,7 +99,7 @@ def router_file_upload(sessionID: str, index: int) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data.
"""
...
pass
@file_upload_router.put(
path='/',
@@ -113,7 +114,7 @@ def router_file_upload_session() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the upload session.
"""
...
pass
@file_upload_router.delete(
path='/{sessionID}',
@@ -131,7 +132,7 @@ def router_file_upload_session_delete(sessionID: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the deletion.
"""
...
pass
@file_upload_router.delete(
path='/',
@@ -146,7 +147,7 @@ def router_file_upload_session_clear() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for clearing all sessions.
"""
...
pass
@file_router.put(
path='/update/{id}',
@@ -164,7 +165,7 @@ def router_file_update(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file update.
"""
...
pass
@file_router.post(
path='/create',
@@ -179,7 +180,7 @@ def router_file_create() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file creation.
"""
...
pass
@file_router.put(
path='/download/{id}',
@@ -197,7 +198,7 @@ def router_file_download(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file download session.
"""
...
pass
@file_router.get(
path='/preview/{id}',
@@ -215,7 +216,7 @@ def router_file_preview(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file preview.
"""
...
pass
@file_router.get(
path='/content/{id}',
@@ -233,7 +234,7 @@ def router_file_content(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the text file content.
"""
...
pass
@file_router.get(
path='/doc/{id}',
@@ -251,7 +252,7 @@ def router_file_doc(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the Office document preview URL.
"""
...
pass
@file_router.get(
path='/thumb/{id}',
@@ -269,7 +270,7 @@ def router_file_thumb(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file thumbnail.
"""
...
pass
@file_router.post(
path='/source/{id}',
@@ -287,7 +288,7 @@ def router_file_source(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file external link.
"""
...
pass
@file_router.post(
path='/archive',
@@ -305,7 +306,7 @@ def router_file_archive(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the archived files.
"""
...
pass
@file_router.post(
path='/compress',
@@ -323,7 +324,7 @@ def router_file_compress(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file compression task.
"""
...
pass
@file_router.post(
path='/decompress',
@@ -341,7 +342,7 @@ def router_file_decompress(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file extraction task.
"""
...
pass
@file_router.post(
path='/relocate',
@@ -359,7 +360,7 @@ def router_file_relocate(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file relocation task.
"""
...
pass
@file_router.get(
path='/search/{type}/{keyword}',
@@ -378,4 +379,4 @@ def router_file_search(type: str, keyword: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the file search.
"""
...
pass

View File

@@ -20,7 +20,7 @@ def router_object_delete() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the object deletion.
"""
...
pass
@object_router.patch(
path='/',
@@ -35,7 +35,7 @@ def router_object_move() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the object move.
"""
...
pass
@object_router.post(
path='/copy',
@@ -50,7 +50,7 @@ def router_object_copy() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the object copy.
"""
...
pass
@object_router.post(
path='/rename',
@@ -65,7 +65,7 @@ def router_object_rename() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the object rename.
"""
...
pass
@object_router.get(
path='/property/{id}',
@@ -83,4 +83,4 @@ def router_object_property(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the object properties.
"""
...
pass

View File

@@ -23,7 +23,7 @@ def router_share_get(info: str, id: str) -> ResponseModel:
Returns:
dict: A dictionary containing shared content information.
"""
...
pass
@share_router.put(
path='/download/{id}',
@@ -40,7 +40,7 @@ def router_share_download(id: str) -> ResponseModel:
Returns:
dict: A dictionary containing download session information.
"""
...
pass
@share_router.get(
path='preview/{id}',
@@ -57,7 +57,7 @@ def router_share_preview(id: str) -> ResponseModel:
Returns:
dict: A dictionary containing preview information.
"""
...
pass
@share_router.get(
path='/doc/{id}',
@@ -74,7 +74,7 @@ def router_share_doc(id: str) -> ResponseModel:
Returns:
dict: A dictionary containing the document preview URL.
"""
...
pass
@share_router.get(
path='/content/{id}',
@@ -91,7 +91,7 @@ def router_share_content(id: str) -> ResponseModel:
Returns:
str: The content of the text file.
"""
...
pass
@share_router.get(
path='/list/{id}/{path:path}',
@@ -109,7 +109,7 @@ def router_share_list(id: str, path: str = '') -> ResponseModel:
Returns:
dict: A dictionary containing directory listing information.
"""
...
pass
@share_router.get(
path='/search/{id}/{type}/{keywords}',
@@ -128,7 +128,7 @@ def router_share_search(id: str, type: str, keywords: str) -> ResponseModel:
Returns:
dict: A dictionary containing search results.
"""
...
pass
@share_router.post(
path='/archive/{id}',
@@ -145,7 +145,7 @@ def router_share_archive(id: str) -> ResponseModel:
Returns:
dict: A dictionary containing archive download information.
"""
...
pass
@share_router.get(
path='/readme/{id}',
@@ -162,7 +162,7 @@ def router_share_readme(id: str) -> ResponseModel:
Returns:
str: The content of the README file.
"""
...
pass
@share_router.get(
path='/thumb/{id}/{file}',
@@ -180,7 +180,7 @@ def router_share_thumb(id: str, file: str) -> ResponseModel:
Returns:
str: A Base64 encoded string of the thumbnail image.
"""
...
pass
@share_router.post(
path='/report/{id}',
@@ -197,7 +197,7 @@ def router_share_report(id: str) -> ResponseModel:
Returns:
dict: A dictionary containing report submission information.
"""
...
pass
@share_router.get(
path='/search',
@@ -215,7 +215,7 @@ def router_share_search_public(keywords: str, type: str = 'all') -> ResponseMode
Returns:
dict: A dictionary containing search results for public shares.
"""
...
pass
#####################
# 需要登录的接口
@@ -234,7 +234,7 @@ def router_share_create() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the new share creation.
"""
...
pass
@share_router.get(
path='/',
@@ -249,7 +249,7 @@ def router_share_list() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the list of shares.
"""
...
pass
@share_router.post(
path='/save/{id}',
@@ -267,7 +267,7 @@ def router_share_save(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the saved share.
"""
...
pass
@share_router.patch(
path='/{id}',
@@ -285,7 +285,7 @@ def router_share_update(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the updated share.
"""
...
pass
@share_router.delete(
path='/{id}',
@@ -303,4 +303,4 @@ def router_share_delete(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the deleted share.
"""
...
pass

View File

@@ -34,7 +34,7 @@ def router_site_captcha():
Returns:
str: A Base64 encoded string of the captcha image.
"""
...
pass
@site_router.get(
path='/config',

View File

@@ -44,7 +44,7 @@ def router_slave_post(data: str) -> ResponseModel:
Returns:
ResponseModel: A response model indicating success.
"""
...
pass
@slave_router.get(
path='/get/{speed}/{path}/{name}',
@@ -62,7 +62,7 @@ def router_slave_download(speed: int, path: str, name: str) -> ResponseModel:
Returns:
ResponseModel: A response model containing download information.
"""
...
pass
@slave_router.get(
path='/download/{sign}',
@@ -80,7 +80,7 @@ def router_slave_download_by_sign(sign: str) -> FileResponse:
Returns:
FileResponse: A response containing the file to be downloaded.
"""
...
pass
@slave_router.get(
path='/source/{speed}/{path}/{name}',
@@ -100,7 +100,7 @@ def router_slave_source(speed: int, path: str, name: str) -> ResponseModel:
Returns:
ResponseModel: A response model containing the external link for the file.
"""
...
pass
@slave_router.get(
path='/source/{sign}',
@@ -118,7 +118,7 @@ def router_slave_source_by_sign(sign: str) -> FileResponse:
Returns:
FileResponse: A response containing the file to be retrieved.
"""
...
pass
@slave_router.get(
path='/thumb/{id}',
@@ -136,7 +136,7 @@ def router_slave_thumb(id: str) -> ResponseModel:
Returns:
ResponseModel: A response model containing the Base64 encoded thumbnail image.
"""
...
pass
@slave_router.delete(
path='/delete',
@@ -154,7 +154,7 @@ def router_slave_delete(path: str) -> ResponseModel:
Returns:
ResponseModel: A response model indicating success or failure of the deletion.
"""
...
pass
@slave_aria2_router.post(
path='/test',
@@ -166,7 +166,7 @@ def router_slave_aria2_test() -> ResponseModel:
"""
Test the connection to the Aria2 service from the slave.
"""
...
pass
@slave_aria2_router.get(
path='/get/{gid}',
@@ -184,7 +184,7 @@ def router_slave_aria2_get(gid: str = None) -> ResponseModel:
Returns:
ResponseModel: A response model containing the task information.
"""
...
pass
@slave_aria2_router.post(
path='/add',
@@ -204,7 +204,7 @@ def router_slave_aria2_add(gid: str, url: str, options: dict = None) -> Response
Returns:
ResponseModel: A response model indicating success or failure of the task addition.
"""
...
pass
@slave_aria2_router.delete(
path='/remove/{gid}',
@@ -222,4 +222,4 @@ def router_slave_aria2_remove(gid: str) -> ResponseModel:
Returns:
ResponseModel: A response model indicating success or failure of the task removal.
"""
...
pass

View File

@@ -20,7 +20,7 @@ def router_tag_create_filter() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the created tag.
"""
...
pass
@tag_router.post(
path='/link',
@@ -35,7 +35,7 @@ def router_tag_create_link() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the created tag.
"""
...
pass
@tag_router.delete(
path='/{id}',
@@ -53,4 +53,4 @@ def router_tag_delete(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the deletion operation.
"""
...
pass

View File

@@ -25,7 +25,7 @@ def router_user_session() -> ResponseModel:
Returns:
dict: A dictionary containing user session information.
"""
...
pass
@user_router.post(
path='/',
@@ -39,7 +39,7 @@ def router_user_register() -> ResponseModel:
Returns:
dict: A dictionary containing user registration information.
"""
...
pass
@user_router.post(
path='/2fa',
@@ -53,7 +53,7 @@ def router_user_2fa() -> ResponseModel:
Returns:
dict: A dictionary containing two-factor authentication information.
"""
...
pass
@user_router.post(
path='/reset',
@@ -67,7 +67,7 @@ def router_user_reset() -> ResponseModel:
Returns:
dict: A dictionary containing information about the password reset email.
"""
...
pass
@user_router.patch(
path='/reset',
@@ -81,7 +81,7 @@ def router_user_reset_patch() -> ResponseModel:
Returns:
dict: A dictionary containing information about the password reset.
"""
...
pass
@user_router.get(
path='/qq',
@@ -95,7 +95,7 @@ def router_user_qq() -> ResponseModel:
Returns:
dict: A dictionary containing QQ login initialization information.
"""
...
pass
@user_router.get(
path='/activate/{id}',
@@ -112,7 +112,7 @@ def router_user_activate(id: str) -> ResponseModel:
Returns:
dict: A dictionary containing activation information.
"""
...
pass
@user_router.get(
path='authn/{username}',
@@ -129,7 +129,7 @@ def router_user_authn(username: str) -> ResponseModel:
Returns:
dict: A dictionary containing WebAuthn initialization information.
"""
...
pass
@user_router.post(
path='authn/finish/{username}',
@@ -146,7 +146,7 @@ def router_user_authn_finish(username: str) -> ResponseModel:
Returns:
dict: A dictionary containing WebAuthn login information.
"""
...
pass
@user_router.get(
path='/profile/{id}',
@@ -163,7 +163,7 @@ def router_user_profile(id: str) -> ResponseModel:
Returns:
dict: A dictionary containing user profile information.
"""
...
pass
@user_router.get(
path='/avatar/{id}/{size}',
@@ -181,7 +181,7 @@ def router_user_avatar(id: str, size: int = 128) -> ResponseModel:
Returns:
str: A Base64 encoded string of the user avatar image.
"""
...
pass
#####################
# 需要登录的接口
@@ -200,7 +200,7 @@ def router_user_me() -> ResponseModel:
Returns:
dict: A dictionary containing user information.
"""
...
pass
@user_router.get(
path='/storage',
@@ -215,7 +215,7 @@ def router_user_storage() -> ResponseModel:
Returns:
dict: A dictionary containing user storage information.
"""
...
pass
@user_router.put(
path='/authn/start',
@@ -230,7 +230,7 @@ def router_user_authn_start() -> ResponseModel:
Returns:
dict: A dictionary containing WebAuthn initialization information.
"""
...
pass
@user_router.put(
path='/authn/finish',
@@ -245,7 +245,7 @@ def router_user_authn_finish() -> ResponseModel:
Returns:
dict: A dictionary containing WebAuthn login information.
"""
...
pass
@user_settings_router.get(
path='/policies',
@@ -259,7 +259,7 @@ def router_user_settings_policies() -> ResponseModel:
Returns:
dict: A dictionary containing available storage policies for the user.
"""
...
pass
@user_settings_router.get(
path='/nodes',
@@ -274,7 +274,7 @@ def router_user_settings_nodes() -> ResponseModel:
Returns:
dict: A dictionary containing available nodes for the user.
"""
...
pass
@user_settings_router.get(
path='/tasks',
@@ -289,7 +289,7 @@ def router_user_settings_tasks() -> ResponseModel:
Returns:
dict: A dictionary containing the user's task queue information.
"""
...
pass
@user_settings_router.get(
path='/',
@@ -304,7 +304,7 @@ def router_user_settings() -> ResponseModel:
Returns:
dict: A dictionary containing the user's current settings.
"""
...
pass
@user_settings_router.post(
path='/avatar',
@@ -319,7 +319,7 @@ def router_user_settings_avatar() -> ResponseModel:
Returns:
dict: A dictionary containing the result of the avatar upload.
"""
...
pass
@user_settings_router.put(
path='/avatar',
@@ -334,7 +334,7 @@ def router_user_settings_avatar_gravatar() -> ResponseModel:
Returns:
dict: A dictionary containing the result of setting the Gravatar avatar.
"""
...
pass
@user_settings_router.patch(
path='/{option}',
@@ -352,7 +352,7 @@ def router_user_settings_patch(option: str) -> ResponseModel:
Returns:
dict: A dictionary containing the result of the settings update.
"""
...
pass
@user_settings_router.get(
path='/2fa',
@@ -367,4 +367,4 @@ def router_user_settings_2fa() -> ResponseModel:
Returns:
dict: A dictionary containing two-factor authentication setup information.
"""
...
pass

View File

@@ -20,7 +20,7 @@ def router_vas_pack() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for storage packs and quotas.
"""
...
pass
@vas_router.get(
path='/product',
@@ -35,7 +35,7 @@ def router_vas_product() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for products and payment information.
"""
...
pass
@vas_router.post(
path='/order',
@@ -50,7 +50,7 @@ def router_vas_order() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the created order.
"""
...
pass
@vas_router.get(
path='/order/{id}',
@@ -68,7 +68,7 @@ def router_vas_order_get(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the specified order.
"""
...
pass
@vas_router.get(
path='/redeem',
@@ -86,7 +86,7 @@ def router_vas_redeem(code: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the specified redemption code.
"""
...
pass
@vas_router.post(
path='/redeem',
@@ -101,4 +101,4 @@ def router_vas_redeem_post() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the redeemed code.
"""
...
pass

View File

@@ -21,7 +21,7 @@ def router_webdav_accounts() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the account information.
"""
...
pass
@webdav_router.post(
path='/accounts',
@@ -36,7 +36,7 @@ def router_webdav_create_account() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the created account.
"""
...
pass
@webdav_router.delete(
path='/accounts/{id}',
@@ -54,7 +54,7 @@ def router_webdav_delete_account(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the deletion operation.
"""
...
pass
@webdav_router.post(
path='/mount',
@@ -69,7 +69,7 @@ def router_webdav_create_mount() -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the created mount point.
"""
...
pass
@webdav_router.delete(
path='/mount/{id}',
@@ -87,7 +87,7 @@ def router_webdav_delete_mount(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the deletion operation.
"""
...
pass
@webdav_router.patch(
path='accounts/{id}',
@@ -105,4 +105,4 @@ def router_webdav_update_account(id: str) -> ResponseModel:
Returns:
ResponseModel: A model containing the response data for the updated account.
"""
...
pass

15
service/user/login.py Normal file
View File

@@ -0,0 +1,15 @@
from models.setting import Setting
async def login(
username: str,
password: str
):
"""
"""
isCaptchaRequired = await Setting.get(type='auth', name='login_captcha', type=bool)
captchaType = await Setting.get(type='auth', name='captcha_type', type=str)
# [TODO] 验证码校验

View File

@@ -25,52 +25,4 @@ async def test_initialize_db():
await database.init_db(url='sqlite:///:memory:')
await migration.init_default_settings()
@pytest.mark.asyncio
async def test_add_settings():
"""测试数据库的增删改查"""
from models import database
from models.setting import Setting
await database.init_db(url='sqlite:///:memory:')
# 测试增 Create
await Setting.add(
type='example_type',
name='example_name',
value='example_value')
# 测试查 Read
setting = await Setting.get(
type='example_type',
name='example_name')
assert setting is not None, "设置项应该存在"
assert setting.value == 'example_value', "设置值不匹配"
# 测试改 Update
await Setting.set(
type='example_type',
name='example_name',
value='updated_value')
after_update_setting = await Setting.get(
type='example_type',
name='example_name'
)
assert after_update_setting is not None, "设置项应该存在"
assert after_update_setting.value == 'updated_value', "更新后的设置值不匹配"
# 测试删 Delete
await Setting.delete(
type='example_type',
name='example_name')
after_delete_setting = await Setting.get(
type='example_type',
name='example_name'
)
assert after_delete_setting is None, "设置项应该被删除"
await migration.init_default_settings()

49
tests/test_db_settings.py Normal file
View File

@@ -0,0 +1,49 @@
import pytest
@pytest.mark.asyncio
async def test_settings_curd():
"""测试数据库的增删改查"""
from models import database
from models.setting import Setting
await database.init_db(url='sqlite:///:memory:')
# 测试增 Create
await Setting.add(
type='example_type',
name='example_name',
value='example_value')
# 测试查 Read
setting = await Setting.get(
type='example_type',
name='example_name')
assert setting is not None, "设置项应该存在"
assert setting == 'example_value', "设置值不匹配"
# 测试改 Update
await Setting.set(
type='example_type',
name='example_name',
value='updated_value')
after_update_setting = await Setting.get(
type='example_type',
name='example_name'
)
assert after_update_setting is not None, "设置项应该存在"
assert after_update_setting == 'updated_value', "更新后的设置值不匹配"
# 测试删 Delete
await Setting.delete(
type='example_type',
name='example_name')
after_delete_setting = await Setting.get(
type='example_type',
name='example_name'
)
assert after_delete_setting is None, "设置项应该被删除"

29
tests/test_db_user.py Normal file
View File

@@ -0,0 +1,29 @@
import pytest
@pytest.mark.asyncio
async def test_user_curd():
"""测试数据库的增删改查"""
from models import database
from models.group import Group
from models.user import User
await database.init_db(url='sqlite:///:memory:')
# 新建一个测试用户组
test_group = Group(name='test_group')
created_group = await Group.create(test_group)
test_user = User(
email='test_user',
password='test_password',
group_id=created_group.id
)
# 测试增 Create
created_user = await User.create(test_user)
# 验证用户是否存在
assert created_user.id is not None
assert created_user.email == 'test_user'
assert created_user.password == 'test_password'
assert created_user.group_id == created_group.id