keepsake_sqlx/repository/
audit.rs1use keepsake::{AuditEvent, CommandContext};
2use sqlx::{Postgres, Transaction};
3
4use super::{KeepsakeRepository, RelationCache, RepositoryResult};
5
6impl<C> KeepsakeRepository<C>
7where
8 C: RelationCache,
9{
10 pub async fn append_audit_event(&self, event: &AuditEvent) -> RepositoryResult<i64> {
16 event.subject.validate()?;
17 event.actor.validate()?;
18
19 let mut tx = self.pool.begin().await?;
20 let audit_event_id = record_audit_event_tx(&mut tx, event).await?;
21 tx.commit().await?;
22 Ok(audit_event_id)
23 }
24}
25
26pub(super) async fn record_audit_event_tx(
27 tx: &mut Transaction<'_, Postgres>,
28 event: &AuditEvent,
29) -> RepositoryResult<i64> {
30 let decision = serde_json::to_value(&event.decision)?;
31 let audit_event_id = sqlx::query_scalar::<_, i64>(
32 r"
33 insert into keepsake_audit_events
34 (keepsake_id, relation_id, subject_kind, subject_id, actor_kind, actor_id,
35 event_type, decision, occurred_at)
36 values ($1, $2, $3, $4, $5, $6, $7, $8, $9)
37 returning id
38 ",
39 )
40 .bind(event.keepsake_id)
41 .bind(event.relation_id)
42 .bind(&event.subject.kind)
43 .bind(&event.subject.id)
44 .bind(&event.actor.kind)
45 .bind(&event.actor.id)
46 .bind(event.event_type.as_str())
47 .bind(decision)
48 .bind(event.at)
49 .fetch_one(&mut **tx)
50 .await?;
51
52 for (key, value) in &event.context.attributes {
53 sqlx::query(
54 r"
55 insert into keepsake_audit_context_attributes
56 (audit_event_id, key, value)
57 values ($1, $2, $3)
58 ",
59 )
60 .bind(audit_event_id)
61 .bind(key)
62 .bind(value)
63 .execute(&mut **tx)
64 .await?;
65 }
66
67 Ok(audit_event_id)
68}
69
70pub(super) fn audit_context_from_command(context: &CommandContext) -> keepsake::AuditContext {
71 let mut attributes = context.metadata.clone();
72 if let Some(idempotency_key) = &context.idempotency_key {
73 attributes
74 .entry("idempotency_key".to_owned())
75 .or_insert_with(|| idempotency_key.clone());
76 }
77 keepsake::AuditContext { attributes }
78}