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:
gilesb
2026-01-07 19:04:52 +00:00
parent 9a0e77a852
commit 0edea7b91b

172
server.py
View File

@@ -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")