Linted code.
This commit is contained in:
1 parent
894f969d0f
commit
174d7e855a
16 files changed
+273
-175
No files matched your search
@@ -45,6 +45,7 @@
|
|||||||
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
RUST_SRC_PATH = rustPlatform.rustLibSrc;
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
export PROJECT_ROOT=${PROJECT_ROOT};
|
export PROJECT_ROOT=${PROJECT_ROOT};
|
||||||
|
export PATH=$PROJECT_ROOT/tools:$PATH;
|
||||||
'';
|
'';
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|||||||
+47
-18
@@ -1,5 +1,5 @@
|
|||||||
use rusqlite::{Connection, OptionalExtension, params, Result, Row, Transaction};
|
use fraction::Fraction;
|
||||||
use fraction::{Fraction};
|
use rusqlite::{params, Connection, OptionalExtension, Result, Row, Transaction};
|
||||||
|
|
||||||
use crate::db::_external_id::{AccountId, Id, UserId};
|
use crate::db::_external_id::{AccountId, Id, UserId};
|
||||||
use crate::db::_securities::Security;
|
use crate::db::_securities::Security;
|
||||||
@@ -100,7 +100,6 @@ impl AccountHistory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn list_accounts<'a>(conn: &Connection) -> CustomResult<Vec<Account>> {
|
pub fn list_accounts<'a>(conn: &Connection) -> CustomResult<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,
|
||||||
@@ -135,17 +134,25 @@ pub fn get_account_by_id(tx: &Connection, id: &AccountId) -> CustomResult<Accoun
|
|||||||
FROM accounts acc
|
FROM accounts acc
|
||||||
JOIN securities sec ON acc.security = sec.id
|
JOIN securities sec ON acc.security = sec.id
|
||||||
WHERE acc.id = ?1";
|
WHERE acc.id = ?1";
|
||||||
return tx.query_row(q, params![id], Account::from_row).map_err(Error::SqlError);
|
return tx
|
||||||
|
.query_row(q, params![id], Account::from_row)
|
||||||
|
.map_err(Error::SqlError);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_account(tx: &Connection, sec: &SecurityId, user: &UserId, account_type: AccountType) -> CustomResult<Account> {
|
fn create_account(
|
||||||
|
tx: &Connection,
|
||||||
|
sec: &SecurityId,
|
||||||
|
user: &UserId,
|
||||||
|
account_type: AccountType,
|
||||||
|
) -> CustomResult<Account> {
|
||||||
let q = "INSERT INTO accounts(security, version, amount, timestamp, account_type)
|
let q = "INSERT INTO accounts(security, version, amount, timestamp, account_type)
|
||||||
VALUES (?1, 0, 0, CURRENT_TIMESTAMP, ?2)";
|
VALUES (?1, 0, 0, CURRENT_TIMESTAMP, ?2)";
|
||||||
let account_type = match account_type {
|
let account_type = match account_type {
|
||||||
AccountType::Checking => "checking",
|
AccountType::Checking => "checking",
|
||||||
AccountType::SecurityEmitter => "security_emitter",
|
AccountType::SecurityEmitter => "security_emitter",
|
||||||
};
|
};
|
||||||
tx.execute(q, (sec, account_type)).map_err(Error::SqlError)?;
|
tx.execute(q, (sec, account_type))
|
||||||
|
.map_err(Error::SqlError)?;
|
||||||
let id = AccountId::from_id(tx.last_insert_rowid() as i64);
|
let id = AccountId::from_id(tx.last_insert_rowid() as i64);
|
||||||
|
|
||||||
let q = "INSERT INTO account_user_permissions(user_id, account_id)
|
let q = "INSERT INTO account_user_permissions(user_id, account_id)
|
||||||
@@ -165,7 +172,8 @@ fn get_account_history(tx: &Connection, id: &AccountId) -> CustomResult<AccountH
|
|||||||
WHERE account_id = ?1
|
WHERE account_id = ?1
|
||||||
ORDER BY account_version DESC";
|
ORDER BY account_version DESC";
|
||||||
let mut stmt = tx.prepare(q).map_err(Error::SqlError)?;
|
let mut stmt = tx.prepare(q).map_err(Error::SqlError)?;
|
||||||
let snapshots: Vec<AccountSnapshot> = stmt.query_map(params![id], AccountSnapshot::from_row)
|
let snapshots: Vec<AccountSnapshot> = stmt
|
||||||
|
.query_map(params![id], AccountSnapshot::from_row)
|
||||||
.map_err(Error::SqlError)?
|
.map_err(Error::SqlError)?
|
||||||
.filter(Result::is_ok)
|
.filter(Result::is_ok)
|
||||||
.map(Result::unwrap)
|
.map(Result::unwrap)
|
||||||
@@ -174,11 +182,19 @@ fn get_account_history(tx: &Connection, id: &AccountId) -> CustomResult<AccountH
|
|||||||
return AccountHistory::new(get_account_by_id(tx, id)?, snapshots);
|
return AccountHistory::new(get_account_by_id(tx, id)?, snapshots);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_checking_account(tx: &Connection, sec: &SecurityId, user: &UserId) -> CustomResult<Account> {
|
pub fn create_checking_account(
|
||||||
|
tx: &Connection,
|
||||||
|
sec: &SecurityId,
|
||||||
|
user: &UserId,
|
||||||
|
) -> CustomResult<Account> {
|
||||||
create_account(tx, sec, user, AccountType::Checking)
|
create_account(tx, sec, user, AccountType::Checking)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_security_emitter_account(tx: &Connection, sec: &SecurityId, user: &UserId) -> CustomResult<Account> {
|
pub fn create_security_emitter_account(
|
||||||
|
tx: &Connection,
|
||||||
|
sec: &SecurityId,
|
||||||
|
user: &UserId,
|
||||||
|
) -> CustomResult<Account> {
|
||||||
create_account(tx, sec, user, AccountType::SecurityEmitter)
|
create_account(tx, sec, user, AccountType::SecurityEmitter)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -186,7 +202,10 @@ pub fn is_owner(tx: &Connection, account_id: &AccountId, user_id: &UserId) -> Cu
|
|||||||
let q = "SELECT id FROM account_user_permissions WHERE account_id = ?1 AND user_id = ?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 params = params![account_id, user_id];
|
||||||
let ite = |row: &Row| row.get::<usize, i64>(0);
|
let ite = |row: &Row| row.get::<usize, i64>(0);
|
||||||
let option = tx.query_row(q, params, ite).optional().map_err(Error::SqlError)?;
|
let option = tx
|
||||||
|
.query_row(q, params, ite)
|
||||||
|
.optional()
|
||||||
|
.map_err(Error::SqlError)?;
|
||||||
Ok(option.is_some())
|
Ok(option.is_some())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,13 +237,16 @@ fn update_account_history(tx: &Connection, account_id: &AccountId) -> CustomResu
|
|||||||
FROM accounts
|
FROM accounts
|
||||||
WHERE id = ?1
|
WHERE id = ?1
|
||||||
";
|
";
|
||||||
tx.execute(q, params![account_id]).map_err(Error::SqlError)?;
|
tx.execute(q, params![account_id])
|
||||||
|
.map_err(Error::SqlError)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn add_to_account(tx: &Connection,
|
pub fn add_to_account(
|
||||||
|
tx: &Connection,
|
||||||
account_id: &AccountId,
|
account_id: &AccountId,
|
||||||
amount: i64) -> CustomResult<AccountChangeReceipt> {
|
amount: i64,
|
||||||
|
) -> CustomResult<AccountChangeReceipt> {
|
||||||
let q = "
|
let q = "
|
||||||
UPDATE accounts SET amount = CASE account_type
|
UPDATE accounts SET amount = CASE account_type
|
||||||
WHEN 'security_emitter'
|
WHEN 'security_emitter'
|
||||||
@@ -236,7 +258,9 @@ pub fn add_to_account(tx: &Connection,
|
|||||||
RETURNING id, version, amount, UNIXEPOCH(CURRENT_TIMESTAMP) as timestamp
|
RETURNING id, version, amount, UNIXEPOCH(CURRENT_TIMESTAMP) as timestamp
|
||||||
";
|
";
|
||||||
let params = params![amount, account_id];
|
let params = params![amount, account_id];
|
||||||
let ret = tx.query_row(q, params, AccountChangeReceipt::from_row).map_err(Error::SqlError);
|
let ret = tx
|
||||||
|
.query_row(q, params, AccountChangeReceipt::from_row)
|
||||||
|
.map_err(Error::SqlError);
|
||||||
update_account_history(tx, account_id)?;
|
update_account_history(tx, account_id)?;
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
@@ -261,7 +285,11 @@ mod test_accounts {
|
|||||||
return (conn, users, securities);
|
return (conn, users, securities);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn create_all_accounts(tx: &Transaction, users: &Vec<User>, secs: &Vec<Security>) -> CustomResult<Vec<Account>> {
|
fn create_all_accounts(
|
||||||
|
tx: &Transaction,
|
||||||
|
users: &Vec<User>,
|
||||||
|
secs: &Vec<Security>,
|
||||||
|
) -> CustomResult<Vec<Account>> {
|
||||||
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() {
|
||||||
@@ -361,8 +389,10 @@ mod test_accounts {
|
|||||||
let acc = get_account_by_id(&tx2, &acc.id()).unwrap();
|
let acc = get_account_by_id(&tx2, &acc.id()).unwrap();
|
||||||
assert_eq!(acc.timestamp, receipt.timestamp);
|
assert_eq!(acc.timestamp, receipt.timestamp);
|
||||||
assert_eq!(acc.amount, 33);
|
assert_eq!(acc.amount, 33);
|
||||||
assert_eq!(get_account_history(&tx2, &acc.id())?.average_amount(),
|
assert_eq!(
|
||||||
Fraction::from(0 + 33) / Fraction::from(2));
|
get_account_history(&tx2, &acc.id())?.average_amount(),
|
||||||
|
Fraction::from(0 + 33) / Fraction::from(2)
|
||||||
|
);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -390,4 +420,3 @@ mod test_accounts {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+1
-4
@@ -14,10 +14,7 @@ pub struct Cheque {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Cheque {
|
impl Cheque {
|
||||||
pub fn new(drawer: Drawer,
|
pub fn new(drawer: Drawer, payee: Payee, amount: i64, cheque_number: i64) -> Cheque {
|
||||||
payee: Payee,
|
|
||||||
amount: i64,
|
|
||||||
cheque_number: i64) -> Cheque {
|
|
||||||
Cheque {
|
Cheque {
|
||||||
drawer_user_id: drawer.0,
|
drawer_user_id: drawer.0,
|
||||||
drawer_account_id: drawer.1,
|
drawer_account_id: drawer.1,
|
||||||
|
|||||||
+50
-25
@@ -8,7 +8,6 @@ struct SafeId {
|
|||||||
signature: [u8; 8],
|
signature: [u8; 8],
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub trait Id {
|
pub trait Id {
|
||||||
type Output;
|
type Output;
|
||||||
fn from_regex_captures(caps: &Captures) -> Option<Self::Output>;
|
fn from_regex_captures(caps: &Captures) -> Option<Self::Output>;
|
||||||
@@ -19,7 +18,12 @@ pub trait Id {
|
|||||||
|
|
||||||
impl fmt::Display for SafeId {
|
impl fmt::Display for SafeId {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}-{}", hex::encode(self.encrypted_id), hex::encode(self.signature))
|
write!(
|
||||||
|
f,
|
||||||
|
"{}-{}",
|
||||||
|
hex::encode(self.encrypted_id),
|
||||||
|
hex::encode(self.signature)
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,11 +59,8 @@ impl Id for SafeId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
macro_rules! impl_ids {
|
macro_rules! impl_ids {
|
||||||
($T:ident, $prefix:literal) => {
|
($T:ident, $prefix:literal) => {
|
||||||
|
|
||||||
impl fmt::Display for $T {
|
impl fmt::Display for $T {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
write!(f, "{}-{}", $prefix, self.0)
|
write!(f, "{}-{}", $prefix, self.0)
|
||||||
@@ -85,7 +86,8 @@ macro_rules! impl_ids {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn from_string(s: &String) -> Option<$T> {
|
fn from_string(s: &String) -> Option<$T> {
|
||||||
let external_id_pattern = Regex::new(r"^(?P<type>\w)-(?P<id>\w+)-(?P<sign>\w+)$").unwrap();
|
let external_id_pattern =
|
||||||
|
Regex::new(r"^(?P<type>\w)-(?P<id>\w+)-(?P<sign>\w+)$").unwrap();
|
||||||
let caps = external_id_pattern.captures(&s)?;
|
let caps = external_id_pattern.captures(&s)?;
|
||||||
Self::from_regex_captures(&caps)
|
Self::from_regex_captures(&caps)
|
||||||
}
|
}
|
||||||
@@ -98,7 +100,7 @@ macro_rules! impl_ids {
|
|||||||
self.0.get_id()
|
self.0.get_id()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(PartialEq, Debug, Clone)]
|
#[derive(PartialEq, Debug, Clone)]
|
||||||
@@ -121,17 +123,15 @@ pub enum ExternalId {
|
|||||||
SecurityId(SecurityId),
|
SecurityId(SecurityId),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn deserialize_str(s: &str) -> Option<[u8; 8]> {
|
fn deserialize_str(s: &str) -> Option<[u8; 8]> {
|
||||||
let mut encrypted_id_bytes = [0u8; 8];
|
let mut encrypted_id_bytes = [0u8; 8];
|
||||||
let r = hex::decode_to_slice(s, &mut encrypted_id_bytes as &mut [u8]);
|
let r = hex::decode_to_slice(s, &mut encrypted_id_bytes as &mut [u8]);
|
||||||
return match r {
|
return match r {
|
||||||
Ok(_) => Some(encrypted_id_bytes),
|
Ok(_) => Some(encrypted_id_bytes),
|
||||||
Err(_) => None
|
Err(_) => None,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl ToString for ExternalId {
|
impl ToString for ExternalId {
|
||||||
fn to_string(&self) -> String {
|
fn to_string(&self) -> String {
|
||||||
match self {
|
match self {
|
||||||
@@ -142,17 +142,20 @@ impl ToString for ExternalId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
impl ExternalId {
|
impl ExternalId {
|
||||||
pub fn from_string(s: &String) -> Option<ExternalId> {
|
pub fn from_string(s: &String) -> Option<ExternalId> {
|
||||||
let pattern = Regex::new(r"^(?P<type>\w)-(?P<id>\w+)-(?P<sign>\w+)$").unwrap();
|
let pattern = Regex::new(r"^(?P<type>\w)-(?P<id>\w+)-(?P<sign>\w+)$").unwrap();
|
||||||
let caps = pattern.captures(&s)?;
|
let caps = pattern.captures(&s)?;
|
||||||
|
|
||||||
match caps.name("type")?.as_str() {
|
match caps.name("type")?.as_str() {
|
||||||
"a" => Some(ExternalId::AccountId(AccountId::from_regex_captures(&caps)?)),
|
"a" => Some(ExternalId::AccountId(AccountId::from_regex_captures(
|
||||||
|
&caps,
|
||||||
|
)?)),
|
||||||
"u" => Some(ExternalId::UserId(UserId::from_regex_captures(&caps)?)),
|
"u" => Some(ExternalId::UserId(UserId::from_regex_captures(&caps)?)),
|
||||||
"s" => Some(ExternalId::SecurityId(SecurityId::from_regex_captures(&caps)?)),
|
"s" => Some(ExternalId::SecurityId(SecurityId::from_regex_captures(
|
||||||
_ => None
|
&caps,
|
||||||
|
)?)),
|
||||||
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -172,16 +175,31 @@ pub mod test_external_ids {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_id_macro() {
|
fn test_id_macro() {
|
||||||
let id = SecurityId::from_id(33);
|
let id = SecurityId::from_id(33);
|
||||||
assert_eq!(id.to_string(), "s-0000000000000021-0000000000000021".to_string());
|
assert_eq!(
|
||||||
assert!(SecurityId::from_string(&"s-0000000000000021-0000000000000021".to_string()).is_some());
|
id.to_string(),
|
||||||
assert!(SecurityId::from_string(&"u-0000000000000021-0000000000000021".to_string()).is_none());
|
"s-0000000000000021-0000000000000021".to_string()
|
||||||
assert!(SecurityId::from_string(&"0000000000000021-0000000000000021".to_string()).is_none());
|
);
|
||||||
|
assert!(
|
||||||
|
SecurityId::from_string(&"s-0000000000000021-0000000000000021".to_string()).is_some()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
SecurityId::from_string(&"u-0000000000000021-0000000000000021".to_string()).is_none()
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
SecurityId::from_string(&"0000000000000021-0000000000000021".to_string()).is_none()
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn safe_id_happy_path_1() {
|
fn safe_id_happy_path_1() {
|
||||||
let sid = SafeId { encrypted_id: [1, 2, 3, 4, 5, 6, 7, 8], signature: [8, 7, 6, 5, 4, 3, 2, 1] };
|
let sid = SafeId {
|
||||||
assert_eq!("0102030405060708-0807060504030201".to_string(), sid.to_string());
|
encrypted_id: [1, 2, 3, 4, 5, 6, 7, 8],
|
||||||
|
signature: [8, 7, 6, 5, 4, 3, 2, 1],
|
||||||
|
};
|
||||||
|
assert_eq!(
|
||||||
|
"0102030405060708-0807060504030201".to_string(),
|
||||||
|
sid.to_string()
|
||||||
|
);
|
||||||
let deserialized_eid = SafeId::from_string(&sid.to_string()).unwrap();
|
let deserialized_eid = SafeId::from_string(&sid.to_string()).unwrap();
|
||||||
assert_eq!(sid, deserialized_eid);
|
assert_eq!(sid, deserialized_eid);
|
||||||
}
|
}
|
||||||
@@ -189,7 +207,10 @@ pub mod test_external_ids {
|
|||||||
#[test]
|
#[test]
|
||||||
fn safe_id_happy_path_2() {
|
fn safe_id_happy_path_2() {
|
||||||
let sid = SafeId::from_id(56);
|
let sid = SafeId::from_id(56);
|
||||||
assert_eq!("0000000000000038-0000000000000038".to_string(), sid.to_string());
|
assert_eq!(
|
||||||
|
"0000000000000038-0000000000000038".to_string(),
|
||||||
|
sid.to_string()
|
||||||
|
);
|
||||||
let deserialized_eid = SafeId::from_string(&sid.to_string()).unwrap();
|
let deserialized_eid = SafeId::from_string(&sid.to_string()).unwrap();
|
||||||
assert_eq!(sid, deserialized_eid);
|
assert_eq!(sid, deserialized_eid);
|
||||||
assert_eq!(56, deserialized_eid.get_id());
|
assert_eq!(56, deserialized_eid.get_id());
|
||||||
@@ -200,7 +221,11 @@ pub mod test_external_ids {
|
|||||||
let bad_strings = vec!["a", "a-b", "01020304-0708090É"];
|
let bad_strings = vec!["a", "a-b", "01020304-0708090É"];
|
||||||
for bad_string in bad_strings.iter() {
|
for bad_string in bad_strings.iter() {
|
||||||
let ret = SafeId::from_string(&bad_string.to_string());
|
let ret = SafeId::from_string(&bad_string.to_string());
|
||||||
assert!(ret.is_none(), "This should not be a valid safe id: {}", bad_string);
|
assert!(
|
||||||
|
ret.is_none(),
|
||||||
|
"This should not be a valid safe id: {}",
|
||||||
|
bad_string
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -210,21 +235,21 @@ pub mod test_external_ids {
|
|||||||
let eid = ExternalId::from_string(&format!("u-{:}", base)).unwrap();
|
let eid = ExternalId::from_string(&format!("u-{:}", base)).unwrap();
|
||||||
assert!(match eid {
|
assert!(match eid {
|
||||||
ExternalId::UserId(_) => true,
|
ExternalId::UserId(_) => true,
|
||||||
_ => false
|
_ => false,
|
||||||
});
|
});
|
||||||
assert_eq!(eid.to_string(), format!("u-{:}", base));
|
assert_eq!(eid.to_string(), format!("u-{:}", base));
|
||||||
|
|
||||||
let eid = ExternalId::from_string(&format!("a-{:}", base)).unwrap();
|
let eid = ExternalId::from_string(&format!("a-{:}", base)).unwrap();
|
||||||
assert!(match eid {
|
assert!(match eid {
|
||||||
ExternalId::AccountId(_) => true,
|
ExternalId::AccountId(_) => true,
|
||||||
_ => false
|
_ => false,
|
||||||
});
|
});
|
||||||
assert_eq!(eid.to_string(), format!("a-{:}", base));
|
assert_eq!(eid.to_string(), format!("a-{:}", base));
|
||||||
|
|
||||||
let eid = ExternalId::from_string(&format!("s-{:}", base)).unwrap();
|
let eid = ExternalId::from_string(&format!("s-{:}", base)).unwrap();
|
||||||
assert!(match eid {
|
assert!(match eid {
|
||||||
ExternalId::SecurityId(_) => true,
|
ExternalId::SecurityId(_) => true,
|
||||||
_ => false
|
_ => false,
|
||||||
});
|
});
|
||||||
assert_eq!(eid.to_string(), format!("s-{:}", base));
|
assert_eq!(eid.to_string(), format!("s-{:}", base));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,2 +1 @@
|
|||||||
|
|
||||||
|
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use rusqlite::{Connection, params, Result, Row, ToSql, Transaction};
|
use rusqlite::{params, Connection, Result, Row, ToSql, Transaction};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
use crate::db::_external_id::{Id, SecurityId};
|
use crate::db::_external_id::{Id, SecurityId};
|
||||||
@@ -61,8 +61,7 @@ pub struct NewSecurity {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_security(tx: &Transaction,
|
pub fn create_security(tx: &Transaction, sec: &NewSecurity) -> Result<Security> {
|
||||||
sec: &NewSecurity) -> Result<Security> {
|
|
||||||
let q = "INSERT INTO
|
let q = "INSERT INTO
|
||||||
securities(name, symbol, security_type)
|
securities(name, symbol, security_type)
|
||||||
VALUES(?1, ?2, ?3)";
|
VALUES(?1, ?2, ?3)";
|
||||||
@@ -73,7 +72,6 @@ pub fn create_security(tx: &Transaction,
|
|||||||
return get_security_by_id(tx, new_sec_id);
|
return get_security_by_id(tx, new_sec_id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
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
|
||||||
FROM securities
|
FROM securities
|
||||||
@@ -95,10 +93,9 @@ pub fn list_securities(conn: &Connection) -> Result<Vec<Security>> {
|
|||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_securities {
|
mod test_securities {
|
||||||
use rusqlite::{Connection, params};
|
use rusqlite::{params, Connection};
|
||||||
|
|
||||||
use crate::db::_test_utils::*;
|
use crate::db::_test_utils::*;
|
||||||
use crate::migration;
|
use crate::migration;
|
||||||
@@ -160,7 +157,6 @@ mod test_securities {
|
|||||||
assert_eq!(1, ret.len());
|
assert_eq!(1, ret.len());
|
||||||
assert_eq!(&expt, ret.get(0).unwrap());
|
assert_eq!(&expt, ret.get(0).unwrap());
|
||||||
|
|
||||||
|
|
||||||
let sec = mock_currency_2();
|
let sec = mock_currency_2();
|
||||||
let expt = Security {
|
let expt = Security {
|
||||||
id: 2,
|
id: 2,
|
||||||
|
|||||||
+12
-6
@@ -1,11 +1,11 @@
|
|||||||
use rusqlite::{Result, Transaction};
|
use rusqlite::{Result, Transaction};
|
||||||
|
|
||||||
use crate::db::{_securities, _users};
|
|
||||||
use crate::db::_accounts::create_checking_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;
|
||||||
use crate::db::accounts::Account;
|
use crate::db::accounts::Account;
|
||||||
|
use crate::db::{_securities, _users};
|
||||||
use crate::util::CustomResult;
|
use crate::util::CustomResult;
|
||||||
|
|
||||||
pub fn mock_currency_1() -> NewSecurity {
|
pub fn mock_currency_1() -> NewSecurity {
|
||||||
@@ -42,14 +42,20 @@ pub fn setup_users_and_securities(tx: &Transaction) -> Result<(Vec<User>, Vec<Se
|
|||||||
Ok((users, securities))
|
Ok((users, securities))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn setup_basic_environment(tx: &Transaction) -> CustomResult<(Vec<User>, Vec<Security>, Vec<Account>)> {
|
pub fn setup_basic_environment(
|
||||||
|
tx: &Transaction,
|
||||||
|
) -> CustomResult<(Vec<User>, Vec<Security>, Vec<Account>)> {
|
||||||
let (users, securities) = setup_users_and_securities(tx).unwrap();
|
let (users, securities) = setup_users_and_securities(tx).unwrap();
|
||||||
|
|
||||||
let acc1 = create_checking_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_checking_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]))
|
||||||
}
|
}
|
||||||
+9
-7
@@ -1,7 +1,6 @@
|
|||||||
use rusqlite::{Connection, params, Result, Transaction, Row};
|
use crate::db::_external_id::{Id, UserId};
|
||||||
use serde::{Serialize};
|
use rusqlite::{params, Connection, Result, Row, Transaction};
|
||||||
use crate::db::_external_id::{UserId, Id};
|
use serde::Serialize;
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize)]
|
#[derive(Debug, Serialize)]
|
||||||
pub struct User {
|
pub struct User {
|
||||||
@@ -17,8 +16,10 @@ impl User {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn create_user(tx: &Transaction, name: &String, public_key: &String) -> Result<User> {
|
pub fn create_user(tx: &Transaction, name: &String, public_key: &String) -> Result<User> {
|
||||||
tx.execute("INSERT INTO users (name, public_key) VALUES (?1, ?2)",
|
tx.execute(
|
||||||
(name, public_key))?;
|
"INSERT INTO users (name, public_key) VALUES (?1, ?2)",
|
||||||
|
(name, public_key),
|
||||||
|
)?;
|
||||||
return get_user_by_id(tx, tx.last_insert_rowid() as i32);
|
return get_user_by_id(tx, tx.last_insert_rowid() as i32);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,7 +40,8 @@ fn get_user_by_id(tx: &Transaction, id: i32) -> Result<User> {
|
|||||||
|
|
||||||
pub fn list_users(conn: &Connection) -> Result<Vec<User>> {
|
pub fn list_users(conn: &Connection) -> Result<Vec<User>> {
|
||||||
let mut stmt = conn.prepare("SELECT id, name, public_key FROM users")?;
|
let mut stmt = conn.prepare("SELECT id, name, public_key FROM users")?;
|
||||||
let ret: Vec<User> = stmt.query_map([], _user_from_row)?
|
let ret: Vec<User> = stmt
|
||||||
|
.query_map([], _user_from_row)?
|
||||||
.filter(Result::is_ok)
|
.filter(Result::is_ok)
|
||||||
.map(Result::unwrap)
|
.map(Result::unwrap)
|
||||||
.collect();
|
.collect();
|
||||||
|
|||||||
+8
-14
@@ -1,10 +1,10 @@
|
|||||||
mod _users;
|
|
||||||
mod _securities;
|
|
||||||
mod _accounts;
|
mod _accounts;
|
||||||
mod _cheques;
|
mod _cheques;
|
||||||
mod _external_id;
|
mod _external_id;
|
||||||
mod _test_utils;
|
|
||||||
mod _interests;
|
mod _interests;
|
||||||
|
mod _securities;
|
||||||
|
mod _test_utils;
|
||||||
|
mod _users;
|
||||||
|
|
||||||
pub mod external_id {
|
pub mod external_id {
|
||||||
pub use super::_external_id::*;
|
pub use super::_external_id::*;
|
||||||
@@ -19,14 +19,10 @@ pub mod users {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod accounts {
|
pub mod accounts {
|
||||||
pub use super::_accounts::{Account,
|
pub use super::_accounts::{
|
||||||
AccountChangeReceipt,
|
add_to_account, create_checking_account, create_security_emitter_account,
|
||||||
create_checking_account,
|
get_account_by_id, is_owner, list_accounts, Account, AccountChangeReceipt,
|
||||||
create_security_emitter_account,
|
};
|
||||||
is_owner,
|
|
||||||
get_account_by_id,
|
|
||||||
list_accounts,
|
|
||||||
add_to_account};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub mod cheques {
|
pub mod cheques {
|
||||||
@@ -34,7 +30,5 @@ pub mod cheques {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub mod test_utils {
|
pub mod test_utils {
|
||||||
pub use super::_test_utils::{mock_currency_1,
|
pub use super::_test_utils::{mock_currency_1, mock_currency_2, setup_basic_environment};
|
||||||
mock_currency_2,
|
|
||||||
setup_basic_environment};
|
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ use std::fs::File;
|
|||||||
use std::io::{BufReader, Read, Write};
|
use std::io::{BufReader, Read, Write};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
|
use crate::util::{CustomResult, Error};
|
||||||
use apache_avro::types::Value;
|
use apache_avro::types::Value;
|
||||||
use apache_avro::Schema::Union;
|
use apache_avro::Schema::Union;
|
||||||
use apache_avro::{
|
use apache_avro::{
|
||||||
@@ -13,8 +14,6 @@ use apache_avro::{
|
|||||||
use resource::resource_str;
|
use resource::resource_str;
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use crate::util::{CustomResult, Error};
|
|
||||||
|
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
#[derive(Deserialize, Serialize, Debug, PartialEq)]
|
||||||
struct CacheChequeEvent {
|
struct CacheChequeEvent {
|
||||||
@@ -50,7 +49,6 @@ mod test {
|
|||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_basic() -> CustomResult<()> {
|
fn test_basic() -> CustomResult<()> {
|
||||||
let file = tempfile().unwrap();
|
let file = tempfile().unwrap();
|
||||||
|
|||||||
+16
-12
@@ -1,7 +1,7 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use clap::{Arg, ArgAction, ArgMatches, Command, value_parser};
|
|
||||||
use clap::parser::ValuesRef;
|
use clap::parser::ValuesRef;
|
||||||
|
use clap::{value_parser, Arg, ArgAction, ArgMatches, Command};
|
||||||
use log::{debug, error, info};
|
use log::{debug, error, info};
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -99,18 +99,14 @@ fn new_user_command_line_parser() -> Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ls_user_command_line_parser() -> Command {
|
fn ls_user_command_line_parser() -> Command {
|
||||||
return Command::new("ls")
|
return Command::new("ls").long_flag("ls").about("List all users.");
|
||||||
.long_flag("ls")
|
|
||||||
.about("List all users.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
fn process_events_command_line_parser() -> Command {
|
fn process_events_command_line_parser() -> Command {
|
||||||
Command::new("process")
|
Command::new("process")
|
||||||
.about("Process events one after the other. In the order received")
|
.about("Process events one after the other. In the order received")
|
||||||
.arg_required_else_help(true)
|
.arg_required_else_help(true)
|
||||||
.arg(clap::arg!(<PATH> ... "Stuff to add")
|
.arg(clap::arg!(<PATH> ... "Stuff to add").value_parser(value_parser!(PathBuf)))
|
||||||
.value_parser(value_parser!(PathBuf)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn user_command_line_parser() -> Command {
|
fn user_command_line_parser() -> Command {
|
||||||
@@ -136,16 +132,19 @@ fn ls_securities_command_line_parser() -> Command {
|
|||||||
|
|
||||||
fn run_web_ui_command_line_parser() -> Command {
|
fn run_web_ui_command_line_parser() -> Command {
|
||||||
return Command::new("run")
|
return Command::new("run")
|
||||||
.arg(Arg::new("port")
|
.arg(
|
||||||
|
Arg::new("port")
|
||||||
.long("port")
|
.long("port")
|
||||||
.value_parser(clap::value_parser!(u16).range(3000..))
|
.value_parser(clap::value_parser!(u16).range(3000..))
|
||||||
.action(ArgAction::Set)
|
.action(ArgAction::Set)
|
||||||
.default_value("3200"))
|
.default_value("3200"),
|
||||||
.arg(Arg::new("enable-template-autoreload")
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::new("enable-template-autoreload")
|
||||||
.long("enable-template-autoreload")
|
.long("enable-template-autoreload")
|
||||||
.required(false)
|
.required(false)
|
||||||
.num_args(0)
|
.num_args(0)
|
||||||
.help("Reload underlying template (for development)")
|
.help("Reload underlying template (for development)"),
|
||||||
)
|
)
|
||||||
.about("Run web user interface.");
|
.about("Run web user interface.");
|
||||||
}
|
}
|
||||||
@@ -356,7 +355,12 @@ mod cli_test {
|
|||||||
self.process_events_count += 1;
|
self.process_events_count += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_webui(&mut self, conn: &mut Connection, port: u16, enable_template_autoreload: bool) {
|
fn run_webui(
|
||||||
|
&mut self,
|
||||||
|
conn: &mut Connection,
|
||||||
|
port: u16,
|
||||||
|
enable_template_autoreload: bool,
|
||||||
|
) {
|
||||||
self.run_webui_count += 1;
|
self.run_webui_count += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+14
-9
@@ -1,7 +1,7 @@
|
|||||||
use std::time::Instant;
|
use resource::resource_str;
|
||||||
use rusqlite::{Connection, Transaction};
|
use rusqlite::{Connection, Transaction};
|
||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
use resource::{resource_str};
|
use std::time::Instant;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum IntegrityError {
|
pub enum IntegrityError {
|
||||||
@@ -33,10 +33,10 @@ fn get_id_of_new_migration(tx: &Transaction) -> Result<usize, rusqlite::Error> {
|
|||||||
return tx.query_row(q, (), |row| row.get::<usize, usize>(0));
|
return tx.query_row(q, (), |row| row.get::<usize, usize>(0));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn migrate(conn: &mut Connection, migrations: &Vec<String>) -> Result<(), Error> {
|
pub fn migrate(conn: &mut Connection, migrations: &Vec<String>) -> Result<(), Error> {
|
||||||
let tx = conn.transaction().map_err(|e| Error::Sql(e))?;
|
let tx = conn.transaction().map_err(|e| Error::Sql(e))?;
|
||||||
tx.execute(INITIAL_MIGRATION, ()).map_err(|e| Error::Sql(e))?;
|
tx.execute(INITIAL_MIGRATION, ())
|
||||||
|
.map_err(|e| Error::Sql(e))?;
|
||||||
let id = get_id_of_new_migration(&tx).map_err(|e| Error::Sql(e))?;
|
let id = get_id_of_new_migration(&tx).map_err(|e| Error::Sql(e))?;
|
||||||
|
|
||||||
for (i, migration) in migrations.iter().enumerate() {
|
for (i, migration) in migrations.iter().enumerate() {
|
||||||
@@ -56,13 +56,13 @@ fn apply_migration(tx: &Transaction, id: usize, migration: &String) -> Result<()
|
|||||||
let q = "INSERT INTO shitty_migrations(id, sha256) VALUES (?1, ?2)";
|
let q = "INSERT INTO shitty_migrations(id, sha256) VALUES (?1, ?2)";
|
||||||
tx.execute(q, (&id, &hash)).map_err(|e| Error::Sql(e))?;
|
tx.execute(q, (&id, &hash)).map_err(|e| Error::Sql(e))?;
|
||||||
|
|
||||||
|
|
||||||
let now = Instant::now();
|
let now = Instant::now();
|
||||||
tx.execute_batch(migration).map_err(|e| Error::Sql(e))?;
|
tx.execute_batch(migration).map_err(|e| Error::Sql(e))?;
|
||||||
let elapsed_time = now.elapsed();
|
let elapsed_time = now.elapsed();
|
||||||
|
|
||||||
let q = "UPDATE shitty_migrations SET duration = ?1 WHERE id = ?2";
|
let q = "UPDATE shitty_migrations SET duration = ?1 WHERE id = ?2";
|
||||||
tx.execute(q, (elapsed_time.as_secs_f32(), &id)).map_err(|e| Error::Sql(e))?;
|
tx.execute(q, (elapsed_time.as_secs_f32(), &id))
|
||||||
|
.map_err(|e| Error::Sql(e))?;
|
||||||
|
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
@@ -76,7 +76,9 @@ fn compute_migration_sha256(migration: &String) -> String {
|
|||||||
|
|
||||||
fn check_migration(tx: &Transaction, id: usize, migration: &String) -> Result<(), Error> {
|
fn check_migration(tx: &Transaction, id: usize, migration: &String) -> Result<(), Error> {
|
||||||
let q = "SELECT sha256 FROM shitty_migrations WHERE id = ?1";
|
let q = "SELECT sha256 FROM shitty_migrations WHERE id = ?1";
|
||||||
let res = tx.query_row(q, (&id, ), |row| row.get::<usize, String>(0)).map_err(|e| Error::Sql(e))?;
|
let res = tx
|
||||||
|
.query_row(q, (&id,), |row| row.get::<usize, String>(0))
|
||||||
|
.map_err(|e| Error::Sql(e))?;
|
||||||
let expected = compute_migration_sha256(migration);
|
let expected = compute_migration_sha256(migration);
|
||||||
if !res.eq(&expected) {
|
if !res.eq(&expected) {
|
||||||
return Err(Error::Integrity(IntegrityError::MigrationCheckFailed {
|
return Err(Error::Integrity(IntegrityError::MigrationCheckFailed {
|
||||||
@@ -91,11 +93,14 @@ fn check_migration(tx: &Transaction, id: usize, migration: &String) -> Result<()
|
|||||||
|
|
||||||
fn get_migrations() -> Vec<String> {
|
fn get_migrations() -> Vec<String> {
|
||||||
let mut ret = Vec::new();
|
let mut ret = Vec::new();
|
||||||
ret.push(resource_str!("migrations/sql/create_tables_001.sql").as_ref().to_string());
|
ret.push(
|
||||||
|
resource_str!("migrations/sql/create_tables_001.sql")
|
||||||
|
.as_ref()
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub fn apply_migrations(conn: &mut Connection) -> Result<(), Error> {
|
pub fn apply_migrations(conn: &mut Connection) -> Result<(), Error> {
|
||||||
let migrations = get_migrations();
|
let migrations = get_migrations();
|
||||||
return migrate(conn, &migrations);
|
return migrate(conn, &migrations);
|
||||||
|
|||||||
+104
-64
@@ -1,4 +1,4 @@
|
|||||||
use rusqlite::{Connection, ffi, params, Savepoint, Transaction};
|
use rusqlite::{ffi, params, Connection, Savepoint, Transaction};
|
||||||
|
|
||||||
use crate::db::accounts;
|
use crate::db::accounts;
|
||||||
use crate::db::accounts::get_account_by_id;
|
use crate::db::accounts::get_account_by_id;
|
||||||
@@ -6,22 +6,32 @@ use crate::db::cheques::Cheque;
|
|||||||
use crate::db::external_id::AccountId;
|
use crate::db::external_id::AccountId;
|
||||||
use crate::util::*;
|
use crate::util::*;
|
||||||
|
|
||||||
fn _move_securities(tx: &Savepoint,
|
fn _move_securities(
|
||||||
|
tx: &Savepoint,
|
||||||
cheque: &Cheque,
|
cheque: &Cheque,
|
||||||
account_id: &AccountId) -> CustomResult<(accounts::AccountChangeReceipt, accounts::AccountChangeReceipt)> {
|
account_id: &AccountId,
|
||||||
|
) -> CustomResult<(
|
||||||
|
accounts::AccountChangeReceipt,
|
||||||
|
accounts::AccountChangeReceipt,
|
||||||
|
)> {
|
||||||
let drawer_receipt = accounts::add_to_account(tx, &cheque.drawer_account_id, -cheque.amount)?;
|
let drawer_receipt = accounts::add_to_account(tx, &cheque.drawer_account_id, -cheque.amount)?;
|
||||||
let payee_receipt = accounts::add_to_account(tx, account_id, cheque.amount)?;
|
let payee_receipt = accounts::add_to_account(tx, account_id, cheque.amount)?;
|
||||||
if drawer_receipt.amount < 0 {
|
if drawer_receipt.amount < 0 {
|
||||||
let m = format!("Drawer of {:?} does not have sufficient amount in account.", cheque);
|
let m = format!(
|
||||||
|
"Drawer of {:?} does not have sufficient amount in account.",
|
||||||
|
cheque
|
||||||
|
);
|
||||||
return Err(Error::InvalidAmount(m));
|
return Err(Error::InvalidAmount(m));
|
||||||
}
|
}
|
||||||
Ok((drawer_receipt, payee_receipt))
|
Ok((drawer_receipt, payee_receipt))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _add_cheque_to_cashed_cheques_log(tx: &Connection,
|
fn _add_cheque_to_cashed_cheques_log(
|
||||||
|
tx: &Connection,
|
||||||
cheque: &Cheque,
|
cheque: &Cheque,
|
||||||
drawer_receipt: &accounts::AccountChangeReceipt,
|
drawer_receipt: &accounts::AccountChangeReceipt,
|
||||||
payee_receipt: &accounts::AccountChangeReceipt) -> Result<(), Error> {
|
payee_receipt: &accounts::AccountChangeReceipt,
|
||||||
|
) -> Result<(), Error> {
|
||||||
let q = "
|
let q = "
|
||||||
INSERT INTO cashed_cheques(
|
INSERT INTO cashed_cheques(
|
||||||
drawer_user_id,
|
drawer_user_id,
|
||||||
@@ -36,7 +46,8 @@ fn _add_cheque_to_cashed_cheques_log(tx: &Connection,
|
|||||||
)
|
)
|
||||||
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
VALUES(?1, ?2, ?3, ?4, ?5, ?6, ?7, ?8, ?9)
|
||||||
";
|
";
|
||||||
let params = params![cheque.drawer_user_id,
|
let params = params![
|
||||||
|
cheque.drawer_user_id,
|
||||||
cheque.drawer_account_id,
|
cheque.drawer_account_id,
|
||||||
drawer_receipt.version,
|
drawer_receipt.version,
|
||||||
cheque.payee_user_id,
|
cheque.payee_user_id,
|
||||||
@@ -44,41 +55,60 @@ fn _add_cheque_to_cashed_cheques_log(tx: &Connection,
|
|||||||
payee_receipt.version,
|
payee_receipt.version,
|
||||||
cheque.cheque_number,
|
cheque.cheque_number,
|
||||||
cheque.amount,
|
cheque.amount,
|
||||||
cheque.security_id];
|
cheque.security_id
|
||||||
|
];
|
||||||
let ret = tx.execute(q, params).map_err(Error::SqlError);
|
let ret = tx.execute(q, params).map_err(Error::SqlError);
|
||||||
match ret {
|
match ret {
|
||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(Error::SqlError(rusqlite::Error::SqliteFailure(ffi::Error { code: _, extended_code: 2067 }, _))) =>
|
Err(Error::SqlError(rusqlite::Error::SqliteFailure(
|
||||||
Err(Error::DoubleSpendingError(format!("Cheque number {} from user {} has already been cached.", cheque.cheque_number, cheque.drawer_user_id))),
|
ffi::Error {
|
||||||
Err(a) => Err(a)
|
code: _,
|
||||||
|
extended_code: 2067,
|
||||||
|
},
|
||||||
|
_,
|
||||||
|
))) => Err(Error::DoubleSpendingError(format!(
|
||||||
|
"Cheque number {} from user {} has already been cached.",
|
||||||
|
cheque.cheque_number, cheque.drawer_user_id
|
||||||
|
))),
|
||||||
|
Err(a) => Err(a),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cash_cheque_unsafe(savepoint: &Savepoint,
|
fn cash_cheque_unsafe(
|
||||||
|
savepoint: &Savepoint,
|
||||||
cheque: &Cheque,
|
cheque: &Cheque,
|
||||||
account_id: &AccountId) -> CustomResult<()> {
|
account_id: &AccountId,
|
||||||
|
) -> CustomResult<()> {
|
||||||
if cheque.amount < 0 {
|
if cheque.amount < 0 {
|
||||||
return Err(Error::InvalidAmount(format!("The cheque {:?} has a negative amount.", cheque)));
|
return Err(Error::InvalidAmount(format!(
|
||||||
|
"The cheque {:?} has a negative amount.",
|
||||||
|
cheque
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
if !accounts::is_owner(&savepoint, &cheque.drawer_account_id, &cheque.drawer_user_id)? {
|
if !accounts::is_owner(
|
||||||
return Err(Error::InvalidDrawer(format!("The drawer is the security owner: {:?}", cheque)));
|
&savepoint,
|
||||||
|
&cheque.drawer_account_id,
|
||||||
|
&cheque.drawer_user_id,
|
||||||
|
)? {
|
||||||
|
return Err(Error::InvalidDrawer(format!(
|
||||||
|
"The drawer is the security owner: {:?}",
|
||||||
|
cheque
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
if !accounts::is_owner(&savepoint, account_id, &cheque.payee_user_id)? {
|
if !accounts::is_owner(&savepoint, account_id, &cheque.payee_user_id)? {
|
||||||
return Err(Error::InvalidPayee(format!("Trying to cash the cheque {:?} in \
|
return Err(Error::InvalidPayee(format!(
|
||||||
an account {:?} that is not owned by the payee.", cheque, account_id)));
|
"Trying to cash the cheque {:?} in \
|
||||||
|
an account {:?} that is not owned by the payee.",
|
||||||
|
cheque, account_id
|
||||||
|
)));
|
||||||
}
|
}
|
||||||
let (drawer_receipt, payee_receipt) = _move_securities(&savepoint, cheque, account_id)?;
|
let (drawer_receipt, payee_receipt) = _move_securities(&savepoint, cheque, account_id)?;
|
||||||
_add_cheque_to_cashed_cheques_log(&savepoint,
|
_add_cheque_to_cashed_cheques_log(&savepoint, cheque, &drawer_receipt, &payee_receipt)?;
|
||||||
cheque,
|
|
||||||
&drawer_receipt,
|
|
||||||
&payee_receipt)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cash_cheque(tx: &mut Transaction,
|
fn cash_cheque(tx: &mut Transaction, cheque: &Cheque, account_id: &AccountId) -> CustomResult<()> {
|
||||||
cheque: &Cheque,
|
|
||||||
account_id: &AccountId) -> CustomResult<()> {
|
|
||||||
let mut savepoint = tx.savepoint().map_err(Error::SqlError)?;
|
let mut savepoint = tx.savepoint().map_err(Error::SqlError)?;
|
||||||
let ret = cash_cheque_unsafe(&savepoint, cheque, account_id);
|
let ret = cash_cheque_unsafe(&savepoint, cheque, account_id);
|
||||||
match ret {
|
match ret {
|
||||||
@@ -86,12 +116,9 @@ fn cash_cheque(tx: &mut Transaction,
|
|||||||
savepoint.rollback().map_err(Error::SqlError)?;
|
savepoint.rollback().map_err(Error::SqlError)?;
|
||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
Ok(_) => {
|
Ok(_) => savepoint.commit().map_err(Error::SqlError),
|
||||||
savepoint.commit().map_err(Error::SqlError)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_caching_cheques {
|
mod test_caching_cheques {
|
||||||
@@ -118,7 +145,8 @@ 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 security_emitter_account = create_security_emitter_account(&tx, &sec.id(), &drawer.id()).unwrap();
|
let security_emitter_account =
|
||||||
|
create_security_emitter_account(&tx, &sec.id(), &drawer.id()).unwrap();
|
||||||
let payee_account = create_checking_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();
|
||||||
@@ -127,9 +155,13 @@ mod test_caching_cheques {
|
|||||||
(conn, security_emitter, payee, payee_account.id())
|
(conn, security_emitter, payee, payee_account.id())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn _create_100_secs_in_payee_account(tx: &mut Transaction, security_emitter: &Drawer, payee: &Payee, payee_account: &AccountId) {
|
fn _create_100_secs_in_payee_account(
|
||||||
let cheque = Cheque::new(
|
tx: &mut Transaction,
|
||||||
security_emitter.clone(), payee.clone(), 100, 99);
|
security_emitter: &Drawer,
|
||||||
|
payee: &Payee,
|
||||||
|
payee_account: &AccountId,
|
||||||
|
) {
|
||||||
|
let cheque = Cheque::new(security_emitter.clone(), payee.clone(), 100, 99);
|
||||||
|
|
||||||
cash_cheque(tx, &cheque, &payee_account).unwrap();
|
cash_cheque(tx, &cheque, &payee_account).unwrap();
|
||||||
}
|
}
|
||||||
@@ -144,7 +176,8 @@ mod test_caching_cheques {
|
|||||||
let mut tx = conn.transaction().unwrap();
|
let mut tx = conn.transaction().unwrap();
|
||||||
|
|
||||||
_create_100_secs_in_payee_account(&mut tx, &security_emitter, &payee, &payee_account);
|
_create_100_secs_in_payee_account(&mut tx, &security_emitter, &payee, &payee_account);
|
||||||
let reg_account = create_checking_account(&tx, &security_emitter.2, &security_emitter.0).unwrap();
|
let reg_account =
|
||||||
|
create_checking_account(&tx, &security_emitter.2, &security_emitter.0).unwrap();
|
||||||
|
|
||||||
tx.commit().unwrap();
|
tx.commit().unwrap();
|
||||||
|
|
||||||
@@ -154,14 +187,12 @@ mod test_caching_cheques {
|
|||||||
return (conn, drawer, new_payee, reg_account.id());
|
return (conn, drawer, new_payee, reg_account.id());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_happy_path_currency_emitter() -> CustomResult<()> {
|
fn test_happy_path_currency_emitter() -> CustomResult<()> {
|
||||||
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
|
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
|
||||||
let mut tx = conn.transaction().unwrap();
|
let mut tx = conn.transaction().unwrap();
|
||||||
|
|
||||||
let cheque = Cheque::new(
|
let cheque = Cheque::new(security_emitter, payee, 1000, 99);
|
||||||
security_emitter, payee, 1000, 99);
|
|
||||||
|
|
||||||
cash_cheque(&mut tx, &cheque, &payee_account)?;
|
cash_cheque(&mut tx, &cheque, &payee_account)?;
|
||||||
tx.commit().unwrap();
|
tx.commit().unwrap();
|
||||||
@@ -178,8 +209,7 @@ mod test_caching_cheques {
|
|||||||
let (mut conn, drawer, payee, payee_account) = setup_basic();
|
let (mut conn, drawer, payee, payee_account) = setup_basic();
|
||||||
let mut tx = conn.transaction().unwrap();
|
let mut tx = conn.transaction().unwrap();
|
||||||
|
|
||||||
let cheque = Cheque::new(
|
let cheque = Cheque::new(drawer.clone(), payee, 25, 10001);
|
||||||
drawer.clone(), payee, 25, 10001);
|
|
||||||
|
|
||||||
cash_cheque(&mut tx, &cheque, &payee_account)?;
|
cash_cheque(&mut tx, &cheque, &payee_account)?;
|
||||||
|
|
||||||
@@ -192,16 +222,22 @@ mod test_caching_cheques {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_negative_cheque(conn: &mut Connection, drawer: Drawer, payee: Payee, payee_account: AccountId) -> CustomResult<()> {
|
fn test_negative_cheque(
|
||||||
|
conn: &mut Connection,
|
||||||
|
drawer: Drawer,
|
||||||
|
payee: Payee,
|
||||||
|
payee_account: AccountId,
|
||||||
|
) -> CustomResult<()> {
|
||||||
let mut tx = conn.transaction().unwrap();
|
let mut tx = conn.transaction().unwrap();
|
||||||
|
|
||||||
let cheque = Cheque::new(
|
let cheque = Cheque::new(drawer, payee, -1, 99);
|
||||||
drawer, payee, -1, 99);
|
|
||||||
|
|
||||||
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
|
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
|
||||||
match ret {
|
match ret {
|
||||||
Err(Error::InvalidAmount(_)) => Ok(()),
|
Err(Error::InvalidAmount(_)) => Ok(()),
|
||||||
_ => Err(Error::Testing("Expected invalid amount error here.".to_string()))
|
_ => Err(Error::Testing(
|
||||||
|
"Expected invalid amount error here.".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -217,15 +253,15 @@ mod test_caching_cheques {
|
|||||||
test_negative_cheque(&mut conn, drawer, payee, payee_account)
|
test_negative_cheque(&mut conn, drawer, payee, payee_account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_double_spend(conn: &mut Connection, drawer: Drawer, payee: Payee, payee_account: AccountId) -> CustomResult<()> {
|
fn test_double_spend(
|
||||||
|
conn: &mut Connection,
|
||||||
|
drawer: Drawer,
|
||||||
|
payee: Payee,
|
||||||
|
payee_account: AccountId,
|
||||||
|
) -> CustomResult<()> {
|
||||||
let mut tx = conn.transaction().unwrap();
|
let mut tx = conn.transaction().unwrap();
|
||||||
|
|
||||||
let cheque = Cheque::new(
|
let cheque = Cheque::new(drawer, payee, 10, 99);
|
||||||
drawer,
|
|
||||||
payee,
|
|
||||||
10,
|
|
||||||
99,
|
|
||||||
);
|
|
||||||
|
|
||||||
cash_cheque(&mut tx, &cheque, &payee_account)?;
|
cash_cheque(&mut tx, &cheque, &payee_account)?;
|
||||||
tx.commit().unwrap();
|
tx.commit().unwrap();
|
||||||
@@ -234,7 +270,9 @@ mod test_caching_cheques {
|
|||||||
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
|
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
|
||||||
match ret {
|
match ret {
|
||||||
Err(Error::DoubleSpendingError(_)) => Ok(()),
|
Err(Error::DoubleSpendingError(_)) => Ok(()),
|
||||||
_ => Err(Error::Testing("Expected double spending error here.".to_string()))
|
_ => Err(Error::Testing(
|
||||||
|
"Expected double spending error here.".to_string(),
|
||||||
|
)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -250,21 +288,21 @@ mod test_caching_cheques {
|
|||||||
test_double_spend(&mut conn, drawer, payee, payee_account)
|
test_double_spend(&mut conn, drawer, payee, payee_account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_invalid_drawer(conn: &mut Connection, drawer: Drawer, payee: Payee, payee_account: AccountId) -> CustomResult<()> {
|
fn test_invalid_drawer(
|
||||||
|
conn: &mut Connection,
|
||||||
|
drawer: Drawer,
|
||||||
|
payee: Payee,
|
||||||
|
payee_account: AccountId,
|
||||||
|
) -> CustomResult<()> {
|
||||||
let mut tx = conn.transaction().unwrap();
|
let mut tx = conn.transaction().unwrap();
|
||||||
let not_currency_owner_user_id = UserId::from_id(drawer.0.get_id() + 72623);
|
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 not_currency_owner: Drawer = (not_currency_owner_user_id, drawer.1, drawer.2);
|
||||||
let cheque = Cheque::new(
|
let cheque = Cheque::new(not_currency_owner, payee, 100, 99);
|
||||||
not_currency_owner,
|
|
||||||
payee,
|
|
||||||
100,
|
|
||||||
99,
|
|
||||||
);
|
|
||||||
|
|
||||||
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
|
let ret = cash_cheque(&mut tx, &cheque, &payee_account);
|
||||||
match ret {
|
match ret {
|
||||||
Err(Error::InvalidDrawer(_)) => Ok(()),
|
Err(Error::InvalidDrawer(_)) => Ok(()),
|
||||||
_ => Err(Error::Testing("Expected invalid drawer here.".to_string()))
|
_ => Err(Error::Testing("Expected invalid drawer here.".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -280,22 +318,24 @@ mod test_caching_cheques {
|
|||||||
test_invalid_drawer(&mut conn, drawer, payee, payee_account)
|
test_invalid_drawer(&mut conn, drawer, payee, payee_account)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_invalid_payee(conn: &mut Connection, security_emitter: Drawer, payee: Payee) -> CustomResult<()> {
|
fn test_invalid_payee(
|
||||||
|
conn: &mut Connection,
|
||||||
|
security_emitter: Drawer,
|
||||||
|
payee: Payee,
|
||||||
|
) -> CustomResult<()> {
|
||||||
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
|
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
|
||||||
let mut tx = conn.transaction().unwrap();
|
let mut tx = conn.transaction().unwrap();
|
||||||
let wrong_payee_account = &security_emitter.1;
|
let wrong_payee_account = &security_emitter.1;
|
||||||
|
|
||||||
let cheque = Cheque::new(
|
let cheque = Cheque::new(security_emitter.clone(), payee.clone(), 1000, 99);
|
||||||
security_emitter.clone(), payee.clone(), 1000, 99);
|
|
||||||
|
|
||||||
let ret = cash_cheque(&mut tx, &cheque, wrong_payee_account);
|
let ret = cash_cheque(&mut tx, &cheque, wrong_payee_account);
|
||||||
match ret {
|
match ret {
|
||||||
Err(Error::InvalidPayee(_)) => Ok(()),
|
Err(Error::InvalidPayee(_)) => Ok(()),
|
||||||
_ => Err(Error::Testing("Expected invalid payee here.".to_string()))
|
_ => Err(Error::Testing("Expected invalid payee here.".to_string())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_invalid_payee_security_emitter_cheque() -> CustomResult<()> {
|
fn test_invalid_payee_security_emitter_cheque() -> CustomResult<()> {
|
||||||
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
|
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
|
||||||
|
|||||||
+2
-3
@@ -3,12 +3,12 @@ mod views;
|
|||||||
use actix_utils::future::{ready, Ready};
|
use actix_utils::future::{ready, Ready};
|
||||||
use minijinja;
|
use minijinja;
|
||||||
use minijinja_autoreload::AutoReloader;
|
use minijinja_autoreload::AutoReloader;
|
||||||
use std::{collections::HashMap, env, fs, io, path::PathBuf};
|
|
||||||
use std::io::{Error, ErrorKind};
|
use std::io::{Error, ErrorKind};
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
use std::{collections::HashMap, env, fs, io, path::PathBuf};
|
||||||
|
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
dev::{ServiceResponse},
|
dev::ServiceResponse,
|
||||||
error,
|
error,
|
||||||
http::{header::ContentType, StatusCode},
|
http::{header::ContentType, StatusCode},
|
||||||
middleware::{ErrorHandlerResponse, ErrorHandlers, Logger},
|
middleware::{ErrorHandlerResponse, ErrorHandlers, Logger},
|
||||||
@@ -54,7 +54,6 @@ impl FromRequest for MiniJinjaRenderer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
pub async fn web_ui_main(port: u16, enable_template_autoreload: bool) -> std::io::Result<()> {
|
pub async fn web_ui_main(port: u16, enable_template_autoreload: bool) -> std::io::Result<()> {
|
||||||
// The closure is invoked every time the environment is outdated to recreate it.
|
// The closure is invoked every time the environment is outdated to recreate it.
|
||||||
|
|||||||
+2
-2
@@ -1,6 +1,6 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use actix_web::{Responder, web};
|
|
||||||
use crate::webui::MiniJinjaRenderer;
|
use crate::webui::MiniJinjaRenderer;
|
||||||
|
use actix_web::{web, Responder};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
#[actix_web::get("/")]
|
#[actix_web::get("/")]
|
||||||
pub async fn index(
|
pub async fn index(
|
||||||
|
|||||||
Executable
+3
@@ -0,0 +1,3 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
find ${PROJECT_ROOT}/src -name '*.rs' | xargs rustfmt --edition 2021
|
||||||
Reference in new issue
Block a user