"""SQLAlchemy models for realtime messaging Tables: - tr_messages: Stores all messages sent in incident rooms - tr_message_reactions: User reactions to messages (emoji) - tr_message_edit_history: Audit trail for message edits Note: All tables use 'tr_' prefix to avoid conflicts in shared database. """ from sqlalchemy import Column, Integer, String, Text, DateTime, Enum, ForeignKey, UniqueConstraint, Index, BigInteger, JSON from sqlalchemy.orm import relationship from datetime import datetime import enum import uuid from app.core.database import Base class MessageType(str, enum.Enum): """Types of messages in incident rooms""" TEXT = "text" IMAGE_REF = "image_ref" FILE_REF = "file_ref" SYSTEM = "system" INCIDENT_DATA = "incident_data" class Message(Base): """Message model for incident room communications""" __tablename__ = "tr_messages" message_id = Column(String(36), primary_key=True, default=lambda: str(uuid.uuid4())) room_id = Column(String(36), ForeignKey("tr_incident_rooms.room_id", ondelete="CASCADE"), nullable=False) sender_id = Column(String(255), nullable=False) # User email/ID content = Column(Text, nullable=False) message_type = Column(Enum(MessageType), default=MessageType.TEXT, nullable=False) # Message metadata for structured data, file references, etc. message_metadata = Column(JSON) # @Mention tracking - stores array of mentioned user_ids mentions = Column(JSON, default=list) # Timestamps created_at = Column(DateTime, default=datetime.utcnow, nullable=False) edited_at = Column(DateTime) # Last edit timestamp deleted_at = Column(DateTime) # Soft delete timestamp # Sequence number for FIFO ordering within a room sequence_number = Column(BigInteger, nullable=False) # Relationships reactions = relationship("MessageReaction", back_populates="message", cascade="all, delete-orphan") edit_history = relationship("MessageEditHistory", back_populates="message", cascade="all, delete-orphan") # Indexes for common queries __table_args__ = ( Index("ix_tr_messages_room_created", "room_id", "created_at"), Index("ix_tr_messages_room_sequence", "room_id", "sequence_number"), Index("ix_tr_messages_sender", "sender_id"), ) class MessageReaction(Base): """Message reaction model for emoji reactions""" __tablename__ = "tr_message_reactions" reaction_id = Column(Integer, primary_key=True, autoincrement=True) message_id = Column(String(36), ForeignKey("tr_messages.message_id", ondelete="CASCADE"), nullable=False) user_id = Column(String(255), nullable=False) # User email/ID who reacted emoji = Column(String(10), nullable=False) # Emoji character or code # Timestamp created_at = Column(DateTime, default=datetime.utcnow, nullable=False) # Relationships message = relationship("Message", back_populates="reactions") # Constraints and indexes __table_args__ = ( # Ensure unique reaction per user per message UniqueConstraint("message_id", "user_id", "emoji", name="uq_tr_message_reaction"), Index("ix_tr_message_reactions_message", "message_id"), ) class MessageEditHistory(Base): """Message edit history model for audit trail""" __tablename__ = "tr_message_edit_history" edit_id = Column(Integer, primary_key=True, autoincrement=True) message_id = Column(String(36), ForeignKey("tr_messages.message_id", ondelete="CASCADE"), nullable=False) original_content = Column(Text, nullable=False) # Content before edit edited_by = Column(String(255), nullable=False) # User who made the edit # Timestamp edited_at = Column(DateTime, default=datetime.utcnow, nullable=False) # Relationships message = relationship("Message", back_populates="edit_history") # Indexes __table_args__ = ( Index("ix_tr_message_edit_history_message", "message_id", "edited_at"), )