Add package dependencies and project settings page

Package Dependencies:
- Add artifact dependency management system
- Add dependency API endpoints (get, resolve, reverse)
- Add ensure file parsing for declaring dependencies
- Add circular dependency and conflict detection
- Add frontend dependency visualization with graph modal
- Add migration for artifact_dependencies table

Project Settings Page (#65):
- Add dedicated settings page for project admins
- General settings section (description, visibility)
- Access management section (moved from project page)
- Danger zone with inline delete confirmation
- Add Settings button to project page header
This commit is contained in:
Mondo Diaz
2026-01-27 15:29:51 +00:00
parent 6c8b922818
commit ba7cd96107
24 changed files with 4894 additions and 29 deletions

View File

@@ -916,3 +916,140 @@ class ProjectWithAccessResponse(ProjectResponse):
"""Project response with user's access level"""
user_access_level: Optional[str] = None
# Artifact Dependency schemas
class DependencyCreate(BaseModel):
"""Schema for creating a dependency"""
project: str
package: str
version: Optional[str] = None
tag: Optional[str] = None
@field_validator('version', 'tag')
@classmethod
def validate_constraint(cls, v, info):
return v
def model_post_init(self, __context):
"""Validate that exactly one of version or tag is set"""
if self.version is None and self.tag is None:
raise ValueError("Either 'version' or 'tag' must be specified")
if self.version is not None and self.tag is not None:
raise ValueError("Cannot specify both 'version' and 'tag'")
class DependencyResponse(BaseModel):
"""Schema for dependency response"""
id: UUID
artifact_id: str
project: str
package: str
version: Optional[str] = None
tag: Optional[str] = None
created_at: datetime
class Config:
from_attributes = True
@classmethod
def from_orm_model(cls, dep) -> "DependencyResponse":
"""Create from ORM model with field mapping"""
return cls(
id=dep.id,
artifact_id=dep.artifact_id,
project=dep.dependency_project,
package=dep.dependency_package,
version=dep.version_constraint,
tag=dep.tag_constraint,
created_at=dep.created_at,
)
class ArtifactDependenciesResponse(BaseModel):
"""Response containing all dependencies for an artifact"""
artifact_id: str
dependencies: List[DependencyResponse]
class DependentInfo(BaseModel):
"""Information about an artifact that depends on a package"""
artifact_id: str
project: str
package: str
version: Optional[str] = None
constraint_type: str # 'version' or 'tag'
constraint_value: str
class ReverseDependenciesResponse(BaseModel):
"""Response containing packages that depend on a given package"""
project: str
package: str
dependents: List[DependentInfo]
pagination: PaginationMeta
class EnsureFileDependency(BaseModel):
"""Dependency entry from orchard.ensure file"""
project: str
package: str
version: Optional[str] = None
tag: Optional[str] = None
@field_validator('version', 'tag')
@classmethod
def validate_constraint(cls, v, info):
return v
def model_post_init(self, __context):
"""Validate that exactly one of version or tag is set"""
if self.version is None and self.tag is None:
raise ValueError("Either 'version' or 'tag' must be specified")
if self.version is not None and self.tag is not None:
raise ValueError("Cannot specify both 'version' and 'tag'")
class EnsureFileContent(BaseModel):
"""Parsed content of orchard.ensure file"""
dependencies: List[EnsureFileDependency] = []
class ResolvedArtifact(BaseModel):
"""A resolved artifact in the dependency tree"""
artifact_id: str
project: str
package: str
version: Optional[str] = None
tag: Optional[str] = None
size: int
download_url: str
class DependencyResolutionResponse(BaseModel):
"""Response from dependency resolution endpoint"""
requested: Dict[str, str] # project, package, ref
resolved: List[ResolvedArtifact]
total_size: int
artifact_count: int
class DependencyConflict(BaseModel):
"""Details about a dependency conflict"""
project: str
package: str
requirements: List[Dict[str, Any]] # version/tag and required_by info
class DependencyConflictError(BaseModel):
"""Error response for dependency conflicts"""
error: str = "dependency_conflict"
message: str
conflicts: List[DependencyConflict]
class CircularDependencyError(BaseModel):
"""Error response for circular dependencies"""
error: str = "circular_dependency"
message: str
cycle: List[str] # List of "project/package" strings showing the cycle