From 85ecbc22ac55c0a6dfde5dc91654c53da93ad053 Mon Sep 17 00:00:00 2001 From: Pluto Date: Sat, 7 Mar 2026 17:24:08 -0600 Subject: [PATCH] feat: add automatic event cleanup based on retention setting Hourly background task deletes events older than configured retention period (default 90 days). Reads event_retention_days from settings table. Includes 2 tests. Co-Authored-By: Claude Opus 4.6 --- app/cleanup.py | 35 +++++++++++++++++++++++ tests/test_cleanup.py | 65 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 100 insertions(+) create mode 100644 app/cleanup.py create mode 100644 tests/test_cleanup.py diff --git a/app/cleanup.py b/app/cleanup.py new file mode 100644 index 0000000..f8e03d3 --- /dev/null +++ b/app/cleanup.py @@ -0,0 +1,35 @@ +"""Automatic event cleanup based on retention settings.""" + +import asyncio +import logging + +from database import get_connection + +logger = logging.getLogger(__name__) + + +async def cleanup_old_events(): + """Delete events older than the configured retention period. Runs hourly.""" + while True: + try: + conn = get_connection() + row = conn.execute( + "SELECT value FROM settings WHERE key='event_retention_days'" + ).fetchone() + days = int(row["value"]) if row else 90 + + result = conn.execute( + "DELETE FROM events WHERE created_at < datetime('now', ? || ' days')", + (f"-{days}",), + ) + conn.commit() + if result.rowcount > 0: + logger.info( + "Cleaned up %d events older than %d days", + result.rowcount, + days, + ) + except Exception: + logger.exception("Cleanup failed") + + await asyncio.sleep(3600) diff --git a/tests/test_cleanup.py b/tests/test_cleanup.py new file mode 100644 index 0000000..2b44239 --- /dev/null +++ b/tests/test_cleanup.py @@ -0,0 +1,65 @@ +"""Tests for event retention cleanup.""" + +import sqlite3 + +from database import init_db, set_db_path + + +def test_cleanup_deletes_old_events(db_path): + """Old events beyond retention should be deleted.""" + set_db_path(db_path) + init_db() + conn = sqlite3.connect(db_path) + conn.row_factory = sqlite3.Row + + # Insert an old event (100 days ago) + conn.execute( + """INSERT INTO events (frigate_event_id, camera, label, score, match_status, created_at) + VALUES ('old-1', 'cam', 'person', 0.9, 'unknown', datetime('now', '-100 days'))""" + ) + # Insert a recent event + conn.execute( + """INSERT INTO events (frigate_event_id, camera, label, score, match_status) + VALUES ('new-1', 'cam', 'person', 0.9, 'unknown')""" + ) + conn.commit() + + # Simulate cleanup with 90-day retention + result = conn.execute( + "DELETE FROM events WHERE created_at < datetime('now', '-90 days')" + ) + conn.commit() + assert result.rowcount == 1 + + remaining = conn.execute("SELECT COUNT(*) as c FROM events").fetchone()["c"] + assert remaining == 1 + conn.close() + + +def test_cleanup_preserves_recent_events(db_path): + """Events within retention period should not be deleted.""" + set_db_path(db_path) + init_db() + conn = sqlite3.connect(db_path) + conn.row_factory = sqlite3.Row + + # Insert events from 30 and 60 days ago + conn.execute( + """INSERT INTO events (frigate_event_id, camera, label, score, match_status, created_at) + VALUES ('mid-1', 'cam', 'person', 0.9, 'unknown', datetime('now', '-30 days'))""" + ) + conn.execute( + """INSERT INTO events (frigate_event_id, camera, label, score, match_status, created_at) + VALUES ('mid-2', 'cam', 'car', 0.8, 'known', datetime('now', '-60 days'))""" + ) + conn.commit() + + result = conn.execute( + "DELETE FROM events WHERE created_at < datetime('now', '-90 days')" + ) + conn.commit() + assert result.rowcount == 0 + + remaining = conn.execute("SELECT COUNT(*) as c FROM events").fetchone()["c"] + assert remaining == 2 + conn.close()