diff --git a/migrations/create_tables_001.sql b/migrations/create_tables_001.sql index e7d18b9..256f50b 100644 --- a/migrations/create_tables_001.sql +++ b/migrations/create_tables_001.sql @@ -53,13 +53,11 @@ CREATE TABLE IF NOT EXISTS accounts security INTEGER NOT NULL, account_type TEXT NOT NULL, - owner INTEGER NOT NULL, -- todo: remove amount INTEGER NOT NULL, timestamp DATETIME NOT NULL, creation_time DATETIME DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (security) REFERENCES securities (id), - FOREIGN KEY (owner) REFERENCES users (id), FOREIGN KEY (account_type) REFERENCES account_types (name) ); @@ -70,7 +68,7 @@ CREATE TABLE IF NOT EXISTS account_user_permissions user_id INTEGER NOT NULL, creation_time DATETIME DEFAULT CURRENT_TIMESTAMP, - FOREIGN KEY (account_id) REFERENCES account (id), + FOREIGN KEY (account_id) REFERENCES accounts (id), FOREIGN KEY (user_id) REFERENCES users (id) ); diff --git a/src/db/_accounts.rs b/src/db/_accounts.rs index 41add1d..ae71ad5 100644 --- a/src/db/_accounts.rs +++ b/src/db/_accounts.rs @@ -88,14 +88,18 @@ pub fn get_account_by_id(tx: &Transaction, id: &AccountId) -> Result { } 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 q = "INSERT INTO accounts(security, amount, timestamp, account_type) + VALUES (?1, 0, CURRENT_TIMESTAMP, ?2)"; let account_type = match account_type { AccountType::Checking => "checking", AccountType::SecurityEmitter => "security_emitter", }; - tx.execute(q, (sec, user, account_type))?; + tx.execute(q, (sec, account_type))?; let id = AccountId::from_id(tx.last_insert_rowid() as u32); + + let q = "INSERT INTO account_user_permissions(user_id, account_id) + VALUES(?1, ?2)"; + tx.execute(q, params![user, id])?; return get_account_by_id(tx, &id); } @@ -108,7 +112,7 @@ pub fn create_security_emitter_account(tx: &Transaction, sec: &SecurityId, user: } 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 q = "SELECT id FROM account_user_permissions WHERE account_id = ?1 AND user_id = ?2"; let params = params![account_id, user_id]; let ite = |row: &Row| row.get::(0); let option = tx.query_row(q, params, ite).optional()?; @@ -159,8 +163,8 @@ mod test_accounts { 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())?); + assert!(is_owner(&tx, &acc.id(), &user.id())?); + assert!(!is_owner(&tx, &acc.id(), ¬_owner.id())?); Ok(()) } @@ -176,8 +180,8 @@ mod test_accounts { 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())?); + assert!(is_owner(&tx, &acc.id(), &user.id())?); + assert!(!is_owner(&tx, &acc.id(), ¬_owner.id())?); Ok(()) } diff --git a/src/db/_cheques.rs b/src/db/_cheques.rs index f15bf71..b2feaf0 100644 --- a/src/db/_cheques.rs +++ b/src/db/_cheques.rs @@ -2,9 +2,13 @@ use rusqlite::{Result, Transaction}; use crate::db::_external_id::{AccountId, SecurityId, UserId}; +pub type Drawer = (UserId, AccountId, SecurityId); +pub type Payee = (UserId, SecurityId); + #[derive(Debug)] pub struct SecurityEmitterCheque { pub drawer_user_id: UserId, + pub drawer_account_id: AccountId, pub security_id: SecurityId, pub payee_user_id: UserId, pub cheque_number: u32, @@ -12,15 +16,15 @@ pub struct SecurityEmitterCheque { } impl SecurityEmitterCheque { - pub fn new(drawer_user_id: UserId, - security_id: SecurityId, - payee_user_id: UserId, + pub fn new(drawer: Drawer, + payee: Payee, amount: u32, cheque_number: u32) -> SecurityEmitterCheque { SecurityEmitterCheque { - drawer_user_id, - security_id, - payee_user_id, + drawer_user_id: drawer.0, + drawer_account_id: drawer.1, + security_id: drawer.2, + payee_user_id: payee.0, cheque_number, amount, } diff --git a/src/db/mod.rs b/src/db/mod.rs index 9d4a78f..7d28dbb 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -18,11 +18,15 @@ pub mod users { } pub mod accounts { - pub use super::_accounts::{Account, create_checking_account, get_account_by_id, list_accounts}; + pub use super::_accounts::{Account, + create_checking_account, + create_security_emitter_account, + get_account_by_id, + list_accounts}; } pub mod cheques { - pub use super::_cheques::SecurityEmitterCheque; + pub use super::_cheques::{SecurityEmitterCheque, Drawer, Payee}; } pub mod test_utils { diff --git a/src/services/_transactions.rs b/src/services/_transactions.rs index b9b1d5c..c80b442 100644 --- a/src/services/_transactions.rs +++ b/src/services/_transactions.rs @@ -1,4 +1,4 @@ -use rusqlite::{ffi, params, Params, Transaction}; +use rusqlite::{ffi, params, Params, Savepoint, Transaction}; use crate::db::accounts::{Account, get_account_by_id}; use crate::db::cheques::SecurityEmitterCheque; @@ -44,18 +44,20 @@ fn _add_cheque_to_cashed_cheques_log(tx: &TransactionHelper, fn cash_cheque(tx: &Transaction, cheque: &SecurityEmitterCheque, account_id: &AccountId) -> CustomResult<()> { - - if !securities::is_owner(tx, &cheque.security_id, &cheque.drawer_user_id).map_err(Error::SqlError)? { + // let mut savepoint: Savepoint = tx.savepoint_with_name("bib").map_err(Error::SqlError)?; + if !securities::is_owner(&tx, &cheque.security_id, &cheque.drawer_user_id).map_err(Error::SqlError)? { return Err(Error::InvalidDrawer(format!("The drawer is the security owner: {:?}", cheque))); } // todo: // 1. payee is owner of the account. + // 2. drawer is owner of the account. let myx = TransactionHelper::new(tx); _add_securities_to_account(&myx, cheque, account_id)?; _add_cheque_to_cashed_cheques_log(&myx, cheque, account_id)?; + // savepoint.rollback().map_err(Error::SqlError)?; // create transaction log Ok(()) } @@ -64,9 +66,10 @@ fn cash_cheque(tx: &Transaction, #[cfg(test)] mod test_caching_cheques { use rusqlite::{Connection, params}; - use crate::db::accounts::create_checking_account; + use crate::db::accounts::{create_checking_account, create_security_emitter_account}; use crate::db::{accounts, test_utils}; + use crate::db::cheques::{Drawer, Payee}; use crate::db::external_id::{Id, SecurityId, UserId}; use crate::db::users::User; use crate::migration; @@ -79,34 +82,32 @@ mod test_caching_cheques { return conn; } - fn setup_basic() -> (Connection, UserId, SecurityId, UserId, AccountId) { + fn setup_basic() -> (Connection, Drawer, Payee, AccountId) { let mut conn = setup_conn(); let tx = conn.transaction().unwrap(); let (users, securities, accounts) = test_utils::setup_basic_environment(&tx).unwrap(); let sec = securities.get(0).unwrap(); let drawer = users.get(0).unwrap(); let payee = users.get(1).unwrap(); + let drawer_account = create_security_emitter_account(&tx, &sec.id(), &drawer.id()).unwrap(); let payee_account = create_checking_account(&tx, &sec.id(), &payee.id()).unwrap(); tx.commit().unwrap(); - (conn, drawer.id(), sec.id(), payee.id(), payee_account.id()) + let drawer: Drawer = (drawer.id(), drawer_account.id(), sec.id()); + let payee: Payee = (payee.id(), sec.id()); + (conn, drawer, payee, payee_account.id()) } #[test] fn test_basic_happy_path() -> CustomResult<()> { - let (mut conn, drawer, sec, payee, payee_account) = setup_basic(); - let tx = conn.transaction().unwrap(); + let (mut conn, drawer, payee, payee_account) = setup_basic(); + let mut tx = conn.transaction().unwrap(); let cheque = SecurityEmitterCheque::new( - drawer, - sec, - payee, - 1000, - 99, - ); + drawer, payee, 1000, 99); - cash_cheque(&tx, &cheque, &payee_account)?; + cash_cheque(&mut tx, &cheque, &payee_account)?; tx.commit().unwrap(); let tx = conn.transaction().unwrap(); @@ -118,23 +119,22 @@ mod test_caching_cheques { #[test] fn test_double_spend() -> CustomResult<()> { - let (mut conn, drawer, sec, payee, payee_account) = setup_basic(); + let (mut conn, drawer, payee, payee_account) = setup_basic(); let amount: u32 = 1000; - let tx = conn.transaction().unwrap(); + let mut tx = conn.transaction().unwrap(); let cheque = SecurityEmitterCheque::new( drawer, - sec, payee, 1000, 99, ); - cash_cheque(&tx, &cheque, &payee_account)?; + cash_cheque(&mut tx, &cheque, &payee_account)?; tx.commit().unwrap(); - let tx = conn.transaction().unwrap(); + let mut tx = conn.transaction().unwrap(); - let ret = cash_cheque(&tx, &cheque, &payee_account); + let ret = cash_cheque(&mut tx, &cheque, &payee_account); match ret { Err(Error::DoubleSpendingError(_)) => Ok(()), _ => Err(Error::Testing("Expected double spending error here.".to_string())) @@ -143,19 +143,19 @@ mod test_caching_cheques { #[test] fn test_invalid_drawer() -> CustomResult<()> { - let (mut conn, drawer, sec, payee, payee_account) = setup_basic(); + let (mut conn, drawer, payee, payee_account) = setup_basic(); let amount: u32 = 1000; - let tx = conn.transaction().unwrap(); - let not_currency_owner = UserId::from_id(drawer.get_id() + 72623); + let mut tx = conn.transaction().unwrap(); + let not_currency_owner_user_id = UserId::from_id(drawer.0.get_id() + 72623); + let not_currency_owner: Drawer = (not_currency_owner_user_id, drawer.1, drawer.2); let cheque = SecurityEmitterCheque::new( not_currency_owner, - sec, payee, 1000, 99, ); - let ret = cash_cheque(&tx, &cheque, &payee_account); + let ret = cash_cheque(&mut tx, &cheque, &payee_account); match ret { Err(Error::InvalidDrawer(_)) => Ok(()), _ => Err(Error::Testing("Expected invalid drawer here.".to_string()))