Source code for rag_service.routers.conversations
"""Conversation history endpoints (list / messages / delete)."""
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from rag_service.bootstrap import RagComponents
from rag_service.conversation import ConversationManager
from rag_service.routers._deps import get_components
router = APIRouter(prefix="/conversations", tags=["conversations"])
def _ensure_conversation_access(owner_user_id: Optional[str], requester_user_id: Optional[str]) -> None:
ConversationManager._ensure_conversation_access(
owner_user_id=owner_user_id,
requester_user_id=requester_user_id,
)
[docs]
@router.get("")
async def list_conversations(
components: RagComponents = Depends(get_components),
user_id: Optional[str] = None,
limit: int = 50,
):
"""List conversations for a user, ordered by most recent."""
pg_repo = components.pg_repo
if pg_repo is None:
raise HTTPException(status_code=503, detail="Database not available")
session = pg_repo.get_session()
try:
rows = pg_repo.list_conversations(session, user_id, limit=limit)
return [
{
"id": str(row.id),
"title": row.title,
"created_at": row.created_at.isoformat() if row.created_at is not None else None,
"updated_at": row.updated_at.isoformat() if row.updated_at else None,
}
for row in rows
]
finally:
session.close()
[docs]
@router.get("/{conversation_id}/messages")
async def get_conversation_messages(
conversation_id: str,
components: RagComponents = Depends(get_components),
user_id: Optional[str] = None,
):
"""Get messages for a conversation (with ownership check)."""
pg_repo = components.pg_repo
if pg_repo is None:
raise HTTPException(status_code=503, detail="Database not available")
session = pg_repo.get_session()
try:
conv = pg_repo.get_conversation(session, conversation_id)
if conv is None:
raise HTTPException(status_code=404, detail="Conversation not found")
_ensure_conversation_access(conv.user_id, user_id)
rows = pg_repo.get_conversation_messages(session, conversation_id, limit=200)
return [
{
"id": str(row.id),
"role": row.role,
"content": row.content,
"mode": row.mode,
"created_at": row.created_at.isoformat() if row.created_at is not None else None,
"sources": (row.metadata_ or {}).get("sources"),
"timings": (row.metadata_ or {}).get("timings"),
"steps": (row.metadata_ or {}).get("steps"),
}
for row in rows
]
finally:
session.close()
[docs]
@router.delete("/{conversation_id}")
async def delete_conversation(
conversation_id: str,
components: RagComponents = Depends(get_components),
user_id: Optional[str] = None,
):
"""Delete a conversation (with ownership check)."""
pg_repo = components.pg_repo
if pg_repo is None:
raise HTTPException(status_code=503, detail="Database not available")
session = pg_repo.get_session()
try:
conv = pg_repo.get_conversation(session, conversation_id)
if conv is None:
raise HTTPException(status_code=404, detail="Conversation not found")
_ensure_conversation_access(conv.user_id, user_id)
pg_repo.delete_conversation(session, conversation_id)
session.commit()
return {"ok": True}
except HTTPException:
raise
except Exception:
session.rollback()
raise
finally:
session.close()