From 7aa9ffda16530c840f0fff11a69e723f5858c6ef Mon Sep 17 00:00:00 2001 From: Mathieu Moran Date: Mon, 10 Aug 2026 04:40:22 -0400 Subject: [PATCH] Add delete with confirmation for trips and dashcam - Confirmation modal in server base.html (window.confirmDelete promise helper) - Trip delete: trash button in Replay column (server-only via data-deletable on trip-list div), DELETE /api/v1/portal/trip/, removes computed trip only (raw tracking_points kept) - Dashcam delete: trash button next to download, DELETE /api/v1/portal/dashcam/, removes from DB + disk (best-effort file removal) - history.js: delete button gated on data-deletable='1' attribute; click handler intercepts before replay logic - db.py: delete_trip(), delete_dashcam_file() helpers Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- server/app.py | 30 +++++++++++++++++++++++++ server/modules/db.py | 21 ++++++++++++++++++ server/templates/base.html | 38 ++++++++++++++++++++++++++++++++ server/templates/components.html | 4 ++-- server/templates/dashcam.html | 31 ++++++++++++++++++++++++-- server/templates/history.html | 3 ++- static/js/history.js | 30 ++++++++++++++++++++++++- 7 files changed, 151 insertions(+), 6 deletions(-) diff --git a/server/app.py b/server/app.py index 10e1617..e452fb6 100644 --- a/server/app.py +++ b/server/app.py @@ -559,6 +559,36 @@ def recompute_trips(): return jsonify(status="ok", trips_computed=n) +@app.delete("/api/v1/portal/trip/") +@require_login +def portal_delete_trip(trip_id): + device_id = _portal_device_id() + if device_id is None: + return jsonify(error="device not configured"), 404 + deleted = db.delete_trip(device_id, trip_id) + if not deleted: + return jsonify(error="trip not found"), 404 + return jsonify(status="ok") + + +@app.delete("/api/v1/portal/dashcam/") +@require_login +def portal_delete_dashcam(file_id): + device_id = _portal_device_id() + if device_id is None: + return jsonify(error="device not configured"), 404 + storage_path = db.delete_dashcam_file(device_id, file_id) + if storage_path is None: + return jsonify(error="file not found"), 404 + # Remove from disk (best-effort — don't fail if file is already gone) + try: + if os.path.isfile(storage_path): + os.remove(storage_path) + except OSError: + pass + return jsonify(status="ok") + + @app.get("/api/trips") @require_login def portal_trips_compat(): diff --git a/server/modules/db.py b/server/modules/db.py index 63e0477..d117510 100644 --- a/server/modules/db.py +++ b/server/modules/db.py @@ -348,6 +348,27 @@ def get_dashcam_file(device_id, file_id): return {"id": r[0], "relative_path": r[1], "size_bytes": r[2], "uploaded_at": r[3].isoformat(), "storage_path": r[4]} +def delete_trip(device_id, trip_id): + """Delete a computed trip record. Raw tracking_points are NOT removed.""" + with get_cursor(commit=True) as cur: + cur.execute( + "DELETE FROM trips WHERE id = %s AND device_id = %s RETURNING id", + (trip_id, device_id), + ) + return cur.fetchone() is not None + + +def delete_dashcam_file(device_id, file_id): + """Delete dashcam file from DB and disk. Returns storage_path if deleted, None if not found.""" + with get_cursor(commit=True) as cur: + cur.execute( + "DELETE FROM dashcam_files WHERE id = %s AND device_id = %s RETURNING storage_path", + (file_id, device_id), + ) + row = cur.fetchone() + return row[0] if row else None + + def get_overview_stats(device_id): with get_cursor() as cur: cur.execute( diff --git a/server/templates/base.html b/server/templates/base.html index 30c99fd..d264b14 100644 --- a/server/templates/base.html +++ b/server/templates/base.html @@ -106,6 +106,44 @@ + + {# ── Shared confirmation modal ─────────────────────────────────────────── #} + + {% block scripts %}{% endblock %} diff --git a/server/templates/components.html b/server/templates/components.html index 324d25d..97e79e1 100644 --- a/server/templates/components.html +++ b/server/templates/components.html @@ -1,5 +1,5 @@ -{% macro data_table(id, columns, empty_title, empty_message) %} -
+{% macro data_table(id, columns, empty_title, empty_message, deletable=false) %} +