Spaces:
Sleeping
Sleeping
| """Document endpoints: upload, list, detail, page images, re-process, delete.""" | |
| from __future__ import annotations | |
| import uuid | |
| from datetime import datetime, timezone | |
| from fastapi import APIRouter, BackgroundTasks, File, HTTPException, UploadFile | |
| from fastapi.responses import FileResponse | |
| from app.core.config import settings | |
| from app.core.logging import get_logger | |
| from app.schemas.documents import DocStatus, DocumentDetail, DocumentMeta | |
| from app.services import storage, vectorstore | |
| from app.services.pipeline import process_document | |
| log = get_logger(__name__) | |
| router = APIRouter(prefix="/api/documents", tags=["documents"]) | |
| ALLOWED = { | |
| ".pdf", ".docx", ".doc", ".pptx", ".xlsx", ".html", ".md", ".txt", | |
| ".png", ".jpg", ".jpeg", ".tiff", ".bmp", | |
| } | |
| async def upload(background: BackgroundTasks, file: UploadFile = File(...)): | |
| ext = "." + (file.filename or "").rsplit(".", 1)[-1].lower() if "." in (file.filename or "") else "" | |
| if ext not in ALLOWED: | |
| raise HTTPException(400, f"Unsupported file type '{ext}'. Allowed: {sorted(ALLOWED)}") | |
| doc_id = uuid.uuid4().hex[:12] | |
| dest = settings.uploads_path / f"{doc_id}{ext}" | |
| data = await file.read() | |
| dest.write_bytes(data) | |
| detail = DocumentDetail( | |
| id=doc_id, | |
| filename=file.filename or dest.name, | |
| content_type=file.content_type or "application/octet-stream", | |
| size_bytes=len(data), | |
| status=DocStatus.uploaded, | |
| created_at=datetime.now(timezone.utc).isoformat(), | |
| ) | |
| storage.save(detail) | |
| # kick off async processing | |
| background.add_task(process_document, doc_id, dest) | |
| log.info("Uploaded %s (%d bytes) -> %s", file.filename, len(data), doc_id) | |
| return DocumentMeta.model_validate(detail.model_dump()) | |
| async def list_documents(): | |
| return storage.list_all() | |
| async def get_document(doc_id: str): | |
| detail = storage.get(doc_id) | |
| if not detail: | |
| raise HTTPException(404, "Document not found") | |
| return detail | |
| async def get_page_image(doc_id: str, page: int): | |
| path = settings.renders_path / doc_id / f"page-{page}.png" | |
| if not path.exists(): | |
| raise HTTPException(404, "Page image not found") | |
| return FileResponse(path, media_type="image/png") | |
| async def delete_document(doc_id: str): | |
| if not storage.get(doc_id): | |
| raise HTTPException(404, "Document not found") | |
| vectorstore.delete(doc_id) | |
| storage.delete(doc_id) | |
| return {"ok": True} | |