feat(domain): RemoteActor fields, RemoteNote, FetchRemoteActorPosts event, fetch_outbox_page port

This commit is contained in:
2026-05-14 22:08:26 +02:00
parent cbfaeb95ac
commit 70fc4fbcd0
9 changed files with 78 additions and 1 deletions

View File

@@ -1394,6 +1394,11 @@ impl domain::ports::FederationActionPort for ActivityPubService {
public_key: actor.public_key_pem.clone(), public_key: actor.public_key_pem.clone(),
avatar_url: actor.avatar_url.as_ref().map(|u| u.to_string()), avatar_url: actor.avatar_url.as_ref().map(|u| u.to_string()),
last_fetched_at: actor.last_refreshed_at, last_fetched_at: actor.last_refreshed_at,
bio: None,
banner_url: None,
also_known_as: None,
outbox_url: None,
attachment: vec![],
}) })
} }
@@ -1511,6 +1516,16 @@ impl domain::ports::FederationActionPort for ActivityPubService {
serde_json::to_string(&obj) serde_json::to_string(&obj)
.map_err(|e| domain::errors::DomainError::ExternalService(e.to_string())) .map_err(|e| domain::errors::DomainError::ExternalService(e.to_string()))
} }
async fn fetch_outbox_page(
&self,
_outbox_url: &str,
_page: u32,
) -> Result<Vec<domain::models::remote_note::RemoteNote>, domain::errors::DomainError> {
Err(domain::errors::DomainError::Internal(
"not implemented".into(),
))
}
} }
#[cfg(test)] #[cfg(test)]

View File

@@ -68,6 +68,10 @@ pub enum EventPayload {
UserRegistered { UserRegistered {
user_id: String, user_id: String,
}, },
FetchRemoteActorPosts {
actor_ap_url: String,
outbox_url: String,
},
} }
impl EventPayload { impl EventPayload {
@@ -88,6 +92,7 @@ impl EventPayload {
Self::UserBlocked { .. } => "users.blocked", Self::UserBlocked { .. } => "users.blocked",
Self::UserUnblocked { .. } => "users.unblocked", Self::UserUnblocked { .. } => "users.unblocked",
Self::UserRegistered { .. } => "users.registered", Self::UserRegistered { .. } => "users.registered",
Self::FetchRemoteActorPosts { .. } => "federation.fetch_outbox",
} }
} }
} }
@@ -197,6 +202,13 @@ impl From<&DomainEvent> for EventPayload {
DomainEvent::UserRegistered { user_id } => Self::UserRegistered { DomainEvent::UserRegistered { user_id } => Self::UserRegistered {
user_id: user_id.to_string(), user_id: user_id.to_string(),
}, },
DomainEvent::FetchRemoteActorPosts {
actor_ap_url,
outbox_url,
} => Self::FetchRemoteActorPosts {
actor_ap_url: actor_ap_url.clone(),
outbox_url: outbox_url.clone(),
},
} }
} }
} }
@@ -315,6 +327,13 @@ impl TryFrom<EventPayload> for DomainEvent {
EventPayload::UserRegistered { user_id } => DomainEvent::UserRegistered { EventPayload::UserRegistered { user_id } => DomainEvent::UserRegistered {
user_id: UserId::from_uuid(parse_uuid(&user_id, "user_id")?), user_id: UserId::from_uuid(parse_uuid(&user_id, "user_id")?),
}, },
EventPayload::FetchRemoteActorPosts {
actor_ap_url,
outbox_url,
} => DomainEvent::FetchRemoteActorPosts {
actor_ap_url,
outbox_url,
},
}) })
} }
} }

View File

