1
pub mod user {
2
    use crate::db::DBError;
3
    use crate::error::ServerError;
4
    use crate::user::User;
5
    use finance::tag::Tag;
6
    use sqlx::types::Uuid;
7

            
8
    pub struct ScriptInfo {
9
        pub id: Uuid,
10
        pub name: Option<String>,
11
        pub size: i32,
12
        pub enabled: Option<String>,
13
    }
14

            
15
    pub struct ScriptDetail {
16
        pub id: Uuid,
17
        pub name: Option<String>,
18
        pub bytecode: Vec<u8>,
19
        pub enabled: Option<String>,
20
    }
21

            
22
    impl User {
23
6
        pub async fn list_scripts(&self) -> Result<Vec<ScriptInfo>, ServerError> {
24
6
            let mut conn = self.get_connection().await?;
25

            
26
6
            let scripts = sqlx::query_file!("sql/select/scripts/all.sql")
27
6
                .fetch_all(&mut *conn)
28
6
                .await
29
6
                .map_err(|err| {
30
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
31
                    ServerError::DB(DBError::Sqlx(err))
32
                })?;
33

            
34
6
            Ok(scripts
35
6
                .into_iter()
36
6
                .map(|r| ScriptInfo {
37
8
                    id: r.id,
38
8
                    name: r.script_name,
39
8
                    size: r.size.unwrap_or(0),
40
8
                    enabled: r.enabled,
41
8
                })
42
6
                .collect())
43
6
        }
44

            
45
8
        pub async fn get_script(&self, id: Uuid) -> Result<ScriptDetail, ServerError> {
46
8
            let mut conn = self.get_connection().await?;
47

            
48
8
            let script = sqlx::query_file!("sql/select/scripts/by_id.sql", &id)
49
8
                .fetch_one(&mut *conn)
50
8
                .await
51
8
                .map_err(|err| {
52
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
53
                    ServerError::DB(DBError::Sqlx(err))
54
                })?;
55

            
56
8
            Ok(ScriptDetail {
57
8
                id: script.id,
58
8
                name: script.script_name,
59
8
                bytecode: script.bytecode,
60
8
                enabled: script.enabled,
61
8
            })
62
8
        }
63

            
64
10
        pub async fn create_script(
65
10
            &self,
66
10
            bytecode: Vec<u8>,
67
10
            name: Option<String>,
68
10
        ) -> Result<Uuid, ServerError> {
69
10
            let mut conn = self.get_connection().await?;
70

            
71
10
            let id = Uuid::new_v4();
72
10
            sqlx::query_file!("sql/insert/scripts/script.sql", &id, &bytecode)
73
10
                .execute(&mut *conn)
74
10
                .await
75
10
                .map_err(|err| {
76
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
77
                    ServerError::DB(DBError::Sqlx(err))
78
                })?;
79

            
80
10
            if let Some(name) = name {
81
6
                let tag_id = Uuid::new_v4();
82
6
                Tag {
83
6
                    id: tag_id,
84
6
                    tag_name: "name".to_string(),
85
6
                    tag_value: name,
86
6
                    description: None,
87
6
                }
88
6
                .commit(&mut *conn)
89
6
                .await
90
6
                .map_err(|err| {
91
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
92
                    ServerError::Finance(err)
93
                })?;
94

            
95
6
                sqlx::query_file!("sql/insert/script_tags/script_tag.sql", &id, &tag_id)
96
6
                    .execute(&mut *conn)
97
6
                    .await
98
6
                    .map_err(|err| {
99
                        log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
100
                        ServerError::DB(DBError::Sqlx(err))
101
                    })?;
102
4
            }
103

            
104
10
            Ok(id)
105
10
        }
106

            
107
1
        pub async fn update_script_bytecode(
108
1
            &self,
109
1
            id: Uuid,
110
1
            bytecode: Vec<u8>,
111
1
        ) -> Result<(), ServerError> {
112
1
            let mut conn = self.get_connection().await?;
113

            
114
1
            sqlx::query_file!("sql/update/scripts/bytecode.sql", &id, &bytecode)
115
1
                .execute(&mut *conn)
116
1
                .await
117
1
                .map_err(|err| {
118
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
119
                    ServerError::DB(DBError::Sqlx(err))
120
                })?;
121

            
122
1
            Ok(())
123
1
        }
124

            
125
2
        pub async fn delete_script(&self, id: Uuid) -> Result<(), ServerError> {
126
2
            let mut conn = self.get_connection().await?;
127

            
128
2
            sqlx::query!("DELETE FROM script_tags WHERE script_id = $1", &id)
129
2
                .execute(&mut *conn)
130
2
                .await
131
2
                .map_err(|err| {
132
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
133
                    ServerError::DB(DBError::Sqlx(err))
134
                })?;
135

            
136
2
            sqlx::query_file!("sql/delete/scripts/by_id.sql", &id)
137
2
                .execute(&mut *conn)
138
2
                .await
139
2
                .map_err(|err| {
140
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
141
                    ServerError::DB(DBError::Sqlx(err))
142
                })?;
143

            
144
2
            Ok(())
145
2
        }
146

            
147
2
        pub async fn set_script_enabled(&self, id: Uuid, enabled: bool) -> Result<(), ServerError> {
148
2
            let mut conn = self.get_connection().await?;
149

            
150
2
            sqlx::query!(
151
                "DELETE FROM script_tags WHERE script_id = $1 AND tag_id IN (
152
                    SELECT t.id FROM tags t
153
                    INNER JOIN script_tags st ON st.tag_id = t.id
154
                    WHERE st.script_id = $1 AND t.tag_name = 'enabled'
155
                )",
156
                &id
157
            )
158
2
            .execute(&mut *conn)
159
2
            .await
160
2
            .map_err(|err| {
161
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
162
                ServerError::DB(DBError::Sqlx(err))
163
            })?;
164

            
165
2
            if !enabled {
166
1
                let tag_id = Uuid::new_v4();
167
1
                Tag {
168
1
                    id: tag_id,
169
1
                    tag_name: "enabled".to_string(),
170
1
                    tag_value: "false".to_string(),
171
1
                    description: None,
172
1
                }
173
1
                .commit(&mut *conn)
174
1
                .await
175
1
                .map_err(|err| {
176
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
177
                    ServerError::Finance(err)
178
                })?;
179

            
180
1
                sqlx::query_file!("sql/insert/script_tags/script_tag.sql", &id, &tag_id)
181
1
                    .execute(&mut *conn)
182
1
                    .await
183
1
                    .map_err(|err| {
184
                        log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
185
                        ServerError::DB(DBError::Sqlx(err))
186
                    })?;
187
1
            }
188

            
189
2
            Ok(())
190
2
        }
191

            
192
2
        pub async fn update_script_name(
193
2
            &self,
194
2
            id: Uuid,
195
2
            name: Option<String>,
196
2
        ) -> Result<(), ServerError> {
197
2
            let mut conn = self.get_connection().await?;
198

            
199
2
            sqlx::query!(
200
                "DELETE FROM script_tags WHERE script_id = $1 AND tag_id IN (
201
                    SELECT t.id FROM tags t
202
                    INNER JOIN script_tags st ON st.tag_id = t.id
203
                    WHERE st.script_id = $1 AND t.tag_name = 'name'
204
                )",
205
                &id
206
            )
207
2
            .execute(&mut *conn)
208
2
            .await
209
2
            .map_err(|err| {
210
                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
211
                ServerError::DB(DBError::Sqlx(err))
212
            })?;
213

            
214
2
            if let Some(name) = name {
215
1
                let tag_id = Uuid::new_v4();
216
1
                Tag {
217
1
                    id: tag_id,
218
1
                    tag_name: "name".to_string(),
219
1
                    tag_value: name,
220
1
                    description: None,
221
1
                }
222
1
                .commit(&mut *conn)
223
1
                .await
224
1
                .map_err(|err| {
225
                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
226
                    ServerError::Finance(err)
227
                })?;
228

            
229
1
                sqlx::query_file!("sql/insert/script_tags/script_tag.sql", &id, &tag_id)
230
1
                    .execute(&mut *conn)
231
1
                    .await
232
1
                    .map_err(|err| {
233
                        log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
234
                        ServerError::DB(DBError::Sqlx(err))
235
                    })?;
236
1
            }
237

            
238
2
            Ok(())
239
2
        }
