feat: UserUnblocked + UserRegistered events, fix unblock_user and register signatures
This commit is contained in:
@@ -61,6 +61,13 @@ pub enum EventPayload {
|
|||||||
blocker_id: String,
|
blocker_id: String,
|
||||||
blocked_id: String,
|
blocked_id: String,
|
||||||
},
|
},
|
||||||
|
UserUnblocked {
|
||||||
|
blocker_id: String,
|
||||||
|
blocked_id: String,
|
||||||
|
},
|
||||||
|
UserRegistered {
|
||||||
|
user_id: String,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventPayload {
|
impl EventPayload {
|
||||||
@@ -79,6 +86,8 @@ impl EventPayload {
|
|||||||
Self::FollowRejected { .. } => "follows.rejected",
|
Self::FollowRejected { .. } => "follows.rejected",
|
||||||
Self::Unfollowed { .. } => "follows.removed",
|
Self::Unfollowed { .. } => "follows.removed",
|
||||||
Self::UserBlocked { .. } => "users.blocked",
|
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 {
|
DomainEvent::UserBlocked { blocker_id, blocked_id } => Self::UserBlocked {
|
||||||
blocker_id: blocker_id.to_string(), blocked_id: blocked_id.to_string(),
|
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<EventPayload> for DomainEvent {
|
|||||||
blocker_id: UserId::from_uuid(parse_uuid(&blocker_id, "blocker_id")?),
|
blocker_id: UserId::from_uuid(parse_uuid(&blocker_id, "blocker_id")?),
|
||||||
blocked_id: UserId::from_uuid(parse_uuid(&blocked_id, "blocked_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")?),
|
||||||
|
},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
|
events::DomainEvent,
|
||||||
models::user::User,
|
models::user::User,
|
||||||
ports::{AuthService, EventPublisher, PasswordHasher, UserRepository},
|
ports::{AuthService, EventPublisher, PasswordHasher, UserRepository},
|
||||||
value_objects::{Email, UserId, Username},
|
value_objects::{Email, UserId, Username},
|
||||||
@@ -13,7 +14,7 @@ pub async fn register(
|
|||||||
users: &dyn UserRepository,
|
users: &dyn UserRepository,
|
||||||
hasher: &dyn PasswordHasher,
|
hasher: &dyn PasswordHasher,
|
||||||
auth: &dyn AuthService,
|
auth: &dyn AuthService,
|
||||||
_events: &dyn EventPublisher,
|
events: &dyn EventPublisher,
|
||||||
input: RegisterInput,
|
input: RegisterInput,
|
||||||
) -> Result<RegisterOutput, DomainError> {
|
) -> Result<RegisterOutput, DomainError> {
|
||||||
let username = Username::new(input.username)?;
|
let username = Username::new(input.username)?;
|
||||||
@@ -27,6 +28,7 @@ pub async fn register(
|
|||||||
let hash = hasher.hash(&input.password).await?;
|
let hash = hasher.hash(&input.password).await?;
|
||||||
let user = User::new_local(UserId::new(), username, email, hash);
|
let user = User::new_local(UserId::new(), username, email, hash);
|
||||||
users.save(&user).await?;
|
users.save(&user).await?;
|
||||||
|
events.publish(&DomainEvent::UserRegistered { user_id: user.id.clone() }).await?;
|
||||||
let token = auth.generate_token(&user.id)?;
|
let token = auth.generate_token(&user.id)?;
|
||||||
Ok(RegisterOutput { user, token: token.token })
|
Ok(RegisterOutput { user, token: token.token })
|
||||||
}
|
}
|
||||||
@@ -56,6 +58,7 @@ mod tests {
|
|||||||
use async_trait::async_trait;
|
use async_trait::async_trait;
|
||||||
use domain::{
|
use domain::{
|
||||||
errors::DomainError,
|
errors::DomainError,
|
||||||
|
events::DomainEvent,
|
||||||
ports::{AuthService, GeneratedToken, PasswordHasher},
|
ports::{AuthService, GeneratedToken, PasswordHasher},
|
||||||
testing::{NoOpEventPublisher, TestStore},
|
testing::{NoOpEventPublisher, TestStore},
|
||||||
value_objects::{PasswordHash, UserId},
|
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();
|
let err = login(&store, &FakeHasher, &FakeAuth, LoginInput { email: "alice@ex.com".into(), password: "wrong".into() }).await.unwrap_err();
|
||||||
assert!(matches!(err, DomainError::Unauthorized));
|
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 { .. }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -67,8 +67,17 @@ pub async fn block_user(blocks: &dyn BlockRepository, events: &dyn EventPublishe
|
|||||||
Ok(())
|
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?;
|
blocks.delete(blocker_id, blocked_id).await?;
|
||||||
|
events.publish(&DomainEvent::UserUnblocked {
|
||||||
|
blocker_id: blocker_id.clone(),
|
||||||
|
blocked_id: blocked_id.clone(),
|
||||||
|
}).await?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -114,4 +123,17 @@ mod tests {
|
|||||||
let err = follow_user(&store, &store, &alice.id, &alice.id).await.unwrap_err();
|
let err = follow_user(&store, &store, &alice.id, &alice.id).await.unwrap_err();
|
||||||
assert!(matches!(err, DomainError::InvalidInput(_)));
|
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 { .. }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ pub enum DomainEvent {
|
|||||||
FollowRejected { follower_id: UserId, following_id: UserId },
|
FollowRejected { follower_id: UserId, following_id: UserId },
|
||||||
Unfollowed { follower_id: UserId, following_id: UserId },
|
Unfollowed { follower_id: UserId, following_id: UserId },
|
||||||
UserBlocked { blocker_id: UserId, blocked_id: UserId },
|
UserBlocked { blocker_id: UserId, blocked_id: UserId },
|
||||||
|
UserUnblocked { blocker_id: UserId, blocked_id: UserId },
|
||||||
|
UserRegistered { user_id: UserId },
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct EventEnvelope {
|
pub struct EventEnvelope {
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ pub async fn post_block(State(s): State<AppState>, AuthUser(uid): AuthUser, Path
|
|||||||
Ok(StatusCode::NO_CONTENT)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
pub async fn delete_block(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(target): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
pub async fn delete_block(State(s): State<AppState>, AuthUser(uid): AuthUser, Path(target): Path<Uuid>) -> Result<StatusCode, ApiError> {
|
||||||
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)
|
Ok(StatusCode::NO_CONTENT)
|
||||||
}
|
}
|
||||||
pub async fn put_top_friends(State(s): State<AppState>, AuthUser(uid): AuthUser, Json(body): Json<SetTopFriendsRequest>) -> Result<StatusCode, ApiError> {
|
pub async fn put_top_friends(State(s): State<AppState>, AuthUser(uid): AuthUser, Json(body): Json<SetTopFriendsRequest>) -> Result<StatusCode, ApiError> {
|
||||||
|
|||||||
Reference in New Issue
Block a user