rex_db/models/
activities.rs

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