krz/hutch-stats

Server-side utility for calculating contributions for sourcehut users.

clone: git clone https://gitbay.org/krz/hutch-stats.git

main: src/srht_contrib/api/routes_repositories.py · raw

  1from __future__ import annotations
  2
  3from fastapi import APIRouter, Depends, HTTPException, Query, Response, status
  4from sqlalchemy import select
  5from sqlalchemy.exc import IntegrityError
  6from sqlalchemy.orm import Session
  7
  8from srht_contrib.api.dependencies import get_actor_identity_resolver, get_db, require_api_key
  9from srht_contrib.models import TrackedRepository
 10from srht_contrib.schemas import (
 11    TrackedRepositoryCreateRequest,
 12    TrackedRepositoryResponse,
 13    TrackedRepositoryUpdateRequest,
 14)
 15from srht_contrib.utils.identity import ActorIdentityResolver
 16from srht_contrib.utils.repositories import canonicalize_repository_name
 17
 18router = APIRouter(prefix="/api/repositories", tags=["repositories"], dependencies=[Depends(require_api_key)])
 19
 20
 21def _to_response(repository: TrackedRepository) -> TrackedRepositoryResponse:
 22    return TrackedRepositoryResponse(
 23        id=repository.id,
 24        service=repository.service,
 25        actor=repository.actor,
 26        repo_name=repository.repo_name,
 27    )
 28
 29
 30def _get_repository_or_404(db: Session, repository_id: int) -> TrackedRepository:
 31    repository = db.scalar(select(TrackedRepository).where(TrackedRepository.id == repository_id))
 32    if repository is None:
 33        raise HTTPException(status_code=404, detail="Tracked repository not found.")
 34    return repository
 35
 36
 37@router.get("", response_model=list[TrackedRepositoryResponse])
 38def list_tracked_repositories(
 39    actor: str | None = Query(default=None),
 40    db: Session = Depends(get_db),
 41    actor_identity_resolver: ActorIdentityResolver = Depends(get_actor_identity_resolver),
 42) -> list[TrackedRepositoryResponse]:
 43    stmt = select(TrackedRepository).where(TrackedRepository.service == "git").order_by(TrackedRepository.repo_name)
 44    if actor:
 45        canonical_actor = actor_identity_resolver.canonicalize(actor, db=db)
 46        stmt = stmt.where(TrackedRepository.actor == canonical_actor)
 47
 48    repositories = db.scalars(stmt).all()
 49    return [_to_response(repository) for repository in repositories]
 50
 51
 52@router.get("/{repository_id}", response_model=TrackedRepositoryResponse)
 53def get_tracked_repository(
 54    repository_id: int,
 55    db: Session = Depends(get_db),
 56) -> TrackedRepositoryResponse:
 57    return _to_response(_get_repository_or_404(db, repository_id))
 58
 59
 60@router.post("", response_model=TrackedRepositoryResponse, status_code=status.HTTP_201_CREATED)
 61def create_tracked_repository(
 62    payload: TrackedRepositoryCreateRequest,
 63    db: Session = Depends(get_db),
 64    actor_identity_resolver: ActorIdentityResolver = Depends(get_actor_identity_resolver),
 65) -> TrackedRepositoryResponse:
 66    canonical_actor = actor_identity_resolver.canonicalize(payload.actor, db=db)
 67    repository = TrackedRepository(
 68        service="git",
 69        actor=canonical_actor,
 70        repo_name=canonicalize_repository_name(canonical_actor, payload.repo_name),
 71    )
 72    db.add(repository)
 73    try:
 74        db.commit()
 75    except IntegrityError as exc:
 76        db.rollback()
 77        raise HTTPException(status_code=409, detail="Tracked repository already exists.") from exc
 78
 79    db.refresh(repository)
 80    return _to_response(repository)
 81
 82
 83@router.patch("/{repository_id}", response_model=TrackedRepositoryResponse)
 84def update_tracked_repository(
 85    repository_id: int,
 86    payload: TrackedRepositoryUpdateRequest,
 87    db: Session = Depends(get_db),
 88    actor_identity_resolver: ActorIdentityResolver = Depends(get_actor_identity_resolver),
 89) -> TrackedRepositoryResponse:
 90    repository = _get_repository_or_404(db, repository_id)
 91
 92    if payload.actor is not None:
 93        repository.actor = actor_identity_resolver.canonicalize(payload.actor, db=db)
 94
 95    if payload.repo_name is not None:
 96        repository.repo_name = canonicalize_repository_name(repository.actor, payload.repo_name)
 97
 98    db.add(repository)
 99    try:
100        db.commit()
101    except IntegrityError as exc:
102        db.rollback()
103        raise HTTPException(status_code=409, detail="Tracked repository already exists.") from exc
104
105    db.refresh(repository)
106    return _to_response(repository)
107
108
109@router.delete("/{repository_id}", status_code=status.HTTP_204_NO_CONTENT)
110def delete_tracked_repository(
111    repository_id: int,
112    db: Session = Depends(get_db),
113) -> Response:
114    repository = _get_repository_or_404(db, repository_id)
115    db.delete(repository)
116    db.commit()
117    return Response(status_code=status.HTTP_204_NO_CONTENT)