diff --git a/crates/adapters/postgres/src/remote_actor.rs b/crates/adapters/postgres/src/remote_actor.rs index ab3bd22..1e9425f 100644 --- a/crates/adapters/postgres/src/remote_actor.rs +++ b/crates/adapters/postgres/src/remote_actor.rs @@ -44,6 +44,6 @@ impl RemoteActorRepository for PgRemoteActorRepository { "SELECT url,handle,display_name,inbox_url,shared_inbox_url,public_key,last_fetched_at FROM remote_actors WHERE url=$1" ).bind(url).fetch_optional(&self.pool).await .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, 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: None, last_fetched_at: r.last_fetched_at })) } } diff --git a/crates/domain/src/errors.rs b/crates/domain/src/errors.rs index f8a6af5..fb8ac55 100644 --- a/crates/domain/src/errors.rs +++ b/crates/domain/src/errors.rs @@ -12,6 +12,8 @@ pub enum DomainError { Conflict(String), #[error("invalid input: {0}")] InvalidInput(String), + #[error("external service error: {0}")] + ExternalService(String), #[error("internal error: {0}")] Internal(String), } diff --git a/crates/domain/src/models/remote_actor.rs b/crates/domain/src/models/remote_actor.rs index f8d439c..07c2fb6 100644 --- a/crates/domain/src/models/remote_actor.rs +++ b/crates/domain/src/models/remote_actor.rs @@ -8,5 +8,6 @@ pub struct RemoteActor { pub inbox_url: String, pub shared_inbox_url: Option, pub public_key: String, + pub avatar_url: Option, pub last_fetched_at: DateTime, } diff --git a/crates/domain/src/ports.rs b/crates/domain/src/ports.rs index 6ffc407..86d6ff3 100644 --- a/crates/domain/src/ports.rs +++ b/crates/domain/src/ports.rs @@ -194,6 +194,12 @@ pub trait RemoteActorRepository: Send + Sync { async fn find_by_url(&self, url: &str) -> Result, DomainError>; } +#[async_trait] +pub trait FederationActionPort: Send + Sync { + async fn lookup_actor(&self, handle: &str) -> Result; + async fn follow_remote(&self, local_user_id: &UserId, handle: &str) -> Result<(), DomainError>; +} + #[async_trait] pub trait FeedRepository: Send + Sync { async fn home_feed( diff --git a/crates/domain/src/testing.rs b/crates/domain/src/testing.rs index 38078ec..c0d81fe 100644 --- a/crates/domain/src/testing.rs +++ b/crates/domain/src/testing.rs @@ -534,6 +534,21 @@ impl RemoteActorRepository for TestStore { } } +#[async_trait] +impl FederationActionPort for TestStore { + async fn lookup_actor(&self, _handle: &str) -> Result { + Err(DomainError::NotFound) + } + + async fn follow_remote( + &self, + _local_user_id: &UserId, + _handle: &str, + ) -> Result<(), DomainError> { + Ok(()) + } +} + #[async_trait] impl FeedRepository for TestStore { async fn home_feed( @@ -767,6 +782,32 @@ mod ap_repo_tests { } } +#[cfg(test)] +mod federation_port_tests { + use super::*; + use crate::value_objects::UserId; + + fn uid() -> UserId { + UserId::new() + } + + #[tokio::test] + async fn test_store_lookup_returns_not_found() { + let store = TestStore::default(); + let err = store.lookup_actor("@alice@example.com").await.unwrap_err(); + assert!(matches!(err, DomainError::NotFound)); + } + + #[tokio::test] + async fn test_store_follow_remote_is_noop_ok() { + let store = TestStore::default(); + store + .follow_remote(&uid(), "@alice@example.com") + .await + .unwrap(); + } +} + #[cfg(test)] mod search_tests { use super::*; diff --git a/crates/presentation/src/errors.rs b/crates/presentation/src/errors.rs index 9b320bf..db1ec41 100644 --- a/crates/presentation/src/errors.rs +++ b/crates/presentation/src/errors.rs @@ -28,6 +28,9 @@ impl IntoResponse for ApiError { Self::Domain(DomainError::Forbidden) => (StatusCode::FORBIDDEN, "forbidden".into()), Self::Domain(DomainError::Conflict(m)) => (StatusCode::CONFLICT, m), Self::Domain(DomainError::InvalidInput(m)) => (StatusCode::UNPROCESSABLE_ENTITY, m), + Self::Domain(DomainError::ExternalService(_)) => { + (StatusCode::BAD_GATEWAY, "external service error".into()) + } Self::Domain(DomainError::Internal(_)) => ( StatusCode::INTERNAL_SERVER_ERROR, "internal server error".into(),