From 7e02f15a85f8281ba1b729a6acb4a26d6af8c61a Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Fri, 10 Jul 2026 16:26:57 +0200 Subject: [PATCH] =?UTF-8?q?refactor:=20consolidate=2011=20social=20use=20c?= =?UTF-8?q?ases=20=E2=86=92=20SocialCmd/SocialQry=20enum=20dispatch?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit -280 net lines. 11 one-file use cases replaced by execute_command/ execute_query with enum dispatch. Added FollowRejected event (reject was silently dropping). Command→event mapping now explicit in one match. --- crates/adapters/event-payload/src/lib.rs | 22 + crates/adapters/nats/src/subject.rs | 1 + crates/application/src/social/accept.rs | 23 - crates/application/src/social/block.rs | 18 - crates/application/src/social/commands.rs | 62 ++- crates/application/src/social/execute.rs | 101 ++++ crates/application/src/social/follow.rs | 18 - crates/application/src/social/get_blocked.rs | 14 - .../application/src/social/get_followers.rs | 18 - .../application/src/social/get_following.rs | 18 - crates/application/src/social/get_pending.rs | 18 - crates/application/src/social/mod.rs | 13 +- crates/application/src/social/queries.rs | 19 +- crates/application/src/social/reject.rs | 17 - .../application/src/social/remove_follower.rs | 23 - crates/application/src/social/tests/accept.rs | 65 --- crates/application/src/social/tests/block.rs | 47 -- .../application/src/social/tests/execute.rs | 449 ++++++++++++++++++ crates/application/src/social/tests/follow.rs | 92 ---- .../src/social/tests/get_followers.rs | 57 --- .../src/social/tests/get_following.rs | 74 --- .../src/social/tests/get_pending.rs | 46 -- crates/application/src/social/tests/reject.rs | 55 --- .../src/social/tests/remove_follower.rs | 74 --- .../application/src/social/tests/unblock.rs | 58 --- .../application/src/social/tests/unfollow.rs | 63 --- crates/application/src/social/unblock.rs | 18 - crates/application/src/social/unfollow.rs | 18 - crates/application/src/tests/worker.rs | 1 + crates/domain/src/events.rs | 4 + crates/presentation/src/handlers/social.rs | 92 ++-- 31 files changed, 659 insertions(+), 939 deletions(-) delete mode 100644 crates/application/src/social/accept.rs delete mode 100644 crates/application/src/social/block.rs create mode 100644 crates/application/src/social/execute.rs delete mode 100644 crates/application/src/social/follow.rs delete mode 100644 crates/application/src/social/get_blocked.rs delete mode 100644 crates/application/src/social/get_followers.rs delete mode 100644 crates/application/src/social/get_following.rs delete mode 100644 crates/application/src/social/get_pending.rs delete mode 100644 crates/application/src/social/reject.rs delete mode 100644 crates/application/src/social/remove_follower.rs delete mode 100644 crates/application/src/social/tests/accept.rs delete mode 100644 crates/application/src/social/tests/block.rs create mode 100644 crates/application/src/social/tests/execute.rs delete mode 100644 crates/application/src/social/tests/follow.rs delete mode 100644 crates/application/src/social/tests/get_followers.rs delete mode 100644 crates/application/src/social/tests/get_following.rs delete mode 100644 crates/application/src/social/tests/get_pending.rs delete mode 100644 crates/application/src/social/tests/reject.rs delete mode 100644 crates/application/src/social/tests/remove_follower.rs delete mode 100644 crates/application/src/social/tests/unblock.rs delete mode 100644 crates/application/src/social/tests/unfollow.rs delete mode 100644 crates/application/src/social/unblock.rs delete mode 100644 crates/application/src/social/unfollow.rs diff --git a/crates/adapters/event-payload/src/lib.rs b/crates/adapters/event-payload/src/lib.rs index bc44702..199c529 100644 --- a/crates/adapters/event-payload/src/lib.rs +++ b/crates/adapters/event-payload/src/lib.rs @@ -72,6 +72,11 @@ pub enum EventPayload { requester_kind: String, requester_id: String, }, + FollowRejected { + owner_id: String, + requester_kind: String, + requester_id: String, + }, Unfollowed { follower_id: String, target_kind: String, @@ -164,6 +169,7 @@ impl EventPayload { EventPayload::WatchlistEntryRemoved { .. } => "WatchlistEntryRemoved", EventPayload::FollowRequested { .. } => "FollowRequested", EventPayload::FollowAccepted { .. } => "FollowAccepted", + EventPayload::FollowRejected { .. } => "FollowRejected", EventPayload::Unfollowed { .. } => "Unfollowed", EventPayload::FollowerRemoved { .. } => "FollowerRemoved", EventPayload::ActorBlocked { .. } => "ActorBlocked", @@ -328,6 +334,14 @@ impl From<&DomainEvent> for EventPayload { requester_id: id, } } + DomainEvent::FollowRejected { owner, requester } => { + let (kind, id) = identity_to_payload(requester); + EventPayload::FollowRejected { + owner_id: owner.value().to_string(), + requester_kind: kind, + requester_id: id, + } + } DomainEvent::Unfollowed { follower, target } => { let (kind, id) = identity_to_payload(target); EventPayload::Unfollowed { @@ -559,6 +573,14 @@ impl TryFrom for DomainEvent { owner: UserId::from_uuid(parse_uuid(&owner_id, "owner_id")?), requester: payload_to_identity(&requester_kind, requester_id)?, }), + EventPayload::FollowRejected { + owner_id, + requester_kind, + requester_id, + } => Ok(DomainEvent::FollowRejected { + owner: UserId::from_uuid(parse_uuid(&owner_id, "owner_id")?), + requester: payload_to_identity(&requester_kind, requester_id)?, + }), EventPayload::Unfollowed { follower_id, target_kind, diff --git a/crates/adapters/nats/src/subject.rs b/crates/adapters/nats/src/subject.rs index f6a1ee9..3425ab6 100644 --- a/crates/adapters/nats/src/subject.rs +++ b/crates/adapters/nats/src/subject.rs @@ -14,6 +14,7 @@ pub fn event_to_subject(prefix: &str, event: &DomainEvent) -> String { DomainEvent::WatchlistEntryRemoved { .. } => "watchlist.entry.removed", DomainEvent::FollowRequested { .. } => "follow.requested", DomainEvent::FollowAccepted { .. } => "follow.accepted", + DomainEvent::FollowRejected { .. } => "follow.rejected", DomainEvent::Unfollowed { .. } => "follow.unfollowed", DomainEvent::FollowerRemoved { .. } => "follower.removed", DomainEvent::ActorBlocked { .. } => "actor.blocked", diff --git a/crates/application/src/social/accept.rs b/crates/application/src/social/accept.rs deleted file mode 100644 index 261c38f..0000000 --- a/crates/application/src/social/accept.rs +++ /dev/null @@ -1,23 +0,0 @@ -use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId}; - -use super::{commands::AcceptFollowCommand, deps::SocialCommandDeps}; - -pub async fn execute( - deps: &SocialCommandDeps, - cmd: AcceptFollowCommand, -) -> Result<(), DomainError> { - let owner = UserId::from_uuid(cmd.owner_id); - deps.social_command - .accept_follow(&owner, &cmd.requester) - .await?; - deps.event_publisher - .publish(&DomainEvent::FollowAccepted { - owner, - requester: cmd.requester, - }) - .await -} - -#[cfg(test)] -#[path = "tests/accept.rs"] -mod tests; diff --git a/crates/application/src/social/block.rs b/crates/application/src/social/block.rs deleted file mode 100644 index 543877a..0000000 --- a/crates/application/src/social/block.rs +++ /dev/null @@ -1,18 +0,0 @@ -use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId}; - -use super::{commands::BlockCommand, deps::SocialCommandDeps}; - -pub async fn execute(deps: &SocialCommandDeps, cmd: BlockCommand) -> Result<(), DomainError> { - let blocker = UserId::from_uuid(cmd.blocker_id); - deps.social_command.block(&blocker, &cmd.target).await?; - deps.event_publisher - .publish(&DomainEvent::ActorBlocked { - blocker, - target: cmd.target, - }) - .await -} - -#[cfg(test)] -#[path = "tests/block.rs"] -mod tests; diff --git a/crates/application/src/social/commands.rs b/crates/application/src/social/commands.rs index 6ea662d..1b15517 100644 --- a/crates/application/src/social/commands.rs +++ b/crates/application/src/social/commands.rs @@ -1,37 +1,33 @@ use domain::value_objects::{FollowTarget, SocialIdentity}; use uuid::Uuid; -pub struct FollowCommand { - pub follower_id: Uuid, - pub target: FollowTarget, -} - -pub struct UnfollowCommand { - pub follower_id: Uuid, - pub target: SocialIdentity, -} - -pub struct AcceptFollowCommand { - pub owner_id: Uuid, - pub requester: SocialIdentity, -} - -pub struct RejectFollowCommand { - pub owner_id: Uuid, - pub requester: SocialIdentity, -} - -pub struct RemoveFollowerCommand { - pub owner_id: Uuid, - pub follower: SocialIdentity, -} - -pub struct BlockCommand { - pub blocker_id: Uuid, - pub target: SocialIdentity, -} - -pub struct UnblockCommand { - pub blocker_id: Uuid, - pub target: SocialIdentity, +pub enum SocialCmd { + Follow { + follower_id: Uuid, + target: FollowTarget, + }, + Unfollow { + follower_id: Uuid, + target: SocialIdentity, + }, + AcceptFollow { + owner_id: Uuid, + requester: SocialIdentity, + }, + RejectFollow { + owner_id: Uuid, + requester: SocialIdentity, + }, + RemoveFollower { + owner_id: Uuid, + follower: SocialIdentity, + }, + Block { + blocker_id: Uuid, + target: SocialIdentity, + }, + Unblock { + blocker_id: Uuid, + target: SocialIdentity, + }, } diff --git a/crates/application/src/social/execute.rs b/crates/application/src/social/execute.rs new file mode 100644 index 0000000..4ca8fff --- /dev/null +++ b/crates/application/src/social/execute.rs @@ -0,0 +1,101 @@ +use domain::{ + errors::DomainError, + events::DomainEvent, + value_objects::{SocialActor, UserId}, +}; + +use super::{ + commands::SocialCmd, + deps::{SocialCommandDeps, SocialQueryDeps}, + queries::SocialQry, +}; + +pub async fn execute_command( + deps: &SocialCommandDeps, + cmd: SocialCmd, +) -> Result<(), DomainError> { + let event = match cmd { + SocialCmd::Follow { + follower_id, + target, + } => { + let follower = UserId::from_uuid(follower_id); + deps.social_command.follow(&follower, &target).await?; + DomainEvent::FollowRequested { follower, target } + } + SocialCmd::Unfollow { + follower_id, + target, + } => { + let follower = UserId::from_uuid(follower_id); + deps.social_command.unfollow(&follower, &target).await?; + DomainEvent::Unfollowed { follower, target } + } + SocialCmd::AcceptFollow { + owner_id, + requester, + } => { + let owner = UserId::from_uuid(owner_id); + deps.social_command + .accept_follow(&owner, &requester) + .await?; + DomainEvent::FollowAccepted { owner, requester } + } + SocialCmd::RejectFollow { + owner_id, + requester, + } => { + let owner = UserId::from_uuid(owner_id); + deps.social_command + .reject_follow(&owner, &requester) + .await?; + DomainEvent::FollowRejected { owner, requester } + } + SocialCmd::RemoveFollower { owner_id, follower } => { + let owner = UserId::from_uuid(owner_id); + deps.social_command + .remove_follower(&owner, &follower) + .await?; + DomainEvent::FollowerRemoved { owner, follower } + } + SocialCmd::Block { + blocker_id, + target, + } => { + let blocker = UserId::from_uuid(blocker_id); + deps.social_command.block(&blocker, &target).await?; + DomainEvent::ActorBlocked { blocker, target } + } + SocialCmd::Unblock { + blocker_id, + target, + } => { + let blocker = UserId::from_uuid(blocker_id); + deps.social_command.unblock(&blocker, &target).await?; + DomainEvent::ActorUnblocked { blocker, target } + } + }; + deps.event_publisher.publish(&event).await +} + +pub async fn execute_query( + deps: &SocialQueryDeps, + query: SocialQry, +) -> Result, DomainError> { + let user_id = match &query { + SocialQry::GetFollowing { user_id } + | SocialQry::GetFollowers { user_id } + | SocialQry::GetPending { user_id } + | SocialQry::GetBlocked { user_id } => UserId::from_uuid(*user_id), + }; + match query { + SocialQry::GetFollowing { .. } => deps.social_query.get_following(&user_id).await, + SocialQry::GetFollowers { .. } => deps.social_query.get_followers(&user_id).await, + SocialQry::GetPending { .. } => deps.social_query.get_pending_followers(&user_id).await, + SocialQry::GetBlocked { .. } => deps.social_query.get_blocked(&user_id).await, + } +} + +#[cfg(test)] +#[path = "tests/execute.rs"] +mod tests; diff --git a/crates/application/src/social/follow.rs b/crates/application/src/social/follow.rs deleted file mode 100644 index 6e2f29c..0000000 --- a/crates/application/src/social/follow.rs +++ /dev/null @@ -1,18 +0,0 @@ -use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId}; - -use super::{commands::FollowCommand, deps::SocialCommandDeps}; - -pub async fn execute(deps: &SocialCommandDeps, cmd: FollowCommand) -> Result<(), DomainError> { - let follower = UserId::from_uuid(cmd.follower_id); - deps.social_command.follow(&follower, &cmd.target).await?; - deps.event_publisher - .publish(&DomainEvent::FollowRequested { - follower, - target: cmd.target, - }) - .await -} - -#[cfg(test)] -#[path = "tests/follow.rs"] -mod tests; diff --git a/crates/application/src/social/get_blocked.rs b/crates/application/src/social/get_blocked.rs deleted file mode 100644 index 5864978..0000000 --- a/crates/application/src/social/get_blocked.rs +++ /dev/null @@ -1,14 +0,0 @@ -use domain::{ - errors::DomainError, - value_objects::{SocialActor, UserId}, -}; - -use super::{deps::SocialQueryDeps, queries::GetBlockedQuery}; - -pub async fn execute( - deps: &SocialQueryDeps, - query: GetBlockedQuery, -) -> Result, DomainError> { - let user_id = UserId::from_uuid(query.user_id); - deps.social_query.get_blocked(&user_id).await -} diff --git a/crates/application/src/social/get_followers.rs b/crates/application/src/social/get_followers.rs deleted file mode 100644 index 95cf473..0000000 --- a/crates/application/src/social/get_followers.rs +++ /dev/null @@ -1,18 +0,0 @@ -use domain::{ - errors::DomainError, - value_objects::{SocialActor, UserId}, -}; - -use super::{deps::SocialQueryDeps, queries::GetFollowersQuery}; - -pub async fn execute( - deps: &SocialQueryDeps, - query: GetFollowersQuery, -) -> Result, DomainError> { - let user_id = UserId::from_uuid(query.user_id); - deps.social_query.get_followers(&user_id).await -} - -#[cfg(test)] -#[path = "tests/get_followers.rs"] -mod tests; diff --git a/crates/application/src/social/get_following.rs b/crates/application/src/social/get_following.rs deleted file mode 100644 index a7d4e9e..0000000 --- a/crates/application/src/social/get_following.rs +++ /dev/null @@ -1,18 +0,0 @@ -use domain::{ - errors::DomainError, - value_objects::{SocialActor, UserId}, -}; - -use super::{deps::SocialQueryDeps, queries::GetFollowingQuery}; - -pub async fn execute( - deps: &SocialQueryDeps, - query: GetFollowingQuery, -) -> Result, DomainError> { - let user_id = UserId::from_uuid(query.user_id); - deps.social_query.get_following(&user_id).await -} - -#[cfg(test)] -#[path = "tests/get_following.rs"] -mod tests; diff --git a/crates/application/src/social/get_pending.rs b/crates/application/src/social/get_pending.rs deleted file mode 100644 index c4b38a9..0000000 --- a/crates/application/src/social/get_pending.rs +++ /dev/null @@ -1,18 +0,0 @@ -use domain::{ - errors::DomainError, - value_objects::{SocialActor, UserId}, -}; - -use super::{deps::SocialQueryDeps, queries::GetPendingFollowersQuery}; - -pub async fn execute( - deps: &SocialQueryDeps, - query: GetPendingFollowersQuery, -) -> Result, DomainError> { - let user_id = UserId::from_uuid(query.user_id); - deps.social_query.get_pending_followers(&user_id).await -} - -#[cfg(test)] -#[path = "tests/get_pending.rs"] -mod tests; diff --git a/crates/application/src/social/mod.rs b/crates/application/src/social/mod.rs index c2cf320..7e812ba 100644 --- a/crates/application/src/social/mod.rs +++ b/crates/application/src/social/mod.rs @@ -1,15 +1,4 @@ pub mod commands; pub mod deps; +pub mod execute; pub mod queries; - -pub mod accept; -pub mod block; -pub mod follow; -pub mod get_blocked; -pub mod get_followers; -pub mod get_following; -pub mod get_pending; -pub mod reject; -pub mod remove_follower; -pub mod unblock; -pub mod unfollow; diff --git a/crates/application/src/social/queries.rs b/crates/application/src/social/queries.rs index ec140dd..df69dc1 100644 --- a/crates/application/src/social/queries.rs +++ b/crates/application/src/social/queries.rs @@ -1,17 +1,8 @@ use uuid::Uuid; -pub struct GetFollowingQuery { - pub user_id: Uuid, -} - -pub struct GetFollowersQuery { - pub user_id: Uuid, -} - -pub struct GetPendingFollowersQuery { - pub user_id: Uuid, -} - -pub struct GetBlockedQuery { - pub user_id: Uuid, +pub enum SocialQry { + GetFollowing { user_id: Uuid }, + GetFollowers { user_id: Uuid }, + GetPending { user_id: Uuid }, + GetBlocked { user_id: Uuid }, } diff --git a/crates/application/src/social/reject.rs b/crates/application/src/social/reject.rs deleted file mode 100644 index ffeabea..0000000 --- a/crates/application/src/social/reject.rs +++ /dev/null @@ -1,17 +0,0 @@ -use domain::{errors::DomainError, value_objects::UserId}; - -use super::{commands::RejectFollowCommand, deps::SocialCommandDeps}; - -pub async fn execute( - deps: &SocialCommandDeps, - cmd: RejectFollowCommand, -) -> Result<(), DomainError> { - let owner = UserId::from_uuid(cmd.owner_id); - deps.social_command - .reject_follow(&owner, &cmd.requester) - .await -} - -#[cfg(test)] -#[path = "tests/reject.rs"] -mod tests; diff --git a/crates/application/src/social/remove_follower.rs b/crates/application/src/social/remove_follower.rs deleted file mode 100644 index 69d0299..0000000 --- a/crates/application/src/social/remove_follower.rs +++ /dev/null @@ -1,23 +0,0 @@ -use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId}; - -use super::{commands::RemoveFollowerCommand, deps::SocialCommandDeps}; - -pub async fn execute( - deps: &SocialCommandDeps, - cmd: RemoveFollowerCommand, -) -> Result<(), DomainError> { - let owner = UserId::from_uuid(cmd.owner_id); - deps.social_command - .remove_follower(&owner, &cmd.follower) - .await?; - deps.event_publisher - .publish(&DomainEvent::FollowerRemoved { - owner, - follower: cmd.follower, - }) - .await -} - -#[cfg(test)] -#[path = "tests/remove_follower.rs"] -mod tests; diff --git a/crates/application/src/social/tests/accept.rs b/crates/application/src/social/tests/accept.rs deleted file mode 100644 index 1281879..0000000 --- a/crates/application/src/social/tests/accept.rs +++ /dev/null @@ -1,65 +0,0 @@ -use std::sync::Arc; - -use domain::{ - events::DomainEvent, - testing::{InMemorySocialRepository, NoopEventPublisher}, - value_objects::{FollowTarget, SocialIdentity, UserId}, -}; -use uuid::Uuid; - -use crate::social::{ - accept, - commands::{AcceptFollowCommand, FollowCommand}, - deps::SocialCommandDeps, - follow, -}; - -fn make_deps() -> ( - Arc, - Arc, - SocialCommandDeps, -) { - let social = InMemorySocialRepository::new(); - let events = NoopEventPublisher::new(); - let deps = SocialCommandDeps { - social_command: Arc::clone(&social) as _, - social_query: Arc::clone(&social) as _, - event_publisher: Arc::clone(&events) as _, - }; - (social, events, deps) -} - -#[tokio::test] -async fn accept_follow_emits_follow_accepted_event() { - let (_social, events, deps) = make_deps(); - let follower_id = Uuid::new_v4(); - let owner_id = Uuid::new_v4(); - let requester = SocialIdentity::Local(UserId::from_uuid(follower_id)); - - follow::execute( - &deps, - FollowCommand { - follower_id, - target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))), - }, - ) - .await - .unwrap(); - - accept::execute( - &deps, - AcceptFollowCommand { - owner_id, - requester, - }, - ) - .await - .unwrap(); - - let published = events.published(); - assert!( - published - .iter() - .any(|e| matches!(e, DomainEvent::FollowAccepted { .. })) - ); -} diff --git a/crates/application/src/social/tests/block.rs b/crates/application/src/social/tests/block.rs deleted file mode 100644 index 0216f77..0000000 --- a/crates/application/src/social/tests/block.rs +++ /dev/null @@ -1,47 +0,0 @@ -use std::sync::Arc; - -use domain::{ - events::DomainEvent, - testing::{InMemorySocialRepository, NoopEventPublisher}, - value_objects::{SocialIdentity, UserId}, -}; -use uuid::Uuid; - -use crate::social::{block, commands::BlockCommand, deps::SocialCommandDeps}; - -fn make_deps() -> ( - Arc, - Arc, - SocialCommandDeps, -) { - let social = InMemorySocialRepository::new(); - let events = NoopEventPublisher::new(); - let deps = SocialCommandDeps { - social_command: Arc::clone(&social) as _, - social_query: Arc::clone(&social) as _, - event_publisher: Arc::clone(&events) as _, - }; - (social, events, deps) -} - -#[tokio::test] -async fn block_emits_actor_blocked_event() { - let (_social, events, deps) = make_deps(); - - block::execute( - &deps, - BlockCommand { - blocker_id: Uuid::new_v4(), - target: SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4())), - }, - ) - .await - .unwrap(); - - let published = events.published(); - assert!( - published - .iter() - .any(|e| matches!(e, DomainEvent::ActorBlocked { .. })) - ); -} diff --git a/crates/application/src/social/tests/execute.rs b/crates/application/src/social/tests/execute.rs new file mode 100644 index 0000000..c0abd8e --- /dev/null +++ b/crates/application/src/social/tests/execute.rs @@ -0,0 +1,449 @@ +use std::sync::Arc; + +use domain::{ + events::DomainEvent, + testing::{InMemorySocialRepository, NoopEventPublisher}, + value_objects::{FollowTarget, SocialIdentity, UserId}, +}; +use uuid::Uuid; + +use crate::social::{ + commands::SocialCmd, + deps::{SocialCommandDeps, SocialQueryDeps}, + execute::{execute_command, execute_query}, + queries::SocialQry, +}; + +fn make_cmd_deps() -> ( + Arc, + Arc, + SocialCommandDeps, +) { + let social = InMemorySocialRepository::new(); + let events = NoopEventPublisher::new(); + let deps = SocialCommandDeps { + social_command: Arc::clone(&social) as _, + social_query: Arc::clone(&social) as _, + event_publisher: Arc::clone(&events) as _, + }; + (social, events, deps) +} + +// ── Follow ────────────────────────────────────────────────────────────────── + +#[tokio::test] +async fn follow_emits_follow_requested_event() { + let (_social, events, deps) = make_cmd_deps(); + + execute_command( + &deps, + SocialCmd::Follow { + follower_id: Uuid::new_v4(), + target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid( + Uuid::new_v4(), + ))), + }, + ) + .await + .unwrap(); + + let published = events.published(); + assert!( + published + .iter() + .any(|e| matches!(e, DomainEvent::FollowRequested { .. })) + ); +} + +#[tokio::test] +async fn cannot_follow_yourself() { + let (_social, _events, deps) = make_cmd_deps(); + let user_id = Uuid::new_v4(); + + let result = execute_command( + &deps, + SocialCmd::Follow { + follower_id: user_id, + target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(user_id))), + }, + ) + .await; + + assert!(result.is_err()); +} + +#[tokio::test] +async fn cannot_follow_same_target_twice() { + let (_social, _events, deps) = make_cmd_deps(); + let follower_id = Uuid::new_v4(); + let target = FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4()))); + + execute_command( + &deps, + SocialCmd::Follow { + follower_id, + target: target.clone(), + }, + ) + .await + .unwrap(); + + let result = execute_command( + &deps, + SocialCmd::Follow { + follower_id, + target, + }, + ) + .await; + + assert!(result.is_err()); +} + +// ── Unfollow ──────────────────────────────────────────────────────────────── + +#[tokio::test] +async fn unfollow_emits_unfollowed_event() { + let (_social, events, deps) = make_cmd_deps(); + let follower_id = Uuid::new_v4(); + let target = SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4())); + + execute_command( + &deps, + SocialCmd::Follow { + follower_id, + target: FollowTarget::Identity(target.clone()), + }, + ) + .await + .unwrap(); + + execute_command( + &deps, + SocialCmd::Unfollow { + follower_id, + target, + }, + ) + .await + .unwrap(); + + let published = events.published(); + assert!( + published + .iter() + .any(|e| matches!(e, DomainEvent::Unfollowed { .. })) + ); +} + +// ── Accept ────────────────────────────────────────────────────────────────── + +#[tokio::test] +async fn accept_follow_emits_follow_accepted_event() { + let (_social, events, deps) = make_cmd_deps(); + let follower_id = Uuid::new_v4(); + let owner_id = Uuid::new_v4(); + let requester = SocialIdentity::Local(UserId::from_uuid(follower_id)); + + execute_command( + &deps, + SocialCmd::Follow { + follower_id, + target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))), + }, + ) + .await + .unwrap(); + + execute_command( + &deps, + SocialCmd::AcceptFollow { + owner_id, + requester, + }, + ) + .await + .unwrap(); + + let published = events.published(); + assert!( + published + .iter() + .any(|e| matches!(e, DomainEvent::FollowAccepted { .. })) + ); +} + +// ── Reject ────────────────────────────────────────────────────────────────── + +#[tokio::test] +async fn reject_follow_emits_follow_rejected_event() { + let (_social, events, deps) = make_cmd_deps(); + let follower_id = Uuid::new_v4(); + let owner_id = Uuid::new_v4(); + + execute_command( + &deps, + SocialCmd::Follow { + follower_id, + target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))), + }, + ) + .await + .unwrap(); + + execute_command( + &deps, + SocialCmd::RejectFollow { + owner_id, + requester: SocialIdentity::Local(UserId::from_uuid(follower_id)), + }, + ) + .await + .unwrap(); + + let published = events.published(); + assert!( + published + .iter() + .any(|e| matches!(e, DomainEvent::FollowRejected { .. })) + ); +} + +// ── Remove follower ───────────────────────────────────────────────────────── + +#[tokio::test] +async fn remove_follower_emits_follower_removed_event() { + let (_social, events, deps) = make_cmd_deps(); + let follower_id = Uuid::new_v4(); + let owner_id = Uuid::new_v4(); + + execute_command( + &deps, + SocialCmd::Follow { + follower_id, + target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))), + }, + ) + .await + .unwrap(); + + execute_command( + &deps, + SocialCmd::AcceptFollow { + owner_id, + requester: SocialIdentity::Local(UserId::from_uuid(follower_id)), + }, + ) + .await + .unwrap(); + + execute_command( + &deps, + SocialCmd::RemoveFollower { + owner_id, + follower: SocialIdentity::Local(UserId::from_uuid(follower_id)), + }, + ) + .await + .unwrap(); + + let published = events.published(); + assert!( + published + .iter() + .any(|e| matches!(e, DomainEvent::FollowerRemoved { .. })) + ); +} + +// ── Block ─────────────────────────────────────────────────────────────────── + +#[tokio::test] +async fn block_emits_actor_blocked_event() { + let (_social, events, deps) = make_cmd_deps(); + + execute_command( + &deps, + SocialCmd::Block { + blocker_id: Uuid::new_v4(), + target: SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4())), + }, + ) + .await + .unwrap(); + + let published = events.published(); + assert!( + published + .iter() + .any(|e| matches!(e, DomainEvent::ActorBlocked { .. })) + ); +} + +// ── Unblock ───────────────────────────────────────────────────────────────── + +#[tokio::test] +async fn unblock_emits_actor_unblocked_event() { + let (_social, events, deps) = make_cmd_deps(); + let target = SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4())); + let blocker_id = Uuid::new_v4(); + + execute_command( + &deps, + SocialCmd::Block { + blocker_id, + target: target.clone(), + }, + ) + .await + .unwrap(); + + execute_command(&deps, SocialCmd::Unblock { blocker_id, target }) + .await + .unwrap(); + + let published = events.published(); + assert!( + published + .iter() + .any(|e| matches!(e, DomainEvent::ActorUnblocked { .. })) + ); +} + +// ── Get following ─────────────────────────────────────────────────────────── + +#[tokio::test] +async fn returns_accepted_follows() { + let social = InMemorySocialRepository::new(); + let events = NoopEventPublisher::new(); + let cmd_deps = SocialCommandDeps { + social_command: Arc::clone(&social) as _, + social_query: Arc::clone(&social) as _, + event_publisher: Arc::clone(&events) as _, + }; + let query_deps = SocialQueryDeps { + social_query: Arc::clone(&social) as _, + }; + + let follower_id = Uuid::new_v4(); + let target_id = Uuid::new_v4(); + + execute_command( + &cmd_deps, + SocialCmd::Follow { + follower_id, + target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(target_id))), + }, + ) + .await + .unwrap(); + + // Pending follow should not appear + let following = execute_query( + &query_deps, + SocialQry::GetFollowing { + user_id: follower_id, + }, + ) + .await + .unwrap(); + assert!(following.is_empty()); + + // Accept, then it should appear + execute_command( + &cmd_deps, + SocialCmd::AcceptFollow { + owner_id: target_id, + requester: SocialIdentity::Local(UserId::from_uuid(follower_id)), + }, + ) + .await + .unwrap(); + + let following = execute_query( + &query_deps, + SocialQry::GetFollowing { + user_id: follower_id, + }, + ) + .await + .unwrap(); + assert_eq!(following.len(), 1); +} + +// ── Get followers ─────────────────────────────────────────────────────────── + +#[tokio::test] +async fn returns_accepted_followers() { + let social = InMemorySocialRepository::new(); + let events = NoopEventPublisher::new(); + let cmd_deps = SocialCommandDeps { + social_command: Arc::clone(&social) as _, + social_query: Arc::clone(&social) as _, + event_publisher: Arc::clone(&events) as _, + }; + let query_deps = SocialQueryDeps { + social_query: Arc::clone(&social) as _, + }; + + let follower_id = Uuid::new_v4(); + let owner_id = Uuid::new_v4(); + + execute_command( + &cmd_deps, + SocialCmd::Follow { + follower_id, + target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))), + }, + ) + .await + .unwrap(); + + execute_command( + &cmd_deps, + SocialCmd::AcceptFollow { + owner_id, + requester: SocialIdentity::Local(UserId::from_uuid(follower_id)), + }, + ) + .await + .unwrap(); + + let followers = execute_query(&query_deps, SocialQry::GetFollowers { user_id: owner_id }) + .await + .unwrap(); + assert_eq!(followers.len(), 1); +} + +// ── Get pending ───────────────────────────────────────────────────────────── + +#[tokio::test] +async fn returns_only_pending_followers() { + let social = InMemorySocialRepository::new(); + let events = NoopEventPublisher::new(); + let cmd_deps = SocialCommandDeps { + social_command: Arc::clone(&social) as _, + social_query: Arc::clone(&social) as _, + event_publisher: Arc::clone(&events) as _, + }; + let query_deps = SocialQueryDeps { + social_query: Arc::clone(&social) as _, + }; + + let follower_id = Uuid::new_v4(); + let owner_id = Uuid::new_v4(); + + execute_command( + &cmd_deps, + SocialCmd::Follow { + follower_id, + target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))), + }, + ) + .await + .unwrap(); + + let pending = execute_query(&query_deps, SocialQry::GetPending { user_id: owner_id }) + .await + .unwrap(); + assert_eq!(pending.len(), 1); +} diff --git a/crates/application/src/social/tests/follow.rs b/crates/application/src/social/tests/follow.rs deleted file mode 100644 index 67fefd8..0000000 --- a/crates/application/src/social/tests/follow.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::sync::Arc; - -use domain::{ - events::DomainEvent, - testing::{InMemorySocialRepository, NoopEventPublisher}, - value_objects::{FollowTarget, SocialIdentity, UserId}, -}; -use uuid::Uuid; - -use crate::social::{commands::FollowCommand, deps::SocialCommandDeps, follow}; - -fn make_deps() -> ( - Arc, - Arc, - SocialCommandDeps, -) { - let social = InMemorySocialRepository::new(); - let events = NoopEventPublisher::new(); - let deps = SocialCommandDeps { - social_command: Arc::clone(&social) as _, - social_query: Arc::clone(&social) as _, - event_publisher: Arc::clone(&events) as _, - }; - (social, events, deps) -} - -#[tokio::test] -async fn follow_emits_follow_requested_event() { - let (_social, events, deps) = make_deps(); - - follow::execute( - &deps, - FollowCommand { - follower_id: Uuid::new_v4(), - target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4()))), - }, - ) - .await - .unwrap(); - - let published = events.published(); - assert!( - published - .iter() - .any(|e| matches!(e, DomainEvent::FollowRequested { .. })) - ); -} - -#[tokio::test] -async fn cannot_follow_yourself() { - let (_social, _events, deps) = make_deps(); - let user_id = Uuid::new_v4(); - - let result = follow::execute( - &deps, - FollowCommand { - follower_id: user_id, - target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(user_id))), - }, - ) - .await; - - assert!(result.is_err()); -} - -#[tokio::test] -async fn cannot_follow_same_target_twice() { - let (_social, _events, deps) = make_deps(); - let follower_id = Uuid::new_v4(); - let target = FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4()))); - - follow::execute( - &deps, - FollowCommand { - follower_id, - target: target.clone(), - }, - ) - .await - .unwrap(); - - let result = follow::execute( - &deps, - FollowCommand { - follower_id, - target, - }, - ) - .await; - - assert!(result.is_err()); -} diff --git a/crates/application/src/social/tests/get_followers.rs b/crates/application/src/social/tests/get_followers.rs deleted file mode 100644 index 3ad1fed..0000000 --- a/crates/application/src/social/tests/get_followers.rs +++ /dev/null @@ -1,57 +0,0 @@ -use std::sync::Arc; - -use domain::{ - testing::{InMemorySocialRepository, NoopEventPublisher}, - value_objects::{FollowTarget, SocialIdentity, UserId}, -}; -use uuid::Uuid; - -use crate::social::{ - accept, - commands::{AcceptFollowCommand, FollowCommand}, - deps::{SocialCommandDeps, SocialQueryDeps}, - follow, get_followers, - queries::GetFollowersQuery, -}; - -#[tokio::test] -async fn returns_accepted_followers() { - let social = InMemorySocialRepository::new(); - let events = NoopEventPublisher::new(); - let cmd_deps = SocialCommandDeps { - social_command: Arc::clone(&social) as _, - social_query: Arc::clone(&social) as _, - event_publisher: Arc::clone(&events) as _, - }; - let query_deps = SocialQueryDeps { - social_query: Arc::clone(&social) as _, - }; - - let follower_id = Uuid::new_v4(); - let owner_id = Uuid::new_v4(); - - follow::execute( - &cmd_deps, - FollowCommand { - follower_id, - target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))), - }, - ) - .await - .unwrap(); - - accept::execute( - &cmd_deps, - AcceptFollowCommand { - owner_id, - requester: SocialIdentity::Local(UserId::from_uuid(follower_id)), - }, - ) - .await - .unwrap(); - - let followers = get_followers::execute(&query_deps, GetFollowersQuery { user_id: owner_id }) - .await - .unwrap(); - assert_eq!(followers.len(), 1); -} diff --git a/crates/application/src/social/tests/get_following.rs b/crates/application/src/social/tests/get_following.rs deleted file mode 100644 index 1de05c1..0000000 --- a/crates/application/src/social/tests/get_following.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::sync::Arc; - -use domain::{ - testing::{InMemorySocialRepository, NoopEventPublisher}, - value_objects::{FollowTarget, SocialIdentity, UserId}, -}; -use uuid::Uuid; - -use crate::social::{ - accept, - commands::{AcceptFollowCommand, FollowCommand}, - deps::{SocialCommandDeps, SocialQueryDeps}, - follow, get_following, - queries::GetFollowingQuery, -}; - -#[tokio::test] -async fn returns_accepted_follows() { - let social = InMemorySocialRepository::new(); - let events = NoopEventPublisher::new(); - let cmd_deps = SocialCommandDeps { - social_command: Arc::clone(&social) as _, - social_query: Arc::clone(&social) as _, - event_publisher: Arc::clone(&events) as _, - }; - let query_deps = SocialQueryDeps { - social_query: Arc::clone(&social) as _, - }; - - let follower_id = Uuid::new_v4(); - let target_id = Uuid::new_v4(); - - follow::execute( - &cmd_deps, - FollowCommand { - follower_id, - target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(target_id))), - }, - ) - .await - .unwrap(); - - // Pending follow should not appear - let following = get_following::execute( - &query_deps, - GetFollowingQuery { - user_id: follower_id, - }, - ) - .await - .unwrap(); - assert!(following.is_empty()); - - // Accept, then it should appear - accept::execute( - &cmd_deps, - AcceptFollowCommand { - owner_id: target_id, - requester: SocialIdentity::Local(UserId::from_uuid(follower_id)), - }, - ) - .await - .unwrap(); - - let following = get_following::execute( - &query_deps, - GetFollowingQuery { - user_id: follower_id, - }, - ) - .await - .unwrap(); - assert_eq!(following.len(), 1); -} diff --git a/crates/application/src/social/tests/get_pending.rs b/crates/application/src/social/tests/get_pending.rs deleted file mode 100644 index 2358169..0000000 --- a/crates/application/src/social/tests/get_pending.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::sync::Arc; - -use domain::{ - testing::{InMemorySocialRepository, NoopEventPublisher}, - value_objects::{FollowTarget, SocialIdentity, UserId}, -}; -use uuid::Uuid; - -use crate::social::{ - commands::FollowCommand, - deps::{SocialCommandDeps, SocialQueryDeps}, - follow, get_pending, - queries::GetPendingFollowersQuery, -}; - -#[tokio::test] -async fn returns_only_pending_followers() { - let social = InMemorySocialRepository::new(); - let events = NoopEventPublisher::new(); - let cmd_deps = SocialCommandDeps { - social_command: Arc::clone(&social) as _, - social_query: Arc::clone(&social) as _, - event_publisher: Arc::clone(&events) as _, - }; - let query_deps = SocialQueryDeps { - social_query: Arc::clone(&social) as _, - }; - - let follower_id = Uuid::new_v4(); - let owner_id = Uuid::new_v4(); - - follow::execute( - &cmd_deps, - FollowCommand { - follower_id, - target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))), - }, - ) - .await - .unwrap(); - - let pending = get_pending::execute(&query_deps, GetPendingFollowersQuery { user_id: owner_id }) - .await - .unwrap(); - assert_eq!(pending.len(), 1); -} diff --git a/crates/application/src/social/tests/reject.rs b/crates/application/src/social/tests/reject.rs deleted file mode 100644 index 6c4d261..0000000 --- a/crates/application/src/social/tests/reject.rs +++ /dev/null @@ -1,55 +0,0 @@ -use std::sync::Arc; - -use domain::{ - testing::{InMemorySocialRepository, NoopEventPublisher}, - value_objects::{FollowTarget, SocialIdentity, UserId}, -}; -use uuid::Uuid; - -use crate::social::{ - commands::{FollowCommand, RejectFollowCommand}, - deps::SocialCommandDeps, - follow, reject, -}; - -fn make_deps() -> ( - Arc, - Arc, - SocialCommandDeps, -) { - let social = InMemorySocialRepository::new(); - let events = NoopEventPublisher::new(); - let deps = SocialCommandDeps { - social_command: Arc::clone(&social) as _, - social_query: Arc::clone(&social) as _, - event_publisher: Arc::clone(&events) as _, - }; - (social, events, deps) -} - -#[tokio::test] -async fn reject_follow_completes_without_error() { - let (_social, _events, deps) = make_deps(); - let follower_id = Uuid::new_v4(); - let owner_id = Uuid::new_v4(); - - follow::execute( - &deps, - FollowCommand { - follower_id, - target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))), - }, - ) - .await - .unwrap(); - - reject::execute( - &deps, - RejectFollowCommand { - owner_id, - requester: SocialIdentity::Local(UserId::from_uuid(follower_id)), - }, - ) - .await - .unwrap(); -} diff --git a/crates/application/src/social/tests/remove_follower.rs b/crates/application/src/social/tests/remove_follower.rs deleted file mode 100644 index 97aa823..0000000 --- a/crates/application/src/social/tests/remove_follower.rs +++ /dev/null @@ -1,74 +0,0 @@ -use std::sync::Arc; - -use domain::{ - events::DomainEvent, - testing::{InMemorySocialRepository, NoopEventPublisher}, - value_objects::{FollowTarget, SocialIdentity, UserId}, -}; -use uuid::Uuid; - -use crate::social::{ - accept, - commands::{AcceptFollowCommand, FollowCommand, RemoveFollowerCommand}, - deps::SocialCommandDeps, - follow, remove_follower, -}; - -fn make_deps() -> ( - Arc, - Arc, - SocialCommandDeps, -) { - let social = InMemorySocialRepository::new(); - let events = NoopEventPublisher::new(); - let deps = SocialCommandDeps { - social_command: Arc::clone(&social) as _, - social_query: Arc::clone(&social) as _, - event_publisher: Arc::clone(&events) as _, - }; - (social, events, deps) -} - -#[tokio::test] -async fn remove_follower_emits_follower_removed_event() { - let (_social, events, deps) = make_deps(); - let follower_id = Uuid::new_v4(); - let owner_id = Uuid::new_v4(); - - follow::execute( - &deps, - FollowCommand { - follower_id, - target: FollowTarget::Identity(SocialIdentity::Local(UserId::from_uuid(owner_id))), - }, - ) - .await - .unwrap(); - - accept::execute( - &deps, - AcceptFollowCommand { - owner_id, - requester: SocialIdentity::Local(UserId::from_uuid(follower_id)), - }, - ) - .await - .unwrap(); - - remove_follower::execute( - &deps, - RemoveFollowerCommand { - owner_id, - follower: SocialIdentity::Local(UserId::from_uuid(follower_id)), - }, - ) - .await - .unwrap(); - - let published = events.published(); - assert!( - published - .iter() - .any(|e| matches!(e, DomainEvent::FollowerRemoved { .. })) - ); -} diff --git a/crates/application/src/social/tests/unblock.rs b/crates/application/src/social/tests/unblock.rs deleted file mode 100644 index bd3e910..0000000 --- a/crates/application/src/social/tests/unblock.rs +++ /dev/null @@ -1,58 +0,0 @@ -use std::sync::Arc; - -use domain::{ - events::DomainEvent, - testing::{InMemorySocialRepository, NoopEventPublisher}, - value_objects::{SocialIdentity, UserId}, -}; -use uuid::Uuid; - -use crate::social::{ - block, - commands::{BlockCommand, UnblockCommand}, - deps::SocialCommandDeps, - unblock, -}; - -fn make_deps() -> ( - Arc, - Arc, - SocialCommandDeps, -) { - let social = InMemorySocialRepository::new(); - let events = NoopEventPublisher::new(); - let deps = SocialCommandDeps { - social_command: Arc::clone(&social) as _, - social_query: Arc::clone(&social) as _, - event_publisher: Arc::clone(&events) as _, - }; - (social, events, deps) -} - -#[tokio::test] -async fn unblock_emits_actor_unblocked_event() { - let (_social, events, deps) = make_deps(); - let target = SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4())); - let blocker_id = Uuid::new_v4(); - - block::execute( - &deps, - BlockCommand { - blocker_id, - target: target.clone(), - }, - ) - .await - .unwrap(); - - unblock::execute(&deps, UnblockCommand { blocker_id, target }) - .await - .unwrap(); - - let published = events.published(); - assert!( - published - .iter() - .any(|e| matches!(e, DomainEvent::ActorUnblocked { .. })) - ); -} diff --git a/crates/application/src/social/tests/unfollow.rs b/crates/application/src/social/tests/unfollow.rs deleted file mode 100644 index 2f7f26a..0000000 --- a/crates/application/src/social/tests/unfollow.rs +++ /dev/null @@ -1,63 +0,0 @@ -use std::sync::Arc; - -use domain::{ - events::DomainEvent, - testing::{InMemorySocialRepository, NoopEventPublisher}, - value_objects::{FollowTarget, SocialIdentity, UserId}, -}; -use uuid::Uuid; - -use crate::social::{ - commands::{FollowCommand, UnfollowCommand}, - deps::SocialCommandDeps, - follow, unfollow, -}; - -fn make_deps() -> ( - Arc, - Arc, - SocialCommandDeps, -) { - let social = InMemorySocialRepository::new(); - let events = NoopEventPublisher::new(); - let deps = SocialCommandDeps { - social_command: Arc::clone(&social) as _, - social_query: Arc::clone(&social) as _, - event_publisher: Arc::clone(&events) as _, - }; - (social, events, deps) -} - -#[tokio::test] -async fn unfollow_emits_unfollowed_event() { - let (_social, events, deps) = make_deps(); - let follower_id = Uuid::new_v4(); - let target = SocialIdentity::Local(UserId::from_uuid(Uuid::new_v4())); - - follow::execute( - &deps, - FollowCommand { - follower_id, - target: FollowTarget::Identity(target.clone()), - }, - ) - .await - .unwrap(); - - unfollow::execute( - &deps, - UnfollowCommand { - follower_id, - target, - }, - ) - .await - .unwrap(); - - let published = events.published(); - assert!( - published - .iter() - .any(|e| matches!(e, DomainEvent::Unfollowed { .. })) - ); -} diff --git a/crates/application/src/social/unblock.rs b/crates/application/src/social/unblock.rs deleted file mode 100644 index 3a08e03..0000000 --- a/crates/application/src/social/unblock.rs +++ /dev/null @@ -1,18 +0,0 @@ -use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId}; - -use super::{commands::UnblockCommand, deps::SocialCommandDeps}; - -pub async fn execute(deps: &SocialCommandDeps, cmd: UnblockCommand) -> Result<(), DomainError> { - let blocker = UserId::from_uuid(cmd.blocker_id); - deps.social_command.unblock(&blocker, &cmd.target).await?; - deps.event_publisher - .publish(&DomainEvent::ActorUnblocked { - blocker, - target: cmd.target, - }) - .await -} - -#[cfg(test)] -#[path = "tests/unblock.rs"] -mod tests; diff --git a/crates/application/src/social/unfollow.rs b/crates/application/src/social/unfollow.rs deleted file mode 100644 index 1cfe93b..0000000 --- a/crates/application/src/social/unfollow.rs +++ /dev/null @@ -1,18 +0,0 @@ -use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId}; - -use super::{commands::UnfollowCommand, deps::SocialCommandDeps}; - -pub async fn execute(deps: &SocialCommandDeps, cmd: UnfollowCommand) -> Result<(), DomainError> { - let follower = UserId::from_uuid(cmd.follower_id); - deps.social_command.unfollow(&follower, &cmd.target).await?; - deps.event_publisher - .publish(&DomainEvent::Unfollowed { - follower, - target: cmd.target, - }) - .await -} - -#[cfg(test)] -#[path = "tests/unfollow.rs"] -mod tests; diff --git a/crates/application/src/tests/worker.rs b/crates/application/src/tests/worker.rs index 2a170c3..2e156a7 100644 --- a/crates/application/src/tests/worker.rs +++ b/crates/application/src/tests/worker.rs @@ -74,6 +74,7 @@ impl EventHandler for RecordingHandler { } DomainEvent::FollowRequested { .. } => "follow_requested", DomainEvent::FollowAccepted { .. } => "follow_accepted", + DomainEvent::FollowRejected { .. } => "follow_rejected", DomainEvent::Unfollowed { .. } => "unfollowed", DomainEvent::FollowerRemoved { .. } => "follower_removed", DomainEvent::ActorBlocked { .. } => "actor_blocked", diff --git a/crates/domain/src/events.rs b/crates/domain/src/events.rs index e828460..073c6e3 100644 --- a/crates/domain/src/events.rs +++ b/crates/domain/src/events.rs @@ -71,6 +71,10 @@ pub enum DomainEvent { owner: UserId, requester: crate::value_objects::SocialIdentity, }, + FollowRejected { + owner: UserId, + requester: crate::value_objects::SocialIdentity, + }, Unfollowed { follower: UserId, target: crate::value_objects::SocialIdentity, diff --git a/crates/presentation/src/handlers/social.rs b/crates/presentation/src/handlers/social.rs index f730ae5..aa29a85 100644 --- a/crates/presentation/src/handlers/social.rs +++ b/crates/presentation/src/handlers/social.rs @@ -171,9 +171,9 @@ pub async fn block_actor_api( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - application::social::block::execute( + application::social::execute::execute_command( &deps, - application::social::commands::BlockCommand { + application::social::commands::SocialCmd::Block { blocker_id: user.0.value(), target: SocialIdentity::Remote { actor_url: body.actor_url, @@ -203,9 +203,9 @@ pub async fn unblock_actor_api( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - application::social::unblock::execute( + application::social::execute::execute_command( &deps, - application::social::commands::UnblockCommand { + application::social::commands::SocialCmd::Unblock { blocker_id: user.0.value(), target: SocialIdentity::Remote { actor_url: body.actor_url, @@ -231,9 +231,9 @@ pub async fn get_blocked_actors_api( let deps = SocialQueryDeps { social_query: state.app_ctx.repos.social_query_unified.clone(), }; - let identities = application::social::get_blocked::execute( + let identities = application::social::execute::execute_query( &deps, - application::social::queries::GetBlockedQuery { + application::social::queries::SocialQry::GetBlocked { user_id: user.0.value(), }, ) @@ -261,9 +261,9 @@ pub async fn get_following( let deps = SocialQueryDeps { social_query: state.app_ctx.repos.social_query_unified.clone(), }; - let identities = application::social::get_following::execute( + let identities = application::social::execute::execute_query( &deps, - application::social::queries::GetFollowingQuery { + application::social::queries::SocialQry::GetFollowing { user_id: user.0.value(), }, ) @@ -288,9 +288,9 @@ pub async fn get_followers( let deps = SocialQueryDeps { social_query: state.app_ctx.repos.social_query_unified.clone(), }; - let identities = application::social::get_followers::execute( + let identities = application::social::execute::execute_query( &deps, - application::social::queries::GetFollowersQuery { + application::social::queries::SocialQry::GetFollowers { user_id: user.0.value(), }, ) @@ -308,9 +308,9 @@ pub async fn get_user_following( let deps = SocialQueryDeps { social_query: state.app_ctx.repos.social_query_unified.clone(), }; - let identities = application::social::get_following::execute( + let identities = application::social::execute::execute_query( &deps, - application::social::queries::GetFollowingQuery { user_id }, + application::social::queries::SocialQry::GetFollowing { user_id }, ) .await?; Ok(Json(ActorListResponse { @@ -326,9 +326,9 @@ pub async fn get_user_followers( let deps = SocialQueryDeps { social_query: state.app_ctx.repos.social_query_unified.clone(), }; - let identities = application::social::get_followers::execute( + let identities = application::social::execute::execute_query( &deps, - application::social::queries::GetFollowersQuery { user_id }, + application::social::queries::SocialQry::GetFollowers { user_id }, ) .await?; Ok(Json(ActorListResponse { @@ -355,9 +355,9 @@ pub async fn follow( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - application::social::follow::execute( + application::social::execute::execute_command( &deps, - application::social::commands::FollowCommand { + application::social::commands::SocialCmd::Follow { follower_id: user.0.value(), target: FollowTarget::Handle(body.handle), }, @@ -385,9 +385,9 @@ pub async fn unfollow( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - application::social::unfollow::execute( + application::social::execute::execute_command( &deps, - application::social::commands::UnfollowCommand { + application::social::commands::SocialCmd::Unfollow { follower_id: user.0.value(), target: SocialIdentity::Remote { actor_url: body.actor_url, @@ -417,9 +417,9 @@ pub async fn accept_follower( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - application::social::accept::execute( + application::social::execute::execute_command( &deps, - application::social::commands::AcceptFollowCommand { + application::social::commands::SocialCmd::AcceptFollow { owner_id: user.0.value(), requester: SocialIdentity::Remote { actor_url: body.actor_url, @@ -449,9 +449,9 @@ pub async fn reject_follower( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - application::social::reject::execute( + application::social::execute::execute_command( &deps, - application::social::commands::RejectFollowCommand { + application::social::commands::SocialCmd::RejectFollow { owner_id: user.0.value(), requester: SocialIdentity::Remote { actor_url: body.actor_url, @@ -481,9 +481,9 @@ pub async fn remove_follower( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - application::social::remove_follower::execute( + application::social::execute::execute_command( &deps, - application::social::commands::RemoveFollowerCommand { + application::social::commands::SocialCmd::RemoveFollower { owner_id: user.0.value(), follower: SocialIdentity::Remote { actor_url: body.actor_url, @@ -509,9 +509,9 @@ pub async fn get_pending_followers( let deps = SocialQueryDeps { social_query: state.app_ctx.repos.social_query_unified.clone(), }; - let identities = application::social::get_pending::execute( + let identities = application::social::execute::execute_query( &deps, - application::social::queries::GetPendingFollowersQuery { + application::social::queries::SocialQry::GetPending { user_id: user.0.value(), }, ) @@ -548,9 +548,9 @@ pub async fn follow_remote_user( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - match application::social::follow::execute( + match application::social::execute::execute_command( &deps, - application::social::commands::FollowCommand { + application::social::commands::SocialCmd::Follow { follower_id: user_id.value(), target: FollowTarget::Handle(form.handle), }, @@ -589,9 +589,9 @@ pub async fn unfollow_remote_user( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - match application::social::unfollow::execute( + match application::social::execute::execute_command( &deps, - application::social::commands::UnfollowCommand { + application::social::commands::SocialCmd::Unfollow { follower_id: user_id.value(), target: SocialIdentity::Remote { actor_url: form.actor_url, @@ -632,9 +632,9 @@ pub async fn accept_follower_html( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - match application::social::accept::execute( + match application::social::execute::execute_command( &deps, - application::social::commands::AcceptFollowCommand { + application::social::commands::SocialCmd::AcceptFollow { owner_id: user_id.value(), requester: SocialIdentity::Remote { actor_url: form.actor_url, @@ -669,9 +669,9 @@ pub async fn reject_follower_html( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - match application::social::reject::execute( + match application::social::execute::execute_command( &deps, - application::social::commands::RejectFollowCommand { + application::social::commands::SocialCmd::RejectFollow { owner_id: user_id.value(), requester: SocialIdentity::Remote { actor_url: form.actor_url, @@ -773,9 +773,9 @@ pub async fn get_following_page( let deps = SocialQueryDeps { social_query: state.app_ctx.repos.social_query_unified.clone(), }; - match application::social::get_following::execute( + match application::social::execute::execute_query( &deps, - application::social::queries::GetFollowingQuery { + application::social::queries::SocialQry::GetFollowing { user_id: user_id.value(), }, ) @@ -824,9 +824,9 @@ pub async fn get_followers_page( let deps = SocialQueryDeps { social_query: state.app_ctx.repos.social_query_unified.clone(), }; - match application::social::get_followers::execute( + match application::social::execute::execute_query( &deps, - application::social::queries::GetFollowersQuery { + application::social::queries::SocialQry::GetFollowers { user_id: user_id.value(), }, ) @@ -874,9 +874,9 @@ pub async fn remove_follower_html( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - match application::social::remove_follower::execute( + match application::social::execute::execute_command( &deps, - application::social::commands::RemoveFollowerCommand { + application::social::commands::SocialCmd::RemoveFollower { owner_id: user_id.value(), follower: SocialIdentity::Remote { actor_url: form.actor_url, @@ -1000,9 +1000,9 @@ pub async fn get_blocked_actors_page( let deps = SocialQueryDeps { social_query: state.app_ctx.repos.social_query_unified.clone(), }; - match application::social::get_blocked::execute( + match application::social::execute::execute_query( &deps, - application::social::queries::GetBlockedQuery { + application::social::queries::SocialQry::GetBlocked { user_id: user_id.value(), }, ) @@ -1049,9 +1049,9 @@ pub async fn post_block_actor_html( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - match application::social::block::execute( + match application::social::execute::execute_command( &deps, - application::social::commands::BlockCommand { + application::social::commands::SocialCmd::Block { blocker_id: user_id.value(), target: SocialIdentity::Remote { actor_url: form.actor_url, @@ -1082,9 +1082,9 @@ pub async fn post_unblock_actor( social_query: state.app_ctx.repos.social_query_unified.clone(), event_publisher: state.app_ctx.services.event_publisher.clone(), }; - match application::social::unblock::execute( + match application::social::execute::execute_command( &deps, - application::social::commands::UnblockCommand { + application::social::commands::SocialCmd::Unblock { blocker_id: user_id.value(), target: SocialIdentity::Remote { actor_url: form.actor_url,