feat: harden campaign delivery and editing

This commit is contained in:
2026-07-30 14:27:04 +02:00
parent 4a120e8009
commit cd223cbb95
34 changed files with 3370 additions and 165 deletions
@@ -1,7 +1,7 @@
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Query, status
from fastapi import APIRouter, Depends, Header, HTTPException, Query, Response, status
from sqlalchemy.orm import Session
from govoplan_campaign.backend.schemas import (
@@ -101,6 +101,7 @@ def list_versions(
def get_version_detail(
campaign_id: str,
version_id: str,
response: Response,
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(require_scope("campaigns:campaign:read")),
):
@@ -113,10 +114,12 @@ def get_version_detail(
campaign_id=campaign_id,
version_id=version_id,
)
return CampaignVersionDetailResponse.model_validate(
result = CampaignVersionDetailResponse.model_validate(
version,
context=_campaign_response_context(principal),
)
response.headers["ETag"] = version.strong_etag
return result
except CampaignPersistenceError as exc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail=str(exc)
@@ -335,18 +338,23 @@ def update_version_detail(
campaign_id: str,
version_id: str,
payload: CampaignVersionUpdateRequest,
response: Response,
if_match: str | None = Header(default=None, alias="If-Match"),
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(require_scope("campaigns:campaign:update")),
):
return _update_campaign_version_detail_response(
result = _update_campaign_version_detail_response(
session,
principal,
campaign_id,
version_id,
payload,
if_match=if_match,
autosave=False,
audit_action="campaign.version_updated",
)
response.headers["ETag"] = result.strong_etag
return result
@router.post(
@@ -357,18 +365,23 @@ def autosave_version(
campaign_id: str,
version_id: str,
payload: CampaignVersionUpdateRequest,
response: Response,
if_match: str | None = Header(default=None, alias="If-Match"),
session: Session = Depends(get_session),
principal: ApiPrincipal = Depends(require_scope("campaigns:campaign:update")),
):
return _update_campaign_version_detail_response(
result = _update_campaign_version_detail_response(
session,
principal,
campaign_id,
version_id,
payload,
if_match=if_match,
autosave=True,
audit_action="campaign.version_autosaved",
)
response.headers["ETag"] = result.strong_etag
return result
@router.post(