use crate::users::{ deps::GetProfileDeps, queries::{GetUserProfileQuery, ProfileView}, }; use domain::{ errors::DomainError, models::{ DiaryEntry, DiaryFilter, SortDirection, UserStats, UserTrends, collections::{PageParams, Paginated}, }, ports::FeedSortBy, value_objects::UserId, }; pub struct PendingFollowerView { pub url: String, pub handle: String, pub display_name: Option, pub avatar_url: Option, } pub struct UserProfileData { pub stats: UserStats, pub entries: Option>, pub history: Option>, pub trends: Option, pub following_count: usize, pub followers_count: usize, pub pending_followers: Vec, } pub async fn execute( deps: &GetProfileDeps, query: GetUserProfileQuery, ) -> Result { let user_id = UserId::from_uuid(query.user_id); let stats = deps.stats.get_user_stats(&user_id).await?; let (following_count, followers_count, pending_followers) = load_social_counts(deps, query.user_id, query.is_own_profile).await; let base = |entries, history, trends| UserProfileData { stats, entries, history, trends, following_count, followers_count, pending_followers, }; match query.view { ProfileView::History => { let all_entries = deps.diary.get_user_history(&user_id).await?; Ok(base(None, Some(all_entries), None)) } ProfileView::Trends => { let trends = deps.stats.get_user_trends(&user_id).await?; Ok(base(None, None, Some(trends))) } ProfileView::Ratings | ProfileView::Recent => { let sort_direction = feed_sort_to_direction(query.sort_by); let filter = paged_user_filter( user_id, sort_direction, query.limit, query.offset, query.search.clone(), )?; let entries = deps.diary.query_diary(&filter).await?; Ok(base(Some(entries), None, None)) } } } async fn load_social_counts( deps: &GetProfileDeps, user_id: uuid::Uuid, is_own_profile: bool, ) -> (usize, usize, Vec) { let following = deps .social_query .count_following(user_id) .await .unwrap_or(0); let followers = deps .social_query .count_accepted_followers(user_id) .await .unwrap_or(0); if !is_own_profile { return (following, followers, vec![]); } let pending = deps .social_query .get_pending_followers(user_id) .await .unwrap_or_default() .into_iter() .map(|p| PendingFollowerView { url: p.url, handle: p.handle, display_name: p.display_name, avatar_url: p.avatar_url, }) .collect(); (following, followers, pending) } fn feed_sort_to_direction(sort_by: FeedSortBy) -> SortDirection { match sort_by { FeedSortBy::Date => SortDirection::Descending, FeedSortBy::DateAsc => SortDirection::Ascending, FeedSortBy::Rating => SortDirection::ByRatingDesc, FeedSortBy::RatingAsc => SortDirection::ByRatingAsc, } } fn paged_user_filter( user_id: UserId, sort_by: SortDirection, limit: Option, offset: Option, search: Option, ) -> Result { let page = PageParams::new(limit, offset)?; Ok(DiaryFilter { sort_by, page, movie_id: None, user_id: Some(user_id), search, }) } #[cfg(test)] #[path = "tests/get_profile.rs"] mod tests; #[cfg(test)] mod helper_tests { use super::*; #[test] fn feed_sort_to_direction_all_variants() { use domain::ports::FeedSortBy; assert!(matches!( feed_sort_to_direction(FeedSortBy::Date), SortDirection::Descending )); assert!(matches!( feed_sort_to_direction(FeedSortBy::DateAsc), SortDirection::Ascending )); assert!(matches!( feed_sort_to_direction(FeedSortBy::Rating), SortDirection::ByRatingDesc )); assert!(matches!( feed_sort_to_direction(FeedSortBy::RatingAsc), SortDirection::ByRatingAsc )); } #[test] fn paged_user_filter_builds_correctly() { let uid = UserId::from_uuid(uuid::Uuid::new_v4()); let filter = paged_user_filter( uid.clone(), SortDirection::Descending, Some(20), Some(5), Some("blade".into()), ) .unwrap(); assert_eq!(filter.user_id.unwrap().value(), uid.value()); assert_eq!(filter.search.as_deref(), Some("blade")); } }