Skip to main content

modo/audit/
record.rs

1use serde::Serialize;
2
3use crate::db::{ColumnMap, FromRow};
4use crate::error::Result;
5
6/// Stored audit event returned by [`AuditRepo`](super::AuditRepo) queries.
7///
8/// All fields are flat — [`ClientInfo`](crate::ip::ClientInfo) is
9/// expanded into `ip`, `user_agent`, `fingerprint` columns.
10#[derive(Debug, Clone, Serialize)]
11pub struct AuditRecord {
12    /// Unique identifier (ULID).
13    pub id: String,
14    /// Who performed the action (user ID, API key, `"system"`, etc.).
15    pub actor: String,
16    /// Dot-delimited action identifier (e.g. `"doc.deleted"`).
17    pub action: String,
18    /// Kind of resource affected (e.g. `"document"`, `"user"`).
19    pub resource_type: String,
20    /// Identifier of the affected resource.
21    pub resource_id: String,
22    /// Arbitrary JSON payload attached at record time; defaults to `{}`.
23    pub metadata: serde_json::Value,
24    /// Client IP address, if provided.
25    pub ip: Option<String>,
26    /// Client user-agent string, if provided.
27    pub user_agent: Option<String>,
28    /// Client fingerprint, if provided.
29    pub fingerprint: Option<String>,
30    /// Tenant identifier for multi-tenant apps.
31    pub tenant_id: Option<String>,
32    /// ISO 8601 timestamp of when the event was recorded.
33    pub created_at: String,
34}
35
36impl FromRow for AuditRecord {
37    fn from_row(row: &libsql::Row) -> Result<Self> {
38        let cols = ColumnMap::from_row(row);
39        let metadata_str: String = cols.get(row, "metadata")?;
40        let metadata: serde_json::Value = serde_json::from_str(&metadata_str).map_err(|e| {
41            crate::error::Error::internal(format!("invalid audit metadata JSON: {e}"))
42        })?;
43
44        Ok(Self {
45            id: cols.get(row, "id")?,
46            actor: cols.get(row, "actor")?,
47            action: cols.get(row, "action")?,
48            resource_type: cols.get(row, "resource_type")?,
49            resource_id: cols.get(row, "resource_id")?,
50            metadata,
51            ip: cols.get(row, "ip")?,
52            user_agent: cols.get(row, "user_agent")?,
53            fingerprint: cols.get(row, "fingerprint")?,
54            tenant_id: cols.get(row, "tenant_id")?,
55            created_at: cols.get(row, "created_at")?,
56        })
57    }
58}