feat: atomic social local writes, remote review enrichment

SocialCommand methods take events. Local follow ops = one FollowCommand::apply
tx (rows + events). k_ap path (remote, block/unblock): best-effort publish in
CompositeSocialAdapter after k_ap write; not atomic, k_ap owns tx.
save_remote_review takes events, enqueued in same tx via
sqlite::persistence::enqueue_events. Deps drop event_publisher: social
command, ReviewObjectHandler.

Remaining .publish( sites:
announce-only: users/delete_account UserDeleted, person/get + get_credits
PersonEnrichmentRequested, jobs/enrichment_staleness, http-axum search,
ImageStored (sync_poster, poster_sync_handler, image-converter backfill,
update_profile; object storage), federation_event_bridge (handler reaction),
nats relay (transport).
non-atomic by necessity: CompositeSocialAdapter::announce (k_ap-owned write).
Everything else rides its write.
This commit is contained in:
2026-10-26 01:12:58 +01:00
parent 6c295866a0
commit 9a4d0c1714
29 changed files with 1115 additions and 408 deletions

View File

@@ -1,13 +1,17 @@
use activitypub::{RemoteReviewRepository, RemoteReviewUpdate};
use anyhow::{Result, anyhow};
use async_trait::async_trait;
use domain::models::{Review, ReviewSource};
use domain::{
events::DomainEvent,
models::{Review, ReviewSource},
};
use super::SqliteFederationRepository;
use sqlite::persistence::datetime_to_str;
use sqlite::persistence::{datetime_to_str, enqueue_events};
#[async_trait]
impl RemoteReviewRepository for SqliteFederationRepository {
#[allow(clippy::too_many_arguments)]
async fn save_remote_review(
&self,
review: &Review,
@@ -16,6 +20,7 @@ impl RemoteReviewRepository for SqliteFederationRepository {
release_year: u16,
external_metadata_id: Option<&str>,
poster_url: Option<&str>,
events: &[DomainEvent],
) -> Result<()> {
let actor_url = match review.source() {
ReviewSource::Remote { actor_url } => actor_url.clone(),
@@ -24,6 +29,7 @@ impl RemoteReviewRepository for SqliteFederationRepository {
}
};
let movie_id = review.movie_id().value().to_string();
let mut tx = self.pool.begin().await?;
let _ = sqlx::query(
"INSERT INTO movies (id, external_metadata_id, title, release_year, director, poster_path)
VALUES (?, ?, ?, ?, NULL, ?)
@@ -36,7 +42,7 @@ impl RemoteReviewRepository for SqliteFederationRepository {
.bind(movie_title)
.bind(release_year.max(1888) as i64)
.bind(poster_url)
.execute(&self.pool)
.execute(&mut *tx)
.await?;
let id = review.id().value().to_string();
@@ -59,8 +65,10 @@ impl RemoteReviewRepository for SqliteFederationRepository {
.bind(&actor_url)
.bind(ap_id)
.bind(review.spoilers())
.execute(&self.pool)
.execute(&mut *tx)
.await?;
enqueue_events(&mut tx, events).await?;
tx.commit().await?;
Ok(())
}