Rename content_hash/output_hash to cid throughout
Refactor to use IPFS CID as the primary content identifier: - Update database schema: content_hash -> cid, output_hash -> output_cid - Update all services, routers, and tasks to use cid terminology - Update HTML templates to display CID instead of hash - Update cache_manager parameter names - Update README documentation This completes the transition to CID-only content addressing. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -155,13 +155,13 @@ async def run_recipe_endpoint(
|
||||
# Check if already completed
|
||||
cached = await database.get_run_cache(run_id)
|
||||
if cached:
|
||||
output_hash = cached.get("output_hash")
|
||||
if cache.has_content(output_hash):
|
||||
output_cid = cached.get("output_cid")
|
||||
if cache.has_content(output_cid):
|
||||
return {
|
||||
"status": "completed",
|
||||
"run_id": run_id,
|
||||
"output_hash": output_hash,
|
||||
"output_ipfs_cid": cache.get_ipfs_cid(output_hash),
|
||||
"output_cid": output_cid,
|
||||
"output_ipfs_cid": cache.get_ipfs_cid(output_cid),
|
||||
"cached": True,
|
||||
}
|
||||
|
||||
@@ -224,7 +224,7 @@ async def get_run_status(
|
||||
if result.successful():
|
||||
task_result = result.get()
|
||||
data["status"] = task_result.get("status", "completed")
|
||||
data["output_hash"] = task_result.get("output_cache_id")
|
||||
data["output_cid"] = task_result.get("output_cache_id")
|
||||
data["output_ipfs_cid"] = task_result.get("output_ipfs_cid")
|
||||
data["total_steps"] = task_result.get("total_steps")
|
||||
data["cached"] = task_result.get("cached")
|
||||
@@ -250,7 +250,7 @@ async def get_run_status(
|
||||
return {
|
||||
"run_id": run_id,
|
||||
"status": "completed",
|
||||
"output_hash": cached.get("output_hash"),
|
||||
"output_cid": cached.get("output_cid"),
|
||||
"cached": True,
|
||||
}
|
||||
|
||||
|
||||
@@ -40,9 +40,9 @@ def get_cache_service():
|
||||
return CacheService(database, get_cache_manager())
|
||||
|
||||
|
||||
@router.get("/{content_hash}")
|
||||
@router.get("/{cid}")
|
||||
async def get_cached(
|
||||
content_hash: str,
|
||||
cid: str,
|
||||
request: Request,
|
||||
cache_service: CacheService = Depends(get_cache_service),
|
||||
):
|
||||
@@ -50,16 +50,16 @@ async def get_cached(
|
||||
auth_service = AuthService(get_redis_client())
|
||||
ctx = auth_service.get_user_from_cookie(request)
|
||||
|
||||
cache_item = await cache_service.get_cache_item(content_hash)
|
||||
cache_item = await cache_service.get_cache_item(cid)
|
||||
if not cache_item:
|
||||
if wants_html(request):
|
||||
templates = get_templates(request)
|
||||
return render(templates, "cache/not_found.html", request,
|
||||
content_hash=content_hash,
|
||||
cid=cid,
|
||||
user=ctx,
|
||||
active_tab="media",
|
||||
)
|
||||
raise HTTPException(404, f"Content {content_hash} not in cache")
|
||||
raise HTTPException(404, f"Content {cid} not in cache")
|
||||
|
||||
# JSON response
|
||||
if wants_json(request):
|
||||
@@ -71,7 +71,7 @@ async def get_cached(
|
||||
return RedirectResponse(url="/auth", status_code=302)
|
||||
|
||||
# Check access
|
||||
has_access = await cache_service.check_access(content_hash, ctx.actor_id, ctx.username)
|
||||
has_access = await cache_service.check_access(cid, ctx.actor_id, ctx.username)
|
||||
if not has_access:
|
||||
raise HTTPException(403, "Access denied")
|
||||
|
||||
@@ -83,27 +83,27 @@ async def get_cached(
|
||||
)
|
||||
|
||||
|
||||
@router.get("/{content_hash}/raw")
|
||||
@router.get("/{cid}/raw")
|
||||
async def get_cached_raw(
|
||||
content_hash: str,
|
||||
cid: str,
|
||||
cache_service: CacheService = Depends(get_cache_service),
|
||||
):
|
||||
"""Get raw cached content (file download)."""
|
||||
file_path, media_type, filename = await cache_service.get_raw_file(content_hash)
|
||||
file_path, media_type, filename = await cache_service.get_raw_file(cid)
|
||||
|
||||
if not file_path:
|
||||
raise HTTPException(404, f"Content {content_hash} not in cache")
|
||||
raise HTTPException(404, f"Content {cid} not in cache")
|
||||
|
||||
return FileResponse(file_path, media_type=media_type, filename=filename)
|
||||
|
||||
|
||||
@router.get("/{content_hash}/mp4")
|
||||
@router.get("/{cid}/mp4")
|
||||
async def get_cached_mp4(
|
||||
content_hash: str,
|
||||
cid: str,
|
||||
cache_service: CacheService = Depends(get_cache_service),
|
||||
):
|
||||
"""Get cached content as MP4 (transcodes MKV on first request)."""
|
||||
mp4_path, error = await cache_service.get_as_mp4(content_hash)
|
||||
mp4_path, error = await cache_service.get_as_mp4(cid)
|
||||
|
||||
if error:
|
||||
raise HTTPException(400 if "not a video" in error else 404, error)
|
||||
@@ -111,29 +111,29 @@ async def get_cached_mp4(
|
||||
return FileResponse(mp4_path, media_type="video/mp4")
|
||||
|
||||
|
||||
@router.get("/{content_hash}/meta")
|
||||
@router.get("/{cid}/meta")
|
||||
async def get_metadata(
|
||||
content_hash: str,
|
||||
cid: str,
|
||||
ctx: UserContext = Depends(require_auth),
|
||||
cache_service: CacheService = Depends(get_cache_service),
|
||||
):
|
||||
"""Get content metadata."""
|
||||
meta = await cache_service.get_metadata(content_hash, ctx.actor_id)
|
||||
meta = await cache_service.get_metadata(cid, ctx.actor_id)
|
||||
if meta is None:
|
||||
raise HTTPException(404, "Content not found")
|
||||
return meta
|
||||
|
||||
|
||||
@router.patch("/{content_hash}/meta")
|
||||
@router.patch("/{cid}/meta")
|
||||
async def update_metadata(
|
||||
content_hash: str,
|
||||
cid: str,
|
||||
req: UpdateMetadataRequest,
|
||||
ctx: UserContext = Depends(require_auth),
|
||||
cache_service: CacheService = Depends(get_cache_service),
|
||||
):
|
||||
"""Update content metadata."""
|
||||
success, error = await cache_service.update_metadata(
|
||||
content_hash=content_hash,
|
||||
cid=cid,
|
||||
actor_id=ctx.actor_id,
|
||||
title=req.title,
|
||||
description=req.description,
|
||||
@@ -147,16 +147,16 @@ async def update_metadata(
|
||||
return {"updated": True}
|
||||
|
||||
|
||||
@router.post("/{content_hash}/publish")
|
||||
@router.post("/{cid}/publish")
|
||||
async def publish_content(
|
||||
content_hash: str,
|
||||
cid: str,
|
||||
request: Request,
|
||||
ctx: UserContext = Depends(require_auth),
|
||||
cache_service: CacheService = Depends(get_cache_service),
|
||||
):
|
||||
"""Publish content to L2 and IPFS."""
|
||||
ipfs_cid, error = await cache_service.publish_to_l2(
|
||||
content_hash=content_hash,
|
||||
cid=cid,
|
||||
actor_id=ctx.actor_id,
|
||||
l2_server=ctx.l2_server,
|
||||
auth_token=request.cookies.get("auth_token"),
|
||||
@@ -173,14 +173,14 @@ async def publish_content(
|
||||
return {"ipfs_cid": ipfs_cid, "published": True}
|
||||
|
||||
|
||||
@router.delete("/{content_hash}")
|
||||
@router.delete("/{cid}")
|
||||
async def delete_content(
|
||||
content_hash: str,
|
||||
cid: str,
|
||||
ctx: UserContext = Depends(require_auth),
|
||||
cache_service: CacheService = Depends(get_cache_service),
|
||||
):
|
||||
"""Delete content from cache."""
|
||||
success, error = await cache_service.delete_content(content_hash, ctx.actor_id)
|
||||
success, error = await cache_service.delete_content(cid, ctx.actor_id)
|
||||
|
||||
if error:
|
||||
raise HTTPException(400 if "Cannot" in error or "pinned" in error else 404, error)
|
||||
@@ -195,12 +195,12 @@ async def import_from_ipfs(
|
||||
cache_service: CacheService = Depends(get_cache_service),
|
||||
):
|
||||
"""Import content from IPFS."""
|
||||
content_hash, error = await cache_service.import_from_ipfs(ipfs_cid, ctx.actor_id)
|
||||
cid, error = await cache_service.import_from_ipfs(ipfs_cid, ctx.actor_id)
|
||||
|
||||
if error:
|
||||
raise HTTPException(400, error)
|
||||
|
||||
return {"content_hash": content_hash, "imported": True}
|
||||
return {"cid": cid, "imported": True}
|
||||
|
||||
|
||||
@router.post("/upload")
|
||||
@@ -211,7 +211,7 @@ async def upload_content(
|
||||
):
|
||||
"""Upload content to cache and IPFS."""
|
||||
content = await file.read()
|
||||
content_hash, ipfs_cid, error = await cache_service.upload_content(
|
||||
cid, ipfs_cid, error = await cache_service.upload_content(
|
||||
content=content,
|
||||
filename=file.filename,
|
||||
actor_id=ctx.actor_id,
|
||||
@@ -222,7 +222,7 @@ async def upload_content(
|
||||
|
||||
return {
|
||||
"cid": ipfs_cid,
|
||||
"content_hash": content_hash, # Legacy, for backwards compatibility
|
||||
"cid": cid, # Legacy, for backwards compatibility
|
||||
"filename": file.filename,
|
||||
"size": len(content),
|
||||
"uploaded": True,
|
||||
@@ -272,9 +272,9 @@ async def list_media(
|
||||
|
||||
|
||||
# HTMX metadata form
|
||||
@router.get("/{content_hash}/meta-form", response_class=HTMLResponse)
|
||||
@router.get("/{cid}/meta-form", response_class=HTMLResponse)
|
||||
async def get_metadata_form(
|
||||
content_hash: str,
|
||||
cid: str,
|
||||
request: Request,
|
||||
cache_service: CacheService = Depends(get_cache_service),
|
||||
):
|
||||
@@ -285,11 +285,11 @@ async def get_metadata_form(
|
||||
if not ctx:
|
||||
return HTMLResponse('<div class="text-red-400">Login required</div>')
|
||||
|
||||
meta = await cache_service.get_metadata(content_hash, ctx.actor_id)
|
||||
meta = await cache_service.get_metadata(cid, ctx.actor_id)
|
||||
|
||||
return HTMLResponse(f'''
|
||||
<h2 class="text-lg font-semibold mb-4">Metadata</h2>
|
||||
<form hx-patch="/cache/{content_hash}/meta"
|
||||
<form hx-patch="/cache/{cid}/meta"
|
||||
hx-target="#metadata-section"
|
||||
hx-swap="innerHTML"
|
||||
class="space-y-4">
|
||||
@@ -312,9 +312,9 @@ async def get_metadata_form(
|
||||
''')
|
||||
|
||||
|
||||
@router.patch("/{content_hash}/meta", response_class=HTMLResponse)
|
||||
@router.patch("/{cid}/meta", response_class=HTMLResponse)
|
||||
async def update_metadata_htmx(
|
||||
content_hash: str,
|
||||
cid: str,
|
||||
request: Request,
|
||||
cache_service: CacheService = Depends(get_cache_service),
|
||||
):
|
||||
@@ -328,7 +328,7 @@ async def update_metadata_htmx(
|
||||
form_data = await request.form()
|
||||
|
||||
success, error = await cache_service.update_metadata(
|
||||
content_hash=content_hash,
|
||||
cid=cid,
|
||||
actor_id=ctx.actor_id,
|
||||
title=form_data.get("title"),
|
||||
description=form_data.get("description"),
|
||||
|
||||
@@ -350,7 +350,7 @@ async def run_recipe(
|
||||
if node.get("type") == "SOURCE" and "asset" in config:
|
||||
asset_name = config["asset"]
|
||||
if asset_name in assets:
|
||||
config["content_hash"] = assets[asset_name].get("hash")
|
||||
config["cid"] = assets[asset_name].get("hash")
|
||||
|
||||
# Resolve effect references for EFFECT nodes
|
||||
if node.get("type") == "EFFECT" and "effect" in config:
|
||||
@@ -392,21 +392,21 @@ async def run_recipe(
|
||||
input_name_to_node[node["name"].replace("-", "_")] = node_id
|
||||
|
||||
# Map user-provided input names to content hashes (for variable inputs)
|
||||
for input_name, content_hash in req.inputs.items():
|
||||
for input_name, cid in req.inputs.items():
|
||||
# Try direct node ID match first
|
||||
if input_name in nodes:
|
||||
node = nodes[input_name]
|
||||
if node.get("node_type") == "SOURCE":
|
||||
if "config" not in node:
|
||||
node["config"] = {}
|
||||
node["config"]["content_hash"] = content_hash
|
||||
node["config"]["cid"] = cid
|
||||
# Try input name lookup
|
||||
elif input_name in input_name_to_node:
|
||||
node_id = input_name_to_node[input_name]
|
||||
node = nodes[node_id]
|
||||
if "config" not in node:
|
||||
node["config"] = {}
|
||||
node["config"]["content_hash"] = content_hash
|
||||
node["config"]["cid"] = cid
|
||||
|
||||
# Transform output to output_id
|
||||
if "output" in dag_copy:
|
||||
@@ -527,7 +527,7 @@ async def publish_recipe(
|
||||
# Use cache service to publish (recipes are stored in cache)
|
||||
cache_service = CacheService(database, get_cache_manager())
|
||||
ipfs_cid, error = await cache_service.publish_to_l2(
|
||||
content_hash=recipe_id,
|
||||
cid=recipe_id,
|
||||
actor_id=ctx.actor_id,
|
||||
l2_server=ctx.l2_server,
|
||||
auth_token=request.cookies.get("auth_token"),
|
||||
|
||||
@@ -99,7 +99,7 @@ class RunStatus(BaseModel):
|
||||
output_name: Optional[str] = None
|
||||
created_at: Optional[str] = None
|
||||
completed_at: Optional[str] = None
|
||||
output_hash: Optional[str] = None
|
||||
output_cid: Optional[str] = None
|
||||
username: Optional[str] = None
|
||||
provenance_cid: Optional[str] = None
|
||||
celery_task_id: Optional[str] = None
|
||||
@@ -244,13 +244,13 @@ async def get_run(
|
||||
# Build artifacts list from output and inputs
|
||||
artifacts = []
|
||||
output_media_type = None
|
||||
if run.get("output_hash"):
|
||||
if run.get("output_cid"):
|
||||
# Detect media type using magic bytes
|
||||
output_hash = run["output_hash"]
|
||||
output_cid = run["output_cid"]
|
||||
media_type = None
|
||||
try:
|
||||
from ..services.run_service import detect_media_type
|
||||
cache_path = get_cache_manager().get_by_content_hash(output_hash)
|
||||
cache_path = get_cache_manager().get_by_cid(output_cid)
|
||||
if cache_path and cache_path.exists():
|
||||
simple_type = detect_media_type(cache_path)
|
||||
media_type = type_to_mime(simple_type)
|
||||
@@ -258,7 +258,7 @@ async def get_run(
|
||||
except Exception:
|
||||
pass
|
||||
artifacts.append({
|
||||
"hash": output_hash,
|
||||
"hash": output_cid,
|
||||
"step_name": "Output",
|
||||
"media_type": media_type or "application/octet-stream",
|
||||
})
|
||||
@@ -271,7 +271,7 @@ async def get_run(
|
||||
for i, input_hash in enumerate(run["inputs"]):
|
||||
media_type = None
|
||||
try:
|
||||
cache_path = cache_manager.get_by_content_hash(input_hash)
|
||||
cache_path = cache_manager.get_by_cid(input_hash)
|
||||
if cache_path and cache_path.exists():
|
||||
simple_type = detect_media_type(cache_path)
|
||||
media_type = type_to_mime(simple_type)
|
||||
@@ -393,9 +393,9 @@ async def list_runs(
|
||||
|
||||
for run in runs:
|
||||
# Add output media info
|
||||
if run.get("output_hash"):
|
||||
if run.get("output_cid"):
|
||||
try:
|
||||
cache_path = cache_manager.get_by_content_hash(run["output_hash"])
|
||||
cache_path = cache_manager.get_by_cid(run["output_cid"])
|
||||
if cache_path and cache_path.exists():
|
||||
simple_type = detect_media_type(cache_path)
|
||||
run["output_media_type"] = type_to_mime(simple_type)
|
||||
@@ -409,7 +409,7 @@ async def list_runs(
|
||||
for input_hash in inputs[:3]:
|
||||
preview = {"hash": input_hash, "media_type": None}
|
||||
try:
|
||||
cache_path = cache_manager.get_by_content_hash(input_hash)
|
||||
cache_path = cache_manager.get_by_cid(input_hash)
|
||||
if cache_path and cache_path.exists():
|
||||
simple_type = detect_media_type(cache_path)
|
||||
preview["media_type"] = type_to_mime(simple_type)
|
||||
@@ -756,8 +756,8 @@ async def publish_run(
|
||||
raise HTTPException(404, "Run not found")
|
||||
|
||||
# Check if run has output
|
||||
output_hash = run.get("output_hash")
|
||||
if not output_hash:
|
||||
output_cid = run.get("output_cid")
|
||||
if not output_cid:
|
||||
error = "Run has no output to publish"
|
||||
if wants_html(request):
|
||||
return HTMLResponse(f'<span class="text-red-400">{error}</span>')
|
||||
@@ -766,7 +766,7 @@ async def publish_run(
|
||||
# Use cache service to publish the output
|
||||
cache_service = CacheService(database, get_cache_manager())
|
||||
ipfs_cid, error = await cache_service.publish_to_l2(
|
||||
content_hash=output_hash,
|
||||
cid=output_cid,
|
||||
actor_id=ctx.actor_id,
|
||||
l2_server=ctx.l2_server,
|
||||
auth_token=request.cookies.get("auth_token"),
|
||||
@@ -780,4 +780,4 @@ async def publish_run(
|
||||
if wants_html(request):
|
||||
return HTMLResponse(f'<span class="text-green-400">Shared: {ipfs_cid[:16]}...</span>')
|
||||
|
||||
return {"ipfs_cid": ipfs_cid, "output_hash": output_hash, "published": True}
|
||||
return {"ipfs_cid": ipfs_cid, "output_cid": output_cid, "published": True}
|
||||
|
||||
Reference in New Issue
Block a user