use rusqlite::{Connection, OptionalExtension, params, Result, Row, Transaction}; use sha2::digest::typenum::op; use crate::db::_external_id::{Id, AccountId, UserId}; use crate::db::_securities::Security; use crate::db::_users::User; use crate::db::external_id::SecurityId; #[derive(PartialEq, Debug)] pub enum AccountType { Checking, SecurityEmitter, } impl AccountType { pub fn from_row(row: &Row) -> Result { let s: String = row.get("acc_account_type")?; if s == "security_emitter" { Ok(AccountType::SecurityEmitter) } else { Ok(AccountType::Checking) } } } pub struct Account { id: u32, amount: u32, timestamp: i32, security: Security, account_type: AccountType, } impl Account { pub fn id(&self) -> AccountId { AccountId::from_id(self.id) } pub fn from_row(row: &Row) -> Result { return Ok(Account { id: row.get("acc_id")?, amount: row.get("acc_amount")?, timestamp: row.get("acc_timestamp")?, security: Security::from_row(row)?, account_type: AccountType::from_row(row)?, }); } pub fn raw_amount(&self) -> u32 { self.amount } } pub fn list_accounts<'a>(conn: &Connection) -> Result> { let q = "SELECT acc.id AS acc_id, acc.amount AS acc_amount, UNIXEPOCH(acc.timestamp) AS acc_timestamp, account_type AS acc_account_type, sec.id AS sec_id, sec.symbol AS sec_symbol, sec.security_type AS sec_security_type FROM accounts acc JOIN securities sec ON acc.security = sec.id ORDER BY acc.id ASC"; let mut stmt = conn.prepare(q)?; let ret: Vec = 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: &AccountId) -> Result { let q = "SELECT acc.id AS acc_id, acc.amount AS acc_amount, UNIXEPOCH(acc.timestamp) AS acc_timestamp, account_type AS acc_account_type, sec.id AS sec_id, sec.symbol AS sec_symbol, sec.security_type AS sec_security_type FROM accounts acc JOIN securities sec ON acc.security = sec.id WHERE acc.id = ?1"; return tx.query_row(q, params![id], Account::from_row); } fn create_account(tx: &Transaction, sec: &SecurityId, user: &UserId, account_type: AccountType) -> Result { let q = "INSERT INTO accounts(security, owner, amount, timestamp, account_type) VALUES (?1, ?2, 0, CURRENT_TIMESTAMP, ?3)"; let account_type = match account_type { AccountType::Checking => "checking", AccountType::SecurityEmitter => "security_emitter", }; tx.execute(q, (sec, user, account_type))?; let id = AccountId::from_id(tx.last_insert_rowid() as u32); return get_account_by_id(tx, &id); } pub fn create_checking_account(tx: &Transaction, sec: &SecurityId, user: &UserId) -> Result { create_account(tx, sec, user, AccountType::Checking) } pub fn create_security_emitter_account(tx: &Transaction, sec: &SecurityId, user: &UserId) -> Result { create_account(tx, sec, user, AccountType::SecurityEmitter) } pub fn is_owner(tx: &Transaction, account_id: &AccountId, user_id: &UserId) -> Result { let q = "SELECT id FROM accounts WHERE id = ?1 AND owner = ?2"; let params = params![account_id, user_id]; let ite = |row: &Row| row.get::(0); let option = tx.query_row(q, params, ite).optional()?; Ok(option.is_some()) } #[cfg(test)] mod test_accounts { use rusqlite::Connection; use crate::db::_test_utils::{setup_users_and_securities}; use crate::db::_securities; use crate::db::_users; use crate::migration; use super::*; fn setup() -> (Connection, Vec, Vec) { let mut conn = Connection::open_in_memory().unwrap(); migration::apply_migrations(&mut conn).unwrap(); let tx = conn.transaction().unwrap(); let (users, securities) = setup_users_and_securities(&tx).unwrap(); tx.commit().unwrap(); return (conn, users, securities); } fn create_all_accounts(tx: &Transaction, users: &Vec, secs: &Vec) -> Result> { let mut ret: Vec = Vec::new(); for sec in secs.iter() { for user in users.iter() { ret.push(create_checking_account(&tx, &sec.id(), &user.id())?); } } return Ok(ret); } #[test] fn test_simplest_checking_account() -> Result<()> { let (mut conn, users, secs) = setup(); let user = users.get(0).unwrap(); let not_owner = users.get(1).unwrap(); let sec = secs.get(0).unwrap(); let tx = conn.transaction()?; let acc = create_checking_account(&tx, &sec.id(), &user.id())?; assert_eq!(acc.account_type, AccountType::Checking); assert_eq!(0, acc.raw_amount()); assert!(is_owner(& tx, & acc.id(), &user.id())?); assert!(!is_owner(& tx, & acc.id(), ¬_owner.id())?); Ok(()) } #[test] fn test_simplest_security_emitter_account() -> Result<()> { let (mut conn, users, secs) = setup(); let user = users.get(0).unwrap(); let not_owner = users.get(1).unwrap(); let sec = secs.get(0).unwrap(); let tx = conn.transaction()?; let acc = create_security_emitter_account(&tx, &sec.id(), &user.id())?; assert_eq!(acc.account_type, AccountType::SecurityEmitter); assert_eq!(0, acc.raw_amount()); assert!(is_owner(& tx, & acc.id(), &user.id())?); assert!(!is_owner(& tx, & acc.id(), ¬_owner.id())?); 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); 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(()); } #[test] fn test_is_owner() -> Result<()> { let (mut conn, users, secs) = setup(); let tx = &conn.transaction()?; 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(); let acc = create_checking_account(&tx, &sec.id(), &user_1.id())?; assert!(is_owner(tx, &acc.id(), &user_1.id())?); assert!(!is_owner(tx, &acc.id(), &user_2.id())?); Ok(()) } }