from sqlalchemy import Column, Integer, String, Text, DateTime, Boolean, ForeignKey, Enum from sqlalchemy.orm import relationship from sqlalchemy.sql import func from database import Base import enum class User(Base): __tablename__ = "users" id = Column(Integer, primary_key=True, index=True) username = Column(String(50), unique=True, index=True, nullable=False) display_name = Column(String(100), nullable=False) email = Column(String(255), unique=True, index=True, nullable=True) hashed_password = Column(String, nullable=True) google_id = Column(String, unique=True, nullable=True) avatar_color = Column(String(7), default="#6366f1") public_key = Column(Text, nullable=True) # RSA public key for E2EE created_at = Column(DateTime(timezone=True), server_default=func.now()) is_online = Column(Boolean, default=False) last_seen = Column(DateTime(timezone=True), nullable=True) sent_messages = relationship("Message", foreign_keys="Message.sender_id", back_populates="sender") conversations = relationship("ConversationMember", back_populates="user") group_memberships = relationship("GroupMember", back_populates="user") class Conversation(Base): __tablename__ = "conversations" id = Column(Integer, primary_key=True, index=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) members = relationship("ConversationMember", back_populates="conversation") messages = relationship("Message", back_populates="conversation", order_by="Message.created_at") class ConversationMember(Base): __tablename__ = "conversation_members" id = Column(Integer, primary_key=True) conversation_id = Column(Integer, ForeignKey("conversations.id")) user_id = Column(Integer, ForeignKey("users.id")) conversation = relationship("Conversation", back_populates="members") user = relationship("User", back_populates="conversations") class Message(Base): __tablename__ = "messages" id = Column(Integer, primary_key=True, index=True) conversation_id = Column(Integer, ForeignKey("conversations.id")) sender_id = Column(Integer, ForeignKey("users.id")) # Encrypted ciphertext (base64 encoded, encrypted with recipient's public key) ciphertext_for_recipient = Column(Text, nullable=False) # Also store for sender so they can read their own messages ciphertext_for_sender = Column(Text, nullable=True) created_at = Column(DateTime(timezone=True), server_default=func.now()) is_read = Column(Boolean, default=False) sender = relationship("User", foreign_keys=[sender_id], back_populates="sent_messages") conversation = relationship("Conversation", back_populates="messages") class Group(Base): __tablename__ = "groups" id = Column(Integer, primary_key=True, index=True) name = Column(String(100), nullable=False) description = Column(String(255), nullable=True) created_by = Column(Integer, ForeignKey("users.id")) avatar_color = Column(String(7), default="#8b5cf6") created_at = Column(DateTime(timezone=True), server_default=func.now()) members = relationship("GroupMember", back_populates="group") messages = relationship("GroupMessage", back_populates="group", order_by="GroupMessage.created_at") creator = relationship("User", foreign_keys=[created_by]) class GroupMember(Base): __tablename__ = "group_members" id = Column(Integer, primary_key=True) group_id = Column(Integer, ForeignKey("groups.id")) user_id = Column(Integer, ForeignKey("users.id")) role = Column(String(20), default="member") # admin, member joined_at = Column(DateTime(timezone=True), server_default=func.now()) group = relationship("Group", back_populates="members") user = relationship("User", back_populates="group_memberships") class GroupMessage(Base): __tablename__ = "group_messages" id = Column(Integer, primary_key=True, index=True) group_id = Column(Integer, ForeignKey("groups.id")) sender_id = Column(Integer, ForeignKey("users.id")) # For group chats: AES key encrypted per-member (stored as JSON), message encrypted with AES encrypted_aes_keys = Column(Text, nullable=False) # JSON: {user_id: encrypted_aes_key} ciphertext = Column(Text, nullable=False) # AES-GCM encrypted message iv = Column(String(100), nullable=False) # AES-GCM IV created_at = Column(DateTime(timezone=True), server_default=func.now()) sender = relationship("User", foreign_keys=[sender_id]) group = relationship("Group", back_populates="messages")