From cb5805a82959ed7843da72f2d69157221898b8a7 Mon Sep 17 00:00:00 2001 From: didier amyot Date: Fri, 23 Dec 2022 17:58:30 -0500 Subject: [PATCH] fuck off, I call that a day. --- Cargo.lock | 51 +++++++++++++++++ Cargo.toml | 3 +- migrations/create_tables_001.sql | 7 ++- src/db/_accounts.rs | 5 +- src/db/_cheques.rs | 2 - src/db/_external_id.rs | 3 - src/db/_securities.rs | 25 ++++---- src/db/_test_utils.rs | 6 +- src/event_processor.rs | 47 +++++++++++++++ src/main.rs | 98 +++++++++++++++++++++++++++----- src/services/_transactions.rs | 39 +++++++------ src/util.rs | 29 +--------- 12 files changed, 229 insertions(+), 86 deletions(-) create mode 100644 src/event_processor.rs diff --git a/Cargo.lock b/Cargo.lock index 446a052..666c50e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -164,6 +164,7 @@ dependencies = [ "serde_json", "sha2", "simple_logger", + "tempfile", ] [[package]] @@ -178,6 +179,15 @@ version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7360491ce676a36bf9bb3c56c1aa791658183a54d2744120f27285738d90465a" +[[package]] +name = "fastrand" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a407cfaa3385c4ae6b23e84623d48c2798d06e3e6a1878f7f59f17b3f86499" +dependencies = [ + "instant", +] + [[package]] name = "generic-array" version = "0.14.6" @@ -247,6 +257,15 @@ dependencies = [ "winapi", ] +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "itoa" version = "1.0.4" @@ -360,6 +379,15 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "redox_syscall" +version = "0.2.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a" +dependencies = [ + "bitflags", +] + [[package]] name = "regex" version = "1.7.0" @@ -377,6 +405,15 @@ version = "0.6.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848" +[[package]] +name = "remove_dir_all" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" +dependencies = [ + "winapi", +] + [[package]] name = "resource" version = "0.5.0" @@ -481,6 +518,20 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tempfile" +version = "3.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5cdb1ef4eaeeaddc8fbd371e5017057064af0911902ef36b39801f67cc6d79e4" +dependencies = [ + "cfg-if", + "fastrand", + "libc", + "redox_syscall", + "remove_dir_all", + "winapi", +] + [[package]] name = "termcolor" version = "1.1.3" diff --git a/Cargo.toml b/Cargo.toml index 5276d15..52a8b94 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,4 +18,5 @@ serde_json = "1.0" resource = "0.5.0" hex = "0.4.3" regex = "1.7.0" -lazy_static = "1.4.0" \ No newline at end of file +lazy_static = "1.4.0" +tempfile = "3.3.0" \ No newline at end of file diff --git a/migrations/create_tables_001.sql b/migrations/create_tables_001.sql index aad706a..401e0f3 100644 --- a/migrations/create_tables_001.sql +++ b/migrations/create_tables_001.sql @@ -65,15 +65,18 @@ CREATE TABLE IF NOT EXISTS cashed_cheques ( id INTEGER PRIMARY KEY AUTOINCREMENT, drawer_user_id INTEGER NOT NULL, - drawer_account_id INTEGER, + drawer_account_id INTEGER NOT NULL, cheque_number INTEGER NOT NULL, amount INTEGER NOT NULL, underlying_security INTEGER NOT NULL, + payee_user_id INTEGER NOT NULL, + payee_account_id INTEGER NOT NULL, creation_time DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (underlying_security) REFERENCES securities (id), FOREIGN KEY (drawer_user_id) REFERENCES users (id), FOREIGN KEY (drawer_account_id) REFERENCES accounts (id), + FOREIGN KEY (payee_user_id) REFERENCES users (id), + FOREIGN KEY (payee_account_id) REFERENCES accounts (id), CONSTRAINT double_spending UNIQUE (drawer_user_id, cheque_number) -- no double spending ); diff --git a/src/db/_accounts.rs b/src/db/_accounts.rs index fd26775..f729035 100644 --- a/src/db/_accounts.rs +++ b/src/db/_accounts.rs @@ -1,5 +1,4 @@ use rusqlite::{Connection, OptionalExtension, params, Result, Row, Transaction}; -use sha2::digest::typenum::op; use crate::db::_external_id::{AccountId, Id, UserId}; use crate::db::_securities::Security; @@ -156,9 +155,7 @@ pub fn add_to_account(tx: &Connection, mod test_accounts { use rusqlite::Connection; - use crate::db::_securities; use crate::db::_test_utils::setup_users_and_securities; - use crate::db::_users; use crate::migration; use super::*; @@ -243,7 +240,7 @@ mod test_accounts { fn test_is_owner() -> Result<()> { let (mut conn, users, secs) = setup(); let tx = &conn.transaction()?; - let accounts = create_all_accounts(tx, &users, &secs)?; + let _accounts = create_all_accounts(tx, &users, &secs)?; let user_1 = users.get(0).unwrap(); let user_2 = users.get(1).unwrap(); let sec = secs.get(0).unwrap(); diff --git a/src/db/_cheques.rs b/src/db/_cheques.rs index 0b60a5f..7f4d6f0 100644 --- a/src/db/_cheques.rs +++ b/src/db/_cheques.rs @@ -1,5 +1,3 @@ -use rusqlite::{Result, Transaction}; - use crate::db::_external_id::{AccountId, SecurityId, UserId}; pub type Drawer = (UserId, AccountId, SecurityId); diff --git a/src/db/_external_id.rs b/src/db/_external_id.rs index 2cb519b..b153a41 100644 --- a/src/db/_external_id.rs +++ b/src/db/_external_id.rs @@ -1,4 +1,3 @@ -use std::io::Read; use std::fmt; use regex::{Captures, Regex}; @@ -168,8 +167,6 @@ impl ExternalId { #[cfg(test)] pub mod test_external_ids { - use std::any::Any; - use super::*; #[test] diff --git a/src/db/_securities.rs b/src/db/_securities.rs index a34750e..8fe67db 100644 --- a/src/db/_securities.rs +++ b/src/db/_securities.rs @@ -1,7 +1,7 @@ -use rusqlite::{Connection, OptionalExtension, params, Result, Row, ToSql, Transaction}; +use rusqlite::{Connection, params, Result, Row, ToSql, Transaction}; use serde::Serialize; -use crate::db::_external_id::{Id, SecurityId, UserId}; +use crate::db::_external_id::{Id, SecurityId}; use crate::db::users::User; #[derive(Debug, Serialize, PartialEq)] @@ -62,8 +62,7 @@ pub struct NewSecurity { } pub fn create_security(tx: &Transaction, - sec: &NewSecurity, - owner: &UserId) -> Result { + sec: &NewSecurity) -> Result { let q = "INSERT INTO securities(name, symbol, security_type) VALUES(?1, ?2, ?3)"; @@ -118,7 +117,7 @@ mod test_securities { #[test] fn test_security_types() -> Result<(), rusqlite::Error> { - let (mut conn, users) = setup(); + let (conn, _users) = setup(); let q = "CREATE TABLE test_security_types( name TEXT, FOREIGN KEY (name) REFERENCES security_types (name) @@ -134,25 +133,23 @@ mod test_securities { #[test] fn test_create_security() -> Result<(), rusqlite::Error> { - let (mut conn, users) = setup(); - let owner = users.get(0).unwrap(); - let not_owner = users.get(1).unwrap(); - let sec = mock_currency_1(); + let (mut conn, _users) = setup(); + let new_sec = mock_currency_1(); let tx = conn.transaction()?; - let sec = create_security(&tx, &sec, &owner.id())?; + let sec = create_security(&tx, &new_sec)?; + assert_eq!(sec.symbol, new_sec.symbol); tx.commit() } #[test] fn test_list_security() -> Result<(), rusqlite::Error> { - let (mut conn, users) = setup(); - let user = users.get(0).unwrap(); + let (mut conn, _users) = setup(); assert_eq!(0, list_securities(&conn)?.len()); let sec = mock_currency_1(); let tx = conn.transaction()?; - create_security(&tx, &sec, &user.id())?; + create_security(&tx, &sec)?; tx.commit()?; let ret = list_securities(&conn)?; let expt = Security { @@ -171,7 +168,7 @@ mod test_securities { symbol: "B".to_string(), }; let tx = conn.transaction()?; - create_security(&tx, &sec, &user.id())?; + create_security(&tx, &sec)?; tx.commit()?; let ret = list_securities(&conn)?; assert_eq!(2, ret.len()); diff --git a/src/db/_test_utils.rs b/src/db/_test_utils.rs index 109f245..88133f0 100644 --- a/src/db/_test_utils.rs +++ b/src/db/_test_utils.rs @@ -29,9 +29,9 @@ pub fn setup_users(tx: &Transaction) -> Result> { return Ok(vec![user_1, user_2]); } -pub fn setup_securities(tx: &Transaction, owner: &UserId) -> Result> { - let sec_1 = _securities::create_security(tx, &mock_currency_1(), owner)?; - let sec_2 = _securities::create_security(tx, &mock_currency_2(), owner)?; +pub fn setup_securities(tx: &Transaction, _owner: &UserId) -> Result> { + let sec_1 = _securities::create_security(tx, &mock_currency_1())?; + let sec_2 = _securities::create_security(tx, &mock_currency_2())?; return Ok(vec![sec_1, sec_2]); } diff --git a/src/event_processor.rs b/src/event_processor.rs new file mode 100644 index 0000000..2b36d42 --- /dev/null +++ b/src/event_processor.rs @@ -0,0 +1,47 @@ +use std::borrow::Borrow; +use std::fs; +use std::fs::File; +use std::path::PathBuf; + +use rusqlite::Connection; + +use crate::util::{CustomResult, Error}; + +// struct + + +fn process_event_file(conn: &mut Connection, file: File) -> CustomResult<()> { + return Ok(()); +} + +pub fn process_event_files(conn: &mut Connection, paths: I) -> CustomResult<()> + where I: Iterator, + I::Item: Borrow { + for path in paths { + let file = File::open(path.borrow()).map_err(Error::IOError)?; + process_event_file(conn, file).unwrap(); + } + Ok(()) +} + + +mod test { + use std::fs::File; + + use tempfile::tempfile; + + use super::*; + + fn test_basic_logic(file: File) -> Result<(), Error> { + Ok(()) + } + + + #[test] + fn test_basic() -> CustomResult<()> { + let file = tempfile().unwrap(); + let mut conn = Connection::open_in_memory().unwrap(); + let ret = process_event_file(&mut conn, file); + ret + } +} \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 054f9f2..552ae7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,19 +1,23 @@ use std::path::PathBuf; use clap::{Arg, ArgAction, ArgMatches, Command, value_parser}; -use log::{debug, info}; +use clap::parser::ValuesRef; +use log::{debug, error, info}; use rusqlite::Connection; use serde::Serialize; use simple_logger::SimpleLogger; -use crate::migration::apply_migrations; use db::securities::list_securities; + use crate::db::users::{create_user, list_users}; +use crate::event_processor::process_event_files; +use crate::migration::apply_migrations; mod migration; mod db; mod services; mod util; +mod event_processor; #[derive(Debug, Serialize)] struct Output { @@ -33,6 +37,8 @@ trait CommandLineUserLogic { fn list_user(&mut self, conn: &Connection); fn list_securities(&mut self, conn: &Connection); + + fn process_events(&mut self, conn: &mut Connection, files: ValuesRef); } fn verbosity_arg() -> Arg { @@ -58,10 +64,10 @@ fn db_path_arg() -> Arg { .long("db-path") .value_name("FILE") .value_parser(value_parser!(PathBuf)) - .default_value( "ledger.sqlite"); + .default_value("ledger.sqlite"); } -fn get_db_path(m: & ArgMatches) -> & PathBuf{ +fn get_db_path(m: &ArgMatches) -> &PathBuf { return m.get_one::("db-path").unwrap(); } @@ -79,10 +85,28 @@ fn ls_user_command_line_parser() -> Command { .about("List all users."); } + +fn process_events_command_line_parser() -> Command { + Command::new("process") + .about("Process events one after the other. In the order received") + .arg_required_else_help(true) + .arg(clap::arg!( ... "Stuff to add") + .value_parser(value_parser!(PathBuf))) +} + fn user_command_line_parser() -> Command { return Command::new("users") + .about("Handy command about users.") .subcommand(new_user_command_line_parser()) - .subcommand(ls_user_command_line_parser()); + .subcommand(ls_user_command_line_parser()) + .arg_required_else_help(true); +} + +fn event_command_line_parser() -> Command { + return Command::new("events") + .about("Process and query events files") + .subcommand(process_events_command_line_parser()) + .arg_required_else_help(true); } fn ls_securities_command_line_parser() -> Command { @@ -93,15 +117,18 @@ fn ls_securities_command_line_parser() -> Command { fn securities_command_line_parser() -> Command { return Command::new("securities") - .subcommand(ls_securities_command_line_parser()); + .subcommand(ls_securities_command_line_parser()) + .arg_required_else_help(true); } fn command_line_parser() -> Command { return Command::new("prog") .arg(verbosity_arg()) .arg(db_path_arg()) + .subcommand(event_command_line_parser()) .subcommand(user_command_line_parser()) - .subcommand(securities_command_line_parser()); + .subcommand(securities_command_line_parser()) + .arg_required_else_help(true); } fn process_new_user_command_line(conn: &mut Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { @@ -134,6 +161,17 @@ fn process_list_securities_command_line(conn: &Connection, m: &ArgMatches, logic logic.list_securities(conn); } +fn process_process_events_command_line(conn: &mut Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { + debug!("Processing process event command line {:?}.", m); + let mut files = m.get_many::("PATH").unwrap(); + if files.any(|x| !x.exists()) { + error!("Some passed files do not exists. Existing right now."); + return; + } + + logic.process_events(conn, files); +} + fn process_securities_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { debug!("Processing securities command line {:?}.", m); match m.subcommand() { @@ -144,6 +182,16 @@ fn process_securities_command_line(conn: &Connection, m: &ArgMatches, logic: &mu } } +fn process_events_command_line(conn: &mut Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { + debug!("Processing events command line {:?}.", m); + match m.subcommand() { + Some(("process", process_events_match)) => { + process_process_events_command_line(conn, process_events_match, logic); + } + _ => unreachable!(), + } +} + fn process_command_line(conn: &mut Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { debug!("Processing command line {:?}.", m); match m.subcommand() { @@ -154,10 +202,13 @@ fn process_command_line(conn: &mut Connection, m: &ArgMatches, logic: &mut dyn C Some(("securities", securities_match)) => { process_securities_command_line(conn, securities_match, logic); } + Some(("events", events_match)) => { + process_events_command_line(conn, events_match, logic); + } _ => { - println!("Received unparsable command. {:?}", m.subcommand()); + println!("Received un parsable command. {:?}", m.subcommand()); unreachable!(); - }, + } } } @@ -169,6 +220,7 @@ mod cli_test { list_user_count: usize, create_user_count: usize, list_securities_count: usize, + process_events_count: usize, } impl TestCommandLineLogic { @@ -177,6 +229,7 @@ mod cli_test { list_user_count: 0, create_user_count: 0, list_securities_count: 0, + process_events_count: 0, }; } } @@ -193,6 +246,10 @@ mod cli_test { fn list_securities(&mut self, _conn: &Connection) { self.list_securities_count += 1; } + + fn process_events(&mut self, conn: &mut Connection, _files: ValuesRef) { + self.process_events_count += 1; + } } #[test] @@ -257,6 +314,16 @@ mod cli_test { process_command_line(&mut conn, &m, &mut logic); assert_eq!(logic.list_securities_count, 1); } + + #[test] + fn process_events() { + let cmd = command_line_parser(); + let m = cmd.get_matches_from(vec!["test", "events", "process", "/dev/null"]); + let mut logic = TestCommandLineLogic::new(); + let mut conn = Connection::open_in_memory().unwrap(); + process_command_line(&mut conn, &m, &mut logic); + assert_eq!(logic.process_events_count, 1); + } } struct ProCommandLineLogic {} @@ -270,7 +337,7 @@ impl ProCommandLineLogic { impl CommandLineUserLogic for ProCommandLineLogic { fn create_user(&mut self, conn: &mut Connection, name: &String, public_key: &String) { let tx = conn.transaction().unwrap(); - create_user(& tx, name, public_key).unwrap(); + create_user(&tx, name, public_key).unwrap(); tx.commit().unwrap(); } @@ -287,20 +354,25 @@ impl CommandLineUserLogic for ProCommandLineLogic { } } - fn list_securities(&mut self, conn: & Connection) { + 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); } }; } -} + fn process_events(&mut self, conn: &mut Connection, files: ValuesRef) { + process_event_files(conn, files); + + println!("fuck me"); + } +} fn get_connection(m: &ArgMatches) -> Connection { let path = get_db_path(&m); diff --git a/src/services/_transactions.rs b/src/services/_transactions.rs index 8ce661a..75b5cf9 100644 --- a/src/services/_transactions.rs +++ b/src/services/_transactions.rs @@ -1,17 +1,16 @@ -use rusqlite::{Connection, ffi, params, Params, Savepoint, Transaction}; +use rusqlite::{Connection, ffi, params, Savepoint, Transaction}; -use crate::db::{accounts, securities}; -use crate::db::accounts::{Account, get_account_by_id}; +use crate::db::accounts; +use crate::db::accounts::get_account_by_id; use crate::db::cheques::Cheque; use crate::db::external_id::AccountId; -use crate::db::securities::get_security_by_id; use crate::util::*; fn _move_securities(tx: &Savepoint, cheque: &Cheque, account_id: &AccountId) -> CustomResult<()> { let drawer_receipt = accounts::add_to_account(tx, &cheque.drawer_account_id, -cheque.amount)?; - let payee_receipt = accounts::add_to_account(tx, account_id, cheque.amount)?; + let _payee_receipt = accounts::add_to_account(tx, account_id, cheque.amount)?; if drawer_receipt.amount < 0 { let m = format!("Drawer of {:?} does not have sufficient amount in account.", cheque); return Err(Error::InvalidAmount(m)); @@ -21,20 +20,27 @@ fn _move_securities(tx: &Savepoint, fn _add_cheque_to_cashed_cheques_log(tx: &Connection, cheque: &Cheque, - account_id: &AccountId) -> Result<(), Error> { + payee_account_id: &AccountId) -> Result<(), Error> { let q = " INSERT INTO cashed_cheques( drawer_user_id, + drawer_account_id, + payee_user_id, + payee_account_id, cheque_number, amount, underlying_security ) - VALUES(?1, ?2, ?3, ?4) + VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7) "; - let ret = tx.execute(q, params![cheque.drawer_user_id, + let params = params![cheque.drawer_user_id, + cheque.drawer_account_id, + cheque.payee_user_id, + payee_account_id, cheque.cheque_number, cheque.amount, - cheque.security_id]).map_err(Error::SqlError); + cheque.security_id]; + let ret = tx.execute(q, params).map_err(Error::SqlError); match ret { Ok(_) => Ok(()), Err(Error::SqlError(rusqlite::Error::SqliteFailure(ffi::Error { code: _, extended_code: 2067 }, _))) => @@ -81,13 +87,12 @@ fn cash_cheque(tx: &mut Transaction, #[cfg(test)] mod test_caching_cheques { - use rusqlite::{Connection, params}; + use rusqlite::Connection; - use crate::db::{accounts, test_utils}; use crate::db::accounts::{create_checking_account, create_security_emitter_account}; use crate::db::cheques::{Drawer, Payee}; - use crate::db::external_id::{Id, SecurityId, UserId}; - use crate::db::users::User; + use crate::db::external_id::{Id, UserId}; + use crate::db::test_utils; use crate::migration; use super::*; @@ -138,7 +143,7 @@ mod test_caching_cheques { let drawer: Drawer = _create_drawer(payee, payee_account); let new_payee: Payee = (security_emitter.0, security_emitter.2); - return (conn, drawer, new_payee, reg_account.id()) + return (conn, drawer, new_payee, reg_account.id()); } @@ -267,7 +272,7 @@ mod test_caching_cheques { test_invalid_drawer(&mut conn, drawer, payee, payee_account) } - fn test_invalid_payee(conn: &mut Connection, security_emitter: Drawer, payee: Payee, payee_account: AccountId) -> CustomResult<()> { + fn test_invalid_payee(conn: &mut Connection, security_emitter: Drawer, payee: Payee) -> CustomResult<()> { let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter(); let mut tx = conn.transaction().unwrap(); let wrong_payee_account = &security_emitter.1; @@ -286,12 +291,12 @@ mod test_caching_cheques { #[test] fn test_invalid_payee_security_emitter_cheque() -> CustomResult<()> { let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter(); - test_invalid_payee(&mut conn, security_emitter, payee, payee_account) + test_invalid_payee(&mut conn, security_emitter, payee) } #[test] fn test_invalid_payee_regular_cheque() -> CustomResult<()> { let (mut conn, drawer, payee, payee_account) = setup_basic(); - test_invalid_payee(&mut conn, drawer, payee, payee_account) + test_invalid_payee(&mut conn, drawer, payee) } } \ No newline at end of file diff --git a/src/util.rs b/src/util.rs index 94f2b32..b84bdf0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,39 +1,14 @@ -use rusqlite::{Params, Row, Transaction}; +use std::io; #[derive(Debug)] pub enum Error { SqlError(rusqlite::Error), - DoubleBooking(String), InvalidDrawer(String), InvalidAmount(String), InvalidPayee(String), DoubleSpendingError(String), + IOError(io::Error), Testing(String), } pub type CustomResult = Result; - -pub struct TransactionHelper<'a> { - tx: &'a Transaction<'a>, -} - -impl TransactionHelper<'_> { - pub fn new<'a>(tx: &'a Transaction) -> TransactionHelper<'a> { - TransactionHelper { tx } - } - - pub fn execute(&self, sql: &str, params: P) -> CustomResult { - self.tx.execute(sql, params).map_err(Error::SqlError) - } - - pub fn query_row(&self, sql: &str, params: P, f: F) -> CustomResult - where - P: Params, - F: FnOnce(&Row<'_>) -> rusqlite::Result, { - self.tx.query_row(sql, params, f).map_err(Error::SqlError) - } - // - // pub fn commit(&self) -> CustomResult<()> { - // self.tx.commit().map_err(Error::SqlError) - // } -}