perf(campaign): search share targets server-side

This commit is contained in:
2026-07-30 01:30:13 +02:00
parent 46df12c025
commit c769be39da
5 changed files with 285 additions and 22 deletions

View File

@@ -1,9 +1,15 @@
from __future__ import annotations
from typing import Literal
from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy.orm import Session
from govoplan_core.api.v1.schemas import (
ReferenceOptionListResponse,
ReferenceOptionResponse,
)
from govoplan_campaign.backend.schemas import (
CampaignShareItem,
CampaignShareListResponse,
@@ -19,6 +25,11 @@ from govoplan_campaign.backend.db.models import (
CampaignShare,
)
from govoplan_core.db.session import get_session
from govoplan_core.core.references import (
access_scope_reference_page,
access_scope_reference_provider_available,
)
from govoplan_core.core.runtime import get_registry
from govoplan_core.security.time import utc_now
@@ -31,6 +42,51 @@ from govoplan_campaign.backend.route_support import (
router = APIRouter(prefix="/campaigns", tags=["campaigns"])
@router.get(
"/{campaign_id}/share-target-options",
response_model=ReferenceOptionListResponse,
)
def search_campaign_share_targets(
campaign_id: str,
target_type: Literal["user", "group"],
q: str = "",
selected: list[str] = Query(default=[]),
limit: int = Query(default=50, ge=1, le=200),
cursor: str | None = None,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(require_scope("campaigns:campaign:share")),
) -> ReferenceOptionListResponse:
_get_campaign_for_principal(session, campaign_id, principal, write=True)
registry = get_registry()
try:
page = access_scope_reference_page(
registry,
principal,
scope_type=target_type,
reference_kind="membership" if target_type == "user" else "group",
query=q,
selected_values=selected,
limit=limit,
cursor=cursor,
administrative=True,
session=session,
)
except ValueError as exc:
raise HTTPException(
status_code=status.HTTP_422_UNPROCESSABLE_CONTENT,
detail=str(exc),
) from exc
return ReferenceOptionListResponse(
options=[
ReferenceOptionResponse(**option.to_dict())
for option in page.options
],
provider_available=access_scope_reference_provider_available(registry),
next_cursor=page.next_cursor,
has_more=page.has_more,
)
@router.get("/{campaign_id}/share-targets", response_model=CampaignShareTargetsResponse)
def list_campaign_share_targets(
campaign_id: str,