use async_trait::async_trait; use domain::{ errors::DomainError, models::thought::Thought, 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, pub sensitive: bool, pub content_warning: Option, pub visibility: &'a str, pub in_reply_to: Option<&'a str>, } /// 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, 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>, limit: usize, ) -> Result, 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, 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; /// 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; /// Apply an Update to a previously accepted remote Note. async fn apply_note_update(&self, ap_id: &str, new_content: &str) -> 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; /// 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, 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, 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>; }