diff --git a/crates/adapters/activitypub-base/src/service.rs b/crates/adapters/activitypub-base/src/service.rs index 93c30ff..d86b6c0 100644 --- a/crates/adapters/activitypub-base/src/service.rs +++ b/crates/adapters/activitypub-base/src/service.rs @@ -1561,6 +1561,15 @@ impl domain::ports::OutboundFederationPort for ActivityPubService { .await .map_err(|e| domain::errors::DomainError::Internal(e.to_string())) } + + async fn broadcast_actor_update( + &self, + user_id: &domain::value_objects::UserId, + ) -> Result<(), domain::errors::DomainError> { + self.broadcast_actor_update(user_id.as_uuid()) + .await + .map_err(|e| domain::errors::DomainError::Internal(e.to_string())) + } } #[async_trait::async_trait] diff --git a/crates/adapters/event-payload/src/lib.rs b/crates/adapters/event-payload/src/lib.rs index ebf758d..17cb713 100644 --- a/crates/adapters/event-payload/src/lib.rs +++ b/crates/adapters/event-payload/src/lib.rs @@ -68,6 +68,9 @@ pub enum EventPayload { UserRegistered { user_id: String, }, + ProfileUpdated { + user_id: String, + }, FetchRemoteActorPosts { actor_ap_url: String, outbox_url: String, @@ -98,6 +101,7 @@ impl EventPayload { Self::UserBlocked { .. } => "users.blocked", Self::UserUnblocked { .. } => "users.unblocked", Self::UserRegistered { .. } => "users.registered", + Self::ProfileUpdated { .. } => "users.profile_updated", Self::FetchRemoteActorPosts { .. } => "federation.fetch_outbox", Self::FetchActorConnections { .. } => "federation.fetch_connections", } @@ -209,6 +213,9 @@ impl From<&DomainEvent> for EventPayload { DomainEvent::UserRegistered { user_id } => Self::UserRegistered { user_id: user_id.to_string(), }, + DomainEvent::ProfileUpdated { user_id } => Self::ProfileUpdated { + user_id: user_id.to_string(), + }, DomainEvent::FetchRemoteActorPosts { actor_ap_url, outbox_url, @@ -345,6 +352,9 @@ impl TryFrom for DomainEvent { EventPayload::UserRegistered { user_id } => DomainEvent::UserRegistered { user_id: UserId::from_uuid(parse_uuid(&user_id, "user_id")?), }, + EventPayload::ProfileUpdated { user_id } => DomainEvent::ProfileUpdated { + user_id: UserId::from_uuid(parse_uuid(&user_id, "user_id")?), + }, EventPayload::FetchRemoteActorPosts { actor_ap_url, outbox_url, diff --git a/crates/application/src/services/federation_event.rs b/crates/application/src/services/federation_event.rs index a59847d..5fe335a 100644 --- a/crates/application/src/services/federation_event.rs +++ b/crates/application/src/services/federation_event.rs @@ -277,6 +277,10 @@ impl FederationEventService { .await } + DomainEvent::ProfileUpdated { user_id } => { + self.ap.broadcast_actor_update(user_id).await + } + _ => Ok(()), } } @@ -308,6 +312,7 @@ mod tests { undo_announced: Mutex>, liked: Mutex>, undo_liked: Mutex>, + actor_updated: Mutex>, } #[async_trait] @@ -366,6 +371,11 @@ mod tests { self.undo_liked.lock().unwrap().push(ap_id.to_string()); Ok(()) } + + async fn broadcast_actor_update(&self, user_id: &UserId) -> Result<(), DomainError> { + self.actor_updated.lock().unwrap().push(user_id.clone()); + Ok(()) + } } fn alice() -> User { diff --git a/crates/application/src/use_cases/profile.rs b/crates/application/src/use_cases/profile.rs index 3b09e44..4ebbc83 100644 --- a/crates/application/src/use_cases/profile.rs +++ b/crates/application/src/use_cases/profile.rs @@ -1,7 +1,8 @@ use domain::{ errors::DomainError, + events::DomainEvent, models::{top_friend::TopFriend, user::User}, - ports::{TopFriendRepository, UserRepository}, + ports::{EventPublisher, TopFriendRepository, UserRepository}, value_objects::{UserId, Username}, }; @@ -38,8 +39,10 @@ pub async fn get_user_by_id_or_username( } } +#[allow(clippy::too_many_arguments)] pub async fn update_profile( users: &dyn UserRepository, + events: &dyn EventPublisher, user_id: &UserId, display_name: Option, bio: Option, @@ -56,6 +59,11 @@ pub async fn update_profile( header_url, custom_css, ) + .await?; + events + .publish(&DomainEvent::ProfileUpdated { + user_id: user_id.clone(), + }) .await } diff --git a/crates/domain/src/events.rs b/crates/domain/src/events.rs index 2e6b879..a91d429 100644 --- a/crates/domain/src/events.rs +++ b/crates/domain/src/events.rs @@ -60,6 +60,9 @@ pub enum DomainEvent { UserRegistered { user_id: UserId, }, + ProfileUpdated { + user_id: UserId, + }, FetchRemoteActorPosts { actor_ap_url: String, outbox_url: String, diff --git a/crates/domain/src/ports.rs b/crates/domain/src/ports.rs index 38f754c..1caf8d5 100644 --- a/crates/domain/src/ports.rs +++ b/crates/domain/src/ports.rs @@ -471,4 +471,7 @@ pub trait OutboundFederationPort: Send + Sync { object_ap_id: &str, author_inbox_url: &str, ) -> Result<(), DomainError>; + + /// Fan out an Update(Actor) to all accepted followers after a profile change. + async fn broadcast_actor_update(&self, user_id: &UserId) -> Result<(), DomainError>; } diff --git a/crates/presentation/src/handlers/users.rs b/crates/presentation/src/handlers/users.rs index 0def2d9..dd073da 100644 --- a/crates/presentation/src/handlers/users.rs +++ b/crates/presentation/src/handlers/users.rs @@ -72,6 +72,7 @@ pub async fn patch_profile( ) -> Result, ApiError> { update_profile( &*s.users, + &*s.events, &uid, body.display_name, body.bio,