Going somewhere.

This commit is contained in:
didier amyot committed 2022-12-13 09:29:27 -05:00
1 parent 20cc644bfb
commit 7a212d6d28
5 files changed
+200 -78

No files matched your search

+7 -3
View File
@@ -3,7 +3,7 @@ CREATE TABLE IF NOT EXISTS users
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL, name TEXT NOT NULL,
public_key TEXT NOT NULL, public_key TEXT NOT NULL,
foo INTEGER creation_time DATETIME DEFAULT CURRENT_TIMESTAMP
); );
CREATE TABLE IF NOT EXISTS security_types CREATE TABLE IF NOT EXISTS security_types
@@ -24,13 +24,17 @@ CREATE TABLE IF NOT EXISTS securities
FOREIGN KEY (security_type) REFERENCES security_types (name) FOREIGN KEY (security_type) REFERENCES security_types (name)
); );
CREATE TABLE IF NOT EXISTS account CREATE TABLE IF NOT EXISTS accounts
( (
id INTEGER PRIMARY KEY AUTOINCREMENT, id INTEGER PRIMARY KEY AUTOINCREMENT,
security INTEGER NOT NULL, security INTEGER NOT NULL,
owner INTEGER NOT NULL, owner INTEGER NOT NULL,
amount INTEGER NOT NULL,
timestamp DATETIME NOT NULL,
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (security) REFERENCES securities (id) FOREIGN KEY (security) REFERENCES securities (id),
FOREIGN KEY (owner) REFERENCES users (id)
); );
CREATE TABLE IF NOT EXISTS account_values CREATE TABLE IF NOT EXISTS account_values
+99 -6
View File
@@ -1,16 +1,109 @@
use rusqlite::Connection; use rusqlite::{Connection, params, Result, Row, Transaction};
use crate::securities::Security; use crate::securities::Security;
use crate::users::User; use crate::users::User;
struct Account<'a> { pub struct Account {
security: &'a Security, id: i64,
user: &'a User,
amount: i64, amount: i64,
time_stamp: i64 timestamp: i64,
} }
fn _account_from_row(row: &Row) -> Result<Account> {
return Ok(Account{
id: row.get(0)?,
amount: row.get(1)?,
timestamp: row.get(2)?,
});
}
pub fn list_accounts(conn: & Connection){ pub fn list_accounts<'a>(conn: &Connection) -> Result<Vec<Account>> {
let q = "SELECT id, amount, UNIXEPOCH(timestamp)
FROM accounts
ORDER BY id ASC";
let mut stmt = conn.prepare(q)?;
let ret: Vec<Account> = stmt
.query_map([], &_account_from_row)?
.filter(Result::is_ok)
.map(Result::unwrap)
.collect();
return Ok(ret);
}
pub fn get_account_by_id(tx: &Transaction, id: u32) -> Result<Account>{
let q = "SELECT id, amount, UNIXEPOCH(timestamp) FROM accounts WHERE id = ?1";
return tx.query_row(q, params![id], _account_from_row);
}
pub fn create_account(tx: &Transaction, sec: &Security, user: &User) -> Result<Account>{
let q = "INSERT INTO accounts(security, owner, amount, timestamp)
VALUES (?1, ?2, 0, CURRENT_TIMESTAMP)";
tx.execute(q, (sec.id, user.id))?;
return get_account_by_id(tx, tx.last_insert_rowid() as u32);
}
#[cfg(test)]
mod test {
use rusqlite::{Connection};
use crate::users;
use crate::securities;
use crate::migration;
use super::*;
fn setup_users(tx: &Transaction) -> Result<Vec<User>> {
let user_1 = users::create_user(tx, &"bernard".to_string(), &"123".to_string())?;
let user_2 = users::create_user(tx, &"sam".to_string(), &"abc".to_string())?;
return Ok(vec![user_1, user_2]);
}
fn setup_securities(tx: &Transaction) -> Result<Vec<Security>> {
let sec_1 = securities::create_security(tx, &securities::test_util::mock_currency_1())?;
let sec_2 = securities::create_security(tx, &securities::test_util::mock_currency_2())?;
return Ok(vec![sec_1, sec_2]);
}
fn setup() -> (Connection, Vec<User>, Vec<Security>) {
let mut conn = Connection::open_in_memory().unwrap();
migration::apply_migrations(&mut conn).unwrap();
let tx = conn.transaction().unwrap();
let users = setup_users(&tx).unwrap();
let securities = setup_securities(&tx).unwrap();
tx.commit().unwrap();
return (conn, users, securities);
}
fn create_all_accounts(tx: & Transaction, users: & Vec<User>, secs: & Vec<Security>) -> Result<()>{
for sec in secs.iter() {
for user in users.iter() {
create_account(&tx, sec, user)?;
}
}
return Ok(());
}
#[test]
fn test_happy_path() -> Result<()>{
let (mut conn, users, secs) = setup();
let tx = conn.transaction()?;
create_all_accounts(& tx, &users, &secs)?;
tx.commit()?;
let accs = list_accounts(& conn)?;
assert_eq!(4, accs.len());
assert_eq!(1, accs.get(0).unwrap().id);
assert_eq!(4, accs.get(3).unwrap().id);
return Ok(());
}
#[test]
fn test_happy_path_no_commit() -> Result<()>{
let (mut conn, users, secs) = setup();
create_all_accounts(& conn.transaction()?, &users, &secs)?;
let accs = list_accounts(& conn)?;
assert_eq!(0, accs.len());
return Ok(());
}
} }
+17 -21
View File
@@ -27,7 +27,7 @@ impl<T> Output<T> {
} }
trait CommandLineUserLogic { trait CommandLineUserLogic {
fn create_user(&mut self, conn: &Connection, name: &String, public_key: &String); fn create_user(&mut self, conn: &mut Connection, name: &String, public_key: &String);
fn list_user(&mut self, conn: &Connection); fn list_user(&mut self, conn: &Connection);
@@ -103,7 +103,7 @@ fn command_line_parser() -> Command {
.subcommand(securities_command_line_parser()); .subcommand(securities_command_line_parser());
} }
fn process_new_user_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { fn process_new_user_command_line(conn: &mut Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) {
debug!("Processing new user command line {:?}.", m); debug!("Processing new user command line {:?}.", m);
let name = m.get_one::<String>("name").unwrap(); let name = m.get_one::<String>("name").unwrap();
let pk = m.get_one::<String>("public_key").unwrap(); let pk = m.get_one::<String>("public_key").unwrap();
@@ -115,7 +115,7 @@ fn process_list_user_command_line(conn: &Connection, m: &ArgMatches, logic: &mut
logic.list_user(conn); logic.list_user(conn);
} }
fn process_user_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { fn process_user_command_line(conn: &mut Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) {
debug!("Processing user command line {:?}.", m); debug!("Processing user command line {:?}.", m);
match m.subcommand() { match m.subcommand() {
Some(("new", new_user_match)) => { Some(("new", new_user_match)) => {
@@ -143,7 +143,7 @@ fn process_securities_command_line(conn: &Connection, m: &ArgMatches, logic: &mu
} }
} }
fn process_command_line(conn: &Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) { fn process_command_line(conn: &mut Connection, m: &ArgMatches, logic: &mut dyn CommandLineUserLogic) {
debug!("Processing command line {:?}.", m); debug!("Processing command line {:?}.", m);
match m.subcommand() { match m.subcommand() {
Some(("users", user_match)) => { Some(("users", user_match)) => {
@@ -181,7 +181,7 @@ mod test {
} }
impl CommandLineUserLogic for TestCommandLineLogic { impl CommandLineUserLogic for TestCommandLineLogic {
fn create_user(&mut self, _conn: &Connection, _name: &String, _public_key: &String) { fn create_user(&mut self, _conn: &mut Connection, _name: &String, _public_key: &String) {
self.create_user_count += 1; self.create_user_count += 1;
} }
@@ -232,8 +232,8 @@ mod test {
let cmd = command_line_parser(); let cmd = command_line_parser();
let m = cmd.get_matches_from(vec!["test", "users", "new", "--name", "paul", "--public-key", "123abc"]); let m = cmd.get_matches_from(vec!["test", "users", "new", "--name", "paul", "--public-key", "123abc"]);
let mut logic = TestCommandLineLogic::new(); let mut logic = TestCommandLineLogic::new();
let conn = Connection::open_in_memory().unwrap(); let mut conn = Connection::open_in_memory().unwrap();
process_command_line(&conn, &m, &mut logic); process_command_line(&mut conn, &m, &mut logic);
assert_eq!(logic.create_user_count, 1); assert_eq!(logic.create_user_count, 1);
} }
@@ -242,8 +242,8 @@ mod test {
let cmd = command_line_parser(); let cmd = command_line_parser();
let m = cmd.get_matches_from(vec!["test", "users", "ls"]); let m = cmd.get_matches_from(vec!["test", "users", "ls"]);
let mut logic = TestCommandLineLogic::new(); let mut logic = TestCommandLineLogic::new();
let conn = Connection::open_in_memory().unwrap(); let mut conn = Connection::open_in_memory().unwrap();
process_command_line(&conn, &m, &mut logic); process_command_line(&mut conn, &m, &mut logic);
assert_eq!(logic.list_user_count, 1); assert_eq!(logic.list_user_count, 1);
} }
@@ -252,8 +252,8 @@ mod test {
let cmd = command_line_parser(); let cmd = command_line_parser();
let m = cmd.get_matches_from(vec!["test", "securities", "ls"]); let m = cmd.get_matches_from(vec!["test", "securities", "ls"]);
let mut logic = TestCommandLineLogic::new(); let mut logic = TestCommandLineLogic::new();
let conn = Connection::open_in_memory().unwrap(); let mut conn = Connection::open_in_memory().unwrap();
process_command_line(&conn, &m, &mut logic); process_command_line(&mut conn, &m, &mut logic);
assert_eq!(logic.list_securities_count, 1); assert_eq!(logic.list_securities_count, 1);
} }
} }
@@ -267,21 +267,17 @@ impl ProCommandLineLogic {
} }
impl CommandLineUserLogic for ProCommandLineLogic { impl CommandLineUserLogic for ProCommandLineLogic {
fn create_user(&mut self, conn: &Connection, name: &String, public_key: &String) { fn create_user(&mut self, conn: &mut Connection, name: &String, public_key: &String) {
users::create_user(conn, name, public_key); let tx = conn.transaction().unwrap();
users::create_user(& tx, name, public_key);
tx.commit().unwrap();
} }
fn list_user(&mut self, conn: &Connection) { fn list_user(&mut self, conn: &Connection) {
let users = users::list_users(conn); let users = users::list_users(conn);
match users { match users {
Ok(u) => { Ok(u) => {
let r: Vec<& users::User> = u.iter() let out = Output::success(u);
.filter(|& x| x.is_ok())
.map(| x| x.as_ref().unwrap())
.collect();
let out = Output::success(r);
println!("{}", serde_json::to_string(&out).unwrap()); println!("{}", serde_json::to_string(&out).unwrap());
} }
Err(e) => { Err(e) => {
@@ -325,5 +321,5 @@ fn main() {
apply_migrations(&mut conn).unwrap(); apply_migrations(&mut conn).unwrap();
let mut logic = ProCommandLineLogic::new(); let mut logic = ProCommandLineLogic::new();
process_command_line(&conn, &m, &mut logic); process_command_line(&mut conn, &m, &mut logic);
} }
+54 -38
View File
@@ -1,7 +1,6 @@
use rusqlite::{Connection, Result, ToSql}; use rusqlite::{Connection, params, Result, Row, ToSql, Transaction};
use serde::Serialize; use serde::Serialize;
use crate::migration;
#[derive(Debug, Serialize, PartialEq)] #[derive(Debug, Serialize, PartialEq)]
pub enum SecurityType { pub enum SecurityType {
@@ -33,50 +32,80 @@ impl ToSql for SecurityType {
#[derive(Debug, Serialize, PartialEq)] #[derive(Debug, Serialize, PartialEq)]
pub struct Security { pub struct Security {
id: i32, pub id: i32,
security_type: SecurityType, security_type: SecurityType,
symbol: String, symbol: String,
} }
pub struct NewSecurity { pub struct NewSecurity {
security_type: SecurityType, security_type: SecurityType,
public_key: String,
symbol: String, symbol: String,
name: String, name: String,
} }
pub fn create_security(conn: &mut Connection, sec: &NewSecurity) -> Result<()> { pub fn create_security(tx: &Transaction, sec: &NewSecurity) -> Result<Security> {
let tx = conn.transaction()?;
let q = "INSERT INTO let q = "INSERT INTO
securities(name, symbol, security_type) securities(name, symbol, security_type)
VALUES(?1, ?2, ?3)"; VALUES(?1, ?2, ?3)";
let params = (&sec.name, &sec.symbol, &sec.security_type); let params = (&sec.name, &sec.symbol, &sec.security_type);
tx.execute(q, params)?; tx.execute(q, params)?;
return tx.commit(); return get_security_by_id(tx, tx.last_insert_rowid() as i32);
} }
pub fn list_securities(conn: & Connection) -> Result<Vec<Security>> { fn _security_from_row(row: &Row) -> Result<Security> {
let q = "SELECT id, security_type, symbol
FROM securities
ORDER BY creation_time DESC";
let mut stmt = conn.prepare(q)?;
let ret: Vec<Security> = stmt.query_map([], |row| {
Ok(Security { Ok(Security {
id: row.get(0)?, id: row.get(0)?,
security_type: SecurityType::from_string(row.get(1)?), security_type: SecurityType::from_string(row.get(1)?),
symbol: row.get(2)?, symbol: row.get(2)?,
}) })
}).unwrap().filter(|x| x.is_ok()).map(|x| x.unwrap()).collect(); }
pub fn get_security_by_id(tx: &Transaction, id: i32) -> Result<Security> {
let q = "SELECT id, security_type, symbol
FROM securities
WHERE id = ?1";
tx.query_row(q, params![id], _security_from_row)
}
pub fn list_securities(conn: &Connection) -> Result<Vec<Security>> {
let q = "SELECT id, security_type, symbol
FROM securities
ORDER BY id ASC";
let mut stmt = conn.prepare(q)?;
let ret: Vec<Security> = stmt
.query_map([], &_security_from_row)?
.filter(Result::is_ok)
.map(Result::unwrap)
.collect();
return Ok(ret); return Ok(ret);
} }
pub mod test_util {
use super::*;
pub fn mock_currency_1() -> NewSecurity {
return NewSecurity {
security_type: SecurityType::Currency,
symbol: "P".to_string(),
name: "piastre".to_string(),
};
}
pub fn mock_currency_2() -> NewSecurity {
return NewSecurity {
security_type: SecurityType::Bond,
symbol: "B".to_string(),
name: "bong".to_string(),
};
}
}
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use rusqlite::{Connection, params}; use rusqlite::{Connection, params};
use super::*; use super::*;
use super::test_util::*;
use crate::migration;
fn setup() -> Connection { fn setup() -> Connection {
let mut conn = Connection::open_in_memory().unwrap(); let mut conn = Connection::open_in_memory().unwrap();
@@ -84,24 +113,6 @@ mod test {
return conn; 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] #[test]
fn test_security_types() -> Result<(), rusqlite::Error> { fn test_security_types() -> Result<(), rusqlite::Error> {
let conn = setup(); let conn = setup();
@@ -122,8 +133,9 @@ mod test {
fn test_create_security() -> Result<(), rusqlite::Error> { fn test_create_security() -> Result<(), rusqlite::Error> {
let mut conn = setup(); let mut conn = setup();
let sec = mock_currency_1(); let sec = mock_currency_1();
create_security(&mut conn, &sec)?; let tx = conn.transaction()?;
return Ok(()); create_security(&tx, &sec)?;
return tx.commit();
} }
#[test] #[test]
@@ -132,7 +144,9 @@ mod test {
assert_eq!(0, list_securities(&conn)?.len()); assert_eq!(0, list_securities(&conn)?.len());
let sec = mock_currency_1(); let sec = mock_currency_1();
create_security(&mut conn, &sec)?; let tx = conn.transaction()?;
create_security(&tx, &sec)?;
tx.commit()?;
let ret = list_securities(&conn)?; let ret = list_securities(&conn)?;
let expt = Security { let expt = Security {
id: 1, id: 1,
@@ -149,7 +163,9 @@ mod test {
security_type: SecurityType::Bond, security_type: SecurityType::Bond,
symbol: "B".to_string(), symbol: "B".to_string(),
}; };
create_security(&mut conn, &sec)?; let tx = conn.transaction()?;
create_security(&tx, &sec)?;
tx.commit()?;
let ret = list_securities(&conn)?; let ret = list_securities(&conn)?;
assert_eq!(2, ret.len()); assert_eq!(2, ret.len());
assert_eq!(&expt, ret.get(1).unwrap()); assert_eq!(&expt, ret.get(1).unwrap());
+23 -10
View File
@@ -1,28 +1,41 @@
use rusqlite::{Connection, Result}; use rusqlite::{Connection, params, Result, Transaction, Row};
use serde::{Serialize}; use serde::{Serialize};
#[derive(Debug, Serialize)] #[derive(Debug, Serialize)]
pub struct User { pub struct User {
id: i32, pub id: i32,
name: String, name: String,
public_key: String, public_key: String,
} }
pub fn create_user(conn: & Connection, name: &String, public_key: &String){ pub fn create_user(tx: & Transaction, name: &String, public_key: &String) -> Result<User>{
conn.execute("INSERT INTO users (name, public_key) VALUES (?1, ?2)", tx.execute("INSERT INTO users (name, public_key) VALUES (?1, ?2)",
(name, public_key)).unwrap(); (name, public_key))?;
return get_user_by_id(tx, tx.last_insert_rowid() as i32);
} }
pub fn list_users(conn: & Connection) -> Result<Vec<Result<User>>>{ fn _user_from_row(row: &Row) -> Result<User>{
let mut stmt = conn.prepare("SELECT id, name, public_key FROM users")?;
let ret: Vec<Result<User>> = stmt.query_map([], |row| {
Ok(User { Ok(User {
id: row.get(0)?, id: row.get(0)?,
name: row.get(1)?, name: row.get(1)?,
public_key: row.get(2)?, public_key: row.get(2)?,
}) })
}).unwrap().collect(); }
fn get_user_by_id(tx: & Transaction, id: i32) -> Result<User>{
let q = "SELECT id, name, public_key
FROM users
WHERE id = ?1";
tx.query_row(q, params![id], _user_from_row)
}
pub fn list_users(conn: & Connection) -> Result<Vec<User>>{
let mut stmt = conn.prepare("SELECT id, name, public_key FROM users")?;
let ret: Vec<User> = stmt.query_map([], _user_from_row)?
.filter(Result::is_ok)
.map(Result::unwrap)
.collect();
return Ok(ret); return Ok(ret);
} }