1use serde::{Deserialize, Serialize};
18use std::fs;
19use std::path::{Path, PathBuf};
20use std::str::FromStr;
21use thiserror::Error;
22
23use cmd_lib::run_cmd;
24
25pub mod migrations;
26pub mod seeds;
27pub mod securities;
28pub mod design_docs;
29
30pub fn main_content(rootdir: &PathBuf) -> Result<String, Box<dyn std::error::Error>> {
31 let seeds_db_list: Vec<String> = seeds::db_list(&rootdir)?;
32 let securities_db_list: Vec<String> = securities::db_list(&rootdir)?;
33 let design_docs_db_list: Vec<String> = design_docs::db_list(&rootdir)?;
34 let migrations_db_list: Vec<String> = migrations::db_list(&rootdir)?;
36 let db_migrations: Vec<String> = migrations::dbs(&rootdir)?;
37 let db_seeds: Vec<String> = seeds::dbs(&rootdir)?;
38 let db_securities: Vec<String> = securities::dbs(&rootdir)?;
39 let db_design_docs: Vec<String> = design_docs::dbs(&rootdir)?;
40 let available_databases: String = format!(
42 include_str!("../../static/templates/available_databases.tpl"),
43 migrations_db_list.len(),
44 migrations_db_list
45 .iter()
46 .map(|e| format!(r#""{}","#, e))
47 .collect::<Vec<String>>()
48 .join("\n")
49 .trim(),
50 seeds_db_list.len(),
51 seeds_db_list
52 .iter()
53 .map(|e| format!(r#""{}","#, e))
54 .collect::<Vec<String>>()
55 .join("\n")
56 .trim(),
57 securities_db_list.len(),
58 securities_db_list
59 .iter()
60 .map(|e| format!(r#""{}","#, e))
61 .collect::<Vec<String>>()
62 .join("\n")
63 .trim(),
64 design_docs_db_list.len(),
65 design_docs_db_list
66 .iter()
67 .map(|e| format!(r#""{}","#, e))
68 .collect::<Vec<String>>()
69 .join("\n")
70 .trim(),
71 );
72
73 let available_seeds: Vec<Vec<PathBuf>> = seeds::available_seeds(&rootdir)?;
74 let available_securities: Vec<Vec<PathBuf>> = securities::available_securities(&rootdir)?;
75 let db_migrations_actions: Vec<String> = migrations::db_actions(&rootdir)?;
77 let db_seeds_actions: Vec<String> = seeds::db_actions(&rootdir)?;
78 let db_securities_actions: Vec<String> = securities::db_actions(&rootdir)?;
79 let db_design_docs_actions: Vec<String> = design_docs::db_actions(&rootdir)?;
80 let db_backup_match: Vec<String> = available_seeds
82 .iter()
83 .map(|a| {
84 a.iter()
85 .enumerate()
86 .map(|(index, file)| {
87 format!(
88 include_str!("../../static/templates/backup_match.tpl"),
89 index,
90 file.file_name()
91 .unwrap()
92 .to_str()
93 .unwrap()
94 .to_string()
95 .replace(".rs", ""),
96 index,
97 )
98 })
99 .collect()
100 })
101 .collect();
102 let db_restore_match: Vec<String> = available_seeds
103 .iter()
104 .map(|a| {
105 a.iter()
106 .enumerate()
107 .map(|(index, file)| {
108 format!(
109 include_str!("../../static/templates/restore_match.tpl"),
110 index,
111 file.file_name()
112 .unwrap()
113 .to_str()
114 .unwrap()
115 .to_string()
116 .replace(".rs", "")
117 )
118 })
119 .collect()
120 })
121 .collect();
122 let migrations_db_matches: Vec<String> = migrations::db_matches(&rootdir)?;
124 let seeds_db_matches: Vec<String> = seeds::db_matches(&rootdir)?;
125 let backups_db_matches: Vec<String> = seeds_db_list
126 .iter()
127 .enumerate()
128 .map(|(index, db)| {
129 format!(
130 include_str!("../../static/templates/backups_db_matches.tpl"),
131 index,
132 {
133 let mut c = db.chars();
135 match c.next() {
136 None => String::new(),
137 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
138 }
139 },
140 db_backup_match[index]
141 )
142 })
143 .collect();
144 let restore_db_matches: Vec<String> = seeds_db_list
145 .iter()
146 .enumerate()
147 .map(|(index, db)| {
148 format!(
149 include_str!("../../static/templates/restore_db_matches.tpl"),
150 index,
151 {
152 let mut c = db.chars();
154 match c.next() {
155 None => String::new(),
156 Some(f) => f.to_uppercase().collect::<String>() + c.as_str(),
157 }
158 },
159 db,
160 db_restore_match[index]
161 )
162 })
163 .collect();
164 let securities_db_matches: Vec<String> = securities::db_matches(&rootdir)?;
165 let design_docs_db_matches: Vec<String> = design_docs::db_matches(&rootdir)?;
166 Ok(format!(
168 include_str!("../../static/templates/main.tpl"),
169 db_migrations.join("\n"),
170 db_migrations_actions.join("\n"),
171 db_seeds.join("\n"),
172 db_seeds_actions.join("\n"),
173 db_securities.join("\n"),
174 db_securities_actions.join("\n"),
175 db_design_docs.join("\n"),
176 db_design_docs_actions.join("\n"),
177 available_databases,
178 migrations_db_matches.join("\n"),
179 seeds_db_matches.join("\n"),
180 securities_db_matches.join("\n"),
181 backups_db_matches.join("\n"),
182 restore_db_matches.join("\n"),
183 design_docs_db_matches.join("\n"),
184 ))
185}
186
187pub fn init_meta_dir(rootdir: &PathBuf) -> Result<(), Box<dyn std::error::Error>> {
188 let meta_cargo_path: PathBuf =
189 PathBuf::from_str(&format!("{}/_meta/Cargo.toml", rootdir.to_str().unwrap()))?;
190 let rootdir_str: &str = rootdir.to_str().unwrap();
191
192 let cargo_toml_content: String = format!(
193 include_str!("../../static/templates/cargo.toml.tpl"),
194 env!("CARGO_PKG_VERSION")
195 );
196
197 if !meta_cargo_path.exists() {
198 match run_cmd! {
199 cd $rootdir_str;
200 cargo new --name couchdb-orm-meta _meta/;
201 } {
202 Ok(_) => {
203 append_to_file(&meta_cargo_path, &cargo_toml_content)?;
204
205 fs::create_dir_all(&format!("{}/_meta/src/schemas", rootdir.to_str().unwrap()))?;
206 fs::create_dir_all(&format!(
207 "{}/_meta/src/migrations",
208 rootdir.to_str().unwrap()
209 ))?;
210 fs::create_dir_all(&format!("{}/_meta/src/seeds", rootdir.to_str().unwrap()))?;
211 fs::create_dir_all(&format!(
212 "{}/_meta/src/securities",
213 rootdir.to_str().unwrap()
214 ))?;
215 fs::create_dir_all(&format!(
216 "{}/_meta/src/design_docs",
217 rootdir.to_str().unwrap()
218 ))?;
219
220 let main_path: PathBuf =
221 PathBuf::from_str(&format!("{}/_meta/src/main.rs", rootdir.to_str().unwrap()))?;
222 let lib_path: PathBuf =
223 PathBuf::from_str(&format!("{}/_meta/src/lib.rs", rootdir.to_str().unwrap()))?;
224
225 fs::write(main_path, main_content(rootdir)?)?;
226 fs::write(lib_path, include_str!("../../static/templates/lib.tpl"))?;
227
228 Ok(())
229 }
230 Err(e) => Err(Box::new(e)),
231 }
232 } else {
233 Ok(())
235 }
236}
237
238pub fn append_to_file(
239 file_name: &PathBuf,
240 content: &str,
241) -> Result<(), Box<dyn std::error::Error>> {
242 let old_content = if file_name.exists() {
243 fs::read_to_string(&file_name)?
244 } else {
245 String::from("")
246 };
247 let new_content = format!("{}\n{}", old_content, content);
248 if !old_content.contains(content) {
249 fs::write(&file_name, &new_content.trim())?;
250 }
251 Ok(())
252}
253
254#[derive(Debug, Error, Serialize, Deserialize, Clone)]
255#[error("GetDirEntriesError: {0}")]
256pub struct GetDirEntriesError(String);
257
258pub fn get_dir_entries(path: &Path) -> Result<Vec<PathBuf>, GetDirEntriesError> {
259 let mut dirs = fs::read_dir(&path)
260 .map_err(|e| {
261 GetDirEntriesError(format!(
262 "utils / get_dir_entries / read_dir: could not read dir from {}\n cause: {}",
263 path.display(),
264 e
265 ))
266 })?
267 .map(|res| res.map(|e| e.path()))
268 .collect::<Result<Vec<PathBuf>, std::io::Error>>()
269 .map_err(|e| {
270 GetDirEntriesError(format!(
271 "utils / get_dir_entries / collect: could not collect from {}\n cause: {}",
272 path.display(),
273 e
274 ))
275 })?;
276 dirs.sort();
277 Ok(dirs)
278}
279
280#[cfg(test)]
281mod test {
282 use super::*;
283 use crate::tests::utils::*;
284
285 #[test]
286 fn test_get_dir_entries_error() {
287 let path: PathBuf = PathBuf::from("not_a_dir");
288 assert_eq!(
289 format!("{}", get_dir_entries(&path).unwrap_err()),
290 format!(
291 "GetDirEntriesError: utils / get_dir_entries / read_dir: could not read dir from {}\n cause: {}",
292 path.display(),
293 "No such file or directory (os error 2)"
294 )
295 );
296 }
297
298 #[test]
299 pub fn test_init_meta_dir() {
300 create_tests_folder();
301
302 init_meta_dir(&root_test_path()).unwrap();
303
304 let tests_dir: Vec<PathBuf> = get_dir_entries(&root_test_path()).unwrap();
305
306 let meta_path: PathBuf = root_test_path().join(PathBuf::from("_meta"));
307
308 assert!(&root_test_path().exists());
309 assert!(tests_dir.contains(&meta_path));
310
311 let meta_dir: Vec<PathBuf> = get_dir_entries(&meta_path).unwrap();
312
313 let cargo_toml_path: PathBuf = meta_path.join(PathBuf::from("Cargo.toml"));
314 let src_path: PathBuf = meta_path.join(PathBuf::from("src"));
315
316 assert!(&meta_path.exists());
317 assert!(meta_dir.contains(&cargo_toml_path));
318 assert!(meta_dir.contains(&src_path));
319
320 let src_dir: Vec<PathBuf> = get_dir_entries(&src_path).unwrap();
321 let src_main_path: PathBuf = src_path.join(PathBuf::from("main.rs"));
322 let src_lib_path: PathBuf = src_path.join(PathBuf::from("lib.rs"));
323 let schemas_path: PathBuf = src_path.join(PathBuf::from("schemas"));
324 let migrations_path: PathBuf = src_path.join(PathBuf::from("migrations"));
325 let seeds_path: PathBuf = src_path.join(PathBuf::from("seeds"));
326
327 let main_content_result: &str = r#"extern crate couchdb_orm;
328extern crate serde;
329
330use couchdb_orm::actix_rt;
331use couchdb_orm::client::couchdb::CouchDBClient;
332use couchdb_orm::dialoguer::{
333 Select,
334 Confirm,
335 theme::ColorfulTheme
336};
337
338pub mod schemas;
339pub mod migrations;
340pub mod seeds;
341pub mod securities;
342
343// migrations
344
345
346// seeds
347
348
349// securities
350
351
352
353#[actix_rt::main]
354async fn main() -> Result<(), Box<dyn std::error::Error>> {
355 let migrations_available_databases: [&str;0] = [
356
357 ];
358let seeds_available_databases: [&str;0] = [
359
360 ];
361let securities_available_databases: [&str;0] = [
362
363 ];
364
365 let available_actions: [&str;5] = ["migrate", "seed", "secure", "backup", "restore"];
366
367 let arguments: Vec<String> = std::env::args().collect();
368
369 let action_to_perform: usize = if arguments.len() > 1 {
370 match available_actions.iter().position(|a| a == &arguments[1]) {
371 Some(i) => i,
372 None => {
373 Select::with_theme(&ColorfulTheme::default())
374 .with_prompt("Which action to perform:")
375 .items(&available_actions)
376 .interact()?
377 }
378 }
379 } else {
380 Select::with_theme(&ColorfulTheme::default())
381 .with_prompt("Which action to perform:")
382 .items(&available_actions)
383 .interact()?
384 };
385
386 match action_to_perform {
387 0 => {
388 let db_for_migration: usize = if arguments.len() > 2 {
389 match migrations_available_databases.iter().position(|a| a == &arguments[2]) {
390 Some(i) => i,
391 None => {
392 Select::with_theme(&ColorfulTheme::default())
393 .with_prompt("Which DB to perform migration ?:")
394 .items(&migrations_available_databases)
395 .interact()?
396 }
397 }
398 } else {
399 Select::with_theme(&ColorfulTheme::default())
400 .with_prompt("Which DB to perform migration ?:")
401 .items(&migrations_available_databases)
402 .interact()?
403 };
404
405 match db_for_migration {
406
407 _ => {
408 println!("{} database does not exist", db_for_migration);
409 }
410 }
411 }
412 1 => {
413 let db_for_seeds: usize = if arguments.len() > 2 {
414 match seeds_available_databases.iter().position(|a| a == &arguments[2]) {
415 Some(i) => i,
416 None => {
417 Select::with_theme(&ColorfulTheme::default())
418 .with_prompt("Which DB to perform seeds ?:")
419 .items(&seeds_available_databases)
420 .interact()?
421 }
422 }
423 } else {
424 Select::with_theme(&ColorfulTheme::default())
425 .with_prompt("Which DB to perform seeds ?:")
426 .items(&seeds_available_databases)
427 .interact()?
428 };
429 let db_name = seeds_available_databases[db_for_seeds];
430 let client = CouchDBClient::new(None);
431 let db_status: couchdb_orm::client::couchdb::responses::db_status::DbStatus =
432 client.get_db_status(db_name).await?;
433
434 if db_status.doc_count > 0 {
435 if Confirm::with_theme(&ColorfulTheme::default())
436 .with_prompt("Do you want to clear the database ?")
437 .interact()?
438 {
439 println!("ok let's clear it !");
440
441 client.delete_all_docs(&db_name)
442 .await?;
443 } else {
444 panic!("aborting");
445 }
446 }
447
448 match db_for_seeds {
449
450 _ => {
451 println!("{} database does not exist", db_for_seeds);
452 }
453 }
454 }
455 2 => {
456 let db_for_security: usize = if arguments.len() > 2 {
457 match securities_available_databases.iter().position(|a| a == &arguments[2]) {
458 Some(i) => i,
459 None => {
460 Select::with_theme(&ColorfulTheme::default())
461 .with_prompt("Which DB to create security ?:")
462 .items(&securities_available_databases)
463 .interact()?
464 }
465 }
466 } else {
467 Select::with_theme(&ColorfulTheme::default())
468 .with_prompt("Which DB to create security ?:")
469 .items(&securities_available_databases)
470 .interact()?
471 };
472
473 match db_for_security {
474
475 _ => {
476 println!("{} database does not exist", db_for_security);
477 }
478 }
479 }
480 3 => {
481 let db_for_backups: usize = if arguments.len() > 2 {
482 match seeds_available_databases.iter().position(|a| a == &arguments[2]) {
483 Some(i) => i,
484 None => {
485 Select::with_theme(&ColorfulTheme::default())
486 .with_prompt("Which DB to perform backups ?:")
487 .items(&seeds_available_databases)
488 .interact()?
489 }
490 }
491 } else {
492 Select::with_theme(&ColorfulTheme::default())
493 .with_prompt("Which DB to perform backups ?:")
494 .items(&seeds_available_databases)
495 .interact()?
496 };
497
498 match db_for_backups {
499
500 _ => {
501 println!("{} database does not exist", db_for_backups);
502 }
503 }
504 }
505 4 => {
506 let db_for_restore: usize = if arguments.len() > 2 {
507 match seeds_available_databases.iter().position(|a| a == &arguments[2]) {
508 Some(i) => i,
509 None => {
510 Select::with_theme(&ColorfulTheme::default())
511 .with_prompt("Which DB to perform restore?:")
512 .items(&seeds_available_databases)
513 .interact()?
514 }
515 }
516 } else {
517 Select::with_theme(&ColorfulTheme::default())
518 .with_prompt("Which DB to perform restore?:")
519 .items(&seeds_available_databases)
520 .interact()?
521 };
522 let db_name = seeds_available_databases[db_for_restore];
523 let client = CouchDBClient::new(None);
524 let db_status: couchdb_orm::client::couchdb::responses::db_status::DbStatus = client.get_db_status(db_name).await?;
525
526 if db_status.doc_count > 0 {
527 if Confirm::with_theme(&ColorfulTheme::default())
528 .with_prompt("Do you want to clear the database ?")
529 .interact()?
530 {
531 println!("ok let's clear it !");
532
533 client.delete_all_docs(&db_name)
534 .await?;
535 } else {
536 panic!("aborting");
537 }
538 }
539
540 match db_for_restore {
541
542 _ => {
543 println!("{} database does not exist", db_for_restore);
544 }
545 }
546 }
547 _ => {
548 println!("not an action")
549 }
550 }
551
552 Ok(())
553}"#;
554
555 assert!(&src_path.exists());
556 assert!(src_dir.contains(&src_main_path));
557 assert!(src_dir.contains(&src_lib_path));
558 assert!(src_dir.contains(&schemas_path));
559 assert!(src_dir.contains(&migrations_path));
560 assert!(src_dir.contains(&seeds_path));
561 assert_eq!(
562 fs::read_to_string(&src_main_path)
563 .unwrap()
564 .replace("\n", ""),
565 main_content_result.replace("\n", "")
566 );
567 assert_eq!(
568 fs::read_to_string(&src_lib_path).unwrap(),
569 include_str!("../../static/templates/lib.tpl")
570 );
571 }
572}