from __future__ import annotations from io import BytesIO from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status from dependencies import get_import_ingestion_service, require_roles from models import User from schemas.imports import ( ImportCommitRequest, ProjectImportCommitResponse, ProjectImportPreviewResponse, ScenarioImportCommitResponse, ScenarioImportPreviewResponse, ) from services.importers import ImportIngestionService, UnsupportedImportFormat router = APIRouter(prefix="/imports", tags=["Imports"]) MANAGE_ROLES = ("project_manager", "admin") async def _read_upload_file(upload: UploadFile) -> BytesIO: content = await upload.read() if not content: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Uploaded file is empty.", ) return BytesIO(content) @router.post( "/projects/preview", response_model=ProjectImportPreviewResponse, status_code=status.HTTP_200_OK, ) async def preview_project_import( file: UploadFile = File(..., description="Project import file (CSV or Excel)"), _: User = Depends(require_roles(*MANAGE_ROLES)), ingestion_service: ImportIngestionService = Depends( get_import_ingestion_service), ) -> ProjectImportPreviewResponse: if not file.filename: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Filename is required for import.", ) stream = await _read_upload_file(file) try: preview = ingestion_service.preview_projects(stream, file.filename) except UnsupportedImportFormat as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc), ) from exc return ProjectImportPreviewResponse.model_validate(preview) @router.post( "/scenarios/preview", response_model=ScenarioImportPreviewResponse, status_code=status.HTTP_200_OK, ) async def preview_scenario_import( file: UploadFile = File(..., description="Scenario import file (CSV or Excel)"), _: User = Depends(require_roles(*MANAGE_ROLES)), ingestion_service: ImportIngestionService = Depends( get_import_ingestion_service), ) -> ScenarioImportPreviewResponse: if not file.filename: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail="Filename is required for import.", ) stream = await _read_upload_file(file) try: preview = ingestion_service.preview_scenarios(stream, file.filename) except UnsupportedImportFormat as exc: raise HTTPException( status_code=status.HTTP_400_BAD_REQUEST, detail=str(exc), ) from exc return ScenarioImportPreviewResponse.model_validate(preview) def _value_error_status(exc: ValueError) -> int: detail = str(exc) if detail.lower().startswith("unknown"): return status.HTTP_404_NOT_FOUND return status.HTTP_400_BAD_REQUEST @router.post( "/projects/commit", response_model=ProjectImportCommitResponse, status_code=status.HTTP_200_OK, ) async def commit_project_import_endpoint( payload: ImportCommitRequest, _: User = Depends(require_roles(*MANAGE_ROLES)), ingestion_service: ImportIngestionService = Depends( get_import_ingestion_service), ) -> ProjectImportCommitResponse: try: result = ingestion_service.commit_project_import(payload.token) except ValueError as exc: raise HTTPException( status_code=_value_error_status(exc), detail=str(exc), ) from exc return ProjectImportCommitResponse.model_validate(result) @router.post( "/scenarios/commit", response_model=ScenarioImportCommitResponse, status_code=status.HTTP_200_OK, ) async def commit_scenario_import_endpoint( payload: ImportCommitRequest, _: User = Depends(require_roles(*MANAGE_ROLES)), ingestion_service: ImportIngestionService = Depends( get_import_ingestion_service), ) -> ScenarioImportCommitResponse: try: result = ingestion_service.commit_scenario_import(payload.token) except ValueError as exc: raise HTTPException( status_code=_value_error_status(exc), detail=str(exc), ) from exc return ScenarioImportCommitResponse.model_validate(result)