@@ -45,6 +45,6 @@ impl RemoteActorRepository for PgRemoteActorRepository {
"SELECT url,handle,display_name,inbox_url,shared_inbox_url,public_key,avatar_url,last_fetched_at FROM remote_actors WHERE url=$1" "SELECT url,handle,display_name,inbox_url,shared_inbox_url,public_key,avatar_url,last_fetched_at FROM remote_actors WHERE url=$1"
).bind(url).fetch_optional(&self.pool).await ).bind(url).fetch_optional(&self.pool).await
.map_err(|e| DomainError::Internal(e.to_string())) .map_err(|e| DomainError::Internal(e.to_string()))
.map(|o| o.map(|r| RemoteActor { url: r.url, handle: r.handle, display_name: r.display_name, inbox_url: r.inbox_url, shared_inbox_url: r.shared_inbox_url, public_key: r.public_key, avatar_url: r.avatar_url, last_fetched_at: r.last_fetched_at })) .map(|o| o.map(|r| RemoteActor { url: r.url, handle: r.handle, display_name: r.display_name, inbox_url: r.inbox_url, shared_inbox_url: r.shared_inbox_url, public_key: r.public_key, avatar_url: r.avatar_url, last_fetched_at: r.last_fetched_at, bio: None, banner_url: None, also_known_as: None, outbox_url: None, attachment: vec![] }))
} }
} }

View File

@@ -60,6 +60,10 @@ pub enum DomainEvent {
UserRegistered { UserRegistered {
user_id: UserId, user_id: UserId,
}, },
FetchRemoteActorPosts {
actor_ap_url: String,
outbox_url: String,
},
} }
pub struct EventEnvelope { pub struct EventEnvelope {

View File

@@ -2,6 +2,7 @@ pub mod api_key;
pub mod feed; pub mod feed;
pub mod notification; pub mod notification;
pub mod remote_actor; pub mod remote_actor;
pub mod remote_note;
pub mod social; pub mod social;
pub mod tag; pub mod tag;
pub mod thought; pub mod thought;

View File

@@ -10,4 +10,9 @@ pub struct RemoteActor {
pub public_key: String, pub public_key: String,
pub avatar_url: Option<String>, pub avatar_url: Option<String>,
pub last_fetched_at: DateTime<Utc>, pub last_fetched_at: DateTime<Utc>,
pub bio: Option<String>,
pub banner_url: Option<String>,
pub also_known_as: Option<String>,
pub outbox_url: Option<String>,
pub attachment: Vec<(String, String)>,
} }

View File

@@ -0,0 +1,10 @@
use chrono::{DateTime, Utc};
#[derive(Debug, Clone)]
pub struct RemoteNote {
pub ap_id: String,
pub content: String,
pub published: DateTime<Utc>,
pub sensitive: bool,
pub content_warning: Option<String>,
}

View File

@@ -209,6 +209,11 @@ pub trait FederationActionPort: Send + Sync {
user_id: &UserId, user_id: &UserId,
page: Option<u32>, page: Option<u32>,
) -> Result<String, DomainError>; ) -> Result<String, DomainError>;
async fn fetch_outbox_page(
&self,
outbox_url: &str,
page: u32,
) -> Result<Vec<crate::models::remote_note::RemoteNote>, DomainError>;
} }
#[async_trait] #[async_trait]

View File

@@ -567,6 +567,14 @@ impl FederationActionPort for TestStore {
) -> Result<String, DomainError> { ) -> Result<String, DomainError> {
Err(DomainError::NotFound) Err(DomainError::NotFound)
} }
async fn fetch_outbox_page(
&self,
_outbox_url: &str,
_page: u32,
) -> Result<Vec<crate::models::remote_note::RemoteNote>, DomainError> {
Ok(vec![])
}
} }
#[async_trait] #[async_trait]
@@ -833,6 +841,16 @@ mod federation_port_tests {
let err = store.actor_json(&UserId::new()).await.unwrap_err(); let err = store.actor_json(&UserId::new()).await.unwrap_err();
assert!(matches!(err, DomainError::NotFound)); assert!(matches!(err, DomainError::NotFound));
} }
#[tokio::test]
async fn test_store_fetch_outbox_returns_empty() {
let store = TestStore::default();
let notes = store
.fetch_outbox_page("https://example.com/outbox", 1)
.await
.unwrap();
assert!(notes.is_empty());
}
} }
#[cfg(test)] #[cfg(test)]