Skip to main content

trailcache_core/cache/
refresh.rs

1//! Shared refresh orchestration for base data.
2//!
3//! Fetches all 10 base data sources in parallel, saves to cache,
4//! and returns results so callers can update in-memory state.
5
6use tracing::warn;
7
8use crate::api::ApiClient;
9use crate::cache::CacheManager;
10use crate::cache::offline::CacheProgress;
11use crate::models::{
12    Adult, AdvancementDashboard, Commissioner, Event, Key3Leaders,
13    OrgProfile, Parent, Patrol, UnitInfo, Youth,
14};
15
16/// Results from a base data refresh.
17pub struct RefreshResult {
18    pub youth: Option<Vec<Youth>>,
19    pub adults: Option<Vec<Adult>>,
20    pub events: Option<Vec<Event>>,
21    pub patrols: Option<Vec<Patrol>>,
22    pub unit_info: Option<UnitInfo>,
23    pub key3: Option<Key3Leaders>,
24    pub commissioners: Option<Vec<Commissioner>>,
25    pub org_profile: Option<OrgProfile>,
26    pub parents: Option<Vec<Parent>>,
27    pub advancement: Option<AdvancementDashboard>,
28    pub errors: Vec<String>,
29    pub successes: u32,
30}
31
32/// Fetch all 10 base data sources in parallel, save to cache, deduplicate adults.
33///
34/// Progress is reported via `on_progress` so each frontend can display it.
35/// Returns all fetched data so callers can update in-memory state without re-reading cache.
36pub async fn refresh_base_data(
37    api: &ApiClient,
38    cache: &CacheManager,
39    org_guid: &str,
40    user_id: i64,
41    on_progress: impl Fn(CacheProgress),
42) -> RefreshResult {
43    let total = 10u32;
44    let mut errors: Vec<String> = Vec::new();
45    let mut successes = 0u32;
46
47    on_progress(CacheProgress {
48        current: 0,
49        total,
50        description: "Refreshing data...".into(),
51    });
52
53    // Fetch all 10 sources in parallel
54    let (
55        youth_res, adults_res, events_res, patrols_res, unit_info_res,
56        key3_res, commissioners_res, org_profile_res, parents_res, advancement_res,
57    ) = tokio::join!(
58        api.fetch_youth(org_guid),
59        api.fetch_adults(org_guid),
60        api.fetch_events(user_id),
61        api.fetch_patrols(org_guid),
62        api.fetch_unit_pin(org_guid),
63        api.fetch_key3(org_guid),
64        api.fetch_commissioners(org_guid),
65        api.fetch_org_profile(org_guid),
66        api.fetch_parents(org_guid),
67        api.fetch_advancement_dashboard(org_guid),
68    );
69
70    // Process each result: save to cache, collect data and errors
71    macro_rules! process {
72        ($label:expr, $step:expr, $result:expr, $save:expr) => {{
73            on_progress(CacheProgress {
74                current: $step,
75                total,
76                description: format!("Processing {}...", $label),
77            });
78            match $result {
79                Ok(data) => {
80                    let _ = $save(&data);
81                    successes += 1;
82                    Some(data)
83                }
84                Err(e) => {
85                    let msg = format!("{}: {}", $label, e);
86                    warn!("{}", msg);
87                    errors.push(msg);
88                    None
89                }
90            }
91        }};
92    }
93
94    let youth = process!("scouts", 1, youth_res, |d: &Vec<Youth>| cache.save_youth(d));
95
96    // Deduplicate adults before saving
97    let adults = {
98        on_progress(CacheProgress {
99            current: 2,
100            total,
101            description: "Processing adults...".into(),
102        });
103        match adults_res {
104            Ok(data) => {
105                let deduped = Adult::deduplicate(data);
106                if let Err(e) = cache.save_adults(&deduped) {
107                    warn!("Failed to save adults to cache: {e}");
108                }
109                successes += 1;
110                Some(deduped)
111            }
112            Err(e) => {
113                let msg = format!("adults: {}", e);
114                warn!("{}", msg);
115                errors.push(msg);
116                None
117            }
118        }
119    };
120
121    let events = process!("events", 3, events_res, |d: &Vec<Event>| cache.save_events(d));
122    let patrols = process!("patrols", 4, patrols_res, |d: &Vec<Patrol>| cache.save_patrols(d));
123    let unit_info = process!("unit info", 5, unit_info_res, |d: &UnitInfo| cache.save_unit_info(d));
124    let key3 = process!("Key 3", 6, key3_res, |d: &Key3Leaders| cache.save_key3(d));
125    let commissioners = process!("commissioners", 7, commissioners_res, |d: &Vec<Commissioner>| cache.save_commissioners(d));
126    let org_profile = process!("org profile", 8, org_profile_res, |d: &OrgProfile| cache.save_org_profile(d));
127    let parents = process!("parents", 9, parents_res, |d: &Vec<Parent>| cache.save_parents(d));
128    let advancement = process!("advancement", 10, advancement_res, |d: &AdvancementDashboard| cache.save_advancement_dashboard(d));
129
130    on_progress(CacheProgress {
131        current: total,
132        total,
133        description: "Refresh complete".into(),
134    });
135
136    RefreshResult {
137        youth,
138        adults,
139        events,
140        patrols,
141        unit_info,
142        key3,
143        commissioners,
144        org_profile,
145        parents,
146        advancement,
147        errors,
148        successes,
149    }
150}