backend/model/dto/
guided_tour_impl.rs

1//! Contains the implementation of [`GuidedTourDto`].
2
3use chrono::{Duration, NaiveDateTime, Utc};
4use uuid::Uuid;
5
6use crate::model::entity::{GuidedTourProgress, GuidedTourState, NewGuidedTourProgress};
7
8use super::GuidedTourDto;
9
10/// How many days not to show the tour based on `pause_count`
11/// Fibonacci with max of 30 days
12fn backoff_days(pause_count: i32) -> i64 {
13    if pause_count <= 0 {
14        return 0;
15    }
16
17    let n = pause_count;
18
19    let mut a: i64 = 0;
20    let mut b: i64 = 1;
21
22    for _ in 0..n {
23        let temp = a;
24        a = b;
25        b += temp;
26    }
27
28    a.min(30)
29}
30
31/// compute if the tour is paused based on `paused_at` and `pause_count`
32fn compute_paused(paused_at: Option<NaiveDateTime>, pause_count: i32, now: NaiveDateTime) -> bool {
33    let Some(paused_at) = paused_at else {
34        return false;
35    };
36    let days = backoff_days(pause_count);
37    if days <= 0 {
38        return false;
39    }
40
41    now < (paused_at + Duration::days(days))
42}
43
44impl From<(Uuid, String)> for NewGuidedTourProgress {
45    fn from((user_id, step_key): (Uuid, String)) -> Self {
46        Self { user_id, step_key }
47    }
48}
49
50impl From<(Vec<GuidedTourProgress>, Option<GuidedTourState>)> for GuidedTourDto {
51    fn from(
52        (progress_rows, state_opt): (Vec<GuidedTourProgress>, Option<GuidedTourState>),
53    ) -> Self {
54        let completed_steps = progress_rows
55            .into_iter()
56            .map(|r| r.step_key)
57            .collect::<Vec<_>>();
58
59        let now = Utc::now().naive_utc();
60
61        let (paused_at, pause_count) =
62            state_opt.map_or((None, 0), |s| (s.paused_at, s.pause_count));
63
64        Self {
65            completed_steps,
66            paused: compute_paused(paused_at, pause_count, now),
67        }
68    }
69}