fix admin delete to wipe originals/edits/geojson; rename button to Reset data

The old DELETE /api/admin/users/{handle}/activities only removed *.json
files and _merged/, leaving originals/ (Strava FIT files) and edits/
untouched — causing the 968 MB disk usage after a delete.

_wipe_user_activities() now removes activities/, edits/, originals/,
_merged/, index.json, athlete.json, and .bincio_cache.json. Admin page
button renamed to "Reset data" with updated confirmation text.
This commit is contained in:
Davide Scaini
2026-04-13 20:10:15 +02:00
parent a75dfa160b
commit d2ba96c26a
2 changed files with 42 additions and 21 deletions
+37 -18
View File
@@ -517,38 +517,57 @@ async def admin_rebuild(
return JSONResponse({"ok": True}) return JSONResponse({"ok": True})
def _wipe_user_activities(user_dir: Path) -> int:
"""Delete all extracted activity files and caches for a user.
Removes activities/ (JSON + GeoJSON + timeseries), edits/, originals/,
_merged/, index.json, athlete.json, and the dedup cache.
Leaves the user directory itself intact (account remains in the DB).
Returns the number of files deleted.
"""
import shutil
deleted = 0
for subdir in ("activities", "edits", "originals"):
d = user_dir / subdir
if d.exists():
for f in d.rglob("*"):
if f.is_file():
deleted += 1
shutil.rmtree(d)
for name in ("_merged", ):
d = user_dir / name
if d.exists():
shutil.rmtree(d)
for name in ("index.json", "athlete.json", ".bincio_cache.json"):
f = user_dir / name
if f.exists():
f.unlink()
deleted += 1
return deleted
@app.delete("/api/admin/users/{handle}/activities") @app.delete("/api/admin/users/{handle}/activities")
async def admin_delete_activities( async def admin_delete_activities(
handle: str, handle: str,
bincio_session: Optional[str] = Cookie(default=None), bincio_session: Optional[str] = Cookie(default=None),
) -> JSONResponse: ) -> JSONResponse:
"""Delete all activity JSON files for a user and wipe the merged cache.""" """Delete all activity data for a user and wipe the merged cache."""
_require_admin(bincio_session) _require_admin(bincio_session)
user_dir = _get_data_dir() / handle user_dir = _get_data_dir() / handle
if not user_dir.is_dir(): if not user_dir.is_dir():
raise HTTPException(404, f"No data directory for user '{handle}'") raise HTTPException(404, f"No data directory for user '{handle}'")
deleted = 0 deleted = _wipe_user_activities(user_dir)
activities_dir = user_dir / "activities"
if activities_dir.is_dir():
for f in activities_dir.glob("*.json"):
f.unlink()
deleted += 1
# Wipe merged cache, top-level index, and dedup cache so re-uploads aren't blocked
import shutil
merged_dir = user_dir / "_merged"
if merged_dir.exists():
shutil.rmtree(merged_dir)
for name in ("index.json", ".bincio_cache.json"):
f = user_dir / name
if f.exists():
f.unlink()
_trigger_rebuild(handle) _trigger_rebuild(handle)
return JSONResponse({"ok": True, "deleted": deleted}) return JSONResponse({"ok": True, "deleted": deleted})
# ── Write API (ported from bincio edit, auth-gated) ─────────────────────────── # ── Write API (ported from bincio edit, auth-gated) ───────────────────────────
def _user_data_dir(handle: str) -> Path: def _user_data_dir(handle: str) -> Path:
+5 -3
View File
@@ -33,10 +33,11 @@ import Base from '../../layouts/Base.astro';
<!-- Confirmation dialog --> <!-- Confirmation dialog -->
<dialog id="confirm-dialog" class="rounded-xl bg-zinc-900 border border-zinc-700 p-6 text-white max-w-sm w-full backdrop:bg-black/60"> <dialog id="confirm-dialog" class="rounded-xl bg-zinc-900 border border-zinc-700 p-6 text-white max-w-sm w-full backdrop:bg-black/60">
<p class="text-sm text-zinc-300 mb-5">Delete all activities for <strong id="confirm-handle" class="text-white"></strong>? This cannot be undone.</p> <p class="text-sm text-zinc-300 mb-1">Reset all data for <strong id="confirm-handle" class="text-white"></strong>?</p>
<p class="text-xs text-zinc-500 mb-5">Removes all activities, originals, edits, and images. The account is kept. This cannot be undone.</p>
<div class="flex gap-3 justify-end"> <div class="flex gap-3 justify-end">
<button id="confirm-cancel" class="px-4 py-2 rounded-lg text-sm bg-zinc-800 hover:bg-zinc-700 text-zinc-300 transition-colors">Cancel</button> <button id="confirm-cancel" class="px-4 py-2 rounded-lg text-sm bg-zinc-800 hover:bg-zinc-700 text-zinc-300 transition-colors">Cancel</button>
<button id="confirm-ok" class="px-4 py-2 rounded-lg text-sm bg-red-700 hover:bg-red-600 text-white font-medium transition-colors">Delete</button> <button id="confirm-ok" class="px-4 py-2 rounded-lg text-sm bg-red-700 hover:bg-red-600 text-white font-medium transition-colors">Reset</button>
</div> </div>
</dialog> </dialog>
</div> </div>
@@ -124,7 +125,8 @@ import Base from '../../layouts/Base.astro';
<button <button
class="delete-btn text-xs px-3 py-1.5 rounded-lg bg-zinc-800 hover:bg-red-900 hover:text-red-300 text-zinc-400 transition-colors" class="delete-btn text-xs px-3 py-1.5 rounded-lg bg-zinc-800 hover:bg-red-900 hover:text-red-300 text-zinc-400 transition-colors"
data-handle="${u.handle}" data-handle="${u.handle}"
>Delete activities</button> title="Wipe all activities, originals, edits and images — account is kept"
>Reset data</button>
</div> </div>
</td> </td>
</tr> </tr>