from __future__ import annotations import uuid from datetime import datetime from typing import Any from sqlalchemy import ( Boolean, DateTime, ForeignKey, Index, Integer, JSON, String, Text, UniqueConstraint, ) from sqlalchemy.orm import Mapped, mapped_column, relationship from govoplan_core.db.base import Base, TimestampMixin def new_uuid() -> str: return str(uuid.uuid4()) class PostboxTemplate(Base, TimestampMixin): __tablename__ = "postbox_templates" __table_args__ = ( UniqueConstraint( "tenant_id", "slug", name="uq_postbox_templates_tenant_slug", ), Index("ix_postbox_templates_tenant_status", "tenant_id", "status"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) slug: Mapped[str] = mapped_column(String(120), nullable=False) name: Mapped[str] = mapped_column(String(250), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) status: Mapped[str] = mapped_column( String(24), default="draft", nullable=False, index=True, ) current_revision: Mapped[int] = mapped_column(Integer, default=1, nullable=False) published_revision_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) created_by: Mapped[str | None] = mapped_column(String(255), nullable=True) updated_by: Mapped[str | None] = mapped_column(String(255), nullable=True) retired_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) revisions: Mapped[list["PostboxTemplateRevision"]] = relationship( back_populates="template", cascade="all, delete-orphan", order_by="PostboxTemplateRevision.revision", ) class PostboxTemplateRevision(Base, TimestampMixin): __tablename__ = "postbox_template_revisions" __table_args__ = ( UniqueConstraint( "template_id", "revision", name="uq_postbox_template_revision_number", ), Index( "ix_postbox_template_revisions_function_scope", "tenant_id", "function_type_id", "scope_kind", "scope_id", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) template_id: Mapped[str] = mapped_column( ForeignKey("postbox_templates.id", ondelete="CASCADE"), nullable=False, index=True, ) revision: Mapped[int] = mapped_column(Integer, nullable=False) function_type_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) scope_kind: Mapped[str] = mapped_column( String(30), default="tenant", nullable=False, ) scope_id: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) name_pattern: Mapped[str] = mapped_column( String(500), default="{unit_name} / {function_name}", nullable=False, ) address_pattern: Mapped[str] = mapped_column( String(500), default="{template_slug}.{unit_slug}.{function_slug}", nullable=False, ) classification: Mapped[str] = mapped_column( String(50), default="internal", nullable=False, ) allow_vacant_delivery: Mapped[bool] = mapped_column( Boolean, default=True, nullable=False, ) encryption_profile: Mapped[str] = mapped_column( String(80), default="plaintext_v1", nullable=False, ) history_policy: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) routing_policy: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) retention_policy: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) created_by: Mapped[str | None] = mapped_column(String(255), nullable=True) published_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True, ) template: Mapped[PostboxTemplate] = relationship(back_populates="revisions") class PostboxAddress(Base, TimestampMixin): __tablename__ = "postbox_addresses" __table_args__ = ( UniqueConstraint( "tenant_id", "address_key", name="uq_postbox_addresses_tenant_key", ), UniqueConstraint( "tenant_id", "address", name="uq_postbox_addresses_tenant_address", ), Index( "ix_postbox_addresses_function_scope", "tenant_id", "organization_unit_id", "function_id", "context_key", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) address_key: Mapped[str] = mapped_column(String(500), nullable=False) address: Mapped[str] = mapped_column(String(500), nullable=False) template_id: Mapped[str | None] = mapped_column( ForeignKey("postbox_templates.id", ondelete="SET NULL"), nullable=True, index=True, ) template_revision_id: Mapped[str | None] = mapped_column( ForeignKey("postbox_template_revisions.id", ondelete="SET NULL"), nullable=True, index=True, ) organization_unit_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) organization_unit_name: Mapped[str | None] = mapped_column( String(500), nullable=True, ) function_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) function_name: Mapped[str | None] = mapped_column(String(500), nullable=True) function_type_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) context_key: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) status: Mapped[str] = mapped_column( String(24), default="active", nullable=False, index=True, ) postbox: Mapped["Postbox | None"] = relationship( back_populates="address_record", uselist=False, ) class Postbox(Base, TimestampMixin): __tablename__ = "postboxes" __table_args__ = ( UniqueConstraint("address_id", name="uq_postboxes_address"), Index("ix_postboxes_tenant_status", "tenant_id", "status"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) address_id: Mapped[str] = mapped_column( ForeignKey("postbox_addresses.id", ondelete="RESTRICT"), nullable=False, index=True, ) name: Mapped[str] = mapped_column(String(500), nullable=False) description: Mapped[str | None] = mapped_column(Text, nullable=True) status: Mapped[str] = mapped_column( String(24), default="active", nullable=False, index=True, ) classification: Mapped[str] = mapped_column( String(50), default="internal", nullable=False, index=True, ) encryption_profile: Mapped[str] = mapped_column( String(80), default="plaintext_v1", nullable=False, ) key_epoch: Mapped[int] = mapped_column(Integer, default=1, nullable=False) settings: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) archived_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) address_record: Mapped[PostboxAddress] = relationship( back_populates="postbox", ) bindings: Mapped[list["PostboxBinding"]] = relationship( back_populates="postbox", cascade="all, delete-orphan", ) messages: Mapped[list["PostboxMessage"]] = relationship( back_populates="postbox", cascade="all, delete-orphan", ) class PostboxBinding(Base, TimestampMixin): __tablename__ = "postbox_bindings" __table_args__ = ( Index( "ix_postbox_bindings_function_scope", "tenant_id", "organization_unit_id", "function_id", "is_active", ), Index("ix_postbox_bindings_postbox_active", "postbox_id", "is_active"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) postbox_id: Mapped[str] = mapped_column( ForeignKey("postboxes.id", ondelete="CASCADE"), nullable=False, index=True, ) binding_type: Mapped[str] = mapped_column( String(30), default="function", nullable=False, ) organization_unit_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) function_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) function_type_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) applies_to_subunits: Mapped[bool] = mapped_column( Boolean, default=False, nullable=False, ) source: Mapped[str] = mapped_column( String(30), default="exact", nullable=False, ) is_active: Mapped[bool] = mapped_column( Boolean, default=True, nullable=False, index=True, ) valid_from: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) valid_until: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) settings: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) postbox: Mapped[Postbox] = relationship(back_populates="bindings") class PostboxMessage(Base, TimestampMixin): __tablename__ = "postbox_messages" __table_args__ = ( Index( "ix_postbox_messages_postbox_delivered", "postbox_id", "delivered_at", ), Index("ix_postbox_messages_tenant_status", "tenant_id", "status"), Index( "ix_postbox_messages_producer", "tenant_id", "producer_module", "producer_resource_type", "producer_resource_id", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) postbox_id: Mapped[str] = mapped_column( ForeignKey("postboxes.id", ondelete="CASCADE"), nullable=False, index=True, ) subject: Mapped[str] = mapped_column(String(1000), nullable=False) body_text: Mapped[str | None] = mapped_column(Text, nullable=True) status: Mapped[str] = mapped_column( String(30), default="delivered", nullable=False, index=True, ) classification: Mapped[str] = mapped_column( String(50), default="internal", nullable=False, index=True, ) sender_label: Mapped[str | None] = mapped_column(String(500), nullable=True) producer_module: Mapped[str | None] = mapped_column( String(100), nullable=True, index=True, ) producer_resource_type: Mapped[str | None] = mapped_column( String(100), nullable=True, index=True, ) producer_resource_id: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) in_reply_to_message_id: Mapped[str | None] = mapped_column( ForeignKey("postbox_messages.id", ondelete="SET NULL"), nullable=True, index=True, ) replaces_message_id: Mapped[str | None] = mapped_column( ForeignKey("postbox_messages.id", ondelete="SET NULL"), nullable=True, index=True, ) encryption_profile: Mapped[str] = mapped_column( String(80), default="plaintext_v1", nullable=False, ) key_epoch: Mapped[int] = mapped_column(Integer, default=1, nullable=False) ciphertext_ref: Mapped[str | None] = mapped_column( String(1000), nullable=True, ) signed_manifest_ref: Mapped[str | None] = mapped_column( String(1000), nullable=True, ) wrapped_keys: Mapped[list[dict[str, Any]]] = mapped_column( JSON, default=list, nullable=False, ) delivered_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True, ) expires_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True, ) withdrawn_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, index=True, ) retention_hold_until: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) metadata_: Mapped[dict[str, Any]] = mapped_column( "metadata", JSON, default=dict, nullable=False, ) postbox: Mapped[Postbox] = relationship(back_populates="messages") participants: Mapped[list["PostboxParticipant"]] = relationship( back_populates="message", cascade="all, delete-orphan", order_by="PostboxParticipant.position", ) attachments: Mapped[list["PostboxAttachmentReference"]] = relationship( back_populates="message", cascade="all, delete-orphan", order_by="PostboxAttachmentReference.position", ) receipts: Mapped[list["PostboxMessageReceipt"]] = relationship( back_populates="message", cascade="all, delete-orphan", ) class PostboxParticipant(Base, TimestampMixin): __tablename__ = "postbox_participants" __table_args__ = ( Index("ix_postbox_participants_message_kind", "message_id", "kind"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) message_id: Mapped[str] = mapped_column( ForeignKey("postbox_messages.id", ondelete="CASCADE"), nullable=False, index=True, ) kind: Mapped[str] = mapped_column(String(30), nullable=False) reference_type: Mapped[str] = mapped_column(String(50), nullable=False) reference_id: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) label: Mapped[str | None] = mapped_column(String(500), nullable=True) address: Mapped[str | None] = mapped_column(String(500), nullable=True) position: Mapped[int] = mapped_column(Integer, default=0, nullable=False) metadata_: Mapped[dict[str, Any]] = mapped_column( "metadata", JSON, default=dict, nullable=False, ) message: Mapped[PostboxMessage] = relationship(back_populates="participants") class PostboxAttachmentReference(Base, TimestampMixin): __tablename__ = "postbox_attachment_references" __table_args__ = ( Index( "ix_postbox_attachment_references_target", "tenant_id", "reference_type", "reference_id", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) message_id: Mapped[str] = mapped_column( ForeignKey("postbox_messages.id", ondelete="CASCADE"), nullable=False, index=True, ) reference_type: Mapped[str] = mapped_column(String(50), nullable=False) reference_id: Mapped[str] = mapped_column(String(255), nullable=False) name: Mapped[str | None] = mapped_column(String(1000), nullable=True) media_type: Mapped[str | None] = mapped_column(String(255), nullable=True) size_bytes: Mapped[int | None] = mapped_column(Integer, nullable=True) digest: Mapped[str | None] = mapped_column(String(255), nullable=True) ciphertext_ref: Mapped[str | None] = mapped_column( String(1000), nullable=True, ) position: Mapped[int] = mapped_column(Integer, default=0, nullable=False) metadata_: Mapped[dict[str, Any]] = mapped_column( "metadata", JSON, default=dict, nullable=False, ) message: Mapped[PostboxMessage] = relationship(back_populates="attachments") class PostboxDelivery(Base, TimestampMixin): __tablename__ = "postbox_deliveries" __table_args__ = ( UniqueConstraint( "tenant_id", "producer_module", "idempotency_key", name="uq_postbox_deliveries_producer_idempotency", ), Index("ix_postbox_deliveries_postbox_status", "postbox_id", "status"), Index( "ix_postbox_deliveries_producer", "tenant_id", "producer_module", "producer_resource_type", "producer_resource_id", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) postbox_id: Mapped[str] = mapped_column( ForeignKey("postboxes.id", ondelete="RESTRICT"), nullable=False, index=True, ) message_id: Mapped[str] = mapped_column( ForeignKey("postbox_messages.id", ondelete="RESTRICT"), nullable=False, index=True, ) producer_module: Mapped[str] = mapped_column(String(100), nullable=False) producer_resource_type: Mapped[str] = mapped_column(String(100), nullable=False) producer_resource_id: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) idempotency_key: Mapped[str] = mapped_column(String(255), nullable=False) status: Mapped[str] = mapped_column( String(40), default="accepted", nullable=False, index=True, ) template_revision_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) organization_unit_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) function_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) holder_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) target_snapshot: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) accepted_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True, ) metadata_: Mapped[dict[str, Any]] = mapped_column( "metadata", JSON, default=dict, nullable=False, ) class PostboxRoute(Base, TimestampMixin): __tablename__ = "postbox_routes" __table_args__ = ( UniqueConstraint( "delivery_id", "target_postbox_id", "route_kind", "depth", name="uq_postbox_routes_delivery_target_kind_depth", ), Index("ix_postbox_routes_delivery_kind", "delivery_id", "route_kind"), Index("ix_postbox_routes_target", "tenant_id", "target_postbox_id"), Index( "ix_postbox_routes_due", "status", "execute_after", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) delivery_id: Mapped[str] = mapped_column( ForeignKey("postbox_deliveries.id", ondelete="CASCADE"), nullable=False, index=True, ) source_postbox_id: Mapped[str] = mapped_column( ForeignKey("postboxes.id", ondelete="RESTRICT"), nullable=False, ) source_message_id: Mapped[str] = mapped_column( ForeignKey("postbox_messages.id", ondelete="RESTRICT"), nullable=False, ) target_postbox_id: Mapped[str | None] = mapped_column( ForeignKey("postboxes.id", ondelete="SET NULL"), nullable=True, index=True, ) target_message_id: Mapped[str | None] = mapped_column( ForeignKey("postbox_messages.id", ondelete="SET NULL"), nullable=True, ) route_kind: Mapped[str] = mapped_column(String(40), nullable=False) status: Mapped[str] = mapped_column(String(40), nullable=False) depth: Mapped[int] = mapped_column(Integer, default=0, nullable=False) source_route_id: Mapped[str | None] = mapped_column( ForeignKey("postbox_routes.id", ondelete="SET NULL"), nullable=True, ) execute_after: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) processed_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) policy_snapshot: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) class PostboxMessageReceipt(Base, TimestampMixin): __tablename__ = "postbox_message_receipts" __table_args__ = ( UniqueConstraint( "tenant_id", "message_id", "account_id", name="uq_postbox_receipts_message_account", ), Index( "ix_postbox_receipts_account_state", "tenant_id", "account_id", "read_at", "acknowledged_at", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) message_id: Mapped[str] = mapped_column( ForeignKey("postbox_messages.id", ondelete="CASCADE"), nullable=False, index=True, ) account_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) identity_id: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) assignment_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) read_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) acknowledged_at: Mapped[datetime | None] = mapped_column( DateTime(timezone=True), nullable=True, ) metadata_: Mapped[dict[str, Any]] = mapped_column( "metadata", JSON, default=dict, nullable=False, ) message: Mapped[PostboxMessage] = relationship(back_populates="receipts") class PostboxGrouping(Base, TimestampMixin): __tablename__ = "postbox_groupings" __table_args__ = ( UniqueConstraint( "tenant_id", "account_id", "name", name="uq_postbox_groupings_account_name", ), Index("ix_postbox_groupings_account", "tenant_id", "account_id"), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) account_id: Mapped[str] = mapped_column(String(255), nullable=False, index=True) name: Mapped[str] = mapped_column(String(250), nullable=False) is_default: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) settings: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) sources: Mapped[list["PostboxGroupingSource"]] = relationship( back_populates="grouping", cascade="all, delete-orphan", order_by="PostboxGroupingSource.position", ) class PostboxGroupingSource(Base, TimestampMixin): __tablename__ = "postbox_grouping_sources" __table_args__ = ( UniqueConstraint( "grouping_id", "postbox_id", name="uq_postbox_grouping_sources_postbox", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) grouping_id: Mapped[str] = mapped_column( ForeignKey("postbox_groupings.id", ondelete="CASCADE"), nullable=False, index=True, ) postbox_id: Mapped[str] = mapped_column( ForeignKey("postboxes.id", ondelete="CASCADE"), nullable=False, index=True, ) position: Mapped[int] = mapped_column(Integer, default=0, nullable=False) grouping: Mapped[PostboxGrouping] = relationship(back_populates="sources") class PostboxAccessEvent(Base, TimestampMixin): __tablename__ = "postbox_access_events" __table_args__ = ( Index( "ix_postbox_access_events_resource", "tenant_id", "postbox_id", "message_id", "occurred_at", ), Index( "ix_postbox_access_events_actor", "tenant_id", "account_id", "occurred_at", ), ) id: Mapped[str] = mapped_column(String(36), primary_key=True, default=new_uuid) tenant_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) postbox_id: Mapped[str | None] = mapped_column( ForeignKey("postboxes.id", ondelete="SET NULL"), nullable=True, index=True, ) message_id: Mapped[str | None] = mapped_column( ForeignKey("postbox_messages.id", ondelete="SET NULL"), nullable=True, index=True, ) account_id: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) identity_id: Mapped[str | None] = mapped_column( String(255), nullable=True, index=True, ) assignment_id: Mapped[str | None] = mapped_column( String(36), nullable=True, index=True, ) action: Mapped[str] = mapped_column(String(80), nullable=False, index=True) outcome: Mapped[str] = mapped_column(String(30), nullable=False, index=True) reason_code: Mapped[str] = mapped_column(String(80), nullable=False) occurred_at: Mapped[datetime] = mapped_column( DateTime(timezone=True), nullable=False, index=True, ) details: Mapped[dict[str, Any]] = mapped_column( JSON, default=dict, nullable=False, ) __all__ = [ "Postbox", "PostboxAccessEvent", "PostboxAddress", "PostboxAttachmentReference", "PostboxBinding", "PostboxDelivery", "PostboxGrouping", "PostboxGroupingSource", "PostboxMessage", "PostboxMessageReceipt", "PostboxParticipant", "PostboxRoute", "PostboxTemplate", "PostboxTemplateRevision", "new_uuid", ]