2026-03-21 10:13:29 +08:00
|
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
2026-04-09 17:26:37 +08:00
|
|
|
from sqlalchemy import and_, select
|
2026-03-21 10:13:29 +08:00
|
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
|
from typing import List
|
2026-04-09 17:26:37 +08:00
|
|
|
import shutil
|
|
|
|
|
|
2026-03-21 10:13:29 +08:00
|
|
|
from app.database import get_db
|
|
|
|
|
from app.models.folder import Folder
|
|
|
|
|
from app.models.user import User
|
2026-03-22 13:47:34 +08:00
|
|
|
from app.routers.auth import get_current_user
|
2026-04-09 17:26:37 +08:00
|
|
|
from app.schemas.folder import FolderCreate, FolderOut, FolderTreeOut, FolderUpdate
|
|
|
|
|
from app.services.document_service import DocumentService
|
2026-03-21 10:13:29 +08:00
|
|
|
|
|
|
|
|
router = APIRouter(prefix="/api/folders", tags=["文件夹"])
|
|
|
|
|
|
2026-04-09 17:26:37 +08:00
|
|
|
|
2026-03-21 10:13:29 +08:00
|
|
|
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,
|
2026-04-09 17:26:37 +08:00
|
|
|
children=children,
|
2026-03-21 10:13:29 +08:00
|
|
|
))
|
|
|
|
|
return tree
|
|
|
|
|
|
2026-04-09 17:26:37 +08:00
|
|
|
|
2026-03-21 10:13:29 +08:00
|
|
|
@router.get("", response_model=List[FolderTreeOut])
|
|
|
|
|
async def get_folders(
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2026-04-09 17:26:37 +08:00
|
|
|
current_user: User = Depends(get_current_user),
|
2026-03-21 10:13:29 +08:00
|
|
|
):
|
|
|
|
|
result = await db.execute(
|
|
|
|
|
select(Folder).where(Folder.user_id == current_user.id)
|
|
|
|
|
)
|
|
|
|
|
folders = result.scalars().all()
|
|
|
|
|
return build_folder_tree(list(folders))
|
|
|
|
|
|
2026-04-09 17:26:37 +08:00
|
|
|
|
2026-03-21 10:13:29 +08:00
|
|
|
@router.post("", response_model=FolderOut, status_code=status.HTTP_201_CREATED)
|
|
|
|
|
async def create_folder(
|
|
|
|
|
folder_data: FolderCreate,
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2026-04-09 17:26:37 +08:00
|
|
|
current_user: User = Depends(get_current_user),
|
2026-03-21 10:13:29 +08:00
|
|
|
):
|
|
|
|
|
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,
|
2026-04-09 17:26:37 +08:00
|
|
|
Folder.name == folder_data.name,
|
2026-03-21 10:13:29 +08:00
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
if result.scalar_one_or_none():
|
|
|
|
|
raise HTTPException(status_code=400, detail="同名文件夹已存在")
|
|
|
|
|
|
|
|
|
|
folder = Folder(
|
|
|
|
|
user_id=current_user.id,
|
|
|
|
|
name=folder_data.name,
|
2026-04-09 17:26:37 +08:00
|
|
|
parent_id=folder_data.parent_id,
|
2026-03-21 10:13:29 +08:00
|
|
|
)
|
|
|
|
|
db.add(folder)
|
|
|
|
|
await db.commit()
|
|
|
|
|
await db.refresh(folder)
|
2026-04-09 17:26:37 +08:00
|
|
|
|
|
|
|
|
document_service = DocumentService(db, current_user.id)
|
|
|
|
|
await document_service.ensure_folder_directory(current_user.id, folder.id)
|
2026-03-21 10:13:29 +08:00
|
|
|
return folder
|
|
|
|
|
|
2026-04-09 17:26:37 +08:00
|
|
|
|
2026-03-21 10:13:29 +08:00
|
|
|
@router.put("/{folder_id}", response_model=FolderOut)
|
|
|
|
|
async def rename_folder(
|
|
|
|
|
folder_id: str,
|
|
|
|
|
folder_data: FolderUpdate,
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2026-04-09 17:26:37 +08:00
|
|
|
current_user: User = Depends(get_current_user),
|
2026-03-21 10:13:29 +08:00
|
|
|
):
|
|
|
|
|
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="文件夹不存在")
|
|
|
|
|
|
2026-04-09 17:26:37 +08:00
|
|
|
old_name = folder.name
|
2026-03-21 10:13:29 +08:00
|
|
|
folder.name = folder_data.name
|
2026-04-09 17:26:37 +08:00
|
|
|
|
|
|
|
|
document_service = DocumentService(db, current_user.id)
|
|
|
|
|
await document_service.rename_folder_directory(current_user.id, folder.id, old_name, folder_data.name)
|
2026-03-21 10:13:29 +08:00
|
|
|
await db.commit()
|
|
|
|
|
await db.refresh(folder)
|
|
|
|
|
return folder
|
|
|
|
|
|
2026-04-09 17:26:37 +08:00
|
|
|
|
2026-03-21 10:13:29 +08:00
|
|
|
@router.delete("/{folder_id}", status_code=status.HTTP_204_NO_CONTENT)
|
|
|
|
|
async def delete_folder(
|
|
|
|
|
folder_id: str,
|
|
|
|
|
db: AsyncSession = Depends(get_db),
|
2026-04-09 17:26:37 +08:00
|
|
|
current_user: User = Depends(get_current_user),
|
2026-03-21 10:13:29 +08:00
|
|
|
):
|
|
|
|
|
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="文件夹不存在")
|
|
|
|
|
|
2026-04-09 17:26:37 +08:00
|
|
|
document_service = DocumentService(db, current_user.id)
|
|
|
|
|
folder_path = await document_service._get_storage_directory(current_user.id, folder_id)
|
|
|
|
|
|
2026-03-21 10:13:29 +08:00
|
|
|
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()
|
2026-04-09 17:26:37 +08:00
|
|
|
|
|
|
|
|
if folder_path.exists():
|
|
|
|
|
shutil.rmtree(folder_path, ignore_errors=True)
|