From d262f0d2f892a5f0fe718ae60e7c4d8ccafe8588 Mon Sep 17 00:00:00 2001 From: didier amyot Date: Tue, 20 Dec 2022 10:18:17 -0500 Subject: [PATCH] going somewhere. --- migrations/create_tables_001.sql | 28 ++++++++++- src/db/README.md | 19 ++++---- src/db/_accounts.rs | 82 ++++++++++++++++++++++++++++---- src/db/_test_utils.rs | 14 +++--- src/db/mod.rs | 2 +- src/services/_transactions.rs | 4 +- 6 files changed, 121 insertions(+), 28 deletions(-) diff --git a/migrations/create_tables_001.sql b/migrations/create_tables_001.sql index 2a202b4..e7d18b9 100644 --- a/migrations/create_tables_001.sql +++ b/migrations/create_tables_001.sql @@ -38,17 +38,41 @@ CREATE TABLE IF NOT EXISTS security_permissions ); +CREATE TABLE IF NOT EXISTS account_types +( + name TEXT PRIMARY KEY +) STRICT; + +INSERT INTO account_types(name) +VALUES ('checking'), + ('security_emitter'); + CREATE TABLE IF NOT EXISTS accounts ( id INTEGER PRIMARY KEY AUTOINCREMENT, security INTEGER NOT NULL, - owner 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 (owner) REFERENCES users (id), + FOREIGN KEY (account_type) REFERENCES account_types (name) +); + +CREATE TABLE IF NOT EXISTS account_user_permissions +( + id INTEGER PRIMARY KEY AUTOINCREMENT, + account_id INTEGER NOT NULL, + user_id INTEGER NOT NULL, + creation_time DATETIME DEFAULT CURRENT_TIMESTAMP, + + FOREIGN KEY (account_id) REFERENCES account (id), + FOREIGN KEY (user_id) REFERENCES users (id) + ); diff --git a/src/db/README.md b/src/db/README.md index 7ff2787..dc0e54c 100644 --- a/src/db/README.md +++ b/src/db/README.md @@ -2,12 +2,15 @@ ```mermaid erDiagram - users ||--o{ accounts : owner - users ||--o{ securities : owner - security_types ||--o{ securities: s - securities ||--o{ accounts: ps - securities ||--o{ security_permissions: i - users ||--o{ security_permissions: i - users ||--o{ cheques: ps - securities ||--o{ cheques: ps + security_types ||--o{ securities: enum + account_types ||--o{ accounts: enum + securities ||--o{ accounts: underlying_account_security + accounts ||--o{ account_user_permissions: i + users ||--o{ account_user_permissions: i + users ||--o{ cashed_cheques: drawer_user_id + users ||--o{ cashed_cheques: payee_account_id + securities ||--o{ cashed_cheques: security + accounts ||--o{ cashed_cheques: drawer_account_id + accounts ||--o{ cashed_cheques: payee_account_id + ``` \ No newline at end of file diff --git a/src/db/_accounts.rs b/src/db/_accounts.rs index dffb253..41add1d 100644 --- a/src/db/_accounts.rs +++ b/src/db/_accounts.rs @@ -6,11 +6,29 @@ 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 { @@ -24,6 +42,7 @@ impl Account { amount: row.get("acc_amount")?, timestamp: row.get("acc_timestamp")?, security: Security::from_row(row)?, + account_type: AccountType::from_row(row)?, }); } @@ -36,6 +55,7 @@ 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, @@ -56,6 +76,7 @@ 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, @@ -66,14 +87,26 @@ pub fn get_account_by_id(tx: &Transaction, id: &AccountId) -> Result { return tx.query_row(q, params![id], Account::from_row); } -pub fn create_account(tx: &Transaction, sec: &SecurityId, user: &UserId) -> Result { - let q = "INSERT INTO accounts(security, owner, amount, timestamp) - VALUES (?1, ?2, 0, CURRENT_TIMESTAMP)"; - tx.execute(q, (sec, user))?; +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]; @@ -83,7 +116,7 @@ pub fn is_owner(tx: &Transaction, account_id: &AccountId, user_id: &UserId) -> R } #[cfg(test)] -mod test { +mod test_accounts { use rusqlite::Connection; use crate::db::_test_utils::{setup_users_and_securities}; @@ -108,12 +141,46 @@ mod test { let mut ret: Vec = Vec::new(); for sec in secs.iter() { for user in users.iter() { - ret.push(create_account(&tx, &sec.id(), &user.id())?); + 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(); @@ -123,7 +190,6 @@ mod test { 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(()); } @@ -145,7 +211,7 @@ mod test { let user_2 = users.get(1).unwrap(); let sec = secs.get(0).unwrap(); - let acc = create_account(&tx, &sec.id(), &user_1.id())?; + 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())?); diff --git a/src/db/_test_utils.rs b/src/db/_test_utils.rs index e0327a4..109f245 100644 --- a/src/db/_test_utils.rs +++ b/src/db/_test_utils.rs @@ -1,7 +1,7 @@ use rusqlite::{Result, Transaction}; use crate::db::{_securities, _users}; -use crate::db::_accounts::create_account; +use crate::db::_accounts::create_checking_account; use crate::db::_external_id::UserId; use crate::db::_securities::{NewSecurity, Security, SecurityType}; use crate::db::_users::User; @@ -44,11 +44,11 @@ pub fn setup_users_and_securities(tx: &Transaction) -> Result<(Vec, Vec Result<(Vec, Vec, Vec)> { let (users, securities) = setup_users_and_securities(tx)?; - let acc1 = create_account(tx, - &securities.get(0).unwrap().id(), - &users.get(0).unwrap().id())?; - let acc2 = create_account(tx, - &securities.get(1).unwrap().id(), - &users.get(1).unwrap().id())?; + let acc1 = create_checking_account(tx, + &securities.get(0).unwrap().id(), + &users.get(0).unwrap().id())?; + let acc2 = create_checking_account(tx, + &securities.get(1).unwrap().id(), + &users.get(1).unwrap().id())?; Ok((users, securities, vec![acc1, acc2])) } \ No newline at end of file diff --git a/src/db/mod.rs b/src/db/mod.rs index 7b92974..9d4a78f 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -18,7 +18,7 @@ pub mod users { } pub mod accounts { - pub use super::_accounts::{Account, create_account, get_account_by_id, list_accounts}; + pub use super::_accounts::{Account, create_checking_account, get_account_by_id, list_accounts}; } pub mod cheques { diff --git a/src/services/_transactions.rs b/src/services/_transactions.rs index 51ea41a..b9b1d5c 100644 --- a/src/services/_transactions.rs +++ b/src/services/_transactions.rs @@ -64,7 +64,7 @@ fn cash_cheque(tx: &Transaction, #[cfg(test)] mod test_caching_cheques { use rusqlite::{Connection, params}; - use crate::db::accounts::create_account; + use crate::db::accounts::create_checking_account; use crate::db::{accounts, test_utils}; use crate::db::external_id::{Id, SecurityId, UserId}; @@ -86,7 +86,7 @@ mod test_caching_cheques { let sec = securities.get(0).unwrap(); let drawer = users.get(0).unwrap(); let payee = users.get(1).unwrap(); - let payee_account = create_account(&tx, &sec.id(), &payee.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())