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 a
34 };
35
36 {
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 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 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]
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}