From 79a74df2bb8d6e3e03b9f59f062365cd06489248 Mon Sep 17 00:00:00 2001 From: gilesb Date: Mon, 12 Jan 2026 14:34:56 +0000 Subject: [PATCH] Add clear-data API endpoint - DELETE /api/clear-data clears all user L1 data - Deletes runs, recipes, effects, and media/cache items - Preserves storage provider configurations - Returns counts of deleted items and any errors Co-Authored-By: Claude Opus 4.5 --- app/routers/home.py | 97 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 97 insertions(+) diff --git a/app/routers/home.py b/app/routers/home.py index 50ae2b2..f891b1a 100644 --- a/app/routers/home.py +++ b/app/routers/home.py @@ -73,6 +73,103 @@ async def api_stats(request: Request): return stats +@router.delete("/api/clear-data") +async def clear_user_data(request: Request): + """ + Clear all user L1 data except storage configuration. + + Deletes: runs, recipes, effects, media/cache items. + Preserves: storage provider configurations. + """ + import logging + logger = logging.getLogger(__name__) + + user = await get_current_user(request) + if not user: + raise HTTPException(401, "Authentication required") + + import database + from ..services.recipe_service import RecipeService + from ..services.run_service import RunService + from ..dependencies import get_redis_client, get_cache_manager + + actor_id = user.actor_id + deleted = { + "runs": 0, + "recipes": 0, + "effects": 0, + "media": 0, + } + errors = [] + + # Delete all runs + try: + run_service = RunService(database, get_redis_client(), get_cache_manager()) + runs = await run_service.list_runs(actor_id, offset=0, limit=10000) + for run in runs: + try: + await run_service.discard_run(run["run_id"], actor_id) + deleted["runs"] += 1 + except Exception as e: + errors.append(f"Run {run['run_id']}: {e}") + except Exception as e: + errors.append(f"Failed to list runs: {e}") + + # Delete all recipes + try: + recipe_service = RecipeService(get_redis_client(), get_cache_manager()) + recipes = await recipe_service.list_recipes(actor_id, offset=0, limit=10000) + for recipe in recipes: + try: + await recipe_service.delete_recipe(recipe["recipe_id"], actor_id) + deleted["recipes"] += 1 + except Exception as e: + errors.append(f"Recipe {recipe['recipe_id']}: {e}") + except Exception as e: + errors.append(f"Failed to list recipes: {e}") + + # Delete all effects + try: + cache_manager = get_cache_manager() + effects_dir = Path(cache_manager.cache_dir) / "_effects" + if effects_dir.exists(): + import shutil + for effect_dir in effects_dir.iterdir(): + if effect_dir.is_dir(): + try: + shutil.rmtree(effect_dir) + deleted["effects"] += 1 + except Exception as e: + errors.append(f"Effect {effect_dir.name}: {e}") + except Exception as e: + errors.append(f"Failed to delete effects: {e}") + + # Delete all media/cache items for user + try: + items = await database.get_user_items(actor_id, limit=10000) + for item in items: + try: + cid = item.get("cid") + if cid: + await database.delete_cache_item(cid) + deleted["media"] += 1 + except Exception as e: + errors.append(f"Media {item.get('cid', 'unknown')}: {e}") + except Exception as e: + errors.append(f"Failed to list media: {e}") + + logger.info(f"Cleared data for {actor_id}: {deleted}") + if errors: + logger.warning(f"Errors during clear: {errors[:10]}") # Log first 10 errors + + return { + "message": "User data cleared", + "deleted": deleted, + "errors": errors[:10] if errors else [], # Return first 10 errors + "storage_preserved": True, + } + + @router.get("/") async def home(request: Request): """