feat: implement institutional mandate resolver
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Any
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
from sqlalchemy.orm import Session
|
||||
|
||||
from govoplan_core.auth import ApiPrincipal, get_api_principal, has_scope
|
||||
from govoplan_core.core.institutional import InstitutionalContextError
|
||||
from govoplan_core.db.session import get_session
|
||||
from govoplan_mandates.backend.manifest import READ_SCOPE, WRITE_SCOPE
|
||||
from govoplan_mandates.backend.schemas import (
|
||||
MandateListResponse,
|
||||
MandateResolutionPayload,
|
||||
MandateWriteRequest,
|
||||
)
|
||||
from govoplan_mandates.backend.service import (
|
||||
MandateStoreError,
|
||||
SqlMandateResolver,
|
||||
definition_from_mapping,
|
||||
get_mandate,
|
||||
list_mandates,
|
||||
record_mandate,
|
||||
resolution_request_from_mapping,
|
||||
)
|
||||
|
||||
|
||||
router = APIRouter(prefix="/mandates", tags=["mandates"])
|
||||
|
||||
|
||||
def _require_scope(principal: ApiPrincipal, scope: str) -> None:
|
||||
if not has_scope(principal, scope):
|
||||
raise HTTPException(
|
||||
status_code=status.HTTP_403_FORBIDDEN,
|
||||
detail=f"Missing scope: {scope}",
|
||||
)
|
||||
|
||||
|
||||
def _error(exc: Exception) -> HTTPException:
|
||||
message = str(exc)
|
||||
code = status.HTTP_409_CONFLICT if "conflict" in message.lower() else status.HTTP_400_BAD_REQUEST
|
||||
return HTTPException(status_code=code, detail=message)
|
||||
|
||||
|
||||
@router.get("/definitions", response_model=MandateListResponse)
|
||||
def api_list_mandates(
|
||||
mandate_status: str | None = Query(default=None, alias="status"),
|
||||
limit: int = Query(default=100, ge=1, le=200),
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> MandateListResponse:
|
||||
_require_scope(principal, READ_SCOPE)
|
||||
try:
|
||||
items = list_mandates(
|
||||
session,
|
||||
principal,
|
||||
status=mandate_status,
|
||||
limit=limit,
|
||||
)
|
||||
except (MandateStoreError, InstitutionalContextError) as exc:
|
||||
raise _error(exc) from exc
|
||||
return MandateListResponse(
|
||||
mandates=[item.to_dict(include_inspection=True) for item in items]
|
||||
)
|
||||
|
||||
|
||||
@router.get("/definitions/{mandate_id}", response_model=dict[str, Any])
|
||||
def api_get_mandate(
|
||||
mandate_id: str,
|
||||
revision: str | None = None,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> dict[str, Any]:
|
||||
_require_scope(principal, READ_SCOPE)
|
||||
item = get_mandate(
|
||||
session,
|
||||
principal,
|
||||
mandate_id=mandate_id,
|
||||
revision=revision,
|
||||
)
|
||||
if item is None:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Mandate not found")
|
||||
return item.to_dict(include_inspection=True)
|
||||
|
||||
|
||||
@router.post(
|
||||
"/definitions",
|
||||
response_model=dict[str, Any],
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
)
|
||||
def api_record_mandate(
|
||||
payload: MandateWriteRequest,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> dict[str, Any]:
|
||||
_require_scope(principal, WRITE_SCOPE)
|
||||
try:
|
||||
item = record_mandate(
|
||||
session,
|
||||
principal,
|
||||
definition=definition_from_mapping(payload.definition),
|
||||
expected_revision=payload.expected_revision,
|
||||
)
|
||||
session.commit()
|
||||
except (MandateStoreError, InstitutionalContextError) as exc:
|
||||
session.rollback()
|
||||
raise _error(exc) from exc
|
||||
return item.to_dict(include_inspection=True)
|
||||
|
||||
|
||||
@router.post("/resolve", response_model=dict[str, Any])
|
||||
def api_resolve_mandate(
|
||||
payload: MandateResolutionPayload,
|
||||
session: Session = Depends(get_session),
|
||||
principal: ApiPrincipal = Depends(get_api_principal),
|
||||
) -> dict[str, Any]:
|
||||
_require_scope(principal, READ_SCOPE)
|
||||
try:
|
||||
result = SqlMandateResolver().resolve_mandate(
|
||||
session,
|
||||
principal,
|
||||
request=resolution_request_from_mapping(payload.request),
|
||||
)
|
||||
except (MandateStoreError, InstitutionalContextError) as exc:
|
||||
raise _error(exc) from exc
|
||||
return result.to_dict(include_inspection=True)
|
||||
|
||||
|
||||
__all__ = ["router"]
|
||||
Reference in New Issue
Block a user