backend/model/dto/
guided_tour_impl.rs1use chrono::{Duration, NaiveDateTime, Utc};
4use uuid::Uuid;
5
6use crate::model::entity::{GuidedTourProgress, GuidedTourState, NewGuidedTourProgress};
7
8use super::GuidedTourDto;
9
10fn 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
31fn 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}