Files
socaichat/backend/app/store/postgres.py
T
gongzhiyongandClaude Opus 4.6 efb3c53623 feat: add backend service and Azure deployment workflow
- 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>
2026-04-08 13:31:00 +08:00

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()