| """Chat session and message persistence models.""" |
| from datetime import datetime |
|
|
| from sqlalchemy import JSON, DateTime, ForeignKey, Index, String, Text, UniqueConstraint, func |
| from sqlalchemy.orm import Mapped, mapped_column, relationship |
|
|
| from app.core.database import Base |
| from app.utils.ids import prefixed_id |
|
|
|
|
| class ChatSession(Base): |
| __tablename__ = "chat_sessions" |
| __table_args__ = ( |
| Index("ix_chat_sessions_user_updated_at", "user_id", "updated_at"), |
| ) |
|
|
| id: Mapped[str] = mapped_column( |
| String(40), primary_key=True, default=lambda: prefixed_id("csess"), |
| ) |
| user_id: Mapped[str] = mapped_column( |
| String(40), ForeignKey("users.id", ondelete="CASCADE"), nullable=False, index=True, |
| ) |
| source_id: Mapped[str | None] = mapped_column(String(40), nullable=True, index=True) |
| subject: Mapped[str] = mapped_column(String(120), nullable=False, default="") |
| title: Mapped[str] = mapped_column(String(255), nullable=False, default="New chat") |
| |
| |
| |
| context_data: Mapped[dict[str, object]] = mapped_column( |
| JSON, nullable=False, default=dict, |
| ) |
| created_at: Mapped[datetime] = mapped_column( |
| DateTime(timezone=True), server_default=func.now(), nullable=False, |
| ) |
| updated_at: Mapped[datetime] = mapped_column( |
| DateTime(timezone=True), server_default=func.now(), onupdate=func.now(), nullable=False, |
| ) |
|
|
| messages: Mapped[list["ChatMessageRecord"]] = relationship( |
| back_populates="session", cascade="all, delete-orphan", order_by="ChatMessageRecord.created_at", |
| ) |
|
|
|
|
| class ChatMessageRecord(Base): |
| __tablename__ = "chat_messages" |
| __table_args__ = ( |
| UniqueConstraint( |
| "session_id", |
| "client_turn_id", |
| "role", |
| name="uq_chat_messages_session_turn_role", |
| ), |
| ) |
|
|
| id: Mapped[str] = mapped_column( |
| String(40), primary_key=True, default=lambda: prefixed_id("cmsg"), |
| ) |
| session_id: Mapped[str] = mapped_column( |
| String(40), ForeignKey("chat_sessions.id", ondelete="CASCADE"), nullable=False, index=True, |
| ) |
| role: Mapped[str] = mapped_column(String(20), nullable=False) |
| content: Mapped[str] = mapped_column(Text, nullable=False) |
| intent: Mapped[str | None] = mapped_column(String(40), nullable=True) |
| evidence_label: Mapped[str | None] = mapped_column(String(255), nullable=True) |
| client_turn_id: Mapped[str | None] = mapped_column(String(64), nullable=True) |
| web_sources: Mapped[list[dict[str, object]]] = mapped_column( |
| JSON, nullable=False, default=list, |
| ) |
| created_at: Mapped[datetime] = mapped_column( |
| DateTime(timezone=True), server_default=func.now(), nullable=False, |
| ) |
|
|
| session: Mapped["ChatSession"] = relationship(back_populates="messages") |
|
|