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