diff --git a/Cargo.lock b/Cargo.lock index 605a485..26c7a85 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -212,6 +212,8 @@ dependencies = [ "home", "log", "rusqlite", + "serde", + "serde_json", "simple_logger", ] @@ -298,11 +300,42 @@ dependencies = [ "smallvec", ] +[[package]] +name = "ryu" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4501abdff3ae82a1c1b477a17252eb69cee9e66eb915c1abaa4f44d873df9f09" + [[package]] name = "serde" version = "1.0.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.147" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "serde_json" +version = "1.0.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "020ff22c755c2ed3f8cf162dbb41a7268d934702f3ed3631656ea597e08fc3db" +dependencies = [ + "itoa", + "ryu", + "serde", +] [[package]] name = "simple_logger" diff --git a/Cargo.toml b/Cargo.toml index 41665f7..5239c9b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,8 +7,10 @@ edition = "2021" [dependencies] rusqlite = { version = "0.28.0", features = ["bundled"] } +serde = { version = "1.0", features = ["derive"] } clap = { version = "4.0.26", features = ["derive"] } clap-verbosity-flag = "2.0.0" simple_logger = "4.0.0" log = "0.4.17" -home = "0.5.4" \ No newline at end of file +home = "0.5.4" +serde_json = "1.0" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index bc97f23..464c6bd 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,15 +1,38 @@ -mod migration; -mod users; - use std::any::Any; -use clap::{Command, Arg, ArgAction, ArgMatches, value_parser}; -use log::{debug, info}; -use simple_logger::SimpleLogger; use std::path::PathBuf; -use rusqlite::{Connection}; + +use clap::{Arg, ArgAction, ArgMatches, Command, value_parser}; +use serde::{Serialize}; + +use log::{debug, error, info}; +use rusqlite::Connection; +use simple_logger::SimpleLogger; use crate::migration::migrate; +mod migration; +mod users; + +#[derive(Debug, Serialize)] +struct Output { + status: String, + result: T, +} + +impl Output { + fn success(shit: T) -> Output { + return Output{status: "success".to_string(), result: shit}; + } + +} + +trait CommandLineUserLogic { + fn create_user(&mut self, conn: &Connection, name: &String, public_key: &String); + + fn list_user(&mut self, conn: &Connection); + + // fn list_securities(&mut self, conn: &Connection); +} fn verbosity_arg() -> Arg { return Arg::new("verbosity") @@ -18,7 +41,7 @@ fn verbosity_arg() -> Arg { .default_value("0"); } -fn get_verbosity(m: & ArgMatches) -> log::LevelFilter { +fn get_verbosity(m: &ArgMatches) -> log::LevelFilter { match m.get_one::("verbosity") { None => log::LevelFilter::Error, Some(1) => log::LevelFilter::Warn, @@ -41,10 +64,100 @@ fn get_db_path(m: & ArgMatches) -> & PathBuf{ return m.get_one::("db-path").unwrap(); } +fn new_user_command_line_parser() -> Command { + return Command::new("new") + .long_flag("new") + .about("Create new user.") + .arg(Arg::new("name").long("name").help("name of new user.").action(ArgAction::Set)) + .arg(Arg::new("public_key").long("public-key").help("public key of users.").action(ArgAction::Set)); +} + +fn ls_user_command_line_parser() -> Command { + return Command::new("ls") + .long_flag("ls") + .about("List all users."); +} + +fn user_command_line_parser() -> Command { + return Command::new("users") + .subcommand(new_user_command_line_parser()) + .subcommand(ls_user_command_line_parser()); +} + +fn command_line_parser() -> Command { + return Command::new("prog") + .arg(verbosity_arg()) + .arg(db_path_arg()) + .subcommand(user_command_line_parser()); +} + +fn process_new_user_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { + debug!("Processing new user command line {:?}.", m); + let name = m.get_one::("name").unwrap(); + let pk = m.get_one::("public_key").unwrap(); + logic.create_user(conn, name, pk); +} + +fn process_list_user_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { + debug!("Processing list users command line {:?}.", m); + logic.list_user(conn); +} + +fn process_user_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { + debug!("Processing user command line {:?}.", m); + match m.subcommand() { + Some(("new", new_user_match)) => { + process_new_user_command_line(conn, new_user_match, logic); + } + Some(("ls", list_user_match)) => { + process_list_user_command_line(conn, list_user_match, logic); + } + _ => unreachable!(), + } +} + +fn process_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { + debug!("Processing command line {:?}.", m); + match m.subcommand() { + Some(("users", user_match)) => { + process_user_command_line(conn, user_match, logic); + } + _ => { + println!("Received unparsable command. {:?}", m.subcommand()); + unreachable!(); + }, + } +} + #[cfg(test)] mod test { use super::*; + struct TestCommandLineLogic { + list_user_count: usize, + create_user_count: usize, + + } + + impl TestCommandLineLogic { + fn new() -> TestCommandLineLogic { + return TestCommandLineLogic { + list_user_count: 0, + create_user_count: 0, + }; + } + } + + impl CommandLineUserLogic for TestCommandLineLogic { + fn create_user(&mut self, conn: &Connection, name: &String, public_key: &String) { + self.create_user_count += 1; + } + + fn list_user(&mut self, conn: &Connection) { + self.list_user_count += 1; + } + } + #[test] fn verbosity() { { @@ -67,7 +180,7 @@ mod test { let cmd = Command::new("test").arg(db_path_arg()); let m = cmd.get_matches_from(vec!["test", "--db-path", "/foo/bar"]); let expt = PathBuf::from("/foo/bar"); - assert_eq!(get_db_path(&m), & expt); + assert_eq!(get_db_path(&m), &expt); } { @@ -76,78 +189,70 @@ mod test { let expt = PathBuf::from("ledger.sqlite"); assert_eq!(get_db_path(&m), &expt); } + } + #[test] + fn create_user() { + let cmd = command_line_parser(); + let m = cmd.get_matches_from(vec!["test", "users", "new", "--name", "paul", "--public-key", "123abc"]); + let mut logic = TestCommandLineLogic::new(); + let conn = Connection::open_in_memory().unwrap(); + process_command_line(&conn, &m, &mut logic); + assert_eq!(logic.create_user_count, 1); + } + + #[test] + fn list_users() { + let cmd = command_line_parser(); + let m = cmd.get_matches_from(vec!["test", "users", "ls"]); + let mut logic = TestCommandLineLogic::new(); + let conn = Connection::open_in_memory().unwrap(); + process_command_line(&conn, &m, &mut logic); + assert_eq!(logic.list_user_count, 1); } } -fn new_user_command_line_parser() -> Command { - return Command::new("new") - .long_flag("new") - .about("Create new user.") - .arg(Arg::new("name").help("name of new user.").action(ArgAction::Set)); -} +struct ProCommandLineLogic {} -fn ls_user_command_line_parser() -> Command { - return Command::new("list") - .long_flag("ls") - .about("List all users."); -} - -fn user_command_line_parser() -> Command { - return Command::new("users") - .subcommand(new_user_command_line_parser()) - .subcommand(ls_user_command_line_parser()); -} - - -fn command_line_parser() -> Command { - return Command::new("prog") - .arg(verbosity_arg()) - .arg(db_path_arg()) - .subcommand(user_command_line_parser()); -} - -fn process_new_user_command_line(conn: &Connection, m: &ArgMatches){ - debug!("Processing new user command line {:?}.", m); - -} - -fn process_list_user_command_line(conn: &Connection, m: &ArgMatches){ - debug!("Processing list users command line {:?}.", m); -} - -fn process_user_command_line(conn: &Connection, m: &ArgMatches){ - debug!("Processing user command line {:?}.", m); - match m.subcommand() { - Some(("new", new_user_match)) => { - process_new_user_command_line(conn, new_user_match); - } - Some(("list", list_user_match)) => { - process_list_user_command_line(conn, list_user_match); - } - _ => unreachable!(), - } - - -} - -fn process_command_line(conn: & Connection, m: & ArgMatches) { - debug!("Processing command line {:?}.", m); - match m.subcommand() { - Some(("users", user_match)) => { - process_user_command_line(conn, user_match); - } - _ => unreachable!(), +impl ProCommandLineLogic { + fn new() -> ProCommandLineLogic { + return ProCommandLineLogic {}; } } -fn get_connection(m: & ArgMatches) -> Connection { +impl CommandLineUserLogic for ProCommandLineLogic { + fn create_user(&mut self, conn: &Connection, name: &String, public_key: &String) { + users::create_user(conn, name, public_key); + } + + fn list_user(&mut self, conn: &Connection) { + let users = users::list_users(conn); + match users { + Ok(u) => { + let r: Vec<& users::User> = u.iter() + .filter(|& x| x.is_ok()) + .map(| x| x.as_ref().unwrap()) + .collect(); + + let out = Output::success(r); + + println!("{}", serde_json::to_string(& out).unwrap()); + } + Err(e) => { + println!("{:?}", e); + } + } + } +} + + +fn get_connection(m: &ArgMatches) -> Connection { let path = get_db_path(&m); info!("Sqlite database location is {:?}.", path); return Connection::open(path).unwrap(); } -fn set_logger(m: & ArgMatches) { +fn set_logger(m: &ArgMatches) { SimpleLogger::new().with_level(get_verbosity(&m)).init().unwrap(); } @@ -155,8 +260,9 @@ fn main() { let command = command_line_parser(); let m = command.get_matches(); - set_logger(& m); - let conn = get_connection(& m); + set_logger(&m); + let conn = get_connection(&m); migrate(&conn); - process_command_line(& conn, & m); + let mut logic = ProCommandLineLogic::new(); + process_command_line(&conn, &m, &mut logic); } \ No newline at end of file diff --git a/src/migration.rs b/src/migration.rs index 419369f..bfc133c 100644 --- a/src/migration.rs +++ b/src/migration.rs @@ -5,7 +5,7 @@ const MIGRATION_SCRIPT: &str = " CREATE TABLE IF NOT EXISTS users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, - key TEXT NOT NULL + public_key TEXT NOT NULL ); CREATE TABLE IF NOT EXISTS security_types ( diff --git a/src/users.rs b/src/users.rs index a52e9ef..5df85e8 100644 --- a/src/users.rs +++ b/src/users.rs @@ -1,5 +1,28 @@ -use rusqlite::Connection; +use rusqlite::{Connection, Result}; +use serde::{Serialize}; -fn create_user(conn: & Connection){ +#[derive(Debug, Serialize)] +pub struct User { + id: i32, + name: String, + public_key: String, +} + +pub fn create_user(conn: & Connection, name: &String, public_key: &String){ + conn.execute("INSERT INTO users (name, public_key) VALUES (?1, ?2)", + (name, public_key)).unwrap(); +} + +pub fn list_users(conn: & Connection) -> Result>>{ + + let mut stmt = conn.prepare("SELECT id, name, public_key FROM users")?; + let ret: Vec> = stmt.query_map([], |row| { + Ok(User { + id: row.get(0)?, + name: row.get(1)?, + public_key: row.get(2)?, + }) + }).unwrap().collect(); + return Ok(ret); } \ No newline at end of file