From 09bebf7dc94bf94b7b1dca0408f45383fbadf81c Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Fri, 15 May 2026 05:09:44 +0200 Subject: [PATCH] fix: store in_reply_to on remote notes + use full handle in federation component links/actions --- crates/adapters/activitypub/src/handler.rs | 1 + crates/adapters/postgres/src/activitypub.rs | 19 +++++++++++++++++-- .../src/services/federation_event.rs | 1 + crates/domain/src/ports.rs | 1 + crates/domain/src/testing.rs | 1 + .../federation/pending-requests.tsx | 2 +- .../federation/remote-followers.tsx | 2 +- .../federation/remote-following.tsx | 9 +++++---- 8 files changed, 28 insertions(+), 8 deletions(-) diff --git a/crates/adapters/activitypub/src/handler.rs b/crates/adapters/activitypub/src/handler.rs index 63b85de..7c24659 100644 --- a/crates/adapters/activitypub/src/handler.rs +++ b/crates/adapters/activitypub/src/handler.rs @@ -144,6 +144,7 @@ impl ApObjectHandler for ThoughtsObjectHandler { note.sensitive, note.summary, visibility, + note.in_reply_to.as_ref(), ) .await .map_err(|e| anyhow!("{e}")) diff --git a/crates/adapters/postgres/src/activitypub.rs b/crates/adapters/postgres/src/activitypub.rs index 85fc66e..5267192 100644 --- a/crates/adapters/postgres/src/activitypub.rs +++ b/crates/adapters/postgres/src/activitypub.rs @@ -218,11 +218,24 @@ impl ActivityPubRepository for PgActivityPubRepository { sensitive: bool, content_warning: Option, visibility: &str, + in_reply_to: Option<&Url>, ) -> Result<(), DomainError> { let capped: String = content.chars().take(500).collect(); + let (in_reply_to_id, in_reply_to_url) = match in_reply_to { + Some(url) => { + // If the parent is a local thought, extract its UUID for in_reply_to_id. + let local_uuid = url + .path() + .strip_prefix("/thoughts/") + .and_then(|s| s.split('/').next()) + .and_then(|s| uuid::Uuid::parse_str(s).ok()); + (local_uuid, Some(url.as_str().to_string())) + } + None => (None, None), + }; sqlx::query( - "INSERT INTO thoughts(id,user_id,content,ap_id,visibility,sensitive,local,content_warning,created_at) - VALUES($1,$2,$3,$4,$8,$5,false,$6,$7) ON CONFLICT(ap_id) DO NOTHING", + "INSERT INTO thoughts(id,user_id,content,ap_id,visibility,sensitive,local,content_warning,created_at,in_reply_to_id,in_reply_to_url) + VALUES($1,$2,$3,$4,$8,$5,false,$6,$7,$9,$10) ON CONFLICT(ap_id) DO NOTHING", ) .bind(uuid::Uuid::new_v4()) .bind(author_id.as_uuid()) @@ -232,6 +245,8 @@ impl ActivityPubRepository for PgActivityPubRepository { .bind(content_warning) .bind(published) .bind(visibility) + .bind(in_reply_to_id) + .bind(&in_reply_to_url) .execute(&self.pool) .await .map_err(|e| DomainError::Internal(e.to_string())) diff --git a/crates/application/src/services/federation_event.rs b/crates/application/src/services/federation_event.rs index 7d4f9d1..a59847d 100644 --- a/crates/application/src/services/federation_event.rs +++ b/crates/application/src/services/federation_event.rs @@ -173,6 +173,7 @@ impl FederationEventService { note.sensitive, note.content_warning, "public", + None, ) .await; } diff --git a/crates/domain/src/ports.rs b/crates/domain/src/ports.rs index 68f8858..38f754c 100644 --- a/crates/domain/src/ports.rs +++ b/crates/domain/src/ports.rs @@ -391,6 +391,7 @@ pub trait ActivityPubRepository: Send + Sync { sensitive: bool, content_warning: Option, visibility: &str, + in_reply_to: Option<&url::Url>, ) -> Result<(), DomainError>; /// Apply an Update to a previously accepted remote Note. diff --git a/crates/domain/src/testing.rs b/crates/domain/src/testing.rs index 31234ef..f492eac 100644 --- a/crates/domain/src/testing.rs +++ b/crates/domain/src/testing.rs @@ -847,6 +847,7 @@ impl ActivityPubRepository for TestStore { _sensitive: bool, _content_warning: Option, _visibility: &str, + _in_reply_to: Option<&url::Url>, ) -> Result<(), DomainError> { Ok(()) } diff --git a/thoughts-frontend/components/federation/pending-requests.tsx b/thoughts-frontend/components/federation/pending-requests.tsx index 436517b..6c00f0b 100644 --- a/thoughts-frontend/components/federation/pending-requests.tsx +++ b/thoughts-frontend/components/federation/pending-requests.tsx @@ -59,7 +59,7 @@ export function PendingRequests({ compact = false }: Props) { className="flex items-center justify-between gap-3" > (
  • setLoading(false)); }, [token]); - const unfollow = async (handle: string) => { + const unfollow = async (actor: RemoteActor) => { if (!token) return; - setFollowing((prev) => prev.filter((f) => f.handle !== handle)); + const handle = fullFediverseHandle(actor.handle, actor.url); + setFollowing((prev) => prev.filter((f) => f.url !== actor.url)); await unfollowRemoteActor(handle, token).catch(() => { toast.error("Failed to unfollow"); }); @@ -39,7 +40,7 @@ export function RemoteFollowing() { {following.map((actor) => (
  • unfollow(actor.handle)} + onClick={() => unfollow(actor)} > Unfollow