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
+213 -91

No files matched your search

+13 -9
View File
@@ -1,9 +1,9 @@
CREATE TABLE IF NOT EXISTS users
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
public_key TEXT NOT NULL,
foo INTEGER
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
public_key TEXT NOT NULL,
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP
);
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)
);
CREATE TABLE IF NOT EXISTS account
CREATE TABLE IF NOT EXISTS accounts
(
id INTEGER PRIMARY KEY AUTOINCREMENT,
security INTEGER NOT NULL,
owner INTEGER NOT NULL,
id INTEGER PRIMARY KEY AUTOINCREMENT,
security 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
+99 -6
View File
@@ -1,16 +1,109 @@
use rusqlite::Connection;
use rusqlite::{Connection, params, Result, Row, Transaction};
use crate::securities::Security;
use crate::users::User;
struct Account<'a> {
security: &'a Security,
user: &'a User,
pub struct Account {
id: 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 {
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);
@@ -103,7 +103,7 @@ fn command_line_parser() -> Command {
.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);
let name = m.get_one::<String>("name").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);
}
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);
match m.subcommand() {
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);
match m.subcommand() {
Some(("users", user_match)) => {
@@ -181,7 +181,7 @@ mod test {
}
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;
}
@@ -232,8 +232,8 @@ mod test {
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);
let mut conn = Connection::open_in_memory().unwrap();
process_command_line(&mut conn, &m, &mut logic);
assert_eq!(logic.create_user_count, 1);
}
@@ -242,8 +242,8 @@ mod test {
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);
let mut conn = Connection::open_in_memory().unwrap();
process_command_line(&mut conn, &m, &mut logic);
assert_eq!(logic.list_user_count, 1);
}
@@ -252,8 +252,8 @@ mod test {
let cmd = command_line_parser();
let m = cmd.get_matches_from(vec!["test", "securities", "ls"]);
let mut logic = TestCommandLineLogic::new();
let conn = Connection::open_in_memory().unwrap();
process_command_line(&conn, &m, &mut logic);
let mut conn = Connection::open_in_memory().unwrap();
process_command_line(&mut conn, &m, &mut logic);
assert_eq!(logic.list_securities_count, 1);
}
}
@@ -267,21 +267,17 @@ impl ProCommandLineLogic {
}
impl CommandLineUserLogic for ProCommandLineLogic {
fn create_user(&mut self, conn: &Connection, name: &String, public_key: &String) {
users::create_user(conn, name, public_key);
fn create_user(&mut self, conn: &mut Connection, name: &String, public_key: &String) {
let tx = conn.transaction().unwrap();
users::create_user(& tx, name, public_key);
tx.commit().unwrap();
}
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);
let out = Output::success(u);
println!("{}", serde_json::to_string(&out).unwrap());
}
Err(e) => {
@@ -325,5 +321,5 @@ fn main() {
apply_migrations(&mut conn).unwrap();
let mut logic = ProCommandLineLogic::new();
process_command_line(&conn, &m, &mut logic);
process_command_line(&mut conn, &m, &mut logic);
}
+58 -42
View File
@@ -1,7 +1,6 @@
use rusqlite::{Connection, Result, ToSql};
use rusqlite::{Connection, params, Result, Row, ToSql, Transaction};
use serde::Serialize;
use crate::migration;
#[derive(Debug, Serialize, PartialEq)]
pub enum SecurityType {
@@ -33,50 +32,80 @@ impl ToSql for SecurityType {
#[derive(Debug, Serialize, PartialEq)]
pub struct Security {
id: i32,
pub id: i32,
security_type: SecurityType,
symbol: String,
}
pub struct NewSecurity {
security_type: SecurityType,
public_key: String,
symbol: String,
name: String,
}
pub fn create_security(conn: &mut Connection, sec: &NewSecurity) -> Result<()> {
let tx = conn.transaction()?;
pub fn create_security(tx: &Transaction, sec: &NewSecurity) -> Result<Security> {
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 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> {
Ok(Security {
id: row.get(0)?,
security_type: SecurityType::from_string(row.get(1)?),
symbol: row.get(2)?,
})
}
pub fn get_security_by_id(tx: &Transaction, id: i32) -> Result<Security> {
let q = "SELECT id, security_type, symbol
FROM securities
ORDER BY creation_time DESC";
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([], |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();
let ret: Vec<Security> = stmt
.query_map([], &_security_from_row)?
.filter(Result::is_ok)
.map(Result::unwrap)
.collect();
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)]
mod test {
use rusqlite::{Connection, params};
use super::*;
use super::test_util::*;
use crate::migration;
fn setup() -> Connection {
let mut conn = Connection::open_in_memory().unwrap();
@@ -84,24 +113,6 @@ 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();
@@ -122,18 +133,21 @@ mod test {
fn test_create_security() -> Result<(), rusqlite::Error> {
let mut conn = setup();
let sec = mock_currency_1();
create_security(&mut conn, &sec)?;
return Ok(());
let tx = conn.transaction()?;
create_security(&tx, &sec)?;
return tx.commit();
}
#[test]
fn test_list_security() -> Result<(), rusqlite::Error> {
let mut conn = setup();
assert_eq!(0, list_securities(& conn)?.len());
assert_eq!(0, list_securities(&conn)?.len());
let sec = mock_currency_1();
create_security(&mut conn, &sec)?;
let ret = list_securities(& conn)?;
let tx = conn.transaction()?;
create_security(&tx, &sec)?;
tx.commit()?;
let ret = list_securities(&conn)?;
let expt = Security {
id: 1,
security_type: SecurityType::Currency,
@@ -149,8 +163,10 @@ mod test {
security_type: SecurityType::Bond,
symbol: "B".to_string(),
};
create_security(&mut conn, &sec)?;
let ret = list_securities(& conn)?;
let tx = conn.transaction()?;
create_security(&tx, &sec)?;
tx.commit()?;
let ret = list_securities(&conn)?;
assert_eq!(2, ret.len());
assert_eq!(&expt, ret.get(1).unwrap());
+26 -13
View File
@@ -1,28 +1,41 @@
use rusqlite::{Connection, Result};
use rusqlite::{Connection, params, Result, Transaction, Row};
use serde::{Serialize};
#[derive(Debug, Serialize)]
pub struct User {
id: i32,
pub 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 create_user(tx: & Transaction, name: &String, public_key: &String) -> Result<User>{
tx.execute("INSERT INTO users (name, public_key) VALUES (?1, ?2)",
(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>{
Ok(User {
id: row.get(0)?,
name: row.get(1)?,
public_key: row.get(2)?,
})
}
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<Result<User>> = stmt.query_map([], |row| {
Ok(User {
id: row.get(0)?,
name: row.get(1)?,
public_key: row.get(2)?,
})
}).unwrap().collect();
let ret: Vec<User> = stmt.query_map([], _user_from_row)?
.filter(Result::is_ok)
.map(Result::unwrap)
.collect();
return Ok(ret);
}