modo-rs 0.8.0

Rust web framework for small monolithic apps
Documentation
use serde::Serialize;

use crate::db::{ColumnMap, FromRow};
use crate::error::Result;

/// Stored audit event returned by [`AuditRepo`](super::AuditRepo) queries.
///
/// All fields are flat — [`ClientInfo`](crate::ip::ClientInfo) is
/// expanded into `ip`, `user_agent`, `fingerprint` columns.
#[derive(Debug, Clone, Serialize)]
pub struct AuditRecord {
    /// Unique identifier (ULID).
    pub id: String,
    /// Who performed the action (user ID, API key, `"system"`, etc.).
    pub actor: String,
    /// Dot-delimited action identifier (e.g. `"doc.deleted"`).
    pub action: String,
    /// Kind of resource affected (e.g. `"document"`, `"user"`).
    pub resource_type: String,
    /// Identifier of the affected resource.
    pub resource_id: String,
    /// Arbitrary JSON payload attached at record time; defaults to `{}`.
    pub metadata: serde_json::Value,
    /// Client IP address, if provided.
    pub ip: Option<String>,
    /// Client user-agent string, if provided.
    pub user_agent: Option<String>,
    /// Client fingerprint, if provided.
    pub fingerprint: Option<String>,
    /// Tenant identifier for multi-tenant apps.
    pub tenant_id: Option<String>,
    /// ISO 8601 timestamp of when the event was recorded.
    pub created_at: String,
}

impl FromRow for AuditRecord {
    fn from_row(row: &libsql::Row) -> Result<Self> {
        let cols = ColumnMap::from_row(row);
        let metadata_str: String = cols.get(row, "metadata")?;
        let metadata: serde_json::Value = serde_json::from_str(&metadata_str).map_err(|e| {
            crate::error::Error::internal(format!("invalid audit metadata JSON: {e}"))
        })?;

        Ok(Self {
            id: cols.get(row, "id")?,
            actor: cols.get(row, "actor")?,
            action: cols.get(row, "action")?,
            resource_type: cols.get(row, "resource_type")?,
            resource_id: cols.get(row, "resource_id")?,
            metadata,
            ip: cols.get(row, "ip")?,
            user_agent: cols.get(row, "user_agent")?,
            fingerprint: cols.get(row, "fingerprint")?,
            tenant_id: cols.get(row, "tenant_id")?,
            created_at: cols.get(row, "created_at")?,
        })
    }
}