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