diff --git a/migrations/create_tables_001.sql b/migrations/create_tables_001.sql index 256f50b..aad706a 100644 --- a/migrations/create_tables_001.sql +++ b/migrations/create_tables_001.sql @@ -25,19 +25,6 @@ CREATE TABLE IF NOT EXISTS securities FOREIGN KEY (security_type) REFERENCES security_types (name) ); - -CREATE TABLE IF NOT EXISTS security_permissions -( - id INTEGER PRIMARY KEY AUTOINCREMENT, - security_id INTEGER NOT NULL, - user_id INTEGER NOT NULL, - creation_time DATETIME DEFAULT CURRENT_TIMESTAMP, - - FOREIGN KEY (security_id) REFERENCES securities (id), - FOREIGN KEY (user_id) REFERENCES users (id) - -); - CREATE TABLE IF NOT EXISTS account_types ( name TEXT PRIMARY KEY diff --git a/src/db/_accounts.rs b/src/db/_accounts.rs index ae71ad5..bc0a9ab 100644 --- a/src/db/_accounts.rs +++ b/src/db/_accounts.rs @@ -1,7 +1,7 @@ use rusqlite::{Connection, OptionalExtension, params, Result, Row, Transaction}; use sha2::digest::typenum::op; -use crate::db::_external_id::{Id, AccountId, UserId}; +use crate::db::_external_id::{AccountId, Id, UserId}; use crate::db::_securities::Security; use crate::db::_users::User; use crate::db::external_id::SecurityId; @@ -26,7 +26,7 @@ impl AccountType { pub struct Account { id: u32, amount: u32, - timestamp: i32, + timestamp: i64, security: Security, account_type: AccountType, } @@ -119,12 +119,39 @@ pub fn is_owner(tx: &Transaction, account_id: &AccountId, user_id: &UserId) -> R Ok(option.is_some()) } +pub struct Receipt { + account_id: AccountId, + timestamp: i64, + amount: i64, +} + +impl Receipt { + pub fn from_row(row: &Row) -> Result { + Ok(Receipt { + account_id: AccountId::from_id(row.get("id")?), + amount: row.get("amount")?, + timestamp: row.get("timestamp")?, + }) + } +} + +pub fn add_to_account(tx: &Transaction, account_id: &AccountId, amount: i64) -> Result { + let q = " + UPDATE accounts SET amount = amount + ?1, + timestamp = CURRENT_TIMESTAMP + WHERE id = ?2 + RETURNING id, amount, UNIXEPOCH(CURRENT_TIMESTAMP) as timestamp + "; + let params = params![amount, account_id]; + tx.query_row(q, params, Receipt::from_row) +} + #[cfg(test)] mod test_accounts { use rusqlite::Connection; - use crate::db::_test_utils::{setup_users_and_securities}; use crate::db::_securities; + use crate::db::_test_utils::setup_users_and_securities; use crate::db::_users; use crate::migration; @@ -222,5 +249,25 @@ mod test_accounts { Ok(()) } + + #[test] + fn test_add_to_account() -> Result<()> { + let (mut conn, users, secs) = setup(); + let tx = conn.transaction().unwrap(); + let sec = secs.get(0).unwrap().id(); + let user = users.get(0).unwrap().id(); + let acc = create_checking_account(&tx, &sec, &user)?; + let receipt = add_to_account(& tx, & acc.id(), 33)?; + assert_eq!(receipt.account_id, acc.id()); + assert_eq!(receipt.amount, 33); + tx.commit()?; + + let tx = conn.transaction().unwrap(); + let acc = get_account_by_id(&tx, & acc.id())?; + assert_eq!(acc.timestamp, receipt.timestamp); + assert_eq!(acc.amount, 33); + + Ok(()) + } } diff --git a/src/db/_external_id.rs b/src/db/_external_id.rs index 31cb56a..5a1a025 100644 --- a/src/db/_external_id.rs +++ b/src/db/_external_id.rs @@ -3,7 +3,7 @@ use std::fmt; use regex::{Captures, Regex}; -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] struct SafeId { encrypted_id: [u8; 4], signature: [u8; 4], @@ -102,13 +102,13 @@ macro_rules! impl_ids { } } -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] pub struct AccountId(SafeId); -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] pub struct UserId(SafeId); -#[derive(PartialEq, Debug)] +#[derive(PartialEq, Debug, Clone)] pub struct SecurityId(SafeId); impl_ids!(AccountId, "a"); diff --git a/src/db/_securities.rs b/src/db/_securities.rs index 3062e8e..74d5166 100644 --- a/src/db/_securities.rs +++ b/src/db/_securities.rs @@ -1,9 +1,9 @@ use rusqlite::{Connection, OptionalExtension, params, Result, Row, ToSql, Transaction}; use serde::Serialize; + use crate::db::_external_id::{Id, SecurityId, UserId}; use crate::db::users::User; - #[derive(Debug, Serialize, PartialEq)] pub enum SecurityType { Currency, @@ -71,24 +71,9 @@ pub fn create_security(tx: &Transaction, tx.execute(q, params)?; let new_sec_id = tx.last_insert_rowid() as i32; - let q = "INSERT INTO - security_permissions(security_id, user_id) - VALUES(?1, ?2)"; - tx.execute(q, params![new_sec_id, owner])?; - return get_security_by_id(tx, new_sec_id); } -pub fn is_owner(tx: &Transaction, sec_id: &SecurityId, user_id: &UserId) -> Result { - let q = "SELECT id - FROM security_permissions - WHERE security_id = ?1 AND user_id = ?2 - LIMIT 1"; - let ret: Option = tx.query_row(q, - params![sec_id, user_id], - |r| r.get(0)).optional()?; - return Ok(ret.is_some()); -} pub fn get_security_by_id(tx: &Transaction, id: i32) -> Result { let q = "SELECT id AS sec_id, security_type AS sec_security_type, symbol AS sec_symbol @@ -115,10 +100,12 @@ pub fn list_securities(conn: &Connection) -> Result> { #[cfg(test)] mod test { use rusqlite::{Connection, params}; - use super::*; + use crate::db::_test_utils::*; use crate::migration; + use super::*; + fn setup() -> (Connection, Vec) { let mut conn = Connection::open_in_memory().unwrap(); migration::apply_migrations(&mut conn).unwrap(); @@ -154,9 +141,6 @@ mod test { let tx = conn.transaction()?; let sec = create_security(&tx, &sec, &owner.id())?; - assert!(is_owner(&tx, &sec.id(), &owner.id())?); - assert!(!is_owner(&tx, &sec.id(), ¬_owner.id())?); - tx.commit() } diff --git a/src/db/mod.rs b/src/db/mod.rs index 7d28dbb..7ea5978 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -10,7 +10,7 @@ pub mod external_id { } pub mod securities { - pub use super::_securities::{get_security_by_id, list_securities, Security, is_owner}; + pub use super::_securities::{get_security_by_id, list_securities, Security}; } pub mod users { @@ -21,6 +21,7 @@ pub mod accounts { pub use super::_accounts::{Account, create_checking_account, create_security_emitter_account, + is_owner, get_account_by_id, list_accounts}; } diff --git a/src/services/_transactions.rs b/src/services/_transactions.rs index c80b442..5d2564a 100644 --- a/src/services/_transactions.rs +++ b/src/services/_transactions.rs @@ -1,9 +1,9 @@ use rusqlite::{ffi, params, Params, Savepoint, Transaction}; +use crate::db::{accounts, securities}; use crate::db::accounts::{Account, get_account_by_id}; use crate::db::cheques::SecurityEmitterCheque; use crate::db::external_id::AccountId; -use crate::db::securities; use crate::db::securities::get_security_by_id; use crate::util::*; @@ -44,21 +44,20 @@ fn _add_cheque_to_cashed_cheques_log(tx: &TransactionHelper, fn cash_cheque(tx: &Transaction, cheque: &SecurityEmitterCheque, account_id: &AccountId) -> CustomResult<()> { - // 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)? { + if !accounts::is_owner(&tx, &cheque.drawer_account_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. - + if !accounts::is_owner(&tx, account_id, &cheque.payee_user_id).map_err(Error::SqlError)? { + return Err(Error::InvalidPayee(format!("Trying to cash the cheque {:?} in \ + an account {:?} that is not owned by the payee.", cheque, account_id))); + } 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 + // create transaction loggetAllPurchaseHistory Ok(()) } @@ -66,9 +65,9 @@ fn cash_cheque(tx: &Transaction, #[cfg(test)] mod test_caching_cheques { use rusqlite::{Connection, params}; - use crate::db::accounts::{create_checking_account, create_security_emitter_account}; 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; @@ -161,4 +160,21 @@ mod test_caching_cheques { _ => Err(Error::Testing("Expected invalid drawer here.".to_string())) } } + + #[test] + fn test_invalid_payee() -> CustomResult<()> { + let (mut conn, drawer, payee, payee_account) = setup_basic(); + let mut tx = conn.transaction().unwrap(); + // let wrong_payee_account = create_checking_account(&tx, &sec.id(), &payee.id()).unwrap(); + let wrong_payee_account = &drawer.1; + + let cheque = SecurityEmitterCheque::new( + drawer.clone(), payee.clone(), 1000, 99); + + let ret = cash_cheque(&mut tx, &cheque, wrong_payee_account); + match ret { + Err(Error::InvalidPayee(_)) => Ok(()), + _ => Err(Error::Testing("Expected invalid payee here.".to_string())) + } + } } \ No newline at end of file diff --git a/src/util.rs b/src/util.rs index f11ff37..e1e8abd 100644 --- a/src/util.rs +++ b/src/util.rs @@ -4,6 +4,7 @@ use rusqlite::{Params, Transaction}; pub enum Error { SqlError(rusqlite::Error), InvalidDrawer(String), + InvalidPayee(String), DoubleSpendingError(String), Testing(String), }