From 911871d1f6d044fe9f75e67f17d8e87d73e5d41e Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 7 Jun 2026 15:17:41 +0200 Subject: [PATCH] feat: use hub-core domains router --- api/routers/domains.py | 148 +++++++++++------------------------------ 1 file changed, 39 insertions(+), 109 deletions(-) diff --git a/api/routers/domains.py b/api/routers/domains.py index 5c54c2b..f837b04 100644 --- a/api/routers/domains.py +++ b/api/routers/domains.py @@ -1,8 +1,5 @@ -import uuid - -from fastapi import APIRouter, Depends, HTTPException, Query, Response, status +from fastapi import HTTPException from sqlalchemy import func, select -from sqlalchemy.orm import noload from sqlalchemy.ext.asyncio import AsyncSession from api.database import get_session @@ -12,90 +9,46 @@ from api.models.managed_repo import ManagedRepo from api.models.technical_debt import TechnicalDebt from api.models.topic import Topic from api.models.workstream import Workstream -from api.schemas.domain import DomainCreate, DomainDetail, DomainRead, DomainRename, DomainUpdate, RepoStub - -router = APIRouter(prefix="/domains", tags=["domains"]) +from api.schemas.domain import ( + DomainCreate, + DomainDetail, + DomainRead, + DomainRename, + DomainUpdate, + RepoStub, +) +from hub_core.routers.domains import create_domains_router -@router.get("/", response_model=list[DomainRead]) -async def list_domains( - response: Response, - status: str | None = Query(None, description="active | archived | all"), - session: AsyncSession = Depends(get_session), -) -> list[Domain]: - response.headers["Cache-Control"] = "max-age=60, stale-while-revalidate=30" - q = select(Domain).options( - noload(Domain.topics), - noload(Domain.repos), - noload(Domain.goals), - ).order_by(Domain.name) - if status and status != "all": - q = q.where(Domain.status == status) - elif status is None: - q = q.where(Domain.status == "active") - result = await session.execute(q) - return list(result.scalars().all()) - - -@router.post("/", response_model=DomainRead, status_code=status.HTTP_201_CREATED) -async def create_domain( - body: DomainCreate, - session: AsyncSession = Depends(get_session), -) -> Domain: - existing = await session.execute(select(Domain).where(Domain.slug == body.slug)) - if existing.scalar_one_or_none(): - raise HTTPException(status_code=409, detail=f"Domain slug '{body.slug}' already exists") - domain = Domain(slug=body.slug, name=body.name, description=body.description) - session.add(domain) - await session.commit() - await session.refresh(domain) - return domain - - -@router.get("/{slug}", response_model=DomainDetail) -async def get_domain( - slug: str, - session: AsyncSession = Depends(get_session), -) -> DomainDetail: - domain = await _get_domain_by_slug(slug, session) - - # Count topics +async def _build_domain_detail(domain: Domain, session: AsyncSession) -> DomainDetail: topic_count_row = await session.execute( select(func.count()).select_from(Topic).where(Topic.domain_id == domain.id) ) topic_count = topic_count_row.scalar_one() - # Count active workstreams (via topics) - topic_ids_row = await session.execute( - select(Topic.id).where(Topic.domain_id == domain.id) - ) - topic_ids = [r[0] for r in topic_ids_row.all()] + topic_ids_row = await session.execute(select(Topic.id).where(Topic.domain_id == domain.id)) + topic_ids = [row[0] for row in topic_ids_row.all()] - ws_count = 0 + workstream_count = 0 if topic_ids: - ws_count_row = await session.execute( + workstream_count_row = await session.execute( select(func.count()).select_from(Workstream) .where(Workstream.topic_id.in_(topic_ids)) .where(Workstream.status == "active") ) - ws_count = ws_count_row.scalar_one() + workstream_count = workstream_count_row.scalar_one() - # Count EPs and TDs ep_count_row = await session.execute( select(func.count()).select_from(ExtensionPoint) .where(ExtensionPoint.domain_id == domain.id) ) - ep_count = ep_count_row.scalar_one() - td_count_row = await session.execute( select(func.count()).select_from(TechnicalDebt) .where(TechnicalDebt.domain_id == domain.id) ) - td_count = td_count_row.scalar_one() - - # Repos repos_row = await session.execute( - select(ManagedRepo).where(ManagedRepo.domain_id == domain.id) + select(ManagedRepo) + .where(ManagedRepo.domain_id == domain.id) .where(ManagedRepo.status == "active") .order_by(ManagedRepo.name) ) @@ -110,43 +63,14 @@ async def get_domain( created_at=domain.created_at, updated_at=domain.updated_at, topic_count=topic_count, - workstream_count=ws_count, - ep_count=ep_count, - td_count=td_count, - repos=[RepoStub.model_validate(r) for r in repos], + workstream_count=workstream_count, + ep_count=ep_count_row.scalar_one(), + td_count=td_count_row.scalar_one(), + repos=[RepoStub.model_validate(repo) for repo in repos], ) -@router.patch("/{slug}/rename", response_model=DomainRead) -async def rename_domain( - slug: str, - body: DomainRename, - session: AsyncSession = Depends(get_session), -) -> Domain: - domain = await _get_domain_by_slug(slug, session) - - if body.new_slug != slug: - conflict = await session.execute(select(Domain).where(Domain.slug == body.new_slug)) - if conflict.scalar_one_or_none(): - raise HTTPException(status_code=409, detail=f"Slug '{body.new_slug}' already taken") - - old_slug = domain.slug - domain.slug = body.new_slug - domain.name = body.new_name - - await session.commit() - await session.refresh(domain) - return domain - - -@router.patch("/{slug}/archive", response_model=DomainRead) -async def archive_domain( - slug: str, - session: AsyncSession = Depends(get_session), -) -> Domain: - domain = await _get_domain_by_slug(slug, session) - - # Reject if any active topics exist for this domain +async def _reject_archive_with_active_topics(domain: Domain, session: AsyncSession) -> None: active_topics = await session.execute( select(func.count()).select_from(Topic) .where(Topic.domain_id == domain.id) @@ -158,15 +82,21 @@ async def archive_domain( detail="Cannot archive domain with active topics. Archive or reassign topics first.", ) - domain.status = "archived" - await session.commit() - await session.refresh(domain) - return domain +router = create_domains_router( + get_session, + domain_model=Domain, + repo_model=ManagedRepo, + domain_create_schema=DomainCreate, + domain_detail_schema=DomainDetail, + domain_read_schema=DomainRead, + domain_rename_schema=DomainRename, + domain_update_schema=DomainUpdate, + repo_stub_schema=RepoStub, + detail_builder=_build_domain_detail, + before_archive=_reject_archive_with_active_topics, + list_noload_fields=("topics", "repos", "goals"), + include_update_route=False, +) -async def _get_domain_by_slug(slug: str, session: AsyncSession) -> Domain: - result = await session.execute(select(Domain).where(Domain.slug == slug)) - domain = result.scalar_one_or_none() - if domain is None: - raise HTTPException(status_code=404, detail=f"Domain '{slug}' not found") - return domain +__all__ = ["router"]