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 NotificationMessage(Base, TimestampMixin): __tablename__ = "notification_messages" __table_args__ = ( Index("ix_notification_messages_tenant_status", "tenant_id", "status"), Index("ix_notification_messages_tenant_recipient", "tenant_id", "recipient_type", "recipient_id"), Index( "ix_notification_messages_summary", "tenant_id", "recipient_id", "deleted_at", "status", "read_at", ), Index("ix_notification_messages_source", "tenant_id", "source_module", "source_resource_type", "source_resource_id"), Index("ix_notification_messages_due", "tenant_id", "status", "not_before_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) source_module: Mapped[str] = mapped_column(String(100), nullable=False, index=True) source_resource_type: Mapped[str] = mapped_column(String(100), nullable=False, index=True) source_resource_id: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) event_kind: Mapped[str] = mapped_column(String(100), nullable=False, index=True) channel: Mapped[str] = mapped_column(String(40), default="inbox", nullable=False, index=True) recipient: Mapped[str | None] = mapped_column(String(500), nullable=True, index=True) recipient_type: Mapped[str | None] = mapped_column(String(40), nullable=True, index=True) recipient_id: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) recipient_label: Mapped[str | None] = mapped_column(String(500), nullable=True) subject: Mapped[str | None] = mapped_column(String(500), nullable=True) body_text: Mapped[str | None] = mapped_column(Text, nullable=True) body_html: Mapped[str | None] = mapped_column(Text, nullable=True) action_url: Mapped[str | None] = mapped_column(String(1000), nullable=True) priority: Mapped[int] = mapped_column(Integer, default=0, nullable=False, index=True) status: Mapped[str] = mapped_column(String(40), default="pending", nullable=False, index=True) not_before_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True) queued_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) sent_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) failed_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=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) cancelled_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) attempt_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False) last_error: Mapped[str | None] = mapped_column(Text, nullable=True) external_message_id: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) payload: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False) metadata_: Mapped[dict[str, Any]] = mapped_column("metadata", JSON, default=dict, nullable=False) deleted_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True, index=True) attempts: Mapped[list["NotificationDeliveryAttempt"]] = relationship( back_populates="notification", cascade="all, delete-orphan", order_by="NotificationDeliveryAttempt.created_at", ) class NotificationDeliveryAttempt(Base, TimestampMixin): __tablename__ = "notification_delivery_attempts" __table_args__ = ( Index("ix_notification_attempts_notification", "notification_id", "attempt_no"), Index("ix_notification_attempts_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) notification_id: Mapped[str] = mapped_column(ForeignKey("notification_messages.id", ondelete="CASCADE"), nullable=False, index=True) attempt_no: Mapped[int] = mapped_column(Integer, nullable=False) channel: Mapped[str] = mapped_column(String(40), nullable=False, index=True) provider: Mapped[str | None] = mapped_column(String(100), nullable=True, index=True) status: Mapped[str] = mapped_column(String(40), nullable=False, index=True) started_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) finished_at: Mapped[datetime | None] = mapped_column(DateTime(timezone=True), nullable=True) external_message_id: Mapped[str | None] = mapped_column(String(255), nullable=True) error: Mapped[str | None] = mapped_column(Text, nullable=True) details: Mapped[dict[str, Any]] = mapped_column(JSON, default=dict, nullable=False) notification: Mapped[NotificationMessage] = relationship(back_populates="attempts") class NotificationPreference(Base, TimestampMixin): __tablename__ = "notification_preferences" __table_args__ = ( UniqueConstraint("tenant_id", "user_id", name="uq_notification_preferences_tenant_user"), Index("ix_notification_preferences_tenant_user", "tenant_id", "user_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) user_id: Mapped[str] = mapped_column(String(36), nullable=False, index=True) show_unread_badge: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False) email_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) email_digest_enabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False) muted_source_modules: Mapped[list[str]] = mapped_column(JSON, default=list, nullable=False) metadata_: Mapped[dict[str, Any]] = mapped_column("metadata", JSON, default=dict, nullable=False) __all__ = ["NotificationDeliveryAttempt", "NotificationMessage", "NotificationPreference", "new_uuid"]