couchdb_orm/utils/
mod.rs

1// Copyright (C) 2020-2023  OpenToolAdd
2//
3// This program is free software: you can redistribute it and/or modify
4// it under the terms of the GNU General Public License as published by
5// the Free Software Foundation, either version 3 of the License, or
6// (at your option) any later version.
7//
8// This program is distributed in the hope that it will be useful,
9// but WITHOUT ANY WARRANTY; without even the implied warranty of
10// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11// GNU General Public License for more details.
12//
13// You should have received a copy of the GNU General Public License
14// along with this program.  If not, see <http://www.gnu.org/licenses/>.
15// contact: contact@tool-add.com
16
17use 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    // println!("design_docs_db_list {:?}", design_docs_db_list);
35    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    // println!("db_design_docs {:?}", db_design_docs);
41    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    // println!("available_design_docs {:?}", available_design_docs);
76    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    // println!("db_design_docs_actions: {:?}", db_design_docs_actions);
81    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    // println!("db_design_docs_match: {:?}", db_design_docs_match);
123    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                    // capitalize first letter
134                    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                    // capitalize first letter
153                    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    // println!("design_docs_db_matches: {:?}", design_docs_db_matches);
167    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        // println!("_meta already exists in {:?}", rootdir);
234        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}