Files
k-ap/src/data.rs
Gabriel Kaszewski 7171a1791a feat: actor cache TTL with staleness-aware re-fetch
Adds fetched_at to RemoteActor, configurable TTL via builder
(.actor_cache_ttl_secs, default 24h), and get_or_refresh_remote_actor
helper that re-fetches stale actors from origin.

Closes #3
2026-05-30 02:46:54 +02:00

109 lines
3.7 KiB
Rust

use std::sync::Arc;
use crate::content::{ApContentReader, ApObjectHandler};
use crate::repository::{
ActivityRepository, ActorRepository, BlocklistRepository, FollowRepository,
};
use crate::user::ApUserRepository;
/// Typed event emitted by the federation layer.
///
/// **Delivery:** When an [`EventPublisher`] is configured, outbound activities
/// are published as [`FederationEvent::DeliveryRequested`] instead of being sent
/// directly. Process them by calling
/// [`crate::service::ActivityPubService::deliver_to_inbox`].
///
/// **Backfill:** When a follower is accepted and an [`EventPublisher`] is
/// configured, [`FederationEvent::BackfillRequested`] is published instead of
/// spawning an in-process task. Process it by calling
/// [`crate::service::ActivityPubService::run_backfill_for_follower`].
///
/// Without a publisher, both fall back to `tokio::spawn`.
#[derive(Debug, Clone)]
pub enum FederationEvent {
/// An outbound activity must be delivered to `inbox`.
/// Call `ActivityPubService::deliver_to_inbox(inbox, activity, signing_actor_id)`.
DeliveryRequested {
inbox: url::Url,
activity: serde_json::Value,
signing_actor_id: uuid::Uuid,
},
/// Delivery to `inbox` failed permanently after all in-process retries.
DeliveryFailed {
inbox: url::Url,
activity: serde_json::Value,
signing_actor_id: uuid::Uuid,
error: String,
},
/// A new follower was accepted and their inbox needs backfilling.
/// Call `ActivityPubService::run_backfill_for_follower(owner_user_id, follower_inbox_url)`.
BackfillRequested {
owner_user_id: uuid::Uuid,
follower_inbox_url: String,
},
}
/// Receives typed federation events.
#[async_trait::async_trait]
pub trait EventPublisher: Send + Sync {
async fn publish(&self, event: FederationEvent) -> anyhow::Result<()>;
}
#[derive(Clone)]
pub struct FederationData {
pub(crate) activity_repo: Arc<dyn ActivityRepository>,
pub(crate) follow_repo: Arc<dyn FollowRepository>,
pub(crate) actor_repo: Arc<dyn ActorRepository>,
pub(crate) blocklist_repo: Arc<dyn BlocklistRepository>,
pub(crate) user_repo: Arc<dyn ApUserRepository>,
pub(crate) content_reader: Arc<dyn ApContentReader>,
pub(crate) object_handler: Arc<dyn ApObjectHandler>,
pub(crate) base_url: String,
pub(crate) domain: String,
pub(crate) allow_registration: bool,
pub(crate) software_name: String,
pub(crate) event_publisher: Option<Arc<dyn EventPublisher>>,
pub(crate) actor_cache_ttl: std::time::Duration,
}
impl FederationData {
#[allow(clippy::too_many_arguments)]
pub fn new(
activity_repo: Arc<dyn ActivityRepository>,
follow_repo: Arc<dyn FollowRepository>,
actor_repo: Arc<dyn ActorRepository>,
blocklist_repo: Arc<dyn BlocklistRepository>,
user_repo: Arc<dyn ApUserRepository>,
content_reader: Arc<dyn ApContentReader>,
object_handler: Arc<dyn ApObjectHandler>,
base_url: String,
allow_registration: bool,
software_name: String,
event_publisher: Option<Arc<dyn EventPublisher>>,
actor_cache_ttl: std::time::Duration,
) -> Self {
let domain = base_url
.trim_start_matches("https://")
.trim_start_matches("http://")
.split('/')
.next()
.unwrap_or("")
.to_string();
Self {
activity_repo,
follow_repo,
actor_repo,
blocklist_repo,
user_repo,
content_reader,
object_handler,
base_url,
domain,
allow_registration,
software_name,
event_publisher,
actor_cache_ttl,
}
}
}