rex_db/models/
activities.rs1use 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}