added backends, improved templating, rbac

This commit is contained in:
2026-06-10 14:40:22 +02:00
parent d9ca48addc
commit ce43f2658f
28 changed files with 1183 additions and 78 deletions

View File

@@ -1,6 +1,7 @@
from __future__ import annotations
import fnmatch
import re
from enum import StrEnum
from pathlib import Path
from typing import Any, Iterable
@@ -8,6 +9,7 @@ from typing import Any, Iterable
from pydantic import BaseModel, ConfigDict, Field
from app.mailer.campaign.entries import load_campaign_entries
from app.mailer.campaign.field_values import effective_entry_field_values
from app.mailer.campaign.models import AttachmentConfig, Behavior, CampaignConfig, EntryConfig
@@ -126,11 +128,39 @@ def _resolve_path(campaign_file: str | Path, raw_path: str) -> Path:
return (campaign_path.parent / path).resolve()
_DOLLAR_FIELD_PATTERN = re.compile(r"(?<!\\)\$\{(.*?)(?<!\\)\}")
_BRACE_FIELD_PATTERN = re.compile(r"(?<!\\)\{\{\s*(.*?)\s*\}\}")
def _normalize_template_key(raw: str) -> str:
key = raw.strip()
if key.startswith("fields."):
key = key.removeprefix("fields.")
elif key.startswith("local."):
key = "local::" + key.removeprefix("local.")
elif key.startswith("global."):
key = "global::" + key.removeprefix("global.")
if key.startswith("local::") or key.startswith("global::"):
return key
if key.startswith("local:"):
return "local::" + key.removeprefix("local:")
if key.startswith("global:"):
return "global::" + key.removeprefix("global:")
return key
def _render_template(template: str, values: dict[str, Any]) -> str:
rendered = template
for key, value in values.items():
rendered = rendered.replace("${" + key + "}", "" if value is None else str(value))
return rendered
def replace(match: re.Match[str]) -> str:
key = _normalize_template_key(match.group(1))
if key in values:
value = values[key]
return "" if value is None else str(value)
return match.group(0)
rendered = _DOLLAR_FIELD_PATTERN.sub(replace, template)
rendered = _BRACE_FIELD_PATTERN.sub(replace, rendered)
return rendered.replace(r"\${", "${").replace(r"\}", "}")
def _recipient_values(entry: EntryConfig) -> dict[str, str]:
@@ -153,7 +183,7 @@ def _template_values(config: CampaignConfig, entry: EntryConfig) -> dict[str, An
values: dict[str, Any] = {}
for key, value in config.global_values.items():
values[f"global::{key}"] = value
for key, value in entry.fields.items():
for key, value in effective_entry_field_values(config, entry).items():
values[f"local::{key}"] = value
if entry.id:
values["local::id"] = entry.id