- Add complete Python backend (Litestar + LangGraph) with chat, conversations, tickets APIs - Add GitHub Actions workflow for auto-deploying backend to Azure Web App (soc-backend) - Add gunicorn to requirements.txt for production serving - Update CLAUDE.md and EXTERNAL_SERVICES.md with latest config - Remove obsolete claudehd.md (merged into gpthd.md) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
99 lines
3.1 KiB
Python
99 lines
3.1 KiB
Python
"""PostgreSQL models, engine, and session management."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from sqlalchemy import DateTime, ForeignKey, String, Text, func
|
|
from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker, create_async_engine
|
|
from sqlalchemy.orm import DeclarativeBase, Mapped, mapped_column, relationship
|
|
|
|
from app.config import settings
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Engine & session factory
|
|
# ---------------------------------------------------------------------------
|
|
|
|
engine = create_async_engine(
|
|
settings.database_url,
|
|
echo=False,
|
|
pool_size=5,
|
|
max_overflow=10,
|
|
pool_pre_ping=True,
|
|
)
|
|
|
|
async_session_factory = async_sessionmaker(engine, expire_on_commit=False)
|
|
|
|
|
|
async def get_session() -> AsyncSession:
|
|
"""Yield a new async session (for use in route handlers)."""
|
|
async with async_session_factory() as session:
|
|
yield session
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# ORM base and models
|
|
# ---------------------------------------------------------------------------
|
|
|
|
class Base(DeclarativeBase):
|
|
pass
|
|
|
|
|
|
def _utcnow() -> datetime:
|
|
return datetime.now(timezone.utc)
|
|
|
|
|
|
class Conversation(Base):
|
|
__tablename__ = "conversations"
|
|
|
|
id: Mapped[str] = mapped_column(
|
|
String(64), primary_key=True, default=lambda: str(uuid.uuid4())
|
|
)
|
|
title: Mapped[str] = mapped_column(String(512), default="New conversation")
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=_utcnow, server_default=func.now()
|
|
)
|
|
updated_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=_utcnow, onupdate=_utcnow, server_default=func.now()
|
|
)
|
|
|
|
messages: Mapped[list[Message]] = relationship(
|
|
back_populates="conversation",
|
|
cascade="all, delete-orphan",
|
|
order_by="Message.created_at",
|
|
)
|
|
|
|
|
|
class Message(Base):
|
|
__tablename__ = "messages"
|
|
|
|
id: Mapped[str] = mapped_column(
|
|
String(64), primary_key=True, default=lambda: str(uuid.uuid4())
|
|
)
|
|
conversation_id: Mapped[str] = mapped_column(
|
|
String(64), ForeignKey("conversations.id", ondelete="CASCADE"), index=True
|
|
)
|
|
role: Mapped[str] = mapped_column(String(32)) # "human", "ai", "system"
|
|
content: Mapped[str] = mapped_column(Text, default="")
|
|
created_at: Mapped[datetime] = mapped_column(
|
|
DateTime(timezone=True), default=_utcnow, server_default=func.now()
|
|
)
|
|
|
|
conversation: Mapped[Conversation] = relationship(back_populates="messages")
|
|
|
|
|
|
# ---------------------------------------------------------------------------
|
|
# Table creation helper
|
|
# ---------------------------------------------------------------------------
|
|
|
|
async def create_tables() -> None:
|
|
"""Create all tables if they don't exist."""
|
|
async with engine.begin() as conn:
|
|
await conn.run_sync(Base.metadata.create_all)
|
|
|
|
|
|
async def dispose_engine() -> None:
|
|
"""Dispose the engine (for clean shutdown)."""
|
|
await engine.dispose()
|