feat: 更新模型以支持 UUID,添加注册请求 DTO,重构用户注册逻辑

This commit is contained in:
2025-12-19 16:32:49 +08:00
parent e031f3cc40
commit 922692b820
17 changed files with 380 additions and 147 deletions

View File

@@ -37,7 +37,7 @@ async def router_directory_get(
:param path: 目录路径
:return: 目录内容
"""
folder = await Object.get_by_path(session, user.id, path or "/")
folder = await Object.get_by_path(session, user.id, path or "/", user.username)
if not folder:
raise HTTPException(status_code=404, detail="目录不存在")
@@ -50,7 +50,7 @@ async def router_directory_get(
objects = [
ObjectResponse(
id=str(child.id),
id=child.id,
name=child.name,
path=f"/{child.name}", # TODO: 完整路径
thumb=False,
@@ -63,7 +63,7 @@ async def router_directory_get(
for child in children
]
policy=PolicyResponse(
policy_response = PolicyResponse(
id=str(policy.id),
name=policy.name,
type=policy.type.value,
@@ -71,9 +71,10 @@ async def router_directory_get(
)
return DirectoryResponse(
parent=str(folder.parent_id) if folder.parent_id else None,
id=folder.id,
parent=folder.parent_id,
objects=objects,
policy=policy,
policy=policy_response,
)
@@ -91,26 +92,20 @@ async def router_directory_create(
:param session: 数据库会话
:param user: 当前登录用户
:param request: 创建请求
:param request: 创建请求(包含 parent_id UUID 和 name
:return: 创建结果
"""
path = request.path.strip()
if not path or path == "/":
raise HTTPException(status_code=400, detail="路径不能为空或根目录")
# 验证目录名称
name = request.name.strip()
if not name:
raise HTTPException(status_code=400, detail="目录名称不能为空")
# 解析路径
if path.startswith("/"):
path = path[1:]
parts = [p for p in path.split("/") if p]
if "/" in name or "\\" in name:
raise HTTPException(status_code=400, detail="目录名称不能包含斜杠")
if not parts:
raise HTTPException(status_code=400, detail="无效的目录路径")
new_folder_name = parts[-1]
parent_path = "/" + "/".join(parts[:-1]) if len(parts) > 1 else "/"
parent = await Object.get_by_path(session, user.id, parent_path)
if not parent:
# 通过 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:
@@ -121,25 +116,29 @@ async def router_directory_create(
session,
(Object.owner_id == user.id) &
(Object.parent_id == parent.id) &
(Object.name == new_folder_name)
(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 = await Object(
name=new_folder_name,
new_folder = Object(
name=name,
type=ObjectType.FOLDER,
owner_id=user.id,
parent_id=parent.id,
parent_id=parent_id,
policy_id=policy_id,
).save(session)
)
new_folder_id = new_folder.id # 在 save 前保存 UUID
new_folder_name = new_folder.name
await new_folder.save(session)
return response.ResponseModel(
data={
"id": new_folder.id,
"name": new_folder.name,
"path": f"{parent_path.rstrip('/')}/{new_folder_name}",
"id": new_folder_id,
"name": new_folder_name,
"parent_id": parent_id,
}
)

View File

@@ -1,5 +1,10 @@
from fastapi import APIRouter, Depends
from middleware.auth import SignRequired
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 ResponseModel
object_router = APIRouter(
@@ -7,41 +12,106 @@ object_router = APIRouter(
tags=["object"]
)
@object_router.delete(
path='/',
summary='删除对象',
description='Delete an object endpoint.',
dependencies=[Depends(SignRequired)]
description='删除一个或多个对象(文件或目录)',
)
def router_object_delete() -> ResponseModel:
async def router_object_delete(
session: SessionDep,
user: Annotated[User, Depends(AuthRequired)],
request: ObjectDeleteRequest,
) -> ResponseModel:
"""
Delete an object endpoint.
Returns:
ResponseModel: A model containing the response data for the object deletion.
删除对象端点
:param session: 数据库会话
:param user: 当前登录用户
:param request: 删除请求包含待删除对象的UUID列表
:return: 删除结果
"""
pass
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 ResponseModel(
data={
"deleted": deleted_count,
"total": len(request.ids),
}
)
@object_router.patch(
path='/',
summary='移动对象',
description='Move an object endpoint.',
dependencies=[Depends(SignRequired)]
description='移动一个或多个对象到目标目录',
)
def router_object_move() -> ResponseModel:
async def router_object_move(
session: SessionDep,
user: Annotated[User, Depends(AuthRequired)],
request: ObjectMoveRequest,
) -> ResponseModel:
"""
Move an object endpoint.
Returns:
ResponseModel: A model containing the response data for the object move.
移动对象端点
:param session: 数据库会话
:param user: 当前登录用户
:param request: 移动请求包含源对象UUID列表和目标目录UUID
:return: 移动结果
"""
pass
# 验证目标目录
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 ResponseModel(
data={
"moved": moved_count,
"total": len(request.src_ids),
}
)
@object_router.post(
path='/copy',
summary='复制对象',
description='Copy an object endpoint.',
dependencies=[Depends(SignRequired)]
dependencies=[Depends(AuthRequired)]
)
def router_object_copy() -> ResponseModel:
"""
@@ -56,7 +126,7 @@ def router_object_copy() -> ResponseModel:
path='/rename',
summary='重命名对象',
description='Rename an object endpoint.',
dependencies=[Depends(SignRequired)]
dependencies=[Depends(AuthRequired)]
)
def router_object_rename() -> ResponseModel:
"""
@@ -71,7 +141,7 @@ def router_object_rename() -> ResponseModel:
path='/property/{id}',
summary='获取对象属性',
description='Get object properties endpoint.',
dependencies=[Depends(SignRequired)]
dependencies=[Depends(AuthRequired)]
)
def router_object_property(id: str) -> ResponseModel:
"""

View File

@@ -1,11 +1,11 @@
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
import pyotp
from itsdangerous import URLSafeTimedSerializer, BadSignature, SignatureExpired
import models
@@ -93,14 +93,77 @@ async def router_user_session(
summary='用户注册',
description='User registration endpoint.',
)
def router_user_register() -> models.response.ResponseModel:
async def router_user_register(
session: SessionDep,
request: models.RegisterRequest,
) -> models.response.ResponseModel:
"""
User registration endpoint.
Returns:
dict: A dictionary containing user registration information.
用户注册端点
流程:
1. 验证用户名唯一性
2. 获取默认用户组
3. 创建用户记录
4. 创建以用户名命名的根目录
:param session: 数据库会话
:param request: 注册请求
:return: 注册结果
:raises HTTPException 400: 用户名已存在
:raises HTTPException 500: 默认用户组或存储策略不存在
"""
pass
# 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.ResponseModel(
data={
"user_id": new_user_id,
"username": new_user_username,
},
msg="注册成功",
)
@user_router.post(
path='/code',