intermittent commit

This commit is contained in:
2026-07-14 13:22:12 +02:00
parent c1afce7bdb
commit 2f1b7fb6b8
9 changed files with 364 additions and 9 deletions

View File

@@ -1,5 +1,8 @@
from __future__ import annotations
import dataclasses
from typing import Any
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
@@ -8,6 +11,8 @@ from govoplan_core.db.session import get_session
from govoplan_poll.backend.schemas import PollResultSummaryResponse
from govoplan_scheduling.backend.manifest import READ_SCOPE, WRITE_SCOPE
from govoplan_scheduling.backend.schemas import (
SchedulingAddressLookupCandidate,
SchedulingAddressLookupResponse,
SchedulingCalendarActionResponse,
SchedulingDecisionRequest,
SchedulingNotificationCreateRequest,
@@ -20,6 +25,7 @@ from govoplan_scheduling.backend.schemas import (
SchedulingStatusResponse,
SchedulingSummaryResponse,
)
from govoplan_scheduling.backend.runtime import get_registry
from govoplan_scheduling.backend.service import (
SchedulingError,
cancel_scheduling_request,
@@ -42,6 +48,39 @@ from govoplan_scheduling.backend.service import (
router = APIRouter(prefix="/scheduling", tags=["scheduling"])
CAPABILITY_ADDRESSES_LOOKUP = "addresses.lookup"
def _capability_payload(value: object) -> dict[str, Any]:
if dataclasses.is_dataclass(value):
return dataclasses.asdict(value)
if isinstance(value, dict):
return dict(value)
payload: dict[str, Any] = {}
for key in (
"contact_id",
"address_book_id",
"display_name",
"email",
"email_label",
"organization",
"role_title",
"tags",
"source_kind",
"source_ref",
"source_revision",
"provenance",
):
if hasattr(value, key):
payload[key] = getattr(value, key)
return payload
def _registry_capability(name: str) -> object | None:
registry = get_registry()
if registry is None or not hasattr(registry, "has_capability") or not registry.has_capability(name):
return None
return registry.capability(name)
def _require_scope(principal: ApiPrincipal, scope: str) -> None:
@@ -61,6 +100,24 @@ def _request_response(request, *, invitation_tokens: dict[str, str] | None = Non
)
@router.get("/address-lookup", response_model=SchedulingAddressLookupResponse)
def api_lookup_scheduling_addresses(
query: str = Query(min_length=1),
limit: int = Query(default=25, ge=1, le=100),
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(get_api_principal),
) -> SchedulingAddressLookupResponse:
_require_scope(principal, WRITE_SCOPE)
capability = _registry_capability(CAPABILITY_ADDRESSES_LOOKUP)
if capability is None or not hasattr(capability, "lookup"):
return SchedulingAddressLookupResponse(available=False, candidates=[])
candidates = getattr(capability, "lookup")(session, principal, query=query, limit=limit)
return SchedulingAddressLookupResponse(
available=True,
candidates=[SchedulingAddressLookupCandidate.model_validate(_capability_payload(candidate)) for candidate in candidates],
)
@router.get("/requests", response_model=SchedulingRequestListResponse)
def api_list_scheduling_requests(
status_filter: str | None = Query(default=None, alias="status"),