agentmux_srv\backend\storage/
history.rs1use rusqlite::params;
15use serde::{Deserialize, Serialize};
16
17use super::error::StoreError;
18use super::store::Store;
19
20#[derive(Debug, Clone, Serialize, Deserialize)]
22pub struct AgentHistory {
23 pub id: i64,
24 pub agent_id: String,
25 pub session_date: String,
26 pub entry: String,
27 pub timestamp: i64,
28}
29
30impl Store {
31 pub fn agent_history_append(
33 &self,
34 agent_id: &str,
35 entry: &str,
36 ) -> Result<AgentHistory, StoreError> {
37 let now = std::time::SystemTime::now()
38 .duration_since(std::time::UNIX_EPOCH)
39 .unwrap_or_default()
40 .as_millis() as i64;
41 let secs = (now / 1000) as u64;
43 let days = secs / 86400;
44 let session_date = format_epoch_date(days);
46 let conn = self.conn.lock().unwrap();
47 conn.execute(
48 "INSERT INTO db_agent_history (agent_id, session_date, entry, timestamp) VALUES (?1, ?2, ?3, ?4)",
49 params![agent_id, session_date, entry, now],
50 )?;
51 let id = conn.last_insert_rowid();
52 Ok(AgentHistory {
53 id,
54 agent_id: agent_id.to_string(),
55 session_date,
56 entry: entry.to_string(),
57 timestamp: now,
58 })
59 }
60
61 pub fn agent_history_list(
63 &self,
64 agent_id: &str,
65 session_date: Option<&str>,
66 limit: i64,
67 offset: i64,
68 ) -> Result<Vec<AgentHistory>, StoreError> {
69 let conn = self.conn.lock().unwrap();
70 let (sql, params_vec): (String, Vec<Box<dyn rusqlite::types::ToSql>>) = match session_date {
71 Some(date) => (
72 "SELECT id, agent_id, session_date, entry, timestamp
73 FROM db_agent_history WHERE agent_id=?1 AND session_date=?2
74 ORDER BY timestamp DESC LIMIT ?3 OFFSET ?4"
75 .to_string(),
76 vec![
77 Box::new(agent_id.to_string()),
78 Box::new(date.to_string()),
79 Box::new(limit),
80 Box::new(offset),
81 ],
82 ),
83 None => (
84 "SELECT id, agent_id, session_date, entry, timestamp
85 FROM db_agent_history WHERE agent_id=?1
86 ORDER BY timestamp DESC LIMIT ?2 OFFSET ?3"
87 .to_string(),
88 vec![
89 Box::new(agent_id.to_string()),
90 Box::new(limit),
91 Box::new(offset),
92 ],
93 ),
94 };
95 let params_refs: Vec<&dyn rusqlite::types::ToSql> =
96 params_vec.iter().map(|b| b.as_ref()).collect();
97 let mut stmt = conn.prepare(&sql)?;
98 let rows = stmt.query_map(params_refs.as_slice(), |row| {
99 Ok(AgentHistory {
100 id: row.get(0)?,
101 agent_id: row.get(1)?,
102 session_date: row.get(2)?,
103 entry: row.get(3)?,
104 timestamp: row.get(4)?,
105 })
106 })?;
107 let mut entries = Vec::new();
108 for row in rows {
109 entries.push(row?);
110 }
111 Ok(entries)
112 }
113
114 pub fn agent_history_search(
116 &self,
117 agent_id: &str,
118 query: &str,
119 limit: i64,
120 ) -> Result<Vec<AgentHistory>, StoreError> {
121 let conn = self.conn.lock().unwrap();
122 let pattern = format!("%{}%", query);
123 let mut stmt = conn.prepare(
124 "SELECT id, agent_id, session_date, entry, timestamp
125 FROM db_agent_history WHERE agent_id=?1 AND entry LIKE ?2
126 ORDER BY timestamp DESC LIMIT ?3",
127 )?;
128 let rows = stmt.query_map(params![agent_id, pattern, limit], |row| {
129 Ok(AgentHistory {
130 id: row.get(0)?,
131 agent_id: row.get(1)?,
132 session_date: row.get(2)?,
133 entry: row.get(3)?,
134 timestamp: row.get(4)?,
135 })
136 })?;
137 let mut entries = Vec::new();
138 for row in rows {
139 entries.push(row?);
140 }
141 Ok(entries)
142 }
143}
144
145fn format_epoch_date(days_since_epoch: u64) -> String {
148 let z = days_since_epoch + 719468;
150 let era = z / 146097;
151 let doe = z - era * 146097;
152 let yoe = (doe - doe / 1460 + doe / 36524 - doe / 146096) / 365;
153 let y = yoe + era * 400;
154 let doy = doe - (365 * yoe + yoe / 4 - yoe / 100);
155 let mp = (5 * doy + 2) / 153;
156 let d = doy - (153 * mp + 2) / 5 + 1;
157 let m = if mp < 10 { mp + 3 } else { mp - 9 };
158 let y = if m <= 2 { y + 1 } else { y };
159 format!("{:04}-{:02}-{:02}", y, m, d)
160}