use crate::ports::{Database, DbError, Statement};
use sea_query::{Alias, Expr, OnConflict, Query};
fn iden(name: &str) -> Alias {
Alias::new(name)
}
#[derive(Debug, Clone)]
pub struct Inbox {
table: String,
}
impl Inbox {
#[must_use]
pub fn new(table: impl Into<String>) -> Self {
Self {
table: table.into(),
}
}
#[must_use]
pub fn create_table_sql(&self) -> String {
format!(
"CREATE TABLE IF NOT EXISTS {table} (\n \
event_key TEXT PRIMARY KEY,\n \
seen_at TEXT NOT NULL\n);",
table = self.table
)
}
pub async fn claim(
&self,
db: &dyn Database,
event_key: &str,
seen_at: &str,
) -> Result<bool, DbError> {
let mut insert = Query::insert();
insert
.into_table(iden(&self.table))
.columns(["event_key", "seen_at"])
.values_panic([event_key.to_owned().into(), seen_at.to_owned().into()])
.on_conflict(
OnConflict::column(iden("event_key"))
.do_nothing()
.to_owned(),
);
let affected = db.execute(&Statement::render(&insert)).await?;
Ok(affected == 1)
}
pub async fn seen(&self, db: &dyn Database, event_key: &str) -> Result<bool, DbError> {
let mut select = Query::select();
select
.expr(Expr::val(1))
.from(iden(&self.table))
.and_where(Expr::col(iden("event_key")).eq(event_key))
.limit(1);
let rows = db.query(&Statement::render(&select)).await?;
Ok(!rows.is_empty())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_table_sql_is_portable_ddl() {
let sql = Inbox::new("billing_inbox").create_table_sql();
assert!(sql.contains("CREATE TABLE IF NOT EXISTS billing_inbox"));
assert!(sql.contains("event_key TEXT PRIMARY KEY"));
assert!(sql.contains("seen_at TEXT NOT NULL"));
}
#[test]
fn claim_renders_insert_on_conflict_do_nothing() {
let inbox = Inbox::new("billing_inbox");
let mut insert = Query::insert();
insert
.into_table(iden(&inbox.table))
.columns(["event_key", "seen_at"])
.values_panic([
"evt_1".to_owned().into(),
"2026-09-07T00:00:00Z".to_owned().into(),
])
.on_conflict(
OnConflict::column(iden("event_key"))
.do_nothing()
.to_owned(),
);
let stmt = Statement::render(&insert);
assert!(stmt.sql.contains("INSERT INTO"));
assert!(stmt.sql.contains("billing_inbox"));
assert!(stmt.sql.to_uppercase().contains("ON CONFLICT"));
assert!(stmt.sql.to_uppercase().contains("DO NOTHING"));
assert_eq!(stmt.values.0.len(), 2);
}
}