from typing import Optional from sqlalchemy import create_engine, Column, Integer, JSON, ForeignKey from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column from sqlalchemy.sql.expression import select class Base(DeclarativeBase): pass def create_sql_engine (data_directory): sql_engine = create_engine(f"sqlite:///{data_directory}/horde.db") Base.metadata.create_all(sql_engine) return sql_engine class User(Base): __modelname__ = "user" __tablename__ = 'users' id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) email: Mapped[str] username: Mapped[str] # Stores the plain password. This must be changed eventually to store a salted hash instead. password: Mapped[str] class Agent(Base): __modelname__ = "agent" __tablename__ = 'agents' id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) handle: Mapped[Optional[str]] visibility: Mapped[int] = mapped_column(default=0) # Comma separated list of permission symbols permissions: Mapped[str] = mapped_column(default="") name: Mapped[str] description: Mapped[Optional[str]] picture: Mapped[Optional[str]] ordering: Mapped[Optional[int]] # Agents can be any of a set of agent classes kind: Mapped[str] # Stores the agent data specific to its class data = mapped_column(JSON, default={}) class Chat(Base): __modelname__ = 'chat' __tablename__ = 'chats' id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) user_id: Mapped[int] = mapped_column(ForeignKey("users.id")) name: Mapped[str] description: Mapped[Optional[str]] picture: Mapped[Optional[str]] ordering: Mapped[Optional[int]] # For indexing purposes, some information is taken from the last chat message. # Preview is the (possibly cropped) content, # time is the timestamp of the last event. last_message_preview: Mapped[Optional[str]] last_message_time: Mapped[Optional[int]] # Stores membership of agents in chats. If a record exists linking an agent and a chat, # it means the agent participates in that chat. class Membership(Base): __modelname__ = 'membership' __tablename__ = 'memberships' id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) agent_id: Mapped[int] = mapped_column(ForeignKey("agents.id")) chat_id: Mapped[int] = mapped_column(ForeignKey("chats.id")) class Message(Base): __modelname__ = 'message' __tablename__ = 'messages' id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) chat_id: Mapped[int] = mapped_column(ForeignKey("chats.id")) # Timestamp of this message time: Mapped[int] # Identifier of the sender: user / system / agent-$ID sender: Mapped[str] # Predefined set of message types, interpreted as 'text' when empty. kind: Mapped[str] content: Mapped[str]