Revert "Add API endpoints for listing tagged versions and artifacts"

This reverts commit 54e33e67ce.
This commit is contained in:
Mondo Diaz
2025-12-12 09:38:38 -06:00
committed by Armando Diaz
parent 21555d64a3
commit 11852adc66
2 changed files with 9 additions and 324 deletions

View File

@@ -1,4 +1,3 @@
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, Request, Query, Header, Response from fastapi import APIRouter, Depends, HTTPException, UploadFile, File, Form, Request, Query, Header, Response
from fastapi.responses import StreamingResponse from fastapi.responses import StreamingResponse
from sqlalchemy.orm import Session from sqlalchemy.orm import Session
@@ -11,13 +10,13 @@ import hashlib
from .database import get_db from .database import get_db
from .storage import get_storage, S3Storage, MULTIPART_CHUNK_SIZE from .storage import get_storage, S3Storage, MULTIPART_CHUNK_SIZE
from .models import Project, Package, Artifact, Tag, TagHistory, Upload, Consumer from .models import Project, Package, Artifact, Tag, Upload, Consumer
from .schemas import ( from .schemas import (
ProjectCreate, ProjectResponse, ProjectCreate, ProjectResponse,
PackageCreate, PackageResponse, PackageDetailResponse, TagSummary, PackageCreate, PackageResponse, PackageDetailResponse, TagSummary,
PACKAGE_FORMATS, PACKAGE_PLATFORMS, PACKAGE_FORMATS, PACKAGE_PLATFORMS,
ArtifactResponse, ArtifactDetailResponse, ArtifactTagInfo, PackageArtifactResponse, ArtifactResponse,
TagCreate, TagResponse, TagDetailResponse, TagHistoryResponse, TagCreate, TagResponse,
UploadResponse, UploadResponse,
ConsumerResponse, ConsumerResponse,
HealthResponse, HealthResponse,
@@ -851,17 +850,8 @@ def download_artifact_compat(
# Tag routes # Tag routes
@router.get("/api/v1/project/{project_name}/{package_name}/tags", response_model=PaginatedResponse[TagDetailResponse]) @router.get("/api/v1/project/{project_name}/{package_name}/tags", response_model=List[TagResponse])
def list_tags( def list_tags(project_name: str, package_name: str, db: Session = Depends(get_db)):
project_name: str,
package_name: str,
page: int = Query(default=1, ge=1, description="Page number"),
limit: int = Query(default=20, ge=1, le=100, description="Items per page"),
search: Optional[str] = Query(default=None, description="Search by tag name"),
sort: str = Query(default="name", description="Sort field (name, created_at)"),
order: str = Query(default="asc", description="Sort order (asc, desc)"),
db: Session = Depends(get_db),
):
project = db.query(Project).filter(Project.name == project_name).first() project = db.query(Project).filter(Project.name == project_name).first()
if not project: if not project:
raise HTTPException(status_code=404, detail="Project not found") raise HTTPException(status_code=404, detail="Project not found")
@@ -870,65 +860,8 @@ def list_tags(
if not package: if not package:
raise HTTPException(status_code=404, detail="Package not found") raise HTTPException(status_code=404, detail="Package not found")
# Validate sort field tags = db.query(Tag).filter(Tag.package_id == package.id).order_by(Tag.name).all()
valid_sort_fields = {"name": Tag.name, "created_at": Tag.created_at} return tags
if sort not in valid_sort_fields:
raise HTTPException(status_code=400, detail=f"Invalid sort field. Must be one of: {', '.join(valid_sort_fields.keys())}")
# Validate order
if order not in ("asc", "desc"):
raise HTTPException(status_code=400, detail="Invalid order. Must be 'asc' or 'desc'")
# Base query with JOIN to artifact for metadata
query = db.query(Tag, Artifact).join(Artifact, Tag.artifact_id == Artifact.id).filter(Tag.package_id == package.id)
# Apply search filter (case-insensitive on tag name)
if search:
query = query.filter(func.lower(Tag.name).contains(search.lower()))
# Get total count before pagination
total = query.count()
# Apply sorting
sort_column = valid_sort_fields[sort]
if order == "desc":
query = query.order_by(sort_column.desc())
else:
query = query.order_by(sort_column.asc())
# Apply pagination
offset = (page - 1) * limit
results = query.offset(offset).limit(limit).all()
# Calculate total pages
total_pages = math.ceil(total / limit) if total > 0 else 1
# Build detailed responses with artifact metadata
detailed_tags = []
for tag, artifact in results:
detailed_tags.append(TagDetailResponse(
id=tag.id,
package_id=tag.package_id,
name=tag.name,
artifact_id=tag.artifact_id,
created_at=tag.created_at,
created_by=tag.created_by,
artifact_size=artifact.size,
artifact_content_type=artifact.content_type,
artifact_original_name=artifact.original_name,
artifact_created_at=artifact.created_at,
artifact_format_metadata=artifact.format_metadata,
))
return PaginatedResponse(
items=detailed_tags,
pagination=PaginationMeta(
page=page,
limit=limit,
total=total,
total_pages=total_pages,
),
)
@router.post("/api/v1/project/{project_name}/{package_name}/tags", response_model=TagResponse) @router.post("/api/v1/project/{project_name}/{package_name}/tags", response_model=TagResponse)
@@ -975,70 +908,6 @@ def create_tag(
return db_tag return db_tag
@router.get("/api/v1/project/{project_name}/{package_name}/tags/{tag_name}", response_model=TagDetailResponse)
def get_tag(
project_name: str,
package_name: str,
tag_name: str,
db: Session = Depends(get_db),
):
"""Get a single tag with full artifact metadata"""
project = db.query(Project).filter(Project.name == project_name).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
package = db.query(Package).filter(Package.project_id == project.id, Package.name == package_name).first()
if not package:
raise HTTPException(status_code=404, detail="Package not found")
result = db.query(Tag, Artifact).join(Artifact, Tag.artifact_id == Artifact.id).filter(
Tag.package_id == package.id,
Tag.name == tag_name
).first()
if not result:
raise HTTPException(status_code=404, detail="Tag not found")
tag, artifact = result
return TagDetailResponse(
id=tag.id,
package_id=tag.package_id,
name=tag.name,
artifact_id=tag.artifact_id,
created_at=tag.created_at,
created_by=tag.created_by,
artifact_size=artifact.size,
artifact_content_type=artifact.content_type,
artifact_original_name=artifact.original_name,
artifact_created_at=artifact.created_at,
artifact_format_metadata=artifact.format_metadata,
)
@router.get("/api/v1/project/{project_name}/{package_name}/tags/{tag_name}/history", response_model=List[TagHistoryResponse])
def get_tag_history(
project_name: str,
package_name: str,
tag_name: str,
db: Session = Depends(get_db),
):
"""Get the history of artifact assignments for a tag"""
project = db.query(Project).filter(Project.name == project_name).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
package = db.query(Package).filter(Package.project_id == project.id, Package.name == package_name).first()
if not package:
raise HTTPException(status_code=404, detail="Package not found")
tag = db.query(Tag).filter(Tag.package_id == package.id, Tag.name == tag_name).first()
if not tag:
raise HTTPException(status_code=404, detail="Tag not found")
history = db.query(TagHistory).filter(TagHistory.tag_id == tag.id).order_by(TagHistory.changed_at.desc()).all()
return history
# Consumer routes # Consumer routes
@router.get("/api/v1/project/{project_name}/{package_name}/consumers", response_model=List[ConsumerResponse]) @router.get("/api/v1/project/{project_name}/{package_name}/consumers", response_model=List[ConsumerResponse])
def get_consumers(project_name: str, package_name: str, db: Session = Depends(get_db)): def get_consumers(project_name: str, package_name: str, db: Session = Depends(get_db)):
@@ -1054,122 +923,10 @@ def get_consumers(project_name: str, package_name: str, db: Session = Depends(ge
return consumers return consumers
# Package artifacts
@router.get("/api/v1/project/{project_name}/{package_name}/artifacts", response_model=PaginatedResponse[PackageArtifactResponse])
def list_package_artifacts(
project_name: str,
package_name: str,
page: int = Query(default=1, ge=1, description="Page number"),
limit: int = Query(default=20, ge=1, le=100, description="Items per page"),
content_type: Optional[str] = Query(default=None, description="Filter by content type"),
created_after: Optional[datetime] = Query(default=None, description="Filter artifacts created after this date"),
created_before: Optional[datetime] = Query(default=None, description="Filter artifacts created before this date"),
db: Session = Depends(get_db),
):
"""List all unique artifacts uploaded to a package"""
project = db.query(Project).filter(Project.name == project_name).first()
if not project:
raise HTTPException(status_code=404, detail="Project not found")
package = db.query(Package).filter(Package.project_id == project.id, Package.name == package_name).first()
if not package:
raise HTTPException(status_code=404, detail="Package not found")
# Get distinct artifacts uploaded to this package via uploads table
artifact_ids_subquery = db.query(func.distinct(Upload.artifact_id)).filter(
Upload.package_id == package.id
).subquery()
query = db.query(Artifact).filter(Artifact.id.in_(artifact_ids_subquery))
# Apply content_type filter
if content_type:
query = query.filter(Artifact.content_type == content_type)
# Apply date range filters
if created_after:
query = query.filter(Artifact.created_at >= created_after)
if created_before:
query = query.filter(Artifact.created_at <= created_before)
# Get total count before pagination
total = query.count()
# Apply pagination
offset = (page - 1) * limit
artifacts = query.order_by(Artifact.created_at.desc()).offset(offset).limit(limit).all()
# Calculate total pages
total_pages = math.ceil(total / limit) if total > 0 else 1
# Build responses with tag info
artifact_responses = []
for artifact in artifacts:
# Get tags pointing to this artifact in this package
tags = db.query(Tag.name).filter(
Tag.package_id == package.id,
Tag.artifact_id == artifact.id
).all()
tag_names = [t.name for t in tags]
artifact_responses.append(PackageArtifactResponse(
id=artifact.id,
size=artifact.size,
content_type=artifact.content_type,
original_name=artifact.original_name,
created_at=artifact.created_at,
created_by=artifact.created_by,
format_metadata=artifact.format_metadata,
tags=tag_names,
))
return PaginatedResponse(
items=artifact_responses,
pagination=PaginationMeta(
page=page,
limit=limit,
total=total,
total_pages=total_pages,
),
)
# Artifact by ID # Artifact by ID
@router.get("/api/v1/artifact/{artifact_id}", response_model=ArtifactDetailResponse) @router.get("/api/v1/artifact/{artifact_id}", response_model=ArtifactResponse)
def get_artifact(artifact_id: str, db: Session = Depends(get_db)): def get_artifact(artifact_id: str, db: Session = Depends(get_db)):
"""Get artifact metadata including list of packages/tags referencing it"""
artifact = db.query(Artifact).filter(Artifact.id == artifact_id).first() artifact = db.query(Artifact).filter(Artifact.id == artifact_id).first()
if not artifact: if not artifact:
raise HTTPException(status_code=404, detail="Artifact not found") raise HTTPException(status_code=404, detail="Artifact not found")
return artifact
# Get all tags referencing this artifact with package and project info
tags_with_context = db.query(Tag, Package, Project).join(
Package, Tag.package_id == Package.id
).join(
Project, Package.project_id == Project.id
).filter(
Tag.artifact_id == artifact_id
).all()
tag_infos = [
ArtifactTagInfo(
id=tag.id,
name=tag.name,
package_id=package.id,
package_name=package.name,
project_name=project.name,
)
for tag, package, project in tags_with_context
]
return ArtifactDetailResponse(
id=artifact.id,
size=artifact.size,
content_type=artifact.content_type,
original_name=artifact.original_name,
created_at=artifact.created_at,
created_by=artifact.created_by,
ref_count=artifact.ref_count,
format_metadata=artifact.format_metadata,
tags=tag_infos,
)

View File

@@ -129,78 +129,6 @@ class TagResponse(BaseModel):
from_attributes = True from_attributes = True
class TagDetailResponse(BaseModel):
"""Tag with embedded artifact metadata"""
id: UUID
package_id: UUID
name: str
artifact_id: str
created_at: datetime
created_by: str
# Artifact metadata
artifact_size: int
artifact_content_type: Optional[str]
artifact_original_name: Optional[str]
artifact_created_at: datetime
artifact_format_metadata: Optional[Dict[str, Any]] = None
class Config:
from_attributes = True
class TagHistoryResponse(BaseModel):
"""History entry for tag changes"""
id: UUID
tag_id: UUID
old_artifact_id: Optional[str]
new_artifact_id: str
changed_at: datetime
changed_by: str
class Config:
from_attributes = True
class ArtifactTagInfo(BaseModel):
"""Tag info for embedding in artifact responses"""
id: UUID
name: str
package_id: UUID
package_name: str
project_name: str
class ArtifactDetailResponse(BaseModel):
"""Artifact with list of tags/packages referencing it"""
id: str
size: int
content_type: Optional[str]
original_name: Optional[str]
created_at: datetime
created_by: str
ref_count: int
format_metadata: Optional[Dict[str, Any]] = None
tags: List[ArtifactTagInfo] = []
class Config:
from_attributes = True
class PackageArtifactResponse(BaseModel):
"""Artifact with tags for package artifact listing"""
id: str
size: int
content_type: Optional[str]
original_name: Optional[str]
created_at: datetime
created_by: str
format_metadata: Optional[Dict[str, Any]] = None
tags: List[str] = [] # Tag names pointing to this artifact
class Config:
from_attributes = True
# Upload response # Upload response
class UploadResponse(BaseModel): class UploadResponse(BaseModel):
artifact_id: str artifact_id: str