refactor(domain): remove FetchRemoteActorPosts/FetchActorConnections from DomainEvent; add FederationSchedulerPort
This commit is contained in:
@@ -12,9 +12,7 @@ pub struct FederationEventService {
|
||||
pub users: Arc<dyn UserRepository>,
|
||||
pub ap: Arc<dyn OutboundFederationPort>,
|
||||
pub base_url: String,
|
||||
pub federation_action: Arc<dyn domain::ports::FederationActionPort>,
|
||||
pub ap_repo: Arc<dyn ActivityPubRepository>,
|
||||
pub remote_actor_connections: Arc<dyn domain::ports::RemoteActorConnectionRepository>,
|
||||
}
|
||||
|
||||
impl FederationEventService {
|
||||
@@ -148,112 +146,6 @@ impl FederationEventService {
|
||||
.await
|
||||
}
|
||||
|
||||
DomainEvent::FetchRemoteActorPosts {
|
||||
actor_ap_url,
|
||||
outbox_url,
|
||||
} => {
|
||||
let notes = match self
|
||||
.federation_action
|
||||
.fetch_outbox_page(outbox_url, 1)
|
||||
.await
|
||||
{
|
||||
Ok(n) => n,
|
||||
Err(e) => {
|
||||
tracing::warn!(outbox_url, error = %e, "failed to fetch remote outbox");
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
let actor_url = url::Url::parse(actor_ap_url)
|
||||
.map_err(|e| DomainError::ExternalService(e.to_string()))?;
|
||||
|
||||
let author_id = self.ap_repo.intern_remote_actor(&actor_url).await?;
|
||||
|
||||
// Resolve and cache display info so thought cards show proper names.
|
||||
let profiles = self
|
||||
.federation_action
|
||||
.resolve_actor_profiles(vec![actor_ap_url.clone()])
|
||||
.await;
|
||||
if let Some(profile) = profiles.into_iter().next() {
|
||||
let _ = self
|
||||
.ap_repo
|
||||
.update_remote_actor_display(
|
||||
&author_id,
|
||||
profile.display_name.as_deref(),
|
||||
profile.avatar_url.as_deref(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
for note in notes {
|
||||
let ap_id = match url::Url::parse(¬e.ap_id) {
|
||||
Ok(u) => u,
|
||||
Err(_) => continue,
|
||||
};
|
||||
let _ = self
|
||||
.ap_repo
|
||||
.accept_note(
|
||||
&ap_id,
|
||||
&author_id,
|
||||
¬e.content,
|
||||
note.published,
|
||||
note.sensitive,
|
||||
note.content_warning,
|
||||
"public",
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
DomainEvent::FetchActorConnections {
|
||||
actor_ap_url,
|
||||
collection_url,
|
||||
connection_type,
|
||||
page,
|
||||
} => {
|
||||
let urls = match self
|
||||
.federation_action
|
||||
.fetch_actor_urls_from_collection(collection_url)
|
||||
.await
|
||||
{
|
||||
Ok(u) => u,
|
||||
Err(e) => {
|
||||
tracing::warn!(
|
||||
collection_url,
|
||||
error = %e,
|
||||
"failed to fetch actor connections collection"
|
||||
);
|
||||
return Ok(());
|
||||
}
|
||||
};
|
||||
|
||||
if urls.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let summaries = self.federation_action.resolve_actor_profiles(urls).await;
|
||||
|
||||
if summaries.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
tracing::info!(
|
||||
count = summaries.len(),
|
||||
connection_type,
|
||||
actor = actor_ap_url,
|
||||
"caching actor connections"
|
||||
);
|
||||
|
||||
self.remote_actor_connections
|
||||
.upsert_connections(actor_ap_url, connection_type, *page, &summaries)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
DomainEvent::LikeAdded {
|
||||
like_id: _,
|
||||
user_id,
|
||||
@@ -438,9 +330,7 @@ mod tests {
|
||||
users: Arc::new(store.clone()),
|
||||
ap: spy,
|
||||
base_url: "https://example.com".to_string(),
|
||||
federation_action: Arc::new(store.clone()),
|
||||
ap_repo: Arc::new(store.clone()),
|
||||
remote_actor_connections: Arc::new(store.clone()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -772,35 +662,6 @@ mod tests {
|
||||
assert!(spy.updated.lock().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fetch_remote_actor_posts_is_noop_when_outbox_empty() {
|
||||
let store = TestStore::default();
|
||||
let spy = Arc::new(SpyPort::default());
|
||||
svc(&store, spy.clone())
|
||||
.process(&DomainEvent::FetchRemoteActorPosts {
|
||||
actor_ap_url: "https://mastodon.social/users/alice".into(),
|
||||
outbox_url: "https://mastodon.social/users/alice/outbox".into(),
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
// TestStore.fetch_outbox_page returns Ok(vec![]) — no notes, no error
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn fetch_actor_connections_is_noop_when_collection_empty() {
|
||||
let store = TestStore::default();
|
||||
let spy = Arc::new(SpyPort::default());
|
||||
svc(&store, spy.clone())
|
||||
.process(&DomainEvent::FetchActorConnections {
|
||||
actor_ap_url: "https://mastodon.social/users/alice".into(),
|
||||
collection_url: "https://mastodon.social/users/alice/followers".into(),
|
||||
connection_type: "followers".into(),
|
||||
page: 1,
|
||||
})
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn like_added_local_user_remote_thought_broadcasts_like() {
|
||||
let store = TestStore::default();
|
||||
|
||||
@@ -1,14 +1,13 @@
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
events::DomainEvent,
|
||||
models::{
|
||||
actor_connection_summary::ActorConnectionSummary,
|
||||
feed::{FeedEntry, PageParams, Paginated},
|
||||
remote_actor::RemoteActor,
|
||||
},
|
||||
ports::{
|
||||
ActivityPubRepository, EventPublisher, FederationActionPort, FeedRepository,
|
||||
FollowRepository, RemoteActorConnectionRepository, UserRepository,
|
||||
ActivityPubRepository, EventPublisher, FederationActionPort, FederationSchedulerPort,
|
||||
FeedRepository, FollowRepository, RemoteActorConnectionRepository, UserRepository,
|
||||
},
|
||||
value_objects::UserId,
|
||||
};
|
||||
@@ -75,7 +74,7 @@ pub async fn get_remote_actor_posts(
|
||||
federation: &dyn FederationActionPort,
|
||||
ap_repo: &dyn ActivityPubRepository,
|
||||
feed: &dyn FeedRepository,
|
||||
events: &dyn EventPublisher,
|
||||
scheduler: &dyn FederationSchedulerPort,
|
||||
handle: &str,
|
||||
page: PageParams,
|
||||
viewer_id: Option<&UserId>,
|
||||
@@ -88,11 +87,8 @@ pub async fn get_remote_actor_posts(
|
||||
};
|
||||
let result = feed.user_feed(&author_id, &page, viewer_id).await?;
|
||||
if let Some(outbox_url) = actor.outbox_url {
|
||||
let _ = events
|
||||
.publish(&DomainEvent::FetchRemoteActorPosts {
|
||||
actor_ap_url: actor.url,
|
||||
outbox_url,
|
||||
})
|
||||
let _ = scheduler
|
||||
.schedule_actor_posts_fetch(&actor.url, &outbox_url)
|
||||
.await;
|
||||
}
|
||||
Ok(result)
|
||||
@@ -103,7 +99,7 @@ const ACTOR_CONNECTIONS_CACHE_TTL_SECS: i64 = 3600;
|
||||
pub async fn get_actor_connections_page(
|
||||
federation: &dyn FederationActionPort,
|
||||
connections: &dyn RemoteActorConnectionRepository,
|
||||
events: &dyn EventPublisher,
|
||||
scheduler: &dyn FederationSchedulerPort,
|
||||
handle: &str,
|
||||
connection_type: &str,
|
||||
page: u32,
|
||||
@@ -128,13 +124,8 @@ pub async fn get_actor_connections_page(
|
||||
}
|
||||
};
|
||||
if stale {
|
||||
let _ = events
|
||||
.publish(&DomainEvent::FetchActorConnections {
|
||||
actor_ap_url: actor.url,
|
||||
collection_url,
|
||||
connection_type: connection_type.to_string(),
|
||||
page,
|
||||
})
|
||||
let _ = scheduler
|
||||
.schedule_connections_fetch(&actor.url, &collection_url, connection_type, page)
|
||||
.await;
|
||||
}
|
||||
let has_more = items.len() >= PAGE_SIZE;
|
||||
|
||||
Reference in New Issue
Block a user