Skip to main content

orbok_db/repo/
events.rs

1//! Application event log repository (RFC-002 ยง7.13).
2//!
3//! Log hygiene contract (NFR-014, RFC-015): events must not contain
4//! document body text. Callers pass short messages and optional
5//! pre-redacted JSON details.
6
7use crate::catalog::{Catalog, db_err};
8use orbok_core::{EventId, OrbokResult, now_iso8601};
9use rusqlite::params;
10
11/// Event severity (matches the `app_events.severity` CHECK).
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
13pub enum Severity {
14    Debug,
15    Info,
16    Warning,
17    Error,
18}
19
20impl Severity {
21    pub fn as_str(&self) -> &'static str {
22        match self {
23            Severity::Debug => "debug",
24            Severity::Info => "info",
25            Severity::Warning => "warning",
26            Severity::Error => "error",
27        }
28    }
29}
30
31pub struct EventRepository<'a> {
32    catalog: &'a Catalog,
33}
34
35impl<'a> EventRepository<'a> {
36    pub fn new(catalog: &'a Catalog) -> Self {
37        Self { catalog }
38    }
39
40    /// Append an event. `redacted_details_json` must already be free of
41    /// document contents.
42    pub fn append(
43        &self,
44        event_type: &str,
45        severity: Severity,
46        message: &str,
47        redacted_details_json: Option<&str>,
48    ) -> OrbokResult<()> {
49        let conn = self.catalog.lock();
50        conn.execute(
51            "INSERT INTO app_events (event_id, event_type, severity, message, \
52             redacted_details_json, created_at) VALUES (?1,?2,?3,?4,?5,?6)",
53            params![
54                EventId::generate().as_str(),
55                event_type,
56                severity.as_str(),
57                message,
58                redacted_details_json,
59                now_iso8601(),
60            ],
61        )
62        .map_err(db_err)?;
63        Ok(())
64    }
65
66    /// Most recent events, newest first.
67    pub fn recent(&self, limit: u32) -> OrbokResult<Vec<(String, String, String)>> {
68        let conn = self.catalog.lock();
69        let mut stmt = conn
70            .prepare(
71                "SELECT event_type, severity, message FROM app_events \
72                 ORDER BY created_at DESC LIMIT ?1",
73            )
74            .map_err(db_err)?;
75        let rows = stmt
76            .query_map(params![limit], |row| {
77                Ok((row.get(0)?, row.get(1)?, row.get(2)?))
78            })
79            .map_err(db_err)?;
80        let mut out = Vec::new();
81        for row in rows {
82            out.push(row.map_err(db_err)?);
83        }
84        Ok(out)
85    }
86}