- background.start_periodic(interval, work, label=) replaces the three hand-rolled
while-True/sleep/try loops in logging, auth and notifications.
- services/scheduler.ScheduledJob replaces the four private BackgroundScheduler
copies in recurrence/version_pinning/trash/db_maintenance schedulers; public
start_/stop_/reschedule_ surfaces unchanged.
- api_keys.hash_token is the one sha256 helper; auth.py used to inline it 5x.
- auth.is_registration_open reads via settings.get_admin_setting; notification
prefs read via settings.get_setting; _fire_share_email uses _get_user_email.
- projects.get_project_summary / milestones.get_project_milestone_summary are
now the one-id view of their batch siblings instead of a second copy of the
queries; sharing.best_permission_by (was _deduplicate_by_permission) is the
one rank-dedup, now also used by list_projects_for_user.
- backup: the row builders for every section both exporters carry are named
functions, so a column added to one export cannot silently miss the other.
- iso() from models.base replaces the attr.isoformat()-if-attr-else-None idiom
and db_maintenance._iso across services; backup keeps its explicit shape.
- trash.py hoists the sql_delete/timedelta imports it re-imported per function.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds a daily off-hours VACUUM (ANALYZE) over the high-churn tables the
retention/purge sweeps churn (app_logs, notifications, token tables, notes,
note_versions), on top of Postgres autovacuum, to reclaim bloat left by the
nightly bulk DELETEs and keep planner stats fresh.
- services/db_maintenance.py: run_maintenance() over a closed table allowlist
via an AUTOCOMMIT connection (VACUUM can't run in a txn); per-table summary
persisted as the db_maintenance_last_run admin setting.
- services/db_maintenance_scheduler.py: BackgroundScheduler cron (default
04:00 UTC, after the 03:30 trash purge); enabled-gate checked at fire time;
live reschedule on hour change. Wired into app.py start/stop.
- routes/admin.py: admin-only GET/PUT /api/admin/db-maintenance + POST /run.
- settings.py: set_admin_setting() (write-side of get_admin_setting) for
out-of-request writes.
- SettingsView.vue: admin 'Database maintenance' card — enable toggle, run-hour
(UTC), Run-now, last-run summary.
- Tests: allowlist is closed, VACUUM issued per table, one failure doesn't
abort the rest, summary persisted; route/scheduler/service surface.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>