From 70fc4fbcd0874975c6b89d5caffad16f69f883ad Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Thu, 14 May 2026 22:08:26 +0200 Subject: [PATCH] feat(domain): RemoteActor fields, RemoteNote, FetchRemoteActorPosts event, fetch_outbox_page port --- .../adapters/activitypub-base/src/service.rs | 15 +++++++++++++++ crates/adapters/event-payload/src/lib.rs | 19 +++++++++++++++++++ crates/adapters/postgres/src/remote_actor.rs | 2 +- crates/domain/src/events.rs | 4 ++++ crates/domain/src/models/mod.rs | 1 + crates/domain/src/models/remote_actor.rs | 5 +++++ crates/domain/src/models/remote_note.rs | 10 ++++++++++ crates/domain/src/ports.rs | 5 +++++ crates/domain/src/testing.rs | 18 ++++++++++++++++++ 9 files changed, 78 insertions(+), 1 deletion(-) create mode 100644 crates/domain/src/models/remote_note.rs diff --git a/crates/adapters/activitypub-base/src/service.rs b/crates/adapters/activitypub-base/src/service.rs index 475ff23..d714397 100644 --- a/crates/adapters/activitypub-base/src/service.rs +++ b/crates/adapters/activitypub-base/src/service.rs @@ -1394,6 +1394,11 @@ impl domain::ports::FederationActionPort for ActivityPubService { public_key: actor.public_key_pem.clone(), avatar_url: actor.avatar_url.as_ref().map(|u| u.to_string()), last_fetched_at: actor.last_refreshed_at, + bio: None, + banner_url: None, + also_known_as: None, + outbox_url: None, + attachment: vec![], }) } @@ -1511,6 +1516,16 @@ impl domain::ports::FederationActionPort for ActivityPubService { serde_json::to_string(&obj) .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) } + + async fn fetch_outbox_page( + &self, + _outbox_url: &str, + _page: u32, + ) -> Result, domain::errors::DomainError> { + Err(domain::errors::DomainError::Internal( + "not implemented".into(), + )) + } } #[cfg(test)] diff --git a/crates/adapters/event-payload/src/lib.rs b/crates/adapters/event-payload/src/lib.rs index 98bd2ed..cf7f750 100644 --- a/crates/adapters/event-payload/src/lib.rs +++ b/crates/adapters/event-payload/src/lib.rs @@ -68,6 +68,10 @@ pub enum EventPayload { UserRegistered { user_id: String, }, + FetchRemoteActorPosts { + actor_ap_url: String, + outbox_url: String, + }, } impl EventPayload { @@ -88,6 +92,7 @@ impl EventPayload { Self::UserBlocked { .. } => "users.blocked", Self::UserUnblocked { .. } => "users.unblocked", Self::UserRegistered { .. } => "users.registered", + Self::FetchRemoteActorPosts { .. } => "federation.fetch_outbox", } } } @@ -197,6 +202,13 @@ impl From<&DomainEvent> for EventPayload { DomainEvent::UserRegistered { user_id } => Self::UserRegistered { user_id: user_id.to_string(), }, + DomainEvent::FetchRemoteActorPosts { + actor_ap_url, + outbox_url, + } => Self::FetchRemoteActorPosts { + actor_ap_url: actor_ap_url.clone(), + outbox_url: outbox_url.clone(), + }, } } } @@ -315,6 +327,13 @@ impl TryFrom for DomainEvent { EventPayload::UserRegistered { user_id } => DomainEvent::UserRegistered { user_id: UserId::from_uuid(parse_uuid(&user_id, "user_id")?), }, + EventPayload::FetchRemoteActorPosts { + actor_ap_url, + outbox_url, + } => DomainEvent::FetchRemoteActorPosts { + actor_ap_url, + outbox_url, + }, }) } } diff --git a/crates/adapters/postgres/src/remote_actor.rs b/crates/adapters/postgres/src/remote_actor.rs index 36fddf3..d94fbb5 100644 --- a/crates/adapters/postgres/src/remote_actor.rs +++ b/crates/adapters/postgres/src/remote_actor.rs @@ -45,6 +45,6 @@ impl RemoteActorRepository for PgRemoteActorRepository { "SELECT url,handle,display_name,inbox_url,shared_inbox_url,public_key,avatar_url,last_fetched_at FROM remote_actors WHERE url=$1" ).bind(url).fetch_optional(&self.pool).await .map_err(|e| DomainError::Internal(e.to_string())) - .map(|o| o.map(|r| RemoteActor { url: r.url, handle: r.handle, display_name: r.display_name, inbox_url: r.inbox_url, shared_inbox_url: r.shared_inbox_url, public_key: r.public_key, avatar_url: r.avatar_url, last_fetched_at: r.last_fetched_at })) + .map(|o| o.map(|r| RemoteActor { url: r.url, handle: r.handle, display_name: r.display_name, inbox_url: r.inbox_url, shared_inbox_url: r.shared_inbox_url, public_key: r.public_key, avatar_url: r.avatar_url, last_fetched_at: r.last_fetched_at, bio: None, banner_url: None, also_known_as: None, outbox_url: None, attachment: vec![] })) } } diff --git a/crates/domain/src/events.rs b/crates/domain/src/events.rs index 09970c2..e7ef3eb 100644 --- a/crates/domain/src/events.rs +++ b/crates/domain/src/events.rs @@ -60,6 +60,10 @@ pub enum DomainEvent { UserRegistered { user_id: UserId, }, + FetchRemoteActorPosts { + actor_ap_url: String, + outbox_url: String, + }, } pub struct EventEnvelope { diff --git a/crates/domain/src/models/mod.rs b/crates/domain/src/models/mod.rs index bb56c47..3588235 100644 --- a/crates/domain/src/models/mod.rs +++ b/crates/domain/src/models/mod.rs @@ -2,6 +2,7 @@ pub mod api_key; pub mod feed; pub mod notification; pub mod remote_actor; +pub mod remote_note; pub mod social; pub mod tag; pub mod thought; diff --git a/crates/domain/src/models/remote_actor.rs b/crates/domain/src/models/remote_actor.rs index 07c2fb6..9ed6919 100644 --- a/crates/domain/src/models/remote_actor.rs +++ b/crates/domain/src/models/remote_actor.rs @@ -10,4 +10,9 @@ pub struct RemoteActor { pub public_key: String, pub avatar_url: Option, pub last_fetched_at: DateTime, + pub bio: Option, + pub banner_url: Option, + pub also_known_as: Option, + pub outbox_url: Option, + pub attachment: Vec<(String, String)>, } diff --git a/crates/domain/src/models/remote_note.rs b/crates/domain/src/models/remote_note.rs new file mode 100644 index 0000000..279d342 --- /dev/null +++ b/crates/domain/src/models/remote_note.rs @@ -0,0 +1,10 @@ +use chrono::{DateTime, Utc}; + +#[derive(Debug, Clone)] +pub struct RemoteNote { + pub ap_id: String, + pub content: String, + pub published: DateTime, + pub sensitive: bool, + pub content_warning: Option, +} diff --git a/crates/domain/src/ports.rs b/crates/domain/src/ports.rs index ab8b3cc..15f3831 100644 --- a/crates/domain/src/ports.rs +++ b/crates/domain/src/ports.rs @@ -209,6 +209,11 @@ pub trait FederationActionPort: Send + Sync { user_id: &UserId, page: Option, ) -> Result; + async fn fetch_outbox_page( + &self, + outbox_url: &str, + page: u32, + ) -> Result, DomainError>; } #[async_trait] diff --git a/crates/domain/src/testing.rs b/crates/domain/src/testing.rs index 0b13746..4fb97c8 100644 --- a/crates/domain/src/testing.rs +++ b/crates/domain/src/testing.rs @@ -567,6 +567,14 @@ impl FederationActionPort for TestStore { ) -> Result { Err(DomainError::NotFound) } + + async fn fetch_outbox_page( + &self, + _outbox_url: &str, + _page: u32, + ) -> Result, DomainError> { + Ok(vec![]) + } } #[async_trait] @@ -833,6 +841,16 @@ mod federation_port_tests { let err = store.actor_json(&UserId::new()).await.unwrap_err(); assert!(matches!(err, DomainError::NotFound)); } + + #[tokio::test] + async fn test_store_fetch_outbox_returns_empty() { + let store = TestStore::default(); + let notes = store + .fetch_outbox_page("https://example.com/outbox", 1) + .await + .unwrap(); + assert!(notes.is_empty()); + } } #[cfg(test)]