| 1 | from __future__ import annotations |
| 2 | |
| 3 | import hashlib |
| 4 | |
| 5 | from fastapi import APIRouter, Depends, File, Form, HTTPException, UploadFile |
| 6 | from sqlalchemy import select |
| 7 | from sqlalchemy.orm import Session |
| 8 | |
| 9 | from app.api.deps import Perm, get_db, require |
| 10 | from app.core.config import settings |
| 11 | from app.core.storage import get_storage |
| 12 | from app.models import AuditLog, Import, User |
| 13 | |
| 14 | router = APIRouter(prefix="/imports", tags=["imports"]) |
| 15 | |
| 16 | |
| 17 | @router.post("", status_code=201) |
| 18 | async def create_import( |
| 19 | file: UploadFile = File(...), |
| 20 | source_id: str | None = Form(default=None), |
| 21 | process_now: bool = Form(default=True), |
| 22 | db: Session = Depends(get_db), |
| 23 | user: User = Depends(require(Perm.INGEST)), |
| 24 | ): |
| 25 | data = await file.read() |
| 26 | if not data: |
| 27 | raise HTTPException(400, "empty file") |
| 28 | if len(data) > settings.max_upload_bytes: |
| 29 | raise HTTPException(413, f"file exceeds {settings.max_upload_bytes} bytes") |
| 30 | sha = hashlib.sha256(data).hexdigest() |
| 31 | |
| 32 | existing = db.scalar(select(Import).where(Import.sha256 == sha)) |
| 33 | if existing: |
| 34 | return {"import_id": existing.id, "status": existing.status, "deduplicated": True, |
| 35 | "row_count": existing.row_count} |
| 36 | |
| 37 | blob_ref = get_storage().put(f"imports/{sha[:16]}_{file.filename}", data) |
| 38 | |
| 39 | imp = Import(source_id=source_id, filename=file.filename, content_type=file.content_type, |
| 40 | sha256=sha, raw_blob_ref=blob_ref, status="received", created_by=user.id) |
| 41 | db.add(imp) |
| 42 | db.flush() |
| 43 | db.add(AuditLog(actor_user_id=user.id, action="import.upload", |
| 44 | entity_type="import", entity_id=imp.id, meta={"filename": file.filename})) |
| 45 | db.commit() |
| 46 | db.refresh(imp) |
| 47 | |
| 48 | if process_now: |
| 49 | from app.services.ingest import process_import |
| 50 | summary = process_import(db, imp.id) |
| 51 | return {"import_id": imp.id, **summary} |
| 52 | else: |
| 53 | from app.workers.tasks import process_import_task |
| 54 | process_import_task.delay(imp.id) |
| 55 | return {"import_id": imp.id, "status": "queued"} |
| 56 | |
| 57 | |
| 58 | @router.get("/{import_id}") |
| 59 | def get_import(import_id: str, db: Session = Depends(get_db), _: User = Depends(require(Perm.READ))): |
| 60 | imp = db.get(Import, import_id) |
| 61 | if not imp: |
| 62 | raise HTTPException(404, "import not found") |
| 63 | return {"id": imp.id, "filename": imp.filename, "status": imp.status, |
| 64 | "row_count": imp.row_count, "error": imp.error, "sha256": imp.sha256, |
| 65 | "created_at": imp.created_at} |
| 66 | |
| 67 | |
| 68 | @router.get("") |
| 69 | def list_imports(db: Session = Depends(get_db), _: User = Depends(require(Perm.READ))): |
| 70 | rows = db.scalars(select(Import).order_by(Import.created_at.desc()).limit(100)).all() |
| 71 | return [{"id": i.id, "filename": i.filename, "status": i.status, |
| 72 | "row_count": i.row_count, "created_at": i.created_at} for i in rows] |