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, Page, SourceCreate, SourceRead, SourceUpdate 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="/sources", tags=["sources"], dependencies=[Depends(require_admin)]) @router.get("", response_model=Page[SourceRead]) def list_sources( q: str = Query("", max_length=200), page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100), db: Session = Depends(get_db), ): statement = select(Source).where(Source.is_deleted.is_(False)) if q: statement = statement.where( or_( Source.name.icontains(q, autoescape=True), Source.notes.icontains(q, autoescape=True), ) ) return page_result(db, statement.order_by(Source.id.desc()), page, page_size) @router.post("", response_model=SourceRead, status_code=201) def create_source(payload: SourceCreate, db: Session = Depends(get_db)): item = Source(**payload.model_dump(mode="json")) db.add(item) record_event(db, item, "create") db.commit() return item @router.get("/{item_id}", response_model=SourceRead) def read_source(item_id: int, db: Session = Depends(get_db)): return get_item(db, Source, item_id) @router.patch("/{item_id}", response_model=SourceRead) def update_source(item_id: int, payload: SourceUpdate, db: Session = Depends(get_db)): item = get_item(db, Source, item_id, lock=True) ensure_version(item, payload.expected_version) update_values(item, payload) record_event(db, item, "update") invalidate_knowledge( db, KnowledgeItem.source_id == item.id, source_url=item.source_url, update_url=True ) db.commit() return item @router.delete("/{item_id}", status_code=204) def delete_source( item_id: int, expected_version: int = Query(..., ge=1), db: Session = Depends(get_db) ): item = get_item(db, Source, item_id, lock=True) ensure_version(item, expected_version) for model in (Document, KnowledgeItem): if ( db.scalar( select(model.id) .where(model.source_id == item_id, model.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 source_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, Source, item_id, page, page_size)