from __future__ import annotations from io import BytesIO from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status from fastapi import Request from fastapi.responses import HTMLResponse from dependencies import ( get_import_ingestion_service, require_roles, require_roles_html, ) from models import User from schemas.imports import ( ImportCommitRequest, ProjectImportCommitResponse, ProjectImportPreviewResponse, ScenarioImportCommitResponse, ScenarioImportPreviewResponse, ) from services.importers import ImportIngestionService, UnsupportedImportFormat from routes.template_filters import create_templates router = APIRouter(prefix="/imports", tags=["Imports"]) templates = create_templates() MANAGE_ROLES = ("project_manager", "admin") @router.get( "/ui", response_class=HTMLResponse, include_in_schema=False, name="imports.ui", ) def import_dashboard( request: Request, _: User = Depends(require_roles_html(*MANAGE_ROLES)), ) -> HTMLResponse: return templates.TemplateResponse( request, "imports/ui.html", { "title": "Imports", }, ) 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)