275 lines
12 KiB
Python
275 lines
12 KiB
Python
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
|
|
from govoplan_core.core.campaigns import (
|
|
CAPABILITY_CAMPAIGNS_ACCESS,
|
|
CAPABILITY_CAMPAIGNS_DELIVERY_TASKS,
|
|
CAPABILITY_CAMPAIGNS_MAIL_POLICY_CONTEXT,
|
|
CAPABILITY_CAMPAIGNS_POLICY_CONTEXT,
|
|
CAPABILITY_CAMPAIGNS_RETENTION,
|
|
)
|
|
from govoplan_core.core.module_guards import drop_table_retirement_provider, persistent_table_uninstall_guard
|
|
from govoplan_core.core.modules import (
|
|
FrontendModule,
|
|
MigrationSpec,
|
|
ModuleContext,
|
|
ModuleInterfaceProvider,
|
|
ModuleInterfaceRequirement,
|
|
ModuleManifest,
|
|
NavItem,
|
|
PermissionDefinition,
|
|
RoleTemplate,
|
|
)
|
|
from govoplan_core.db.base import Base
|
|
from govoplan_campaign.backend.change_tracking import register_campaign_change_tracking
|
|
from govoplan_campaign.backend.db import models as campaign_models # noqa: F401 - populate Campaign ORM metadata
|
|
|
|
register_campaign_change_tracking()
|
|
|
|
|
|
def _permission(scope: str, label: str, description: str, category: str) -> PermissionDefinition:
|
|
module_id, resource, action = scope.split(":", 2)
|
|
return PermissionDefinition(
|
|
scope=scope,
|
|
label=label,
|
|
description=description,
|
|
category=category,
|
|
level="tenant",
|
|
module_id=module_id,
|
|
resource=resource,
|
|
action=action,
|
|
)
|
|
|
|
|
|
PERMISSIONS = (
|
|
_permission("campaigns:campaign:read", "View campaigns", "Open campaign metadata, versions and permitted message summaries.", "Campaigns"),
|
|
_permission("campaigns:campaign:create", "Create campaigns", "Create new campaigns in an accessible tenant scope.", "Campaigns"),
|
|
_permission("campaigns:campaign:update", "Edit campaigns", "Edit current working campaign versions.", "Campaigns"),
|
|
_permission("campaigns:campaign:copy", "Copy campaigns", "Create campaigns or working versions from existing campaigns.", "Campaigns"),
|
|
_permission("campaigns:campaign:archive", "Archive campaigns", "Archive campaigns without destroying audit evidence.", "Campaigns"),
|
|
_permission("campaigns:campaign:delete", "Delete campaigns", "Delete draft-only campaigns where retention policy allows it.", "Campaigns"),
|
|
_permission("campaigns:campaign:share", "Share campaigns", "Grant or revoke explicit campaign access.", "Campaigns"),
|
|
_permission("campaigns:campaign:validate", "Validate campaigns", "Run technical validation and manage validation locks.", "Campaigns"),
|
|
_permission("campaigns:campaign:build", "Build campaigns", "Build exact messages and attachment evidence.", "Campaigns"),
|
|
_permission("campaigns:campaign:review", "Approve campaign review", "Approve or reject built messages and review conditions.", "Campaigns"),
|
|
_permission("campaigns:campaign:send_test", "Mock-send campaigns", "Use mock delivery and verification tools.", "Campaigns"),
|
|
_permission("campaigns:campaign:queue", "Queue campaigns", "Place approved executions into the delivery queue.", "Campaigns"),
|
|
_permission("campaigns:campaign:control", "Control delivery", "Pause, resume or cancel queued and sending jobs.", "Campaigns"),
|
|
_permission("campaigns:campaign:send", "Send campaigns", "Start real SMTP delivery.", "Campaigns"),
|
|
_permission("campaigns:campaign:retry", "Retry delivery", "Retry failed or unattempted delivery jobs.", "Campaigns"),
|
|
_permission("campaigns:campaign:reconcile", "Reconcile delivery", "Resolve outcome-unknown SMTP attempts after inspection.", "Campaigns"),
|
|
_permission("campaigns:recipient:read", "View recipients", "Read recipient lists and recipient-specific campaign data.", "Recipients"),
|
|
_permission("campaigns:recipient:write", "Edit recipients", "Create and edit recipient rows and field values.", "Recipients"),
|
|
_permission("campaigns:recipient:import", "Import recipients", "Bulk-import recipient lists.", "Recipients"),
|
|
_permission("campaigns:recipient:export", "Export recipients", "Export recipient data and reports.", "Recipients"),
|
|
_permission("campaigns:report:read", "View reports", "View campaign delivery reports and aggregate outcomes.", "Reports"),
|
|
_permission("campaigns:report:export", "Export reports", "Download detailed campaign reports.", "Reports"),
|
|
_permission("campaigns:report:send", "Send reports", "Email campaign reports to configured recipients.", "Reports"),
|
|
)
|
|
|
|
ROLE_TEMPLATES = (
|
|
RoleTemplate(
|
|
slug="campaign_manager",
|
|
name="Campaign manager",
|
|
description="Prepare, validate and build campaigns without approving real delivery.",
|
|
permissions=(
|
|
"campaigns:campaign:read",
|
|
"campaigns:campaign:create",
|
|
"campaigns:campaign:update",
|
|
"campaigns:campaign:copy",
|
|
"campaigns:campaign:validate",
|
|
"campaigns:campaign:build",
|
|
"campaigns:recipient:read",
|
|
"campaigns:recipient:write",
|
|
"campaigns:recipient:import",
|
|
"campaigns:report:read",
|
|
),
|
|
),
|
|
RoleTemplate(
|
|
slug="campaign_reviewer",
|
|
name="Campaign reviewer",
|
|
description="Inspect and approve prepared campaign messages.",
|
|
permissions=(
|
|
"campaigns:campaign:read",
|
|
"campaigns:campaign:validate",
|
|
"campaigns:campaign:review",
|
|
"campaigns:recipient:read",
|
|
"campaigns:report:read",
|
|
),
|
|
),
|
|
RoleTemplate(
|
|
slug="campaign_sender",
|
|
name="Campaign sender",
|
|
description="Queue, test, control, send and reconcile prepared campaigns.",
|
|
permissions=(
|
|
"campaigns:campaign:read",
|
|
"campaigns:campaign:send_test",
|
|
"campaigns:campaign:queue",
|
|
"campaigns:campaign:control",
|
|
"campaigns:campaign:send",
|
|
"campaigns:campaign:retry",
|
|
"campaigns:campaign:reconcile",
|
|
"campaigns:recipient:read",
|
|
"campaigns:report:read",
|
|
"campaigns:report:send",
|
|
),
|
|
),
|
|
)
|
|
|
|
|
|
def _tenant_summary(session, tenant_id: str) -> dict[str, int]:
|
|
from govoplan_campaign.backend.db.models import Campaign
|
|
|
|
return {"campaigns": session.query(Campaign).filter(Campaign.tenant_id == tenant_id).count()}
|
|
|
|
|
|
def _campaigns_router(context: ModuleContext):
|
|
from govoplan_campaign.backend.runtime import configure_runtime
|
|
|
|
configure_runtime(registry=context.registry, settings=context.settings)
|
|
from fastapi import APIRouter
|
|
from govoplan_campaign.backend.router import router
|
|
from govoplan_campaign.backend.schema_router import router as schema_router
|
|
|
|
aggregate = APIRouter()
|
|
aggregate.include_router(router)
|
|
aggregate.include_router(schema_router)
|
|
return aggregate
|
|
|
|
|
|
manifest = ModuleManifest(
|
|
id="campaigns",
|
|
name="Campaigns",
|
|
version="0.1.6",
|
|
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
|
|
optional_dependencies=("files", "mail"),
|
|
provides_interfaces=(
|
|
ModuleInterfaceProvider(name="campaigns.access", version="0.1.6"),
|
|
ModuleInterfaceProvider(name="campaigns.delivery_tasks", version="0.1.6"),
|
|
ModuleInterfaceProvider(name="campaigns.mail_policy_context", version="0.1.6"),
|
|
ModuleInterfaceProvider(name="campaigns.policy_context", version="0.1.6"),
|
|
ModuleInterfaceProvider(name="campaigns.retention", version="0.1.6"),
|
|
),
|
|
requires_interfaces=(
|
|
ModuleInterfaceRequirement(
|
|
name="files.campaign_attachments",
|
|
version_min="0.1.0",
|
|
version_max_exclusive="0.2.0",
|
|
optional=True,
|
|
),
|
|
ModuleInterfaceRequirement(
|
|
name="mail.campaign_delivery",
|
|
version_min="0.1.0",
|
|
version_max_exclusive="0.2.0",
|
|
optional=True,
|
|
),
|
|
),
|
|
permissions=PERMISSIONS,
|
|
route_factory=_campaigns_router,
|
|
role_templates=ROLE_TEMPLATES,
|
|
tenant_summary_providers=(_tenant_summary,),
|
|
nav_items=(
|
|
NavItem(path="/campaigns", label="Campaigns", icon="campaign", required_any=("campaigns:campaign:read",), order=20),
|
|
NavItem(
|
|
path="/operator",
|
|
label="Operator Queue",
|
|
icon="activity",
|
|
required_any=(
|
|
"campaigns:campaign:queue",
|
|
"campaigns:campaign:retry",
|
|
"campaigns:campaign:reconcile",
|
|
"campaigns:campaign:control",
|
|
"campaigns:campaign:send",
|
|
),
|
|
order=30,
|
|
),
|
|
NavItem(path="/reports", label="Reports", icon="reports", required_any=("campaigns:report:read",), order=70),
|
|
),
|
|
frontend=FrontendModule(
|
|
module_id="campaigns",
|
|
package_name="@govoplan/campaign-webui",
|
|
nav_items=(
|
|
NavItem(path="/campaigns", label="Campaigns", icon="campaign", required_any=("campaigns:campaign:read",), order=20),
|
|
NavItem(
|
|
path="/operator",
|
|
label="Operator Queue",
|
|
icon="activity",
|
|
required_any=(
|
|
"campaigns:campaign:queue",
|
|
"campaigns:campaign:retry",
|
|
"campaigns:campaign:reconcile",
|
|
"campaigns:campaign:control",
|
|
"campaigns:campaign:send",
|
|
),
|
|
order=30,
|
|
),
|
|
NavItem(path="/reports", label="Reports", icon="reports", required_any=("campaigns:report:read",), order=70),
|
|
NavItem(path="/address-book", label="Address Book", icon="users", order=80),
|
|
NavItem(path="/templates", label="Templates", icon="form", order=90),
|
|
),
|
|
),
|
|
migration_spec=MigrationSpec(
|
|
module_id="campaigns",
|
|
metadata=Base.metadata,
|
|
script_location=str(Path(__file__).with_name("migrations") / "versions"),
|
|
retirement_supported=True,
|
|
retirement_provider=drop_table_retirement_provider(
|
|
campaign_models.Campaign,
|
|
campaign_models.CampaignShare,
|
|
campaign_models.RecipientImportMappingProfile,
|
|
campaign_models.CampaignVersion,
|
|
campaign_models.CampaignJob,
|
|
campaign_models.CampaignIssue,
|
|
campaign_models.AttachmentBlob,
|
|
campaign_models.AttachmentInstance,
|
|
campaign_models.SendAttempt,
|
|
campaign_models.ImapAppendAttempt,
|
|
label="Campaigns",
|
|
),
|
|
retirement_notes="Destructive retirement drops campaign-owned database tables after the installer captures a database snapshot.",
|
|
),
|
|
uninstall_guard_providers=(
|
|
persistent_table_uninstall_guard(
|
|
campaign_models.Campaign,
|
|
campaign_models.CampaignShare,
|
|
campaign_models.RecipientImportMappingProfile,
|
|
campaign_models.CampaignVersion,
|
|
campaign_models.CampaignJob,
|
|
campaign_models.CampaignIssue,
|
|
campaign_models.AttachmentBlob,
|
|
campaign_models.AttachmentInstance,
|
|
campaign_models.SendAttempt,
|
|
campaign_models.ImapAppendAttempt,
|
|
label="Campaigns",
|
|
),
|
|
),
|
|
capability_factories={
|
|
CAPABILITY_CAMPAIGNS_ACCESS: lambda context: __import__(
|
|
"govoplan_campaign.backend.capabilities",
|
|
fromlist=["access_capability"],
|
|
).access_capability(context),
|
|
CAPABILITY_CAMPAIGNS_DELIVERY_TASKS: lambda context: __import__(
|
|
"govoplan_campaign.backend.capabilities",
|
|
fromlist=["delivery_tasks_capability"],
|
|
).delivery_tasks_capability(context),
|
|
CAPABILITY_CAMPAIGNS_MAIL_POLICY_CONTEXT: lambda context: __import__(
|
|
"govoplan_campaign.backend.capabilities",
|
|
fromlist=["mail_policy_context_capability"],
|
|
).mail_policy_context_capability(context),
|
|
CAPABILITY_CAMPAIGNS_POLICY_CONTEXT: lambda context: __import__(
|
|
"govoplan_campaign.backend.capabilities",
|
|
fromlist=["policy_context_capability"],
|
|
).policy_context_capability(context),
|
|
CAPABILITY_CAMPAIGNS_RETENTION: lambda context: __import__(
|
|
"govoplan_campaign.backend.capabilities",
|
|
fromlist=["retention_capability"],
|
|
).retention_capability(context),
|
|
},
|
|
)
|
|
|
|
|
|
def get_manifest() -> ModuleManifest:
|
|
return manifest
|