168 lines
5.7 KiB
Python
168 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
from fastapi import APIRouter, Depends
|
|
from sqlalchemy.orm import Session
|
|
|
|
from app.core.errors import AppError
|
|
from app.db.session import get_db
|
|
from app.models import Area, Project
|
|
from app.providers.registry import fetch_provider_data, get_provider, import_provider_dataset, list_provider_capabilities
|
|
from app.schemas import (
|
|
CoverageCatalogResponse,
|
|
CoverageResolveRequest,
|
|
CoverageResolveResponse,
|
|
Envelope,
|
|
ExternalFetchRequest,
|
|
ExternalFetchResponse,
|
|
ProviderCapabilitiesResponse,
|
|
ProviderCapabilityResponse,
|
|
ProviderImportRequest,
|
|
ProviderImportResponse,
|
|
ProviderLayersResponse,
|
|
ProviderStatusResponse,
|
|
)
|
|
from app.services.coverage_registry_service import CoverageRegistryService
|
|
from app.utils.response import envelope
|
|
|
|
router = APIRouter(prefix="/external", tags=["external"])
|
|
|
|
|
|
def _validate_area_in_project(db: Session, project_id, area_id: str | None) -> None:
|
|
if area_id is None:
|
|
return
|
|
area = db.get(Area, area_id)
|
|
if not area:
|
|
raise AppError(code="AREA_NOT_FOUND", message="Area not found", status_code=404)
|
|
if area.project_id != project_id:
|
|
raise AppError(code="INVALID_DATASET_SCOPE", message="Area does not belong to this project", status_code=400)
|
|
|
|
|
|
def _assert_project_exists(db: Session, project_id):
|
|
project = db.get(Project, project_id)
|
|
if not project:
|
|
raise AppError(code="PROJECT_NOT_FOUND", message="Project not found", status_code=404)
|
|
|
|
|
|
def _normalize_layer_input(layers: list[str] | None) -> list[str]:
|
|
return [layer.strip() for layer in (layers or []) if isinstance(layer, str) and layer.strip()]
|
|
|
|
|
|
|
|
|
|
def _provider_payload(provider_name: str) -> dict:
|
|
return get_provider(provider_name).capability.to_dict()
|
|
|
|
|
|
@router.get("/providers", response_model=Envelope[ProviderCapabilitiesResponse])
|
|
def list_external_providers() -> dict:
|
|
return envelope({
|
|
"providers": [provider.to_dict() for provider in list_provider_capabilities()],
|
|
})
|
|
|
|
|
|
@router.get("/coverage/catalog", response_model=Envelope[CoverageCatalogResponse])
|
|
def get_coverage_catalog() -> dict:
|
|
return envelope(CoverageRegistryService.catalog().model_dump())
|
|
|
|
|
|
@router.post("/coverage/resolve", response_model=Envelope[CoverageResolveResponse])
|
|
def resolve_project_coverage(payload: CoverageResolveRequest, db: Session = Depends(get_db)) -> dict:
|
|
result = CoverageRegistryService.resolve(
|
|
db,
|
|
project_id=payload.project_id,
|
|
bbox=payload.bbox,
|
|
themes=payload.themes,
|
|
)
|
|
return envelope(result.model_dump())
|
|
|
|
|
|
@router.get(
|
|
"/providers/capabilities",
|
|
response_model=Envelope[ProviderCapabilitiesResponse],
|
|
)
|
|
def get_external_provider_capabilities() -> dict:
|
|
return envelope({
|
|
"providers": [provider.to_dict() for provider in list_provider_capabilities()],
|
|
})
|
|
|
|
|
|
@router.get(
|
|
"/providers/{provider_name}",
|
|
response_model=Envelope[ProviderCapabilityResponse],
|
|
)
|
|
def get_external_provider(provider_name: str) -> dict:
|
|
return envelope(_provider_payload(provider_name))
|
|
|
|
|
|
@router.get(
|
|
"/providers/{provider_name}/layers",
|
|
response_model=Envelope[ProviderLayersResponse],
|
|
)
|
|
def get_external_provider_layers(provider_name: str) -> dict:
|
|
provider = get_provider(provider_name)
|
|
return envelope({
|
|
"provider_name": provider.provider_name,
|
|
"layers": provider.supported_layers,
|
|
})
|
|
|
|
|
|
@router.get(
|
|
"/providers/{provider_name}/status",
|
|
response_model=Envelope[ProviderStatusResponse],
|
|
)
|
|
def get_external_provider_status(provider_name: str) -> dict:
|
|
provider = get_provider(provider_name)
|
|
return envelope({
|
|
"provider_name": provider.provider_name,
|
|
"configured": provider.is_configured,
|
|
"status": provider.capability.status,
|
|
"limitation_message": provider.limitation_message,
|
|
})
|
|
|
|
|
|
@router.post(
|
|
"/providers/{provider_name}/import",
|
|
response_model=Envelope[ProviderImportResponse],
|
|
)
|
|
def import_external_provider_dataset(provider_name: str, payload: ProviderImportRequest) -> dict:
|
|
result = import_provider_dataset(
|
|
provider_name=provider_name,
|
|
project_id=payload.project_id,
|
|
area_id=payload.area_id,
|
|
layers=_normalize_layer_input(payload.layers),
|
|
requested_dataset_role=payload.dataset_role,
|
|
)
|
|
return envelope(result.model_dump())
|
|
|
|
|
|
def _run_fetch(payload: ExternalFetchRequest, provider_name: str) -> ExternalFetchResponse:
|
|
area_id_str = str(payload.area_id) if payload.area_id else None
|
|
response = fetch_provider_data(
|
|
provider_name=provider_name,
|
|
project_id=str(payload.project_id),
|
|
area_id=area_id_str,
|
|
layers=_normalize_layer_input(payload.layers),
|
|
)
|
|
return ExternalFetchResponse(
|
|
provider=provider_name,
|
|
status=response.get("status", "not_configured"),
|
|
message=response.get("message", "Provider fetch executed."),
|
|
requested_layers=_normalize_layer_input(payload.layers),
|
|
project_id=payload.project_id,
|
|
area_id=payload.area_id,
|
|
)
|
|
|
|
|
|
@router.post("/osm/fetch", response_model=Envelope[ExternalFetchResponse])
|
|
def fetch_osm(payload: ExternalFetchRequest, db: Session = Depends(get_db)) -> dict:
|
|
_assert_project_exists(db, payload.project_id)
|
|
_validate_area_in_project(db, payload.project_id, payload.area_id)
|
|
return envelope(_run_fetch(payload, "osm").model_dump())
|
|
|
|
|
|
@router.post("/grb/fetch", response_model=Envelope[ExternalFetchResponse])
|
|
def fetch_grb(payload: ExternalFetchRequest, db: Session = Depends(get_db)) -> dict:
|
|
_assert_project_exists(db, payload.project_id)
|
|
_validate_area_in_project(db, payload.project_id, payload.area_id)
|
|
return envelope(_run_fetch(payload, "grb").model_dump())
|