first
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
"""Artifact relationship APIs — explicit edges.
|
||||
|
||||
All endpoints are tenant-scoped. Cross-tenant edges can never be created
|
||||
because both endpoint artifacts are validated against the tenant context.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Optional
|
||||
from uuid import UUID
|
||||
|
||||
from fastapi import APIRouter, Depends, HTTPException, Query, status
|
||||
|
||||
from asf_domain import (
|
||||
ArtifactRelationship,
|
||||
ArtifactRelationshipCreate,
|
||||
RelationshipType,
|
||||
)
|
||||
from ...repositories import NotFoundError, DuplicateError, TenantMismatchError
|
||||
from .deps import relationship_service
|
||||
from ...services import RelationshipService
|
||||
|
||||
router = APIRouter()
|
||||
|
||||
|
||||
@router.post(
|
||||
"",
|
||||
response_model=ArtifactRelationship,
|
||||
status_code=status.HTTP_201_CREATED,
|
||||
summary="Create a directed edge: source <relationship_type> target",
|
||||
)
|
||||
async def create_relationship(
|
||||
body: ArtifactRelationshipCreate,
|
||||
svc: RelationshipService = Depends(relationship_service),
|
||||
) -> ArtifactRelationship:
|
||||
try:
|
||||
return await svc.create(body)
|
||||
except DuplicateError as exc:
|
||||
raise HTTPException(status_code=409, detail=str(exc)) from exc
|
||||
except TenantMismatchError as exc:
|
||||
raise HTTPException(status_code=403, detail=str(exc)) from exc
|
||||
|
||||
|
||||
@router.delete(
|
||||
"/{relationship_id}",
|
||||
status_code=status.HTTP_204_NO_CONTENT,
|
||||
summary="Delete a relationship",
|
||||
)
|
||||
async def delete_relationship(
|
||||
relationship_id: UUID,
|
||||
svc: RelationshipService = Depends(relationship_service),
|
||||
) -> None:
|
||||
try:
|
||||
await svc.delete(relationship_id)
|
||||
except (NotFoundError, TenantMismatchError) as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
return None
|
||||
|
||||
|
||||
@router.get(
|
||||
"/from/{artifact_id}",
|
||||
response_model=list[ArtifactRelationship],
|
||||
summary="Outgoing edges: artifact → *",
|
||||
)
|
||||
async def list_outgoing(
|
||||
artifact_id: UUID,
|
||||
relationship_type: Optional[RelationshipType] = Query(None),
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
svc: RelationshipService = Depends(relationship_service),
|
||||
) -> list[ArtifactRelationship]:
|
||||
return await svc.outgoing(artifact_id, relationship_type=relationship_type, limit=limit)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/to/{artifact_id}",
|
||||
response_model=list[ArtifactRelationship],
|
||||
summary="Incoming edges: * → artifact",
|
||||
)
|
||||
async def list_incoming(
|
||||
artifact_id: UUID,
|
||||
relationship_type: Optional[RelationshipType] = Query(None),
|
||||
limit: int = Query(100, ge=1, le=500),
|
||||
svc: RelationshipService = Depends(relationship_service),
|
||||
) -> list[ArtifactRelationship]:
|
||||
return await svc.incoming(artifact_id, relationship_type=relationship_type, limit=limit)
|
||||
|
||||
|
||||
@router.get(
|
||||
"/for-artifact/{artifact_id}",
|
||||
response_model=list[ArtifactRelationship],
|
||||
summary="All edges touching an artifact (in + out)",
|
||||
)
|
||||
async def list_for_artifact(
|
||||
artifact_id: UUID,
|
||||
limit: int = Query(200, ge=1, le=1000),
|
||||
svc: RelationshipService = Depends(relationship_service),
|
||||
) -> list[ArtifactRelationship]:
|
||||
return await svc.for_artifact(artifact_id, limit=limit)
|
||||
Reference in New Issue
Block a user