import uuid from datetime import datetime from sqlalchemy import ( Boolean, Column, DateTime, Enum, ForeignKey, Index, Integer, String, Table, Text, ) from sqlalchemy.orm import relationship from sqlalchemy.sql import func from db.database import Base def _uuid() -> str: return str(uuid.uuid4()) # ── Many-to-many: Conversation ↔ Contact ────────────────────────────────────── conversation_participants = Table( "conversation_participants", Base.metadata, Column("conversation_id", String, ForeignKey("conversations.id", ondelete="CASCADE")), Column("contact_id", String, ForeignKey("contacts.id", ondelete="CASCADE")), ) # ── Contact ──────────────────────────────────────────────────────────────────── class Contact(Base): __tablename__ = "contacts" id = Column(String, primary_key=True, default=_uuid) name = Column(String(255), nullable=False, index=True) phone = Column(String(32), nullable=True, unique=True) email = Column(String(255), nullable=True) telegram_id = Column(String(64), nullable=True, unique=True) telegram_username = Column(String(255), nullable=True) whatsapp_phone = Column(String(32), nullable=True) notes = Column(Text, nullable=True) created_at = Column(DateTime, default=func.now(), nullable=False) updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) conversations = relationship( "Conversation", secondary=conversation_participants, back_populates="participants", ) messages_sent = relationship( "Message", back_populates="sender", foreign_keys="Message.sender_id", ) # ── Conversation ─────────────────────────────────────────────────────────────── class Conversation(Base): __tablename__ = "conversations" id = Column(String, primary_key=True, default=_uuid) channel = Column( Enum("telegram", "whatsapp", "sms", name="channel_type"), nullable=False, index=True, ) channel_conversation_id = Column(String(255), nullable=True) title = Column(String(255), nullable=True) is_group = Column(Boolean, default=False, nullable=False) is_archived = Column(Boolean, default=False, nullable=False) last_message_at = Column(DateTime, nullable=True) created_at = Column(DateTime, default=func.now(), nullable=False) updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) participants = relationship( "Contact", secondary=conversation_participants, back_populates="conversations", ) messages = relationship( "Message", back_populates="conversation", cascade="all, delete-orphan", order_by="Message.created_at", ) __table_args__ = ( Index("idx_conv_channel_native", "channel", "channel_conversation_id"), ) # ── Message ──────────────────────────────────────────────────────────────────── class Message(Base): __tablename__ = "messages" id = Column(String, primary_key=True, default=_uuid) conversation_id = Column( String, ForeignKey("conversations.id", ondelete="CASCADE"), nullable=False, index=True ) sender_id = Column(String, ForeignKey("contacts.id"), nullable=True) channel = Column( Enum("telegram", "whatsapp", "sms", name="channel_type"), nullable=False, index=True, ) channel_message_id = Column(String(255), nullable=True) direction = Column( Enum("inbound", "outbound", name="message_direction"), nullable=False, default="outbound", ) text = Column(Text, nullable=False) status = Column( Enum("pending", "sent", "delivered", "read", "failed", name="message_status"), default="pending", nullable=False, index=True, ) reply_to_id = Column(String, ForeignKey("messages.id"), nullable=True) is_edited = Column(Boolean, default=False, nullable=False) error_message = Column(Text, nullable=True) retry_count = Column(Integer, default=0, nullable=False) created_at = Column(DateTime, default=func.now(), nullable=False, index=True) updated_at = Column(DateTime, default=func.now(), onupdate=func.now()) sent_at = Column(DateTime, nullable=True) delivered_at = Column(DateTime, nullable=True) read_at = Column(DateTime, nullable=True) conversation = relationship("Conversation", back_populates="messages") sender = relationship("Contact", back_populates="messages_sent", foreign_keys=[sender_id]) reply_to = relationship("Message", remote_side="Message.id", backref="replies") __table_args__ = ( Index("idx_msg_channel_native", "channel", "channel_message_id"), Index("idx_msg_conv_created", "conversation_id", "created_at"), )