Avec un cafe.
This commit is contained in:
1 parent
d78b4f1d90
commit
352bb498d4
7 files changed
+86
-50
No files matched your search
@@ -25,19 +25,6 @@ CREATE TABLE IF NOT EXISTS securities
|
|||||||
FOREIGN KEY (security_type) REFERENCES security_types (name)
|
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
|
CREATE TABLE IF NOT EXISTS account_types
|
||||||
(
|
(
|
||||||
name TEXT PRIMARY KEY
|
name TEXT PRIMARY KEY
|
||||||
|
|||||||
+50
-3
@@ -1,7 +1,7 @@
|
|||||||
use rusqlite::{Connection, OptionalExtension, params, Result, Row, Transaction};
|
use rusqlite::{Connection, OptionalExtension, params, Result, Row, Transaction};
|
||||||
use sha2::digest::typenum::op;
|
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::_securities::Security;
|
||||||
use crate::db::_users::User;
|
use crate::db::_users::User;
|
||||||
use crate::db::external_id::SecurityId;
|
use crate::db::external_id::SecurityId;
|
||||||
@@ -26,7 +26,7 @@ impl AccountType {
|
|||||||
pub struct Account {
|
pub struct Account {
|
||||||
id: u32,
|
id: u32,
|
||||||
amount: u32,
|
amount: u32,
|
||||||
timestamp: i32,
|
timestamp: i64,
|
||||||
security: Security,
|
security: Security,
|
||||||
account_type: AccountType,
|
account_type: AccountType,
|
||||||
}
|
}
|
||||||
@@ -119,12 +119,39 @@ pub fn is_owner(tx: &Transaction, account_id: &AccountId, user_id: &UserId) -> R
|
|||||||
Ok(option.is_some())
|
Ok(option.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct Receipt {
|
||||||
|
account_id: AccountId,
|
||||||
|
timestamp: i64,
|
||||||
|
amount: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Receipt {
|
||||||
|
pub fn from_row(row: &Row) -> Result<Receipt> {
|
||||||
|
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<Receipt> {
|
||||||
|
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)]
|
#[cfg(test)]
|
||||||
mod test_accounts {
|
mod test_accounts {
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
|
|
||||||
use crate::db::_test_utils::{setup_users_and_securities};
|
|
||||||
use crate::db::_securities;
|
use crate::db::_securities;
|
||||||
|
use crate::db::_test_utils::setup_users_and_securities;
|
||||||
use crate::db::_users;
|
use crate::db::_users;
|
||||||
use crate::migration;
|
use crate::migration;
|
||||||
|
|
||||||
@@ -222,5 +249,25 @@ mod test_accounts {
|
|||||||
|
|
||||||
Ok(())
|
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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3,7 +3,7 @@ use std::fmt;
|
|||||||
|
|
||||||
use regex::{Captures, Regex};
|
use regex::{Captures, Regex};
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
struct SafeId {
|
struct SafeId {
|
||||||
encrypted_id: [u8; 4],
|
encrypted_id: [u8; 4],
|
||||||
signature: [u8; 4],
|
signature: [u8; 4],
|
||||||
@@ -102,13 +102,13 @@ macro_rules! impl_ids {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
pub struct AccountId(SafeId);
|
pub struct AccountId(SafeId);
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
pub struct UserId(SafeId);
|
pub struct UserId(SafeId);
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
pub struct SecurityId(SafeId);
|
pub struct SecurityId(SafeId);
|
||||||
|
|
||||||
impl_ids!(AccountId, "a");
|
impl_ids!(AccountId, "a");
|
||||||
|
|||||||
+4
-20
@@ -1,9 +1,9 @@
|
|||||||
use rusqlite::{Connection, OptionalExtension, params, Result, Row, ToSql, Transaction};
|
use rusqlite::{Connection, OptionalExtension, params, Result, Row, ToSql, Transaction};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::db::_external_id::{Id, SecurityId, UserId};
|
use crate::db::_external_id::{Id, SecurityId, UserId};
|
||||||
use crate::db::users::User;
|
use crate::db::users::User;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, PartialEq)]
|
#[derive(Debug, Serialize, PartialEq)]
|
||||||
pub enum SecurityType {
|
pub enum SecurityType {
|
||||||
Currency,
|
Currency,
|
||||||
@@ -71,24 +71,9 @@ pub fn create_security(tx: &Transaction,
|
|||||||
tx.execute(q, params)?;
|
tx.execute(q, params)?;
|
||||||
let new_sec_id = tx.last_insert_rowid() as i32;
|
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);
|
return get_security_by_id(tx, new_sec_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn is_owner(tx: &Transaction, sec_id: &SecurityId, user_id: &UserId) -> Result<bool> {
|
|
||||||
let q = "SELECT id
|
|
||||||
FROM security_permissions
|
|
||||||
WHERE security_id = ?1 AND user_id = ?2
|
|
||||||
LIMIT 1";
|
|
||||||
let ret: Option<u32> = 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<Security> {
|
pub fn get_security_by_id(tx: &Transaction, id: i32) -> Result<Security> {
|
||||||
let q = "SELECT id AS sec_id, security_type AS sec_security_type, symbol AS sec_symbol
|
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<Vec<Security>> {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test {
|
mod test {
|
||||||
use rusqlite::{Connection, params};
|
use rusqlite::{Connection, params};
|
||||||
use super::*;
|
|
||||||
use crate::db::_test_utils::*;
|
use crate::db::_test_utils::*;
|
||||||
use crate::migration;
|
use crate::migration;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
fn setup() -> (Connection, Vec<User>) {
|
fn setup() -> (Connection, Vec<User>) {
|
||||||
let mut conn = Connection::open_in_memory().unwrap();
|
let mut conn = Connection::open_in_memory().unwrap();
|
||||||
migration::apply_migrations(&mut conn).unwrap();
|
migration::apply_migrations(&mut conn).unwrap();
|
||||||
@@ -154,9 +141,6 @@ mod test {
|
|||||||
let tx = conn.transaction()?;
|
let tx = conn.transaction()?;
|
||||||
let sec = create_security(&tx, &sec, &owner.id())?;
|
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()
|
tx.commit()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+2
-1
@@ -10,7 +10,7 @@ pub mod external_id {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod securities {
|
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 {
|
pub mod users {
|
||||||
@@ -21,6 +21,7 @@ pub mod accounts {
|
|||||||
pub use super::_accounts::{Account,
|
pub use super::_accounts::{Account,
|
||||||
create_checking_account,
|
create_checking_account,
|
||||||
create_security_emitter_account,
|
create_security_emitter_account,
|
||||||
|
is_owner,
|
||||||
get_account_by_id,
|
get_account_by_id,
|
||||||
list_accounts};
|
list_accounts};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use rusqlite::{ffi, params, Params, Savepoint, Transaction};
|
use rusqlite::{ffi, params, Params, Savepoint, Transaction};
|
||||||
|
|
||||||
|
use crate::db::{accounts, securities};
|
||||||
use crate::db::accounts::{Account, get_account_by_id};
|
use crate::db::accounts::{Account, get_account_by_id};
|
||||||
use crate::db::cheques::SecurityEmitterCheque;
|
use crate::db::cheques::SecurityEmitterCheque;
|
||||||
use crate::db::external_id::AccountId;
|
use crate::db::external_id::AccountId;
|
||||||
use crate::db::securities;
|
|
||||||
use crate::db::securities::get_security_by_id;
|
use crate::db::securities::get_security_by_id;
|
||||||
use crate::util::*;
|
use crate::util::*;
|
||||||
|
|
||||||
@@ -44,21 +44,20 @@ fn _add_cheque_to_cashed_cheques_log(tx: &TransactionHelper,
|
|||||||
fn cash_cheque(tx: &Transaction,
|
fn cash_cheque(tx: &Transaction,
|
||||||
cheque: &SecurityEmitterCheque,
|
cheque: &SecurityEmitterCheque,
|
||||||
account_id: &AccountId) -> CustomResult<()> {
|
account_id: &AccountId) -> CustomResult<()> {
|
||||||
// let mut savepoint: Savepoint = tx.savepoint_with_name("bib").map_err(Error::SqlError)?;
|
if !accounts::is_owner(&tx, &cheque.drawer_account_id, &cheque.drawer_user_id).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)));
|
return Err(Error::InvalidDrawer(format!("The drawer is the security owner: {:?}", cheque)));
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo:
|
if !accounts::is_owner(&tx, account_id, &cheque.payee_user_id).map_err(Error::SqlError)? {
|
||||||
// 1. payee is owner of the account.
|
return Err(Error::InvalidPayee(format!("Trying to cash the cheque {:?} in \
|
||||||
// 2. drawer is owner of the account.
|
an account {:?} that is not owned by the payee.", cheque, account_id)));
|
||||||
|
}
|
||||||
|
|
||||||
let myx = TransactionHelper::new(tx);
|
let myx = TransactionHelper::new(tx);
|
||||||
_add_securities_to_account(&myx, cheque, account_id)?;
|
_add_securities_to_account(&myx, cheque, account_id)?;
|
||||||
_add_cheque_to_cashed_cheques_log(&myx, cheque, account_id)?;
|
_add_cheque_to_cashed_cheques_log(&myx, cheque, account_id)?;
|
||||||
// savepoint.rollback().map_err(Error::SqlError)?;
|
// savepoint.rollback().map_err(Error::SqlError)?;
|
||||||
// create transaction log
|
// create transaction loggetAllPurchaseHistory
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -66,9 +65,9 @@ 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_checking_account, create_security_emitter_account};
|
|
||||||
|
|
||||||
use crate::db::{accounts, test_utils};
|
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::cheques::{Drawer, Payee};
|
||||||
use crate::db::external_id::{Id, SecurityId, UserId};
|
use crate::db::external_id::{Id, SecurityId, UserId};
|
||||||
use crate::db::users::User;
|
use crate::db::users::User;
|
||||||
@@ -161,4 +160,21 @@ mod test_caching_cheques {
|
|||||||
_ => Err(Error::Testing("Expected invalid drawer here.".to_string()))
|
_ => 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()))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -4,6 +4,7 @@ use rusqlite::{Params, Transaction};
|
|||||||
pub enum Error {
|
pub enum Error {
|
||||||
SqlError(rusqlite::Error),
|
SqlError(rusqlite::Error),
|
||||||
InvalidDrawer(String),
|
InvalidDrawer(String),
|
||||||
|
InvalidPayee(String),
|
||||||
DoubleSpendingError(String),
|
DoubleSpendingError(String),
|
||||||
Testing(String),
|
Testing(String),
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user