diff --git a/bincio/serve/server.py b/bincio/serve/server.py index 1fced65..9003d02 100644 --- a/bincio/serve/server.py +++ b/bincio/serve/server.py @@ -597,6 +597,56 @@ async def post_activity( return JSONResponse({"ok": True}) +@app.delete("/api/activity/{activity_id}") +async def delete_activity( + activity_id: str, + bincio_session: Optional[str] = Cookie(default=None), +) -> JSONResponse: + """Delete a single activity and all associated files for the logged-in user.""" + user = _require_user(bincio_session) + _check_id(activity_id) + dd = _get_data_dir() / user.handle + acts_dir = dd / "activities" + + json_path = acts_dir / f"{activity_id}.json" + if not json_path.exists(): + raise HTTPException(404, "Activity not found") + + import shutil + + # Remove the source files (activities dir) + for suffix in (".json", ".geojson", ".timeseries.json"): + p = acts_dir / f"{activity_id}{suffix}" + p.unlink(missing_ok=True) + + # Remove sidecar edit and images + sidecar = dd / "edits" / f"{activity_id}.md" + sidecar.unlink(missing_ok=True) + images_dir = dd / "edits" / "images" / activity_id + if images_dir.exists(): + shutil.rmtree(images_dir) + + # Remove from dedup cache so the file can be re-uploaded if needed + cache_path = dd / ".bincio_cache.json" + if cache_path.exists(): + try: + cache = json.loads(cache_path.read_text(encoding="utf-8")) + if isinstance(cache, dict) and "activities" in cache: + cache["activities"] = [ + a for a in cache["activities"] if a.get("id") != activity_id + ] + cache_path.write_text(json.dumps(cache, indent=2, ensure_ascii=False)) + except Exception: + pass # corrupt cache — leave it; next extract will rebuild + + # Full merge needed: activity removed from index + from bincio.render.merge import merge_all + merge_all(dd) + _trigger_rebuild(user.handle) + + return JSONResponse({"ok": True}) + + @app.get("/api/activity/{activity_id}/images") async def list_images( activity_id: str, diff --git a/site/src/components/ActivityDetail.svelte b/site/src/components/ActivityDetail.svelte index 615a90b..81b2280 100644 --- a/site/src/components/ActivityDetail.svelte +++ b/site/src/components/ActivityDetail.svelte @@ -112,7 +112,7 @@ {#if editOpen && editEnabled} - editOpen = false} /> + editOpen = false} on:deleted={() => { window.location.href = base; }} /> {/if} diff --git a/site/src/components/EditDrawer.svelte b/site/src/components/EditDrawer.svelte index 9397030..47b18fc 100644 --- a/site/src/components/EditDrawer.svelte +++ b/site/src/components/EditDrawer.svelte @@ -5,7 +5,7 @@ export let activityId: string; export let editUrl: string; - const dispatch = createEventDispatcher<{ saved: { title: string; description: string }; close: void }>(); + const dispatch = createEventDispatcher<{ saved: { title: string; description: string }; close: void; deleted: void }>(); const SPORTS: Sport[] = ['cycling', 'running', 'hiking', 'walking', 'swimming', 'skiing', 'other']; const STAT_PANELS = [ @@ -21,6 +21,8 @@ let saving = false; let saveStatus = ''; let saveOk = false; + let confirmDelete = false; + let deleting = false; // Form state let title = ''; @@ -120,6 +122,22 @@ : [...hideStats, key]; } + async function deleteActivity() { + if (!confirmDelete) { confirmDelete = true; return; } + deleting = true; + try { + const res = await fetch(api, { method: 'DELETE' }); + if (!res.ok) throw new Error(await res.text()); + dispatch('deleted'); + } catch (e: any) { + saveStatus = `Delete failed: ${e.message}`; + saveOk = false; + confirmDelete = false; + } finally { + deleting = false; + } + } + load(); @@ -284,11 +302,26 @@
+ {#if saveStatus} {saveStatus}