Skip to main content

server/
script_mgmt.rs

1pub 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        pub async fn list_scripts(&self) -> Result<Vec<ScriptInfo>, ServerError> {
24            let mut conn = self.get_connection().await?;
25
26            let scripts = sqlx::query_file!("sql/select/scripts/all.sql")
27                .fetch_all(&mut *conn)
28                .await
29                .map_err(|err| {
30                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
31                    ServerError::DB(DBError::Sqlx(err))
32                })?;
33
34            Ok(scripts
35                .into_iter()
36                .map(|r| ScriptInfo {
37                    id: r.id,
38                    name: r.script_name,
39                    size: r.size.unwrap_or(0),
40                    enabled: r.enabled,
41                })
42                .collect())
43        }
44
45        pub async fn get_script(&self, id: Uuid) -> Result<ScriptDetail, ServerError> {
46            let mut conn = self.get_connection().await?;
47
48            let script = sqlx::query_file!("sql/select/scripts/by_id.sql", &id)
49                .fetch_one(&mut *conn)
50                .await
51                .map_err(|err| {
52                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
53                    ServerError::DB(DBError::Sqlx(err))
54                })?;
55
56            Ok(ScriptDetail {
57                id: script.id,
58                name: script.script_name,
59                bytecode: script.bytecode,
60                enabled: script.enabled,
61            })
62        }
63
64        pub async fn create_script(
65            &self,
66            bytecode: Vec<u8>,
67            name: Option<String>,
68        ) -> Result<Uuid, ServerError> {
69            let mut conn = self.get_connection().await?;
70
71            let id = Uuid::new_v4();
72            sqlx::query_file!("sql/insert/scripts/script.sql", &id, &bytecode)
73                .execute(&mut *conn)
74                .await
75                .map_err(|err| {
76                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
77                    ServerError::DB(DBError::Sqlx(err))
78                })?;
79
80            if let Some(name) = name {
81                let tag_id = Uuid::new_v4();
82                Tag {
83                    id: tag_id,
84                    tag_name: "name".to_string(),
85                    tag_value: name,
86                    description: None,
87                }
88                .commit(&mut *conn)
89                .await
90                .map_err(|err| {
91                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
92                    ServerError::Finance(err)
93                })?;
94
95                sqlx::query_file!("sql/insert/script_tags/script_tag.sql", &id, &tag_id)
96                    .execute(&mut *conn)
97                    .await
98                    .map_err(|err| {
99                        log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
100                        ServerError::DB(DBError::Sqlx(err))
101                    })?;
102            }
103
104            Ok(id)
105        }
106
107        pub async fn update_script_bytecode(
108            &self,
109            id: Uuid,
110            bytecode: Vec<u8>,
111        ) -> Result<(), ServerError> {
112            let mut conn = self.get_connection().await?;
113
114            sqlx::query_file!("sql/update/scripts/bytecode.sql", &id, &bytecode)
115                .execute(&mut *conn)
116                .await
117                .map_err(|err| {
118                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
119                    ServerError::DB(DBError::Sqlx(err))
120                })?;
121
122            Ok(())
123        }
124
125        pub async fn delete_script(&self, id: Uuid) -> Result<(), ServerError> {
126            let mut conn = self.get_connection().await?;
127
128            sqlx::query!("DELETE FROM script_tags WHERE script_id = $1", &id)
129                .execute(&mut *conn)
130                .await
131                .map_err(|err| {
132                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
133                    ServerError::DB(DBError::Sqlx(err))
134                })?;
135
136            sqlx::query_file!("sql/delete/scripts/by_id.sql", &id)
137                .execute(&mut *conn)
138                .await
139                .map_err(|err| {
140                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
141                    ServerError::DB(DBError::Sqlx(err))
142                })?;
143
144            Ok(())
145        }
146
147        pub async fn set_script_enabled(&self, id: Uuid, enabled: bool) -> Result<(), ServerError> {
148            let mut conn = self.get_connection().await?;
149
150            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            .execute(&mut *conn)
159            .await
160            .map_err(|err| {
161                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
162                ServerError::DB(DBError::Sqlx(err))
163            })?;
164
165            if !enabled {
166                let tag_id = Uuid::new_v4();
167                Tag {
168                    id: tag_id,
169                    tag_name: "enabled".to_string(),
170                    tag_value: "false".to_string(),
171                    description: None,
172                }
173                .commit(&mut *conn)
174                .await
175                .map_err(|err| {
176                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
177                    ServerError::Finance(err)
178                })?;
179
180                sqlx::query_file!("sql/insert/script_tags/script_tag.sql", &id, &tag_id)
181                    .execute(&mut *conn)
182                    .await
183                    .map_err(|err| {
184                        log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
185                        ServerError::DB(DBError::Sqlx(err))
186                    })?;
187            }
188
189            Ok(())
190        }
191
192        pub async fn update_script_name(
193            &self,
194            id: Uuid,
195            name: Option<String>,
196        ) -> Result<(), ServerError> {
197            let mut conn = self.get_connection().await?;
198
199            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            .execute(&mut *conn)
208            .await
209            .map_err(|err| {
210                log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
211                ServerError::DB(DBError::Sqlx(err))
212            })?;
213
214            if let Some(name) = name {
215                let tag_id = Uuid::new_v4();
216                Tag {
217                    id: tag_id,
218                    tag_name: "name".to_string(),
219                    tag_value: name,
220                    description: None,
221                }
222                .commit(&mut *conn)
223                .await
224                .map_err(|err| {
225                    log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
226                    ServerError::Finance(err)
227                })?;
228
229                sqlx::query_file!("sql/insert/script_tags/script_tag.sql", &id, &tag_id)
230                    .execute(&mut *conn)
231                    .await
232                    .map_err(|err| {
233                        log::error!("{}", t!("Database error: %{err}", err = err : {:?}));
234                        ServerError::DB(DBError::Sqlx(err))
235                    })?;
236            }
237
238            Ok(())
239        }
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        async fn setup() {
258            CONTEXT
259                .get_or_init(|| async {
260                    #[cfg(feature = "testlog")]
261                    let _ = env_logger::builder()
262                        .is_test(true)
263                        .filter_level(log::LevelFilter::Trace)
264                        .try_init();
265                })
266                .await;
267
268            USER.get_or_init(|| async { User { id: Uuid::new_v4() } })
269                .await;
270        }
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}