use auditlog::{Actor, AuditId, AuditOptions, Auditable, SqlxBackend, ValueMap, as_user};
use serde_json::json;
#[derive(Clone)]
struct Post {
id: i64,
title: String,
body: String,
secret_token: String,
}
impl Auditable for Post {
fn auditable_type() -> &'static str {
"Post"
}
fn auditable_id(&self) -> AuditId {
self.id.into()
}
fn audited_attributes(&self) -> ValueMap {
let mut m = ValueMap::new();
m.insert("id".into(), json!(self.id));
m.insert("title".into(), json!(self.title));
m.insert("body".into(), json!(self.body));
m.insert("secret_token".into(), json!(self.secret_token));
m
}
fn audit_options() -> AuditOptions {
AuditOptions::builder().redacted(["secret_token"]).build()
}
}
#[tokio::main]
async fn main() -> auditlog::Result<()> {
let backend = SqlxBackend::connect_sqlite("sqlite::memory:").await?;
backend.migrate().await?;
let mut post = Post {
id: 1,
title: "Hello".into(),
body: "First draft.".into(),
secret_token: "tok_abc".into(),
};
as_user(Actor::record("User", 42), async {
post.audited_create(&backend).await
})
.await?;
let previous = post.clone();
post.title = "Hello, world".into();
post.secret_token = "tok_xyz".into();
as_user(Actor::name("editor-bot"), async {
post.audited_update_with_comment(&backend, &previous, "fixed the title")
.await
})
.await?;
post.audited_destroy(&backend).await?;
println!("Audit trail for Post #1:");
for audit in Post::audits(&backend, 1).await? {
let who = match audit.user() {
Some(Actor::Record { user_type, user_id }) => format!("{user_type}#{user_id}"),
Some(Actor::Name(name)) => name,
None => "<system>".into(),
};
println!(
" v{} {:<7} by {:<10} changes={} {}",
audit.version,
audit.action.to_string(),
who,
serde_json::to_string(&audit.audited_changes).unwrap(),
audit.comment.map(|c| format!("// {c}")).unwrap_or_default(),
);
}
let v1 = Post::revision(&backend, 1, 1).await?.expect("v1 exists");
println!(
"\nPost #1 at version 1: title = {}",
v1.attributes.get("title").unwrap()
);
let revs = Post::revisions(&backend, 1).await?;
let last = revs.last().unwrap();
println!(
"Latest revision new_record={} (it was destroyed): title = {}",
last.new_record,
last.attributes.get("title").unwrap()
);
Ok(())
}