- 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>
114 lines
3.8 KiB
Python
114 lines
3.8 KiB
Python
"""Conversation CRUD endpoints backed by PostgreSQL."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import uuid
|
|
from datetime import datetime, timezone
|
|
|
|
from litestar import delete, get, patch, post
|
|
from litestar.exceptions import NotFoundException
|
|
from sqlalchemy import select
|
|
|
|
from app.schemas import (
|
|
ConversationCreate,
|
|
ConversationDetail,
|
|
ConversationOut,
|
|
ConversationUpdate,
|
|
MessageOut,
|
|
)
|
|
from app.store.postgres import Conversation, Message, async_session_factory
|
|
|
|
|
|
def _conv_to_out(conv: Conversation) -> ConversationOut:
|
|
"""Convert a Conversation ORM object to the API response model."""
|
|
return ConversationOut(
|
|
id=conv.id,
|
|
title=conv.title,
|
|
created_at=conv.created_at.isoformat(),
|
|
updated_at=conv.updated_at.isoformat(),
|
|
)
|
|
|
|
|
|
@get("/api/conversations")
|
|
async def list_conversations() -> list[ConversationOut]:
|
|
"""GET /api/conversations - List all conversations."""
|
|
async with async_session_factory() as session:
|
|
stmt = select(Conversation).order_by(Conversation.updated_at.desc())
|
|
result = await session.execute(stmt)
|
|
convs = result.scalars().all()
|
|
return [_conv_to_out(c) for c in convs]
|
|
|
|
|
|
@get("/api/conversations/{conversation_id:str}")
|
|
async def get_conversation(conversation_id: str) -> ConversationDetail:
|
|
"""GET /api/conversations/:id - Get a single conversation with messages."""
|
|
async with async_session_factory() as session:
|
|
conv = await session.get(Conversation, conversation_id)
|
|
if conv is None:
|
|
raise NotFoundException(detail=f"Conversation {conversation_id} not found")
|
|
# Eagerly load messages
|
|
stmt = select(Message).where(
|
|
Message.conversation_id == conversation_id
|
|
).order_by(Message.created_at)
|
|
result = await session.execute(stmt)
|
|
msgs = result.scalars().all()
|
|
return ConversationDetail(
|
|
id=conv.id,
|
|
title=conv.title,
|
|
created_at=conv.created_at.isoformat(),
|
|
updated_at=conv.updated_at.isoformat(),
|
|
messages=[
|
|
MessageOut(
|
|
id=m.id,
|
|
role=m.role,
|
|
content=m.content,
|
|
created_at=m.created_at.isoformat(),
|
|
)
|
|
for m in msgs
|
|
],
|
|
)
|
|
|
|
|
|
@post("/api/conversations")
|
|
async def create_conversation(data: ConversationCreate) -> ConversationOut:
|
|
"""POST /api/conversations - Create a new conversation."""
|
|
conv = Conversation(
|
|
id=str(uuid.uuid4()),
|
|
title=data.title,
|
|
)
|
|
async with async_session_factory() as session:
|
|
session.add(conv)
|
|
await session.commit()
|
|
await session.refresh(conv)
|
|
return _conv_to_out(conv)
|
|
|
|
|
|
@patch("/api/conversations/{conversation_id:str}")
|
|
async def update_conversation(
|
|
conversation_id: str,
|
|
data: ConversationUpdate,
|
|
) -> ConversationOut:
|
|
"""PATCH /api/conversations/:id - Update conversation title."""
|
|
async with async_session_factory() as session:
|
|
conv = await session.get(Conversation, conversation_id)
|
|
if conv is None:
|
|
raise NotFoundException(
|
|
detail=f"Conversation {conversation_id} not found"
|
|
)
|
|
conv.title = data.title
|
|
conv.updated_at = datetime.now(timezone.utc)
|
|
await session.commit()
|
|
await session.refresh(conv)
|
|
return _conv_to_out(conv)
|
|
|
|
|
|
@delete("/api/conversations/{conversation_id:str}", status_code=200)
|
|
async def delete_conversation(conversation_id: str) -> dict:
|
|
"""DELETE /api/conversations/:id - Delete a conversation."""
|
|
async with async_session_factory() as session:
|
|
conv = await session.get(Conversation, conversation_id)
|
|
if conv is not None:
|
|
await session.delete(conv)
|
|
await session.commit()
|
|
return {"deleted": True}
|