"""Conversation and participant models for DM chat.""" from sqlalchemy import ( Column, Integer, String, DateTime, ForeignKey, UniqueConstraint, Index, ) from sqlalchemy.orm import relationship from sqlalchemy.sql import func from app.database import Base class Conversation(Base): """A DM conversation between two users. Each conversation is uniquely identified by the pair of participant user IDs (regardless of order). A conversation is soft-deletable via ``left_at`` on the participant row. """ __tablename__ = "conversations" id = Column(Integer, primary_key=True, index=True) # The most recent message's ID in this conversation (denormalised # for fast listing; updated by triggers or app logic). latest_message_id = Column( Integer, ForeignKey("messages.id", ondelete="SET NULL"), nullable=True ) created_at = Column(DateTime(timezone=True), server_default=func.now()) updated_at = Column(DateTime(timezone=True), server_default=func.now(), onupdate=func.now()) # Relationships latest_message = relationship( "Message", foreign_keys=[latest_message_id], lazy="select", ) participants = relationship( "ConversationParticipant", back_populates="conversation", cascade="all, delete-orphan", order_by="ConversationParticipant.id", ) __table_args__ = ( # Ensure exactly one conversation per unique pair of users UniqueConstraint("participant_1_id", "participant_2_id", name="uq_conversation_participants"), ) def __repr__(self) -> str: return f"" class ConversationParticipant(Base): """Link table between conversations and users. Stores per-participant metadata such as when they left the conversation (for soft-delete / archival). """ __tablename__ = "conversation_participants" id = Column(Integer, primary_key=True, index=True) conversation_id = Column( Integer, ForeignKey("conversations.id", ondelete="CASCADE"), nullable=False, ) user_id = Column( Integer, ForeignKey("users.id", ondelete="CASCADE"), nullable=False, ) # When the participant left (NULL = still active). left_at = Column(DateTime(timezone=True), nullable=True) # Timestamp of the last message read by this participant. last_read_at = Column(DateTime(timezone=True), nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) conversation = relationship("Conversation", back_populates="participants") user = relationship("User") __table_args__ = ( UniqueConstraint("conversation_id", "user_id", name="uq_conversation_user"), Index("ix_conversation_participants_user_id", "user_id"), ) def __repr__(self) -> str: return f""