From 189901b77886789125619a676143d8031a564615 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Fri, 15 May 2026 13:49:58 +0200 Subject: [PATCH] =?UTF-8?q?refactor(ports):=20CQRS=20split=20=E2=80=94=20F?= =?UTF-8?q?ederationActionPort=20into=20four=20focused=20sub-ports?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../adapters/activitypub-base/src/service.rs | 81 ++++++++++--------- .../activitypub-base/src/tests/service.rs | 24 +++++- .../src/use_cases/federation_management.rs | 19 ++--- crates/application/src/use_cases/social.rs | 6 +- crates/domain/src/ports.rs | 55 +++++++++---- crates/domain/src/testing.rs | 65 ++++++++------- 6 files changed, 155 insertions(+), 95 deletions(-) diff --git a/crates/adapters/activitypub-base/src/service.rs b/crates/adapters/activitypub-base/src/service.rs index 802750c..4ca9d58 100644 --- a/crates/adapters/activitypub-base/src/service.rs +++ b/crates/adapters/activitypub-base/src/service.rs @@ -1626,7 +1626,7 @@ impl domain::ports::FederationSchedulerPort for ActivityPubService { } #[async_trait::async_trait] -impl domain::ports::FederationActionPort for ActivityPubService { +impl domain::ports::FederationLookupPort for ActivityPubService { async fn lookup_actor( &self, handle: &str, @@ -1703,31 +1703,6 @@ impl domain::ports::FederationActionPort for ActivityPubService { }) } - async fn follow_remote( - &self, - local_user_id: &domain::value_objects::UserId, - handle: &str, - ) -> Result<(), domain::errors::DomainError> { - self.follow(local_user_id.as_uuid(), handle) - .await - .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) - } - - async fn unfollow_remote( - &self, - local_user_id: &domain::value_objects::UserId, - handle: &str, - ) -> Result<(), domain::errors::DomainError> { - let data = self.federation_config.to_request_data(); - let remote_actor: DbActor = Self::webfinger_https(handle, &data) - .await - .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))?; - let actor_url = remote_actor.ap_id.to_string(); - self.unfollow(local_user_id.as_uuid(), &actor_url) - .await - .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) - } - async fn actor_json( &self, user_id: &domain::value_objects::UserId, @@ -1832,7 +1807,10 @@ impl domain::ports::FederationActionPort for ActivityPubService { serde_json::to_string(&obj) .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) } +} +#[async_trait::async_trait] +impl domain::ports::FederationFetchPort for ActivityPubService { async fn fetch_outbox_page( &self, outbox_url: &str, @@ -2026,7 +2004,48 @@ impl domain::ports::FederationActionPort for ActivityPubService { }) .collect() } +} +#[async_trait::async_trait] +impl domain::ports::FederationFollowPort for ActivityPubService { + async fn follow_remote( + &self, + local_user_id: &domain::value_objects::UserId, + handle: &str, + ) -> Result<(), domain::errors::DomainError> { + self.follow(local_user_id.as_uuid(), handle) + .await + .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) + } + + async fn unfollow_remote( + &self, + local_user_id: &domain::value_objects::UserId, + handle: &str, + ) -> Result<(), domain::errors::DomainError> { + let data = self.federation_config.to_request_data(); + let remote_actor: DbActor = Self::webfinger_https(handle, &data) + .await + .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))?; + let actor_url = remote_actor.ap_id.to_string(); + self.unfollow(local_user_id.as_uuid(), &actor_url) + .await + .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) + } + + async fn get_remote_following( + &self, + user_id: &domain::value_objects::UserId, + ) -> Result, domain::errors::DomainError> { + self.get_following(user_id.as_uuid()) + .await + .map(|v| v.into_iter().map(Self::adapter_actor_to_domain).collect()) + .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) + } +} + +#[async_trait::async_trait] +impl domain::ports::FederationFollowRequestPort for ActivityPubService { async fn get_pending_followers( &self, user_id: &domain::value_objects::UserId, @@ -2076,16 +2095,6 @@ impl domain::ports::FederationActionPort for ActivityPubService { .await .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) } - - async fn get_remote_following( - &self, - user_id: &domain::value_objects::UserId, - ) -> Result, domain::errors::DomainError> { - self.get_following(user_id.as_uuid()) - .await - .map(|v| v.into_iter().map(Self::adapter_actor_to_domain).collect()) - .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) - } } #[cfg(test)] diff --git a/crates/adapters/activitypub-base/src/tests/service.rs b/crates/adapters/activitypub-base/src/tests/service.rs index 3f81776..0f8b034 100644 --- a/crates/adapters/activitypub-base/src/tests/service.rs +++ b/crates/adapters/activitypub-base/src/tests/service.rs @@ -1,10 +1,28 @@ -fn _assert_impl_federation_action_port() +fn _assert_impl_federation_lookup_port() where - crate::service::ActivityPubService: domain::ports::FederationActionPort, + crate::service::ActivityPubService: domain::ports::FederationLookupPort, { } -fn _assert_impl_federation_action_port_connections() +fn _assert_impl_federation_follow_port() +where + crate::service::ActivityPubService: domain::ports::FederationFollowPort, +{ +} + +fn _assert_impl_federation_follow_request_port() +where + crate::service::ActivityPubService: domain::ports::FederationFollowRequestPort, +{ +} + +fn _assert_impl_federation_fetch_port() +where + crate::service::ActivityPubService: domain::ports::FederationFetchPort, +{ +} + +fn _assert_impl_federation_action_port() where crate::service::ActivityPubService: domain::ports::FederationActionPort, { diff --git a/crates/application/src/use_cases/federation_management.rs b/crates/application/src/use_cases/federation_management.rs index fb4193b..6d5432e 100644 --- a/crates/application/src/use_cases/federation_management.rs +++ b/crates/application/src/use_cases/federation_management.rs @@ -6,8 +6,9 @@ use domain::{ remote_actor::RemoteActor, }, ports::{ - ActivityPubRepository, EventPublisher, FederationActionPort, FederationSchedulerPort, - FeedRepository, FollowRepository, RemoteActorConnectionRepository, UserReader, + ActivityPubRepository, EventPublisher, FederationActionPort, FederationFollowPort, + FederationFollowRequestPort, FederationSchedulerPort, FeedRepository, FollowRepository, + RemoteActorConnectionRepository, UserReader, }, value_objects::UserId, }; @@ -15,14 +16,14 @@ use domain::{ use super::social; pub async fn list_pending_requests( - federation: &dyn FederationActionPort, + federation: &dyn FederationFollowRequestPort, user_id: &UserId, ) -> Result, DomainError> { federation.get_pending_followers(user_id).await } pub async fn accept_follow_request( - federation: &dyn FederationActionPort, + federation: &dyn FederationFollowRequestPort, user_id: &UserId, actor_url: &str, ) -> Result<(), DomainError> { @@ -30,7 +31,7 @@ pub async fn accept_follow_request( } pub async fn reject_follow_request( - federation: &dyn FederationActionPort, + federation: &dyn FederationFollowRequestPort, user_id: &UserId, actor_url: &str, ) -> Result<(), DomainError> { @@ -38,14 +39,14 @@ pub async fn reject_follow_request( } pub async fn list_remote_followers( - federation: &dyn FederationActionPort, + federation: &dyn FederationFollowRequestPort, user_id: &UserId, ) -> Result, DomainError> { federation.get_remote_followers(user_id).await } pub async fn remove_remote_follower( - federation: &dyn FederationActionPort, + federation: &dyn FederationFollowRequestPort, user_id: &UserId, actor_url: &str, ) -> Result<(), DomainError> { @@ -53,7 +54,7 @@ pub async fn remove_remote_follower( } pub async fn list_remote_following( - federation: &dyn FederationActionPort, + federation: &dyn FederationFollowPort, user_id: &UserId, ) -> Result, DomainError> { federation.get_remote_following(user_id).await @@ -62,7 +63,7 @@ pub async fn list_remote_following( pub async fn remove_remote_following( follows: &dyn FollowRepository, users: &dyn UserReader, - federation: &dyn FederationActionPort, + federation: &dyn FederationFollowPort, events: &dyn EventPublisher, user_id: &UserId, handle: &str, diff --git a/crates/application/src/use_cases/social.rs b/crates/application/src/use_cases/social.rs index 27663f7..e53bc03 100644 --- a/crates/application/src/use_cases/social.rs +++ b/crates/application/src/use_cases/social.rs @@ -4,7 +4,7 @@ use domain::{ events::DomainEvent, models::social::{Block, Boost, Follow, FollowState, Like}, ports::{ - BlockRepository, BoostRepository, EventPublisher, FederationActionPort, FollowRepository, + BlockRepository, BoostRepository, EventPublisher, FederationFollowPort, FollowRepository, LikeRepository, UserReader, }, value_objects::{BoostId, LikeId, ThoughtId, UserId, Username}, @@ -93,7 +93,7 @@ pub async fn unboost_thought( pub async fn follow_actor( follows: &dyn FollowRepository, users: &dyn UserReader, - federation: &dyn FederationActionPort, + federation: &dyn FederationFollowPort, events: &dyn EventPublisher, follower_id: &UserId, username: &str, @@ -140,7 +140,7 @@ pub async fn follow_user( pub async fn unfollow_actor( follows: &dyn FollowRepository, users: &dyn UserReader, - federation: &dyn FederationActionPort, + federation: &dyn FederationFollowPort, events: &dyn EventPublisher, follower_id: &UserId, username: &str, diff --git a/crates/domain/src/ports.rs b/crates/domain/src/ports.rs index 03b48c6..d24530a 100644 --- a/crates/domain/src/ports.rs +++ b/crates/domain/src/ports.rs @@ -230,14 +230,35 @@ pub trait RemoteActorConnectionRepository: Send + Sync { } #[async_trait] -pub trait FederationActionPort: Send + Sync { +pub trait FederationLookupPort: Send + Sync { async fn lookup_actor(&self, handle: &str) -> Result; + async fn actor_json(&self, user_id: &UserId) -> Result; + async fn followers_collection_json( + &self, + user_id: &UserId, + page: Option, + ) -> Result; + async fn following_collection_json( + &self, + user_id: &UserId, + page: Option, + ) -> Result; +} + +#[async_trait] +pub trait FederationFollowPort: Send + Sync { async fn follow_remote(&self, local_user_id: &UserId, handle: &str) -> Result<(), DomainError>; async fn unfollow_remote( &self, local_user_id: &UserId, handle: &str, ) -> Result<(), DomainError>; + async fn get_remote_following(&self, user_id: &UserId) + -> Result, DomainError>; +} + +#[async_trait] +pub trait FederationFollowRequestPort: Send + Sync { async fn get_pending_followers( &self, user_id: &UserId, @@ -259,36 +280,38 @@ pub trait FederationActionPort: Send + Sync { user_id: &UserId, actor_url: &str, ) -> Result<(), DomainError>; - async fn get_remote_following(&self, user_id: &UserId) - -> Result, DomainError>; - async fn actor_json(&self, user_id: &UserId) -> Result; - async fn followers_collection_json( - &self, - user_id: &UserId, - page: Option, - ) -> Result; - async fn following_collection_json( - &self, - user_id: &UserId, - page: Option, - ) -> Result; +} + +#[async_trait] +pub trait FederationFetchPort: Send + Sync { async fn fetch_outbox_page( &self, outbox_url: &str, page: u32, ) -> Result, DomainError>; - async fn fetch_actor_urls_from_collection( &self, collection_url: &str, ) -> Result, DomainError>; - async fn resolve_actor_profiles( &self, urls: Vec, ) -> Vec; } +pub trait FederationActionPort: + FederationLookupPort + FederationFollowPort + FederationFollowRequestPort + FederationFetchPort +{ +} +impl< + T: FederationLookupPort + + FederationFollowPort + + FederationFollowRequestPort + + FederationFetchPort, + > FederationActionPort for T +{ +} + #[async_trait] pub trait FeedRepository: Send + Sync { async fn home_feed( diff --git a/crates/domain/src/testing.rs b/crates/domain/src/testing.rs index 4aa7e3b..52110d9 100644 --- a/crates/domain/src/testing.rs +++ b/crates/domain/src/testing.rs @@ -555,11 +555,34 @@ impl RemoteActorRepository for TestStore { } #[async_trait] -impl FederationActionPort for TestStore { +impl FederationLookupPort for TestStore { async fn lookup_actor(&self, _handle: &str) -> Result { Err(DomainError::NotFound) } + async fn actor_json(&self, _user_id: &UserId) -> Result { + Err(DomainError::NotFound) + } + + async fn followers_collection_json( + &self, + _user_id: &UserId, + _page: Option, + ) -> Result { + Err(DomainError::NotFound) + } + + async fn following_collection_json( + &self, + _user_id: &UserId, + _page: Option, + ) -> Result { + Err(DomainError::NotFound) + } +} + +#[async_trait] +impl FederationFollowPort for TestStore { async fn follow_remote( &self, _local_user_id: &UserId, @@ -576,6 +599,16 @@ impl FederationActionPort for TestStore { Ok(()) } + async fn get_remote_following( + &self, + _user_id: &UserId, + ) -> Result, DomainError> { + Ok(vec![]) + } +} + +#[async_trait] +impl FederationFollowRequestPort for TestStore { async fn get_pending_followers( &self, _user_id: &UserId, @@ -613,34 +646,10 @@ impl FederationActionPort for TestStore { ) -> Result<(), DomainError> { Ok(()) } +} - async fn get_remote_following( - &self, - _user_id: &UserId, - ) -> Result, DomainError> { - Ok(vec![]) - } - - async fn actor_json(&self, _user_id: &UserId) -> Result { - Err(DomainError::NotFound) - } - - async fn followers_collection_json( - &self, - _user_id: &UserId, - _page: Option, - ) -> Result { - Err(DomainError::NotFound) - } - - async fn following_collection_json( - &self, - _user_id: &UserId, - _page: Option, - ) -> Result { - Err(DomainError::NotFound) - } - +#[async_trait] +impl FederationFetchPort for TestStore { async fn fetch_outbox_page( &self, _outbox_url: &str,