feat(application): all use cases
This commit is contained in:
117
crates/application/src/use_cases/social.rs
Normal file
117
crates/application/src/use_cases/social.rs
Normal file
@@ -0,0 +1,117 @@
|
||||
use chrono::Utc;
|
||||
use domain::{
|
||||
errors::DomainError,
|
||||
events::DomainEvent,
|
||||
models::social::{Block, Boost, Follow, FollowState, Like},
|
||||
ports::{BlockRepository, BoostRepository, EventPublisher, FollowRepository, LikeRepository},
|
||||
value_objects::{BoostId, LikeId, ThoughtId, UserId},
|
||||
};
|
||||
|
||||
pub async fn like_thought(likes: &dyn LikeRepository, events: &dyn EventPublisher, user_id: &UserId, thought_id: &ThoughtId) -> Result<(), DomainError> {
|
||||
let like = Like { id: LikeId::new(), user_id: user_id.clone(), thought_id: thought_id.clone(), ap_id: None, created_at: Utc::now() };
|
||||
likes.save(&like).await?;
|
||||
events.publish(&DomainEvent::LikeAdded { like_id: like.id, user_id: user_id.clone(), thought_id: thought_id.clone() }).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn unlike_thought(likes: &dyn LikeRepository, events: &dyn EventPublisher, user_id: &UserId, thought_id: &ThoughtId) -> Result<(), DomainError> {
|
||||
likes.delete(user_id, thought_id).await?;
|
||||
events.publish(&DomainEvent::LikeRemoved { user_id: user_id.clone(), thought_id: thought_id.clone() }).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn boost_thought(boosts: &dyn BoostRepository, events: &dyn EventPublisher, user_id: &UserId, thought_id: &ThoughtId) -> Result<(), DomainError> {
|
||||
let boost = Boost { id: BoostId::new(), user_id: user_id.clone(), thought_id: thought_id.clone(), ap_id: None, created_at: Utc::now() };
|
||||
boosts.save(&boost).await?;
|
||||
events.publish(&DomainEvent::BoostAdded { boost_id: boost.id, user_id: user_id.clone(), thought_id: thought_id.clone() }).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn unboost_thought(boosts: &dyn BoostRepository, events: &dyn EventPublisher, user_id: &UserId, thought_id: &ThoughtId) -> Result<(), DomainError> {
|
||||
boosts.delete(user_id, thought_id).await?;
|
||||
events.publish(&DomainEvent::BoostRemoved { user_id: user_id.clone(), thought_id: thought_id.clone() }).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn follow_user(follows: &dyn FollowRepository, events: &dyn EventPublisher, follower_id: &UserId, following_id: &UserId) -> Result<(), DomainError> {
|
||||
if follower_id == following_id { return Err(DomainError::InvalidInput("cannot follow yourself".into())); }
|
||||
let follow = Follow { follower_id: follower_id.clone(), following_id: following_id.clone(), state: FollowState::Accepted, ap_id: None, created_at: Utc::now() };
|
||||
follows.save(&follow).await?;
|
||||
events.publish(&DomainEvent::FollowRequested { follower_id: follower_id.clone(), following_id: following_id.clone() }).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn unfollow_user(follows: &dyn FollowRepository, events: &dyn EventPublisher, follower_id: &UserId, following_id: &UserId) -> Result<(), DomainError> {
|
||||
follows.delete(follower_id, following_id).await?;
|
||||
events.publish(&DomainEvent::Unfollowed { follower_id: follower_id.clone(), following_id: following_id.clone() }).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn accept_follow(follows: &dyn FollowRepository, events: &dyn EventPublisher, follower_id: &UserId, following_id: &UserId) -> Result<(), DomainError> {
|
||||
follows.update_state(follower_id, following_id, &FollowState::Accepted).await?;
|
||||
events.publish(&DomainEvent::FollowAccepted { follower_id: follower_id.clone(), following_id: following_id.clone() }).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn reject_follow(follows: &dyn FollowRepository, events: &dyn EventPublisher, follower_id: &UserId, following_id: &UserId) -> Result<(), DomainError> {
|
||||
follows.update_state(follower_id, following_id, &FollowState::Rejected).await?;
|
||||
events.publish(&DomainEvent::FollowRejected { follower_id: follower_id.clone(), following_id: following_id.clone() }).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn block_user(blocks: &dyn BlockRepository, events: &dyn EventPublisher, blocker_id: &UserId, blocked_id: &UserId) -> Result<(), DomainError> {
|
||||
if blocker_id == blocked_id { return Err(DomainError::InvalidInput("cannot block yourself".into())); }
|
||||
let block = Block { blocker_id: blocker_id.clone(), blocked_id: blocked_id.clone(), created_at: Utc::now() };
|
||||
blocks.save(&block).await?;
|
||||
events.publish(&DomainEvent::UserBlocked { blocker_id: blocker_id.clone(), blocked_id: blocked_id.clone() }).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn unblock_user(blocks: &dyn BlockRepository, blocker_id: &UserId, blocked_id: &UserId) -> Result<(), DomainError> {
|
||||
blocks.delete(blocker_id, blocked_id).await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use domain::{
|
||||
models::{thought::{Thought, Visibility}, user::User},
|
||||
testing::TestStore,
|
||||
value_objects::*,
|
||||
};
|
||||
|
||||
fn user(name: &str) -> User {
|
||||
User::new_local(UserId::new(), Username::new(name).unwrap(), Email::new(format!("{name}@ex.com")).unwrap(), PasswordHash("h".into()))
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn like_and_unlike() {
|
||||
let store = TestStore::default();
|
||||
let alice = user("alice");
|
||||
let tid = ThoughtId::new();
|
||||
store.thoughts.lock().unwrap().push(Thought::new_local(tid.clone(), alice.id.clone(), Content::new_local("hi").unwrap(), None, Visibility::Public, None, false));
|
||||
like_thought(&store, &store, &alice.id, &tid).await.unwrap();
|
||||
assert_eq!(store.likes.lock().unwrap().len(), 1);
|
||||
unlike_thought(&store, &store, &alice.id, &tid).await.unwrap();
|
||||
assert!(store.likes.lock().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn follow_and_unfollow() {
|
||||
let store = TestStore::default();
|
||||
let alice = user("alice"); let bob = user("bob");
|
||||
follow_user(&store, &store, &alice.id, &bob.id).await.unwrap();
|
||||
assert_eq!(store.follows.lock().unwrap().len(), 1);
|
||||
unfollow_user(&store, &store, &alice.id, &bob.id).await.unwrap();
|
||||
assert!(store.follows.lock().unwrap().is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn cannot_follow_self() {
|
||||
let store = TestStore::default();
|
||||
let alice = user("alice");
|
||||
let err = follow_user(&store, &store, &alice.id, &alice.id).await.unwrap_err();
|
||||
assert!(matches!(err, DomainError::InvalidInput(_)));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user