Fix effects router to use proper ownership model
- Upload: Create item_types entry to track user-effect relationship - List: Query item_types for user's effects instead of scanning filesystem - Delete: Remove ownership link, only delete files if orphaned (garbage collect) This matches the ownership model used by recipes and media, where multiple users can "own" the same cached content through item_types entries. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -200,6 +200,15 @@ async def upload_effect(
|
|||||||
# Also store metadata in IPFS for discoverability
|
# Also store metadata in IPFS for discoverability
|
||||||
meta_cid = ipfs_client.add_json(full_meta)
|
meta_cid = ipfs_client.add_json(full_meta)
|
||||||
|
|
||||||
|
# Track ownership in item_types
|
||||||
|
import database
|
||||||
|
await database.save_item_metadata(
|
||||||
|
cid=cid,
|
||||||
|
actor_id=ctx.actor_id,
|
||||||
|
item_type="effect",
|
||||||
|
filename=file.filename,
|
||||||
|
)
|
||||||
|
|
||||||
# Assign friendly name
|
# Assign friendly name
|
||||||
from ..services.naming_service import get_naming_service
|
from ..services.naming_service import get_naming_service
|
||||||
naming = get_naming_service()
|
naming = get_naming_service()
|
||||||
@@ -314,31 +323,33 @@ async def list_effects(
|
|||||||
limit: int = 20,
|
limit: int = 20,
|
||||||
ctx: UserContext = Depends(require_auth),
|
ctx: UserContext = Depends(require_auth),
|
||||||
):
|
):
|
||||||
"""List uploaded effects with pagination."""
|
"""List user's effects with pagination."""
|
||||||
|
import database
|
||||||
effects_dir = get_effects_dir()
|
effects_dir = get_effects_dir()
|
||||||
effects = []
|
effects = []
|
||||||
|
|
||||||
|
# Get user's effect CIDs from item_types
|
||||||
|
user_items = await database.get_user_items(ctx.actor_id, item_type="effect", limit=1000)
|
||||||
|
effect_cids = [item["cid"] for item in user_items]
|
||||||
|
|
||||||
# Get naming service for friendly name lookup
|
# Get naming service for friendly name lookup
|
||||||
from ..services.naming_service import get_naming_service
|
from ..services.naming_service import get_naming_service
|
||||||
naming = get_naming_service()
|
naming = get_naming_service()
|
||||||
|
|
||||||
if effects_dir.exists():
|
for cid in effect_cids:
|
||||||
for effect_dir in effects_dir.iterdir():
|
effect_dir = effects_dir / cid
|
||||||
if effect_dir.is_dir():
|
metadata_path = effect_dir / "metadata.json"
|
||||||
metadata_path = effect_dir / "metadata.json"
|
if metadata_path.exists():
|
||||||
if metadata_path.exists():
|
try:
|
||||||
try:
|
meta = json.loads(metadata_path.read_text())
|
||||||
meta = json.loads(metadata_path.read_text())
|
# Add friendly name if available
|
||||||
# Add friendly name if available
|
friendly = await naming.get_by_cid(ctx.actor_id, cid)
|
||||||
cid = meta.get("cid")
|
if friendly:
|
||||||
if cid:
|
meta["friendly_name"] = friendly["friendly_name"]
|
||||||
friendly = await naming.get_by_cid(ctx.actor_id, cid)
|
meta["base_name"] = friendly["base_name"]
|
||||||
if friendly:
|
effects.append(meta)
|
||||||
meta["friendly_name"] = friendly["friendly_name"]
|
except json.JSONDecodeError:
|
||||||
meta["base_name"] = friendly["base_name"]
|
pass
|
||||||
effects.append(meta)
|
|
||||||
except json.JSONDecodeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Sort by upload time (newest first)
|
# Sort by upload time (newest first)
|
||||||
effects.sort(key=lambda e: e.get("uploaded_at", ""), reverse=True)
|
effects.sort(key=lambda e: e.get("uploaded_at", ""), reverse=True)
|
||||||
@@ -412,25 +423,29 @@ async def delete_effect(
|
|||||||
cid: str,
|
cid: str,
|
||||||
ctx: UserContext = Depends(require_auth),
|
ctx: UserContext = Depends(require_auth),
|
||||||
):
|
):
|
||||||
"""Delete an effect from local cache (IPFS content is immutable)."""
|
"""Remove user's ownership link to an effect."""
|
||||||
effects_dir = get_effects_dir()
|
import database
|
||||||
effect_dir = effects_dir / cid
|
|
||||||
|
|
||||||
if not effect_dir.exists():
|
# Remove user's ownership link from item_types
|
||||||
raise HTTPException(404, f"Effect {cid[:16]}... not found in local cache")
|
await database.delete_item_type(cid, ctx.actor_id, "effect")
|
||||||
|
|
||||||
# Check ownership
|
# Remove friendly name
|
||||||
metadata_path = effect_dir / "metadata.json"
|
await database.delete_friendly_name(ctx.actor_id, cid)
|
||||||
if metadata_path.exists():
|
|
||||||
meta = json.loads(metadata_path.read_text())
|
|
||||||
if meta.get("uploader") != ctx.actor_id:
|
|
||||||
raise HTTPException(403, "Can only delete your own effects")
|
|
||||||
|
|
||||||
import shutil
|
# Check if anyone still owns this effect
|
||||||
shutil.rmtree(effect_dir)
|
remaining_owners = await database.get_item_types(cid)
|
||||||
|
|
||||||
# Unpin from IPFS (content remains available if pinned elsewhere)
|
# Only delete local files if no one owns it anymore
|
||||||
ipfs_client.unpin(cid)
|
if not remaining_owners:
|
||||||
|
effects_dir = get_effects_dir()
|
||||||
|
effect_dir = effects_dir / cid
|
||||||
|
if effect_dir.exists():
|
||||||
|
import shutil
|
||||||
|
shutil.rmtree(effect_dir)
|
||||||
|
|
||||||
logger.info(f"Deleted effect {cid[:16]}... by {ctx.actor_id}")
|
# Unpin from IPFS
|
||||||
return {"deleted": True, "note": "Unpinned from local IPFS; content may still exist on other nodes"}
|
ipfs_client.unpin(cid)
|
||||||
|
logger.info(f"Garbage collected effect {cid[:16]}... (no remaining owners)")
|
||||||
|
|
||||||
|
logger.info(f"Removed effect {cid[:16]}... ownership for {ctx.actor_id}")
|
||||||
|
return {"deleted": True}
|
||||||
|
|||||||
Reference in New Issue
Block a user