feat: checkbox batch trip delete on the Pi (local-only)

- Pi history page gets a select checkbox column + 'Delete selected' button
  (mirrors the server UI)
- POST /api/trips/delete batch endpoint
- app_sql.delete_trips_batch() + deleted_trip_markers table so a later trip
  rebuild can't resurrect locally-deleted trips
- Deletion is local-only: raw tracking rows are kept and the remote server
  is never notified (it keeps its archival copy recomputed from uploaded rows)
This commit is contained in:
Mathieu Moran
2026-08-15 16:55:30 -04:00
parent c9d36cca33
commit 99035e86ee
4 changed files with 85 additions and 2 deletions
+36 -1
View File
@@ -116,6 +116,14 @@ SQL_CREATE_ALERT_STATE_TABLE = """CREATE TABLE IF NOT EXISTS alert_state (
LAST_VALUE text
);"""
# Local-only trip deletion markers. trip_id == the trip's START_TRACKING_ID, so
# a later rebuild can skip re-inserting a trip the user deleted. This is
# independent of the server's own deleted_trip_markers — Pi deletions are never
# pushed to the remote server (archival copy kept there).
SQL_CREATE_DELETED_TRIP_MARKERS_TABLE = """CREATE TABLE IF NOT EXISTS deleted_trip_markers (
trip_id integer PRIMARY KEY
);"""
def create_conn(db_file):
"""Open a SQLite connection to db_file, creating its parent folder if needed.
@@ -161,6 +169,7 @@ def get_conn():
create_table(_conn, SQL_CREATE_TRIPS_TABLE)
create_table(_conn, SQL_CREATE_TRIP_BUILD_STATE_TABLE)
create_table(_conn, SQL_CREATE_ALERT_STATE_TABLE)
create_table(_conn, SQL_CREATE_DELETED_TRIP_MARKERS_TABLE)
_migrate_tracking_table(_conn)
_migrate_trips_table(_conn)
_migrate_trip_build_state_table(_conn)
@@ -435,6 +444,12 @@ def refresh_trips(force=False):
trips = app_trip.build_trip_summaries(rows)
with _conn_lock:
try:
deleted_ids = {
row[0] for row in conn.execute(
"SELECT trip_id FROM deleted_trip_markers"
).fetchall()
}
trips = [t for t in trips if t["id"] not in deleted_ids]
conn.execute(
"DELETE FROM trips WHERE END_TRACKING_ID >= ?",
(rebuild_start_id,),
@@ -604,16 +619,36 @@ def get_trip_route(trip_id, max_points=2000):
def delete_trip(trip_id):
"""Delete a computed trip record. Raw tracking rows are NOT removed."""
"""Delete a computed trip record. Raw tracking rows are NOT removed, and the
deletion is local-only — the remote server keeps its own archival copy
(recomputed from the uploaded tracking rows) and is never told about this.
The trip id is remembered in deleted_trip_markers so a later trip rebuild
can't silently resurrect it."""
conn = get_conn()
if conn is None:
return False
with _conn_lock:
cur = conn.execute("DELETE FROM trips WHERE id = ?", (trip_id,))
if cur.rowcount > 0:
conn.execute(
"INSERT OR IGNORE INTO deleted_trip_markers (trip_id) VALUES (?)",
(trip_id,),
)
conn.commit()
return cur.rowcount > 0
def delete_trips_batch(trip_ids):
"""Delete multiple computed trips locally (raw tracking rows preserved,
remote server untouched). Returns the number deleted."""
deleted = 0
for trip_id in trip_ids:
if delete_trip(trip_id):
deleted += 1
return deleted
def get_setting(key, default=None):
"""Read a persisted UI/app setting (e.g. the dashcam-upload toggle)."""
conn = get_conn()