240
    }
241

            
242
    #[cfg(test)]
243
    mod script_mgmt_tests {
244
        use super::*;
245
        use crate::db::DB_POOL;
246
        #[cfg(feature = "testlog")]
247
        use env_logger;
248
        #[cfg(feature = "testlog")]
249
        use log;
250
        use sqlx::PgPool;
251
        use supp_macro::local_db_sqlx_test;
252
        use tokio::sync::OnceCell;
253

            
254
        static CONTEXT: OnceCell<()> = OnceCell::const_new();
255
        static USER: OnceCell<User> = OnceCell::const_new();
256

            
257
8
        async fn setup() {
258
8
            CONTEXT
259
8
                .get_or_init(|| async {
260
                    #[cfg(feature = "testlog")]
261
1
                    let _ = env_logger::builder()
262
1
                        .is_test(true)
263
1
                        .filter_level(log::LevelFilter::Trace)
264
1
                        .try_init();
265
2
                })
266
8
                .await;
267

            
268
8
            USER.get_or_init(|| async { User { id: Uuid::new_v4() } })
269
8
                .await;
270
8
        }
271

            
272
        #[local_db_sqlx_test]
273
        async fn test_create_and_list_scripts(pool: PgPool) -> Result<(), anyhow::Error> {
274
            USER.get()
275
                .unwrap()
276
                .commit()
277
                .await
278
                .expect("Failed to commit user to database");
279

            
280
            let user = USER.get().unwrap();
281
            let bytecode = vec![0x00, 0x61, 0x73, 0x6d];
282

            
283
            let id = user.create_script(bytecode.clone(), None).await?;
284
            assert!(!id.is_nil());
285

            
286
            let scripts = user.list_scripts().await?;
287
            assert_eq!(scripts.len(), 1);
288
            assert_eq!(scripts[0].id, id);
289
            assert_eq!(scripts[0].size, bytecode.len() as i32);
290
            assert!(scripts[0].name.is_none());
291
        }
292

            
293
        #[local_db_sqlx_test]
294
        async fn test_create_script_with_name(pool: PgPool) -> Result<(), anyhow::Error> {
295
            USER.get()
296
                .unwrap()
297
                .commit()
298
                .await
299
                .expect("Failed to commit user to database");
300

            
301
            let user = USER.get().unwrap();
302
            let bytecode = vec![0x00, 0x61, 0x73, 0x6d];
303
            let name = "test_script".to_string();
304

            
305
            let _id = user.create_script(bytecode, Some(name.clone())).await?;
306

            
307
            let scripts = user.list_scripts().await?;
308
            assert_eq!(scripts.len(), 1);
309
            assert_eq!(scripts[0].name, Some(name));
310
        }
311

            
312
        #[local_db_sqlx_test]
313
        async fn test_get_script(pool: PgPool) -> Result<(), anyhow::Error> {
314
            USER.get()
315
                .unwrap()
316
                .commit()
317
                .await
318
                .expect("Failed to commit user to database");
319

            
320
            let user = USER.get().unwrap();
321
            let bytecode = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
322
            let name = "fetch_test".to_string();
323

            
324
            let id = user
325
                .create_script(bytecode.clone(), Some(name.clone()))
326
                .await?;
327

            
328
            let script = user.get_script(id).await?;
329
            assert_eq!(script.id, id);
330
            assert_eq!(script.bytecode, bytecode);
331
            assert_eq!(script.name, Some(name));
332
        }
333

            
334
        #[local_db_sqlx_test]
335
        async fn test_update_script_bytecode(pool: PgPool) -> Result<(), anyhow::Error> {
336
            USER.get()
337
                .unwrap()
338
                .commit()
339
                .await
340
                .expect("Failed to commit user to database");
341

            
342
            let user = USER.get().unwrap();
343
            let original_bytecode = vec![0x00, 0x61, 0x73, 0x6d];
344
            let updated_bytecode = vec![0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00];
345

            
346
            let id = user.create_script(original_bytecode, None).await?;
347

            
348
            user.update_script_bytecode(id, updated_bytecode.clone())
349
                .await?;
350

            
351
            let script = user.get_script(id).await?;
352
            assert_eq!(script.bytecode, updated_bytecode);
353
        }
354

            
355
        #[local_db_sqlx_test]
356
        async fn test_delete_script(pool: PgPool) -> Result<(), anyhow::Error> {
357
            USER.get()
358
                .unwrap()
359
                .commit()
360
                .await
361
                .expect("Failed to commit user to database");
362

            
363
            let user = USER.get().unwrap();
364
            let bytecode = vec![0x00, 0x61, 0x73, 0x6d];
365

            
366
            let id = user
367
                .create_script(bytecode, Some("to_delete".to_string()))
368
                .await?;
369

            
370
            let scripts = user.list_scripts().await?;
371
            assert_eq!(scripts.len(), 1);
372

            
373
            user.delete_script(id).await?;
374

            
375
            let scripts = user.list_scripts().await?;
376
            assert!(scripts.is_empty());
377
        }
378

            
379
        #[local_db_sqlx_test]
380
        async fn test_set_script_enabled(pool: PgPool) -> Result<(), anyhow::Error> {
381
            USER.get()
382
                .unwrap()
383
                .commit()
384
                .await
385
                .expect("Failed to commit user to database");
386

            
387
            let user = USER.get().unwrap();
388
            let bytecode = vec![0x00, 0x61, 0x73, 0x6d];
389

            
390
            let id = user.create_script(bytecode, None).await?;
391

            
392
            let script = user.get_script(id).await?;
393
            assert!(script.enabled.is_none());
394

            
395
            user.set_script_enabled(id, false).await?;
396
            let script = user.get_script(id).await?;
397
            assert_eq!(script.enabled, Some("false".to_string()));
398

            
399
            user.set_script_enabled(id, true).await?;
400
            let script = user.get_script(id).await?;
401
            assert!(script.enabled.is_none());
402
        }
403

            
404
        #[local_db_sqlx_test]
405
        async fn test_update_script_name(pool: PgPool) -> Result<(), anyhow::Error> {
406
            USER.get()
407
                .unwrap()
408
                .commit()
409
                .await
410
                .expect("Failed to commit user to database");
411

            
412
            let user = USER.get().unwrap();
413
            let bytecode = vec![0x00, 0x61, 0x73, 0x6d];
414

            
415
            let id = user
416
                .create_script(bytecode, Some("original_name".to_string()))
417
                .await?;
418

            
419
            let script = user.get_script(id).await?;
420
            assert_eq!(script.name, Some("original_name".to_string()));
421

            
422
            user.update_script_name(id, Some("new_name".to_string()))
423
                .await?;
424
            let script = user.get_script(id).await?;
425
            assert_eq!(script.name, Some("new_name".to_string()));
426

            
427
            user.update_script_name(id, None).await?;
428
            let script = user.get_script(id).await?;
429
            assert!(script.name.is_none());
430
        }
431

            
432
        #[local_db_sqlx_test]
433
        async fn test_multiple_scripts(pool: PgPool) -> Result<(), anyhow::Error> {
434
            USER.get()
435
                .unwrap()
436
                .commit()
437
                .await
438
                .expect("Failed to commit user to database");
439

            
440
            let user = USER.get().unwrap();
441

            
442
            let id1 = user
443
                .create_script(vec![0x01], Some("script1".to_string()))
444
                .await?;
445
            let id2 = user
446
                .create_script(vec![0x02, 0x03], Some("script2".to_string()))
447
                .await?;
448
            let id3 = user.create_script(vec![0x04, 0x05, 0x06], None).await?;
449

            
450
            let scripts = user.list_scripts().await?;
451
            assert_eq!(scripts.len(), 3);
452

            
453
            let ids: Vec<Uuid> = scripts.iter().map(|s| s.id).collect();
454
            assert!(ids.contains(&id1));
455
            assert!(ids.contains(&id2));
456
            assert!(ids.contains(&id3));
457

            
458
            user.delete_script(id2).await?;
459
            let scripts = user.list_scripts().await?;
460
            assert_eq!(scripts.len(), 2);
461

            
462
            let ids: Vec<Uuid> = scripts.iter().map(|s| s.id).collect();
463
            assert!(ids.contains(&id1));
464
            assert!(!ids.contains(&id2));
465
            assert!(ids.contains(&id3));
466
        }
467
    }
468
}