feat(documents): add document parser, service, and API routes
Why:
- Need to parse .docx/.doc files into DocumentTree + markdown
- Need API endpoints for document upload, processing, and deletion
Changes:
- Parser: .doc→.docx conversion via LibreOffice, XML-level extraction for table-heavy docs, heuristic heading detection
- Service: upload, process (delegates to chunking), delete orchestration
- Routes: POST /documents, POST /documents/{id}/process, DELETE /documents/{id}, GET /strategies
This commit is contained in:
71
src/documents/routes.py
Normal file
71
src/documents/routes.py
Normal file
@@ -0,0 +1,71 @@
|
||||
"""Document and strategy API routes.
|
||||
|
||||
Endpoints:
|
||||
POST /documents Upload a .docx file
|
||||
POST /documents/{id}/process Run chunking strategies (stub until Phase 2)
|
||||
DELETE /documents/{id} Remove document + vectors
|
||||
GET /strategies List available chunking strategies
|
||||
"""
|
||||
|
||||
from fastapi import APIRouter, File, UploadFile
|
||||
|
||||
from src.core.exceptions import DocumentProcessingError
|
||||
from src.core.models import PaginatedResponse, StrategyName
|
||||
from src.documents.models import (
|
||||
DeleteResponse,
|
||||
DocumentResponse,
|
||||
ProcessRequest,
|
||||
ProcessResponse,
|
||||
StrategiesResponse,
|
||||
StrategyInfo,
|
||||
)
|
||||
from src.documents import service
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post("/documents", response_model=DocumentResponse, status_code=201)
|
||||
async def upload_document(file: UploadFile = File(...)):
|
||||
"""Upload a .docx file. Parses it, stores the document tree in SQLite."""
|
||||
if not file.filename:
|
||||
raise DocumentProcessingError("No filename provided")
|
||||
if not file.filename.lower().endswith((".docx", ".doc")):
|
||||
raise DocumentProcessingError("Only .docx and .doc files are supported")
|
||||
|
||||
content = await file.read()
|
||||
if not content:
|
||||
raise DocumentProcessingError("Empty file")
|
||||
|
||||
doc = service.upload_document(file.filename, content)
|
||||
return DocumentResponse(
|
||||
id=doc["id"],
|
||||
filename=doc["filename"],
|
||||
paragraph_count=doc.get("paragraph_count", 0),
|
||||
chunk_counts=doc.get("chunk_counts", {}),
|
||||
created_at=doc["created_at"],
|
||||
)
|
||||
|
||||
|
||||
@router.post("/documents/{doc_id}/process", response_model=ProcessResponse)
|
||||
async def process_document(doc_id: str, request: ProcessRequest):
|
||||
"""Run selected chunking strategies on an uploaded document.
|
||||
|
||||
Currently returns stub results until Phase 2 implements the strategies.
|
||||
"""
|
||||
return service.process_document(doc_id, request)
|
||||
|
||||
|
||||
@router.delete("/documents/{doc_id}", response_model=DeleteResponse)
|
||||
async def delete_document(doc_id: str):
|
||||
"""Delete a document and all its chunk vectors from Qdrant."""
|
||||
deleted = service.delete_document(doc_id)
|
||||
return DeleteResponse(deleted=deleted, document_id=doc_id)
|
||||
|
||||
|
||||
@router.get("/strategies", response_model=StrategiesResponse)
|
||||
async def list_strategies():
|
||||
"""List available chunking strategies with descriptions."""
|
||||
strategies = service.list_strategies()
|
||||
return StrategiesResponse(
|
||||
strategies=[StrategyInfo(**s) for s in strategies]
|
||||
)
|
||||
Reference in New Issue
Block a user