Files
movies-diary/crates/bootstrap/src/federation.rs

169 lines
6.6 KiB
Rust

//! Social and federation wiring for the server and worker.
//!
//! Federation off still wires real local social behaviour (`LocalSocialService`):
//! the follow graph and block queries must never silently report empty.
use std::sync::Arc;
use domain::ports::{
BlockQuery, EventPublisher, FederatedProfileQuery, FederationAdminQuery, FollowGraphQuery,
RemoteWatchlistRepository, SocialCommand,
};
use domain::value_objects::InstanceIdentity;
use crate::database::DatabaseOutput;
pub struct ServerSocial {
pub ap_router: axum::Router,
#[cfg(feature = "federation")]
pub ap_document: Arc<dyn domain::ports::ApDocumentPort>,
#[cfg(feature = "federation")]
pub ap_blocklist: Arc<dyn domain::ports::InstanceBlocklistPort>,
pub social_command: Arc<dyn SocialCommand>,
pub follow_graph: Arc<dyn FollowGraphQuery>,
pub block_query: Arc<dyn BlockQuery>,
pub remote_watchlist: Arc<dyn RemoteWatchlistRepository>,
pub federation_admin: Arc<dyn FederationAdminQuery>,
pub federated_profile: Option<Arc<dyn FederatedProfileQuery>>,
}
#[cfg(not(feature = "federation"))]
pub async fn wire_server_social(
db: &DatabaseOutput,
instance: &InstanceIdentity,
_config: &config::AppConfig,
_event_publisher: &Arc<dyn EventPublisher>,
) -> anyhow::Result<ServerSocial> {
let local = Arc::new(application::social::local_service::LocalSocialService::new(
Arc::clone(&db.user),
Arc::clone(&db.follow_command),
Arc::clone(&db.follow_query),
instance.clone(),
));
Ok(ServerSocial {
ap_router: axum::Router::new(),
social_command: Arc::clone(&local) as Arc<dyn SocialCommand>,
follow_graph: Arc::clone(&local) as Arc<dyn FollowGraphQuery>,
block_query: local as Arc<dyn BlockQuery>,
remote_watchlist: Arc::new(domain::ports::noop::NoopRemoteWatchlistRepository),
// The empty list this noop returns is an invariant, not an approximation:
// federation-off, `LocalSocialService::follow_resolved` hard-errors on any
// non-Local target and block/unblock always error, so no remote follow can
// ever be persisted through this wiring. (Stale rows from an instance
// previously built federation-ON are the one exception — display staleness
// only; see ADR-0009.)
federation_admin: Arc::new(domain::ports::noop::NoopFederationAdminQuery),
federated_profile: None,
})
}
#[cfg(feature = "federation")]
pub async fn wire_server_social(
db: &DatabaseOutput,
instance: &InstanceIdentity,
config: &config::AppConfig,
event_publisher: &Arc<dyn EventPublisher>,
) -> anyhow::Result<ServerSocial> {
let fed_repos = sqlite_federation::wire(db.db_pool.clone(), instance.clone());
let ap = activitypub::wire(activitypub::ActivityPubDeps {
activity_repo: fed_repos.activity,
follow_repo: fed_repos.follow,
actor_repo: fed_repos.actor,
blocklist_repo: fed_repos.blocklist,
review_store: fed_repos.review_store,
remote_watchlist_repo: fed_repos.remote_watchlist.clone(),
remote_goal_repo: Arc::clone(&db.remote_goal),
local_ap_content: Arc::clone(&db.ap_content),
movie_repo: Arc::clone(&db.movie_query),
review_repo: Arc::clone(&db.review),
diary_repo: Arc::clone(&db.diary),
goal_repo: Arc::clone(&db.goal_query),
stats_repo: Arc::clone(&db.stats),
user_repo: Arc::clone(&db.user),
federation_settings: Arc::clone(&db.federation_settings),
follow_command: Arc::clone(&fed_repos.follow_command),
follow_query: Arc::clone(&fed_repos.follow_query),
instance: instance.clone(),
allow_registration: config.server.allow_registration,
federation: config.federation.clone(),
event_publisher: Arc::clone(event_publisher),
})
.await?;
let local_social: Arc<dyn domain::ports::LocalSocial> =
Arc::new(application::social::local_service::LocalSocialService::new(
Arc::clone(&db.user),
Arc::clone(&db.follow_command),
Arc::clone(&db.follow_query),
instance.clone(),
));
let composite_social = Arc::new(activitypub::CompositeSocialAdapter::new(
local_social,
ap.service,
Arc::clone(&db.user),
instance.clone(),
));
Ok(ServerSocial {
ap_router: ap.router,
ap_document: ap.document,
ap_blocklist: ap.blocklist,
social_command: composite_social.clone() as Arc<dyn SocialCommand>,
follow_graph: composite_social.clone() as Arc<dyn FollowGraphQuery>,
block_query: composite_social as Arc<dyn BlockQuery>,
remote_watchlist: fed_repos.remote_watchlist,
federation_admin: fed_repos.admin_query,
federated_profile: Some(sqlite::create_federated_profile_query(
db.db_pool.clone(),
instance.clone(),
)),
})
}
/// The worker's federation event handlers: the ActivityPub delivery handler and
/// the follow-accepted outbox backfill.
#[cfg(feature = "federation")]
pub async fn wire_worker_handlers(
db: &DatabaseOutput,
instance: &InstanceIdentity,
config: &config::AppConfig,
event_publisher: &Arc<dyn EventPublisher>,
) -> anyhow::Result<Vec<Arc<dyn domain::ports::EventHandler>>> {
let fed_repos = sqlite_federation::wire(db.db_pool.clone(), instance.clone());
let ap_wire = activitypub::wire(activitypub::ActivityPubDeps {
activity_repo: fed_repos.activity,
follow_repo: fed_repos.follow,
actor_repo: fed_repos.actor,
blocklist_repo: fed_repos.blocklist,
review_store: fed_repos.review_store,
remote_watchlist_repo: fed_repos.remote_watchlist,
remote_goal_repo: Arc::clone(&db.remote_goal),
local_ap_content: Arc::clone(&db.ap_content),
movie_repo: Arc::clone(&db.movie_query),
review_repo: Arc::clone(&db.review),
diary_repo: Arc::clone(&db.diary),
goal_repo: Arc::clone(&db.goal_query),
stats_repo: Arc::clone(&db.stats),
user_repo: Arc::clone(&db.user),
follow_command: fed_repos.follow_command,
follow_query: fed_repos.follow_query,
instance: instance.clone(),
allow_registration: config.server.allow_registration,
federation: config.federation.clone(),
event_publisher: Arc::clone(event_publisher),
federation_settings: Arc::clone(&db.federation_settings),
})
.await?;
tracing::info!("federation event handler registered");
Ok(vec![
ap_wire.event_handler,
Arc::new(crate::follow_backfill_handler::FollowBackfillHandler {
backfill: ap_wire.backfill,
}),
])
}