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
124
        pub async fn create_account(
56
124
            &self,
57
124
            name: &str,
58
124
            parent: Option<Uuid>,
59
182
        ) -> Result<Account, ServerError> {
60
116
            let a = Account {
61
116
                id: Uuid::new_v4(),
62
116
                parent,
63
116
            };
64

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

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

            
79
112
            Ok(a)
80
116
        }
81

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

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

            
110
114
            Ok(())
111
114
        }
112

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

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

            
135
8
            Ok(())
136
8
        }
137

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

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

            
152
10
            Ok(tags)
153
10
        }
154

            
155
3
        pub async fn get_account_tag(&self, a: &Account, tag: &String) -> Result<Tag, ServerError> {
156
2
            let mut conn = self.get_connection().await.map_err(|err| {
157
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
158
                ServerError::DB(err)
159
            })?;
160

            
161
2
            let tag = sqlx::query_file_as!(Tag, "sql/select/accounts/tag.sql", &a.id, tag)
162
2
                .fetch_one(&mut *conn)
163
2
                .await
164
2
                .map_err(|err| {
165
                    log::error!("Database error: {err:?}");
166
                    ServerError::DB(DBError::Sqlx(err))
167
                })?;
168

            
169
2
            Ok(tag)
170
2
        }
171
    }
172

            
173
    #[cfg(test)]
174
    mod account_tests {
175
        use super::*;
176
        use crate::db::DB_POOL;
177
        #[cfg(feature = "testlog")]
178
        use env_logger;
179
        use finance::commodity::Commodity;
180
        #[cfg(feature = "testlog")]
181
        use log;
182
        use sqlx::PgPool;
183

            
184
        use supp_macro::local_db_sqlx_test;
185
        use tokio::sync::OnceCell;
186

            
187
        /// Context for keeping environment intact
188
        static CONTEXT: OnceCell<()> = OnceCell::const_new();
189
        static COMMODITY: OnceCell<Commodity> = OnceCell::const_new();
190
        static USER: OnceCell<User> = OnceCell::const_new();
191

            
192
9
        async fn setup() {
193
6
            CONTEXT
194
6
                .get_or_init(|| async {
195
                    #[cfg(feature = "testlog")]
196
2
                    let _ = env_logger::builder()
197
2
                        .is_test(true)
198
2
                        .filter_level(log::LevelFilter::Trace)
199
2
                        .try_init();
200
4
                })
201
6
                .await;
202

            
203
6
            COMMODITY
204
6
                .get_or_init(|| async {
205
2
                    Commodity {
206
2
                        fraction: 1000,
207
2
                        id: Uuid::new_v4(),
208
2
                    }
209
4
                })
210
6
                .await;
211

            
212
6
            USER.get_or_init(|| async { User { id: Uuid::new_v4() } })
213
6
                .await;
214
6
        }
215

            
216
        // #[local_db_sqlx_test]
217
        // async fn test_add_account(pool: PgPool) -> Result<(), anyhow::Error> {
218
        //     let mut conn = pool.acquire().await.unwrap();
219
        //     USER.get()
220
        //         .unwrap()
221
        //         .commit()
222
        //         .await
223
        //         .expect("Failed to commit user to database");
224

            
225
        //     let mut conn = pool.acquire().await.unwrap();
226
        //     COMMODITY.get().unwrap().commit(&mut *conn).await?;
227

            
228
        //     let account = Account {
229
        //         id: Uuid::new_v4(),
230
        //         commodity_id: COMMODITY.get().unwrap().id,
231
        //         parent: None,
232
        //     };
233

            
234
        //     account.commit(&mut *conn).await?;
235

            
236
        //     let script = vec![];
237
        //     let user = USER.get().unwrap();
238
        //     let acc = user.add_account(&script).await?;
239
        //     assert!(!acc.commodity_id.is_nil());
240
        // }
241

            
242
        #[local_db_sqlx_test]
243
        async fn test_create_account(pool: PgPool) -> Result<(), anyhow::Error> {
244
            USER.get()
245
                .unwrap()
246
                .commit()
247
                .await
248
                .expect("Failed to commit user to database");
249
            let mut conn = pool.acquire().await.unwrap();
250

            
251
            COMMODITY.get().unwrap().commit(&mut *conn).await?;
252

            
253
            let user = USER.get().unwrap();
254
            let a = user.create_account("Test Account", None).await?;
255

            
256
            let tag = user.get_account_tag(&a, &"name".to_string()).await?;
257
            assert_eq!(tag.tag_value, "Test Account");
258
        }
259

            
260
        #[local_db_sqlx_test]
261
        async fn test_account_tag(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
            COMMODITY.get().unwrap().commit(&mut *conn).await?;
269

            
270
            let account = Account {
271
                id: Uuid::new_v4(),
272
                parent: None,
273
            };
274
            account.commit(&mut *conn).await?;
275

            
276
            let user = USER.get().unwrap();
277
            user.set_account_tag(
278
                &account,
279
                &Tag::builder()
280
                    .id(Uuid::new_v4())
281
                    .tag_name("test")
282
                    .tag_value("testval")
283
                    .build()?,
284
            )
285
            .await?;
286

            
287
            let tags = user.get_account_tags(&account).await?;
288
            assert_eq!(tags.len(), 1);
289
            assert_eq!(tags[0].tag_name, "test");
290
            assert_eq!(tags[0].tag_value, "testval");
291

            
292
            user.set_account_tag(
293
                &account,
294
                &Tag::builder()
295
                    .id(Uuid::new_v4())
296
                    .tag_name("test")
297
                    .tag_value("testval2")
298
                    .build()?,
299
            )
300
            .await?;
301

            
302
            let tags = user.get_account_tags(&account).await?;
303
            assert_eq!(tags.len(), 1);
304
            assert_eq!(tags[0].tag_name, "test");
305
            assert_eq!(tags[0].tag_value, "testval2");
306
        }
307

            
308
        #[local_db_sqlx_test]
309
        async fn test_get_account_tags(pool: PgPool) -> Result<(), anyhow::Error> {
310
            USER.get()
311
                .unwrap()
312
                .commit()
313
                .await
314
                .expect("Failed to commit user to database");
315
            let mut conn = pool.acquire().await.unwrap();
316
            COMMODITY.get().unwrap().commit(&mut *conn).await?;
317

            
318
            let account = Account {
319
                id: Uuid::new_v4(),
320
                parent: None,
321
            };
322
            account.commit(&mut *conn).await?;
323

            
324
            let user = USER.get().unwrap();
325
            user.set_account_tag(
326
                &account,
327
                &Tag::builder()
328
                    .id(Uuid::new_v4())
329
                    .tag_name("test1")
330
                    .tag_value("value1")
331
                    .build()?,
332
            )
333
            .await?;
334

            
335
            let tags = user.get_account_tags(&account).await?;
336
            assert_eq!(tags.len(), 1);
337

            
338
            user.set_account_tag(
339
                &account,
340
                &Tag::builder()
341
                    .id(Uuid::new_v4())
342
                    .tag_name("test2")
343
                    .tag_value("value2")
344
                    .build()?,
345
            )
346
            .await?;
347

            
348
            let mut newtags = user.get_account_tags(&account).await?;
349
            assert_eq!(newtags.len(), 2);
350

            
351
            newtags[0].tag_value = "newvalue1".to_string();
352
            user.update_account_tags(&account, &newtags).await?;
353

            
354
            let updated_tags = user.get_account_tags(&account).await?;
355
            assert_eq!(updated_tags[0].tag_value, "newvalue1");
356
        }
357
    }
358
}