1
pub mod user {
2
    use crate::db::DBError;
3
    use crate::error::ServerError;
4
    use crate::user::User;
5
    use finance::{
6
        account::{Account, AccountBuilder},
7
        tag::Tag,
8
    };
9
    use sqlx::types::Uuid;
10
    use std::collections::HashMap;
11
    use std::sync::{Arc, Mutex};
12

            
13
    impl User {
14
        pub async fn add_account(&self, script: &[u8]) -> Result<Account, ServerError> {
15
            let a = AccountBuilder::new().id(Uuid::new_v4()).build()?;
16

            
17
            let mut tags = self.get_account_tags(&a).await?;
18
            let tagdb: Arc<Mutex<HashMap<String, String>>> = Arc::new(Mutex::new(HashMap::new()));
19
            {
20
                let mut tagdb_lock = tagdb.lock().map_err(|err| {
21
                    log::error!("{}", t!("Mutex error: %{err}", err = err : {:?}));
22
                    ServerError::Lock
23
                })?;
24
                for t in &tags {
25
                    tagdb_lock.insert(t.tag_name.clone(), t.tag_value.clone());
26
                }
27
            }
28

            
29
            let account = if script.is_empty() {
30
                a
31
            } else {
32
                // TODO: Implement account hook similar to commodity
33
                a
34
            };
35

            
36
            // Update the tags with the new values from `tagdb`
37
            {
38
                let tagdb_lock = tagdb.lock().map_err(|err| {
39
                    log::error!("{}", t!("Mutex error: %{err}", err = err : {:?}));
40
                    ServerError::Lock
41
                })?;
42
                for t in &mut tags {
43
                    if let Some(value) = tagdb_lock.get(&t.tag_name) {
44
                        t.tag_value = value.to_owned();
45
                    }
46
                }
47
            }
48

            
49
            // Call `update_account_tags` to apply the updates to the database
50
            self.update_account_tags(&account, &tags).await?;
51

            
52
            Ok(account)
53
        }
54

            
55
117
        pub async fn create_account(
56
117
            &self,
57
117
            name: &str,
58
117
            parent: Option<Uuid>,
59
117
        ) -> Result<Account, ServerError> {
60
107
            let a = Account {
61
107
                id: Uuid::new_v4(),
62
107
                parent,
63
107
            };
64

            
65
107
            let mut conn = self.get_connection().await.map_err(|err| {
66
2
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
67
2
                ServerError::DB(err)
68
2
            })?;
69

            
70
105
            a.commit(&mut *conn).await?;
71
105
            let tags: Vec<Tag> = vec![Tag {
72
105
                id: Uuid::new_v4(),
73
105
                tag_name: "name".to_string(),
74
105
                tag_value: name.to_string(),
75
105
                description: None,
76
105
            }];
77
105
            self.update_account_tags(&a, &tags).await?;
78

            
79
105
            Ok(a)
80
107
        }
81

            
82
106
        pub async fn update_account_tags(
83
106
            &self,
84
106
            a: &Account,
85
106
            tags: &[Tag],
86
106
        ) -> Result<(), ServerError> {
87
106
            let tag_ids: Vec<Uuid> = tags.iter().map(|t| t.id).collect();
88
107
            let names: Vec<String> = tags.iter().map(|t| t.tag_name.clone()).collect();
89
107
            let values: Vec<String> = tags.iter().map(|t| t.tag_value.clone()).collect();
90

            
91
106
            let mut conn = self.get_connection().await.map_err(|err| {
92
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
93
                ServerError::DB(err)
94
            })?;
95

            
96
106
            let _ = sqlx::query_file!(
97
106
                "sql/update/accounts/tags.sql",
98
                &a.id,
99
                &tag_ids,
100
                &names,
101
                &values,
102
            )
103
106
            .fetch_one(&mut *conn)
104
106
            .await
105
106
            .map_err(|err| {
106
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
107
                ServerError::DB(DBError::Sqlx(err))
108
            })?;
109

            
110
106
            Ok(())
111
106
        }
112

            
113
8
        pub async fn set_account_tag(&self, a: &Account, t: &Tag) -> Result<(), ServerError> {
114
8
            if t.tag_name.is_empty() || t.tag_value.is_empty() {
115
                return Err(ServerError::Creation);
116
8
            }
117
8
            let mut conn = self.get_connection().await.map_err(|err| {
118
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
119
                ServerError::DB(err)
120
            })?;
121

            
122
8
            let _ = sqlx::query_file!(
123
8
                "sql/set/accounts/tag.sql",
124
                &a.id,
125
                a.parent,
126
                &t.id,
127
                &t.tag_name,
128
                &t.tag_value,
129
                t.description
130
            )
131
8
            .fetch_one(&mut *conn)
132
8
            .await
133
8
            .map_err(|err| {
134
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
135
                ServerError::DB(DBError::Sqlx(err))
136
            })?;
137

            
138
8
            Ok(())
139
8
        }
140

            
141
5
        pub async fn get_account_tags(&self, a: &Account) -> Result<Vec<Tag>, ServerError> {
142
5
            let mut conn = self.get_connection().await.map_err(|err| {
143
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
144
                ServerError::DB(err)
145
            })?;
146

            
147
5
            let tags = sqlx::query_file_as!(Tag, "sql/select/accounts/tags.sql", &a.id)
148
5
                .fetch_all(&mut *conn)
149
5
                .await
150
5
                .map_err(|err| {
151
                    log::error!("Database error: {err:?}");
152
                    ServerError::DB(DBError::Sqlx(err))
153
                })?;
154

            
155
5
            Ok(tags)
156
5
        }
157

            
158
        pub async fn list_transaction_ids_by_account(
159
            &self,
160
            account_id: Uuid,
161
        ) -> Result<Vec<Uuid>, ServerError> {
162
            let mut conn = self.get_connection().await.map_err(|err| {
163
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
164
                ServerError::DB(err)
165
            })?;
166

            
167
            let rows = sqlx::query_file!("sql/select/transactions/by_account_all.sql", &account_id)
168
                .fetch_all(&mut *conn)
169
                .await
170
                .map_err(|err| {
171
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
172
                    ServerError::DB(DBError::Sqlx(err))
173
                })?;
174

            
175
            Ok(rows.into_iter().map(|r| r.id).collect())
176
        }
177

            
178
1
        pub async fn get_account_tag(&self, a: &Account, tag: &String) -> Result<Tag, ServerError> {
179
1
            let mut conn = self.get_connection().await.map_err(|err| {
180
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
181
                ServerError::DB(err)
182
            })?;
183

            
184
1
            let tag = sqlx::query_file_as!(Tag, "sql/select/accounts/tag.sql", &a.id, tag)
185
1
                .fetch_one(&mut *conn)
186
1
                .await
187
1
                .map_err(|err| {
188
                    log::error!("Database error: {err:?}");
189
                    ServerError::DB(DBError::Sqlx(err))
190
                })?;
191

            
192
1
            Ok(tag)
193
1
        }
194
    }
195

            
196
    #[cfg(test)]
197
    mod account_tests {
198
        use super::*;
199
        use crate::db::DB_POOL;
200
        #[cfg(feature = "testlog")]
201
        use env_logger;
202
        use finance::commodity::Commodity;
203
        #[cfg(feature = "testlog")]
204
        use log;
205
        use sqlx::PgPool;
206

            
207
        use supp_macro::local_db_sqlx_test;
208
        use tokio::sync::OnceCell;
209

            
210
        /// Context for keeping environment intact
211
        static CONTEXT: OnceCell<()> = OnceCell::const_new();
212
        static COMMODITY: OnceCell<Commodity> = OnceCell::const_new();
213
        static USER: OnceCell<User> = OnceCell::const_new();
214

            
215
3
        async fn setup() {
216
3
            CONTEXT
217
3
                .get_or_init(|| async {
218
                    #[cfg(feature = "testlog")]
219
1
                    let _ = env_logger::builder()
220
1
                        .is_test(true)
221
1
                        .filter_level(log::LevelFilter::Trace)
222
1
                        .try_init();
223
2
                })
224
3
                .await;
225

            
226
3
            COMMODITY
227
3
                .get_or_init(|| async { Commodity { id: Uuid::new_v4() } })
228
3
                .await;
229

            
230
3
            USER.get_or_init(|| async { User { id: Uuid::new_v4() } })
231
3
                .await;
232
3
        }
233

            
234
        // #[local_db_sqlx_test]
235
        // async fn test_add_account(pool: PgPool) -> Result<(), anyhow::Error> {
236
        //     let mut conn = pool.acquire().await.unwrap();
237
        //     USER.get()
238
        //         .unwrap()
239
        //         .commit()
240
        //         .await
241
        //         .expect("Failed to commit user to database");
242

            
243
        //     let mut conn = pool.acquire().await.unwrap();
244
        //     COMMODITY.get().unwrap().commit(&mut *conn).await?;
245

            
246
        //     let account = Account {
247
        //         id: Uuid::new_v4(),
248
        //         commodity_id: COMMODITY.get().unwrap().id,
249
        //         parent: None,
250
        //     };
251

            
252
        //     account.commit(&mut *conn).await?;
253

            
254
        //     let script = vec![];
255
        //     let user = USER.get().unwrap();
256
        //     let acc = user.add_account(&script).await?;
257
        //     assert!(!acc.commodity_id.is_nil());
258
        // }
259

            
260
        #[local_db_sqlx_test]
261
        async fn test_create_account(pool: PgPool) -> Result<(), anyhow::Error> {
262
            USER.get()
263
                .unwrap()
264
                .commit()
265
                .await
266
                .expect("Failed to commit user to database");
267
            let mut conn = pool.acquire().await.unwrap();
268

            
269
            COMMODITY.get().unwrap().commit(&mut *conn).await?;
270

            
271
            let user = USER.get().unwrap();
272
            let a = user.create_account("Test Account", None).await?;
273

            
274
            let tag = user.get_account_tag(&a, &"name".to_string()).await?;
275
            assert_eq!(tag.tag_value, "Test Account");
276
        }
277

            
278
        #[local_db_sqlx_test]
279
        async fn test_account_tag(pool: PgPool) -> Result<(), anyhow::Error> {
280
            USER.get()
281
                .unwrap()
282
                .commit()
283
                .await
284
                .expect("Failed to commit user to database");
285
            let mut conn = pool.acquire().await.unwrap();
286
            COMMODITY.get().unwrap().commit(&mut *conn).await?;
287

            
288
            let account = Account {
289
                id: Uuid::new_v4(),
290
                parent: None,
291
            };
292
            account.commit(&mut *conn).await?;
293

            
294
            let user = USER.get().unwrap();
295
            user.set_account_tag(
296
                &account,
297
                &Tag::builder()
298
                    .id(Uuid::new_v4())
299
                    .tag_name("test")
300
                    .tag_value("testval")
301
                    .build()?,
302
            )
303
            .await?;
304

            
305
            let tags = user.get_account_tags(&account).await?;
306
            assert_eq!(tags.len(), 1);
307
            assert_eq!(tags[0].tag_name, "test");
308
            assert_eq!(tags[0].tag_value, "testval");
309

            
310
            user.set_account_tag(
311
                &account,
312
                &Tag::builder()
313
                    .id(Uuid::new_v4())
314
                    .tag_name("test")
315
                    .tag_value("testval2")
316
                    .build()?,
317
            )
318
            .await?;
319

            
320
            let tags = user.get_account_tags(&account).await?;
321
            assert_eq!(tags.len(), 1);
322
            assert_eq!(tags[0].tag_name, "test");
323
            assert_eq!(tags[0].tag_value, "testval2");
324
        }
325

            
326
        #[local_db_sqlx_test]
327
        async fn test_get_account_tags(pool: PgPool) -> Result<(), anyhow::Error> {
328
            USER.get()
329
                .unwrap()
330
                .commit()
331
                .await
332
                .expect("Failed to commit user to database");
333
            let mut conn = pool.acquire().await.unwrap();
334
            COMMODITY.get().unwrap().commit(&mut *conn).await?;
335

            
336
            let account = Account {
337
                id: Uuid::new_v4(),
338
                parent: None,
339
            };
340
            account.commit(&mut *conn).await?;
341

            
342
            let user = USER.get().unwrap();
343
            user.set_account_tag(
344
                &account,
345
                &Tag::builder()
346
                    .id(Uuid::new_v4())
347
                    .tag_name("test1")
348
                    .tag_value("value1")
349
                    .build()?,
350
            )
351
            .await?;
352

            
353
            let tags = user.get_account_tags(&account).await?;
354
            assert_eq!(tags.len(), 1);
355

            
356
            user.set_account_tag(
357
                &account,
358
                &Tag::builder()
359
                    .id(Uuid::new_v4())
360
                    .tag_name("test2")
361
                    .tag_value("value2")
362
                    .build()?,
363
            )
364
            .await?;
365

            
366
            let mut newtags = user.get_account_tags(&account).await?;
367
            assert_eq!(newtags.len(), 2);
368

            
369
            newtags[0].tag_value = "newvalue1".to_string();
370
            user.update_account_tags(&account, &newtags).await?;
371

            
372
            let updated_tags = user.get_account_tags(&account).await?;
373
            assert_eq!(updated_tags[0].tag_value, "newvalue1");
374
        }
375
    }
376
}