From 61980b0cfbbd759631b72a5cdff6f1fdc18a00a2 Mon Sep 17 00:00:00 2001 From: Gabriel Kaszewski Date: Thu, 11 Jun 2026 22:47:17 +0200 Subject: [PATCH] refactor(users): GetProfileDeps, UpdateProfileDeps, scoped Arc deps --- crates/application/src/users/deps.rs | 18 ++++ .../src/users/get_current_profile.rs | 28 +++--- crates/application/src/users/get_profile.rs | 29 +++--- crates/application/src/users/get_settings.rs | 11 ++- crates/application/src/users/get_users.rs | 13 +-- crates/application/src/users/mod.rs | 1 + .../src/users/tests/get_current_profile.rs | 20 ++--- .../src/users/tests/get_profile.rs | 70 ++++++++++++--- .../src/users/tests/get_settings.rs | 5 +- .../application/src/users/tests/get_users.rs | 6 +- .../src/users/tests/update_profile.rs | 88 +++++++++++++------ .../src/users/tests/update_profile_fields.rs | 17 ++-- .../src/users/tests/update_settings.rs | 10 +-- .../application/src/users/update_profile.rs | 27 +++--- .../src/users/update_profile_fields.rs | 20 +++-- .../application/src/users/update_settings.rs | 13 +-- crates/presentation/src/handlers/goals.rs | 9 +- crates/presentation/src/handlers/users.rs | 56 ++++++++++-- 18 files changed, 296 insertions(+), 145 deletions(-) create mode 100644 crates/application/src/users/deps.rs diff --git a/crates/application/src/users/deps.rs b/crates/application/src/users/deps.rs new file mode 100644 index 0000000..ade9cd0 --- /dev/null +++ b/crates/application/src/users/deps.rs @@ -0,0 +1,18 @@ +use std::sync::Arc; + +use domain::ports::{ + DiaryRepository, EventPublisher, ObjectStorage, SocialQueryPort, StatsRepository, + UserRepository, +}; + +pub struct GetProfileDeps { + pub stats: Arc, + pub diary: Arc, + pub social_query: Arc, +} + +pub struct UpdateProfileDeps { + pub user: Arc, + pub object_storage: Arc, + pub event_publisher: Arc, +} diff --git a/crates/application/src/users/get_current_profile.rs b/crates/application/src/users/get_current_profile.rs index df37ad3..fb02270 100644 --- a/crates/application/src/users/get_current_profile.rs +++ b/crates/application/src/users/get_current_profile.rs @@ -1,6 +1,8 @@ -use domain::errors::DomainError; +use std::sync::Arc; -use crate::{context::AppContext, users::queries::GetCurrentProfileQuery}; +use domain::{errors::DomainError, ports::UserRepository}; + +use crate::users::queries::GetCurrentProfileQuery; pub struct ProfileFieldData { pub name: String, @@ -19,18 +21,16 @@ pub struct CurrentProfileData { } pub async fn execute( - ctx: &AppContext, + user: Arc, query: GetCurrentProfileQuery, ) -> Result { let user_id = domain::value_objects::UserId::from_uuid(query.user_id); - let user = ctx - .repos - .user + let found = user .find_by_id(&user_id) .await? .ok_or_else(|| DomainError::NotFound("User not found".into()))?; - let fields = user + let fields = found .profile_fields() .iter() .map(|f| ProfileFieldData { @@ -40,14 +40,14 @@ pub async fn execute( .collect(); Ok(CurrentProfileData { - username: user.username().value().to_string(), - display_name: user.display_name().map(|s| s.to_string()), - bio: user.bio().map(|s| s.to_string()), - avatar_path: user.avatar_path().map(|s| s.to_string()), - banner_path: user.banner_path().map(|s| s.to_string()), - also_known_as: user.also_known_as().map(|s| s.to_string()), + username: found.username().value().to_string(), + display_name: found.display_name().map(|s| s.to_string()), + bio: found.bio().map(|s| s.to_string()), + avatar_path: found.avatar_path().map(|s| s.to_string()), + banner_path: found.banner_path().map(|s| s.to_string()), + also_known_as: found.also_known_as().map(|s| s.to_string()), fields, - role: user.role().as_str().into(), + role: found.role().as_str().into(), }) } diff --git a/crates/application/src/users/get_profile.rs b/crates/application/src/users/get_profile.rs index 765b0ca..0962ca4 100644 --- a/crates/application/src/users/get_profile.rs +++ b/crates/application/src/users/get_profile.rs @@ -1,6 +1,6 @@ -use crate::{ - context::AppContext, - users::queries::{GetUserProfileQuery, ProfileView}, +use crate::users::{ + deps::GetProfileDeps, + queries::{GetUserProfileQuery, ProfileView}, }; use domain::{ errors::DomainError, @@ -30,14 +30,14 @@ pub struct UserProfileData { } pub async fn execute( - ctx: &AppContext, + deps: &GetProfileDeps, query: GetUserProfileQuery, ) -> Result { let user_id = UserId::from_uuid(query.user_id); - let stats = ctx.repos.stats.get_user_stats(&user_id).await?; + let stats = deps.stats.get_user_stats(&user_id).await?; let (following_count, followers_count, pending_followers) = - load_social_counts(ctx, query.user_id, query.is_own_profile).await; + load_social_counts(deps, query.user_id, query.is_own_profile).await; let base = |entries, history, trends| UserProfileData { stats, @@ -51,11 +51,11 @@ pub async fn execute( match query.view { ProfileView::History => { - let all_entries = ctx.repos.diary.get_user_history(&user_id).await?; + let all_entries = deps.diary.get_user_history(&user_id).await?; Ok(base(None, Some(all_entries), None)) } ProfileView::Trends => { - let trends = ctx.repos.stats.get_user_trends(&user_id).await?; + let trends = deps.stats.get_user_trends(&user_id).await?; Ok(base(None, None, Some(trends))) } ProfileView::Ratings | ProfileView::Recent => { @@ -67,25 +67,23 @@ pub async fn execute( query.offset, query.search.clone(), )?; - let entries = ctx.repos.diary.query_diary(&filter).await?; + let entries = deps.diary.query_diary(&filter).await?; Ok(base(Some(entries), None, None)) } } } async fn load_social_counts( - ctx: &AppContext, + deps: &GetProfileDeps, user_id: uuid::Uuid, is_own_profile: bool, ) -> (usize, usize, Vec) { - let following = ctx - .repos + let following = deps .social_query .count_following(user_id) .await .unwrap_or(0); - let followers = ctx - .repos + let followers = deps .social_query .count_accepted_followers(user_id) .await @@ -93,8 +91,7 @@ async fn load_social_counts( if !is_own_profile { return (following, followers, vec![]); } - let pending = ctx - .repos + let pending = deps .social_query .get_pending_followers(user_id) .await diff --git a/crates/application/src/users/get_settings.rs b/crates/application/src/users/get_settings.rs index a763447..95e4543 100644 --- a/crates/application/src/users/get_settings.rs +++ b/crates/application/src/users/get_settings.rs @@ -1,10 +1,13 @@ -use domain::{errors::DomainError, models::UserSettings, value_objects::UserId}; +use std::sync::Arc; -use crate::context::AppContext; +use domain::{errors::DomainError, models::UserSettings, ports::UserSettingsRepository, value_objects::UserId}; -pub async fn execute(ctx: &AppContext, user_id: uuid::Uuid) -> Result { +pub async fn execute( + user_settings: Arc, + user_id: uuid::Uuid, +) -> Result { let uid = UserId::from_uuid(user_id); - ctx.repos.user_settings.get(&uid).await + user_settings.get(&uid).await } #[cfg(test)] diff --git a/crates/application/src/users/get_users.rs b/crates/application/src/users/get_users.rs index e22993e..d61b3d2 100644 --- a/crates/application/src/users/get_users.rs +++ b/crates/application/src/users/get_users.rs @@ -1,5 +1,7 @@ -use crate::{context::AppContext, users::queries::GetUsersQuery}; -use domain::{errors::DomainError, models::UserSummary, ports::RemoteActorInfo}; +use std::sync::Arc; + +use crate::users::queries::GetUsersQuery; +use domain::{errors::DomainError, models::UserSummary, ports::{RemoteActorInfo, SocialQueryPort, UserRepository}}; pub struct UsersListData { pub users: Vec, @@ -7,12 +9,13 @@ pub struct UsersListData { } pub async fn execute( - ctx: &AppContext, + user: Arc, + social_query: Arc, _query: GetUsersQuery, ) -> Result { let (users_result, actors_result) = tokio::join!( - ctx.repos.user.list_with_stats(), - ctx.repos.social_query.list_all_followed_remote_actors() + user.list_with_stats(), + social_query.list_all_followed_remote_actors() ); Ok(UsersListData { diff --git a/crates/application/src/users/mod.rs b/crates/application/src/users/mod.rs index 6a79ea4..7a0c3a7 100644 --- a/crates/application/src/users/mod.rs +++ b/crates/application/src/users/mod.rs @@ -1,4 +1,5 @@ pub mod commands; +pub mod deps; pub mod get_current_profile; pub mod get_profile; pub mod get_settings; diff --git a/crates/application/src/users/tests/get_current_profile.rs b/crates/application/src/users/tests/get_current_profile.rs index 59514d0..91b2c22 100644 --- a/crates/application/src/users/tests/get_current_profile.rs +++ b/crates/application/src/users/tests/get_current_profile.rs @@ -15,9 +15,9 @@ use crate::{ #[tokio::test] async fn returns_profile_for_existing_user() { let users = InMemoryUserRepository::new(); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + let user_repo = b.user_repo.clone(); + let ctx = b.build(); register::execute( &ctx, @@ -38,7 +38,7 @@ async fn returns_profile_for_existing_user() { .unwrap(); let profile = get_current_profile::execute( - &ctx, + user_repo, GetCurrentProfileQuery { user_id: user.id().value(), }, @@ -51,10 +51,11 @@ async fn returns_profile_for_existing_user() { #[tokio::test] async fn fails_for_nonexistent_user() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let user_repo = b.user_repo.clone(); let result = get_current_profile::execute( - &ctx, + user_repo, GetCurrentProfileQuery { user_id: Uuid::new_v4(), }, @@ -89,12 +90,11 @@ async fn returns_profile_with_avatar_banner_and_fields() { ); users.store.lock().unwrap().insert(uid.value(), user); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + let user_repo = b.user_repo.clone(); let profile = get_current_profile::execute( - &ctx, + user_repo, GetCurrentProfileQuery { user_id: uid.value(), }, diff --git a/crates/application/src/users/tests/get_profile.rs b/crates/application/src/users/tests/get_profile.rs index 5df6052..4d5416e 100644 --- a/crates/application/src/users/tests/get_profile.rs +++ b/crates/application/src/users/tests/get_profile.rs @@ -4,12 +4,28 @@ use domain::value_objects::Email; use crate::auth::commands::RegisterCommand; use crate::auth::register; use crate::test_helpers::TestContextBuilder; +use crate::users::deps::GetProfileDeps; use crate::users::get_profile; use crate::users::queries::{GetUserProfileQuery, ProfileView}; +fn default_deps() -> GetProfileDeps { + let b = TestContextBuilder::new(); + GetProfileDeps { + stats: b.stats_repo.clone(), + diary: b.diary_repo.clone(), + social_query: b.social_query.clone(), + } +} + #[tokio::test] async fn returns_profile_with_empty_stats() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let deps = GetProfileDeps { + stats: b.stats_repo.clone(), + diary: b.diary_repo.clone(), + social_query: b.social_query.clone(), + }; + let ctx = b.build(); register::execute( &ctx, @@ -28,7 +44,7 @@ async fn returns_profile_with_empty_stats() { let uid = user.id().value(); let result = get_profile::execute( - &ctx, + &deps, GetUserProfileQuery { user_id: uid, view: ProfileView::Recent, @@ -47,7 +63,13 @@ async fn returns_profile_with_empty_stats() { #[tokio::test] async fn returns_history_view() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let deps = GetProfileDeps { + stats: b.stats_repo.clone(), + diary: b.diary_repo.clone(), + social_query: b.social_query.clone(), + }; + let ctx = b.build(); register::execute( &ctx, @@ -66,7 +88,7 @@ async fn returns_history_view() { let uid = user.id().value(); let result = get_profile::execute( - &ctx, + &deps, GetUserProfileQuery { user_id: uid, view: ProfileView::History, @@ -87,7 +109,13 @@ async fn returns_history_view() { #[tokio::test] async fn returns_trends_view() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let deps = GetProfileDeps { + stats: b.stats_repo.clone(), + diary: b.diary_repo.clone(), + social_query: b.social_query.clone(), + }; + let ctx = b.build(); register::execute( &ctx, @@ -106,7 +134,7 @@ async fn returns_trends_view() { let uid = user.id().value(); let result = get_profile::execute( - &ctx, + &deps, GetUserProfileQuery { user_id: uid, view: ProfileView::Trends, @@ -127,7 +155,13 @@ async fn returns_trends_view() { #[tokio::test] async fn returns_ratings_view() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let deps = GetProfileDeps { + stats: b.stats_repo.clone(), + diary: b.diary_repo.clone(), + social_query: b.social_query.clone(), + }; + let ctx = b.build(); register::execute( &ctx, @@ -146,7 +180,7 @@ async fn returns_ratings_view() { let uid = user.id().value(); let result = get_profile::execute( - &ctx, + &deps, GetUserProfileQuery { user_id: uid, view: ProfileView::Ratings, @@ -165,7 +199,13 @@ async fn returns_ratings_view() { #[tokio::test] async fn returns_recent_with_search() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let deps = GetProfileDeps { + stats: b.stats_repo.clone(), + diary: b.diary_repo.clone(), + social_query: b.social_query.clone(), + }; + let ctx = b.build(); register::execute( &ctx, @@ -184,7 +224,7 @@ async fn returns_recent_with_search() { let uid = user.id().value(); let result = get_profile::execute( - &ctx, + &deps, GetUserProfileQuery { user_id: uid, view: ProfileView::Recent, @@ -203,7 +243,13 @@ async fn returns_recent_with_search() { #[tokio::test] async fn non_own_profile_skips_pending_followers() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let deps = GetProfileDeps { + stats: b.stats_repo.clone(), + diary: b.diary_repo.clone(), + social_query: b.social_query.clone(), + }; + let ctx = b.build(); register::execute( &ctx, @@ -222,7 +268,7 @@ async fn non_own_profile_skips_pending_followers() { let uid = user.id().value(); let result = get_profile::execute( - &ctx, + &deps, GetUserProfileQuery { user_id: uid, view: ProfileView::Recent, diff --git a/crates/application/src/users/tests/get_settings.rs b/crates/application/src/users/tests/get_settings.rs index 6299507..6774d4f 100644 --- a/crates/application/src/users/tests/get_settings.rs +++ b/crates/application/src/users/tests/get_settings.rs @@ -4,9 +4,10 @@ use crate::{test_helpers::TestContextBuilder, users::get_settings}; #[tokio::test] async fn returns_default_settings() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let user_settings = b.user_settings_repo.clone(); - let settings = get_settings::execute(&ctx, Uuid::nil()).await.unwrap(); + let settings = get_settings::execute(user_settings, Uuid::nil()).await.unwrap(); assert!(!settings.federate_goals()); } diff --git a/crates/application/src/users/tests/get_users.rs b/crates/application/src/users/tests/get_users.rs index fca482f..8fcc74e 100644 --- a/crates/application/src/users/tests/get_users.rs +++ b/crates/application/src/users/tests/get_users.rs @@ -4,9 +4,11 @@ use crate::users::queries::GetUsersQuery; #[tokio::test] async fn returns_empty_when_no_users() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let user = b.user_repo.clone(); + let social_query = b.social_query.clone(); - let result = get_users::execute(&ctx, GetUsersQuery).await.unwrap(); + let result = get_users::execute(user, social_query, GetUsersQuery).await.unwrap(); assert!(result.users.is_empty()); assert!(result.remote_actors.is_empty()); diff --git a/crates/application/src/users/tests/update_profile.rs b/crates/application/src/users/tests/update_profile.rs index ccce87d..26cfd3f 100644 --- a/crates/application/src/users/tests/update_profile.rs +++ b/crates/application/src/users/tests/update_profile.rs @@ -9,7 +9,7 @@ use uuid::Uuid; use crate::{ auth::{commands::RegisterCommand, register}, test_helpers::TestContextBuilder, - users::{commands::UpdateProfileCommand, update_profile}, + users::{commands::UpdateProfileCommand, deps::UpdateProfileDeps, update_profile}, }; async fn register_user( @@ -40,15 +40,20 @@ async fn register_user( async fn updates_display_name() { let users = InMemoryUserRepository::new(); let events = NoopEventPublisher::new(); - let ctx = TestContextBuilder::new() + let b = TestContextBuilder::new() .with_users(Arc::clone(&users) as _) - .with_event_publisher(Arc::clone(&events) as _) - .build(); + .with_event_publisher(Arc::clone(&events) as _); + let deps = UpdateProfileDeps { + user: b.user_repo.clone(), + object_storage: b.object_storage.clone(), + event_publisher: b.event_publisher.clone(), + }; + let ctx = b.build(); let uid = register_user(&ctx, &users).await; update_profile::execute( - &ctx, + &deps, UpdateProfileCommand { user_id: uid, display_name: Some("Alice W.".into()), @@ -74,14 +79,18 @@ async fn updates_display_name() { #[tokio::test] async fn rejects_invalid_avatar_content_type() { let users = InMemoryUserRepository::new(); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + let deps = UpdateProfileDeps { + user: b.user_repo.clone(), + object_storage: b.object_storage.clone(), + event_publisher: b.event_publisher.clone(), + }; + let ctx = b.build(); let uid = register_user(&ctx, &users).await; let result = update_profile::execute( - &ctx, + &deps, UpdateProfileCommand { user_id: uid, display_name: None, @@ -102,15 +111,20 @@ async fn rejects_invalid_avatar_content_type() { async fn uploads_avatar() { let users = InMemoryUserRepository::new(); let events = NoopEventPublisher::new(); - let ctx = TestContextBuilder::new() + let b = TestContextBuilder::new() .with_users(Arc::clone(&users) as _) - .with_event_publisher(Arc::clone(&events) as _) - .build(); + .with_event_publisher(Arc::clone(&events) as _); + let deps = UpdateProfileDeps { + user: b.user_repo.clone(), + object_storage: b.object_storage.clone(), + event_publisher: b.event_publisher.clone(), + }; + let ctx = b.build(); let uid = register_user(&ctx, &users).await; update_profile::execute( - &ctx, + &deps, UpdateProfileCommand { user_id: uid, display_name: None, @@ -142,15 +156,20 @@ async fn uploads_avatar() { async fn uploads_banner() { let users = InMemoryUserRepository::new(); let events = NoopEventPublisher::new(); - let ctx = TestContextBuilder::new() + let b = TestContextBuilder::new() .with_users(Arc::clone(&users) as _) - .with_event_publisher(Arc::clone(&events) as _) - .build(); + .with_event_publisher(Arc::clone(&events) as _); + let deps = UpdateProfileDeps { + user: b.user_repo.clone(), + object_storage: b.object_storage.clone(), + event_publisher: b.event_publisher.clone(), + }; + let ctx = b.build(); let uid = register_user(&ctx, &users).await; update_profile::execute( - &ctx, + &deps, UpdateProfileCommand { user_id: uid, display_name: None, @@ -180,10 +199,16 @@ async fn uploads_banner() { #[tokio::test] async fn fails_for_nonexistent_user() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let deps = UpdateProfileDeps { + user: b.user_repo.clone(), + object_storage: b.object_storage.clone(), + event_publisher: b.event_publisher.clone(), + }; + let _ctx = b.build(); let result = update_profile::execute( - &ctx, + &deps, UpdateProfileCommand { user_id: Uuid::new_v4(), display_name: Some("Ghost".into()), @@ -203,14 +228,18 @@ async fn fails_for_nonexistent_user() { #[tokio::test] async fn rejects_invalid_banner_content_type() { let users = InMemoryUserRepository::new(); - let ctx = TestContextBuilder::new() - .with_users(Arc::clone(&users) as _) - .build(); + let b = TestContextBuilder::new().with_users(Arc::clone(&users) as _); + let deps = UpdateProfileDeps { + user: b.user_repo.clone(), + object_storage: b.object_storage.clone(), + event_publisher: b.event_publisher.clone(), + }; + let ctx = b.build(); let uid = register_user(&ctx, &users).await; let result = update_profile::execute( - &ctx, + &deps, UpdateProfileCommand { user_id: uid, display_name: None, @@ -231,15 +260,20 @@ async fn rejects_invalid_banner_content_type() { async fn text_only_update_emits_user_updated_no_image_stored() { let users = InMemoryUserRepository::new(); let events = NoopEventPublisher::new(); - let ctx = TestContextBuilder::new() + let b = TestContextBuilder::new() .with_users(Arc::clone(&users) as _) - .with_event_publisher(Arc::clone(&events) as _) - .build(); + .with_event_publisher(Arc::clone(&events) as _); + let deps = UpdateProfileDeps { + user: b.user_repo.clone(), + object_storage: b.object_storage.clone(), + event_publisher: b.event_publisher.clone(), + }; + let ctx = b.build(); let uid = register_user(&ctx, &users).await; update_profile::execute( - &ctx, + &deps, UpdateProfileCommand { user_id: uid, display_name: Some("Alice Updated".into()), diff --git a/crates/application/src/users/tests/update_profile_fields.rs b/crates/application/src/users/tests/update_profile_fields.rs index 59e9988..7c79822 100644 --- a/crates/application/src/users/tests/update_profile_fields.rs +++ b/crates/application/src/users/tests/update_profile_fields.rs @@ -14,13 +14,15 @@ use crate::{ async fn saves_profile_fields() { let fields_repo = InMemoryProfileFieldsRepo::new(); let events = NoopEventPublisher::new(); - let ctx = TestContextBuilder::new() + let b = TestContextBuilder::new() .with_profile_fields(Arc::clone(&fields_repo) as _) - .with_event_publisher(Arc::clone(&events) as _) - .build(); + .with_event_publisher(Arc::clone(&events) as _); + let profile_fields = b.profile_fields_repo.clone(); + let event_publisher = b.event_publisher.clone(); update_profile_fields::execute( - &ctx, + profile_fields, + event_publisher, UpdateProfileFieldsCommand { user_id: Uuid::nil(), fields: vec![ @@ -48,7 +50,9 @@ async fn saves_profile_fields() { #[tokio::test] async fn rejects_more_than_four_fields() { - let ctx = TestContextBuilder::new().build(); + let b = TestContextBuilder::new(); + let profile_fields = b.profile_fields_repo.clone(); + let event_publisher = b.event_publisher.clone(); let fields: Vec = (0..5) .map(|i| ProfileField { @@ -58,7 +62,8 @@ async fn rejects_more_than_four_fields() { .collect(); let result = update_profile_fields::execute( - &ctx, + profile_fields, + event_publisher, UpdateProfileFieldsCommand { user_id: Uuid::nil(), fields, diff --git a/crates/application/src/users/tests/update_settings.rs b/crates/application/src/users/tests/update_settings.rs index fa1d820..6ca753e 100644 --- a/crates/application/src/users/tests/update_settings.rs +++ b/crates/application/src/users/tests/update_settings.rs @@ -11,14 +11,14 @@ use crate::{ #[tokio::test] async fn updates_federate_goals() { let settings_repo = InMemoryUserSettingsRepository::new(); - let ctx = TestContextBuilder::new() - .with_user_settings(Arc::clone(&settings_repo) as _) - .build(); + let b = TestContextBuilder::new() + .with_user_settings(Arc::clone(&settings_repo) as _); + let user_settings = b.user_settings_repo.clone(); let uid = Uuid::nil(); crate::users::update_settings::execute( - &ctx, + user_settings.clone(), UpdateUserSettingsCommand { user_id: uid, federate_goals: true, @@ -27,6 +27,6 @@ async fn updates_federate_goals() { .await .unwrap(); - let settings = get_settings::execute(&ctx, uid).await.unwrap(); + let settings = get_settings::execute(user_settings, uid).await.unwrap(); assert!(settings.federate_goals()); } diff --git a/crates/application/src/users/update_profile.rs b/crates/application/src/users/update_profile.rs index 11e31d4..8b2da82 100644 --- a/crates/application/src/users/update_profile.rs +++ b/crates/application/src/users/update_profile.rs @@ -1,12 +1,11 @@ use domain::{errors::DomainError, events::DomainEvent, value_objects::UserId}; -use crate::{context::AppContext, users::commands::UpdateProfileCommand}; +use crate::users::{commands::UpdateProfileCommand, deps::UpdateProfileDeps}; -pub async fn execute(ctx: &AppContext, cmd: UpdateProfileCommand) -> Result<(), DomainError> { +pub async fn execute(deps: &UpdateProfileDeps, cmd: UpdateProfileCommand) -> Result<(), DomainError> { let user_id = UserId::from_uuid(cmd.user_id); - let user = ctx - .repos + let user = deps .user .find_by_id(&user_id) .await? @@ -21,12 +20,11 @@ pub async fn execute(ctx: &AppContext, cmd: UpdateProfileCommand) -> Result<(), )); } if let Some(old_path) = user.avatar_path() { - let _ = ctx.services.object_storage.delete(old_path).await; + let _ = deps.object_storage.delete(old_path).await; } let key = format!("avatars/{}", user_id.value()); - let stored = ctx.services.object_storage.store(&key, &bytes).await?; - if let Err(e) = ctx - .services + let stored = deps.object_storage.store(&key, &bytes).await?; + if let Err(e) = deps .event_publisher .publish(&DomainEvent::ImageStored { key: stored.clone(), @@ -49,12 +47,11 @@ pub async fn execute(ctx: &AppContext, cmd: UpdateProfileCommand) -> Result<(), )); } if let Some(old_path) = user.banner_path() { - let _ = ctx.services.object_storage.delete(old_path).await; + let _ = deps.object_storage.delete(old_path).await; } let key = format!("banners/{}", user_id.value()); - let stored = ctx.services.object_storage.store(&key, &bytes).await?; - if let Err(e) = ctx - .services + let stored = deps.object_storage.store(&key, &bytes).await?; + if let Err(e) = deps .event_publisher .publish(&DomainEvent::ImageStored { key: stored.clone(), @@ -68,8 +65,7 @@ pub async fn execute(ctx: &AppContext, cmd: UpdateProfileCommand) -> Result<(), user.banner_path().map(|s| s.to_string()) }; - ctx.repos - .user + deps.user .update_profile( &user_id, &domain::models::UserProfile { @@ -83,8 +79,7 @@ pub async fn execute(ctx: &AppContext, cmd: UpdateProfileCommand) -> Result<(), ) .await?; - ctx.services - .event_publisher + deps.event_publisher .publish(&DomainEvent::UserUpdated { user_id }) .await?; diff --git a/crates/application/src/users/update_profile_fields.rs b/crates/application/src/users/update_profile_fields.rs index 8714f0d..2139a24 100644 --- a/crates/application/src/users/update_profile_fields.rs +++ b/crates/application/src/users/update_profile_fields.rs @@ -1,18 +1,20 @@ +use std::sync::Arc; + use domain::{ - errors::DomainError, events::DomainEvent, models::UserProfile, value_objects::UserId, + errors::DomainError, events::DomainEvent, models::UserProfile, ports::{EventPublisher, UserProfileFieldsRepository}, value_objects::UserId, }; -use crate::{context::AppContext, users::commands::UpdateProfileFieldsCommand}; +use crate::users::commands::UpdateProfileFieldsCommand; -pub async fn execute(ctx: &AppContext, cmd: UpdateProfileFieldsCommand) -> Result<(), DomainError> { +pub async fn execute( + profile_fields: Arc, + event_publisher: Arc, + cmd: UpdateProfileFieldsCommand, +) -> Result<(), DomainError> { UserProfile::validate_custom_fields(&cmd.fields)?; let user_id = UserId::from_uuid(cmd.user_id); - ctx.repos - .profile_fields - .set_fields(&user_id, cmd.fields) - .await?; - ctx.services - .event_publisher + profile_fields.set_fields(&user_id, cmd.fields).await?; + event_publisher .publish(&DomainEvent::UserUpdated { user_id }) .await?; Ok(()) diff --git a/crates/application/src/users/update_settings.rs b/crates/application/src/users/update_settings.rs index 57f4042..fa11aaf 100644 --- a/crates/application/src/users/update_settings.rs +++ b/crates/application/src/users/update_settings.rs @@ -1,17 +1,20 @@ -use domain::{errors::DomainError, value_objects::UserId}; +use std::sync::Arc; -use crate::context::AppContext; +use domain::{errors::DomainError, ports::UserSettingsRepository, value_objects::UserId}; pub struct UpdateUserSettingsCommand { pub user_id: uuid::Uuid, pub federate_goals: bool, } -pub async fn execute(ctx: &AppContext, cmd: UpdateUserSettingsCommand) -> Result<(), DomainError> { +pub async fn execute( + user_settings: Arc, + cmd: UpdateUserSettingsCommand, +) -> Result<(), DomainError> { let uid = UserId::from_uuid(cmd.user_id); - let mut settings = ctx.repos.user_settings.get(&uid).await?; + let mut settings = user_settings.get(&uid).await?; settings.set_federate_goals(cmd.federate_goals); - ctx.repos.user_settings.save(&settings).await + user_settings.save(&settings).await } #[cfg(test)] diff --git a/crates/presentation/src/handlers/goals.rs b/crates/presentation/src/handlers/goals.rs index 8b0a9cd..ff9aba4 100644 --- a/crates/presentation/src/handlers/goals.rs +++ b/crates/presentation/src/handlers/goals.rs @@ -169,8 +169,11 @@ pub async fn get_settings( State(state): State, user: AuthenticatedUser, ) -> Result, ApiError> { - let settings = - application::users::get_settings::execute(&state.app_ctx, user.0.value()).await?; + let settings = application::users::get_settings::execute( + state.app_ctx.repos.user_settings.clone(), + user.0.value(), + ) + .await?; Ok(Json(UserSettingsDto { federate_goals: settings.federate_goals(), })) @@ -191,7 +194,7 @@ pub async fn update_settings( Json(req): Json, ) -> Result { application::users::update_settings::execute( - &state.app_ctx, + state.app_ctx.repos.user_settings.clone(), application::users::update_settings::UpdateUserSettingsCommand { user_id: user.0.value(), federate_goals: req.federate_goals, diff --git a/crates/presentation/src/handlers/users.rs b/crates/presentation/src/handlers/users.rs index e5b0a0f..78f837c 100644 --- a/crates/presentation/src/handlers/users.rs +++ b/crates/presentation/src/handlers/users.rs @@ -9,6 +9,7 @@ use axum::{ use uuid::Uuid; use application::users::{ + deps::{GetProfileDeps, UpdateProfileDeps}, get_profile as get_user_profile_uc, get_users, queries::{GetUserProfileQuery, GetUsersQuery}, update_profile, update_profile_fields, @@ -52,7 +53,7 @@ pub async fn get_profile( AuthenticatedUser(user_id): AuthenticatedUser, ) -> Result, ApiError> { let profile = application::users::get_current_profile::execute( - &state.app_ctx, + state.app_ctx.repos.user.clone(), application::users::queries::GetCurrentProfileQuery { user_id: user_id.value(), }, @@ -156,7 +157,12 @@ pub async fn update_profile_handler( also_known_as, }; - match update_profile::execute(&state.app_ctx, cmd).await { + let deps = UpdateProfileDeps { + user: state.app_ctx.repos.user.clone(), + object_storage: state.app_ctx.services.object_storage.clone(), + event_publisher: state.app_ctx.services.event_publisher.clone(), + }; + match update_profile::execute(&deps, cmd).await { Ok(()) => StatusCode::NO_CONTENT.into_response(), Err(e) => crate::errors::domain_error_response(e), } @@ -197,7 +203,13 @@ pub async fn update_profile_fields_handler( fields, }; - match update_profile_fields::execute(&state.app_ctx, cmd).await { + match update_profile_fields::execute( + state.app_ctx.repos.profile_fields.clone(), + state.app_ctx.services.event_publisher.clone(), + cmd, + ) + .await + { Ok(()) => StatusCode::NO_CONTENT.into_response(), Err(e) => crate::errors::domain_error_response(e), } @@ -208,7 +220,12 @@ pub async fn update_profile_fields_handler( responses((status = 200, body = UsersResponse)), )] pub async fn list_users(State(state): State) -> Result, ApiError> { - let result = get_users::execute(&state.app_ctx, GetUsersQuery).await?; + let result = get_users::execute( + state.app_ctx.repos.user.clone(), + state.app_ctx.repos.social_query.clone(), + GetUsersQuery, + ) + .await?; Ok(Json(UsersResponse { users: result .users @@ -262,8 +279,13 @@ pub async fn get_user_profile( } }; + let get_profile_deps = GetProfileDeps { + stats: state.app_ctx.repos.stats.clone(), + diary: state.app_ctx.repos.diary.clone(), + social_query: state.app_ctx.repos.social_query.clone(), + }; let profile = match get_user_profile_uc::execute( - &state.app_ctx, + &get_profile_deps, GetUserProfileQuery { user_id, view: profile_view, @@ -378,7 +400,8 @@ pub async fn get_users_list( ctx.canonical_url = format!("{}/users", state.app_ctx.config.base_url); match application::users::get_users::execute( - &state.app_ctx, + state.app_ctx.repos.user.clone(), + state.app_ctx.repos.social_query.clone(), application::users::queries::GetUsersQuery, ) .await @@ -517,7 +540,12 @@ pub async fn get_user_profile_html( is_own_profile, }; - match application::users::get_profile::execute(&state.app_ctx, query).await { + let html_profile_deps = GetProfileDeps { + stats: state.app_ctx.repos.stats.clone(), + diary: state.app_ctx.repos.diary.clone(), + social_query: state.app_ctx.repos.social_query.clone(), + }; + match application::users::get_profile::execute(&html_profile_deps, query).await { Ok(profile) => { let (offset, has_more, limit) = profile .entries @@ -805,7 +833,12 @@ pub async fn post_profile_settings( banner_content_type, also_known_as, }; - let _ = update_profile::execute(&state.app_ctx, cmd).await; + let update_deps = UpdateProfileDeps { + user: state.app_ctx.repos.user.clone(), + object_storage: state.app_ctx.services.object_storage.clone(), + event_publisher: state.app_ctx.services.event_publisher.clone(), + }; + let _ = update_profile::execute(&update_deps, cmd).await; let fields: Vec = (0..4) .filter_map(|i| { @@ -822,7 +855,12 @@ pub async fn post_profile_settings( user_id: user_id.value(), fields, }; - let _ = update_profile_fields::execute(&state.app_ctx, fields_cmd).await; + let _ = update_profile_fields::execute( + state.app_ctx.repos.profile_fields.clone(), + state.app_ctx.services.event_publisher.clone(), + fields_cmd, + ) + .await; axum::response::Redirect::to("/settings/profile?saved=1").into_response() }