feat: acquire immutable sanctions snapshots
This commit is contained in:
@@ -13,8 +13,16 @@ from govoplan_core.core.tabular_sources import (
|
||||
TabularSourceError,
|
||||
TabularSourceNotFoundError,
|
||||
)
|
||||
from govoplan_core.core.sanctions import SanctionsSnapshotReference
|
||||
from govoplan_core.db.session import get_session
|
||||
from govoplan_connectors.backend.schemas import (
|
||||
SanctionsAcquisitionRunListResponse,
|
||||
SanctionsAcquisitionRunResponse,
|
||||
SanctionsRefreshResponse,
|
||||
SanctionsSnapshotListResponse,
|
||||
SanctionsSnapshotResponse,
|
||||
SanctionsSourceListResponse,
|
||||
SanctionsSourceResponse,
|
||||
SnapshotCreateRequest,
|
||||
TabularColumnResponse,
|
||||
TabularSourceDeleteResponse,
|
||||
@@ -22,6 +30,14 @@ from govoplan_connectors.backend.schemas import (
|
||||
TabularSourcePreviewResponse,
|
||||
TabularSourceResponse,
|
||||
)
|
||||
from govoplan_connectors.backend.sanctions_sources import (
|
||||
SANCTIONS_READ_SCOPE,
|
||||
SANCTIONS_REFRESH_SCOPE,
|
||||
SanctionsSourceAccessError,
|
||||
SanctionsSourceError,
|
||||
SanctionsSourceNotFoundError,
|
||||
SqlSanctionsSnapshotProvider,
|
||||
)
|
||||
from govoplan_connectors.backend.tabular_sources import (
|
||||
ADMIN_SCOPE,
|
||||
READ_SCOPE,
|
||||
@@ -33,6 +49,7 @@ from govoplan_connectors.backend.tabular_sources import (
|
||||
|
||||
router = APIRouter(prefix="/connectors", tags=["connectors"])
|
||||
provider = SqlTabularSourceProvider()
|
||||
sanctions_provider = SqlSanctionsSnapshotProvider()
|
||||
|
||||
|
||||
def _require_any_scope(principal: ApiPrincipal, *scopes: str) -> None:
|
||||
@@ -52,6 +69,25 @@ def _http_error(exc: TabularSourceError) -> HTTPException:
|
||||
return HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc))
|
||||
|
||||
|
||||
def _sanctions_http_error(
|
||||
exc: SanctionsSourceError,
|
||||
) -> HTTPException:
|
||||
if isinstance(exc, SanctionsSourceNotFoundError):
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_404_NOT_FOUND,
|
||||
detail=str(exc),
|
||||
)
|
||||
if isinstance(exc, SanctionsSourceAccessError):
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=str(exc),
|
||||
)
|
||||
return HTTPException(
|
||||
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
|
||||
detail=str(exc),
|
||||
)
|
||||
|
||||
|
||||
@router.get("/tabular-sources", response_model=TabularSourceListResponse)
|
||||
def api_list_tabular_sources(
|
||||
query: str = Query(default="", max_length=200),
|
||||
@@ -183,6 +219,147 @@ def api_delete_tabular_source(
|
||||
return TabularSourceDeleteResponse(deleted=True, source_ref=source_ref)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/sanctions/sources",
|
||||
response_model=SanctionsSourceListResponse,
|
||||
)
|
||||
def api_list_sanctions_sources(
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> SanctionsSourceListResponse:
|
||||
_require_any_scope(
|
||||
principal,
|
||||
SANCTIONS_READ_SCOPE,
|
||||
SANCTIONS_REFRESH_SCOPE,
|
||||
ADMIN_SCOPE,
|
||||
)
|
||||
return SanctionsSourceListResponse(
|
||||
sources=[
|
||||
SanctionsSourceResponse.model_validate(
|
||||
source,
|
||||
from_attributes=True,
|
||||
)
|
||||
for source in sanctions_provider.available_sources()
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/sanctions/sources/{provider_id}/refresh",
|
||||
response_model=SanctionsRefreshResponse,
|
||||
)
|
||||
def api_refresh_sanctions_source(
|
||||
provider_id: str,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> SanctionsRefreshResponse:
|
||||
_require_any_scope(
|
||||
principal,
|
||||
SANCTIONS_REFRESH_SCOPE,
|
||||
ADMIN_SCOPE,
|
||||
)
|
||||
try:
|
||||
result = sanctions_provider.refresh_source(
|
||||
session,
|
||||
principal,
|
||||
provider_id=provider_id,
|
||||
)
|
||||
except SanctionsSourceError as exc:
|
||||
raise _sanctions_http_error(exc) from exc
|
||||
audit_event(
|
||||
session,
|
||||
tenant_id=principal.tenant_id,
|
||||
user_id=getattr(principal.user, "id", None),
|
||||
api_key_id=principal.api_key_id,
|
||||
action="connectors.sanctions_source.refreshed",
|
||||
object_type="connector_sanctions_acquisition_run",
|
||||
object_id=result.run_id,
|
||||
details={
|
||||
"provider_id": provider_id,
|
||||
"status": result.status,
|
||||
"snapshot_ref": (
|
||||
result.snapshot.ref
|
||||
if result.snapshot is not None
|
||||
else None
|
||||
),
|
||||
},
|
||||
)
|
||||
session.commit()
|
||||
return SanctionsRefreshResponse(
|
||||
run_id=result.run_id,
|
||||
provider_id=result.provider_id,
|
||||
status=result.status,
|
||||
snapshot=(
|
||||
_sanctions_snapshot_response(result.snapshot)
|
||||
if result.snapshot is not None
|
||||
else None
|
||||
),
|
||||
error=result.error,
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/sanctions/snapshots",
|
||||
response_model=SanctionsSnapshotListResponse,
|
||||
)
|
||||
def api_list_sanctions_snapshots(
|
||||
limit: int = Query(default=100, ge=1, le=500),
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> SanctionsSnapshotListResponse:
|
||||
_require_any_scope(
|
||||
principal,
|
||||
SANCTIONS_READ_SCOPE,
|
||||
ADMIN_SCOPE,
|
||||
)
|
||||
try:
|
||||
snapshots = sanctions_provider.list_snapshots(
|
||||
session,
|
||||
principal,
|
||||
limit=limit,
|
||||
)
|
||||
except SanctionsSourceError as exc:
|
||||
raise _sanctions_http_error(exc) from exc
|
||||
return SanctionsSnapshotListResponse(
|
||||
snapshots=[
|
||||
_sanctions_snapshot_response(item)
|
||||
for item in snapshots
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/sanctions/runs",
|
||||
response_model=SanctionsAcquisitionRunListResponse,
|
||||
)
|
||||
def api_list_sanctions_runs(
|
||||
limit: int = Query(default=100, ge=1, le=500),
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> SanctionsAcquisitionRunListResponse:
|
||||
_require_any_scope(
|
||||
principal,
|
||||
SANCTIONS_READ_SCOPE,
|
||||
ADMIN_SCOPE,
|
||||
)
|
||||
try:
|
||||
runs = sanctions_provider.list_runs(
|
||||
session,
|
||||
principal,
|
||||
limit=limit,
|
||||
)
|
||||
except SanctionsSourceError as exc:
|
||||
raise _sanctions_http_error(exc) from exc
|
||||
return SanctionsAcquisitionRunListResponse(
|
||||
runs=[
|
||||
SanctionsAcquisitionRunResponse.model_validate(
|
||||
item,
|
||||
from_attributes=True,
|
||||
)
|
||||
for item in runs
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
def _source_response(source: TabularSource) -> TabularSourceResponse:
|
||||
return TabularSourceResponse(
|
||||
ref=source.ref,
|
||||
@@ -208,4 +385,37 @@ def _source_response(source: TabularSource) -> TabularSourceResponse:
|
||||
)
|
||||
|
||||
|
||||
def _sanctions_snapshot_response(
|
||||
snapshot: SanctionsSnapshotReference,
|
||||
) -> SanctionsSnapshotResponse:
|
||||
return SanctionsSnapshotResponse.model_validate(
|
||||
{
|
||||
"ref": snapshot.ref,
|
||||
"provider_id": snapshot.provider_id,
|
||||
"publisher": snapshot.publisher,
|
||||
"jurisdiction": snapshot.jurisdiction,
|
||||
"list_type": snapshot.list_type,
|
||||
"source_id": snapshot.source_id,
|
||||
"source_version": snapshot.source_version,
|
||||
"publication_at": snapshot.publication_at,
|
||||
"effective_at": snapshot.effective_at,
|
||||
"acquired_at": snapshot.acquired_at,
|
||||
"content_type": snapshot.content_type,
|
||||
"byte_count": snapshot.byte_count,
|
||||
"sha256": snapshot.sha256,
|
||||
"parser_version": snapshot.parser_version,
|
||||
"raw_evidence_ref": snapshot.raw_evidence_ref,
|
||||
"connector_run_id": snapshot.connector_run_id,
|
||||
"signature_evidence": dict(
|
||||
snapshot.signature_evidence
|
||||
),
|
||||
"licence_notes": snapshot.licence_notes,
|
||||
"trust_notes": snapshot.trust_notes,
|
||||
"transport_evidence": dict(
|
||||
snapshot.transport_evidence
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
__all__ = ["router"]
|
||||
|
||||
Reference in New Issue
Block a user