refactor(users): GetProfileDeps, UpdateProfileDeps, scoped Arc deps

This commit is contained in:
2026-06-11 22:47:17 +02:00
parent 7bf5c47f5b
commit 61980b0cfb
18 changed files with 296 additions and 145 deletions

View File

@@ -0,0 +1,18 @@
use std::sync::Arc;
use domain::ports::{
DiaryRepository, EventPublisher, ObjectStorage, SocialQueryPort, StatsRepository,
UserRepository,
};
pub struct GetProfileDeps {
pub stats: Arc<dyn StatsRepository>,
pub diary: Arc<dyn DiaryRepository>,
pub social_query: Arc<dyn SocialQueryPort>,
}
pub struct UpdateProfileDeps {
pub user: Arc<dyn UserRepository>,
pub object_storage: Arc<dyn ObjectStorage>,
pub event_publisher: Arc<dyn EventPublisher>,
}

View File

@@ -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<dyn UserRepository>,
query: GetCurrentProfileQuery,
) -> Result<CurrentProfileData, DomainError> {
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(),
})
}

View File

@@ -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<UserProfileData, DomainError> {
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<PendingFollowerView>) {
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

View File

@@ -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<UserSettings, DomainError> {
pub async fn execute(
user_settings: Arc<dyn UserSettingsRepository>,
user_id: uuid::Uuid,
) -> Result<UserSettings, DomainError> {
let uid = UserId::from_uuid(user_id);
ctx.repos.user_settings.get(&uid).await
user_settings.get(&uid).await
}
#[cfg(test)]

View File

@@ -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<UserSummary>,
@@ -7,12 +9,13 @@ pub struct UsersListData {
}
pub async fn execute(
ctx: &AppContext,
user: Arc<dyn UserRepository>,
social_query: Arc<dyn SocialQueryPort>,
_query: GetUsersQuery,
) -> Result<UsersListData, DomainError> {
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 {

View File

@@ -1,4 +1,5 @@
pub mod commands;
pub mod deps;
pub mod get_current_profile;
pub mod get_profile;
pub mod get_settings;

View File

@@ -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(),
},

View File

@@ -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,

View File

@@ -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());
}

View File

@@ -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());

View File

@@ -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()),

View File

@@ -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<ProfileField> = (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,

View File

@@ -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());
}

View File

@@ -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?;

View File

@@ -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<dyn UserProfileFieldsRepository>,
event_publisher: Arc<dyn EventPublisher>,
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(())

View File

@@ -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<dyn UserSettingsRepository>,
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)]

View File

@@ -169,8 +169,11 @@ pub async fn get_settings(
State(state): State<AppState>,
user: AuthenticatedUser,
) -> Result<Json<UserSettingsDto>, 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<UpdateUserSettingsRequest>,
) -> Result<StatusCode, ApiError> {
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,

View File

@@ -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<Json<ProfileResponse>, 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<AppState>) -> Result<Json<UsersResponse>, 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<domain::models::ProfileField> = (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()
}