ishaq101's picture
feat: chat history, room soft-delete, and user migration to PostgreSQL
08df5ae
"""Room management API endpoints."""
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy import select
from sqlalchemy.orm import selectinload
from src.db.postgres.connection import get_db
from src.db.postgres.models import Room, ChatMessage
from src.middlewares.logging import get_logger, log_execution
from pydantic import BaseModel
from typing import List
from datetime import datetime
import uuid
logger = get_logger("room_api")
router = APIRouter(prefix="/api/v1", tags=["Rooms"])
class ChatMessageResponse(BaseModel):
id: str
role: str
content: str
created_at: str
class RoomResponse(BaseModel):
id: str
title: str
created_at: str
updated_at: str | None
messages: List[ChatMessageResponse] = []
class CreateRoomRequest(BaseModel):
user_id: str
title: str = "New Chat"
@router.get("/rooms/{user_id}", response_model=List[RoomResponse])
@log_execution(logger)
async def list_rooms(
user_id: str,
db: AsyncSession = Depends(get_db)
):
"""List all rooms for a user."""
result = await db.execute(
select(Room)
.where(Room.user_id == user_id, Room.status == "active")
.order_by(Room.updated_at.desc())
)
rooms = result.scalars().all()
return [
RoomResponse(
id=room.id,
title=room.title,
created_at=room.created_at.isoformat(),
updated_at=room.updated_at.isoformat() if room.updated_at else None
)
for room in rooms
]
@router.get("/room/{room_id}", response_model=RoomResponse)
@log_execution(logger)
async def get_room(
room_id: str,
db: AsyncSession = Depends(get_db)
):
"""Get a specific room with its chat history."""
result = await db.execute(
select(Room)
.where(Room.id == room_id)
.options(selectinload(Room.messages))
)
room = result.scalars().first()
if not room:
raise HTTPException(
status_code=404,
detail="Room not found"
)
messages = sorted(room.messages, key=lambda m: m.created_at)
return RoomResponse(
id=room.id,
title=room.title,
created_at=room.created_at.isoformat(),
updated_at=room.updated_at.isoformat() if room.updated_at else None,
messages=[
ChatMessageResponse(
id=msg.id,
role=msg.role,
content=msg.content,
created_at=msg.created_at.isoformat()
)
for msg in messages
]
)
@router.delete("/room/{room_id}")
@log_execution(logger)
async def delete_room(
room_id: str,
user_id: str,
db: AsyncSession = Depends(get_db)
):
"""Soft-delete a room by setting its status to inactive."""
result = await db.execute(
select(Room).where(Room.id == room_id)
)
room = result.scalars().first()
if not room:
raise HTTPException(status_code=404, detail="Room not found")
if room.user_id != user_id:
raise HTTPException(status_code=403, detail="Access denied")
room.status = "inactive"
await db.commit()
return {"status": "success", "message": "Room deleted successfully"}
@router.post("/room/create")
@log_execution(logger)
async def create_room(
request: CreateRoomRequest,
db: AsyncSession = Depends(get_db)
):
"""Create a new room."""
room = Room(
id=str(uuid.uuid4()),
user_id=request.user_id,
title=request.title
)
db.add(room)
await db.commit()
await db.refresh(room)
return {
"status": "success",
"message": "Room created successfully",
"data": RoomResponse(
id=room.id,
title=room.title,
created_at=room.created_at.isoformat(),
updated_at=None
)
}