Skip to main content

cfgd_core/state/
applies.rs

1use rusqlite::params;
2
3use super::StateStore;
4use super::types::{ApplyRecord, ApplyStatus};
5use crate::errors::{Result, StateError};
6
7impl StateStore {
8    /// Record a completed apply operation.
9    pub fn record_apply(
10        &self,
11        profile: &str,
12        plan_hash: &str,
13        status: ApplyStatus,
14        summary: Option<&str>,
15    ) -> Result<i64> {
16        let timestamp = crate::utc_now_iso8601();
17        self.conn
18            .execute(
19                "INSERT INTO applies (timestamp, profile, plan_hash, status, summary) VALUES (?1, ?2, ?3, ?4, ?5)",
20                params![timestamp, profile, plan_hash, status.as_str(), summary],
21            )
22            ?;
23        Ok(self.conn.last_insert_rowid())
24    }
25
26    /// Get the most recent apply record.
27    pub fn last_apply(&self) -> Result<Option<ApplyRecord>> {
28        let result = self.conn.query_row(
29            "SELECT id, timestamp, profile, plan_hash, status, summary FROM applies ORDER BY id DESC LIMIT 1",
30            [],
31            |row| {
32                Ok(ApplyRecord {
33                    id: row.get(0)?,
34                    timestamp: row.get(1)?,
35                    profile: row.get(2)?,
36                    plan_hash: row.get(3)?,
37                    status: ApplyStatus::from_str(&row.get::<_, String>(4)?),
38                    summary: row.get(5)?,
39                })
40            },
41        );
42
43        match result {
44            Ok(record) => Ok(Some(record)),
45            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
46            Err(e) => Err(StateError::Database(e.to_string()).into()),
47        }
48    }
49
50    /// Get a specific apply record by ID.
51    pub fn get_apply(&self, apply_id: i64) -> Result<Option<ApplyRecord>> {
52        let result = self.conn.query_row(
53            "SELECT id, timestamp, profile, plan_hash, status, summary FROM applies WHERE id = ?1",
54            params![apply_id],
55            |row| {
56                Ok(ApplyRecord {
57                    id: row.get(0)?,
58                    timestamp: row.get(1)?,
59                    profile: row.get(2)?,
60                    plan_hash: row.get(3)?,
61                    status: ApplyStatus::from_str(&row.get::<_, String>(4)?),
62                    summary: row.get(5)?,
63                })
64            },
65        );
66
67        match result {
68            Ok(record) => Ok(Some(record)),
69            Err(rusqlite::Error::QueryReturnedNoRows) => Ok(None),
70            Err(e) => Err(StateError::Database(e.to_string()).into()),
71        }
72    }
73
74    /// Get apply history (most recent first), limited to `limit` entries.
75    pub fn history(&self, limit: u32) -> Result<Vec<ApplyRecord>> {
76        let mut stmt = self
77            .conn
78            .prepare(
79                "SELECT id, timestamp, profile, plan_hash, status, summary FROM applies ORDER BY id DESC LIMIT ?1",
80            )
81            ?;
82
83        let records = stmt
84            .query_map(params![limit], |row| {
85                Ok(ApplyRecord {
86                    id: row.get(0)?,
87                    timestamp: row.get(1)?,
88                    profile: row.get(2)?,
89                    plan_hash: row.get(3)?,
90                    status: ApplyStatus::from_str(&row.get::<_, String>(4)?),
91                    summary: row.get(5)?,
92                })
93            })?
94            .collect::<std::result::Result<Vec<_>, _>>()?;
95
96        Ok(records)
97    }
98
99    /// Update apply status (for changing "in-progress" to final status).
100    pub fn update_apply_status(
101        &self,
102        apply_id: i64,
103        status: ApplyStatus,
104        summary: Option<&str>,
105    ) -> Result<()> {
106        self.conn.execute(
107            "UPDATE applies SET status = ?1, summary = ?2 WHERE id = ?3",
108            params![status.as_str(), summary, apply_id],
109        )?;
110        Ok(())
111    }
112}