from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy import select, and_ from typing import List from app.database import get_db from app.models.folder import Folder from app.models.user import User from app.schemas.folder import FolderCreate, FolderUpdate, FolderOut, FolderTreeOut from app.routers.auth import get_current_user router = APIRouter(prefix="/api/folders", tags=["文件夹"]) def build_folder_tree(folders: list[Folder], parent_id: str = None) -> List[FolderTreeOut]: """递归构建文件夹树""" tree = [] for folder in folders: if folder.parent_id == parent_id: children = build_folder_tree(folders, folder.id) tree.append(FolderTreeOut( id=folder.id, name=folder.name, parent_id=folder.parent_id, children=children )) return tree @router.get("", response_model=List[FolderTreeOut]) async def get_folders( db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """获取用户的完整文件夹树""" result = await db.execute( select(Folder).where(Folder.user_id == current_user.id) ) folders = result.scalars().all() return build_folder_tree(list(folders)) @router.post("", response_model=FolderOut, status_code=status.HTTP_201_CREATED) async def create_folder( folder_data: FolderCreate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """创建文件夹""" # 验证父文件夹存在且属于当前用户 if folder_data.parent_id: result = await db.execute( select(Folder).where( and_(Folder.id == folder_data.parent_id, Folder.user_id == current_user.id) ) ) if not result.scalar_one_or_none(): raise HTTPException(status_code=404, detail="父文件夹不存在") # 检查同名文件夹 result = await db.execute( select(Folder).where( and_( Folder.user_id == current_user.id, Folder.parent_id == folder_data.parent_id, Folder.name == folder_data.name ) ) ) if result.scalar_one_or_none(): raise HTTPException(status_code=400, detail="同名文件夹已存在") folder = Folder( user_id=current_user.id, name=folder_data.name, parent_id=folder_data.parent_id ) db.add(folder) await db.commit() await db.refresh(folder) return folder @router.put("/{folder_id}", response_model=FolderOut) async def rename_folder( folder_id: str, folder_data: FolderUpdate, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """重命名文件夹""" result = await db.execute( select(Folder).where( and_(Folder.id == folder_id, Folder.user_id == current_user.id) ) ) folder = result.scalar_one_or_none() if not folder: raise HTTPException(status_code=404, detail="文件夹不存在") folder.name = folder_data.name await db.commit() await db.refresh(folder) return folder @router.delete("/{folder_id}", status_code=status.HTTP_204_NO_CONTENT) async def delete_folder( folder_id: str, db: AsyncSession = Depends(get_db), current_user: User = Depends(get_current_user) ): """删除文件夹(级联删除文档)""" from app.models.document import Document from app.services.knowledge_service import KnowledgeService result = await db.execute( select(Folder).where( and_(Folder.id == folder_id, Folder.user_id == current_user.id) ) ) folder = result.scalar_one_or_none() if not folder: raise HTTPException(status_code=404, detail="文件夹不存在") async def delete_recursive(fid: str): # 删除子文件夹(先递归) children = await db.execute( select(Folder).where(Folder.parent_id == fid) ) for child in children.scalars(): await delete_recursive(child.id) # 删除文档 docs = await db.execute( select(Document).where(Document.folder_id == fid) ) for doc in docs.scalars(): knowledge_service = KnowledgeService(db, current_user.id) await knowledge_service.delete_from_vectorstore(current_user.id, doc.id) await db.delete(doc) # 删除文件夹本身 folder_to_delete = await db.get(Folder, fid) if folder_to_delete: await db.delete(folder_to_delete) await delete_recursive(folder_id) await db.commit()