use cratestack_core::{CoolError, CoolEventEnvelope, ModelEventKind};
use crate::error::cool_error_from_sqlx;
use crate::sqlx;
#[derive(Debug, Clone)]
pub(crate) struct EventOutboxRow {
pub(crate) event_id: uuid::Uuid,
pub(crate) model: String,
pub(crate) operation: String,
pub(crate) occurred_at: chrono::DateTime<chrono::Utc>,
pub(crate) payload: serde_json::Value,
pub(crate) attempts: i64,
pub(crate) last_error: Option<String>,
}
impl<'r> sqlx::FromRow<'r, sqlx::postgres::PgRow> for EventOutboxRow {
fn from_row(row: &'r sqlx::postgres::PgRow) -> Result<Self, sqlx::Error> {
use sqlx::Row;
Ok(Self {
event_id: row.try_get("event_id")?,
model: row.try_get("model")?,
operation: row.try_get("operation")?,
occurred_at: row.try_get("occurred_at")?,
payload: row.try_get("payload")?,
attempts: row.try_get("attempts")?,
last_error: row.try_get("last_error")?,
})
}
}
impl EventOutboxRow {
pub(crate) fn try_into_envelope(self) -> Result<CoolEventEnvelope, CoolError> {
let _ = self.attempts;
let _ = &self.last_error;
Ok(CoolEventEnvelope {
event_id: self.event_id,
model: self.model,
operation: ModelEventKind::parse(&self.operation)?,
occurred_at: self.occurred_at,
data: self.payload,
})
}
}
pub(crate) async fn ensure_event_outbox_table<'e, E>(executor: E) -> Result<(), CoolError>
where
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
{
sqlx::query(
"CREATE TABLE IF NOT EXISTS cratestack_event_outbox (\
event_id UUID PRIMARY KEY, \
model TEXT NOT NULL, \
operation TEXT NOT NULL, \
occurred_at TIMESTAMPTZ NOT NULL, \
payload JSONB NOT NULL, \
delivered_at TIMESTAMPTZ, \
attempts BIGINT NOT NULL DEFAULT 0, \
last_error TEXT\
)",
)
.execute(executor)
.await
.map_err(cool_error_from_sqlx)?;
Ok(())
}
pub(crate) async fn enqueue_event_outbox<'e, E, T>(
executor: E,
model: &'static str,
operation: ModelEventKind,
data: &T,
) -> Result<(), CoolError>
where
E: sqlx::Executor<'e, Database = sqlx::Postgres>,
T: serde::Serialize,
{
let payload = serde_json::to_value(data)
.map_err(|error| CoolError::Codec(format!("failed to encode event payload: {error}")))?;
sqlx::query(
"INSERT INTO cratestack_event_outbox (event_id, model, operation, occurred_at, payload) \
VALUES ($1, $2, $3, $4, $5)",
)
.bind(uuid::Uuid::new_v4())
.bind(model)
.bind(operation.as_str())
.bind(chrono::Utc::now())
.bind(payload)
.execute(executor)
.await
.map_err(cool_error_from_sqlx)?;
Ok(())
}