Skip to main content

cfgd_core/state/
compliance.rs

1use rusqlite::params;
2
3use super::StateStore;
4use super::types::ComplianceHistoryRow;
5use crate::errors::{Result, StateError};
6
7impl StateStore {
8    /// Store a compliance snapshot. The caller provides the content hash
9    /// (typically `sha256_hex` of the serialized JSON).
10    pub fn store_compliance_snapshot(
11        &self,
12        snapshot: &crate::compliance::ComplianceSnapshot,
13        hash: &str,
14    ) -> Result<()> {
15        let json = serde_json::to_string(snapshot)
16            .map_err(|e| StateError::Database(format!("failed to serialize snapshot: {}", e)))?;
17        self.conn.execute(
18            "INSERT INTO compliance_snapshots (timestamp, content_hash, snapshot_json, summary_compliant, summary_warning, summary_violation)
19             VALUES (?1, ?2, ?3, ?4, ?5, ?6)",
20            params![
21                snapshot.timestamp,
22                hash,
23                json,
24                snapshot.summary.compliant as i64,
25                snapshot.summary.warning as i64,
26                snapshot.summary.violation as i64,
27            ],
28        )?;
29        Ok(())
30    }
31
32    /// Get the content hash of the most recently stored compliance snapshot.
33    pub fn latest_compliance_hash(&self) -> Result<Option<String>> {
34        let result = self.conn.query_row(
35            "SELECT content_hash FROM compliance_snapshots ORDER BY id DESC LIMIT 1",
36            [],
37            |row| row.get(0),
38        );
39
40        match result {
41            Ok(hash) => Ok(Some(hash)),
42            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
43            Err(e) => Err(StateError::Database(e.to_string()).into()),
44        }
45    }
46
47    /// Get compliance snapshot history as summary rows.
48    /// If `since` is provided, only return snapshots after that ISO 8601 timestamp.
49    pub fn compliance_history(
50        &self,
51        since: Option<&str>,
52        limit: u32,
53    ) -> Result<Vec<ComplianceHistoryRow>> {
54        if let Some(since_ts) = since {
55            let mut stmt = self.conn.prepare(
56                "SELECT id, timestamp, summary_compliant, summary_warning, summary_violation
57                 FROM compliance_snapshots WHERE timestamp > ?1 ORDER BY id DESC LIMIT ?2",
58            )?;
59
60            let rows = stmt
61                .query_map(params![since_ts, limit], |row| {
62                    Ok(ComplianceHistoryRow {
63                        id: row.get(0)?,
64                        timestamp: row.get(1)?,
65                        compliant: row.get(2)?,
66                        warning: row.get(3)?,
67                        violation: row.get(4)?,
68                    })
69                })?
70                .collect::<std::result::Result<Vec<_>, _>>()?;
71            Ok(rows)
72        } else {
73            let mut stmt = self.conn.prepare(
74                "SELECT id, timestamp, summary_compliant, summary_warning, summary_violation
75                 FROM compliance_snapshots ORDER BY id DESC LIMIT ?1",
76            )?;
77
78            let rows = stmt
79                .query_map(params![limit], |row| {
80                    Ok(ComplianceHistoryRow {
81                        id: row.get(0)?,
82                        timestamp: row.get(1)?,
83                        compliant: row.get(2)?,
84                        warning: row.get(3)?,
85                        violation: row.get(4)?,
86                    })
87                })?
88                .collect::<std::result::Result<Vec<_>, _>>()?;
89            Ok(rows)
90        }
91    }
92
93    /// Retrieve a full compliance snapshot by ID.
94    pub fn get_compliance_snapshot(
95        &self,
96        id: i64,
97    ) -> Result<Option<crate::compliance::ComplianceSnapshot>> {
98        let result = self.conn.query_row(
99            "SELECT snapshot_json FROM compliance_snapshots WHERE id = ?1",
100            params![id],
101            |row| row.get::<_, String>(0),
102        );
103
104        match result {
105            Ok(json) => {
106                let snapshot: crate::compliance::ComplianceSnapshot = serde_json::from_str(&json)
107                    .map_err(|e| {
108                    StateError::Database(format!("failed to deserialize snapshot: {}", e))
109                })?;
110                Ok(Some(snapshot))
111            }
112            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
113            Err(e) => Err(StateError::Database(e.to_string()).into()),
114        }
115    }
116
117    /// Remove compliance snapshots older than the given ISO 8601 timestamp.
118    /// Returns the number of rows deleted.
119    pub fn prune_compliance_snapshots(&self, before_timestamp: &str) -> Result<usize> {
120        let deleted = self.conn.execute(
121            "DELETE FROM compliance_snapshots WHERE timestamp < ?1",
122            params![before_timestamp],
123        )?;
124        Ok(deleted)
125    }
126}