From 1127a5946f845592fa8bd4a6b3290962207992de Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Thu, 14 May 2026 11:06:36 +0200 Subject: [PATCH] feat: UserUnblocked + UserRegistered events, fix unblock_user and register signatures --- crates/adapters/event-payload/src/lib.rs | 22 ++++++++++++++++++++ crates/application/src/use_cases/auth.rs | 14 ++++++++++++- crates/application/src/use_cases/social.rs | 24 +++++++++++++++++++++- crates/domain/src/events.rs | 2 ++ crates/presentation/src/handlers/social.rs | 2 +- 5 files changed, 61 insertions(+), 3 deletions(-) diff --git a/crates/adapters/event-payload/src/lib.rs b/crates/adapters/event-payload/src/lib.rs index db4ecc6..0a8c617 100644 --- a/crates/adapters/event-payload/src/lib.rs +++ b/crates/adapters/event-payload/src/lib.rs @@ -61,6 +61,13 @@ pub enum EventPayload { blocker_id: String, blocked_id: String, }, + UserUnblocked { + blocker_id: String, + blocked_id: String, + }, + UserRegistered { + user_id: String, + }, } impl EventPayload { @@ -79,6 +86,8 @@ impl EventPayload { Self::FollowRejected { .. } => "follows.rejected", Self::Unfollowed { .. } => "follows.removed", Self::UserBlocked { .. } => "users.blocked", + Self::UserUnblocked { .. } => "users.unblocked", + Self::UserRegistered { .. } => "users.registered", } } } @@ -126,6 +135,12 @@ impl From<&DomainEvent> for EventPayload { DomainEvent::UserBlocked { blocker_id, blocked_id } => Self::UserBlocked { blocker_id: blocker_id.to_string(), blocked_id: blocked_id.to_string(), }, + DomainEvent::UserUnblocked { blocker_id, blocked_id } => Self::UserUnblocked { + blocker_id: blocker_id.to_string(), blocked_id: blocked_id.to_string(), + }, + DomainEvent::UserRegistered { user_id } => Self::UserRegistered { + user_id: user_id.to_string(), + }, } } } @@ -195,6 +210,13 @@ impl TryFrom for DomainEvent { blocker_id: UserId::from_uuid(parse_uuid(&blocker_id, "blocker_id")?), blocked_id: UserId::from_uuid(parse_uuid(&blocked_id, "blocked_id")?), }, + EventPayload::UserUnblocked { blocker_id, blocked_id } => DomainEvent::UserUnblocked { + blocker_id: UserId::from_uuid(parse_uuid(&blocker_id, "blocker_id")?), + blocked_id: UserId::from_uuid(parse_uuid(&blocked_id, "blocked_id")?), + }, + EventPayload::UserRegistered { user_id } => DomainEvent::UserRegistered { + user_id: UserId::from_uuid(parse_uuid(&user_id, "user_id")?), + }, }) } } diff --git a/crates/application/src/use_cases/auth.rs b/crates/application/src/use_cases/auth.rs index 1e96a6a..d57caa7 100644 --- a/crates/application/src/use_cases/auth.rs +++ b/crates/application/src/use_cases/auth.rs @@ -1,5 +1,6 @@ use domain::{ errors::DomainError, + events::DomainEvent, models::user::User, ports::{AuthService, EventPublisher, PasswordHasher, UserRepository}, value_objects::{Email, UserId, Username}, @@ -13,7 +14,7 @@ pub async fn register( users: &dyn UserRepository, hasher: &dyn PasswordHasher, auth: &dyn AuthService, - _events: &dyn EventPublisher, + events: &dyn EventPublisher, input: RegisterInput, ) -> Result { let username = Username::new(input.username)?; @@ -27,6 +28,7 @@ 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?; + events.publish(&DomainEvent::UserRegistered { user_id: user.id.clone() }).await?; let token = auth.generate_token(&user.id)?; Ok(RegisterOutput { user, token: token.token }) } @@ -56,6 +58,7 @@ mod tests { use async_trait::async_trait; use domain::{ errors::DomainError, + events::DomainEvent, ports::{AuthService, GeneratedToken, PasswordHasher}, testing::{NoOpEventPublisher, TestStore}, value_objects::{PasswordHash, UserId}, @@ -112,4 +115,13 @@ mod tests { let err = login(&store, &FakeHasher, &FakeAuth, LoginInput { email: "alice@ex.com".into(), password: "wrong".into() }).await.unwrap_err(); assert!(matches!(err, DomainError::Unauthorized)); } + + #[tokio::test] + async fn register_publishes_user_registered_event() { + let store = TestStore::default(); + register(&store, &FakeHasher, &FakeAuth, &store, input()).await.unwrap(); + let events = store.events.lock().unwrap(); + assert_eq!(events.len(), 1); + assert!(matches!(events[0], DomainEvent::UserRegistered { .. })); + } } diff --git a/crates/application/src/use_cases/social.rs b/crates/application/src/use_cases/social.rs index 66954ac..2a467a8 100644 --- a/crates/application/src/use_cases/social.rs +++ b/crates/application/src/use_cases/social.rs @@ -67,8 +67,17 @@ pub async fn block_user(blocks: &dyn BlockRepository, events: &dyn EventPublishe Ok(()) } -pub async fn unblock_user(blocks: &dyn BlockRepository, blocker_id: &UserId, blocked_id: &UserId) -> Result<(), DomainError> { +pub async fn unblock_user( + blocks: &dyn BlockRepository, + events: &dyn EventPublisher, + blocker_id: &UserId, + blocked_id: &UserId, +) -> Result<(), DomainError> { blocks.delete(blocker_id, blocked_id).await?; + events.publish(&DomainEvent::UserUnblocked { + blocker_id: blocker_id.clone(), + blocked_id: blocked_id.clone(), + }).await?; Ok(()) } @@ -114,4 +123,17 @@ mod tests { let err = follow_user(&store, &store, &alice.id, &alice.id).await.unwrap_err(); assert!(matches!(err, DomainError::InvalidInput(_))); } + + #[tokio::test] + async fn unblock_user_publishes_event() { + let store = TestStore::default(); + let alice = user("alice"); + let bob = user("bob"); + block_user(&store, &store, &alice.id, &bob.id).await.unwrap(); + store.events.lock().unwrap().clear(); + unblock_user(&store, &store, &alice.id, &bob.id).await.unwrap(); + let events = store.events.lock().unwrap(); + assert_eq!(events.len(), 1); + assert!(matches!(events[0], DomainEvent::UserUnblocked { .. })); + } } diff --git a/crates/domain/src/events.rs b/crates/domain/src/events.rs index 0e7216f..95ef776 100644 --- a/crates/domain/src/events.rs +++ b/crates/domain/src/events.rs @@ -14,6 +14,8 @@ pub enum DomainEvent { FollowRejected { follower_id: UserId, following_id: UserId }, Unfollowed { follower_id: UserId, following_id: UserId }, UserBlocked { blocker_id: UserId, blocked_id: UserId }, + UserUnblocked { blocker_id: UserId, blocked_id: UserId }, + UserRegistered { user_id: UserId }, } pub struct EventEnvelope { diff --git a/crates/presentation/src/handlers/social.rs b/crates/presentation/src/handlers/social.rs index 6bef8ee..26f838c 100644 --- a/crates/presentation/src/handlers/social.rs +++ b/crates/presentation/src/handlers/social.rs @@ -35,7 +35,7 @@ pub async fn post_block(State(s): State, AuthUser(uid): AuthUser, Path Ok(StatusCode::NO_CONTENT) } pub async fn delete_block(State(s): State, AuthUser(uid): AuthUser, Path(target): Path) -> Result { - unblock_user(&*s.blocks, &uid, &UserId::from_uuid(target)).await?; + unblock_user(&*s.blocks, &*s.events, &uid, &UserId::from_uuid(target)).await?; Ok(StatusCode::NO_CONTENT) } pub async fn put_top_friends(State(s): State, AuthUser(uid): AuthUser, Json(body): Json) -> Result {