Introduce the backend pieces for brain memory ingestion, routing, and system telemetry so the new knowledge workflows can project data into a brain view. The supporting tests lock in the new behavior and keep the expanded backend surface stable. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
144 lines
4.7 KiB
Python
144 lines
4.7 KiB
Python
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()
|