use crate::webhook::{LogFuture, WebhookEvent, WebhookLog};
use rustlavel_core::{Json, Result};
use rustlavel_db::{Database, Schema};
pub async fn schema(schema: &Schema<'_>) -> Result<()> {
schema
.create("payment_webhooks", |t| {
t.id();
t.string("dedup_key").unique();
t.string("gateway");
t.string("kind");
t.string("event_id");
t.text("raw");
t.big_integer("received_at");
})
.await
}
pub async fn drop_schema(schema: &Schema<'_>) -> Result<()> {
schema.drop("payment_webhooks").await
}
#[derive(Clone)]
pub struct DatabaseWebhookLog {
db: Database,
gateway: &'static str,
}
impl DatabaseWebhookLog {
pub fn new(db: Database, gateway: &'static str) -> DatabaseWebhookLog {
DatabaseWebhookLog { db, gateway }
}
}
impl WebhookLog for DatabaseWebhookLog {
fn record<'a>(&'a self, event: &'a WebhookEvent) -> LogFuture<'a, bool> {
Box::pin(async move {
let now = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_or(0, |d| d.as_secs()) as i64;
let inserted = self
.db
.table("payment_webhooks")
.insert_without_id(
&self.db,
&[
("dedup_key", event.dedup_key().as_str().into()),
("gateway", self.gateway.into()),
("kind", event.kind.as_str().into()),
("event_id", event.id.as_str().into()),
("raw", event.raw.to_string().as_str().into()),
("received_at", now.into()),
],
)
.await;
match inserted {
Ok(_) => Ok(true),
Err(error) if is_unique_violation(&error) => Ok(false),
Err(error) => Err(error),
}
})
}
fn raw<'a>(&'a self, dedup_key: &'a str) -> LogFuture<'a, Option<Json>> {
Box::pin(async move {
let row = self
.db
.table("payment_webhooks")
.filter("dedup_key", dedup_key)
.first(&self.db)
.await?;
Ok(row.and_then(|row| row.get::<String>("raw").ok()).and_then(|raw| Json::parse(&raw).ok()))
})
}
fn withdraw<'a>(&'a self, dedup_key: &'a str) -> LogFuture<'a, ()> {
Box::pin(async move {
self.db.table("payment_webhooks").filter("dedup_key", dedup_key).delete(&self.db).await?;
Ok(())
})
}
}
fn is_unique_violation(error: &rustlavel_core::Error) -> bool {
let text = error.to_string().to_ascii_lowercase();
text.contains("duplicate key")
|| text.contains("unique constraint")
|| text.contains("duplicate entry")
|| text.contains("cannot insert duplicate key")
|| text.contains("23505")
}