diff --git a/migrations/create_tables_001.sql b/migrations/create_tables_001.sql index e40c7c2..75c1acd 100644 --- a/migrations/create_tables_001.sql +++ b/migrations/create_tables_001.sql @@ -16,10 +16,11 @@ VALUES ('currency'), ('bond'); CREATE TABLE IF NOT EXISTS securities ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - security_type TEXT NOT NULL, - name TEXT NOT NULL UNIQUE, - symbol TEXT NOT NULL UNIQUE, + id INTEGER PRIMARY KEY AUTOINCREMENT, + security_type TEXT NOT NULL, + name TEXT NOT NULL UNIQUE, + symbol TEXT NOT NULL UNIQUE, + creation_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (security_type) REFERENCES security_types (name) ); diff --git a/src/account.rs b/src/account.rs new file mode 100644 index 0000000..2fad85d --- /dev/null +++ b/src/account.rs @@ -0,0 +1,16 @@ +use rusqlite::Connection; +use crate::securities::Security; +use crate::users::User; + +struct Account<'a> { + security: &'a Security, + user: &'a User, + amount: i64, + time_stamp: i64 +} + + +pub fn list_accounts(conn: & Connection){ + +} + diff --git a/src/main.rs b/src/main.rs index 800086a..b8ba8df 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,17 +1,18 @@ use std::path::PathBuf; use clap::{Arg, ArgAction, ArgMatches, Command, value_parser}; -use serde::{Serialize}; - use log::{debug, info}; use rusqlite::Connection; +use serde::Serialize; use simple_logger::SimpleLogger; use crate::migration::apply_migrations; +use crate::securities::list_securities; mod migration; mod users; mod securities; +mod account; #[derive(Debug, Serialize)] struct Output { @@ -21,9 +22,8 @@ struct Output { impl Output { fn success(shit: T) -> Output { - return Output{status: "success".to_string(), result: shit}; + return Output { status: "success".to_string(), result: shit }; } - } trait CommandLineUserLogic { @@ -128,13 +128,11 @@ fn process_user_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn } } - fn process_list_securities_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { debug!("Processing list securities command line {:?}.", m); logic.list_securities(conn); } - fn process_securities_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { debug!("Processing securities command line {:?}.", m); match m.subcommand() { @@ -284,7 +282,7 @@ impl CommandLineUserLogic for ProCommandLineLogic { let out = Output::success(r); - println!("{}", serde_json::to_string(& out).unwrap()); + println!("{}", serde_json::to_string(&out).unwrap()); } Err(e) => { println!("{:?}", e); @@ -292,8 +290,17 @@ impl CommandLineUserLogic for ProCommandLineLogic { } } - fn list_securities(&mut self, _conn: &Connection) { - todo!() + fn list_securities(&mut self, conn: & Connection) { + let securities = list_securities(conn); + match securities { + Ok(s) => { + let out = Output::success(s); + println!("{}", serde_json::to_string(&out).unwrap()); + }, + Err(e) => { + println!("{:?}", e); + } + }; } } diff --git a/src/securities.rs b/src/securities.rs index f113d2b..e75bccb 100644 --- a/src/securities.rs +++ b/src/securities.rs @@ -1,17 +1,29 @@ -use rusqlite::{Connection, ToSql}; -use serde::{Serialize}; +use rusqlite::{Connection, Result, ToSql}; +use serde::Serialize; + use crate::migration; - -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, PartialEq)] pub enum SecurityType { Currency, Bond, } +impl SecurityType { + fn from_string(s: String) -> SecurityType { + if s == "currency".to_string() { + return SecurityType::Currency; + } + if s == "bond".to_string() { + return SecurityType::Bond; + } + panic!("Impossible to convert string {} to SecurityType.", s); + } +} + impl ToSql for SecurityType { #[inline] - fn to_sql(&self) -> rusqlite::Result> { + fn to_sql(&self) -> Result> { Ok(rusqlite::types::ToSqlOutput::from(match self { SecurityType::Currency => "currency", SecurityType::Bond => "bond", @@ -19,11 +31,10 @@ impl ToSql for SecurityType { } } -#[derive(Debug, Serialize)] +#[derive(Debug, Serialize, PartialEq)] pub struct Security { id: i32, security_type: SecurityType, - public_key: String, symbol: String, } @@ -34,22 +45,38 @@ pub struct NewSecurity { name: String, } -pub fn create_security(conn: &mut Connection, sec: &NewSecurity) -> Result<(), rusqlite::Error> { +pub fn create_security(conn: &mut Connection, sec: &NewSecurity) -> Result<()> { let tx = conn.transaction()?; let q = "INSERT INTO securities(name, symbol, security_type) VALUES(?1, ?2, ?3)"; let params = (&sec.name, &sec.symbol, &sec.security_type); tx.execute(q, params)?; - return Ok(()); + return tx.commit(); +} + +pub fn list_securities(conn: & Connection) -> Result> { + let q = "SELECT id, security_type, symbol + FROM securities + ORDER BY creation_time DESC"; + let mut stmt = conn.prepare(q)?; + + let ret: Vec = stmt.query_map([], |row| { + Ok(Security { + id: row.get(0)?, + security_type: SecurityType::from_string(row.get(1)?), + symbol: row.get(2)?, + }) + }).unwrap().filter(|x| x.is_ok()).map(|x| x.unwrap()).collect(); + return Ok(ret); } #[cfg(test)] mod test { - use super::*; use rusqlite::{Connection, params}; + use super::*; fn setup() -> Connection { let mut conn = Connection::open_in_memory().unwrap(); @@ -57,6 +84,24 @@ mod test { return conn; } + fn mock_currency_1() -> NewSecurity { + return NewSecurity { + security_type: SecurityType::Currency, + public_key: "123".to_string(), + symbol: "P".to_string(), + name: "pisatre".to_string(), + }; + } + + fn mock_currency_2() -> NewSecurity { + return NewSecurity { + security_type: SecurityType::Bond, + public_key: "123".to_string(), + symbol: "B".to_string(), + name: "bong".to_string(), + }; + } + #[test] fn test_security_types() -> Result<(), rusqlite::Error> { let conn = setup(); @@ -76,13 +121,39 @@ mod test { #[test] fn test_create_security() -> Result<(), rusqlite::Error> { let mut conn = setup(); - let sec = NewSecurity { - security_type: SecurityType::Currency, - public_key: "123".to_string(), - symbol: "P".to_string(), - name: "pisatre".to_string(), - }; + let sec = mock_currency_1(); create_security(&mut conn, &sec)?; return Ok(()); } + + #[test] + fn test_list_security() -> Result<(), rusqlite::Error> { + let mut conn = setup(); + assert_eq!(0, list_securities(& conn)?.len()); + + let sec = mock_currency_1(); + create_security(&mut conn, &sec)?; + let ret = list_securities(& conn)?; + let expt = Security { + id: 1, + security_type: SecurityType::Currency, + symbol: "P".to_string(), + }; + assert_eq!(1, ret.len()); + assert_eq!(&expt, ret.get(0).unwrap()); + + + let sec = mock_currency_2(); + let expt = Security { + id: 2, + security_type: SecurityType::Bond, + symbol: "B".to_string(), + }; + create_security(&mut conn, &sec)?; + let ret = list_securities(& conn)?; + assert_eq!(2, ret.len()); + assert_eq!(&expt, ret.get(1).unwrap()); + + return Ok(()); + } } \ No newline at end of file