Skip to main content

server/
account.rs

1pub 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        pub async fn create_account(
56            &self,
57            name: &str,
58            parent: Option<Uuid>,
59        ) -> Result<Account, ServerError> {
60            let a = Account {
61                id: Uuid::new_v4(),
62                parent,
63            };
64
65            let mut conn = self.get_connection().await.map_err(|err| {
66                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
67                ServerError::DB(err)
68            })?;
69
70            a.commit(&mut *conn).await?;
71            let tags: Vec<Tag> = vec![Tag {
72                id: Uuid::new_v4(),
73                tag_name: "name".to_string(),
74                tag_value: name.to_string(),
75                description: None,
76            }];
77            self.update_account_tags(&a, &tags).await?;
78
79            Ok(a)
80        }
81
82        pub async fn update_account_tags(
83            &self,
84            a: &Account,
85            tags: &[Tag],
86        ) -> Result<(), ServerError> {
87            for tag in tags {
88                self.set_account_tag(a, tag).await?;
89            }
90            Ok(())
91        }
92
93        pub async fn set_account_tag(&self, a: &Account, t: &Tag) -> Result<(), ServerError> {
94            if t.tag_name.trim().is_empty() || t.tag_value.trim().is_empty() {
95                return Err(ServerError::Creation);
96            }
97            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            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            .execute(&mut *conn)
110            .await
111            .map_err(|err| {
112                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
113                ServerError::DB(DBError::Sqlx(err))
114            })?;
115
116            Ok(())
117        }
118
119        pub async fn get_account_tags(&self, a: &Account) -> Result<Vec<Tag>, ServerError> {
120            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            let tags = sqlx::query_file_as!(Tag, "sql/select/accounts/tags.sql", &a.id)
126                .fetch_all(&mut *conn)
127                .await
128                .map_err(|err| {
129                    log::error!("Database error: {err:?}");
130                    ServerError::DB(DBError::Sqlx(err))
131                })?;
132
133            Ok(tags)
134        }
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        pub async fn get_account_tag(&self, a: &Account, tag: &String) -> Result<Tag, ServerError> {
157            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            let tag = sqlx::query_file_as!(Tag, "sql/select/accounts/tag.sql", &a.id, tag)
163                .fetch_one(&mut *conn)
164                .await
165                .map_err(|err| {
166                    log::error!("Database error: {err:?}");
167                    ServerError::DB(DBError::Sqlx(err))
168                })?;
169
170            Ok(tag)
171        }
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        async fn setup() {
194            CONTEXT
195                .get_or_init(|| async {
196                    #[cfg(feature = "testlog")]
197                    let _ = env_logger::builder()
198                        .is_test(true)
199                        .filter_level(log::LevelFilter::Trace)
200                        .try_init();
201                })
202                .await;
203
204            COMMODITY
205                .get_or_init(|| async { Commodity { id: Uuid::new_v4() } })
206                .await;
207
208            USER.get_or_init(|| async { User { id: Uuid::new_v4() } })
209                .await;
210        }
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}