diff --git a/server.py b/server.py index be4cf58..b1d7d1a 100644 --- a/server.py +++ b/server.py @@ -560,6 +560,12 @@ async def ui_cache_view(content_hash: str, request: Request): + + +
+
Loading metadata...
+
@@ -568,6 +574,330 @@ async def ui_cache_view(content_hash: str, request: Request): return html +@app.get("/ui/cache/{content_hash}/meta-form", response_class=HTMLResponse) +async def ui_cache_meta_form(content_hash: str, request: Request): + """HTMX partial: metadata editing form for a cached item.""" + current_user = get_user_from_cookie(request) + if not current_user: + return '
Login required to edit metadata
' + + # Check ownership + user_hashes = get_user_cache_hashes(current_user) + if content_hash not in user_hashes: + return '
Access denied
' + + # Load metadata + meta = load_cache_meta(content_hash) + origin = meta.get("origin", {}) + origin_type = origin.get("type", "") + origin_url = origin.get("url", "") + origin_note = origin.get("note", "") + description = meta.get("description", "") + tags = meta.get("tags", []) + tags_str = ", ".join(tags) if tags else "" + published = meta.get("published", {}) + + # Detect media type for publish + cache_path = CACHE_DIR / content_hash + media_type = detect_media_type(cache_path) if cache_path.exists() else "unknown" + asset_type = "video" if media_type == "video" else "image" + + # Origin radio checked states + self_checked = 'checked' if origin_type == "self" else '' + external_checked = 'checked' if origin_type == "external" else '' + + # Build publish section + if published.get("to_l2"): + asset_name = published.get("asset_name", "") + published_at = published.get("published_at", "")[:10] + last_synced = published.get("last_synced_at", "")[:10] + publish_html = f''' +
+
Published to L2
+
+ Asset name: {asset_name}
+ Published: {published_at}
+ Last synced: {last_synced} +
+
+
+ + ''' + else: + # Show publish form only if origin is set + if origin_type: + publish_html = f''' +
+
+
+ + +
+ + +
+ ''' + else: + publish_html = ''' +
+ Set an origin (self or external URL) before publishing. +
+ ''' + + return f''' +

Metadata

+
+ +
+ +
+ +
+ + +
+ + +
+
+
+ + +
+ + +
+ + +
+ + +

Comma-separated list

+
+ + +
+ + +
+

Publish to L2 (ActivityPub)

+ {publish_html} +
+ ''' + + +@app.patch("/ui/cache/{content_hash}/meta", response_class=HTMLResponse) +async def ui_update_cache_meta(content_hash: str, request: Request): + """HTMX handler: update cache metadata from form.""" + current_user = get_user_from_cookie(request) + if not current_user: + return '
Login required
' + + # Check ownership + user_hashes = get_user_cache_hashes(current_user) + if content_hash not in user_hashes: + return '
Access denied
' + + # Parse form data + form = await request.form() + origin_type = form.get("origin_type", "") + origin_url = form.get("origin_url", "").strip() + origin_note = form.get("origin_note", "").strip() + description = form.get("description", "").strip() + tags_str = form.get("tags", "").strip() + + # Build origin + origin = None + if origin_type == "self": + origin = {"type": "self"} + elif origin_type == "external": + if not origin_url: + return '
External origin requires a URL
' + origin = {"type": "external", "url": origin_url} + if origin_note: + origin["note"] = origin_note + + # Parse tags + tags = [t.strip() for t in tags_str.split(",") if t.strip()] if tags_str else [] + + # Build updates + updates = {} + if origin: + updates["origin"] = origin + if description: + updates["description"] = description + updates["tags"] = tags + + # Save + save_cache_meta(content_hash, **updates) + + return '
Metadata saved!
' + + +@app.post("/ui/cache/{content_hash}/publish", response_class=HTMLResponse) +async def ui_publish_cache(content_hash: str, request: Request): + """HTMX handler: publish cache item to L2.""" + current_user = get_user_from_cookie(request) + if not current_user: + return '
Login required
' + + token = request.cookies.get("auth_token") + if not token: + return '
Auth token required
' + + # Check ownership + user_hashes = get_user_cache_hashes(current_user) + if content_hash not in user_hashes: + return '
Access denied
' + + # Parse form + form = await request.form() + asset_name = form.get("asset_name", "").strip() + asset_type = form.get("asset_type", "image") + + if not asset_name: + return '
Asset name required
' + + # Load metadata + meta = load_cache_meta(content_hash) + origin = meta.get("origin") + + if not origin or "type" not in origin: + return '
Set origin before publishing
' + + # Call L2 + try: + resp = http_requests.post( + f"{L2_SERVER}/registry/publish-cache", + headers={"Authorization": f"Bearer {token}"}, + json={ + "content_hash": content_hash, + "asset_name": asset_name, + "asset_type": 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=30 + ) + resp.raise_for_status() + 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) + return f'
Error: {error_detail}
' + except Exception as e: + return f'
Error: {e}
' + + # Update local metadata + publish_info = { + "to_l2": True, + "asset_name": 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 f''' +
+ Published to L2 as {asset_name}! + View on L2 +
+ ''' + + +@app.patch("/ui/cache/{content_hash}/republish", response_class=HTMLResponse) +async def ui_republish_cache(content_hash: str, request: Request): + """HTMX handler: re-publish (update) cache item on L2.""" + current_user = get_user_from_cookie(request) + if not current_user: + return '
Login required
' + + token = request.cookies.get("auth_token") + if not token: + return '
Auth token required
' + + # Check ownership + user_hashes = get_user_cache_hashes(current_user) + if content_hash not in user_hashes: + return '
Access denied
' + + # Load metadata + meta = load_cache_meta(content_hash) + published = meta.get("published", {}) + + if not published.get("to_l2"): + return '
Item not published yet
' + + asset_name = published.get("asset_name") + if not asset_name: + return '
No asset name found
' + + # Call L2 update + 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=30 + ) + resp.raise_for_status() + 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) + return f'
Error: {error_detail}
' + except Exception as e: + return f'
Error: {e}
' + + # Update local metadata + published["last_synced_at"] = datetime.now(timezone.utc).isoformat() + save_cache_meta(content_hash, published=published) + + return '
Updated on L2!
' + + @app.get("/cache") async def list_cache(): """List cached content hashes."""