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
This commit is contained in:
2026-05-30 02:46:54 +02:00
parent f08d11034d
commit 7171a1791a
8 changed files with 69 additions and 0 deletions

View File

@@ -1,5 +1,9 @@
use activitypub_federation::fetch::object_id::ObjectId;
use url::Url;
use crate::actors::DbActor;
use crate::repository::RemoteActor;
use super::ActivityPubService;
impl ActivityPubService {
@@ -17,4 +21,42 @@ impl ActivityPubService {
.map_err(|e| anyhow::anyhow!("{e}"))?;
Ok(res.object)
}
/// Get a cached remote actor, re-fetching from origin if stale.
///
/// Returns `None` if the actor has never been seen. Staleness is
/// determined by `actor_cache_ttl_secs` (builder config).
pub async fn get_or_refresh_remote_actor(
&self,
actor_url: &str,
) -> anyhow::Result<Option<RemoteActor>> {
let data = self.federation_config.to_request_data();
let cached = data.actor_repo.get_remote_actor(actor_url).await?;
if let Some(ref actor) = cached {
let is_fresh = actor
.fetched_at
.map(|t| {
let age = chrono::Utc::now().signed_duration_since(t);
age < chrono::Duration::from_std(data.actor_cache_ttl).unwrap_or_default()
})
.unwrap_or(true);
if is_fresh {
return Ok(cached);
}
}
let url = match Url::parse(actor_url) {
Ok(u) => u,
Err(_) => return Ok(cached),
};
match ObjectId::<DbActor>::from(url)
.dereference_forced(&data)
.await
{
Ok(_) => Ok(data.actor_repo.get_remote_actor(actor_url).await?),
Err(e) => {
tracing::warn!(actor_url, error = %e, "re-fetch failed, using stale cache");
Ok(cached)
}
}
}
}