Skip to main content

mxr_store/
event_log.rs

1use mxr_core::AccountId;
2use sqlx::Row;
3
4pub struct EventLogEntry {
5    pub id: i64,
6    pub timestamp: i64,
7    pub level: String,
8    pub category: String,
9    pub account_id: Option<AccountId>,
10    pub message_id: Option<String>,
11    pub rule_id: Option<String>,
12    pub summary: String,
13    pub details: Option<String>,
14}
15
16impl super::Store {
17    pub async fn insert_event_refs(
18        &self,
19        level: &str,
20        category: &str,
21        summary: &str,
22        account_id: Option<&AccountId>,
23        message_id: Option<&str>,
24        rule_id: Option<&str>,
25        details: Option<&str>,
26    ) -> Result<(), sqlx::Error> {
27        let now = chrono::Utc::now().timestamp();
28        let aid = account_id.map(|a| a.as_str());
29        sqlx::query(
30            "INSERT INTO event_log (
31                timestamp,
32                level,
33                category,
34                account_id,
35                message_id,
36                rule_id,
37                summary,
38                details
39             ) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
40        )
41        .bind(now)
42        .bind(level)
43        .bind(category)
44        .bind(aid)
45        .bind(message_id)
46        .bind(rule_id)
47        .bind(summary)
48        .bind(details)
49        .execute(self.writer())
50        .await?;
51        Ok(())
52    }
53
54    pub async fn insert_event(
55        &self,
56        level: &str,
57        category: &str,
58        summary: &str,
59        account_id: Option<&AccountId>,
60        details: Option<&str>,
61    ) -> Result<(), sqlx::Error> {
62        self.insert_event_refs(level, category, summary, account_id, None, None, details)
63            .await
64    }
65
66    // Dynamic SQL — kept as runtime query since the WHERE clause is conditionally built
67    pub async fn list_events(
68        &self,
69        limit: u32,
70        level: Option<&str>,
71        category: Option<&str>,
72    ) -> Result<Vec<EventLogEntry>, sqlx::Error> {
73        let mut sql = String::from("SELECT * FROM event_log WHERE 1=1");
74        if level.is_some() {
75            sql.push_str(" AND level = ?");
76        }
77        if category.is_some() {
78            sql.push_str(" AND category = ?");
79        }
80        sql.push_str(" ORDER BY timestamp DESC LIMIT ?");
81
82        let mut query = sqlx::query(&sql);
83        if let Some(l) = level {
84            query = query.bind(l);
85        }
86        if let Some(c) = category {
87            query = query.bind(c);
88        }
89        query = query.bind(limit);
90
91        let rows = query.fetch_all(self.reader()).await?;
92
93        Ok(rows
94            .iter()
95            .map(|r| {
96                let aid: Option<String> = r.get("account_id");
97                EventLogEntry {
98                    id: r.get("id"),
99                    timestamp: r.get("timestamp"),
100                    level: r.get("level"),
101                    category: r.get("category"),
102                    account_id: aid
103                        .map(|s| AccountId::from_uuid(uuid::Uuid::parse_str(&s).unwrap())),
104                    message_id: r.get("message_id"),
105                    rule_id: r.get("rule_id"),
106                    summary: r.get("summary"),
107                    details: r.get("details"),
108                }
109            })
110            .collect())
111    }
112
113    pub async fn prune_events_before(&self, cutoff_timestamp: i64) -> Result<u64, sqlx::Error> {
114        let result = sqlx::query("DELETE FROM event_log WHERE timestamp < ?")
115            .bind(cutoff_timestamp)
116            .execute(self.writer())
117            .await?;
118        Ok(result.rows_affected())
119    }
120}