From 6dbd4dafdc21dec3c101ad9ac4a2f2ef7ca910a2 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Thu, 4 Jun 2026 23:28:58 +0200 Subject: [PATCH] fix: persist note_extensions on AP Update activity on_update was discarding custom fields (posterUrl, movieTitle, etc), so remote notes from movies-diary lost posters after Update delivery. --- Cargo.lock | 1 + crates/adapters/activitypub/src/handler.rs | 4 ++-- crates/adapters/activitypub/src/port.rs | 7 ++++++- crates/adapters/postgres/src/activitypub/mod.rs | 10 ++++++++-- crates/application/Cargo.toml | 5 +++-- crates/application/src/testing.rs | 2 +- crates/presentation/src/testing.rs | 2 +- 7 files changed, 22 insertions(+), 9 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index c8dfa74..23816f6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -280,6 +280,7 @@ dependencies = [ "domain", "futures", "hex", + "serde_json", "sha2", "thiserror 2.0.18", "tokio", diff --git a/crates/adapters/activitypub/src/handler.rs b/crates/adapters/activitypub/src/handler.rs index 3c36a92..c330ee4 100644 --- a/crates/adapters/activitypub/src/handler.rs +++ b/crates/adapters/activitypub/src/handler.rs @@ -210,11 +210,11 @@ impl ApObjectHandler for ThoughtsObjectHandler { let obj_type = object.get("type").and_then(|v| v.as_str()).unwrap_or(""); match obj_type { "Note" | "Article" | "Page" => { - let Some((note, _)) = ThoughtNote::try_from_ap(object) else { + let Some((note, note_extensions)) = ThoughtNote::try_from_ap(object) else { return Ok(()); }; self.repo - .apply_note_update(ap_id.as_str(), ¬e.content) + .apply_note_update(ap_id.as_str(), ¬e.content, note_extensions) .await .map_err(|e| anyhow!("{e}")) } diff --git a/crates/adapters/activitypub/src/port.rs b/crates/adapters/activitypub/src/port.rs index c3f86cc..909da62 100644 --- a/crates/adapters/activitypub/src/port.rs +++ b/crates/adapters/activitypub/src/port.rs @@ -76,7 +76,12 @@ pub trait ActivityPubRepository: Send + Sync { async fn accept_note(&self, input: AcceptNoteInput<'_>) -> Result; /// Apply an Update to a previously accepted remote Note. - async fn apply_note_update(&self, ap_id: &str, new_content: &str) -> Result<(), DomainError>; + async fn apply_note_update( + &self, + ap_id: &str, + new_content: &str, + note_extensions: Option, + ) -> Result<(), DomainError>; /// Remove a specific remote Note (Delete activity). Only touches /// remotely-originated thoughts. diff --git a/crates/adapters/postgres/src/activitypub/mod.rs b/crates/adapters/postgres/src/activitypub/mod.rs index cbb7fe8..0dad77c 100644 --- a/crates/adapters/postgres/src/activitypub/mod.rs +++ b/crates/adapters/postgres/src/activitypub/mod.rs @@ -270,13 +270,19 @@ impl ActivityPubRepository for PgActivityPubRepository { Ok(ThoughtId::from_uuid(row.0)) } - async fn apply_note_update(&self, ap_id: &str, new_content: &str) -> Result<(), DomainError> { + async fn apply_note_update( + &self, + ap_id: &str, + new_content: &str, + note_extensions: Option, + ) -> Result<(), DomainError> { let capped: String = new_content.chars().take(MAX_REMOTE_CONTENT_CHARS).collect(); sqlx::query( - "UPDATE thoughts SET content=$2,updated_at=NOW() WHERE ap_id=$1 AND local=false", + "UPDATE thoughts SET content=$2,note_extensions=$3,updated_at=NOW() WHERE ap_id=$1 AND local=false", ) .bind(ap_id) .bind(&capped) + .bind(¬e_extensions) .execute(&self.pool) .await .into_domain() diff --git a/crates/application/Cargo.toml b/crates/application/Cargo.toml index 21efe37..8320bb1 100644 --- a/crates/application/Cargo.toml +++ b/crates/application/Cargo.toml @@ -19,5 +19,6 @@ bytes = { workspace = true } futures = { workspace = true } [dev-dependencies] -tokio = { workspace = true, features = ["full"] } -domain = { workspace = true, features = ["test-helpers"] } +tokio = { workspace = true, features = ["full"] } +domain = { workspace = true, features = ["test-helpers"] } +serde_json = { workspace = true } diff --git a/crates/application/src/testing.rs b/crates/application/src/testing.rs index a252a82..ecae317 100644 --- a/crates/application/src/testing.rs +++ b/crates/application/src/testing.rs @@ -90,7 +90,7 @@ impl ActivityPubRepository for TestApRepo { ) -> Result { Ok(ThoughtId::from_uuid(uuid::Uuid::new_v4())) } - async fn apply_note_update(&self, _ap_id: &str, _new_content: &str) -> Result<(), DomainError> { + async fn apply_note_update(&self, _ap_id: &str, _new_content: &str, _: Option) -> Result<(), DomainError> { Ok(()) } async fn retract_note(&self, _ap_id: &str) -> Result<(), DomainError> { diff --git a/crates/presentation/src/testing.rs b/crates/presentation/src/testing.rs index 344956d..82be4fe 100644 --- a/crates/presentation/src/testing.rs +++ b/crates/presentation/src/testing.rs @@ -66,7 +66,7 @@ impl ActivityPubRepository for NoOpApRepo { ) -> Result { Ok(ThoughtId::from_uuid(uuid::Uuid::new_v4())) } - async fn apply_note_update(&self, _: &str, _: &str) -> Result<(), DomainError> { + async fn apply_note_update(&self, _: &str, _: &str, _: Option) -> Result<(), DomainError> { Ok(()) } async fn retract_note(&self, _: &str) -> Result<(), DomainError> {