use std::collections::BTreeMap; use std::sync::Arc; use domain::cycle::CycleDay; use domain::dimension::ComposedEntry; use domain::entry::{Date, DateRange, DayMood}; use domain::ports::{ CycleStartQueryPort, EntryDimensionPort, MoodEntryQueryPort, UserPreferencesQueryPort, UserQueryPort, }; use domain::user::UserId; use crate::cycle::use_cases::read_cycle::calendar_of; use crate::day::{date_of, timezone_for}; use crate::entry::composition::EntryComposer; use crate::errors::ApplicationError; use crate::user::preferences::preferences_of; pub struct CalendarDay { pub date: Date, pub entries: Vec, pub day_mood: Option, pub cycle_day: Option, } pub struct Deps { pub query: Arc, pub dimensions: Vec>, pub cycles: Arc, pub preferences: Arc, pub users: Arc, } #[tracing::instrument(skip(deps))] pub async fn execute( user_id: UserId, range: DateRange, deps: &Deps, ) -> Result, ApplicationError> { let entries = deps.query.find_by_date_range(&user_id, &range).await?; let entries = EntryComposer::new(deps.dimensions.clone()) .compose(entries) .await?; let timezone = timezone_for(&user_id, &deps.users).await?; let mut by_date: BTreeMap> = BTreeMap::new(); for entry in entries { let date = date_of(&entry.entry, &timezone); by_date.entry(date).or_default().push(entry); } let cycles = tracked_cycle(&user_id, deps).await?; let days = by_date .into_iter() .map(|(date, entries)| { let day_mood = day_mood_of(&entries); let cycle_day = cycles .as_ref() .and_then(|calendar| calendar.position_on(&date)) .map(|position| position.day()); CalendarDay { date, entries, day_mood, cycle_day, } }) .collect(); Ok(days) } async fn tracked_cycle( user_id: &domain::user::UserId, deps: &Deps, ) -> Result, ApplicationError> { let preferences = preferences_of(user_id, &deps.preferences).await?; if !preferences.tracks_cycle() { return Ok(None); } Ok(Some(calendar_of(user_id, &deps.cycles).await?)) } fn day_mood_of(entries: &[ComposedEntry]) -> Option { let moods: Vec<_> = entries.iter().map(|entry| entry.entry.mood()).collect(); DayMood::of(&moods) }