going somewhere.
This commit is contained in:
1 parent
9ccaeaf410
commit
d262f0d2f8
6 files changed
+121
-28
No files matched your search
@@ -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
|
CREATE TABLE IF NOT EXISTS accounts
|
||||||
(
|
(
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
security INTEGER NOT NULL,
|
security INTEGER NOT NULL,
|
||||||
owner INTEGER NOT NULL,
|
account_type TEXT NOT NULL,
|
||||||
|
|
||||||
|
owner INTEGER NOT NULL, -- todo: remove
|
||||||
amount INTEGER NOT NULL,
|
amount INTEGER NOT NULL,
|
||||||
timestamp DATETIME NOT NULL,
|
timestamp DATETIME NOT NULL,
|
||||||
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
creation_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
|
|
||||||
FOREIGN KEY (security) REFERENCES securities (id),
|
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)
|
||||||
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+11
-8
@@ -2,12 +2,15 @@
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
erDiagram
|
erDiagram
|
||||||
users ||--o{ accounts : owner
|
security_types ||--o{ securities: enum
|
||||||
users ||--o{ securities : owner
|
account_types ||--o{ accounts: enum
|
||||||
security_types ||--o{ securities: s
|
securities ||--o{ accounts: underlying_account_security
|
||||||
securities ||--o{ accounts: ps
|
accounts ||--o{ account_user_permissions: i
|
||||||
securities ||--o{ security_permissions: i
|
users ||--o{ account_user_permissions: i
|
||||||
users ||--o{ security_permissions: i
|
users ||--o{ cashed_cheques: drawer_user_id
|
||||||
users ||--o{ cheques: ps
|
users ||--o{ cashed_cheques: payee_account_id
|
||||||
securities ||--o{ cheques: ps
|
securities ||--o{ cashed_cheques: security
|
||||||
|
accounts ||--o{ cashed_cheques: drawer_account_id
|
||||||
|
accounts ||--o{ cashed_cheques: payee_account_id
|
||||||
|
|
||||||
```
|
```
|
||||||
+74
-8
@@ -6,11 +6,29 @@ use crate::db::_securities::Security;
|
|||||||
use crate::db::_users::User;
|
use crate::db::_users::User;
|
||||||
use crate::db::external_id::SecurityId;
|
use crate::db::external_id::SecurityId;
|
||||||
|
|
||||||
|
#[derive(PartialEq, Debug)]
|
||||||
|
pub enum AccountType {
|
||||||
|
Checking,
|
||||||
|
SecurityEmitter,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountType {
|
||||||
|
pub fn from_row(row: &Row) -> Result<AccountType> {
|
||||||
|
let s: String = row.get("acc_account_type")?;
|
||||||
|
if s == "security_emitter" {
|
||||||
|
Ok(AccountType::SecurityEmitter)
|
||||||
|
} else {
|
||||||
|
Ok(AccountType::Checking)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct Account {
|
pub struct Account {
|
||||||
id: u32,
|
id: u32,
|
||||||
amount: u32,
|
amount: u32,
|
||||||
timestamp: i32,
|
timestamp: i32,
|
||||||
security: Security,
|
security: Security,
|
||||||
|
account_type: AccountType,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Account {
|
impl Account {
|
||||||
@@ -24,6 +42,7 @@ impl Account {
|
|||||||
amount: row.get("acc_amount")?,
|
amount: row.get("acc_amount")?,
|
||||||
timestamp: row.get("acc_timestamp")?,
|
timestamp: row.get("acc_timestamp")?,
|
||||||
security: Security::from_row(row)?,
|
security: Security::from_row(row)?,
|
||||||
|
account_type: AccountType::from_row(row)?,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -36,6 +55,7 @@ pub fn list_accounts<'a>(conn: &Connection) -> Result<Vec<Account>> {
|
|||||||
let q = "SELECT acc.id AS acc_id,
|
let q = "SELECT acc.id AS acc_id,
|
||||||
acc.amount AS acc_amount,
|
acc.amount AS acc_amount,
|
||||||
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
|
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
|
||||||
|
account_type AS acc_account_type,
|
||||||
|
|
||||||
sec.id AS sec_id,
|
sec.id AS sec_id,
|
||||||
sec.symbol AS sec_symbol,
|
sec.symbol AS sec_symbol,
|
||||||
@@ -56,6 +76,7 @@ pub fn get_account_by_id(tx: &Transaction, id: &AccountId) -> Result<Account> {
|
|||||||
let q = "SELECT acc.id AS acc_id,
|
let q = "SELECT acc.id AS acc_id,
|
||||||
acc.amount AS acc_amount,
|
acc.amount AS acc_amount,
|
||||||
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
|
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
|
||||||
|
account_type AS acc_account_type,
|
||||||
|
|
||||||
sec.id AS sec_id,
|
sec.id AS sec_id,
|
||||||
sec.symbol AS sec_symbol,
|
sec.symbol AS sec_symbol,
|
||||||
@@ -66,14 +87,26 @@ pub fn get_account_by_id(tx: &Transaction, id: &AccountId) -> Result<Account> {
|
|||||||
return tx.query_row(q, params![id], Account::from_row);
|
return tx.query_row(q, params![id], Account::from_row);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_account(tx: &Transaction, sec: &SecurityId, user: &UserId) -> Result<Account> {
|
fn create_account(tx: &Transaction, sec: &SecurityId, user: &UserId, account_type: AccountType) -> Result<Account> {
|
||||||
let q = "INSERT INTO accounts(security, owner, amount, timestamp)
|
let q = "INSERT INTO accounts(security, owner, amount, timestamp, account_type)
|
||||||
VALUES (?1, ?2, 0, CURRENT_TIMESTAMP)";
|
VALUES (?1, ?2, 0, CURRENT_TIMESTAMP, ?3)";
|
||||||
tx.execute(q, (sec, user))?;
|
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);
|
let id = AccountId::from_id(tx.last_insert_rowid() as u32);
|
||||||
return get_account_by_id(tx, &id);
|
return get_account_by_id(tx, &id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn create_checking_account(tx: &Transaction, sec: &SecurityId, user: &UserId) -> Result<Account> {
|
||||||
|
create_account(tx, sec, user, AccountType::Checking)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_security_emitter_account(tx: &Transaction, sec: &SecurityId, user: &UserId) -> Result<Account> {
|
||||||
|
create_account(tx, sec, user, AccountType::SecurityEmitter)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn is_owner(tx: &Transaction, account_id: &AccountId, user_id: &UserId) -> Result<bool> {
|
pub fn is_owner(tx: &Transaction, account_id: &AccountId, user_id: &UserId) -> Result<bool> {
|
||||||
let q = "SELECT id FROM accounts WHERE id = ?1 AND owner = ?2";
|
let q = "SELECT id FROM accounts WHERE id = ?1 AND owner = ?2";
|
||||||
let params = params![account_id, user_id];
|
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)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test_accounts {
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
|
||||||
use crate::db::_test_utils::{setup_users_and_securities};
|
use crate::db::_test_utils::{setup_users_and_securities};
|
||||||
@@ -108,12 +141,46 @@ mod test {
|
|||||||
let mut ret: Vec<Account> = Vec::new();
|
let mut ret: Vec<Account> = Vec::new();
|
||||||
for sec in secs.iter() {
|
for sec in secs.iter() {
|
||||||
for user in users.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);
|
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]
|
#[test]
|
||||||
fn test_happy_path() -> Result<()> {
|
fn test_happy_path() -> Result<()> {
|
||||||
let (mut conn, users, secs) = setup();
|
let (mut conn, users, secs) = setup();
|
||||||
@@ -123,7 +190,6 @@ mod test {
|
|||||||
let accs = list_accounts(&conn)?;
|
let accs = list_accounts(&conn)?;
|
||||||
assert_eq!(4, accs.len());
|
assert_eq!(4, accs.len());
|
||||||
assert_eq!(1, accs.get(0).unwrap().id);
|
assert_eq!(1, accs.get(0).unwrap().id);
|
||||||
assert_eq!(4, accs.get(3).unwrap().id);
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,7 +211,7 @@ mod test {
|
|||||||
let user_2 = users.get(1).unwrap();
|
let user_2 = users.get(1).unwrap();
|
||||||
let sec = secs.get(0).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_1.id())?);
|
||||||
assert!(!is_owner(tx, &acc.id(), &user_2.id())?);
|
assert!(!is_owner(tx, &acc.id(), &user_2.id())?);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
use rusqlite::{Result, Transaction};
|
use rusqlite::{Result, Transaction};
|
||||||
|
|
||||||
use crate::db::{_securities, _users};
|
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::_external_id::UserId;
|
||||||
use crate::db::_securities::{NewSecurity, Security, SecurityType};
|
use crate::db::_securities::{NewSecurity, Security, SecurityType};
|
||||||
use crate::db::_users::User;
|
use crate::db::_users::User;
|
||||||
@@ -44,11 +44,11 @@ pub fn setup_users_and_securities(tx: &Transaction) -> Result<(Vec<User>, Vec<Se
|
|||||||
pub fn setup_basic_environment(tx: &Transaction) -> Result<(Vec<User>, Vec<Security>, Vec<Account>)> {
|
pub fn setup_basic_environment(tx: &Transaction) -> Result<(Vec<User>, Vec<Security>, Vec<Account>)> {
|
||||||
let (users, securities) = setup_users_and_securities(tx)?;
|
let (users, securities) = setup_users_and_securities(tx)?;
|
||||||
|
|
||||||
let acc1 = create_account(tx,
|
let acc1 = create_checking_account(tx,
|
||||||
&securities.get(0).unwrap().id(),
|
&securities.get(0).unwrap().id(),
|
||||||
&users.get(0).unwrap().id())?;
|
&users.get(0).unwrap().id())?;
|
||||||
let acc2 = create_account(tx,
|
let acc2 = create_checking_account(tx,
|
||||||
&securities.get(1).unwrap().id(),
|
&securities.get(1).unwrap().id(),
|
||||||
&users.get(1).unwrap().id())?;
|
&users.get(1).unwrap().id())?;
|
||||||
Ok((users, securities, vec![acc1, acc2]))
|
Ok((users, securities, vec![acc1, acc2]))
|
||||||
}
|
}
|
||||||
+1
-1
@@ -18,7 +18,7 @@ pub mod users {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod accounts {
|
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 {
|
pub mod cheques {
|
||||||
|
|||||||
@@ -64,7 +64,7 @@ fn cash_cheque(tx: &Transaction,
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_caching_cheques {
|
mod test_caching_cheques {
|
||||||
use rusqlite::{Connection, params};
|
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::{accounts, test_utils};
|
||||||
use crate::db::external_id::{Id, SecurityId, UserId};
|
use crate::db::external_id::{Id, SecurityId, UserId};
|
||||||
@@ -86,7 +86,7 @@ mod test_caching_cheques {
|
|||||||
let sec = securities.get(0).unwrap();
|
let sec = securities.get(0).unwrap();
|
||||||
let drawer = users.get(0).unwrap();
|
let drawer = users.get(0).unwrap();
|
||||||
let payee = users.get(1).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();
|
tx.commit().unwrap();
|
||||||
(conn, drawer.id(), sec.id(), payee.id(), payee_account.id())
|
(conn, drawer.id(), sec.id(), payee.id(), payee_account.id())
|
||||||
|
|||||||
Reference in new issue
Block a user