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

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

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

            
79
137
            Ok(a)
80
157
        }
81

            
82
138
        pub async fn update_account_tags(
83
138
            &self,
84
138
            a: &Account,
85
138
            tags: &[Tag],
86
138
        ) -> Result<(), ServerError> {
87
139
            for tag in tags {
88
139
                self.set_account_tag(a, tag).await?;
89
            }
90
138
            Ok(())
91
138
        }
92

            
93
177
        pub async fn set_account_tag(&self, a: &Account, t: &Tag) -> Result<(), ServerError> {
94
177
            if t.tag_name.trim().is_empty() || t.tag_value.trim().is_empty() {
95
                return Err(ServerError::Creation);
96
177
            }
97
177
            let mut conn = self.get_connection().await.map_err(|err| {
98
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
99
                ServerError::DB(err)
100
            })?;
101

            
102
177
            sqlx::query_file!(
103
                "sql/set/accounts/tag.sql",
104
                &a.id,
105
                &t.tag_name,
106
                &t.tag_value,
107
                t.description
108
            )
109
177
            .execute(&mut *conn)
110
177
            .await
111
177
            .map_err(|err| {
112
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
113
                ServerError::DB(DBError::Sqlx(err))
114
            })?;
115

            
116
177
            Ok(())
117
177
        }
118

            
119
5
        pub async fn get_account_tags(&self, a: &Account) -> Result<Vec<Tag>, ServerError> {
120
5
            let mut conn = self.get_connection().await.map_err(|err| {
121
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
122
                ServerError::DB(err)
123
            })?;
124

            
125
5
            let tags = sqlx::query_file_as!(Tag, "sql/select/accounts/tags.sql", &a.id)
126
5
                .fetch_all(&mut *conn)
127
5
                .await
128
5
                .map_err(|err| {
129
                    log::error!("Database error: {err:?}");
130
                    ServerError::DB(DBError::Sqlx(err))
131
                })?;
132

            
133
5
            Ok(tags)
134
5
        }
135

            
136
        pub async fn list_transaction_ids_by_account(
137
            &self,
138
            account_id: Uuid,
139
        ) -> Result<Vec<Uuid>, ServerError> {
140
            let mut conn = self.get_connection().await.map_err(|err| {
141
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
142
                ServerError::DB(err)
143
            })?;
144

            
145
            let rows = sqlx::query_file!("sql/select/transactions/by_account_all.sql", &account_id)
146
                .fetch_all(&mut *conn)
147
                .await
148
                .map_err(|err| {
149
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
150
                    ServerError::DB(DBError::Sqlx(err))
151
                })?;
152

            
153
            Ok(rows.into_iter().map(|r| r.id).collect())
154
        }
155

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

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

            
170
1
            Ok(tag)
171
1
        }
172
    }
173

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

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

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

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

            
204
3
            COMMODITY
205
3
                .get_or_init(|| async { Commodity { id: Uuid::new_v4() } })
206
3
                .await;
207

            
208
3
            USER.get_or_init(|| async { User { id: Uuid::new_v4() } })
209
3
                .await;
210
3
        }
211

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

            
221
        //     let mut conn = pool.acquire().await.unwrap();
222
        //     COMMODITY.get().unwrap().commit(&mut *conn).await?;
223

            
224
        //     let account = Account {
225
        //         id: Uuid::new_v4(),
226
        //         commodity_id: COMMODITY.get().unwrap().id,
227
        //         parent: None,
228
        //     };
229

            
230
        //     account.commit(&mut *conn).await?;
231

            
232
        //     let script = vec![];
233
        //     let user = USER.get().unwrap();
234
        //     let acc = user.add_account(&script).await?;
235
        //     assert!(!acc.commodity_id.is_nil());
236
        // }
237

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

            
247
            COMMODITY.get().unwrap().commit(&mut *conn).await?;
248

            
249
            let user = USER.get().unwrap();
250
            let a = user.create_account("Test Account", None).await?;
251

            
252
            let tag = user.get_account_tag(&a, &"name".to_string()).await?;
253
            assert_eq!(tag.tag_value, "Test Account");
254
        }
255

            
256
        #[local_db_sqlx_test]
257
        async fn test_account_tag(pool: PgPool) -> Result<(), anyhow::Error> {
258
            USER.get()
259
                .unwrap()
260
                .commit()
261
                .await
262
                .expect("Failed to commit user to database");
263
            let mut conn = pool.acquire().await.unwrap();
264
            COMMODITY.get().unwrap().commit(&mut *conn).await?;
265

            
266
            let account = Account {
267
                id: Uuid::new_v4(),
268
                parent: None,
269
            };
270
            account.commit(&mut *conn).await?;
271

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

            
283
            let tags = user.get_account_tags(&account).await?;
284
            assert_eq!(tags.len(), 1);
285
            assert_eq!(tags[0].tag_name, "test");
286
            assert_eq!(tags[0].tag_value, "testval");
287

            
288
            user.set_account_tag(
289
                &account,
290
                &Tag::builder()
291
                    .id(Uuid::new_v4())
292
                    .tag_name("test")
293
                    .tag_value("testval2")
294
                    .build()?,
295
            )
296
            .await?;
297

            
298
            let tags = user.get_account_tags(&account).await?;
299
            assert_eq!(tags.len(), 1);
300
            assert_eq!(tags[0].tag_name, "test");
301
            assert_eq!(tags[0].tag_value, "testval2");
302
        }
303

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

            
314
            let account = Account {
315
                id: Uuid::new_v4(),
316
                parent: None,
317
            };
318
            account.commit(&mut *conn).await?;
319

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

            
331
            let tags = user.get_account_tags(&account).await?;
332
            assert_eq!(tags.len(), 1);
333

            
334
            user.set_account_tag(
335
                &account,
336
                &Tag::builder()
337
                    .id(Uuid::new_v4())
338
                    .tag_name("test2")
339
                    .tag_value("value2")
340
                    .build()?,
341
            )
342
            .await?;
343

            
344
            let mut newtags = user.get_account_tags(&account).await?;
345
            assert_eq!(newtags.len(), 2);
346

            
347
            newtags[0].tag_value = "newvalue1".to_string();
348
            user.update_account_tags(&account, &newtags).await?;
349

            
350
            let updated_tags = user.get_account_tags(&account).await?;
351
            assert_eq!(updated_tags[0].tag_value, "newvalue1");
352
        }
353
    }
354
}