Files
calminer/routes/imports.py

167 lines
4.9 KiB
Python

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 fastapi.templating import Jinja2Templates
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"])
templates = Jinja2Templates(directory="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(*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)