use std::{collections::HashMap, sync::Arc}; use chrono::{DateTime, Utc}; use tokio::sync::RwLock; use uuid::Uuid; #[derive(Clone)] pub struct AuditStore { inner: Arc>, } #[derive(Default)] struct AuditState { event_types_by_name: HashMap, events: Vec, attributes: Vec, } #[derive(Clone)] #[allow(dead_code)] struct AuditEventSnapshot { id: Uuid, audit_event_type_id: Uuid, session_id: Option, user_id: Option, occurred_at: DateTime, } #[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( &self, action: impl Into, session_id: Option, user_id: Option, occurred_at: DateTime, attributes: I, ) where I: IntoIterator, K: Into, V: Into, { 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()) } }