import uuid from fastapi import APIRouter, Depends, HTTPException, status from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession from api.database import get_session from api.models.domain import Domain from api.models.topic import Topic, TopicStatus from api.schemas.topic import TopicCreate, TopicRead, TopicUpdate, TopicWithWorkstreams router = APIRouter(prefix="/topics", tags=["topics"]) async def _resolve_domain_id(domain_slug: str, session: AsyncSession) -> uuid.UUID: """Resolve a domain slug to its UUID. Raises 404 if not found.""" result = await session.execute(select(Domain).where(Domain.slug == domain_slug)) domain = result.scalar_one_or_none() if domain is None: raise HTTPException(status_code=404, detail=f"Domain '{domain_slug}' not found") return domain.id @router.get("/", response_model=list[TopicRead]) async def list_topics( status: TopicStatus | None = None, session: AsyncSession = Depends(get_session), ) -> list[Topic]: q = select(Topic) if status: q = q.where(Topic.status == status) q = q.order_by(Topic.created_at) result = await session.execute(q) return list(result.scalars().all()) @router.post("/", response_model=TopicRead, status_code=status.HTTP_201_CREATED) async def create_topic( body: TopicCreate, session: AsyncSession = Depends(get_session), ) -> Topic: domain_id = await _resolve_domain_id(body.domain, session) existing = await session.execute(select(Topic).where(Topic.slug == body.slug)) if existing.scalar_one_or_none(): raise HTTPException(status_code=409, detail=f"Topic slug '{body.slug}' already exists") topic = Topic( slug=body.slug, title=body.title, description=body.description, domain_id=domain_id, status=body.status, ) session.add(topic) await session.commit() await session.refresh(topic) return topic @router.get("/{topic_id}", response_model=TopicWithWorkstreams) async def get_topic( topic_id: uuid.UUID, session: AsyncSession = Depends(get_session), ) -> Topic: topic = await session.get(Topic, topic_id) if topic is None: raise HTTPException(status_code=404, detail="Topic not found") return topic @router.patch("/{topic_id}", response_model=TopicRead) async def update_topic( topic_id: uuid.UUID, body: TopicUpdate, session: AsyncSession = Depends(get_session), ) -> Topic: topic = await session.get(Topic, topic_id) if topic is None: raise HTTPException(status_code=404, detail="Topic not found") updates = body.model_dump(exclude_unset=True) if "domain" in updates: topic.domain_id = await _resolve_domain_id(updates.pop("domain"), session) for field, value in updates.items(): setattr(topic, field, value) await session.commit() await session.refresh(topic) return topic @router.delete("/{topic_id}", response_model=TopicRead) async def archive_topic( topic_id: uuid.UUID, session: AsyncSession = Depends(get_session), ) -> Topic: topic = await session.get(Topic, topic_id) if topic is None: raise HTTPException(status_code=404, detail="Topic not found") topic.status = TopicStatus.archived await session.commit() await session.refresh(topic) return topic