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.domain_goal import DomainGoal, DomainGoalStatus # noqa: F401 (DomainGoalStatus used in activate) from api.schemas.domain_goal import DomainGoalCreate, DomainGoalRead, DomainGoalUpdate router = APIRouter(prefix="/domain-goals", tags=["domain-goals"]) async def _resolve_domain(domain_slug: str, session: AsyncSession) -> Domain: 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 @router.get("/", response_model=list[DomainGoalRead]) async def list_domain_goals( domain_slug: str | None = None, status: str | None = None, session: AsyncSession = Depends(get_session), ) -> list[DomainGoal]: q = select(DomainGoal) if domain_slug: domain = await _resolve_domain(domain_slug, session) q = q.where(DomainGoal.domain_id == domain.id) if status: q = q.where(DomainGoal.status == status) q = q.order_by(DomainGoal.created_at.desc()) result = await session.execute(q) return list(result.scalars().all()) @router.post("/", response_model=DomainGoalRead, status_code=status.HTTP_201_CREATED) async def create_domain_goal( body: DomainGoalCreate, session: AsyncSession = Depends(get_session), ) -> DomainGoal: if body.status == DomainGoalStatus.active: # Archive any existing active goal for this domain existing = await session.execute( select(DomainGoal).where( DomainGoal.domain_id == body.domain_id, DomainGoal.status == DomainGoalStatus.active, ) ) for old in existing.scalars().all(): old.status = DomainGoalStatus.superseded goal = DomainGoal(**body.model_dump()) session.add(goal) await session.commit() await session.refresh(goal) return goal @router.get("/{goal_id}", response_model=DomainGoalRead) async def get_domain_goal( goal_id: uuid.UUID, session: AsyncSession = Depends(get_session), ) -> DomainGoal: goal = await session.get(DomainGoal, goal_id) if goal is None: raise HTTPException(status_code=404, detail="Domain goal not found") return goal @router.patch("/{goal_id}", response_model=DomainGoalRead) async def update_domain_goal( goal_id: uuid.UUID, body: DomainGoalUpdate, session: AsyncSession = Depends(get_session), ) -> DomainGoal: goal = await session.get(DomainGoal, goal_id) if goal is None: raise HTTPException(status_code=404, detail="Domain goal not found") for field, value in body.model_dump(exclude_unset=True).items(): setattr(goal, field, value) await session.commit() await session.refresh(goal) return goal @router.post("/{goal_id}/activate", response_model=DomainGoalRead) async def activate_domain_goal( goal_id: uuid.UUID, session: AsyncSession = Depends(get_session), ) -> DomainGoal: """Set this goal as the active domain goal, superseding any currently active one.""" goal = await session.get(DomainGoal, goal_id) if goal is None: raise HTTPException(status_code=404, detail="Domain goal not found") # Supersede any other active goal for this domain existing = await session.execute( select(DomainGoal).where( DomainGoal.domain_id == goal.domain_id, DomainGoal.status == DomainGoalStatus.active, DomainGoal.id != goal_id, ) ) for old in existing.scalars().all(): old.status = DomainGoalStatus.superseded goal.status = DomainGoalStatus.active await session.commit() await session.refresh(goal) return goal