Files
hermes/backend/src/audit/mod.rs
T

88 lines
2.1 KiB
Rust

use std::{collections::HashMap, sync::Arc};
use chrono::{DateTime, Utc};
use tokio::sync::RwLock;
use uuid::Uuid;
#[derive(Clone)]
pub struct AuditStore {
inner: Arc<RwLock<AuditState>>,
}
#[derive(Default)]
struct AuditState {
event_types_by_name: HashMap<String, Uuid>,
events: Vec<AuditEventSnapshot>,
attributes: Vec<AuditEventAttributeSnapshot>,
}
#[derive(Clone)]
#[allow(dead_code)]
struct AuditEventSnapshot {
id: Uuid,
audit_event_type_id: Uuid,
session_id: Option<Uuid>,
user_id: Option<Uuid>,
occurred_at: DateTime<Utc>,
}
#[derive(Clone)]
#[allow(dead_code)]
struct AuditEventAttributeSnapshot {
id: Uuid,
audit_event_id: Uuid,
attribute_key: String,
attribute_value: String,
}
impl AuditStore {
pub fn new() -> Self {
Self {
inner: Arc::new(RwLock::new(AuditState::default())),
}
}
pub async fn record<I, K, V>(
&self,
action: impl Into<String>,
session_id: Option<Uuid>,
user_id: Option<Uuid>,
occurred_at: DateTime<Utc>,
attributes: I,
) where
I: IntoIterator<Item = (K, V)>,
K: Into<String>,
V: Into<String>,
{
let mut state = self.inner.write().await;
let action = action.into();
let event_type_id = *state
.event_types_by_name
.entry(action)
.or_insert_with(Uuid::new_v4);
let audit_event_id = Uuid::new_v4();
state.events.push(AuditEventSnapshot {
id: audit_event_id,
audit_event_type_id: event_type_id,
session_id,
user_id,
occurred_at,
});
for (key, value) in attributes {
state.attributes.push(AuditEventAttributeSnapshot {
id: Uuid::new_v4(),
audit_event_id,
attribute_key: key.into(),
attribute_value: value.into(),
});
}
}
pub async fn counts(&self) -> (usize, usize) {
let state = self.inner.read().await;
(state.events.len(), state.attributes.len())
}
}