cratestack_sqlx/audit/
schema.rs1use cratestack_core::CoolError;
4
5use crate::sqlx;
6
7pub const AUDIT_TABLE_DDL: &str = r#"
12CREATE TABLE IF NOT EXISTS cratestack_audit (
13 event_id UUID PRIMARY KEY,
14 schema_name TEXT NOT NULL,
15 model TEXT NOT NULL,
16 operation TEXT NOT NULL,
17 primary_key JSONB NOT NULL,
18 actor JSONB NOT NULL,
19 tenant TEXT,
20 before JSONB,
21 after JSONB,
22 request_id TEXT,
23 occurred_at TIMESTAMPTZ NOT NULL,
24 delivered_at TIMESTAMPTZ,
25 attempts BIGINT NOT NULL DEFAULT 0,
26 last_error TEXT
27);
28
29CREATE INDEX IF NOT EXISTS cratestack_audit_model_idx
30 ON cratestack_audit (schema_name, model, occurred_at DESC);
31
32CREATE INDEX IF NOT EXISTS cratestack_audit_tenant_idx
33 ON cratestack_audit (tenant, occurred_at DESC)
34 WHERE tenant IS NOT NULL;
35
36CREATE INDEX IF NOT EXISTS cratestack_audit_undelivered_idx
37 ON cratestack_audit (occurred_at)
38 WHERE delivered_at IS NULL;
39"#;
40
41pub(crate) async fn ensure_audit_table(pool: &sqlx::PgPool) -> Result<(), CoolError> {
42 for statement in AUDIT_TABLE_DDL
47 .split(';')
48 .map(str::trim)
49 .filter(|s| !s.is_empty())
50 {
51 sqlx::query(statement)
52 .execute(pool)
53 .await
54 .map_err(|error| CoolError::Database(error.to_string()))?;
55 }
56 Ok(())
57}