feat: add L1-L2 publish integration endpoints
- Add POST /cache/{hash}/publish to publish cache items to L2
- Add PATCH /cache/{hash}/republish to sync metadata updates
- Validates origin is set before publishing
- Tracks publish status in cache metadata (to_l2, asset_name, timestamps)
- Forwards auth token to L2 for authentication
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
178
server.py
178
server.py
@@ -614,6 +614,12 @@ class CacheMetaUpdate(BaseModel):
|
||||
collections: Optional[list[str]] = None
|
||||
|
||||
|
||||
class PublishRequest(BaseModel):
|
||||
"""Request to publish a cache item to L2."""
|
||||
asset_name: str
|
||||
asset_type: str = "image" # image, video, etc.
|
||||
|
||||
|
||||
@app.get("/cache/{content_hash}/meta")
|
||||
async def get_cache_meta(content_hash: str, username: str = Depends(get_required_user)):
|
||||
"""Get metadata for a cached file."""
|
||||
@@ -670,6 +676,178 @@ async def update_cache_meta(content_hash: str, update: CacheMetaUpdate, username
|
||||
return meta
|
||||
|
||||
|
||||
@app.post("/cache/{content_hash}/publish")
|
||||
async def publish_cache_to_l2(
|
||||
content_hash: str,
|
||||
req: PublishRequest,
|
||||
request: Request,
|
||||
username: str = Depends(get_required_user)
|
||||
):
|
||||
"""
|
||||
Publish a cache item to L2 (ActivityPub).
|
||||
|
||||
Requires origin to be set in metadata before publishing.
|
||||
"""
|
||||
# Check file exists
|
||||
cache_path = CACHE_DIR / content_hash
|
||||
if not cache_path.exists():
|
||||
raise HTTPException(404, "Content not found")
|
||||
|
||||
# Check ownership
|
||||
user_hashes = get_user_cache_hashes(username)
|
||||
if content_hash not in user_hashes:
|
||||
raise HTTPException(403, "Access denied")
|
||||
|
||||
# Load metadata
|
||||
meta = load_cache_meta(content_hash)
|
||||
|
||||
# Check origin is set
|
||||
origin = meta.get("origin")
|
||||
if not origin or "type" not in origin:
|
||||
raise HTTPException(400, "Origin must be set before publishing. Use --origin self or --origin-url <url>")
|
||||
|
||||
# Get auth token to pass to L2
|
||||
token = request.cookies.get("auth_token")
|
||||
if not token:
|
||||
# Try from header
|
||||
auth_header = request.headers.get("Authorization", "")
|
||||
if auth_header.startswith("Bearer "):
|
||||
token = auth_header[7:]
|
||||
|
||||
if not token:
|
||||
raise HTTPException(401, "Authentication token required")
|
||||
|
||||
# Call L2 publish-cache endpoint
|
||||
try:
|
||||
resp = http_requests.post(
|
||||
f"{L2_SERVER}/registry/publish-cache",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
json={
|
||||
"content_hash": content_hash,
|
||||
"asset_name": req.asset_name,
|
||||
"asset_type": req.asset_type,
|
||||
"origin": origin,
|
||||
"description": meta.get("description"),
|
||||
"tags": meta.get("tags", []),
|
||||
"metadata": {
|
||||
"filename": meta.get("filename"),
|
||||
"folder": meta.get("folder"),
|
||||
"collections": meta.get("collections", [])
|
||||
}
|
||||
},
|
||||
timeout=10
|
||||
)
|
||||
resp.raise_for_status()
|
||||
l2_result = resp.json()
|
||||
except http_requests.exceptions.HTTPError as e:
|
||||
error_detail = ""
|
||||
try:
|
||||
error_detail = e.response.json().get("detail", str(e))
|
||||
except Exception:
|
||||
error_detail = str(e)
|
||||
raise HTTPException(400, f"L2 publish failed: {error_detail}")
|
||||
except Exception as e:
|
||||
raise HTTPException(500, f"L2 publish failed: {e}")
|
||||
|
||||
# Update local metadata with publish status
|
||||
publish_info = {
|
||||
"to_l2": True,
|
||||
"asset_name": req.asset_name,
|
||||
"published_at": datetime.now(timezone.utc).isoformat(),
|
||||
"last_synced_at": datetime.now(timezone.utc).isoformat()
|
||||
}
|
||||
save_cache_meta(content_hash, published=publish_info)
|
||||
|
||||
return {
|
||||
"published": True,
|
||||
"asset_name": req.asset_name,
|
||||
"l2_result": l2_result
|
||||
}
|
||||
|
||||
|
||||
@app.patch("/cache/{content_hash}/republish")
|
||||
async def republish_cache_to_l2(
|
||||
content_hash: str,
|
||||
request: Request,
|
||||
username: str = Depends(get_required_user)
|
||||
):
|
||||
"""
|
||||
Re-publish (update) a cache item on L2 after metadata changes.
|
||||
|
||||
Only works for already-published items.
|
||||
"""
|
||||
# Check file exists
|
||||
cache_path = CACHE_DIR / content_hash
|
||||
if not cache_path.exists():
|
||||
raise HTTPException(404, "Content not found")
|
||||
|
||||
# Check ownership
|
||||
user_hashes = get_user_cache_hashes(username)
|
||||
if content_hash not in user_hashes:
|
||||
raise HTTPException(403, "Access denied")
|
||||
|
||||
# Load metadata
|
||||
meta = load_cache_meta(content_hash)
|
||||
|
||||
# Check already published
|
||||
published = meta.get("published", {})
|
||||
if not published.get("to_l2"):
|
||||
raise HTTPException(400, "Item not published yet. Use publish first.")
|
||||
|
||||
asset_name = published.get("asset_name")
|
||||
if not asset_name:
|
||||
raise HTTPException(400, "No asset name found in publish info")
|
||||
|
||||
# Get auth token
|
||||
token = request.cookies.get("auth_token")
|
||||
if not token:
|
||||
auth_header = request.headers.get("Authorization", "")
|
||||
if auth_header.startswith("Bearer "):
|
||||
token = auth_header[7:]
|
||||
|
||||
if not token:
|
||||
raise HTTPException(401, "Authentication token required")
|
||||
|
||||
# Call L2 update endpoint
|
||||
try:
|
||||
resp = http_requests.patch(
|
||||
f"{L2_SERVER}/registry/{asset_name}",
|
||||
headers={"Authorization": f"Bearer {token}"},
|
||||
json={
|
||||
"description": meta.get("description"),
|
||||
"tags": meta.get("tags"),
|
||||
"origin": meta.get("origin"),
|
||||
"metadata": {
|
||||
"filename": meta.get("filename"),
|
||||
"folder": meta.get("folder"),
|
||||
"collections": meta.get("collections", [])
|
||||
}
|
||||
},
|
||||
timeout=10
|
||||
)
|
||||
resp.raise_for_status()
|
||||
l2_result = resp.json()
|
||||
except http_requests.exceptions.HTTPError as e:
|
||||
error_detail = ""
|
||||
try:
|
||||
error_detail = e.response.json().get("detail", str(e))
|
||||
except Exception:
|
||||
error_detail = str(e)
|
||||
raise HTTPException(400, f"L2 update failed: {error_detail}")
|
||||
except Exception as e:
|
||||
raise HTTPException(500, f"L2 update failed: {e}")
|
||||
|
||||
# Update local metadata
|
||||
published["last_synced_at"] = datetime.now(timezone.utc).isoformat()
|
||||
save_cache_meta(content_hash, published=published)
|
||||
|
||||
return {
|
||||
"updated": True,
|
||||
"asset_name": asset_name,
|
||||
"l2_result": l2_result
|
||||
}
|
||||
|
||||
|
||||
# ============ Folder & Collection Management ============
|
||||
|
||||
@app.get("/user/folders")
|
||||
|
||||
Reference in New Issue
Block a user