| 1 | """Retention enforcement. Deletes plate reads past the cutoff together with the rows that |
| 2 | reference them (sightings, watchlist hits) so the delete doesn't violate foreign keys. |
| 3 | Shared by the admin endpoint and the scheduled Celery task.""" |
| 4 | from __future__ import annotations |
| 5 | |
| 6 | from datetime import datetime |
| 7 | |
| 8 | from sqlalchemy import delete, func, select |
| 9 | from sqlalchemy.orm import Session |
| 10 | |
| 11 | from app.models import PlateRead, Sighting, WatchlistHit |
| 12 | |
| 13 | |
| 14 | def count_expired(db: Session, cutoff: datetime) -> int: |
| 15 | return db.scalar(select(func.count()).select_from(PlateRead) |
| 16 | .where(PlateRead.read_ts < cutoff)) or 0 |
| 17 | |
| 18 | |
| 19 | def purge_expired(db: Session, cutoff: datetime) -> int: |
| 20 | """Delete reads older than cutoff and their dependents. Returns reads deleted.""" |
| 21 | old_ids = [r[0] for r in db.execute( |
| 22 | select(PlateRead.id).where(PlateRead.read_ts < cutoff)).all()] |
| 23 | if not old_ids: |
| 24 | return 0 |
| 25 | db.execute(delete(WatchlistHit).where(WatchlistHit.plate_read_id.in_(old_ids))) |
| 26 | db.execute(delete(Sighting).where(Sighting.plate_read_id.in_(old_ids))) |
| 27 | result = db.execute(delete(PlateRead).where(PlateRead.id.in_(old_ids))) |
| 28 | return result.rowcount |