72 lines
1.8 KiB
Rust
72 lines
1.8 KiB
Rust
use redis::AsyncCommands;
|
|
use std::time::Duration;
|
|
use uuid::Uuid;
|
|
|
|
#[derive(Clone)]
|
|
pub struct DeduplicationManager {
|
|
cache: db::cache::AppCache,
|
|
window: Duration,
|
|
}
|
|
|
|
impl DeduplicationManager {
|
|
pub fn new(cache: db::cache::AppCache) -> Self {
|
|
Self {
|
|
cache,
|
|
window: Duration::from_secs(300),
|
|
}
|
|
}
|
|
|
|
pub async fn check_and_mark(
|
|
&self,
|
|
message_id: Uuid,
|
|
room_id: Uuid,
|
|
) -> Result<bool, crate::error::AppTransportError> {
|
|
let key = format!("dedup:{}:{}", room_id, message_id);
|
|
|
|
let mut conn = self
|
|
.cache
|
|
.conn()
|
|
.await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
// Use atomic SET NX EX to prevent race conditions.
|
|
// Returns true if the key was set (not a duplicate), false if it already exists.
|
|
let result: Option<String> = redis::cmd("SET")
|
|
.arg(&key)
|
|
.arg("1")
|
|
.arg("NX")
|
|
.arg("EX")
|
|
.arg(self.window.as_secs())
|
|
.query_async(&mut conn)
|
|
.await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
Ok(result.is_some())
|
|
}
|
|
|
|
pub async fn is_duplicate(
|
|
&self,
|
|
message_id: Uuid,
|
|
room_id: Uuid,
|
|
) -> Result<bool, crate::error::AppTransportError> {
|
|
let key = format!("dedup:{}:{}", room_id, message_id);
|
|
|
|
let mut conn = self
|
|
.cache
|
|
.conn()
|
|
.await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
let exists: bool = conn
|
|
.exists(&key)
|
|
.await
|
|
.map_err(|_| crate::error::AppTransportError::Internal)?;
|
|
|
|
Ok(exists)
|
|
}
|
|
|
|
pub async fn cleanup_expired(&self) -> Result<(), crate::error::AppTransportError> {
|
|
Ok(())
|
|
}
|
|
}
|