feat(campaign): complete reusable template workflow
This commit is contained in:
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import dataclasses
|
||||
import re
|
||||
from datetime import UTC, datetime
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
|
||||
@@ -43,7 +44,11 @@ from govoplan_campaign.backend.schemas import (
|
||||
)
|
||||
from govoplan_core.auth import ApiPrincipal, has_scope, require_scope
|
||||
from govoplan_core.audit.logging import audit_from_principal
|
||||
from govoplan_core.core.templates import TemplateContentDraftRequest, TemplateRef
|
||||
from govoplan_core.core.templates import (
|
||||
TemplateContentDraftRequest,
|
||||
TemplateFieldRequirement,
|
||||
TemplateRef,
|
||||
)
|
||||
from govoplan_core.core.change_sequence import (
|
||||
decode_sequence_watermark,
|
||||
encode_sequence_watermark,
|
||||
@@ -80,6 +85,7 @@ from govoplan_campaign.backend.integrations import (
|
||||
postbox_integration,
|
||||
templates_integration,
|
||||
)
|
||||
from govoplan_campaign.backend.template_rendering import find_unresolved_placeholders
|
||||
from govoplan_core.db.session import get_session
|
||||
from govoplan_core.core.distribution_lists import (
|
||||
CAPABILITY_DISTRIBUTION_LIST_EXPAND,
|
||||
@@ -975,9 +981,45 @@ def _campaign_content_library_item(template: TemplateRef) -> dict[str, object]:
|
||||
"text": revision.content_text if revision else None,
|
||||
"html": revision.content_html if revision else None,
|
||||
"body_mode": revision_metadata.get("campaign_body_mode") or "both",
|
||||
"required_fields": [
|
||||
dataclasses.asdict(field) for field in revision.required_fields
|
||||
] if revision else [],
|
||||
}
|
||||
|
||||
|
||||
def _campaign_content_required_fields(
|
||||
payload: CampaignContentLibrarySaveRequest,
|
||||
) -> tuple[TemplateFieldRequirement, ...]:
|
||||
if payload.kind == "fragment":
|
||||
values = (
|
||||
payload.subject if payload.target == "subject"
|
||||
else payload.html if payload.target == "html"
|
||||
else payload.text,
|
||||
)
|
||||
else:
|
||||
values = (
|
||||
payload.subject,
|
||||
payload.text if payload.body_mode != "html" else None,
|
||||
payload.html if payload.body_mode != "text" else None,
|
||||
)
|
||||
paths = {
|
||||
path
|
||||
for value in values
|
||||
for key in find_unresolved_placeholders(value)
|
||||
if (path := _campaign_content_requirement_path(key)) is not None
|
||||
}
|
||||
return tuple(
|
||||
TemplateFieldRequirement(path=path, label=path)
|
||||
for path in sorted(paths)
|
||||
)
|
||||
|
||||
|
||||
def _campaign_content_requirement_path(key: str) -> str | None:
|
||||
path = key.replace("local::", "local.", 1).replace("global::", "global.", 1)
|
||||
path = re.sub(r"\[([1-9][0-9]*)\]", r".\1", path)
|
||||
return path if re.fullmatch(r"[A-Za-z_][A-Za-z0-9_.-]*", path) else None
|
||||
|
||||
|
||||
@router.get("/{campaign_id}/content-library")
|
||||
def campaign_content_library(
|
||||
campaign_id: str,
|
||||
@@ -1081,6 +1123,7 @@ def save_campaign_content_library_item(
|
||||
content_text=content_text,
|
||||
content_html=content_html,
|
||||
locale=payload.locale,
|
||||
required_fields=_campaign_content_required_fields(payload),
|
||||
scope_type=("user" if payload.visibility == "personal" else "tenant"),
|
||||
scope_id=(
|
||||
principal.account_id if payload.visibility == "personal" else None
|
||||
|
||||
Reference in New Issue
Block a user