Skip to main content

rex_db/models/
activities.rs

1use chrono::{Datelike, Days, Local, Months, NaiveDate, NaiveDateTime, NaiveTime};
2use diesel::prelude::*;
3use diesel::result::Error;
4use rex_shared::models::LAST_POSSIBLE_TIME;
5
6use crate::ConnCache;
7use crate::models::{ActivityNature, ActivityTx, FullActivityTx};
8use crate::schema::activities;
9
10#[derive(Clone, Queryable, Selectable, Insertable)]
11#[diesel(table_name = activities)]
12pub struct NewActivity {
13    date: NaiveDateTime,
14    activity_type: String,
15}
16
17#[derive(Queryable, Selectable, Insertable)]
18#[diesel(table_name = activities)]
19pub struct Activity {
20    pub id: i32,
21    date: NaiveDateTime,
22    pub activity_type: String,
23}
24
25pub struct ActivityWithTxs {
26    pub activity: Activity,
27    pub txs: Vec<FullActivityTx>,
28}
29
30impl NewActivity {
31    #[must_use]
32    pub fn new(activity_type: ActivityNature) -> Self {
33        let now = Local::now().naive_local();
34
35        Self {
36            date: now,
37            activity_type: activity_type.into(),
38        }
39    }
40
41    pub fn insert(self, db_conn: &mut impl ConnCache) -> Result<Activity, Error> {
42        use crate::schema::activities::dsl::activities;
43
44        diesel::insert_into(activities)
45            .values(self)
46            .returning(Activity::as_returning())
47            .get_result(db_conn.conn())
48    }
49}
50
51impl Activity {
52    #[must_use]
53    pub fn new(date: NaiveDateTime, activity_type: ActivityNature, id: i32) -> Self {
54        Self {
55            id,
56            date,
57            activity_type: activity_type.into(),
58        }
59    }
60
61    pub fn insert(self, db_conn: &mut impl ConnCache) -> Result<Self, Error> {
62        use crate::schema::activities::dsl::activities;
63
64        diesel::insert_into(activities)
65            .values(self)
66            .returning(Self::as_returning())
67            .get_result(db_conn.conn())
68    }
69
70    pub fn get_activities(
71        d: NaiveDate,
72        db_conn: &mut impl ConnCache,
73    ) -> Result<Vec<ActivityWithTxs>, Error> {
74        use crate::schema::activities::dsl as act;
75        use crate::schema::activity_txs::dsl as tx;
76
77        let start_date = NaiveDate::from_ymd_opt(d.year(), d.month(), 1).unwrap();
78        let end_date = start_date + Months::new(1) - Days::new(1);
79
80        let start_date = start_date.and_time(NaiveTime::MIN);
81        let end_date = end_date.and_time(LAST_POSSIBLE_TIME);
82
83        let results: Vec<(Activity, ActivityTx)> = act::activities
84            .inner_join(tx::activity_txs.on(tx::activity_num.eq(act::id)))
85            .filter(act::date.ge(start_date))
86            .filter(act::date.le(end_date))
87            .order((act::date.asc(), act::id.asc()))
88            .select((Activity::as_select(), ActivityTx::as_select()))
89            .load(db_conn.conn())?;
90
91        let mut grouped: Vec<ActivityWithTxs> = Vec::new();
92
93        let activity_txs: Vec<&ActivityTx> = results.iter().map(|(_, tx)| tx).collect();
94
95        let full_activity_txs = ActivityTx::convert_to_full_tx(activity_txs, db_conn)?;
96
97        for ((activity, _), tx) in results.into_iter().zip(full_activity_txs) {
98            if let Some(last) = grouped.last_mut()
99                && last.activity.id == activity.id
100            {
101                last.txs.push(tx);
102                continue;
103            }
104
105            grouped.push(ActivityWithTxs {
106                activity,
107                txs: vec![tx],
108            });
109        }
110
111        Ok(grouped)
112    }
113
114    #[must_use]
115    pub fn to_array(&self) -> Vec<String> {
116        let activity_type: ActivityNature = self.activity_type.as_str().into();
117        vec![
118            self.date.format("%a %d %I:%M %p").to_string(),
119            activity_type.to_string(),
120        ]
121    }
122}
123
124impl ActivityWithTxs {
125    #[must_use]
126    pub fn to_array(&self) -> Vec<Vec<String>> {
127        let first_tx = self.txs.first().unwrap();
128
129        let last_tx = self.txs.last().unwrap();
130
131        match self.activity.activity_type.as_str().into() {
132            ActivityNature::PositionSwap | ActivityNature::EditTx => {
133                assert!(
134                    (first_tx.id != last_tx.id),
135                    "Both activity tx id should not have matched"
136                );
137
138                let lower_id_tx = if first_tx.id < last_tx.id {
139                    first_tx
140                } else {
141                    last_tx
142                };
143
144                let higher_id_tx = if first_tx.id > last_tx.id {
145                    first_tx
146                } else {
147                    last_tx
148                };
149
150                let mut lower_id_tx_array = lower_id_tx.to_array();
151                let mut higher_id_tx_array = higher_id_tx.to_array();
152
153                match self.activity.activity_type.as_str().into() {
154                    ActivityNature::PositionSwap => {
155                        let higher_display_order = higher_id_tx
156                            .display_order
157                            .expect("Display order should not be none for this type of activity");
158                        let lower_display_order = lower_id_tx
159                            .display_order
160                            .expect("Display order should not be none for this type of activity");
161
162                        lower_id_tx_array
163                            .push(format!("{higher_display_order} → {lower_display_order}"));
164
165                        higher_id_tx_array
166                            .push(format!("{lower_display_order} → {higher_display_order}"));
167
168                        vec![lower_id_tx_array, higher_id_tx_array]
169                    }
170                    ActivityNature::EditTx => {
171                        lower_id_tx_array.push("New Tx".to_string());
172
173                        higher_id_tx_array.push("Old Tx".to_string());
174
175                        vec![lower_id_tx_array, higher_id_tx_array]
176                    }
177                    _ => unreachable!(),
178                }
179            }
180            _ => {
181                vec![first_tx.to_array()]
182            }
183        }
184    }
185}