use std::sync::Mutex;
use async_trait::async_trait;
use crate::audit::{Audit, NewAudit};
use crate::backend::{AuditQuery, Backend, Order};
use crate::changes::AuditedChanges;
use crate::error::Result;
use crate::id::AuditId;
#[derive(Default)]
pub struct MemoryBackend {
inner: Mutex<State>,
}
#[derive(Default)]
struct State {
next_id: i64,
audits: Vec<Audit>,
}
impl MemoryBackend {
pub fn new() -> Self {
MemoryBackend {
inner: Mutex::new(State {
next_id: 1,
audits: Vec::new(),
}),
}
}
pub fn total(&self) -> usize {
self.inner.lock().unwrap().audits.len()
}
}
fn sort(audits: &mut [Audit], order: Order) {
match order {
Order::VersionAsc => audits.sort_by(|a, b| a.version.cmp(&b.version).then(a.id.cmp(&b.id))),
Order::VersionDesc => {
audits.sort_by(|a, b| b.version.cmp(&a.version).then(b.id.cmp(&a.id)))
}
Order::CreatedAtAsc => {
audits.sort_by(|a, b| a.created_at.cmp(&b.created_at).then(a.id.cmp(&b.id)))
}
Order::CreatedAtDesc => {
audits.sort_by(|a, b| b.created_at.cmp(&a.created_at).then(b.id.cmp(&a.id)))
}
}
}
fn apply_pagination(mut audits: Vec<Audit>, query: &AuditQuery) -> Vec<Audit> {
if let Some(off) = query.offset {
let off = off.max(0) as usize;
audits = audits.into_iter().skip(off).collect();
}
if let Some(lim) = query.limit {
let lim = lim.max(0) as usize;
audits.truncate(lim);
}
audits
}
#[async_trait]
impl Backend for MemoryBackend {
async fn insert(&self, audit: NewAudit) -> Result<Audit> {
let mut state = self.inner.lock().unwrap();
let version = if audit.action == crate::action::Action::Create {
1
} else {
state
.audits
.iter()
.filter(|a| {
a.auditable_type == audit.auditable_type && a.auditable_id == audit.auditable_id
})
.map(|a| a.version)
.max()
.unwrap_or(0)
+ 1
};
let id = state.next_id;
state.next_id += 1;
let persisted = Audit {
id,
auditable_type: audit.auditable_type,
auditable_id: audit.auditable_id,
associated_type: audit.associated_type,
associated_id: audit.associated_id,
user_type: audit.user_type,
user_id: audit.user_id,
username: audit.username,
action: audit.action,
audited_changes: audit.audited_changes,
version,
comment: audit.comment,
remote_address: audit.remote_address,
request_uuid: audit.request_uuid,
created_at: audit.created_at,
};
state.audits.push(persisted.clone());
Ok(persisted)
}
async fn audits_for_auditable(
&self,
auditable_type: &str,
auditable_id: &AuditId,
query: &AuditQuery,
) -> Result<Vec<Audit>> {
let state = self.inner.lock().unwrap();
let mut out: Vec<Audit> = state
.audits
.iter()
.filter(|a| a.auditable_type == auditable_type && &a.auditable_id == auditable_id)
.filter(|a| query.matches(a))
.cloned()
.collect();
sort(&mut out, query.order);
Ok(apply_pagination(out, query))
}
async fn audits_for_associated(
&self,
associated_type: &str,
associated_id: &AuditId,
query: &AuditQuery,
) -> Result<Vec<Audit>> {
let state = self.inner.lock().unwrap();
let mut out: Vec<Audit> = state
.audits
.iter()
.filter(|a| {
a.associated_type.as_deref() == Some(associated_type)
&& a.associated_id.as_ref() == Some(associated_id)
})
.filter(|a| query.matches(a))
.cloned()
.collect();
sort(&mut out, query.order);
Ok(apply_pagination(out, query))
}
async fn own_and_associated_audits(
&self,
auditable_type: &str,
auditable_id: &AuditId,
) -> Result<Vec<Audit>> {
let state = self.inner.lock().unwrap();
let mut out: Vec<Audit> = state
.audits
.iter()
.filter(|a| {
(a.auditable_type == auditable_type && &a.auditable_id == auditable_id)
|| (a.associated_type.as_deref() == Some(auditable_type)
&& a.associated_id.as_ref() == Some(auditable_id))
})
.cloned()
.collect();
sort(&mut out, Order::CreatedAtDesc);
Ok(out)
}
async fn count_for_auditable(
&self,
auditable_type: &str,
auditable_id: &AuditId,
) -> Result<i64> {
let state = self.inner.lock().unwrap();
let n = state
.audits
.iter()
.filter(|a| a.auditable_type == auditable_type && &a.auditable_id == auditable_id)
.count();
Ok(n as i64)
}
async fn find(&self, id: i64) -> Result<Option<Audit>> {
let state = self.inner.lock().unwrap();
Ok(state.audits.iter().find(|a| a.id == id).cloned())
}
async fn combine(
&self,
target_id: i64,
merged_changes: &AuditedChanges,
comment: Option<&str>,
older_ids: &[i64],
) -> Result<()> {
let mut state = self.inner.lock().unwrap();
if let Some(target) = state.audits.iter_mut().find(|a| a.id == target_id) {
target.audited_changes = merged_changes.clone();
target.comment = comment.map(|c| c.to_string());
}
state.audits.retain(|a| !older_ids.contains(&a.id));
Ok(())
}
}