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}