Add the experience service with sites through analytics/AI hooks, migrations through 0011, and phase docs/manifests marking the track complete. Co-authored-by: Cursor <cursoragent@cursor.com>
258 lines
9.3 KiB
Python
258 lines
9.3 KiB
Python
"""Consumer connectors, widgets & notify dispatches — Phase 11.9.
|
|
|
|
Experience stores connector registration shells, widget definitions/instances,
|
|
and Communication notify client dispatches. No SMS provider ownership (ADR-012/016).
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
|
|
from sqlalchemy import Index, String, Text, UniqueConstraint
|
|
from sqlalchemy.orm import Mapped, mapped_column
|
|
from sqlalchemy.types import JSON
|
|
|
|
from app.core.database import Base
|
|
from app.models.base import (
|
|
ActorAuditMixin,
|
|
OptimisticLockMixin,
|
|
SoftDeleteMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
UUIDPrimaryKeyMixin,
|
|
)
|
|
from app.models.types import (
|
|
GUID,
|
|
ConnectorDispatchDirection,
|
|
ConnectorDispatchStatus,
|
|
ConsumerConnectorKind,
|
|
ConsumerConnectorStatus,
|
|
NotifyDispatchStatus,
|
|
WidgetDefinitionStatus,
|
|
WidgetInstanceStatus,
|
|
WidgetKind,
|
|
)
|
|
|
|
|
|
class ExperienceConsumerConnectorRegistration(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
OptimisticLockMixin,
|
|
):
|
|
__tablename__ = "experience_consumer_connector_registrations"
|
|
|
|
workspace_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
site_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
page_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
kind: Mapped[ConsumerConnectorKind] = mapped_column(nullable=False)
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
status: Mapped[ConsumerConnectorStatus] = mapped_column(
|
|
default=ConsumerConnectorStatus.DRAFT, nullable=False
|
|
)
|
|
external_service_ref: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
entity_ref: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
config_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id",
|
|
"code",
|
|
name="uq_experience_consumer_connector_registrations_tenant_code",
|
|
),
|
|
Index(
|
|
"ix_experience_consumer_connector_registrations_workspace",
|
|
"tenant_id",
|
|
"workspace_id",
|
|
),
|
|
Index(
|
|
"ix_experience_consumer_connector_registrations_status",
|
|
"tenant_id",
|
|
"status",
|
|
),
|
|
Index(
|
|
"ix_experience_consumer_connector_registrations_tenant_deleted",
|
|
"tenant_id",
|
|
"is_deleted",
|
|
),
|
|
)
|
|
|
|
|
|
class ExperienceConsumerConnectorDispatch(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
OptimisticLockMixin,
|
|
):
|
|
__tablename__ = "experience_consumer_connector_dispatches"
|
|
|
|
registration_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
workspace_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
site_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
page_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
direction: Mapped[ConnectorDispatchDirection] = mapped_column(nullable=False)
|
|
event_type: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
payload_ref: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
status: Mapped[ConnectorDispatchStatus] = mapped_column(
|
|
default=ConnectorDispatchStatus.PENDING, nullable=False
|
|
)
|
|
response_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
notes: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
|
|
__table_args__ = (
|
|
Index(
|
|
"ix_experience_consumer_connector_dispatches_registration",
|
|
"tenant_id",
|
|
"registration_id",
|
|
),
|
|
Index(
|
|
"ix_experience_consumer_connector_dispatches_status",
|
|
"tenant_id",
|
|
"status",
|
|
),
|
|
Index(
|
|
"ix_experience_consumer_connector_dispatches_tenant_deleted",
|
|
"tenant_id",
|
|
"is_deleted",
|
|
),
|
|
)
|
|
|
|
|
|
class ExperienceWidgetDefinition(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
OptimisticLockMixin,
|
|
):
|
|
__tablename__ = "experience_widget_definitions"
|
|
|
|
workspace_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
widget_kind: Mapped[WidgetKind] = mapped_column(nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
description: Mapped[str | None] = mapped_column(Text, nullable=True)
|
|
status: Mapped[WidgetDefinitionStatus] = mapped_column(
|
|
default=WidgetDefinitionStatus.DRAFT, nullable=False
|
|
)
|
|
connector_kind: Mapped[ConsumerConnectorKind | None] = mapped_column(nullable=True)
|
|
default_config_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
permission_prefixes: Mapped[list | None] = mapped_column(JSON, nullable=True)
|
|
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id",
|
|
"code",
|
|
name="uq_experience_widget_definitions_tenant_code",
|
|
),
|
|
Index("ix_experience_widget_definitions_status", "tenant_id", "status"),
|
|
Index(
|
|
"ix_experience_widget_definitions_tenant_deleted",
|
|
"tenant_id",
|
|
"is_deleted",
|
|
),
|
|
)
|
|
|
|
|
|
class ExperienceWidgetInstance(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
OptimisticLockMixin,
|
|
):
|
|
__tablename__ = "experience_widget_instances"
|
|
|
|
workspace_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
site_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
page_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
widget_definition_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
widget_kind: Mapped[WidgetKind] = mapped_column(nullable=False)
|
|
code: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
name: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
status: Mapped[WidgetInstanceStatus] = mapped_column(
|
|
default=WidgetInstanceStatus.DRAFT, nullable=False
|
|
)
|
|
connector_registration_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
GUID(), nullable=True
|
|
)
|
|
entity_ref: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
embed_ref: Mapped[str] = mapped_column(String(255), nullable=False)
|
|
config_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
UniqueConstraint(
|
|
"tenant_id",
|
|
"site_id",
|
|
"code",
|
|
name="uq_experience_widget_instances_site_code",
|
|
),
|
|
UniqueConstraint(
|
|
"tenant_id",
|
|
"embed_ref",
|
|
name="uq_experience_widget_instances_embed_ref",
|
|
),
|
|
Index("ix_experience_widget_instances_site", "tenant_id", "site_id"),
|
|
Index("ix_experience_widget_instances_status", "tenant_id", "status"),
|
|
Index(
|
|
"ix_experience_widget_instances_tenant_deleted",
|
|
"tenant_id",
|
|
"is_deleted",
|
|
),
|
|
)
|
|
|
|
|
|
class ExperienceNotifyDispatch(
|
|
Base,
|
|
UUIDPrimaryKeyMixin,
|
|
TenantMixin,
|
|
TimestampMixin,
|
|
SoftDeleteMixin,
|
|
ActorAuditMixin,
|
|
OptimisticLockMixin,
|
|
):
|
|
__tablename__ = "experience_notify_dispatches"
|
|
|
|
workspace_id: Mapped[uuid.UUID] = mapped_column(GUID(), nullable=False)
|
|
site_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
page_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
widget_instance_id: Mapped[uuid.UUID | None] = mapped_column(GUID(), nullable=True)
|
|
connector_registration_id: Mapped[uuid.UUID | None] = mapped_column(
|
|
GUID(), nullable=True
|
|
)
|
|
channel: Mapped[str] = mapped_column(String(50), nullable=False)
|
|
template_key: Mapped[str] = mapped_column(String(100), nullable=False)
|
|
communication_request_ref: Mapped[str | None] = mapped_column(
|
|
String(255), nullable=True
|
|
)
|
|
to_ref: Mapped[str | None] = mapped_column(String(255), nullable=True)
|
|
payload_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
status: Mapped[NotifyDispatchStatus] = mapped_column(
|
|
default=NotifyDispatchStatus.PENDING, nullable=False
|
|
)
|
|
response_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
metadata_json: Mapped[dict | None] = mapped_column(JSON, nullable=True)
|
|
|
|
__table_args__ = (
|
|
Index("ix_experience_notify_dispatches_workspace", "tenant_id", "workspace_id"),
|
|
Index("ix_experience_notify_dispatches_status", "tenant_id", "status"),
|
|
Index(
|
|
"ix_experience_notify_dispatches_tenant_deleted",
|
|
"tenant_id",
|
|
"is_deleted",
|
|
),
|
|
)
|