feat: processed-event ledger, idempotent dispatch

This commit is contained in:
2026-10-26 00:17:28 +01:00
parent fa8e94817a
commit 5681d0c034
29 changed files with 610 additions and 24 deletions

View File

@@ -6,6 +6,7 @@ mod store;
mod store_goal;
mod store_import;
mod store_movie;
mod store_processed_events;
mod store_review;
mod store_session;
mod store_social;

View File

@@ -64,6 +64,7 @@ pub struct InMemoryStore {
pub(super) import_profiles: RwLock<HashMap<Uuid, ImportProfile>>,
pub(super) follows: RwLock<Vec<(Uuid, SocialIdentity, FollowState)>>,
pub(super) blocked: RwLock<Vec<(Uuid, SocialIdentity)>>,
pub(super) processed_events: RwLock<HashMap<(Uuid, String), chrono::NaiveDateTime>>,
}
impl InMemoryStore {
@@ -85,6 +86,7 @@ impl InMemoryStore {
import_profiles: RwLock::default(),
follows: RwLock::default(),
blocked: RwLock::default(),
processed_events: RwLock::default(),
})
}
@@ -122,6 +124,23 @@ impl InMemoryStore {
self.import_sessions.read().unwrap().len()
}
pub fn processed_event_count(&self) -> usize {
self.processed_events.read().unwrap().len()
}
/// Inserts a ledger row with an explicit timestamp (for retention tests).
pub fn seed_processed_event(
&self,
id: &crate::events::EventId,
handler: &str,
processed_at: chrono::NaiveDateTime,
) {
self.processed_events
.write()
.unwrap()
.insert((id.value(), handler.to_owned()), processed_at);
}
pub fn refresh_session_count(&self) -> usize {
self.refresh_sessions.read().unwrap().len()
}

View File

@@ -0,0 +1,33 @@
use async_trait::async_trait;
use crate::{errors::DomainError, events::EventId, ports::ProcessedEventLedger};
use super::InMemoryStore;
#[async_trait]
impl ProcessedEventLedger for InMemoryStore {
async fn has_processed(&self, id: &EventId, handler: &str) -> Result<bool, DomainError> {
Ok(self
.processed_events
.read()
.unwrap()
.contains_key(&(id.value(), handler.to_owned())))
}
/// `(event_id, handler)` is the primary key; a repeat keeps the first row.
async fn record_processed(&self, id: &EventId, handler: &str) -> Result<(), DomainError> {
self.processed_events
.write()
.unwrap()
.entry((id.value(), handler.to_owned()))
.or_insert_with(|| chrono::Utc::now().naive_utc());
Ok(())
}
async fn delete_older_than(&self, cutoff: chrono::NaiveDateTime) -> Result<u64, DomainError> {
let mut rows = self.processed_events.write().unwrap();
let before = rows.len();
rows.retain(|_, at| *at >= cutoff);
Ok((before - rows.len()) as u64)
}
}