diff --git a/Cargo.lock b/Cargo.lock index 3fc1f4c..82eda3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2892,9 +2892,9 @@ dependencies = [ [[package]] name = "k-ap" -version = "0.4.1" +version = "0.4.2" source = "sparse+https://git.gabrielkaszewski.dev/api/packages/GKaszewski/cargo/" -checksum = "03e39c04075b39960c329feba896a16aba37f0863669c28e7106b7cc45a9988d" +checksum = "4291cac43b119cce0be6e2ba8d85339f3f4c69b266cb7c00fb4cb179302b97e4" dependencies = [ "activitypub_federation", "anyhow", @@ -6714,7 +6714,7 @@ version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" dependencies = [ - "windows-sys 0.48.0", + "windows-sys 0.61.2", ] [[package]] diff --git a/crates/adapters/activitypub/Cargo.toml b/crates/adapters/activitypub/Cargo.toml index d11b8e9..2ca85c9 100644 --- a/crates/adapters/activitypub/Cargo.toml +++ b/crates/adapters/activitypub/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2024" [dependencies] -k-ap = { version = "0.4.1", registry = "gitea" } +k-ap = { version = "0.4.2", registry = "gitea" } domain = { workspace = true } axum = { workspace = true } serde = { workspace = true } diff --git a/crates/adapters/activitypub/src/federation_event_bridge.rs b/crates/adapters/activitypub/src/federation_event_bridge.rs index 58d2153..90e0270 100644 --- a/crates/adapters/activitypub/src/federation_event_bridge.rs +++ b/crates/adapters/activitypub/src/federation_event_bridge.rs @@ -46,6 +46,28 @@ impl k_ap::EventPublisher for FederationEventBridge { tracing::warn!(inbox = %inbox, error = %error, "federation delivery failed permanently"); Ok(()) } + FederationEvent::OutboundFollowAccepted { + local_user_id, + remote_actor_url, + outbox_url, + } => { + let identity = domain::value_objects::SocialIdentity::Remote { + actor_url: remote_actor_url, + }; + self.domain_publisher + .publish(&DomainEvent::FollowAccepted { + owner: UserId::from_uuid(local_user_id), + requester: identity, + }) + .await + .map_err(|e| anyhow::anyhow!(e.to_string()))?; + + if let Some(outbox) = outbox_url { + tracing::info!(outbox = %outbox, "importing remote outbox after follow accepted"); + // Handled by FollowBackfillHandler reacting to FollowAccepted + } + Ok(()) + } } } } diff --git a/crates/worker/src/follow_backfill_handler.rs b/crates/worker/src/follow_backfill_handler.rs index 39f0303..9d3ddeb 100644 --- a/crates/worker/src/follow_backfill_handler.rs +++ b/crates/worker/src/follow_backfill_handler.rs @@ -1,9 +1,7 @@ use std::sync::Arc; use async_trait::async_trait; -use domain::{ - errors::DomainError, events::DomainEvent, ports::EventHandler, value_objects::SocialIdentity, -}; +use domain::{errors::DomainError, events::DomainEvent, ports::EventHandler, value_objects::SocialIdentity}; pub struct FollowBackfillHandler { pub ap_service: Arc, @@ -14,10 +12,26 @@ impl EventHandler for FollowBackfillHandler { async fn handle(&self, event: &DomainEvent) -> Result<(), DomainError> { match event { DomainEvent::FollowAccepted { + owner, requester: SocialIdentity::Remote { actor_url }, - .. } => { - tracing::info!(actor = %actor_url, "follow accepted from remote actor"); + tracing::info!(actor = %actor_url, "follow accepted — looking up outbox for import"); + let following = self + .ap_service + .get_following(owner.value()) + .await + .map_err(|e| DomainError::InfrastructureError(e.to_string()))?; + if let Some(actor) = following.iter().find(|a| a.url == *actor_url) { + if let Some(outbox_url) = &actor.outbox_url { + tracing::info!(outbox = %outbox_url, actor = %actor_url, "importing remote outbox"); + self.ap_service + .import_remote_outbox(outbox_url, actor_url) + .await + .map_err(|e| DomainError::InfrastructureError(e.to_string()))?; + } else { + tracing::warn!(actor = %actor_url, "no outbox URL for accepted follow — skipping import"); + } + } Ok(()) } DomainEvent::BackfillFollower {