generated from coulomb/repo-seed
feat: use hub-core repo registry routes
This commit is contained in:
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user