fmt
This commit is contained in:
@@ -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<SpyPort>) -> FederationEventService {
|
||||
}
|
||||
}
|
||||
|
||||
fn svc_with_ap(store: &TestStore, ap_repo: TestApRepo, spy: Arc<SpyPort>) -> FederationEventService {
|
||||
fn svc_with_ap(
|
||||
store: &TestStore,
|
||||
ap_repo: TestApRepo,
|
||||
spy: Arc<SpyPort>,
|
||||
) -> FederationEventService {
|
||||
FederationEventService {
|
||||
thoughts: Arc::new(store.clone()),
|
||||
users: Arc::new(store.clone()),
|
||||
|
||||
@@ -106,11 +106,7 @@ impl ActivityPubRepository for TestApRepo {
|
||||
) -> Result<ThoughtId, DomainError> {
|
||||
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> {
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -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<Option<User>, DomainError> {
|
||||
self.0.find_by_id(id).await
|
||||
}
|
||||
async fn find_by_username(
|
||||
&self,
|
||||
username: &Username,
|
||||
) -> Result<Option<User>, DomainError> {
|
||||
async fn find_by_username(&self, username: &Username) -> Result<Option<User>, DomainError> {
|
||||
self.0.find_by_username(username).await
|
||||
}
|
||||
async fn find_by_email(&self, email: &Email) -> Result<Option<User>, DomainError> {
|
||||
@@ -34,10 +34,16 @@ impl UserReader for ConflictOnSaveStore {
|
||||
async fn count(&self) -> Result<i64, DomainError> {
|
||||
self.0.count().await
|
||||
}
|
||||
async fn list_paginated(&self, page: PageParams) -> Result<Paginated<UserSummary>, DomainError> {
|
||||
async fn list_paginated(
|
||||
&self,
|
||||
page: PageParams,
|
||||
) -> Result<Paginated<UserSummary>, DomainError> {
|
||||
self.0.list_paginated(page).await
|
||||
}
|
||||
async fn find_by_ids(&self, ids: &[UserId]) -> Result<std::collections::HashMap<UserId, User>, DomainError> {
|
||||
async fn find_by_ids(
|
||||
&self,
|
||||
ids: &[UserId],
|
||||
) -> Result<std::collections::HashMap<UserId, User>, DomainError> {
|
||||
self.0.find_by_ids(ids).await
|
||||
}
|
||||
}
|
||||
@@ -57,7 +63,14 @@ impl UserWriter for ConflictOnSaveStore {
|
||||
custom_css: Option<String>,
|
||||
) -> 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<Option<User>, DomainError> {
|
||||
self.0.find_by_id(id).await
|
||||
}
|
||||
async fn find_by_username(
|
||||
&self,
|
||||
username: &Username,
|
||||
) -> Result<Option<User>, DomainError> {
|
||||
async fn find_by_username(&self, username: &Username) -> Result<Option<User>, DomainError> {
|
||||
self.0.find_by_username(username).await
|
||||
}
|
||||
async fn find_by_email(&self, email: &Email) -> Result<Option<User>, DomainError> {
|
||||
@@ -82,10 +92,16 @@ impl UserReader for EmailConflictOnSaveStore {
|
||||
async fn count(&self) -> Result<i64, DomainError> {
|
||||
self.0.count().await
|
||||
}
|
||||
async fn list_paginated(&self, page: PageParams) -> Result<Paginated<UserSummary>, DomainError> {
|
||||
async fn list_paginated(
|
||||
&self,
|
||||
page: PageParams,
|
||||
) -> Result<Paginated<UserSummary>, DomainError> {
|
||||
self.0.list_paginated(page).await
|
||||
}
|
||||
async fn find_by_ids(&self, ids: &[UserId]) -> Result<std::collections::HashMap<UserId, User>, DomainError> {
|
||||
async fn find_by_ids(
|
||||
&self,
|
||||
ids: &[UserId],
|
||||
) -> Result<std::collections::HashMap<UserId, User>, DomainError> {
|
||||
self.0.find_by_ids(ids).await
|
||||
}
|
||||
}
|
||||
@@ -105,7 +121,14 @@ impl UserWriter for EmailConflictOnSaveStore {
|
||||
custom_css: Option<String>,
|
||||
) -> 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
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -13,5 +13,6 @@ pub async fn get_home_feed(
|
||||
) -> Result<Paginated<FeedEntry>, 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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
<TestStore as UserWriter>::save(&store, &user).await.unwrap();
|
||||
<TestStore as UserWriter>::save(&store, &user)
|
||||
.await
|
||||
.unwrap();
|
||||
let thought = make_thought(user.id.clone());
|
||||
<TestStore as ThoughtRepository>::save(&store, &thought).await.unwrap();
|
||||
<TestStore as ThoughtRepository>::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();
|
||||
<TestStore as UserWriter>::save(&store, &user).await.unwrap();
|
||||
<TestStore as UserWriter>::save(&store, &user)
|
||||
.await
|
||||
.unwrap();
|
||||
let root = make_thought(user.id.clone());
|
||||
<TestStore as ThoughtRepository>::save(&store, &root).await.unwrap();
|
||||
<TestStore as ThoughtRepository>::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,
|
||||
);
|
||||
<TestStore as ThoughtRepository>::save(&store, &reply).await.unwrap();
|
||||
<TestStore as ThoughtRepository>::save(&store, &reply)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let entries = get_thread_views(&store, &store, &store, &root.id, None)
|
||||
.await
|
||||
|
||||
Reference in New Issue
Block a user