refactor: move federation port types from adapter to domain
ActivityPubRepository→FederationContentRepository, OutboundFederationPort→FederationBroadcastPort, ActorApUrls→ActorFederationUrls. Removes activitypub dep from application and presentation crates. Adapter re-exports old names as aliases for backward compat. Also fixes count_users test broken by instance actor migration.
This commit is contained in:
2
Cargo.lock
generated
2
Cargo.lock
generated
@@ -273,7 +273,6 @@ dependencies = [
|
|||||||
name = "application"
|
name = "application"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub",
|
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"bytes",
|
"bytes",
|
||||||
"chrono",
|
"chrono",
|
||||||
@@ -2605,7 +2604,6 @@ dependencies = [
|
|||||||
name = "presentation"
|
name = "presentation"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"activitypub",
|
|
||||||
"api-types",
|
"api-types",
|
||||||
"application",
|
"application",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
|
|||||||
@@ -1,176 +1,5 @@
|
|||||||
use async_trait::async_trait;
|
pub use domain::ports::{
|
||||||
use domain::{
|
AcceptNoteInput, ActorFederationUrls as ActorApUrls,
|
||||||
errors::DomainError,
|
FederationBroadcastPort as OutboundFederationPort,
|
||||||
models::thought::Thought,
|
FederationContentRepository as ActivityPubRepository, OutboxEntry,
|
||||||
value_objects::{ThoughtId, UserId, Username},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
pub struct AcceptNoteInput<'a> {
|
|
||||||
pub ap_id: &'a str,
|
|
||||||
pub author_id: &'a UserId,
|
|
||||||
pub content: &'a str,
|
|
||||||
pub published: chrono::DateTime<chrono::Utc>,
|
|
||||||
pub sensitive: bool,
|
|
||||||
pub content_warning: Option<String>,
|
|
||||||
pub visibility: &'a str,
|
|
||||||
pub in_reply_to: Option<&'a str>,
|
|
||||||
pub note_extensions: Option<serde_json::Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// AP-protocol endpoints for a locally-stored user (local or interned remote).
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct ActorApUrls {
|
|
||||||
pub ap_id: String,
|
|
||||||
pub inbox_url: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A local thought ready for AP serialization, with the author's username
|
|
||||||
/// pre-joined so the handler can build AP URLs without a second query.
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct OutboxEntry {
|
|
||||||
pub thought: Thought,
|
|
||||||
pub author_username: Username,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait ActivityPubRepository: Send + Sync {
|
|
||||||
// ── Outbox (local → remote) ──────────────────────────────────────
|
|
||||||
|
|
||||||
/// All public local thoughts for this actor. Used for outbox totals
|
|
||||||
/// and full-collection delivery.
|
|
||||||
async fn outbox_entries_for_actor(
|
|
||||||
&self,
|
|
||||||
user_id: &UserId,
|
|
||||||
) -> Result<Vec<OutboxEntry>, DomainError>;
|
|
||||||
|
|
||||||
/// Cursor page of public local thoughts, newest-first, before `before`.
|
|
||||||
/// Used for OrderedCollectionPage responses.
|
|
||||||
async fn outbox_page_for_actor(
|
|
||||||
&self,
|
|
||||||
user_id: &UserId,
|
|
||||||
before: Option<chrono::DateTime<chrono::Utc>>,
|
|
||||||
limit: usize,
|
|
||||||
) -> Result<Vec<OutboxEntry>, DomainError>;
|
|
||||||
|
|
||||||
// ── Remote actor resolution ──────────────────────────────────────
|
|
||||||
|
|
||||||
/// Find the local UserId for a remote actor by its AP URL.
|
|
||||||
async fn find_remote_actor_id(&self, actor_ap_url: &str)
|
|
||||||
-> Result<Option<UserId>, DomainError>;
|
|
||||||
|
|
||||||
/// Ensure a remote actor placeholder exists; create one if absent.
|
|
||||||
/// Idempotent — safe to call multiple times with the same URL.
|
|
||||||
async fn intern_remote_actor(&self, actor_ap_url: &str) -> Result<UserId, DomainError>;
|
|
||||||
|
|
||||||
/// Update display_name and avatar_url for an already-interned remote actor.
|
|
||||||
async fn update_remote_actor_display(
|
|
||||||
&self,
|
|
||||||
user_id: &UserId,
|
|
||||||
display_name: Option<&str>,
|
|
||||||
avatar_url: Option<&str>,
|
|
||||||
) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
// ── Inbox processing (remote → local) ───────────────────────────
|
|
||||||
|
|
||||||
/// Persist an incoming remote Note. Idempotent on ap_id.
|
|
||||||
async fn accept_note(&self, input: AcceptNoteInput<'_>) -> Result<ThoughtId, DomainError>;
|
|
||||||
|
|
||||||
/// Apply an Update to a previously accepted remote Note.
|
|
||||||
async fn apply_note_update(
|
|
||||||
&self,
|
|
||||||
ap_id: &str,
|
|
||||||
new_content: &str,
|
|
||||||
note_extensions: Option<serde_json::Value>,
|
|
||||||
) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
/// Remove a specific remote Note (Delete activity). Only touches
|
|
||||||
/// remotely-originated thoughts.
|
|
||||||
async fn retract_note(&self, ap_id: &str) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
/// Remove all Notes from a remote actor (actor-level Delete/Tombstone).
|
|
||||||
async fn retract_actor_notes(&self, actor_ap_url: &str) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
// ── Node-level stats ─────────────────────────────────────────────
|
|
||||||
|
|
||||||
/// Total locally-authored thought count for NodeInfo responses.
|
|
||||||
async fn count_local_notes(&self) -> Result<u64, DomainError>;
|
|
||||||
|
|
||||||
/// Return the ActivityPub object URL for a thought, if one is stored.
|
|
||||||
/// Returns None for local thoughts (caller constructs URL from base_url + thought_id).
|
|
||||||
async fn get_thought_ap_id(
|
|
||||||
&self,
|
|
||||||
thought_id: &ThoughtId,
|
|
||||||
) -> Result<Option<String>, DomainError>;
|
|
||||||
|
|
||||||
/// Return the AP actor URL and inbox URL for a user, if stored.
|
|
||||||
/// Returns None for users that have not been federated.
|
|
||||||
async fn get_actor_ap_urls(&self, user_id: &UserId)
|
|
||||||
-> Result<Option<ActorApUrls>, DomainError>;
|
|
||||||
|
|
||||||
/// Sync display_name + avatar_url from remote_actors to users table.
|
|
||||||
async fn sync_remote_actor_to_user(&self, actor_ap_url: &str) -> Result<(), DomainError>;
|
|
||||||
}
|
|
||||||
|
|
||||||
#[async_trait]
|
|
||||||
pub trait OutboundFederationPort: Send + Sync {
|
|
||||||
/// Fan out a new local Note to all accepted followers.
|
|
||||||
async fn broadcast_create(
|
|
||||||
&self,
|
|
||||||
author_user_id: &UserId,
|
|
||||||
thought: &Thought,
|
|
||||||
author_username: &str,
|
|
||||||
in_reply_to_url: Option<&str>,
|
|
||||||
) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
/// Fan out a Delete tombstone for a now-deleted local Note.
|
|
||||||
/// `thought_ap_id` is pre-constructed by the caller because the thought
|
|
||||||
/// has already been deleted from the DB when this fires.
|
|
||||||
async fn broadcast_delete(
|
|
||||||
&self,
|
|
||||||
author_user_id: &UserId,
|
|
||||||
thought_ap_id: &str,
|
|
||||||
) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
/// Fan out an Update(Note) for an edited local thought.
|
|
||||||
async fn broadcast_update(
|
|
||||||
&self,
|
|
||||||
author_user_id: &UserId,
|
|
||||||
thought: &Thought,
|
|
||||||
author_username: &str,
|
|
||||||
in_reply_to_url: Option<&str>,
|
|
||||||
) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
/// Fan out an Announce(object_ap_id) for a boost.
|
|
||||||
async fn broadcast_announce(
|
|
||||||
&self,
|
|
||||||
booster_user_id: &UserId,
|
|
||||||
object_ap_id: &str,
|
|
||||||
) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
/// Fan out an Undo(Announce) to followers when a boost is removed.
|
|
||||||
async fn broadcast_undo_announce(
|
|
||||||
&self,
|
|
||||||
booster_user_id: &UserId,
|
|
||||||
object_ap_id: &str,
|
|
||||||
) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
/// Send a Like activity to a remote thought author's inbox.
|
|
||||||
/// Only called when a LOCAL user likes a REMOTE thought (one with an ap_id).
|
|
||||||
async fn broadcast_like(
|
|
||||||
&self,
|
|
||||||
liker_user_id: &UserId,
|
|
||||||
object_ap_id: &str,
|
|
||||||
author_inbox_url: &str,
|
|
||||||
) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
/// Send Undo(Like) to a remote thought author's inbox.
|
|
||||||
async fn broadcast_undo_like(
|
|
||||||
&self,
|
|
||||||
liker_user_id: &UserId,
|
|
||||||
object_ap_id: &str,
|
|
||||||
author_inbox_url: &str,
|
|
||||||
) -> Result<(), DomainError>;
|
|
||||||
|
|
||||||
/// Fan out an Update(Actor) to all accepted followers after a profile change.
|
|
||||||
async fn broadcast_actor_update(&self, user_id: &UserId) -> Result<(), DomainError>;
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -919,6 +919,7 @@ mod tests {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let repo = PgApUserRepository::new(pool, "https://example.com".into());
|
let repo = PgApUserRepository::new(pool, "https://example.com".into());
|
||||||
assert_eq!(repo.count_users().await.unwrap(), 2);
|
// 2 seeded local users + 1 instance actor from migration 022
|
||||||
|
assert_eq!(repo.count_users().await.unwrap(), 3);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
activitypub = { workspace = true }
|
|
||||||
async-trait = { workspace = true }
|
async-trait = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
uuid = { workspace = true }
|
uuid = { workspace = true }
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
use activitypub::{ActivityPubRepository, OutboundFederationPort};
|
|
||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
events::DomainEvent,
|
events::DomainEvent,
|
||||||
models::thought::Visibility,
|
models::thought::Visibility,
|
||||||
ports::{ThoughtRepository, UserReader},
|
ports::{FederationBroadcastPort, FederationContentRepository, ThoughtRepository, UserReader},
|
||||||
value_objects::ThoughtId,
|
value_objects::ThoughtId,
|
||||||
};
|
};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -15,9 +14,9 @@ fn should_broadcast(t: &domain::models::thought::Thought) -> bool {
|
|||||||
pub struct FederationEventService {
|
pub struct FederationEventService {
|
||||||
pub thoughts: Arc<dyn ThoughtRepository>,
|
pub thoughts: Arc<dyn ThoughtRepository>,
|
||||||
pub users: Arc<dyn UserReader>,
|
pub users: Arc<dyn UserReader>,
|
||||||
pub ap: Arc<dyn OutboundFederationPort>,
|
pub ap: Arc<dyn FederationBroadcastPort>,
|
||||||
pub base_url: String,
|
pub base_url: String,
|
||||||
pub ap_repo: Arc<dyn ActivityPubRepository>,
|
pub ap_repo: Arc<dyn FederationContentRepository>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FederationEventService {
|
impl FederationEventService {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
use crate::testing::TestApRepo;
|
use crate::testing::TestApRepo;
|
||||||
use activitypub::{ActorApUrls, OutboundFederationPort};
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use domain::ports::{ActorFederationUrls, FederationBroadcastPort};
|
||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
events::DomainEvent,
|
events::DomainEvent,
|
||||||
@@ -27,7 +27,7 @@ struct SpyPort {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl OutboundFederationPort for SpyPort {
|
impl FederationBroadcastPort for SpyPort {
|
||||||
async fn broadcast_create(
|
async fn broadcast_create(
|
||||||
&self,
|
&self,
|
||||||
_: &UserId,
|
_: &UserId,
|
||||||
@@ -482,7 +482,7 @@ async fn like_added_local_user_remote_thought_broadcasts_like() {
|
|||||||
let ap_repo = TestApRepo::new(store.clone());
|
let ap_repo = TestApRepo::new(store.clone());
|
||||||
ap_repo.actor_ap_urls.lock().unwrap().insert(
|
ap_repo.actor_ap_urls.lock().unwrap().insert(
|
||||||
author.id.clone(),
|
author.id.clone(),
|
||||||
ActorApUrls {
|
ActorFederationUrls {
|
||||||
ap_id: "https://mastodon.social/users/author".into(),
|
ap_id: "https://mastodon.social/users/author".into(),
|
||||||
inbox_url: "https://mastodon.social/users/author/inbox".into(),
|
inbox_url: "https://mastodon.social/users/author/inbox".into(),
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,9 +1,8 @@
|
|||||||
/// Test helpers for application-layer tests that need activitypub traits.
|
|
||||||
use activitypub::{ActivityPubRepository, ActorApUrls, OutboxEntry};
|
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
models::user::User,
|
models::user::User,
|
||||||
|
ports::{AcceptNoteInput, ActorFederationUrls, FederationContentRepository, OutboxEntry},
|
||||||
testing::TestStore,
|
testing::TestStore,
|
||||||
value_objects::{Email, ThoughtId, UserId, Username},
|
value_objects::{Email, ThoughtId, UserId, Username},
|
||||||
};
|
};
|
||||||
@@ -14,8 +13,8 @@ use std::sync::{Arc, Mutex};
|
|||||||
#[derive(Default, Clone)]
|
#[derive(Default, Clone)]
|
||||||
pub struct TestApRepo {
|
pub struct TestApRepo {
|
||||||
pub inner: TestStore,
|
pub inner: TestStore,
|
||||||
/// UserId → ActorApUrls (for get_actor_ap_urls)
|
/// UserId → ActorFederationUrls (for get_actor_ap_urls)
|
||||||
pub actor_ap_urls: Arc<Mutex<HashMap<UserId, ActorApUrls>>>,
|
pub actor_ap_urls: Arc<Mutex<HashMap<UserId, ActorFederationUrls>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TestApRepo {
|
impl TestApRepo {
|
||||||
@@ -28,7 +27,7 @@ impl TestApRepo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ActivityPubRepository for TestApRepo {
|
impl FederationContentRepository for TestApRepo {
|
||||||
async fn outbox_entries_for_actor(
|
async fn outbox_entries_for_actor(
|
||||||
&self,
|
&self,
|
||||||
_uid: &UserId,
|
_uid: &UserId,
|
||||||
@@ -84,13 +83,15 @@ impl ActivityPubRepository for TestApRepo {
|
|||||||
) -> Result<(), DomainError> {
|
) -> Result<(), DomainError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn accept_note(
|
async fn accept_note(&self, _input: AcceptNoteInput<'_>) -> Result<ThoughtId, DomainError> {
|
||||||
&self,
|
|
||||||
_input: activitypub::AcceptNoteInput<'_>,
|
|
||||||
) -> Result<ThoughtId, DomainError> {
|
|
||||||
Ok(ThoughtId::from_uuid(uuid::Uuid::new_v4()))
|
Ok(ThoughtId::from_uuid(uuid::Uuid::new_v4()))
|
||||||
}
|
}
|
||||||
async fn apply_note_update(&self, _ap_id: &str, _new_content: &str, _: Option<serde_json::Value>) -> Result<(), DomainError> {
|
async fn apply_note_update(
|
||||||
|
&self,
|
||||||
|
_ap_id: &str,
|
||||||
|
_new_content: &str,
|
||||||
|
_: Option<serde_json::Value>,
|
||||||
|
) -> Result<(), DomainError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn retract_note(&self, _ap_id: &str) -> Result<(), DomainError> {
|
async fn retract_note(&self, _ap_id: &str) -> Result<(), DomainError> {
|
||||||
@@ -124,7 +125,7 @@ impl ActivityPubRepository for TestApRepo {
|
|||||||
async fn get_actor_ap_urls(
|
async fn get_actor_ap_urls(
|
||||||
&self,
|
&self,
|
||||||
user_id: &UserId,
|
user_id: &UserId,
|
||||||
) -> Result<Option<ActorApUrls>, DomainError> {
|
) -> Result<Option<ActorFederationUrls>, DomainError> {
|
||||||
Ok(self.actor_ap_urls.lock().unwrap().get(user_id).cloned())
|
Ok(self.actor_ap_urls.lock().unwrap().get(user_id).cloned())
|
||||||
}
|
}
|
||||||
async fn sync_remote_actor_to_user(&self, _actor_ap_url: &str) -> Result<(), DomainError> {
|
async fn sync_remote_actor_to_user(&self, _actor_ap_url: &str) -> Result<(), DomainError> {
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use activitypub::ActivityPubRepository;
|
|
||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
events::DomainEvent,
|
events::DomainEvent,
|
||||||
@@ -8,9 +7,10 @@ use domain::{
|
|||||||
remote_actor::RemoteActor,
|
remote_actor::RemoteActor,
|
||||||
},
|
},
|
||||||
ports::{
|
ports::{
|
||||||
EventPublisher, FederationActionPort, FederationFollowPort, FederationFollowRequestPort,
|
EventPublisher, FederationActionPort, FederationContentRepository, FederationFollowPort,
|
||||||
FederationSchedulerPort, FeedOptions, FeedQuery, FeedRepository, FeedRequest,
|
FederationFollowRequestPort, FederationSchedulerPort, FeedOptions, FeedQuery,
|
||||||
FollowRepository, RemoteActorConnectionRepository, UserReader, UserWriter,
|
FeedRepository, FeedRequest, FollowRepository, RemoteActorConnectionRepository, UserReader,
|
||||||
|
UserWriter,
|
||||||
},
|
},
|
||||||
value_objects::UserId,
|
value_objects::UserId,
|
||||||
};
|
};
|
||||||
@@ -119,7 +119,7 @@ pub async fn remove_remote_following(
|
|||||||
|
|
||||||
pub async fn get_remote_actor_posts(
|
pub async fn get_remote_actor_posts(
|
||||||
federation: &dyn FederationActionPort,
|
federation: &dyn FederationActionPort,
|
||||||
ap_repo: &dyn ActivityPubRepository,
|
ap_repo: &dyn FederationContentRepository,
|
||||||
feed: &dyn FeedRepository,
|
feed: &dyn FeedRepository,
|
||||||
scheduler: &dyn FederationSchedulerPort,
|
scheduler: &dyn FederationSchedulerPort,
|
||||||
handle: &str,
|
handle: &str,
|
||||||
|
|||||||
@@ -511,3 +511,136 @@ pub trait FederationSchedulerPort: Send + Sync {
|
|||||||
page: u32,
|
page: u32,
|
||||||
) -> Result<(), DomainError>;
|
) -> Result<(), DomainError>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Federation content & broadcast ports ────────────────────────────────
|
||||||
|
|
||||||
|
pub struct AcceptNoteInput<'a> {
|
||||||
|
pub ap_id: &'a str,
|
||||||
|
pub author_id: &'a UserId,
|
||||||
|
pub content: &'a str,
|
||||||
|
pub published: chrono::DateTime<chrono::Utc>,
|
||||||
|
pub sensitive: bool,
|
||||||
|
pub content_warning: Option<String>,
|
||||||
|
pub visibility: &'a str,
|
||||||
|
pub in_reply_to: Option<&'a str>,
|
||||||
|
pub note_extensions: Option<serde_json::Value>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct ActorFederationUrls {
|
||||||
|
pub ap_id: String,
|
||||||
|
pub inbox_url: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct OutboxEntry {
|
||||||
|
pub thought: Thought,
|
||||||
|
pub author_username: Username,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait FederationContentRepository: Send + Sync {
|
||||||
|
async fn outbox_entries_for_actor(
|
||||||
|
&self,
|
||||||
|
user_id: &UserId,
|
||||||
|
) -> Result<Vec<OutboxEntry>, DomainError>;
|
||||||
|
|
||||||
|
async fn outbox_page_for_actor(
|
||||||
|
&self,
|
||||||
|
user_id: &UserId,
|
||||||
|
before: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
|
limit: usize,
|
||||||
|
) -> Result<Vec<OutboxEntry>, DomainError>;
|
||||||
|
|
||||||
|
async fn find_remote_actor_id(&self, actor_ap_url: &str)
|
||||||
|
-> Result<Option<UserId>, DomainError>;
|
||||||
|
|
||||||
|
async fn intern_remote_actor(&self, actor_ap_url: &str) -> Result<UserId, DomainError>;
|
||||||
|
|
||||||
|
async fn update_remote_actor_display(
|
||||||
|
&self,
|
||||||
|
user_id: &UserId,
|
||||||
|
display_name: Option<&str>,
|
||||||
|
avatar_url: Option<&str>,
|
||||||
|
) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
async fn accept_note(&self, input: AcceptNoteInput<'_>) -> Result<ThoughtId, DomainError>;
|
||||||
|
|
||||||
|
async fn apply_note_update(
|
||||||
|
&self,
|
||||||
|
ap_id: &str,
|
||||||
|
new_content: &str,
|
||||||
|
note_extensions: Option<serde_json::Value>,
|
||||||
|
) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
async fn retract_note(&self, ap_id: &str) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
async fn retract_actor_notes(&self, actor_ap_url: &str) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
async fn count_local_notes(&self) -> Result<u64, DomainError>;
|
||||||
|
|
||||||
|
async fn get_thought_ap_id(
|
||||||
|
&self,
|
||||||
|
thought_id: &ThoughtId,
|
||||||
|
) -> Result<Option<String>, DomainError>;
|
||||||
|
|
||||||
|
async fn get_actor_ap_urls(
|
||||||
|
&self,
|
||||||
|
user_id: &UserId,
|
||||||
|
) -> Result<Option<ActorFederationUrls>, DomainError>;
|
||||||
|
|
||||||
|
async fn sync_remote_actor_to_user(&self, actor_ap_url: &str) -> Result<(), DomainError>;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[async_trait]
|
||||||
|
pub trait FederationBroadcastPort: Send + Sync {
|
||||||
|
async fn broadcast_create(
|
||||||
|
&self,
|
||||||
|
author_user_id: &UserId,
|
||||||
|
thought: &Thought,
|
||||||
|
author_username: &str,
|
||||||
|
in_reply_to_url: Option<&str>,
|
||||||
|
) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
async fn broadcast_delete(
|
||||||
|
&self,
|
||||||
|
author_user_id: &UserId,
|
||||||
|
thought_ap_id: &str,
|
||||||
|
) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
async fn broadcast_update(
|
||||||
|
&self,
|
||||||
|
author_user_id: &UserId,
|
||||||
|
thought: &Thought,
|
||||||
|
author_username: &str,
|
||||||
|
in_reply_to_url: Option<&str>,
|
||||||
|
) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
async fn broadcast_announce(
|
||||||
|
&self,
|
||||||
|
booster_user_id: &UserId,
|
||||||
|
object_ap_id: &str,
|
||||||
|
) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
async fn broadcast_undo_announce(
|
||||||
|
&self,
|
||||||
|
booster_user_id: &UserId,
|
||||||
|
object_ap_id: &str,
|
||||||
|
) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
async fn broadcast_like(
|
||||||
|
&self,
|
||||||
|
liker_user_id: &UserId,
|
||||||
|
object_ap_id: &str,
|
||||||
|
author_inbox_url: &str,
|
||||||
|
) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
async fn broadcast_undo_like(
|
||||||
|
&self,
|
||||||
|
liker_user_id: &UserId,
|
||||||
|
object_ap_id: &str,
|
||||||
|
author_inbox_url: &str,
|
||||||
|
) -> Result<(), DomainError>;
|
||||||
|
|
||||||
|
async fn broadcast_actor_update(&self, user_id: &UserId) -> Result<(), DomainError>;
|
||||||
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ edition = "2021"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
domain = { workspace = true }
|
domain = { workspace = true }
|
||||||
activitypub = { workspace = true }
|
|
||||||
application = { workspace = true }
|
application = { workspace = true }
|
||||||
api-types = { workspace = true }
|
api-types = { workspace = true }
|
||||||
axum = { workspace = true }
|
axum = { workspace = true }
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ use crate::{
|
|||||||
handlers::feed::to_thought_response,
|
handlers::feed::to_thought_response,
|
||||||
state::AppState,
|
state::AppState,
|
||||||
};
|
};
|
||||||
use activitypub::ActivityPubRepository;
|
|
||||||
use api_types::{
|
use api_types::{
|
||||||
requests::PaginationQuery,
|
requests::PaginationQuery,
|
||||||
responses::{
|
responses::{
|
||||||
@@ -18,6 +17,7 @@ use axum::{
|
|||||||
extract::{Path, Query},
|
extract::{Path, Query},
|
||||||
Json,
|
Json,
|
||||||
};
|
};
|
||||||
|
use domain::ports::FederationContentRepository;
|
||||||
use domain::{
|
use domain::{
|
||||||
models::feed::PageParams,
|
models::feed::PageParams,
|
||||||
ports::{
|
ports::{
|
||||||
@@ -29,7 +29,7 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
pub struct FederationActorsDeps {
|
pub struct FederationActorsDeps {
|
||||||
pub federation: Arc<dyn FederationActionPort>,
|
pub federation: Arc<dyn FederationActionPort>,
|
||||||
pub ap_repo: Arc<dyn ActivityPubRepository>,
|
pub ap_repo: Arc<dyn FederationContentRepository>,
|
||||||
pub feed: Arc<dyn FeedRepository>,
|
pub feed: Arc<dyn FeedRepository>,
|
||||||
pub federation_scheduler: Arc<dyn FederationSchedulerPort>,
|
pub federation_scheduler: Arc<dyn FederationSchedulerPort>,
|
||||||
pub remote_actor_connections: Arc<dyn RemoteActorConnectionRepository>,
|
pub remote_actor_connections: Arc<dyn RemoteActorConnectionRepository>,
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
use activitypub::ActivityPubRepository;
|
|
||||||
use application::use_cases::profile::UploadConfig;
|
use application::use_cases::profile::UploadConfig;
|
||||||
use domain::ports::*;
|
use domain::ports::*;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@@ -24,7 +23,7 @@ pub struct AppState {
|
|||||||
pub events: Arc<dyn EventPublisher>,
|
pub events: Arc<dyn EventPublisher>,
|
||||||
pub outbox: Arc<dyn OutboxWriter>,
|
pub outbox: Arc<dyn OutboxWriter>,
|
||||||
pub federation: Arc<dyn FederationActionPort>,
|
pub federation: Arc<dyn FederationActionPort>,
|
||||||
pub ap_repo: Arc<dyn ActivityPubRepository>,
|
pub ap_repo: Arc<dyn FederationContentRepository>,
|
||||||
pub remote_actor_connections: Arc<dyn RemoteActorConnectionRepository>,
|
pub remote_actor_connections: Arc<dyn RemoteActorConnectionRepository>,
|
||||||
pub federation_scheduler: Arc<dyn FederationSchedulerPort>,
|
pub federation_scheduler: Arc<dyn FederationSchedulerPort>,
|
||||||
pub engagement: Arc<dyn EngagementRepository>,
|
pub engagement: Arc<dyn EngagementRepository>,
|
||||||
|
|||||||
@@ -1,7 +1,9 @@
|
|||||||
use crate::state::AppState;
|
use crate::state::AppState;
|
||||||
use activitypub::{ActivityPubRepository, ActorApUrls, OutboxEntry};
|
|
||||||
use application::use_cases::profile::UploadConfig;
|
use application::use_cases::profile::UploadConfig;
|
||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
|
use domain::ports::{
|
||||||
|
AcceptNoteInput, ActorFederationUrls, FederationContentRepository, OutboxEntry,
|
||||||
|
};
|
||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
ports::{AuthService, DataStream, GeneratedToken, MediaStore, PasswordHasher},
|
ports::{AuthService, DataStream, GeneratedToken, MediaStore, PasswordHasher},
|
||||||
@@ -34,7 +36,7 @@ impl PasswordHasher for NoOpHasher {
|
|||||||
pub struct NoOpApRepo;
|
pub struct NoOpApRepo;
|
||||||
|
|
||||||
#[async_trait]
|
#[async_trait]
|
||||||
impl ActivityPubRepository for NoOpApRepo {
|
impl FederationContentRepository for NoOpApRepo {
|
||||||
async fn outbox_entries_for_actor(&self, _: &UserId) -> Result<Vec<OutboxEntry>, DomainError> {
|
async fn outbox_entries_for_actor(&self, _: &UserId) -> Result<Vec<OutboxEntry>, DomainError> {
|
||||||
Ok(vec![])
|
Ok(vec![])
|
||||||
}
|
}
|
||||||
@@ -60,13 +62,15 @@ impl ActivityPubRepository for NoOpApRepo {
|
|||||||
) -> Result<(), DomainError> {
|
) -> Result<(), DomainError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn accept_note(
|
async fn accept_note(&self, _: AcceptNoteInput<'_>) -> Result<ThoughtId, DomainError> {
|
||||||
&self,
|
|
||||||
_: activitypub::AcceptNoteInput<'_>,
|
|
||||||
) -> Result<ThoughtId, DomainError> {
|
|
||||||
Ok(ThoughtId::from_uuid(uuid::Uuid::new_v4()))
|
Ok(ThoughtId::from_uuid(uuid::Uuid::new_v4()))
|
||||||
}
|
}
|
||||||
async fn apply_note_update(&self, _: &str, _: &str, _: Option<serde_json::Value>) -> Result<(), DomainError> {
|
async fn apply_note_update(
|
||||||
|
&self,
|
||||||
|
_: &str,
|
||||||
|
_: &str,
|
||||||
|
_: Option<serde_json::Value>,
|
||||||
|
) -> Result<(), DomainError> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
async fn retract_note(&self, _: &str) -> Result<(), DomainError> {
|
async fn retract_note(&self, _: &str) -> Result<(), DomainError> {
|
||||||
@@ -81,7 +85,10 @@ impl ActivityPubRepository for NoOpApRepo {
|
|||||||
async fn get_thought_ap_id(&self, _: &ThoughtId) -> Result<Option<String>, DomainError> {
|
async fn get_thought_ap_id(&self, _: &ThoughtId) -> Result<Option<String>, DomainError> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
async fn get_actor_ap_urls(&self, _: &UserId) -> Result<Option<ActorApUrls>, DomainError> {
|
async fn get_actor_ap_urls(
|
||||||
|
&self,
|
||||||
|
_: &UserId,
|
||||||
|
) -> Result<Option<ActorFederationUrls>, DomainError> {
|
||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
async fn sync_remote_actor_to_user(&self, _: &str) -> Result<(), DomainError> {
|
async fn sync_remote_actor_to_user(&self, _: &str) -> Result<(), DomainError> {
|
||||||
|
|||||||
Reference in New Issue
Block a user