refactor!: CQRS repository split — v0.3.0

FederationRepository (34 methods) → 4 focused traits:
  ActivityRepository  (2)  — idempotency tracking
  FollowRepository    (18) — follower/following graph + migration
  ActorRepository     (6)  — keypairs, remote actor cache, announce tracking
  BlocklistRepository (8)  — domain + actor blocklists

ApObjectHandler (10 methods) → 2 traits:
  ApContentReader  (3) — get_local_objects_for_user/page, count_local_posts
  ApObjectHandler  (9) — all inbox callbacks (on_create, on_mention, etc.)

Builder changes from positional args to named setters:
  ActivityPubService::builder(base_url)
    .activity_repo(arc)
    .follow_repo(arc)
    .actor_repo(arc)
    .blocklist_repo(arc)
    .user_repo(arc)
    .content_reader(arc)
    .object_handler(arc)
    .build()

No behaviour changes.
This commit is contained in:
2026-05-29 01:47:23 +02:00
parent e11b0a6609
commit df6ff4c1e8
27 changed files with 529 additions and 358 deletions

View File

@@ -46,7 +46,7 @@ impl Activity for AcceptActivity {
}
let local_user_id = crate::urls::extract_user_id_from_url(self.object.actor.inner())
.ok_or_else(|| Error::bad_request(anyhow::anyhow!("invalid actor URL in Follow")))?;
data.federation_repo
data.follow_repo
.update_following_status(
local_user_id,
self.actor.inner().as_str(),

View File

@@ -57,7 +57,7 @@ impl Activity for AnnounceActivity {
tracing::debug!(actor = %self.actor.inner(), object = %self.object, "received Announce of non-local object");
return Ok(());
}
data.federation_repo
data.actor_repo
.add_announce(
self.id.as_str(),
self.object.as_str(),

View File

@@ -45,8 +45,8 @@ impl Activity for BlockActivity {
return Ok(());
}
if let Some(local_user_id) = crate::urls::extract_user_id_from_url(&self.object) {
let _ = data.federation_repo.remove_following(local_user_id, self.actor.inner().as_str()).await;
let _ = data.federation_repo.remove_follower(local_user_id, self.actor.inner().as_str()).await;
let _ = data.follow_repo.remove_following(local_user_id, self.actor.inner().as_str()).await;
let _ = data.follow_repo.remove_follower(local_user_id, self.actor.inner().as_str()).await;
}
tracing::info!(actor = %self.actor.inner(), "received block — removed following and follower");
Ok(())

View File

@@ -57,7 +57,7 @@ impl Activity for FollowActivity {
}
// Actor block checked BEFORE any outbound HTTP fetch.
if let Some(target_user_id) = crate::urls::extract_user_id_from_url(self.object.inner()) {
if data.federation_repo
if data.blocklist_repo
.is_actor_blocked(target_user_id, self.actor.inner().as_str())
.await?
{
@@ -67,7 +67,7 @@ impl Activity for FollowActivity {
}
let _follower = self.actor.dereference(data).await?;
let local_actor = self.object.dereference(data).await?;
data.federation_repo
data.follow_repo
.add_follower(
local_actor.user_id,
self.actor.inner().as_str(),

View File

@@ -9,13 +9,13 @@ use crate::error::Error;
/// On repo error, skips the check rather than silently dropping the activity.
pub(crate) async fn already_processed(activity_id: &Url, data: &Data<FederationData>) -> bool {
let id = activity_id.as_str();
match data.federation_repo.is_activity_processed(id).await {
match data.activity_repo.is_activity_processed(id).await {
Ok(true) => {
tracing::debug!(activity_id = id, "duplicate activity, skipping");
true
}
Ok(false) => {
if let Err(e) = data.federation_repo.mark_activity_processed(id).await {
if let Err(e) = data.activity_repo.mark_activity_processed(id).await {
tracing::warn!(activity_id = id, error = %e, "failed to mark activity processed");
}
false
@@ -39,7 +39,7 @@ pub(crate) async fn check_guards(
return Ok(true);
}
let domain = actor.host_str().unwrap_or("");
if data.federation_repo.is_domain_blocked(domain).await? {
if data.blocklist_repo.is_domain_blocked(domain).await? {
tracing::info!(actor = %actor, "ignoring activity from blocked domain");
return Ok(true);
}

View File

@@ -59,7 +59,7 @@ impl Activity for MoveActivity {
)));
}
let affected = data
.federation_repo
.follow_repo
.migrate_follower_actor(self.object.as_str(), self.target.as_str())
.await
.map_err(|e| Error::from(anyhow::anyhow!("{e}")))?;

View File

@@ -44,7 +44,7 @@ impl Activity for RejectActivity {
return Ok(());
}
if let Some(user_id) = crate::urls::extract_user_id_from_url(self.object.actor.inner()) {
data.federation_repo
data.follow_repo
.remove_following(user_id, self.actor.inner().as_str())
.await?;
}

View File

@@ -53,7 +53,7 @@ impl Activity for UndoActivity {
&& let Ok(url) = Url::parse(obj_url)
&& let Some(user_id) = crate::urls::extract_user_id_from_url(&url)
{
data.federation_repo
data.follow_repo
.remove_follower(user_id, self.actor.inner().as_str())
.await?;
}