feat: add publish-cache and update asset endpoints
- Add POST /registry/publish-cache for publishing cache items with metadata
- Requires origin (self or external URL) for publishing
- Add PATCH /registry/{name} for updating existing assets
- Update activities now created when assets are modified
- Ownership check ensures only asset owner can update
- Origin info included in ActivityPub objects (generator/source)
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
172
server.py
172
server.py
@@ -95,6 +95,25 @@ class RecordRunRequest(BaseModel):
|
||||
output_name: str
|
||||
|
||||
|
||||
class PublishCacheRequest(BaseModel):
|
||||
"""Request to publish a cache item from L1."""
|
||||
content_hash: str
|
||||
asset_name: str
|
||||
asset_type: str = "image"
|
||||
origin: dict # {type: "self"|"external", url?: str, note?: str}
|
||||
description: Optional[str] = None
|
||||
tags: list[str] = []
|
||||
metadata: dict = {}
|
||||
|
||||
|
||||
class UpdateAssetRequest(BaseModel):
|
||||
"""Request to update an existing asset."""
|
||||
description: Optional[str] = None
|
||||
tags: Optional[list[str]] = None
|
||||
metadata: Optional[dict] = None
|
||||
origin: Optional[dict] = None
|
||||
|
||||
|
||||
# ============ Storage ============
|
||||
|
||||
def load_registry() -> dict:
|
||||
@@ -889,6 +908,66 @@ async def get_asset(name: str):
|
||||
return registry["assets"][name]
|
||||
|
||||
|
||||
@app.patch("/registry/{name}")
|
||||
async def update_asset(name: str, req: UpdateAssetRequest, user: User = Depends(get_required_user)):
|
||||
"""Update an existing asset's metadata. Creates an Update activity."""
|
||||
registry = load_registry()
|
||||
if name not in registry.get("assets", {}):
|
||||
raise HTTPException(404, f"Asset not found: {name}")
|
||||
|
||||
asset = registry["assets"][name]
|
||||
|
||||
# Check ownership
|
||||
if asset.get("owner") != user.username:
|
||||
raise HTTPException(403, f"Not authorized to update asset owned by {asset.get('owner')}")
|
||||
|
||||
# Update fields that were provided
|
||||
if req.description is not None:
|
||||
asset["description"] = req.description
|
||||
if req.tags is not None:
|
||||
asset["tags"] = req.tags
|
||||
if req.metadata is not None:
|
||||
asset["metadata"] = {**asset.get("metadata", {}), **req.metadata}
|
||||
if req.origin is not None:
|
||||
asset["origin"] = req.origin
|
||||
|
||||
asset["updated_at"] = datetime.now(timezone.utc).isoformat()
|
||||
|
||||
# Save registry
|
||||
registry["assets"][name] = asset
|
||||
save_registry(registry)
|
||||
|
||||
# Create Update activity
|
||||
activity = {
|
||||
"activity_id": str(uuid.uuid4()),
|
||||
"activity_type": "Update",
|
||||
"actor_id": f"https://{DOMAIN}/users/{user.username}",
|
||||
"object_data": {
|
||||
"type": asset.get("asset_type", "Object").capitalize(),
|
||||
"name": name,
|
||||
"id": f"https://{DOMAIN}/objects/{asset['content_hash']}",
|
||||
"contentHash": {
|
||||
"algorithm": "sha3-256",
|
||||
"value": asset["content_hash"]
|
||||
},
|
||||
"attributedTo": f"https://{DOMAIN}/users/{user.username}",
|
||||
"summary": req.description,
|
||||
"tag": req.tags or asset.get("tags", [])
|
||||
},
|
||||
"published": asset["updated_at"]
|
||||
}
|
||||
|
||||
# Sign activity
|
||||
activity = sign_activity(activity)
|
||||
|
||||
# Save activity
|
||||
activities = load_activities()
|
||||
activities.append(activity)
|
||||
save_activities(activities)
|
||||
|
||||
return {"asset": asset, "activity": activity}
|
||||
|
||||
|
||||
def _register_asset_impl(req: RegisterRequest, owner: str):
|
||||
"""Internal implementation for registering an asset."""
|
||||
registry = load_registry()
|
||||
@@ -989,6 +1068,99 @@ async def record_run(req: RecordRunRequest, user: User = Depends(get_required_us
|
||||
), user.username)
|
||||
|
||||
|
||||
@app.post("/registry/publish-cache")
|
||||
async def publish_cache(req: PublishCacheRequest, user: User = Depends(get_required_user)):
|
||||
"""
|
||||
Publish a cache item from L1 with metadata.
|
||||
|
||||
Requires origin to be set (self or external URL).
|
||||
Creates a new asset and Create activity.
|
||||
"""
|
||||
# Validate origin
|
||||
if not req.origin or "type" not in req.origin:
|
||||
raise HTTPException(400, "Origin is required for publishing (type: 'self' or 'external')")
|
||||
|
||||
origin_type = req.origin.get("type")
|
||||
if origin_type not in ("self", "external"):
|
||||
raise HTTPException(400, "Origin type must be 'self' or 'external'")
|
||||
|
||||
if origin_type == "external" and not req.origin.get("url"):
|
||||
raise HTTPException(400, "External origin requires a URL")
|
||||
|
||||
# Check if asset name already exists
|
||||
registry = load_registry()
|
||||
if req.asset_name in registry.get("assets", {}):
|
||||
raise HTTPException(400, f"Asset name already exists: {req.asset_name}")
|
||||
|
||||
# Create asset
|
||||
now = datetime.now(timezone.utc).isoformat()
|
||||
asset = {
|
||||
"name": req.asset_name,
|
||||
"content_hash": req.content_hash,
|
||||
"asset_type": req.asset_type,
|
||||
"tags": req.tags,
|
||||
"description": req.description,
|
||||
"origin": req.origin,
|
||||
"metadata": req.metadata,
|
||||
"owner": user.username,
|
||||
"created_at": now
|
||||
}
|
||||
|
||||
# Add to registry
|
||||
if "assets" not in registry:
|
||||
registry["assets"] = {}
|
||||
registry["assets"][req.asset_name] = asset
|
||||
save_registry(registry)
|
||||
|
||||
# Create ownership activity with origin info
|
||||
object_data = {
|
||||
"type": req.asset_type.capitalize(),
|
||||
"name": req.asset_name,
|
||||
"id": f"https://{DOMAIN}/objects/{req.content_hash}",
|
||||
"contentHash": {
|
||||
"algorithm": "sha3-256",
|
||||
"value": req.content_hash
|
||||
},
|
||||
"attributedTo": f"https://{DOMAIN}/users/{user.username}",
|
||||
"tag": req.tags
|
||||
}
|
||||
|
||||
if req.description:
|
||||
object_data["summary"] = req.description
|
||||
|
||||
# Include origin in ActivityPub object
|
||||
if origin_type == "self":
|
||||
object_data["generator"] = {
|
||||
"type": "Application",
|
||||
"name": "Art DAG",
|
||||
"note": "Original content created by the author"
|
||||
}
|
||||
else:
|
||||
object_data["source"] = {
|
||||
"type": "Link",
|
||||
"href": req.origin.get("url"),
|
||||
"name": req.origin.get("note", "External source")
|
||||
}
|
||||
|
||||
activity = {
|
||||
"activity_id": str(uuid.uuid4()),
|
||||
"activity_type": "Create",
|
||||
"actor_id": f"https://{DOMAIN}/users/{user.username}",
|
||||
"object_data": object_data,
|
||||
"published": now
|
||||
}
|
||||
|
||||
# Sign activity
|
||||
activity = sign_activity(activity)
|
||||
|
||||
# Save activity
|
||||
activities = load_activities()
|
||||
activities.append(activity)
|
||||
save_activities(activities)
|
||||
|
||||
return {"asset": asset, "activity": activity}
|
||||
|
||||
|
||||
# ============ Activities Endpoints ============
|
||||
|
||||
@app.get("/activities")
|
||||
|
||||
Reference in New Issue
Block a user