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:
11
src/repository/activity.rs
Normal file
11
src/repository/activity.rs
Normal file
@@ -0,0 +1,11 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
|
||||
/// Tracks which inbound AP activity IDs have already been processed.
|
||||
/// Prevents duplicate handling when remote servers retry delivery.
|
||||
/// Implementations should enforce a UNIQUE constraint on stored IDs.
|
||||
#[async_trait]
|
||||
pub trait ActivityRepository: Send + Sync {
|
||||
async fn is_activity_processed(&self, activity_id: &str) -> Result<bool>;
|
||||
async fn mark_activity_processed(&self, activity_id: &str) -> Result<()>;
|
||||
}
|
||||
34
src/repository/actor.rs
Normal file
34
src/repository/actor.rs
Normal file
@@ -0,0 +1,34 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::RemoteActor;
|
||||
|
||||
/// Manages local actor keypairs, remote actor cache, and Announce tracking.
|
||||
#[async_trait]
|
||||
pub trait ActorRepository: Send + Sync {
|
||||
// ── Local keypairs ──────────────────────────────────────────────────────
|
||||
async fn get_local_actor_keypair(
|
||||
&self,
|
||||
user_id: uuid::Uuid,
|
||||
) -> Result<Option<(String, String)>>;
|
||||
async fn save_local_actor_keypair(
|
||||
&self,
|
||||
user_id: uuid::Uuid,
|
||||
public_key: String,
|
||||
private_key: String,
|
||||
) -> Result<()>;
|
||||
|
||||
// ── Remote actor cache ──────────────────────────────────────────────────
|
||||
async fn upsert_remote_actor(&self, actor: RemoteActor) -> Result<()>;
|
||||
async fn get_remote_actor(&self, actor_url: &str) -> Result<Option<RemoteActor>>;
|
||||
|
||||
// ── Boost (Announce) tracking ───────────────────────────────────────────
|
||||
async fn add_announce(
|
||||
&self,
|
||||
activity_id: &str,
|
||||
object_url: &str,
|
||||
actor_url: &str,
|
||||
announced_at: chrono::DateTime<chrono::Utc>,
|
||||
) -> Result<()>;
|
||||
async fn count_announces(&self, object_url: &str) -> Result<usize>;
|
||||
}
|
||||
35
src/repository/blocklist.rs
Normal file
35
src/repository/blocklist.rs
Normal file
@@ -0,0 +1,35 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::BlockedDomain;
|
||||
|
||||
/// Domain and actor-level blocklists.
|
||||
#[async_trait]
|
||||
pub trait BlocklistRepository: Send + Sync {
|
||||
// ── Domain blocklist ────────────────────────────────────────────────────
|
||||
async fn add_blocked_domain(&self, domain: &str, reason: Option<&str>) -> Result<()>;
|
||||
async fn remove_blocked_domain(&self, domain: &str) -> Result<()>;
|
||||
async fn get_blocked_domains(&self) -> Result<Vec<BlockedDomain>>;
|
||||
async fn is_domain_blocked(&self, domain: &str) -> Result<bool>;
|
||||
|
||||
// ── Per-user actor blocklist ────────────────────────────────────────────
|
||||
async fn add_blocked_actor(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
actor_url: &str,
|
||||
) -> Result<()>;
|
||||
async fn remove_blocked_actor(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
actor_url: &str,
|
||||
) -> Result<()>;
|
||||
async fn get_blocked_actors(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
) -> Result<Vec<String>>;
|
||||
async fn is_actor_blocked(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
actor_url: &str,
|
||||
) -> Result<bool>;
|
||||
}
|
||||
97
src/repository/follow.rs
Normal file
97
src/repository/follow.rs
Normal file
@@ -0,0 +1,97 @@
|
||||
use anyhow::Result;
|
||||
use async_trait::async_trait;
|
||||
|
||||
use super::{Follower, FollowerStatus, FollowingStatus, RemoteActor};
|
||||
|
||||
/// Manages follower/following relationships and account migration.
|
||||
#[async_trait]
|
||||
pub trait FollowRepository: Send + Sync {
|
||||
// ── Inbound followers ───────────────────────────────────────────────────
|
||||
async fn add_follower(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
status: FollowerStatus,
|
||||
follow_activity_id: &str,
|
||||
) -> Result<()>;
|
||||
async fn get_follower_follow_activity_id(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
) -> Result<Option<String>>;
|
||||
async fn remove_follower(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
) -> Result<()>;
|
||||
async fn get_followers(&self, local_user_id: uuid::Uuid) -> Result<Vec<Follower>>;
|
||||
async fn get_followers_page(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
offset: u32,
|
||||
limit: usize,
|
||||
) -> Result<Vec<Follower>>;
|
||||
async fn count_followers(&self, local_user_id: uuid::Uuid) -> Result<usize>;
|
||||
async fn update_follower_status(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
status: FollowerStatus,
|
||||
) -> Result<()>;
|
||||
async fn get_pending_followers(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
) -> Result<Vec<RemoteActor>>;
|
||||
/// Return deduplicated inbox URLs (shared_inbox preferred) for accepted
|
||||
/// followers, excluding blocked actors/domains. DB-side filtering.
|
||||
async fn get_accepted_follower_inboxes(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
) -> Result<Vec<String>>;
|
||||
|
||||
// ── Outbound following ──────────────────────────────────────────────────
|
||||
async fn add_following(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
actor: RemoteActor,
|
||||
follow_activity_id: &str,
|
||||
) -> Result<()>;
|
||||
async fn get_follow_activity_id(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
) -> Result<Option<String>>;
|
||||
async fn remove_following(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
actor_url: &str,
|
||||
) -> Result<()>;
|
||||
async fn get_following(&self, local_user_id: uuid::Uuid) -> Result<Vec<RemoteActor>>;
|
||||
async fn get_following_page(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
offset: u32,
|
||||
limit: usize,
|
||||
) -> Result<Vec<RemoteActor>>;
|
||||
async fn count_following(&self, local_user_id: uuid::Uuid) -> Result<usize>;
|
||||
async fn update_following_status(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
status: FollowingStatus,
|
||||
) -> Result<()>;
|
||||
async fn get_following_outbox_url(
|
||||
&self,
|
||||
local_user_id: uuid::Uuid,
|
||||
remote_actor_url: &str,
|
||||
) -> Result<Option<String>>;
|
||||
|
||||
// ── Account migration ───────────────────────────────────────────────────
|
||||
/// Migrate all follower records from `old_actor_url` to `new_actor_url`.
|
||||
/// Returns local user IDs that need a re-follow sent.
|
||||
async fn migrate_follower_actor(
|
||||
&self,
|
||||
old_actor_url: &str,
|
||||
new_actor_url: &str,
|
||||
) -> Result<Vec<uuid::Uuid>>;
|
||||
}
|
||||
46
src/repository/mod.rs
Normal file
46
src/repository/mod.rs
Normal file
@@ -0,0 +1,46 @@
|
||||
mod activity;
|
||||
mod actor;
|
||||
mod blocklist;
|
||||
mod follow;
|
||||
|
||||
pub use activity::ActivityRepository;
|
||||
pub use actor::ActorRepository;
|
||||
pub use blocklist::BlocklistRepository;
|
||||
pub use follow::FollowRepository;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FollowerStatus {
|
||||
Pending,
|
||||
Accepted,
|
||||
Rejected,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub enum FollowingStatus {
|
||||
Pending,
|
||||
Accepted,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub struct RemoteActor {
|
||||
pub url: String,
|
||||
pub handle: String,
|
||||
pub inbox_url: String,
|
||||
pub shared_inbox_url: Option<String>,
|
||||
pub display_name: Option<String>,
|
||||
pub avatar_url: Option<String>,
|
||||
pub outbox_url: Option<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Follower {
|
||||
pub actor: RemoteActor,
|
||||
pub status: FollowerStatus,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct BlockedDomain {
|
||||
pub domain: String,
|
||||
pub reason: Option<String>,
|
||||
pub blocked_at: String,
|
||||
}
|
||||
Reference in New Issue
Block a user