Skip to main content

modo/audit/
repo.rs

1use std::sync::Arc;
2
3use crate::db::{ConnExt, CursorPage, CursorRequest, Database, ValidatedFilter};
4use crate::error::Result;
5
6use super::record::AuditRecord;
7
8const COLS: &str = "id, actor, action, resource_type, resource_id, metadata, \
9                    ip, user_agent, fingerprint, tenant_id, created_at";
10
11/// Query interface for audit log records.
12///
13/// All methods use cursor pagination (keyset on the `id` column, newest
14/// first). For filtered queries by actor, resource, tenant, or action,
15/// use [`query()`](Self::query) with a [`ValidatedFilter`].
16#[derive(Clone)]
17pub struct AuditRepo {
18    inner: Arc<AuditRepoInner>,
19}
20
21struct AuditRepoInner {
22    db: Database,
23}
24
25impl AuditRepo {
26    /// Create a new audit repo backed by the `audit_log` table.
27    pub fn new(db: Database) -> Self {
28        Self {
29            inner: Arc::new(AuditRepoInner { db }),
30        }
31    }
32
33    /// All entries, newest first.
34    ///
35    /// # Errors
36    ///
37    /// Returns an error if the database query fails.
38    pub async fn list(&self, req: CursorRequest) -> Result<CursorPage<AuditRecord>> {
39        self.inner
40            .db
41            .conn()
42            .select(&format!("SELECT {COLS} FROM audit_log"))
43            .cursor::<AuditRecord>(req)
44            .await
45    }
46
47    /// Flexible query with a pre-validated filter.
48    ///
49    /// # Errors
50    ///
51    /// Returns an error if the database query fails.
52    pub async fn query(
53        &self,
54        filter: ValidatedFilter,
55        req: CursorRequest,
56    ) -> Result<CursorPage<AuditRecord>> {
57        self.inner
58            .db
59            .conn()
60            .select(&format!("SELECT {COLS} FROM audit_log"))
61            .filter(filter)
62            .cursor::<AuditRecord>(req)
63            .await
64    }
65}