110 lines
3.3 KiB
Python
110 lines
3.3 KiB
Python
"""CRUD APIs for Artifacts — the heart of the ASF domain model."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from typing import Optional
|
|
from uuid import UUID
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
|
|
|
from asf_domain import (
|
|
Artifact,
|
|
ArtifactCreate,
|
|
ArtifactUpdate,
|
|
ArtifactType,
|
|
ArtifactRelationship,
|
|
)
|
|
from ...repositories import NotFoundError, TenantMismatchError
|
|
from .deps import artifact_service, relationship_service
|
|
from ...services import ArtifactService, RelationshipService
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("", response_model=list[Artifact], summary="List artifacts (tenant scoped)")
|
|
async def list_artifacts(
|
|
project_id: Optional[UUID] = Query(None),
|
|
type: Optional[ArtifactType] = Query(None),
|
|
status: Optional[str] = Query(None),
|
|
search: Optional[str] = Query(None, description="Case insensitive regex on title+content"),
|
|
limit: int = Query(50, ge=1, le=500),
|
|
offset: int = Query(0, ge=0),
|
|
svc: ArtifactService = Depends(artifact_service),
|
|
) -> list[Artifact]:
|
|
return await svc.list(
|
|
project_id=project_id,
|
|
artifact_type=type,
|
|
status=status,
|
|
search=search,
|
|
limit=limit,
|
|
offset=offset,
|
|
)
|
|
|
|
|
|
@router.post(
|
|
"",
|
|
response_model=Artifact,
|
|
status_code=status.HTTP_201_CREATED,
|
|
summary="Create a new artifact",
|
|
)
|
|
async def create_artifact(
|
|
body: ArtifactCreate,
|
|
svc: ArtifactService = Depends(artifact_service),
|
|
) -> Artifact:
|
|
try:
|
|
return await svc.create(body)
|
|
except NotFoundError as exc:
|
|
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
|
|
|
|
@router.get("/{artifact_id}", response_model=Artifact, summary="Get artifact by id")
|
|
async def get_artifact(
|
|
artifact_id: UUID,
|
|
svc: ArtifactService = Depends(artifact_service),
|
|
) -> Artifact:
|
|
a = await svc.get(artifact_id)
|
|
if a is None:
|
|
raise HTTPException(status_code=404, detail=f"Artifact {artifact_id} not found")
|
|
return a
|
|
|
|
|
|
@router.patch("/{artifact_id}", response_model=Artifact, summary="Update an artifact")
|
|
async def update_artifact(
|
|
artifact_id: UUID,
|
|
body: ArtifactUpdate,
|
|
svc: ArtifactService = Depends(artifact_service),
|
|
) -> Artifact:
|
|
try:
|
|
return await svc.update(artifact_id, body)
|
|
except (NotFoundError, TenantMismatchError) as exc:
|
|
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
|
|
|
|
@router.delete(
|
|
"/{artifact_id}",
|
|
status_code=status.HTTP_204_NO_CONTENT,
|
|
summary="Delete an artifact",
|
|
)
|
|
async def delete_artifact(
|
|
artifact_id: UUID,
|
|
svc: ArtifactService = Depends(artifact_service),
|
|
) -> None:
|
|
try:
|
|
await svc.delete(artifact_id)
|
|
except (NotFoundError, TenantMismatchError) as exc:
|
|
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
|
return None
|
|
|
|
|
|
@router.get(
|
|
"/{artifact_id}/relationships",
|
|
response_model=list[ArtifactRelationship],
|
|
summary="List all relationships (in + out) for an artifact — convenience wrapper for /relationships/for-artifact/{id}",
|
|
)
|
|
async def artifact_relationships(
|
|
artifact_id: UUID,
|
|
limit: int = Query(200, ge=1, le=500),
|
|
rel_svc: RelationshipService = Depends(relationship_service),
|
|
) -> list[ArtifactRelationship]:
|
|
return await rel_svc.for_artifact(artifact_id, limit=limit)
|