diff --git a/crates/adapters/activitypub-base/src/ap_ports.rs b/crates/adapters/activitypub-base/src/ap_ports.rs
index 15190c0..471814f 100644
--- a/crates/adapters/activitypub-base/src/ap_ports.rs
+++ b/crates/adapters/activitypub-base/src/ap_ports.rs
@@ -44,7 +44,7 @@ pub trait ActivityPubRepository: Send + Sync {
/// 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>;
+ -> Result , DomainError>;
/// Ensure a remote actor placeholder exists; create one if absent.
/// Idempotent — safe to call multiple times with the same URL.
@@ -99,7 +99,7 @@ pub trait ActivityPubRepository: Send + Sync {
/// 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>;
+ -> Result , DomainError>;
}
#[async_trait]
diff --git a/crates/adapters/activitypub-base/src/lib.rs b/crates/adapters/activitypub-base/src/lib.rs
index aceb0d5..75bd509 100644
--- a/crates/adapters/activitypub-base/src/lib.rs
+++ b/crates/adapters/activitypub-base/src/lib.rs
@@ -18,7 +18,7 @@ pub mod user;
pub mod webfinger;
pub use activitypub_federation::kinds::object::NoteType;
-pub use ap_ports::{ActorApUrls, ActivityPubRepository, OutboxEntry, OutboundFederationPort};
+pub use ap_ports::{ActivityPubRepository, ActorApUrls, OutboundFederationPort, OutboxEntry};
pub use content::ApObjectHandler;
pub use data::FederationData;
pub use error::Error;
diff --git a/crates/adapters/activitypub-base/src/service.rs b/crates/adapters/activitypub-base/src/service.rs
index eb77f1d..e3d27d7 100644
--- a/crates/adapters/activitypub-base/src/service.rs
+++ b/crates/adapters/activitypub-base/src/service.rs
@@ -1666,10 +1666,7 @@ impl domain::ports::FederationSchedulerPort for ActivityPubService {
let empty = vec![];
let items = val["orderedItems"].as_array().unwrap_or(&empty);
for item in items {
- let actor_url = item
- .as_str()
- .or_else(|| item["id"].as_str())
- .unwrap_or("");
+ let actor_url = item.as_str().or_else(|| item["id"].as_str()).unwrap_or("");
if !actor_url.is_empty() {
all_urls.push(actor_url.to_string());
}
diff --git a/crates/adapters/activitypub/src/handler.rs b/crates/adapters/activitypub/src/handler.rs
index a2635bb..7267b74 100644
--- a/crates/adapters/activitypub/src/handler.rs
+++ b/crates/adapters/activitypub/src/handler.rs
@@ -141,7 +141,8 @@ impl ApObjectHandler for ThoughtsObjectHandler {
"direct"
};
- let thought_id = self.repo
+ let thought_id = self
+ .repo
.accept_note(
ap_id.as_str(),
&author_id,
diff --git a/crates/adapters/auth/src/api_key_service/tests.rs b/crates/adapters/auth/src/api_key_service/tests.rs
index 03fa9c9..86e795b 100644
--- a/crates/adapters/auth/src/api_key_service/tests.rs
+++ b/crates/adapters/auth/src/api_key_service/tests.rs
@@ -18,7 +18,13 @@ impl ApiKeyRepository for FakeApiKeyRepo {
Ok(())
}
async fn find_by_hash(&self, hash: &str) -> Result , DomainError> {
- Ok(self.0.lock().unwrap().iter().find(|k| k.key_hash == hash).cloned())
+ Ok(self
+ .0
+ .lock()
+ .unwrap()
+ .iter()
+ .find(|k| k.key_hash == hash)
+ .cloned())
}
async fn list_for_user(&self, _uid: &UserId) -> Result, DomainError> {
Ok(vec![])
diff --git a/crates/adapters/event-payload/src/lib.rs b/crates/adapters/event-payload/src/lib.rs
index 54a7825..53fa98b 100644
--- a/crates/adapters/event-payload/src/lib.rs
+++ b/crates/adapters/event-payload/src/lib.rs
@@ -356,6 +356,5 @@ impl TryFrom for DomainEvent {
}
}
-
#[cfg(test)]
mod tests;
diff --git a/crates/adapters/event-transport/src/lib.rs b/crates/adapters/event-transport/src/lib.rs
index 6a44373..4205726 100644
--- a/crates/adapters/event-transport/src/lib.rs
+++ b/crates/adapters/event-transport/src/lib.rs
@@ -109,6 +109,5 @@ impl EventConsumer for EventConsumerAdapter {
}
}
-
#[cfg(test)]
mod tests;
diff --git a/crates/adapters/nats/src/lib.rs b/crates/adapters/nats/src/lib.rs
index 6d11b72..025a6f3 100644
--- a/crates/adapters/nats/src/lib.rs
+++ b/crates/adapters/nats/src/lib.rs
@@ -239,6 +239,5 @@ impl MessageSource for NatsMessageSource {
}
}
-
#[cfg(test)]
mod tests;
diff --git a/crates/adapters/postgres/src/activitypub/mod.rs b/crates/adapters/postgres/src/activitypub/mod.rs
index fb74e64..af31713 100644
--- a/crates/adapters/postgres/src/activitypub/mod.rs
+++ b/crates/adapters/postgres/src/activitypub/mod.rs
@@ -254,12 +254,11 @@ impl ActivityPubRepository for PgActivityPubRepository {
.into_domain()?;
// SELECT the id — works whether the INSERT was a no-op or not (idempotent).
- let row: (uuid::Uuid,) =
- sqlx::query_as("SELECT id FROM thoughts WHERE ap_id=$1")
- .bind(ap_id)
- .fetch_one(&self.pool)
- .await
- .into_domain()?;
+ let row: (uuid::Uuid,) = sqlx::query_as("SELECT id FROM thoughts WHERE ap_id=$1")
+ .bind(ap_id)
+ .fetch_one(&self.pool)
+ .await
+ .into_domain()?;
Ok(ThoughtId::from_uuid(row.0))
}
diff --git a/crates/adapters/postgres/src/activitypub/tests.rs b/crates/adapters/postgres/src/activitypub/tests.rs
index 9da0b57..258e7fa 100644
--- a/crates/adapters/postgres/src/activitypub/tests.rs
+++ b/crates/adapters/postgres/src/activitypub/tests.rs
@@ -1,25 +1,56 @@
- use super::*;
- use activitypub_base::ActivityPubRepository;
- #[sqlx::test(migrations = "./migrations")]
- async fn intern_remote_actor_is_idempotent(pool: sqlx::PgPool) {
- let repo = PgActivityPubRepository::new(pool);
- let url = "https://mastodon.social/users/alice";
- let id1 = repo.intern_remote_actor(url).await.unwrap();
- let id2 = repo.intern_remote_actor(url).await.unwrap();
- assert_eq!(id1, id2);
- }
+use super::*;
+use activitypub_base::ActivityPubRepository;
- #[sqlx::test(migrations = "./migrations")]
- async fn accept_and_retract_note(pool: sqlx::PgPool) {
- let repo = PgActivityPubRepository::new(pool);
- let actor_url = "https://remote.example/users/bob";
- let ap_id = "https://remote.example/notes/1";
- let author = repo.intern_remote_actor(actor_url).await.unwrap();
- repo.accept_note(
- ap_id,
- &author,
- "hello from remote",
+#[sqlx::test(migrations = "./migrations")]
+async fn intern_remote_actor_is_idempotent(pool: sqlx::PgPool) {
+ let repo = PgActivityPubRepository::new(pool);
+ let url = "https://mastodon.social/users/alice";
+ let id1 = repo.intern_remote_actor(url).await.unwrap();
+ let id2 = repo.intern_remote_actor(url).await.unwrap();
+ assert_eq!(id1, id2);
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn accept_and_retract_note(pool: sqlx::PgPool) {
+ let repo = PgActivityPubRepository::new(pool);
+ let actor_url = "https://remote.example/users/bob";
+ let ap_id = "https://remote.example/notes/1";
+ let author = repo.intern_remote_actor(actor_url).await.unwrap();
+ repo.accept_note(
+ ap_id,
+ &author,
+ "hello from remote",
+ chrono::Utc::now(),
+ false,
+ None,
+ "public",
+ None,
+ )
+ .await
+ .unwrap();
+ repo.retract_note(ap_id).await.unwrap();
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn count_local_notes_excludes_remote(pool: sqlx::PgPool) {
+ let repo = PgActivityPubRepository::new(pool);
+ assert_eq!(repo.count_local_notes().await.unwrap(), 0);
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn accept_note_returns_thought_id(pool: sqlx::PgPool) {
+ let repo = PgActivityPubRepository::new(pool.clone());
+ let actor_user_id = repo
+ .intern_remote_actor("https://remote.example/users/alice")
+ .await
+ .unwrap();
+
+ let thought_id = repo
+ .accept_note(
+ "https://remote.example/notes/1",
+ &actor_user_id,
+ "Hello #rust world",
chrono::Utc::now(),
false,
None,
@@ -28,41 +59,11 @@
)
.await
.unwrap();
- repo.retract_note(ap_id).await.unwrap();
- }
- #[sqlx::test(migrations = "./migrations")]
- async fn count_local_notes_excludes_remote(pool: sqlx::PgPool) {
- let repo = PgActivityPubRepository::new(pool);
- assert_eq!(repo.count_local_notes().await.unwrap(), 0);
- }
-
- #[sqlx::test(migrations = "./migrations")]
- async fn accept_note_returns_thought_id(pool: sqlx::PgPool) {
- let repo = PgActivityPubRepository::new(pool.clone());
- let actor_user_id = repo
- .intern_remote_actor("https://remote.example/users/alice")
- .await
- .unwrap();
-
- let thought_id = repo
- .accept_note(
- "https://remote.example/notes/1",
- &actor_user_id,
- "Hello #rust world",
- chrono::Utc::now(),
- false,
- None,
- "public",
- None,
- )
- .await
- .unwrap();
-
- let row: (uuid::Uuid,) = sqlx::query_as("SELECT id FROM thoughts WHERE ap_id=$1")
- .bind("https://remote.example/notes/1")
- .fetch_one(&pool)
- .await
- .unwrap();
- assert_eq!(thought_id.as_uuid(), row.0);
- }
+ let row: (uuid::Uuid,) = sqlx::query_as("SELECT id FROM thoughts WHERE ap_id=$1")
+ .bind("https://remote.example/notes/1")
+ .fetch_one(&pool)
+ .await
+ .unwrap();
+ assert_eq!(thought_id.as_uuid(), row.0);
+}
diff --git a/crates/adapters/postgres/src/api_key/tests.rs b/crates/adapters/postgres/src/api_key/tests.rs
index 703a710..fa1725f 100644
--- a/crates/adapters/postgres/src/api_key/tests.rs
+++ b/crates/adapters/postgres/src/api_key/tests.rs
@@ -1,49 +1,50 @@
- use super::*;
- use crate::user::PgUserRepository;
- use chrono::Utc;
- use domain::ports::UserWriter;
- use domain::{models::user::User, value_objects::*};
- async fn seed_user(pool: &sqlx::PgPool) -> User {
- let repo = PgUserRepository::new(pool.clone());
- let u = User::new_local(
- UserId::new(),
- Username::new("alice").unwrap(),
- Email::new("alice@ex.com").unwrap(),
- PasswordHash("h".into()),
- );
- repo.save(&u).await.unwrap();
- u
- }
+use super::*;
+use crate::user::PgUserRepository;
+use chrono::Utc;
+use domain::ports::UserWriter;
+use domain::{models::user::User, value_objects::*};
- #[sqlx::test(migrations = "./migrations")]
- async fn save_and_find_by_hash(pool: sqlx::PgPool) {
- let user = seed_user(&pool).await;
- let repo = PgApiKeyRepository::new(pool);
- let key = ApiKey {
- id: ApiKeyId::new(),
- user_id: user.id.clone(),
- key_hash: "abc123".into(),
- name: "test".into(),
- created_at: Utc::now(),
- };
- repo.save(&key).await.unwrap();
- let found = repo.find_by_hash("abc123").await.unwrap().unwrap();
- assert_eq!(found.name, "test");
- }
+async fn seed_user(pool: &sqlx::PgPool) -> User {
+ let repo = PgUserRepository::new(pool.clone());
+ let u = User::new_local(
+ UserId::new(),
+ Username::new("alice").unwrap(),
+ Email::new("alice@ex.com").unwrap(),
+ PasswordHash("h".into()),
+ );
+ repo.save(&u).await.unwrap();
+ u
+}
- #[sqlx::test(migrations = "./migrations")]
- async fn delete_key(pool: sqlx::PgPool) {
- let user = seed_user(&pool).await;
- let repo = PgApiKeyRepository::new(pool);
- let key = ApiKey {
- id: ApiKeyId::new(),
- user_id: user.id.clone(),
- key_hash: "def456".into(),
- name: "key2".into(),
- created_at: Utc::now(),
- };
- repo.save(&key).await.unwrap();
- repo.delete(&key.id, &user.id).await.unwrap();
- assert!(repo.find_by_hash("def456").await.unwrap().is_none());
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn save_and_find_by_hash(pool: sqlx::PgPool) {
+ let user = seed_user(&pool).await;
+ let repo = PgApiKeyRepository::new(pool);
+ let key = ApiKey {
+ id: ApiKeyId::new(),
+ user_id: user.id.clone(),
+ key_hash: "abc123".into(),
+ name: "test".into(),
+ created_at: Utc::now(),
+ };
+ repo.save(&key).await.unwrap();
+ let found = repo.find_by_hash("abc123").await.unwrap().unwrap();
+ assert_eq!(found.name, "test");
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn delete_key(pool: sqlx::PgPool) {
+ let user = seed_user(&pool).await;
+ let repo = PgApiKeyRepository::new(pool);
+ let key = ApiKey {
+ id: ApiKeyId::new(),
+ user_id: user.id.clone(),
+ key_hash: "def456".into(),
+ name: "key2".into(),
+ created_at: Utc::now(),
+ };
+ repo.save(&key).await.unwrap();
+ repo.delete(&key.id, &user.id).await.unwrap();
+ assert!(repo.find_by_hash("def456").await.unwrap().is_none());
+}
diff --git a/crates/adapters/postgres/src/block/tests.rs b/crates/adapters/postgres/src/block/tests.rs
index 9473974..f13401e 100644
--- a/crates/adapters/postgres/src/block/tests.rs
+++ b/crates/adapters/postgres/src/block/tests.rs
@@ -1,34 +1,35 @@
- use super::*;
- use crate::test_helpers::seed_user;
- use chrono::Utc;
- use domain::value_objects::*;
- #[sqlx::test(migrations = "./migrations")]
- async fn block_exists(pool: sqlx::PgPool) {
- let alice = seed_user(&pool, "alice", "alice@ex.com").await;
- let bob = seed_user(&pool, "bob", "bob@ex.com").await;
- let repo = PgBlockRepository::new(pool);
- let block = Block {
- blocker_id: alice.id.clone(),
- blocked_id: bob.id.clone(),
- created_at: Utc::now(),
- };
- repo.save(&block).await.unwrap();
- assert!(repo.exists(&alice.id, &bob.id).await.unwrap());
- assert!(!repo.exists(&bob.id, &alice.id).await.unwrap());
- }
+use super::*;
+use crate::test_helpers::seed_user;
+use chrono::Utc;
+use domain::value_objects::*;
- #[sqlx::test(migrations = "./migrations")]
- async fn unblock(pool: sqlx::PgPool) {
- let alice = seed_user(&pool, "alice", "alice@ex.com").await;
- let bob = seed_user(&pool, "bob", "bob@ex.com").await;
- let repo = PgBlockRepository::new(pool);
- let block = Block {
- blocker_id: alice.id.clone(),
- blocked_id: bob.id.clone(),
- created_at: Utc::now(),
- };
- repo.save(&block).await.unwrap();
- repo.delete(&alice.id, &bob.id).await.unwrap();
- assert!(!repo.exists(&alice.id, &bob.id).await.unwrap());
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn block_exists(pool: sqlx::PgPool) {
+ let alice = seed_user(&pool, "alice", "alice@ex.com").await;
+ let bob = seed_user(&pool, "bob", "bob@ex.com").await;
+ let repo = PgBlockRepository::new(pool);
+ let block = Block {
+ blocker_id: alice.id.clone(),
+ blocked_id: bob.id.clone(),
+ created_at: Utc::now(),
+ };
+ repo.save(&block).await.unwrap();
+ assert!(repo.exists(&alice.id, &bob.id).await.unwrap());
+ assert!(!repo.exists(&bob.id, &alice.id).await.unwrap());
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn unblock(pool: sqlx::PgPool) {
+ let alice = seed_user(&pool, "alice", "alice@ex.com").await;
+ let bob = seed_user(&pool, "bob", "bob@ex.com").await;
+ let repo = PgBlockRepository::new(pool);
+ let block = Block {
+ blocker_id: alice.id.clone(),
+ blocked_id: bob.id.clone(),
+ created_at: Utc::now(),
+ };
+ repo.save(&block).await.unwrap();
+ repo.delete(&alice.id, &bob.id).await.unwrap();
+ assert!(!repo.exists(&alice.id, &bob.id).await.unwrap());
+}
diff --git a/crates/adapters/postgres/src/boost/tests.rs b/crates/adapters/postgres/src/boost/tests.rs
index f93291a..a4401c0 100644
--- a/crates/adapters/postgres/src/boost/tests.rs
+++ b/crates/adapters/postgres/src/boost/tests.rs
@@ -1,35 +1,36 @@
- use super::*;
- use crate::test_helpers::seed_user_and_thought;
- use chrono::Utc;
- use domain::value_objects::*;
- #[sqlx::test(migrations = "./migrations")]
- async fn boost_and_count(pool: sqlx::PgPool) {
- let (user, thought) = seed_user_and_thought(&pool).await;
- let repo = PgBoostRepository::new(pool);
- let boost = Boost {
- id: BoostId::new(),
- user_id: user.id.clone(),
- thought_id: thought.id.clone(),
- ap_id: None,
- created_at: Utc::now(),
- };
- repo.save(&boost).await.unwrap();
- assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 1);
- }
+use super::*;
+use crate::test_helpers::seed_user_and_thought;
+use chrono::Utc;
+use domain::value_objects::*;
- #[sqlx::test(migrations = "./migrations")]
- async fn unboost(pool: sqlx::PgPool) {
- let (user, thought) = seed_user_and_thought(&pool).await;
- let repo = PgBoostRepository::new(pool);
- let boost = Boost {
- id: BoostId::new(),
- user_id: user.id.clone(),
- thought_id: thought.id.clone(),
- ap_id: None,
- created_at: Utc::now(),
- };
- repo.save(&boost).await.unwrap();
- repo.delete(&user.id, &thought.id).await.unwrap();
- assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 0);
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn boost_and_count(pool: sqlx::PgPool) {
+ let (user, thought) = seed_user_and_thought(&pool).await;
+ let repo = PgBoostRepository::new(pool);
+ let boost = Boost {
+ id: BoostId::new(),
+ user_id: user.id.clone(),
+ thought_id: thought.id.clone(),
+ ap_id: None,
+ created_at: Utc::now(),
+ };
+ repo.save(&boost).await.unwrap();
+ assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 1);
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn unboost(pool: sqlx::PgPool) {
+ let (user, thought) = seed_user_and_thought(&pool).await;
+ let repo = PgBoostRepository::new(pool);
+ let boost = Boost {
+ id: BoostId::new(),
+ user_id: user.id.clone(),
+ thought_id: thought.id.clone(),
+ ap_id: None,
+ created_at: Utc::now(),
+ };
+ repo.save(&boost).await.unwrap();
+ repo.delete(&user.id, &thought.id).await.unwrap();
+ assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 0);
+}
diff --git a/crates/adapters/postgres/src/feed/tests.rs b/crates/adapters/postgres/src/feed/tests.rs
index 9193601..c6dc143 100644
--- a/crates/adapters/postgres/src/feed/tests.rs
+++ b/crates/adapters/postgres/src/feed/tests.rs
@@ -1,69 +1,76 @@
- use super::*;
- use crate::{thought::PgThoughtRepository, user::PgUserRepository};
- use domain::{
- models::{
- feed::PageParams,
- thought::{Thought, Visibility},
- user::User,
- },
- ports::{FeedQuery, ThoughtRepository, UserWriter},
- value_objects::*,
- };
- async fn seed(pool: &sqlx::PgPool, username: &str, content: &str) -> (User, Thought) {
- let urepo = PgUserRepository::new(pool.clone());
- let trepo = PgThoughtRepository::new(pool.clone());
- let u = User::new_local(
- UserId::new(),
- Username::new(username).unwrap(),
- Email::new(format!("{username}@ex.com")).unwrap(),
- PasswordHash("h".into()),
- );
- urepo.save(&u).await.unwrap();
- let t = Thought::new_local(
- ThoughtId::new(),
- u.id.clone(),
- Content::new_local(content).unwrap(),
+use super::*;
+use crate::{thought::PgThoughtRepository, user::PgUserRepository};
+use domain::{
+ models::{
+ feed::PageParams,
+ thought::{Thought, Visibility},
+ user::User,
+ },
+ ports::{FeedQuery, ThoughtRepository, UserWriter},
+ value_objects::*,
+};
+
+async fn seed(pool: &sqlx::PgPool, username: &str, content: &str) -> (User, Thought) {
+ let urepo = PgUserRepository::new(pool.clone());
+ let trepo = PgThoughtRepository::new(pool.clone());
+ let u = User::new_local(
+ UserId::new(),
+ Username::new(username).unwrap(),
+ Email::new(format!("{username}@ex.com")).unwrap(),
+ PasswordHash("h".into()),
+ );
+ urepo.save(&u).await.unwrap();
+ let t = Thought::new_local(
+ ThoughtId::new(),
+ u.id.clone(),
+ Content::new_local(content).unwrap(),
+ None,
+ Visibility::Public,
+ None,
+ false,
+ );
+ trepo.save(&t).await.unwrap();
+ (u, t)
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn public_feed_returns_local_thoughts(pool: sqlx::PgPool) {
+ let (_, _) = seed(&pool, "alice", "hello").await;
+ let repo = PgFeedRepository::new(pool);
+ let result = repo
+ .query(&FeedQuery::public(
+ PageParams {
+ page: 1,
+ per_page: 20,
+ },
None,
- Visibility::Public,
+ ))
+ .await
+ .unwrap();
+ assert_eq!(result.total, 1);
+ assert_eq!(result.items[0].thought.content.as_str(), "hello");
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn search_returns_matching_thoughts(pool: sqlx::PgPool) {
+ let (_, _) = seed(&pool, "alice", "hello world").await;
+ let (_, _) = seed(&pool, "bob", "goodbye world").await;
+ let repo = PgFeedRepository::new(pool);
+ let result = repo
+ .query(&FeedQuery::search(
+ "hello world",
+ PageParams {
+ page: 1,
+ per_page: 20,
+ },
None,
- false,
- );
- trepo.save(&t).await.unwrap();
- (u, t)
- }
-
- #[sqlx::test(migrations = "./migrations")]
- async fn public_feed_returns_local_thoughts(pool: sqlx::PgPool) {
- let (_, _) = seed(&pool, "alice", "hello").await;
- let repo = PgFeedRepository::new(pool);
- let result = repo
- .query(&FeedQuery::public(
- PageParams { page: 1, per_page: 20 },
- None,
- ))
- .await
- .unwrap();
- assert_eq!(result.total, 1);
- assert_eq!(result.items[0].thought.content.as_str(), "hello");
- }
-
- #[sqlx::test(migrations = "./migrations")]
- async fn search_returns_matching_thoughts(pool: sqlx::PgPool) {
- let (_, _) = seed(&pool, "alice", "hello world").await;
- let (_, _) = seed(&pool, "bob", "goodbye world").await;
- let repo = PgFeedRepository::new(pool);
- let result = repo
- .query(&FeedQuery::search(
- "hello world",
- PageParams { page: 1, per_page: 20 },
- None,
- ))
- .await
- .unwrap();
- assert!(result.total >= 1);
- assert!(result
- .items
- .iter()
- .any(|e| e.thought.content.as_str() == "hello world"));
- }
+ ))
+ .await
+ .unwrap();
+ assert!(result.total >= 1);
+ assert!(result
+ .items
+ .iter()
+ .any(|e| e.thought.content.as_str() == "hello world"));
+}
diff --git a/crates/adapters/postgres/src/follow/tests.rs b/crates/adapters/postgres/src/follow/tests.rs
index 05ba499..e3379d8 100644
--- a/crates/adapters/postgres/src/follow/tests.rs
+++ b/crates/adapters/postgres/src/follow/tests.rs
@@ -1,58 +1,59 @@
- use super::*;
- use crate::test_helpers::seed_user;
- use chrono::Utc;
- use domain::value_objects::*;
- #[sqlx::test(migrations = "./migrations")]
- async fn save_and_find_follow(pool: sqlx::PgPool) {
- let alice = seed_user(&pool, "alice", "alice@ex.com").await;
- let bob = seed_user(&pool, "bob", "bob@ex.com").await;
- let repo = PgFollowRepository::new(pool);
- let follow = Follow {
- follower_id: alice.id.clone(),
- following_id: bob.id.clone(),
- state: FollowState::Accepted,
- ap_id: None,
- created_at: Utc::now(),
- };
- repo.save(&follow).await.unwrap();
- let found = repo.find(&alice.id, &bob.id).await.unwrap().unwrap();
- assert_eq!(found.state, FollowState::Accepted);
- }
+use super::*;
+use crate::test_helpers::seed_user;
+use chrono::Utc;
+use domain::value_objects::*;
- #[sqlx::test(migrations = "./migrations")]
- async fn update_state(pool: sqlx::PgPool) {
- let alice = seed_user(&pool, "alice", "alice@ex.com").await;
- let bob = seed_user(&pool, "bob", "bob@ex.com").await;
- let repo = PgFollowRepository::new(pool);
- let follow = Follow {
- follower_id: alice.id.clone(),
- following_id: bob.id.clone(),
- state: FollowState::Pending,
- ap_id: None,
- created_at: Utc::now(),
- };
- repo.save(&follow).await.unwrap();
- repo.update_state(&alice.id, &bob.id, &FollowState::Accepted)
- .await
- .unwrap();
- let found = repo.find(&alice.id, &bob.id).await.unwrap().unwrap();
- assert_eq!(found.state, FollowState::Accepted);
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn save_and_find_follow(pool: sqlx::PgPool) {
+ let alice = seed_user(&pool, "alice", "alice@ex.com").await;
+ let bob = seed_user(&pool, "bob", "bob@ex.com").await;
+ let repo = PgFollowRepository::new(pool);
+ let follow = Follow {
+ follower_id: alice.id.clone(),
+ following_id: bob.id.clone(),
+ state: FollowState::Accepted,
+ ap_id: None,
+ created_at: Utc::now(),
+ };
+ repo.save(&follow).await.unwrap();
+ let found = repo.find(&alice.id, &bob.id).await.unwrap().unwrap();
+ assert_eq!(found.state, FollowState::Accepted);
+}
- #[sqlx::test(migrations = "./migrations")]
- async fn get_accepted_following_ids(pool: sqlx::PgPool) {
- let alice = seed_user(&pool, "alice", "alice@ex.com").await;
- let bob = seed_user(&pool, "bob", "bob@ex.com").await;
- let repo = PgFollowRepository::new(pool);
- let follow = Follow {
- follower_id: alice.id.clone(),
- following_id: bob.id.clone(),
- state: FollowState::Accepted,
- ap_id: None,
- created_at: Utc::now(),
- };
- repo.save(&follow).await.unwrap();
- let ids = repo.get_accepted_following_ids(&alice.id).await.unwrap();
- assert_eq!(ids, vec![bob.id]);
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn update_state(pool: sqlx::PgPool) {
+ let alice = seed_user(&pool, "alice", "alice@ex.com").await;
+ let bob = seed_user(&pool, "bob", "bob@ex.com").await;
+ let repo = PgFollowRepository::new(pool);
+ let follow = Follow {
+ follower_id: alice.id.clone(),
+ following_id: bob.id.clone(),
+ state: FollowState::Pending,
+ ap_id: None,
+ created_at: Utc::now(),
+ };
+ repo.save(&follow).await.unwrap();
+ repo.update_state(&alice.id, &bob.id, &FollowState::Accepted)
+ .await
+ .unwrap();
+ let found = repo.find(&alice.id, &bob.id).await.unwrap().unwrap();
+ assert_eq!(found.state, FollowState::Accepted);
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn get_accepted_following_ids(pool: sqlx::PgPool) {
+ let alice = seed_user(&pool, "alice", "alice@ex.com").await;
+ let bob = seed_user(&pool, "bob", "bob@ex.com").await;
+ let repo = PgFollowRepository::new(pool);
+ let follow = Follow {
+ follower_id: alice.id.clone(),
+ following_id: bob.id.clone(),
+ state: FollowState::Accepted,
+ ap_id: None,
+ created_at: Utc::now(),
+ };
+ repo.save(&follow).await.unwrap();
+ let ids = repo.get_accepted_following_ids(&alice.id).await.unwrap();
+ assert_eq!(ids, vec![bob.id]);
+}
diff --git a/crates/adapters/postgres/src/lib.rs b/crates/adapters/postgres/src/lib.rs
index 7694f24..2d86fdb 100644
--- a/crates/adapters/postgres/src/lib.rs
+++ b/crates/adapters/postgres/src/lib.rs
@@ -1,15 +1,15 @@
pub mod activitypub;
-pub mod engagement;
pub mod api_key;
pub mod block;
pub mod boost;
mod db_error;
+pub mod engagement;
pub mod failed_event;
-pub mod outbox;
pub mod feed;
pub mod follow;
pub mod like;
pub mod notification;
+pub mod outbox;
pub mod remote_actor;
pub mod remote_actor_connections;
pub mod tag;
diff --git a/crates/adapters/postgres/src/like/tests.rs b/crates/adapters/postgres/src/like/tests.rs
index 1106d67..3c2f14e 100644
--- a/crates/adapters/postgres/src/like/tests.rs
+++ b/crates/adapters/postgres/src/like/tests.rs
@@ -1,35 +1,36 @@
- use super::*;
- use crate::test_helpers::seed_user_and_thought;
- use chrono::Utc;
- use domain::value_objects::*;
- #[sqlx::test(migrations = "./migrations")]
- async fn like_and_count(pool: sqlx::PgPool) {
- let (user, thought) = seed_user_and_thought(&pool).await;
- let repo = PgLikeRepository::new(pool);
- let like = Like {
- id: LikeId::new(),
- user_id: user.id.clone(),
- thought_id: thought.id.clone(),
- ap_id: None,
- created_at: Utc::now(),
- };
- repo.save(&like).await.unwrap();
- assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 1);
- }
+use super::*;
+use crate::test_helpers::seed_user_and_thought;
+use chrono::Utc;
+use domain::value_objects::*;
- #[sqlx::test(migrations = "./migrations")]
- async fn unlike(pool: sqlx::PgPool) {
- let (user, thought) = seed_user_and_thought(&pool).await;
- let repo = PgLikeRepository::new(pool);
- let like = Like {
- id: LikeId::new(),
- user_id: user.id.clone(),
- thought_id: thought.id.clone(),
- ap_id: None,
- created_at: Utc::now(),
- };
- repo.save(&like).await.unwrap();
- repo.delete(&user.id, &thought.id).await.unwrap();
- assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 0);
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn like_and_count(pool: sqlx::PgPool) {
+ let (user, thought) = seed_user_and_thought(&pool).await;
+ let repo = PgLikeRepository::new(pool);
+ let like = Like {
+ id: LikeId::new(),
+ user_id: user.id.clone(),
+ thought_id: thought.id.clone(),
+ ap_id: None,
+ created_at: Utc::now(),
+ };
+ repo.save(&like).await.unwrap();
+ assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 1);
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn unlike(pool: sqlx::PgPool) {
+ let (user, thought) = seed_user_and_thought(&pool).await;
+ let repo = PgLikeRepository::new(pool);
+ let like = Like {
+ id: LikeId::new(),
+ user_id: user.id.clone(),
+ thought_id: thought.id.clone(),
+ ap_id: None,
+ created_at: Utc::now(),
+ };
+ repo.save(&like).await.unwrap();
+ repo.delete(&user.id, &thought.id).await.unwrap();
+ assert_eq!(repo.count_for_thought(&thought.id).await.unwrap(), 0);
+}
diff --git a/crates/adapters/postgres/src/notification/tests.rs b/crates/adapters/postgres/src/notification/tests.rs
index 9a9caf8..876a23f 100644
--- a/crates/adapters/postgres/src/notification/tests.rs
+++ b/crates/adapters/postgres/src/notification/tests.rs
@@ -1,67 +1,68 @@
- use super::*;
- use crate::test_helpers;
- use chrono::Utc;
- use domain::{
- models::{notification::NotificationKind, user::User},
- value_objects::*,
+
+use super::*;
+use crate::test_helpers;
+use chrono::Utc;
+use domain::{
+ models::{notification::NotificationKind, user::User},
+ value_objects::*,
+};
+
+#[sqlx::test(migrations = "./migrations")]
+async fn save_and_list(pool: sqlx::PgPool) {
+ let user = test_helpers::seed_user(&pool, "alice", "alice@ex.com").await;
+ let from_user = test_helpers::seed_user(&pool, "bob", "bob@ex.com").await;
+ let repo = PgNotificationRepository::new(pool);
+ use domain::models::feed::PageParams;
+ let n = Notification {
+ id: NotificationId::new(),
+ user_id: user.id.clone(),
+ kind: NotificationKind::Follow {
+ from_user_id: from_user.id.clone(),
+ },
+ read: false,
+ created_at: Utc::now(),
};
-
- #[sqlx::test(migrations = "./migrations")]
- async fn save_and_list(pool: sqlx::PgPool) {
- let user = test_helpers::seed_user(&pool, "alice", "alice@ex.com").await;
- let from_user = test_helpers::seed_user(&pool, "bob", "bob@ex.com").await;
- let repo = PgNotificationRepository::new(pool);
- use domain::models::feed::PageParams;
- let n = Notification {
- id: NotificationId::new(),
- user_id: user.id.clone(),
- kind: NotificationKind::Follow {
- from_user_id: from_user.id.clone(),
+ repo.save(&n).await.unwrap();
+ let page = repo
+ .list_for_user(
+ &user.id,
+ &PageParams {
+ page: 1,
+ per_page: 20,
},
- read: false,
- created_at: Utc::now(),
- };
- repo.save(&n).await.unwrap();
- let page = repo
- .list_for_user(
- &user.id,
- &PageParams {
- page: 1,
- per_page: 20,
- },
- )
- .await
- .unwrap();
- assert_eq!(page.total, 1);
- assert!(!page.items[0].read);
- }
+ )
+ .await
+ .unwrap();
+ assert_eq!(page.total, 1);
+ assert!(!page.items[0].read);
+}
- #[sqlx::test(migrations = "./migrations")]
- async fn mark_all_read(pool: sqlx::PgPool) {
- let user = test_helpers::seed_user(&pool, "alice", "alice@ex.com").await;
- let from_user = test_helpers::seed_user(&pool, "bob", "bob@ex.com").await;
- let repo = PgNotificationRepository::new(pool);
- use domain::models::feed::PageParams;
- let n = Notification {
- id: NotificationId::new(),
- user_id: user.id.clone(),
- kind: NotificationKind::Follow {
- from_user_id: from_user.id.clone(),
+#[sqlx::test(migrations = "./migrations")]
+async fn mark_all_read(pool: sqlx::PgPool) {
+ let user = test_helpers::seed_user(&pool, "alice", "alice@ex.com").await;
+ let from_user = test_helpers::seed_user(&pool, "bob", "bob@ex.com").await;
+ let repo = PgNotificationRepository::new(pool);
+ use domain::models::feed::PageParams;
+ let n = Notification {
+ id: NotificationId::new(),
+ user_id: user.id.clone(),
+ kind: NotificationKind::Follow {
+ from_user_id: from_user.id.clone(),
+ },
+ read: false,
+ created_at: Utc::now(),
+ };
+ repo.save(&n).await.unwrap();
+ repo.mark_all_read(&user.id).await.unwrap();
+ let page = repo
+ .list_for_user(
+ &user.id,
+ &PageParams {
+ page: 1,
+ per_page: 20,
},
- read: false,
- created_at: Utc::now(),
- };
- repo.save(&n).await.unwrap();
- repo.mark_all_read(&user.id).await.unwrap();
- let page = repo
- .list_for_user(
- &user.id,
- &PageParams {
- page: 1,
- per_page: 20,
- },
- )
- .await
- .unwrap();
- assert!(page.items[0].read);
- }
+ )
+ .await
+ .unwrap();
+ assert!(page.items[0].read);
+}
diff --git a/crates/adapters/postgres/src/tag/tests.rs b/crates/adapters/postgres/src/tag/tests.rs
index 0cb5512..92550dd 100644
--- a/crates/adapters/postgres/src/tag/tests.rs
+++ b/crates/adapters/postgres/src/tag/tests.rs
@@ -1,48 +1,49 @@
- use super::*;
- use crate::{thought::PgThoughtRepository, user::PgUserRepository};
- use domain::ports::{ThoughtRepository, UserWriter};
- use domain::{
- models::{
- thought::{Thought, Visibility},
- user::User,
- },
- value_objects::*,
- };
- #[sqlx::test(migrations = "./migrations")]
- async fn find_or_create_tag(pool: sqlx::PgPool) {
- let repo = PgTagRepository::new(pool);
- let t1 = repo.find_or_create("rust").await.unwrap();
- let t2 = repo.find_or_create("rust").await.unwrap();
- assert_eq!(t1.id, t2.id);
- assert_eq!(t1.name, "rust");
- }
+use super::*;
+use crate::{thought::PgThoughtRepository, user::PgUserRepository};
+use domain::ports::{ThoughtRepository, UserWriter};
+use domain::{
+ models::{
+ thought::{Thought, Visibility},
+ user::User,
+ },
+ value_objects::*,
+};
- #[sqlx::test(migrations = "./migrations")]
- async fn attach_and_list(pool: sqlx::PgPool) {
- let urepo = PgUserRepository::new(pool.clone());
- let trepo = PgThoughtRepository::new(pool.clone());
- let u = User::new_local(
- UserId::new(),
- Username::new("alice").unwrap(),
- Email::new("alice@ex.com").unwrap(),
- PasswordHash("h".into()),
- );
- urepo.save(&u).await.unwrap();
- let t = Thought::new_local(
- ThoughtId::new(),
- u.id.clone(),
- Content::new_local("hi").unwrap(),
- None,
- Visibility::Public,
- None,
- false,
- );
- trepo.save(&t).await.unwrap();
- let repo = PgTagRepository::new(pool);
- let tag = repo.find_or_create("greetings").await.unwrap();
- repo.attach_to_thought(&t.id, tag.id).await.unwrap();
- let tags = repo.list_for_thought(&t.id).await.unwrap();
- assert_eq!(tags.len(), 1);
- assert_eq!(tags[0].name, "greetings");
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn find_or_create_tag(pool: sqlx::PgPool) {
+ let repo = PgTagRepository::new(pool);
+ let t1 = repo.find_or_create("rust").await.unwrap();
+ let t2 = repo.find_or_create("rust").await.unwrap();
+ assert_eq!(t1.id, t2.id);
+ assert_eq!(t1.name, "rust");
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn attach_and_list(pool: sqlx::PgPool) {
+ let urepo = PgUserRepository::new(pool.clone());
+ let trepo = PgThoughtRepository::new(pool.clone());
+ let u = User::new_local(
+ UserId::new(),
+ Username::new("alice").unwrap(),
+ Email::new("alice@ex.com").unwrap(),
+ PasswordHash("h".into()),
+ );
+ urepo.save(&u).await.unwrap();
+ let t = Thought::new_local(
+ ThoughtId::new(),
+ u.id.clone(),
+ Content::new_local("hi").unwrap(),
+ None,
+ Visibility::Public,
+ None,
+ false,
+ );
+ trepo.save(&t).await.unwrap();
+ let repo = PgTagRepository::new(pool);
+ let tag = repo.find_or_create("greetings").await.unwrap();
+ repo.attach_to_thought(&t.id, tag.id).await.unwrap();
+ let tags = repo.list_for_thought(&t.id).await.unwrap();
+ assert_eq!(tags.len(), 1);
+ assert_eq!(tags[0].name, "greetings");
+}
diff --git a/crates/adapters/postgres/src/thought/tests.rs b/crates/adapters/postgres/src/thought/tests.rs
index c227c4d..82426e6 100644
--- a/crates/adapters/postgres/src/thought/tests.rs
+++ b/crates/adapters/postgres/src/thought/tests.rs
@@ -1,90 +1,91 @@
- use super::*;
- use crate::test_helpers::seed_user;
- use domain::{
- models::thought::{Thought, Visibility},
- value_objects::*,
- };
- #[sqlx::test(migrations = "./migrations")]
- async fn save_and_find_thought(pool: sqlx::PgPool) {
- let user = seed_user(&pool, "alice", "alice@ex.com").await;
- let repo = PgThoughtRepository::new(pool);
- let t = Thought::new_local(
- ThoughtId::new(),
- user.id.clone(),
- Content::new_local("hello world").unwrap(),
- None,
- Visibility::Public,
- None,
- false,
- );
- repo.save(&t).await.unwrap();
- let found = repo.find_by_id(&t.id).await.unwrap().unwrap();
- assert_eq!(found.content.as_str(), "hello world");
- assert!(found.local);
- }
+use super::*;
+use crate::test_helpers::seed_user;
+use domain::{
+ models::thought::{Thought, Visibility},
+ value_objects::*,
+};
- #[sqlx::test(migrations = "./migrations")]
- async fn delete_thought(pool: sqlx::PgPool) {
- let user = seed_user(&pool, "bob", "bob@ex.com").await;
- let repo = PgThoughtRepository::new(pool);
- let t = Thought::new_local(
- ThoughtId::new(),
- user.id.clone(),
- Content::new_local("bye").unwrap(),
- None,
- Visibility::Public,
- None,
- false,
- );
- repo.save(&t).await.unwrap();
- repo.delete(&t.id, &user.id).await.unwrap();
- assert!(repo.find_by_id(&t.id).await.unwrap().is_none());
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn save_and_find_thought(pool: sqlx::PgPool) {
+ let user = seed_user(&pool, "alice", "alice@ex.com").await;
+ let repo = PgThoughtRepository::new(pool);
+ let t = Thought::new_local(
+ ThoughtId::new(),
+ user.id.clone(),
+ Content::new_local("hello world").unwrap(),
+ None,
+ Visibility::Public,
+ None,
+ false,
+ );
+ repo.save(&t).await.unwrap();
+ let found = repo.find_by_id(&t.id).await.unwrap().unwrap();
+ assert_eq!(found.content.as_str(), "hello world");
+ assert!(found.local);
+}
- #[sqlx::test(migrations = "./migrations")]
- async fn delete_wrong_owner_returns_not_found(pool: sqlx::PgPool) {
- let alice = seed_user(&pool, "alice", "alice@ex.com").await;
- let bob = seed_user(&pool, "bob", "bob@ex.com").await;
- let repo = PgThoughtRepository::new(pool);
- let t = Thought::new_local(
- ThoughtId::new(),
- alice.id.clone(),
- Content::new_local("secret").unwrap(),
- None,
- Visibility::Public,
- None,
- false,
- );
- repo.save(&t).await.unwrap();
- let err = repo.delete(&t.id, &bob.id).await.unwrap_err();
- assert!(matches!(err, DomainError::NotFound));
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn delete_thought(pool: sqlx::PgPool) {
+ let user = seed_user(&pool, "bob", "bob@ex.com").await;
+ let repo = PgThoughtRepository::new(pool);
+ let t = Thought::new_local(
+ ThoughtId::new(),
+ user.id.clone(),
+ Content::new_local("bye").unwrap(),
+ None,
+ Visibility::Public,
+ None,
+ false,
+ );
+ repo.save(&t).await.unwrap();
+ repo.delete(&t.id, &user.id).await.unwrap();
+ assert!(repo.find_by_id(&t.id).await.unwrap().is_none());
+}
- #[sqlx::test(migrations = "./migrations")]
- async fn get_thread_returns_root_and_replies(pool: sqlx::PgPool) {
- let user = seed_user(&pool, "charlie", "charlie@ex.com").await;
- let repo = PgThoughtRepository::new(pool);
- let root = Thought::new_local(
- ThoughtId::new(),
- user.id.clone(),
- Content::new_local("root").unwrap(),
- None,
- Visibility::Public,
- None,
- false,
- );
- let reply = Thought::new_local(
- ThoughtId::new(),
- user.id.clone(),
- Content::new_local("reply").unwrap(),
- Some(root.id.clone()),
- Visibility::Public,
- None,
- false,
- );
- repo.save(&root).await.unwrap();
- repo.save(&reply).await.unwrap();
- let thread = repo.get_thread(&root.id).await.unwrap();
- assert_eq!(thread.len(), 2);
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn delete_wrong_owner_returns_not_found(pool: sqlx::PgPool) {
+ let alice = seed_user(&pool, "alice", "alice@ex.com").await;
+ let bob = seed_user(&pool, "bob", "bob@ex.com").await;
+ let repo = PgThoughtRepository::new(pool);
+ let t = Thought::new_local(
+ ThoughtId::new(),
+ alice.id.clone(),
+ Content::new_local("secret").unwrap(),
+ None,
+ Visibility::Public,
+ None,
+ false,
+ );
+ repo.save(&t).await.unwrap();
+ let err = repo.delete(&t.id, &bob.id).await.unwrap_err();
+ assert!(matches!(err, DomainError::NotFound));
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn get_thread_returns_root_and_replies(pool: sqlx::PgPool) {
+ let user = seed_user(&pool, "charlie", "charlie@ex.com").await;
+ let repo = PgThoughtRepository::new(pool);
+ let root = Thought::new_local(
+ ThoughtId::new(),
+ user.id.clone(),
+ Content::new_local("root").unwrap(),
+ None,
+ Visibility::Public,
+ None,
+ false,
+ );
+ let reply = Thought::new_local(
+ ThoughtId::new(),
+ user.id.clone(),
+ Content::new_local("reply").unwrap(),
+ Some(root.id.clone()),
+ Visibility::Public,
+ None,
+ false,
+ );
+ repo.save(&root).await.unwrap();
+ repo.save(&reply).await.unwrap();
+ let thread = repo.get_thread(&root.id).await.unwrap();
+ assert_eq!(thread.len(), 2);
+}
diff --git a/crates/adapters/postgres/src/top_friend/tests.rs b/crates/adapters/postgres/src/top_friend/tests.rs
index 8e14acc..1042c93 100644
--- a/crates/adapters/postgres/src/top_friend/tests.rs
+++ b/crates/adapters/postgres/src/top_friend/tests.rs
@@ -1,47 +1,48 @@
- use super::*;
- use crate::user::PgUserRepository;
- use domain::ports::UserWriter;
- use domain::{models::user::User, value_objects::*};
- async fn seed_user(pool: &sqlx::PgPool, username: &str, email: &str) -> User {
- let repo = PgUserRepository::new(pool.clone());
- let u = User::new_local(
- UserId::new(),
- Username::new(username).unwrap(),
- Email::new(email).unwrap(),
- PasswordHash("h".into()),
- );
- repo.save(&u).await.unwrap();
- u
- }
+use super::*;
+use crate::user::PgUserRepository;
+use domain::ports::UserWriter;
+use domain::{models::user::User, value_objects::*};
- #[sqlx::test(migrations = "./migrations")]
- async fn set_and_list_top_friends(pool: sqlx::PgPool) {
- let alice = seed_user(&pool, "alice", "alice@ex.com").await;
- let bob = seed_user(&pool, "bob", "bob@ex.com").await;
- let repo = PgTopFriendRepository::new(pool);
- repo.set_top_friends(&alice.id, vec![(bob.id.clone(), 1)])
- .await
- .unwrap();
- let friends = repo.list_for_user(&alice.id).await.unwrap();
- assert_eq!(friends.len(), 1);
- assert_eq!(friends[0].0.position, 1);
- assert_eq!(friends[0].1.username.as_str(), "bob");
- }
+async fn seed_user(pool: &sqlx::PgPool, username: &str, email: &str) -> User {
+ let repo = PgUserRepository::new(pool.clone());
+ let u = User::new_local(
+ UserId::new(),
+ Username::new(username).unwrap(),
+ Email::new(email).unwrap(),
+ PasswordHash("h".into()),
+ );
+ repo.save(&u).await.unwrap();
+ u
+}
- #[sqlx::test(migrations = "./migrations")]
- async fn replace_top_friends(pool: sqlx::PgPool) {
- let alice = seed_user(&pool, "alice", "alice@ex.com").await;
- let bob = seed_user(&pool, "bob", "bob@ex.com").await;
- let carol = seed_user(&pool, "carol", "carol@ex.com").await;
- let repo = PgTopFriendRepository::new(pool);
- repo.set_top_friends(&alice.id, vec![(bob.id.clone(), 1)])
- .await
- .unwrap();
- repo.set_top_friends(&alice.id, vec![(carol.id.clone(), 1)])
- .await
- .unwrap();
- let friends = repo.list_for_user(&alice.id).await.unwrap();
- assert_eq!(friends.len(), 1);
- assert_eq!(friends[0].1.username.as_str(), "carol");
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn set_and_list_top_friends(pool: sqlx::PgPool) {
+ let alice = seed_user(&pool, "alice", "alice@ex.com").await;
+ let bob = seed_user(&pool, "bob", "bob@ex.com").await;
+ let repo = PgTopFriendRepository::new(pool);
+ repo.set_top_friends(&alice.id, vec![(bob.id.clone(), 1)])
+ .await
+ .unwrap();
+ let friends = repo.list_for_user(&alice.id).await.unwrap();
+ assert_eq!(friends.len(), 1);
+ assert_eq!(friends[0].0.position, 1);
+ assert_eq!(friends[0].1.username.as_str(), "bob");
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn replace_top_friends(pool: sqlx::PgPool) {
+ let alice = seed_user(&pool, "alice", "alice@ex.com").await;
+ let bob = seed_user(&pool, "bob", "bob@ex.com").await;
+ let carol = seed_user(&pool, "carol", "carol@ex.com").await;
+ let repo = PgTopFriendRepository::new(pool);
+ repo.set_top_friends(&alice.id, vec![(bob.id.clone(), 1)])
+ .await
+ .unwrap();
+ repo.set_top_friends(&alice.id, vec![(carol.id.clone(), 1)])
+ .await
+ .unwrap();
+ let friends = repo.list_for_user(&alice.id).await.unwrap();
+ assert_eq!(friends.len(), 1);
+ assert_eq!(friends[0].1.username.as_str(), "carol");
+}
diff --git a/crates/adapters/postgres/src/user/mod.rs b/crates/adapters/postgres/src/user/mod.rs
index 1f68bd7..9e30ad8 100644
--- a/crates/adapters/postgres/src/user/mod.rs
+++ b/crates/adapters/postgres/src/user/mod.rs
@@ -139,7 +139,10 @@ impl UserReader for PgUserRepository {
.into_domain()
}
- async fn list_paginated(&self, page: PageParams) -> Result, DomainError> {
+ async fn list_paginated(
+ &self,
+ page: PageParams,
+ ) -> Result, DomainError> {
#[derive(sqlx::FromRow)]
struct Row {
id: uuid::Uuid,
@@ -187,7 +190,12 @@ impl UserReader for PgUserRepository {
following_count: r.following_count,
})
.collect();
- Ok(Paginated { items, total, page: page.page, per_page: page.per_page })
+ Ok(Paginated {
+ items,
+ total,
+ page: page.page,
+ per_page: page.per_page,
+ })
}
async fn find_by_ids(&self, ids: &[UserId]) -> Result, DomainError> {
@@ -195,18 +203,19 @@ impl UserReader for PgUserRepository {
return Ok(HashMap::new());
}
let uuids: Vec = ids.iter().map(|id| id.as_uuid()).collect();
- let rows = sqlx::query_as::<_, UserRow>(
- &format!("{USER_SELECT} WHERE id = ANY($1)")
- )
- .bind(&uuids[..])
- .fetch_all(&self.pool)
- .await
- .into_domain()?;
+ let rows = sqlx::query_as::<_, UserRow>(&format!("{USER_SELECT} WHERE id = ANY($1)"))
+ .bind(&uuids[..])
+ .fetch_all(&self.pool)
+ .await
+ .into_domain()?;
- Ok(rows.into_iter().map(|r| {
- let user = User::from(r);
- (user.id.clone(), user)
- }).collect())
+ Ok(rows
+ .into_iter()
+ .map(|r| {
+ let user = User::from(r);
+ (user.id.clone(), user)
+ })
+ .collect())
}
}
diff --git a/crates/adapters/postgres/src/user/tests.rs b/crates/adapters/postgres/src/user/tests.rs
index b7cf915..5f5aa92 100644
--- a/crates/adapters/postgres/src/user/tests.rs
+++ b/crates/adapters/postgres/src/user/tests.rs
@@ -1,69 +1,70 @@
- use super::*;
- use domain::{models::user::User, value_objects::*};
- #[sqlx::test(migrations = "./migrations")]
- async fn save_and_find_by_id(pool: sqlx::PgPool) {
- let repo = PgUserRepository::new(pool);
- let user = User::new_local(
- UserId::new(),
- Username::new("alice").unwrap(),
- Email::new("alice@ex.com").unwrap(),
- PasswordHash("hash".into()),
- );
- repo.save(&user).await.unwrap();
- let found = repo.find_by_id(&user.id).await.unwrap().unwrap();
- assert_eq!(found.username.as_str(), "alice");
- assert_eq!(found.email.as_str(), "alice@ex.com");
- }
+use super::*;
+use domain::{models::user::User, value_objects::*};
- #[sqlx::test(migrations = "./migrations")]
- async fn find_by_username_returns_none_when_missing(pool: sqlx::PgPool) {
- let repo = PgUserRepository::new(pool);
- let result = repo
- .find_by_username(&Username::new("ghost").unwrap())
- .await
- .unwrap();
- assert!(result.is_none());
- }
+#[sqlx::test(migrations = "./migrations")]
+async fn save_and_find_by_id(pool: sqlx::PgPool) {
+ let repo = PgUserRepository::new(pool);
+ let user = User::new_local(
+ UserId::new(),
+ Username::new("alice").unwrap(),
+ Email::new("alice@ex.com").unwrap(),
+ PasswordHash("hash".into()),
+ );
+ repo.save(&user).await.unwrap();
+ let found = repo.find_by_id(&user.id).await.unwrap().unwrap();
+ assert_eq!(found.username.as_str(), "alice");
+ assert_eq!(found.email.as_str(), "alice@ex.com");
+}
- #[sqlx::test(migrations = "./migrations")]
- async fn find_by_email(pool: sqlx::PgPool) {
- let repo = PgUserRepository::new(pool);
- let user = User::new_local(
- UserId::new(),
- Username::new("bob").unwrap(),
- Email::new("bob@ex.com").unwrap(),
- PasswordHash("hash".into()),
- );
- repo.save(&user).await.unwrap();
- let found = repo
- .find_by_email(&Email::new("bob@ex.com").unwrap())
- .await
- .unwrap();
- assert!(found.is_some());
- }
-
- #[sqlx::test(migrations = "./migrations")]
- async fn update_profile_changes_fields(pool: sqlx::PgPool) {
- let repo = PgUserRepository::new(pool);
- let user = User::new_local(
- UserId::new(),
- Username::new("charlie").unwrap(),
- Email::new("charlie@ex.com").unwrap(),
- PasswordHash("hash".into()),
- );
- repo.save(&user).await.unwrap();
- repo.update_profile(
- &user.id,
- Some("Charlie".into()),
- Some("bio".into()),
- None,
- None,
- None,
- )
+#[sqlx::test(migrations = "./migrations")]
+async fn find_by_username_returns_none_when_missing(pool: sqlx::PgPool) {
+ let repo = PgUserRepository::new(pool);
+ let result = repo
+ .find_by_username(&Username::new("ghost").unwrap())
.await
.unwrap();
- let found = repo.find_by_id(&user.id).await.unwrap().unwrap();
- assert_eq!(found.display_name.as_deref(), Some("Charlie"));
- assert_eq!(found.bio.as_deref(), Some("bio"));
- }
+ assert!(result.is_none());
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn find_by_email(pool: sqlx::PgPool) {
+ let repo = PgUserRepository::new(pool);
+ let user = User::new_local(
+ UserId::new(),
+ Username::new("bob").unwrap(),
+ Email::new("bob@ex.com").unwrap(),
+ PasswordHash("hash".into()),
+ );
+ repo.save(&user).await.unwrap();
+ let found = repo
+ .find_by_email(&Email::new("bob@ex.com").unwrap())
+ .await
+ .unwrap();
+ assert!(found.is_some());
+}
+
+#[sqlx::test(migrations = "./migrations")]
+async fn update_profile_changes_fields(pool: sqlx::PgPool) {
+ let repo = PgUserRepository::new(pool);
+ let user = User::new_local(
+ UserId::new(),
+ Username::new("charlie").unwrap(),
+ Email::new("charlie@ex.com").unwrap(),
+ PasswordHash("hash".into()),
+ );
+ repo.save(&user).await.unwrap();
+ repo.update_profile(
+ &user.id,
+ Some("Charlie".into()),
+ Some("bio".into()),
+ None,
+ None,
+ None,
+ )
+ .await
+ .unwrap();
+ let found = repo.find_by_id(&user.id).await.unwrap().unwrap();
+ assert_eq!(found.display_name.as_deref(), Some("Charlie"));
+ assert_eq!(found.bio.as_deref(), Some("bio"));
+}
diff --git a/crates/application/src/services/federation_event/tests.rs b/crates/application/src/services/federation_event/tests.rs
index 9a407d8..b6ac54e 100644
--- a/crates/application/src/services/federation_event/tests.rs
+++ b/crates/application/src/services/federation_event/tests.rs
@@ -1,7 +1,7 @@
use super::*;
+use crate::testing::TestApRepo;
use activitypub_base::{ActorApUrls, OutboundFederationPort};
use async_trait::async_trait;
-use crate::testing::TestApRepo;
use domain::{
errors::DomainError,
events::DomainEvent,
@@ -56,21 +56,12 @@ impl OutboundFederationPort for SpyPort {
self.announced.lock().unwrap().push(ap_id.to_string());
Ok(())
}
- async fn broadcast_undo_announce(
- &self,
- _: &UserId,
- ap_id: &str,
- ) -> Result<(), DomainError> {
+ async fn broadcast_undo_announce(&self, _: &UserId, ap_id: &str) -> Result<(), DomainError> {
self.undo_announced.lock().unwrap().push(ap_id.to_string());
Ok(())
}
- async fn broadcast_like(
- &self,
- _: &UserId,
- ap_id: &str,
- _: &str,
- ) -> Result<(), DomainError> {
+ async fn broadcast_like(&self, _: &UserId, ap_id: &str, _: &str) -> Result<(), DomainError> {
self.liked.lock().unwrap().push(ap_id.to_string());
Ok(())
}
@@ -123,7 +114,11 @@ fn svc(store: &TestStore, spy: Arc) -> FederationEventService {
}
}
-fn svc_with_ap(store: &TestStore, ap_repo: TestApRepo, spy: Arc) -> FederationEventService {
+fn svc_with_ap(
+ store: &TestStore,
+ ap_repo: TestApRepo,
+ spy: Arc,
+) -> FederationEventService {
FederationEventService {
thoughts: Arc::new(store.clone()),
users: Arc::new(store.clone()),
diff --git a/crates/application/src/testing.rs b/crates/application/src/testing.rs
index b35a40c..64e2653 100644
--- a/crates/application/src/testing.rs
+++ b/crates/application/src/testing.rs
@@ -106,11 +106,7 @@ impl ActivityPubRepository for TestApRepo {
) -> Result {
Ok(ThoughtId::from_uuid(uuid::Uuid::new_v4()))
}
- async fn apply_note_update(
- &self,
- _ap_id: &str,
- _new_content: &str,
- ) -> Result<(), DomainError> {
+ async fn apply_note_update(&self, _ap_id: &str, _new_content: &str) -> Result<(), DomainError> {
Ok(())
}
async fn retract_note(&self, _ap_id: &str) -> Result<(), DomainError> {
diff --git a/crates/application/src/use_cases/auth/mod.rs b/crates/application/src/use_cases/auth/mod.rs
index be41385..9359536 100644
--- a/crates/application/src/use_cases/auth/mod.rs
+++ b/crates/application/src/use_cases/auth/mod.rs
@@ -34,21 +34,16 @@ pub async fn register(
}
let hash = hasher.hash(&input.password).await?;
let user = User::new_local(UserId::new(), username, email, hash);
- users
- .save(&user)
- .await
- .map_err(|e| match e {
- DomainError::UniqueViolation { field: "username" } => {
- DomainError::Conflict("username taken".into())
- }
- DomainError::UniqueViolation { field: "email" } => {
- DomainError::Conflict("email taken".into())
- }
- DomainError::UniqueViolation { .. } => {
- DomainError::Conflict("already exists".into())
- }
- other => other,
- })?;
+ users.save(&user).await.map_err(|e| match e {
+ DomainError::UniqueViolation { field: "username" } => {
+ DomainError::Conflict("username taken".into())
+ }
+ DomainError::UniqueViolation { field: "email" } => {
+ DomainError::Conflict("email taken".into())
+ }
+ DomainError::UniqueViolation { .. } => DomainError::Conflict("already exists".into()),
+ other => other,
+ })?;
events
.publish(&DomainEvent::UserRegistered {
user_id: user.id.clone(),
diff --git a/crates/application/src/use_cases/auth/tests.rs b/crates/application/src/use_cases/auth/tests.rs
index 86e4d1b..d5985eb 100644
--- a/crates/application/src/use_cases/auth/tests.rs
+++ b/crates/application/src/use_cases/auth/tests.rs
@@ -3,7 +3,10 @@ use async_trait::async_trait;
use domain::{
errors::DomainError,
events::DomainEvent,
- models::{feed::{PageParams, Paginated, UserSummary}, user::User},
+ models::{
+ feed::{PageParams, Paginated, UserSummary},
+ user::User,
+ },
ports::{AuthService, GeneratedToken, PasswordHasher, UserReader, UserWriter},
testing::{NoOpEventPublisher, TestStore},
value_objects::{Email, PasswordHash, UserId, Username},
@@ -19,10 +22,7 @@ impl UserReader for ConflictOnSaveStore {
async fn find_by_id(&self, id: &UserId) -> Result, DomainError> {
self.0.find_by_id(id).await
}
- async fn find_by_username(
- &self,
- username: &Username,
- ) -> Result , DomainError> {
+ async fn find_by_username(&self, username: &Username) -> Result , DomainError> {
self.0.find_by_username(username).await
}
async fn find_by_email(&self, email: &Email) -> Result , DomainError> {
@@ -34,10 +34,16 @@ impl UserReader for ConflictOnSaveStore {
async fn count(&self) -> Result {
self.0.count().await
}
- async fn list_paginated(&self, page: PageParams) -> Result, DomainError> {
+ async fn list_paginated(
+ &self,
+ page: PageParams,
+ ) -> Result, DomainError> {
self.0.list_paginated(page).await
}
- async fn find_by_ids(&self, ids: &[UserId]) -> Result, DomainError> {
+ async fn find_by_ids(
+ &self,
+ ids: &[UserId],
+ ) -> Result, DomainError> {
self.0.find_by_ids(ids).await
}
}
@@ -57,7 +63,14 @@ impl UserWriter for ConflictOnSaveStore {
custom_css: Option,
) -> Result<(), DomainError> {
self.0
- .update_profile(user_id, display_name, bio, avatar_url, header_url, custom_css)
+ .update_profile(
+ user_id,
+ display_name,
+ bio,
+ avatar_url,
+ header_url,
+ custom_css,
+ )
.await
}
}
@@ -67,10 +80,7 @@ impl UserReader for EmailConflictOnSaveStore {
async fn find_by_id(&self, id: &UserId) -> Result, DomainError> {
self.0.find_by_id(id).await
}
- async fn find_by_username(
- &self,
- username: &Username,
- ) -> Result , DomainError> {
+ async fn find_by_username(&self, username: &Username) -> Result , DomainError> {
self.0.find_by_username(username).await
}
async fn find_by_email(&self, email: &Email) -> Result , DomainError> {
@@ -82,10 +92,16 @@ impl UserReader for EmailConflictOnSaveStore {
async fn count(&self) -> Result {
self.0.count().await
}
- async fn list_paginated(&self, page: PageParams) -> Result, DomainError> {
+ async fn list_paginated(
+ &self,
+ page: PageParams,
+ ) -> Result, DomainError> {
self.0.list_paginated(page).await
}
- async fn find_by_ids(&self, ids: &[UserId]) -> Result, DomainError> {
+ async fn find_by_ids(
+ &self,
+ ids: &[UserId],
+ ) -> Result, DomainError> {
self.0.find_by_ids(ids).await
}
}
@@ -105,7 +121,14 @@ impl UserWriter for EmailConflictOnSaveStore {
custom_css: Option,
) -> Result<(), DomainError> {
self.0
- .update_profile(user_id, display_name, bio, avatar_url, header_url, custom_css)
+ .update_profile(
+ user_id,
+ display_name,
+ bio,
+ avatar_url,
+ header_url,
+ custom_css,
+ )
.await
}
}
diff --git a/crates/application/src/use_cases/federation_management/mod.rs b/crates/application/src/use_cases/federation_management/mod.rs
index a9f2771..f4712e1 100644
--- a/crates/application/src/use_cases/federation_management/mod.rs
+++ b/crates/application/src/use_cases/federation_management/mod.rs
@@ -7,9 +7,9 @@ use domain::{
remote_actor::RemoteActor,
},
ports::{
- EventPublisher, FederationActionPort, FederationFollowPort,
- FederationFollowRequestPort, FederationSchedulerPort, FeedQuery, FeedRepository,
- FollowRepository, RemoteActorConnectionRepository, UserReader,
+ EventPublisher, FederationActionPort, FederationFollowPort, FederationFollowRequestPort,
+ FederationSchedulerPort, FeedQuery, FeedRepository, FollowRepository,
+ RemoteActorConnectionRepository, UserReader,
},
value_objects::UserId,
};
@@ -86,7 +86,13 @@ pub async fn get_remote_actor_posts(
Some(id) => id,
None => ap_repo.intern_remote_actor(&actor.url).await?,
};
- let result = feed.query(&FeedQuery::user(author_id, page.clone(), viewer_id.cloned())).await?;
+ let result = feed
+ .query(&FeedQuery::user(
+ author_id,
+ page.clone(),
+ viewer_id.cloned(),
+ ))
+ .await?;
if let Some(outbox_url) = actor.outbox_url {
let _ = scheduler
.schedule_actor_posts_fetch(&actor.url, &outbox_url)
diff --git a/crates/application/src/use_cases/feed.rs b/crates/application/src/use_cases/feed.rs
index b384e3e..b16e057 100644
--- a/crates/application/src/use_cases/feed.rs
+++ b/crates/application/src/use_cases/feed.rs
@@ -13,5 +13,6 @@ pub async fn get_home_feed(
) -> Result, DomainError> {
let mut following_ids = follows.get_accepted_following_ids(user_id).await?;
following_ids.push(user_id.clone());
- feed.query(&FeedQuery::home(user_id.clone(), following_ids, page)).await
+ feed.query(&FeedQuery::home(user_id.clone(), following_ids, page))
+ .await
}
diff --git a/crates/application/src/use_cases/thoughts/mod.rs b/crates/application/src/use_cases/thoughts/mod.rs
index 6249131..f9627bd 100644
--- a/crates/application/src/use_cases/thoughts/mod.rs
+++ b/crates/application/src/use_cases/thoughts/mod.rs
@@ -5,7 +5,10 @@ use domain::{
feed::{EngagementStats, FeedEntry},
thought::{Thought, Visibility},
},
- ports::{EngagementRepository, EventPublisher, OutboxWriter, TagRepository, ThoughtRepository, UserReader},
+ ports::{
+ EngagementRepository, EventPublisher, OutboxWriter, TagRepository, ThoughtRepository,
+ UserReader,
+ },
value_objects::{Content, ThoughtId, UserId},
};
@@ -133,10 +136,20 @@ pub async fn get_thought_view(
.await?
.ok_or(DomainError::NotFound)?;
let mut map = engagement.get_for_thoughts(&[id.clone()], viewer).await?;
- let (stats, viewer_ctx) = map.remove(id).unwrap_or(
- (EngagementStats { like_count: 0, boost_count: 0, reply_count: 0 }, None)
- );
- Ok(FeedEntry { thought, author, stats, viewer: viewer_ctx })
+ let (stats, viewer_ctx) = map.remove(id).unwrap_or((
+ EngagementStats {
+ like_count: 0,
+ boost_count: 0,
+ reply_count: 0,
+ },
+ None,
+ ));
+ Ok(FeedEntry {
+ thought,
+ author,
+ stats,
+ viewer: viewer_ctx,
+ })
}
/// Fetches a thread (root + replies) enriched with authors + real engagement stats.
@@ -169,10 +182,20 @@ pub async fn get_thread_views(
.get(&thought.user_id)
.cloned()
.ok_or(DomainError::NotFound)?;
- let (stats, viewer_ctx) = engagement_map.remove(&thought.id).unwrap_or(
- (EngagementStats { like_count: 0, boost_count: 0, reply_count: 0 }, None)
- );
- entries.push(FeedEntry { thought, author, stats, viewer: viewer_ctx });
+ let (stats, viewer_ctx) = engagement_map.remove(&thought.id).unwrap_or((
+ EngagementStats {
+ like_count: 0,
+ boost_count: 0,
+ reply_count: 0,
+ },
+ None,
+ ));
+ entries.push(FeedEntry {
+ thought,
+ author,
+ stats,
+ viewer: viewer_ctx,
+ });
}
Ok(entries)
}
diff --git a/crates/application/src/use_cases/thoughts/tests.rs b/crates/application/src/use_cases/thoughts/tests.rs
index 6d69b04..c44d265 100644
--- a/crates/application/src/use_cases/thoughts/tests.rs
+++ b/crates/application/src/use_cases/thoughts/tests.rs
@@ -31,9 +31,16 @@ async fn create_thought_saves_and_stages_outbox_event() {
let outbox = TestOutbox::default();
let u = user();
store.users.lock().unwrap().push(u.clone());
- let out = create_thought(&store, &store, &store, &NoOpEventPublisher, &outbox, input(u.id.clone()))
- .await
- .unwrap();
+ let out = create_thought(
+ &store,
+ &store,
+ &store,
+ &NoOpEventPublisher,
+ &outbox,
+ input(u.id.clone()),
+ )
+ .await
+ .unwrap();
assert_eq!(out.thought.content.as_str(), "hello");
let staged = outbox.staged();
assert_eq!(staged.len(), 1);
@@ -64,7 +71,9 @@ async fn delete_thought_stages_outbox_event() {
let staged = outbox.staged();
assert_eq!(staged.len(), 1);
- assert!(matches!(&staged[0], DomainEvent::ThoughtDeleted { thought_id, .. } if *thought_id == tid));
+ assert!(
+ matches!(&staged[0], DomainEvent::ThoughtDeleted { thought_id, .. } if *thought_id == tid)
+ );
}
#[tokio::test]
@@ -82,9 +91,15 @@ async fn delete_own_thought_succeeds() {
)
.await
.unwrap();
- delete_thought(&store, &NoOpEventPublisher, &NoOpOutboxWriter, &out.thought.id, &u.id)
- .await
- .unwrap();
+ delete_thought(
+ &store,
+ &NoOpEventPublisher,
+ &NoOpOutboxWriter,
+ &out.thought.id,
+ &u.id,
+ )
+ .await
+ .unwrap();
assert!(store.thoughts.lock().unwrap().is_empty());
}
@@ -113,9 +128,15 @@ async fn delete_other_thought_returns_not_found() {
)
.await
.unwrap();
- let err = delete_thought(&store, &NoOpEventPublisher, &NoOpOutboxWriter, &out.thought.id, &bob.id)
- .await
- .unwrap_err();
+ let err = delete_thought(
+ &store,
+ &NoOpEventPublisher,
+ &NoOpOutboxWriter,
+ &out.thought.id,
+ &bob.id,
+ )
+ .await
+ .unwrap_err();
assert!(matches!(err, DomainError::NotFound));
}
@@ -124,9 +145,16 @@ async fn edit_thought_changes_content_and_emits_event() {
let store = TestStore::default();
let alice = user();
store.users.lock().unwrap().push(alice.clone());
- let out = create_thought(&store, &store, &store, &NoOpEventPublisher, &NoOpOutboxWriter, input(alice.id.clone()))
- .await
- .unwrap();
+ let out = create_thought(
+ &store,
+ &store,
+ &store,
+ &NoOpEventPublisher,
+ &NoOpOutboxWriter,
+ input(alice.id.clone()),
+ )
+ .await
+ .unwrap();
let tid = out.thought.id.clone();
edit_thought(&store, &store, &tid, &alice.id, "updated".to_string())
@@ -222,9 +250,13 @@ fn make_thought(user_id: UserId) -> Thought {
async fn get_thought_view_returns_feed_entry() {
let store = TestStore::default();
let user = make_user();
- ::save(&store, &user).await.unwrap();
+ ::save(&store, &user)
+ .await
+ .unwrap();
let thought = make_thought(user.id.clone());
- ::save(&store, &thought).await.unwrap();
+ ::save(&store, &thought)
+ .await
+ .unwrap();
let entry = get_thought_view(&store, &store, &store, &thought.id, None)
.await
@@ -248,9 +280,13 @@ async fn get_thought_view_returns_not_found_for_missing_thought() {
async fn get_thread_views_batches_correctly() {
let store = TestStore::default();
let user = make_user();
- ::save(&store, &user).await.unwrap();
+ ::save(&store, &user)
+ .await
+ .unwrap();
let root = make_thought(user.id.clone());
- ::save(&store, &root).await.unwrap();
+ ::save(&store, &root)
+ .await
+ .unwrap();
let reply = Thought::new_local(
ThoughtId::new(),
user.id.clone(),
@@ -260,7 +296,9 @@ async fn get_thread_views_batches_correctly() {
None,
false,
);
- ::save(&store, &reply).await.unwrap();
+ ::save(&store, &reply)
+ .await
+ .unwrap();
let entries = get_thread_views(&store, &store, &store, &root.id, None)
.await
diff --git a/crates/bootstrap/src/factory.rs b/crates/bootstrap/src/factory.rs
index 57041d9..73da3f0 100644
--- a/crates/bootstrap/src/factory.rs
+++ b/crates/bootstrap/src/factory.rs
@@ -8,7 +8,11 @@ use std::sync::Arc;
use activitypub::ThoughtsObjectHandler;
use activitypub_base::service::ActivityPubService;
use auth::ApiKeyServiceImpl;
-use domain::{errors::DomainError, events::DomainEvent, ports::{EventPublisher, OutboxWriter}};
+use domain::{
+ errors::DomainError,
+ events::DomainEvent,
+ ports::{EventPublisher, OutboxWriter},
+};
use event_transport::EventPublisherAdapter;
use nats::NatsTransport;
use postgres::activitypub::PgActivityPubRepository;
@@ -130,9 +134,9 @@ pub async fn build(cfg: &Config) -> Infrastructure {
ap_repo: Arc::new(PgActivityPubRepository::new(pool.clone())),
remote_actor_connections: Arc::new(PgRemoteActorConnectionRepository::new(pool.clone())),
federation_scheduler: ap_service.clone() as Arc,
- api_key_auth: Arc::new(ApiKeyServiceImpl::new(
- Arc::new(postgres::api_key::PgApiKeyRepository::new(pool.clone())),
- )),
+ api_key_auth: Arc::new(ApiKeyServiceImpl::new(Arc::new(
+ postgres::api_key::PgApiKeyRepository::new(pool.clone()),
+ ))),
engagement: Arc::new(PgEngagementRepository::new(pool.clone())),
};
diff --git a/crates/domain/src/ports.rs b/crates/domain/src/ports.rs
index 8395906..6333cf1 100644
--- a/crates/domain/src/ports.rs
+++ b/crates/domain/src/ports.rs
@@ -58,7 +58,8 @@ pub trait UserReader: Send + Sync {
async fn find_by_email(&self, email: &Email) -> Result, DomainError>;
async fn list_with_stats(&self) -> Result, DomainError>;
async fn count(&self) -> Result;
- async fn list_paginated(&self, page: PageParams) -> Result, DomainError>;
+ async fn list_paginated(&self, page: PageParams)
+ -> Result, DomainError>;
async fn find_by_ids(&self, ids: &[UserId]) -> Result, DomainError>;
}
@@ -353,19 +354,43 @@ pub struct FeedQuery {
impl FeedQuery {
pub fn home(viewer_id: UserId, following_ids: Vec, page: PageParams) -> Self {
- Self { scope: FeedScope::Home { following_ids }, page, viewer_id: Some(viewer_id) }
+ Self {
+ scope: FeedScope::Home { following_ids },
+ page,
+ viewer_id: Some(viewer_id),
+ }
}
pub fn public(page: PageParams, viewer_id: Option) -> Self {
- Self { scope: FeedScope::Public, page, viewer_id }
+ Self {
+ scope: FeedScope::Public,
+ page,
+ viewer_id,
+ }
}
pub fn tag(tag_name: impl Into, page: PageParams, viewer_id: Option) -> Self {
- Self { scope: FeedScope::Tag { tag_name: tag_name.into() }, page, viewer_id }
+ Self {
+ scope: FeedScope::Tag {
+ tag_name: tag_name.into(),
+ },
+ page,
+ viewer_id,
+ }
}
pub fn user(user_id: UserId, page: PageParams, viewer_id: Option) -> Self {
- Self { scope: FeedScope::User { user_id }, page, viewer_id }
+ Self {
+ scope: FeedScope::User { user_id },
+ page,
+ viewer_id,
+ }
}
pub fn search(query: impl Into, page: PageParams, viewer_id: Option) -> Self {
- Self { scope: FeedScope::Search { query: query.into() }, page, viewer_id }
+ Self {
+ scope: FeedScope::Search {
+ query: query.into(),
+ },
+ page,
+ viewer_id,
+ }
}
}
@@ -392,7 +417,6 @@ pub trait SearchPort: Send + Sync {
) -> Result, DomainError>;
}
-
#[async_trait]
pub trait FederationSchedulerPort: Send + Sync {
async fn schedule_actor_posts_fetch(
diff --git a/crates/domain/src/testing/mod.rs b/crates/domain/src/testing/mod.rs
index adc528c..a546a5f 100644
--- a/crates/domain/src/testing/mod.rs
+++ b/crates/domain/src/testing/mod.rs
@@ -83,17 +83,30 @@ impl UserReader for TestStore {
.count() as i64)
}
- async fn list_paginated(&self, page: PageParams) -> Result, DomainError> {
+ async fn list_paginated(
+ &self,
+ page: PageParams,
+ ) -> Result, DomainError> {
let all = self.list_with_stats().await?;
let total = all.len() as i64;
let start = page.offset() as usize;
- let items: Vec = all.into_iter().skip(start).take(page.limit() as usize).collect();
- Ok(Paginated { items, total, page: page.page, per_page: page.per_page })
+ let items: Vec = all
+ .into_iter()
+ .skip(start)
+ .take(page.limit() as usize)
+ .collect();
+ Ok(Paginated {
+ items,
+ total,
+ page: page.page,
+ per_page: page.per_page,
+ })
}
async fn find_by_ids(&self, ids: &[UserId]) -> Result, DomainError> {
let g = self.users.lock().unwrap();
- let map = g.iter()
+ let map = g
+ .iter()
.filter(|u| ids.contains(&u.id))
.map(|u| (u.id.clone(), u.clone()))
.collect();
@@ -294,7 +307,16 @@ impl EngagementRepository for TestStore {
&self,
thought_ids: &[ThoughtId],
viewer_id: Option<&UserId>,
- ) -> Result)>, DomainError> {
+ ) -> Result<
+ HashMap<
+ ThoughtId,
+ (
+ crate::models::feed::EngagementStats,
+ Option,
+ ),
+ >,
+ DomainError,
+ > {
use crate::models::feed::{EngagementStats, ViewerContext};
let likes = self.likes.lock().unwrap();
let boosts = self.boosts.lock().unwrap();
@@ -304,12 +326,29 @@ impl EngagementRepository for TestStore {
for tid in thought_ids {
let like_count = likes.iter().filter(|l| &l.thought_id == tid).count() as i64;
let boost_count = boosts.iter().filter(|b| &b.thought_id == tid).count() as i64;
- let reply_count = thoughts.iter().filter(|t| t.in_reply_to_id.as_ref() == Some(tid)).count() as i64;
+ let reply_count = thoughts
+ .iter()
+ .filter(|t| t.in_reply_to_id.as_ref() == Some(tid))
+ .count() as i64;
let viewer = viewer_id.map(|vid| ViewerContext {
- liked: likes.iter().any(|l| &l.thought_id == tid && &l.user_id == vid),
- boosted: boosts.iter().any(|b| &b.thought_id == tid && &b.user_id == vid),
+ liked: likes
+ .iter()
+ .any(|l| &l.thought_id == tid && &l.user_id == vid),
+ boosted: boosts
+ .iter()
+ .any(|b| &b.thought_id == tid && &b.user_id == vid),
});
- result.insert(tid.clone(), (EngagementStats { like_count, boost_count, reply_count }, viewer));
+ result.insert(
+ tid.clone(),
+ (
+ EngagementStats {
+ like_count,
+ boost_count,
+ reply_count,
+ },
+ viewer,
+ ),
+ );
}
Ok(result)
}
@@ -763,7 +802,10 @@ impl RemoteActorConnectionRepository for TestStore {
#[async_trait]
impl FeedRepository for TestStore {
- async fn query(&self, _q: &crate::ports::FeedQuery) -> Result, DomainError> {
+ async fn query(
+ &self,
+ _q: &crate::ports::FeedQuery,
+ ) -> Result, DomainError> {
Ok(Paginated {
items: vec![],
total: 0,
diff --git a/crates/presentation/src/handlers/api_keys.rs b/crates/presentation/src/handlers/api_keys.rs
index 983b97c..de14777 100644
--- a/crates/presentation/src/handlers/api_keys.rs
+++ b/crates/presentation/src/handlers/api_keys.rs
@@ -8,11 +8,7 @@ use api_types::{
responses::{ApiKeyResponse, CreatedApiKeyResponse},
};
use application::use_cases::api_keys::{create_api_key, delete_api_key, list_api_keys};
-use axum::{
- extract::Path,
- http::StatusCode,
- Json,
-};
+use axum::{extract::Path, http::StatusCode, Json};
use domain::{ports::ApiKeyRepository, value_objects::ApiKeyId};
use uuid::Uuid;
diff --git a/crates/presentation/src/handlers/auth.rs b/crates/presentation/src/handlers/auth.rs
index ed66332..f61ee74 100644
--- a/crates/presentation/src/handlers/auth.rs
+++ b/crates/presentation/src/handlers/auth.rs
@@ -1,8 +1,4 @@
-use crate::{
- deps_struct,
- errors::ApiError,
- extractors::Deps,
-};
+use crate::{deps_struct, errors::ApiError, extractors::Deps};
use api_types::{
requests::{LoginRequest, RegisterRequest},
responses::{AuthResponse, ErrorResponse, UserResponse},
diff --git a/crates/presentation/src/handlers/federation_actors/mod.rs b/crates/presentation/src/handlers/federation_actors/mod.rs
index a84fe94..b44bb50 100644
--- a/crates/presentation/src/handlers/federation_actors/mod.rs
+++ b/crates/presentation/src/handlers/federation_actors/mod.rs
@@ -4,6 +4,7 @@ use crate::{
handlers::feed::to_thought_response,
state::AppState,
};
+use activitypub_base::ActivityPubRepository;
use api_types::{
requests::PaginationQuery,
responses::{ActorConnectionPageResponse, ActorConnectionResponse},
@@ -15,7 +16,6 @@ use axum::{
extract::{Path, Query},
Json,
};
-use activitypub_base::ActivityPubRepository;
use domain::{
models::feed::PageParams,
ports::{
diff --git a/crates/presentation/src/handlers/feed.rs b/crates/presentation/src/handlers/feed.rs
index 80d95f9..9917968 100644
--- a/crates/presentation/src/handlers/feed.rs
+++ b/crates/presentation/src/handlers/feed.rs
@@ -16,7 +16,10 @@ use axum::{
};
use domain::{
models::feed::PageParams,
- ports::{FederationActionPort, FeedQuery, FeedRepository, FollowRepository, SearchPort, TagRepository, UserRepository},
+ ports::{
+ FederationActionPort, FeedQuery, FeedRepository, FollowRepository, SearchPort,
+ TagRepository, UserRepository,
+ },
};
deps_struct!(FeedDeps {
@@ -224,7 +227,10 @@ pub async fn user_thoughts_handler(
page: q.page(),
per_page: q.per_page(),
};
- let result = d.feed.query(&FeedQuery::user(user.id.clone(), page, viewer)).await?;
+ let result = d
+ .feed
+ .query(&FeedQuery::user(user.id.clone(), page, viewer))
+ .await?;
Ok(Json(serde_json::json!({
"total": result.total,
"page": result.page,
@@ -241,7 +247,10 @@ pub async fn get_popular_tags(
.get("limit")
.and_then(|v| v.parse().ok())
.unwrap_or(api_types::requests::DEFAULT_PER_PAGE as usize);
- let tags = d.tags.popular_tags(limit.min(api_types::requests::MAX_PER_PAGE as usize)).await?;
+ let tags = d
+ .tags
+ .popular_tags(limit.min(api_types::requests::MAX_PER_PAGE as usize))
+ .await?;
Ok(Json(serde_json::json!({
"tags": tags.iter().map(|(name, count)| serde_json::json!({
"name": name,
@@ -268,7 +277,10 @@ pub async fn tag_thoughts_handler(
page: q.page(),
per_page: q.per_page(),
};
- let result = d.feed.query(&FeedQuery::tag(&tag_name, page, viewer)).await?;
+ let result = d
+ .feed
+ .query(&FeedQuery::tag(&tag_name, page, viewer))
+ .await?;
Ok(Json(serde_json::json!({
"tag": tag_name,
"total": result.total,
diff --git a/crates/presentation/src/handlers/notifications/mod.rs b/crates/presentation/src/handlers/notifications/mod.rs
index 2374c7f..f5bc2ef 100644
--- a/crates/presentation/src/handlers/notifications/mod.rs
+++ b/crates/presentation/src/handlers/notifications/mod.rs
@@ -8,11 +8,7 @@ use application::use_cases::notifications::{
count_unread_notifications, list_notifications as uc_list_notifications,
mark_all_notifications_read, mark_notification_read as uc_mark_notification_read,
};
-use axum::{
- extract::Path,
- http::StatusCode,
- Json,
-};
+use axum::{extract::Path, http::StatusCode, Json};
use domain::{
models::feed::PageParams, ports::NotificationRepository, value_objects::NotificationId,
};
diff --git a/crates/presentation/src/handlers/social/mod.rs b/crates/presentation/src/handlers/social/mod.rs
index 0958dde..b642c8f 100644
--- a/crates/presentation/src/handlers/social/mod.rs
+++ b/crates/presentation/src/handlers/social/mod.rs
@@ -1,3 +1,4 @@
+use crate::handlers::auth::to_user_response;
use crate::{
deps_struct,
errors::ApiError,
@@ -5,14 +6,9 @@ use crate::{
};
use api_types::requests::SetTopFriendsRequest;
use api_types::responses::TopFriendsResponse;
-use crate::handlers::auth::to_user_response;
use application::use_cases::profile::{get_top_friends, get_user_by_username, set_top_friends};
use application::use_cases::social::*;
-use axum::{
- extract::Path,
- http::StatusCode,
- Json,
-};
+use axum::{extract::Path, http::StatusCode, Json};
use domain::{
ports::{
BlockRepository, BoostRepository, EventPublisher, FederationActionPort, FollowRepository,
diff --git a/crates/presentation/src/handlers/thoughts.rs b/crates/presentation/src/handlers/thoughts.rs
index 1e804e8..8b8d19d 100644
--- a/crates/presentation/src/handlers/thoughts.rs
+++ b/crates/presentation/src/handlers/thoughts.rs
@@ -9,18 +9,16 @@ use api_types::{
responses::ErrorResponse,
};
use application::use_cases::thoughts::{
- create_thought, delete_thought, edit_thought, get_thread_views, get_thought_view,
+ create_thought, delete_thought, edit_thought, get_thought_view, get_thread_views,
CreateThoughtInput,
};
-use axum::{
- extract::Path,
- http::StatusCode,
- response::IntoResponse,
- Json,
-};
+use axum::{extract::Path, http::StatusCode, response::IntoResponse, Json};
use domain::{
models::feed::{EngagementStats, FeedEntry, ViewerContext},
- ports::{EngagementRepository, EventPublisher, OutboxWriter, TagRepository, ThoughtRepository, UserRepository},
+ ports::{
+ EngagementRepository, EventPublisher, OutboxWriter, TagRepository, ThoughtRepository,
+ UserRepository,
+ },
value_objects::ThoughtId,
};
use uuid::Uuid;
@@ -74,8 +72,15 @@ pub async fn post_thought(
let entry = FeedEntry {
thought: out.thought,
author,
- stats: EngagementStats { like_count: 0, boost_count: 0, reply_count: 0 },
- viewer: Some(ViewerContext { liked: false, boosted: false }),
+ stats: EngagementStats {
+ like_count: 0,
+ boost_count: 0,
+ reply_count: 0,
+ },
+ viewer: Some(ViewerContext {
+ liked: false,
+ boosted: false,
+ }),
};
Ok((StatusCode::CREATED, Json(to_thought_response(&entry))))
}
@@ -101,7 +106,9 @@ pub async fn get_thought_handler(
viewer.as_ref(),
)
.await?;
- Ok(Json(serde_json::to_value(to_thought_response(&entry)).unwrap()))
+ Ok(Json(
+ serde_json::to_value(to_thought_response(&entry)).unwrap(),
+ ))
}
#[utoipa::path(
@@ -119,7 +126,14 @@ pub async fn delete_thought_handler(
AuthUser(uid): AuthUser,
Path(id): Path,
) -> Result {
- delete_thought(&*d.thoughts, &*d.events, &*d.outbox, &ThoughtId::from_uuid(id), &uid).await?;
+ delete_thought(
+ &*d.thoughts,
+ &*d.events,
+ &*d.outbox,
+ &ThoughtId::from_uuid(id),
+ &uid,
+ )
+ .await?;
Ok(StatusCode::NO_CONTENT)
}
diff --git a/crates/presentation/src/handlers/users/mod.rs b/crates/presentation/src/handlers/users/mod.rs
index 95560ce..5671f15 100644
--- a/crates/presentation/src/handlers/users/mod.rs
+++ b/crates/presentation/src/handlers/users/mod.rs
@@ -191,9 +191,7 @@ pub async fn get_users(
})))
}
-pub async fn get_user_count(
- Deps(d): Deps,
-) -> Result, ApiError> {
+pub async fn get_user_count(Deps(d): Deps) -> Result, ApiError> {
let count = d.users.count().await?;
Ok(Json(serde_json::json!({ "count": count })))
}
diff --git a/crates/presentation/src/testing.rs b/crates/presentation/src/testing.rs
index 16f7506..a67dd8d 100644
--- a/crates/presentation/src/testing.rs
+++ b/crates/presentation/src/testing.rs
@@ -79,11 +79,7 @@ impl ActivityPubRepository for NoOpApRepo {
) -> Result {
Ok(ThoughtId::from_uuid(uuid::Uuid::new_v4()))
}
- async fn apply_note_update(
- &self,
- _ap_id: &str,
- _new_content: &str,
- ) -> Result<(), DomainError> {
+ async fn apply_note_update(&self, _ap_id: &str, _new_content: &str) -> Result<(), DomainError> {
Ok(())
}
async fn retract_note(&self, _ap_id: &str) -> Result<(), DomainError> {
diff --git a/crates/worker/src/factory.rs b/crates/worker/src/factory.rs
index 8da2445..d3c9fbf 100644
--- a/crates/worker/src/factory.rs
+++ b/crates/worker/src/factory.rs
@@ -5,8 +5,8 @@ use std::sync::Arc;
use activitypub::ThoughtsObjectHandler;
use activitypub_base::ActivityPubService;
-use application::services::{FederationEventService, NotificationEventService};
use activitypub_base::{ActivityPubRepository, OutboundFederationPort};
+use application::services::{FederationEventService, NotificationEventService};
use domain::ports::EventPublisher;
use postgres::activitypub::PgActivityPubRepository;
use postgres_federation::{PostgresApUserRepository, PostgresFederationRepository};
diff --git a/crates/worker/src/main.rs b/crates/worker/src/main.rs
index 8d64b3f..953b395 100644
--- a/crates/worker/src/main.rs
+++ b/crates/worker/src/main.rs
@@ -44,7 +44,11 @@ async fn main() {
Ok(envelope) => {
let event = &envelope.event;
let event_type = event_payload::EventPayload::from(event).subject();
- tracing::info!(event_type, delivery = envelope.delivery_count, "received event");
+ tracing::info!(
+ event_type,
+ delivery = envelope.delivery_count,
+ "received event"
+ );
let n = infra.handlers.notification.handle(event).await;
let f = infra.handlers.federation.handle(event).await;
diff --git a/crates/worker/src/outbox_relay.rs b/crates/worker/src/outbox_relay.rs
index 6631891..fa56cdd 100644
--- a/crates/worker/src/outbox_relay.rs
+++ b/crates/worker/src/outbox_relay.rs
@@ -57,7 +57,11 @@ impl OutboxRelay {
let payload: EventPayload = match serde_json::from_value(row.payload.clone()) {
Ok(p) => p,
Err(e) => {
- tracing::error!(seq = row.seq, event_type = row.event_type, "outbox: failed to deserialize payload: {e}");
+ tracing::error!(
+ seq = row.seq,
+ event_type = row.event_type,
+ "outbox: failed to deserialize payload: {e}"
+ );
// Mark delivered to avoid blocking; investigate manually.
sqlx::query(
"UPDATE outbox_events \
@@ -75,7 +79,10 @@ impl OutboxRelay {
let domain_event = match DomainEvent::try_from(payload) {
Ok(ev) => ev,
Err(e) => {
- tracing::error!(seq = row.seq, "outbox: failed to convert to DomainEvent: {e}");
+ tracing::error!(
+ seq = row.seq,
+ "outbox: failed to convert to DomainEvent: {e}"
+ );
sqlx::query(
"UPDATE outbox_events \
SET delivered = true, delivered_at = now() \
@@ -100,7 +107,11 @@ impl OutboxRelay {
.execute(&mut *tx)
.await?;
tx.commit().await?;
- tracing::info!(seq = row.seq, event_type = row.event_type, "outbox: delivered");
+ tracing::info!(
+ seq = row.seq,
+ event_type = row.event_type,
+ "outbox: delivered"
+ );
}
Err(e) => {
tracing::warn!(seq = row.seq, "outbox: publish failed (will retry): {e}");