heldar_entry/
retention.rs1use std::sync::Arc;
6use std::time::Duration;
7
8use chrono::Utc;
9use heldar_kernel::config::Config;
10use sqlx::SqlitePool;
11
12use crate::config::EntryConfig;
13
14pub async fn run(pool: SqlitePool, cfg: Arc<Config>, ecfg: Arc<EntryConfig>) {
15 let mut tick = tokio::time::interval(Duration::from_secs(cfg.retention_interval_s.max(30)));
16 loop {
17 tick.tick().await;
18 if let Err(e) = sweep(&pool, &cfg, &ecfg).await {
19 tracing::error!(error = %e, "entry retention: sweep failed");
20 }
21 }
22}
23
24async fn sweep(pool: &SqlitePool, cfg: &Config, ecfg: &EntryConfig) -> anyhow::Result<()> {
25 let cutoff = Utc::now() - chrono::Duration::days(ecfg.entry_retention_days.max(1));
26 let old: Vec<(String, sqlx::types::Json<serde_json::Value>)> =
27 sqlx::query_as("SELECT id, evidence FROM entry_events WHERE created_at < ?")
28 .bind(cutoff)
29 .fetch_all(pool)
30 .await?;
31 if old.is_empty() {
32 return Ok(());
33 }
34 for (_id, evidence) in &old {
35 if let Some(name) = evidence
36 .0
37 .get("snapshot_path")
38 .and_then(|v| v.as_str())
39 .and_then(|u| u.rsplit('/').next())
40 {
41 let _ = tokio::fs::remove_file(cfg.snapshots_dir.join(name)).await;
42 }
43 }
44 let n = sqlx::query("DELETE FROM entry_events WHERE created_at < ?")
45 .bind(cutoff)
46 .execute(pool)
47 .await?
48 .rows_affected();
49 tracing::info!(
50 deleted = n,
51 "entry retention: pruned old entry events + evidence"
52 );
53 Ok(())
54}