From 38bde6cf89a4fd6022ee7cfaf84e6270e45edbe9 Mon Sep 17 00:00:00 2001 From: tegwick Date: Sun, 7 Jun 2026 22:24:53 +0200 Subject: [PATCH] feat: use hub-core repo registry routes --- api/routers/repos.py | 157 ++++++++----------------------------------- 1 file changed, 29 insertions(+), 128 deletions(-) diff --git a/api/routers/repos.py b/api/routers/repos.py index c8b0bc7..cccfada 100644 --- a/api/routers/repos.py +++ b/api/routers/repos.py @@ -9,9 +9,8 @@ import uuid from datetime import datetime, timezone from pathlib import Path -from fastapi import APIRouter, Depends, HTTPException, Response, status +from fastapi import APIRouter, Depends, HTTPException from sqlalchemy import case, func, select -from sqlalchemy.orm import noload from sqlalchemy.ext.asyncio import AsyncSession from api.config import settings @@ -46,71 +45,44 @@ from api.schemas.managed_repo import ( RepoUpdate, ScopeIssueDetail, ) +from hub_core.routers.repos import create_repos_router router = APIRouter(prefix="/repos", tags=["repos"]) -@router.get("/", response_model=list[RepoRead]) -async def list_repos( - response: Response, - domain: str | None = None, - session: AsyncSession = Depends(get_session), -) -> list[ManagedRepo]: - response.headers["Cache-Control"] = "max-age=60, stale-while-revalidate=30" - q = select(ManagedRepo).options(noload(ManagedRepo.goals)).order_by(ManagedRepo.name) - if domain: - domain_row = await session.execute(select(Domain).where(Domain.slug == domain)) - domain_obj = domain_row.scalar_one_or_none() - if domain_obj is None: - raise HTTPException(status_code=404, detail=f"Domain '{domain}' not found") - q = q.where(ManagedRepo.domain_id == domain_obj.id) - result = await session.execute(q) - return list(result.scalars().all()) - - -@router.post("/", response_model=RepoRead, status_code=status.HTTP_201_CREATED) -async def register_repo( - body: RepoCreate, - session: AsyncSession = Depends(get_session), -) -> ManagedRepo: - domain_row = await session.execute(select(Domain).where(Domain.slug == body.domain_slug)) - domain_obj = domain_row.scalar_one_or_none() - if domain_obj is None: - raise HTTPException(status_code=404, detail=f"Domain '{body.domain_slug}' not found") - - existing = await session.execute(select(ManagedRepo).where(ManagedRepo.slug == body.slug)) - if existing.scalar_one_or_none(): - raise HTTPException(status_code=409, detail=f"Repo slug '{body.slug}' already exists") - - repo = ManagedRepo( - domain_id=domain_obj.id, - slug=body.slug, - name=body.name, - local_path=body.local_path, - host_paths=body.host_paths, - remote_url=body.remote_url, - git_fingerprint=body.git_fingerprint, - description=body.description, - topic_id=body.topic_id, - ) - session.add(repo) - await session.commit() - await session.refresh(repo) - +async def _publish_repo_registered(repo: ManagedRepo, body: RepoCreate, domain: Domain) -> None: subject = "org.statehub.repo.registered" envelope = EventEnvelope.new( subject, attributes={ "repo_id": str(repo.id), "repo_slug": repo.slug, - "domain_slug": body.domain_slug, + "domain_slug": domain.slug, "remote_url": repo.remote_url, "local_path": repo.local_path, }, ) asyncio.create_task(publish_event(subject, envelope)) - return repo + +def _core_repo_router(**route_flags) -> APIRouter: + return create_repos_router( + get_session, + prefix="", + domain_model=Domain, + repo_model=ManagedRepo, + repo_create_schema=RepoCreate, + repo_update_schema=RepoUpdate, + repo_read_schema=RepoRead, + repo_path_register_schema=RepoPathRegister, + list_noload_fields=("goals",), + create_extension_fields=("topic_id",), + after_register=_publish_repo_registered, + **route_flags, + ) + + +router.include_router(_core_repo_router(include_slug_routes=False)) @router.post("/onboard", response_model=RepoOnboardResult) @@ -184,43 +156,6 @@ async def onboard_repo(body: RepoOnboardRequest) -> RepoOnboardResult: ) -@router.get("/by-fingerprint", response_model=list[RepoRead]) -async def get_repo_by_fingerprint( - hash: str, - remote_url: str | None = None, - session: AsyncSession = Depends(get_session), -) -> list[ManagedRepo]: - """Look up repos by git root-commit SHA-1 fingerprint. - - The fingerprint is the output of ``git rev-list --max-parents=0 HEAD`` and - is identical across every clone of the same repository. Repos that share - git history (forks, monorepo splits) will have the same fingerprint. - - Pass ``remote_url`` to narrow results to a specific remote — useful when - multiple repos share the same ancestor commit. - - Returns an empty list if no match is found. - """ - q = select(ManagedRepo).where(ManagedRepo.git_fingerprint == hash) - if remote_url: - q = q.where(ManagedRepo.remote_url == remote_url) - result = await session.execute(q) - return list(result.scalars().all()) - - -@router.get("/by-remote", response_model=RepoRead) -async def get_repo_by_remote_url( - url: str, - session: AsyncSession = Depends(get_session), -) -> ManagedRepo: - """Look up a repo by its git remote URL (fallback; prefer /by-fingerprint).""" - result = await session.execute(select(ManagedRepo).where(ManagedRepo.remote_url == url)) - repo = result.scalar_one_or_none() - if repo is None: - raise HTTPException(status_code=404, detail=f"No repo with remote_url '{url}' found") - return repo - - @router.get("/doi/summary", response_model=list[DoISummaryEntry]) async def doi_summary(session: AsyncSession = Depends(get_session)) -> list[DoISummaryEntry]: """Return DoI tier for all active repos, worst tier first. @@ -493,46 +428,12 @@ async def list_repo_scope_health( return entries -@router.get("/{slug}", response_model=RepoRead) -async def get_repo( - slug: str, - session: AsyncSession = Depends(get_session), -) -> ManagedRepo: - return await _get_repo_by_slug(slug, session) - - -@router.patch("/{slug}", response_model=RepoRead) -async def update_repo( - slug: str, - body: RepoUpdate, - session: AsyncSession = Depends(get_session), -) -> ManagedRepo: - repo = await _get_repo_by_slug(slug, session) - for field, value in body.model_dump(exclude_unset=True).items(): - setattr(repo, field, value) - await session.commit() - await session.refresh(repo) - return repo - - -@router.post("/{slug}/paths", response_model=RepoRead) -async def register_host_path( - slug: str, - body: RepoPathRegister, - session: AsyncSession = Depends(get_session), -) -> ManagedRepo: - """Register or update the local path for a specific host. - - Merges {"host": path} into host_paths without overwriting other entries. - Use this when a repo lives at a different absolute path on different machines. - """ - repo = await _get_repo_by_slug(slug, session) - updated = dict(repo.host_paths or {}) - updated[body.host] = body.path - repo.host_paths = updated - await session.commit() - await session.refresh(repo) - return repo +router.include_router( + _core_repo_router( + include_collection_routes=False, + include_lookup_routes=False, + ) +) @router.patch("/{slug}/archive", response_model=RepoRead)