use crate::error::Result;
use async_trait::async_trait;
use std::collections::HashSet;
use std::sync::Arc;
use tokio::sync::RwLock;
#[async_trait]
pub trait IdempotencyStore: Send + Sync {
async fn is_processed(&self, event_id: &str) -> Result<bool>;
async fn mark_processed(&self, event_id: String) -> Result<()>;
async fn cleanup_old_entries(&self) -> Result<()> {
Ok(())
}
}
pub struct MemoryIdempotencyStore {
processed: Arc<RwLock<HashSet<String>>>,
}
impl MemoryIdempotencyStore {
pub fn new() -> Self {
Self {
processed: Arc::new(RwLock::new(HashSet::new())),
}
}
}
impl Default for MemoryIdempotencyStore {
fn default() -> Self {
Self::new()
}
}
#[async_trait]
impl IdempotencyStore for MemoryIdempotencyStore {
async fn is_processed(&self, event_id: &str) -> Result<bool> {
let processed = self.processed.read().await;
Ok(processed.contains(event_id))
}
async fn mark_processed(&self, event_id: String) -> Result<()> {
let mut processed = self.processed.write().await;
processed.insert(event_id);
Ok(())
}
}
#[cfg(feature = "database")]
pub struct DatabaseIdempotencyStore {
#[allow(dead_code)]
db: sea_orm::DatabaseConnection,
}
#[cfg(feature = "database")]
impl DatabaseIdempotencyStore {
pub fn new(db: sea_orm::DatabaseConnection) -> Self {
Self { db }
}
}
#[cfg(feature = "database")]
#[async_trait]
impl IdempotencyStore for DatabaseIdempotencyStore {
async fn is_processed(&self, _event_id: &str) -> Result<bool> {
Ok(false)
}
async fn mark_processed(&self, event_id: String) -> Result<()> {
tracing::debug!("Marking event {} as processed", event_id);
Ok(())
}
async fn cleanup_old_entries(&self) -> Result<()> {
tracing::debug!("Cleaning up old webhook events");
Ok(())
}
}