from fastapi import APIRouter, Depends, HTTPException, Query, Response from sqlalchemy import or_, select from sqlalchemy.orm import Session from app.db.session import get_db from app.models import Document, KnowledgeItem, Source from app.schemas.catalog import AuditRead, DocumentCreate, DocumentRead, DocumentUpdate, Page from app.services.auth import require_admin from app.services.catalog import ( ensure_version, get_item, history, invalidate_knowledge, page_result, record_event, update_values, ) router = APIRouter(prefix="/documents", tags=["documents"], dependencies=[Depends(require_admin)]) @router.get("", response_model=Page[DocumentRead]) def list_documents( q: str = Query("", max_length=200), source_id: int | None = Query(None, gt=0), category: str | None = Query(None, max_length=80), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: Session = Depends(get_db), ): statement = select(Document).where(Document.is_deleted.is_(False)) if source_id is not None: statement = statement.where(Document.source_id == source_id) if category is not None: statement = statement.where(Document.category == category) if q: statement = statement.where( or_( Document.title.icontains(q, autoescape=True), Document.content.icontains(q, autoescape=True), ) ) return page_result(db, statement.order_by(Document.id.desc()), page, page_size) @router.post("", response_model=DocumentRead, status_code=201) def create_document(payload: DocumentCreate, db: Session = Depends(get_db)): get_item(db, Source, payload.source_id, lock=True) item = Document(**payload.model_dump()) db.add(item) record_event(db, item, "create") db.commit() return item @router.get("/{item_id}", response_model=DocumentRead) def read_document(item_id: int, db: Session = Depends(get_db)): return get_item(db, Document, item_id) @router.patch("/{item_id}", response_model=DocumentRead) def update_document(item_id: int, payload: DocumentUpdate, db: Session = Depends(get_db)): item = get_item(db, Document, item_id, lock=True) ensure_version(item, payload.expected_version) update_values(item, payload) record_event(db, item, "update") invalidate_knowledge(db, KnowledgeItem.document_id == item.id) db.commit() return item @router.delete("/{item_id}", status_code=204) def delete_document( item_id: int, expected_version: int = Query(..., ge=1), db: Session = Depends(get_db) ): item = get_item(db, Document, item_id, lock=True) ensure_version(item, expected_version) if ( db.scalar( select(KnowledgeItem.id) .where(KnowledgeItem.document_id == item_id, KnowledgeItem.is_deleted.is_(False)) .limit(1) ) is not None ): raise HTTPException(409, "文档仍被知识条目引用,不能删除") item.is_deleted = True record_event(db, item, "delete") db.commit() return Response(status_code=204) @router.get("/{item_id}/history", response_model=Page[AuditRead]) def document_history( item_id: int, page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: Session = Depends(get_db), ): return history(db, Document, item_id, page, page_size)