More things before going to fix my backyard.

This commit is contained in:
didier amyot committed 2023-04-07 09:58:22 -04:00
1 parent a980b1a9b1
commit fe32c526eb
15 files changed
+188 -60

No files matched your search

@@ -53,6 +53,7 @@ CREATE TABLE IF NOT EXISTS accounts
version INTEGER NOT NULL CHECK ( version >= 0 ),
security INTEGER NOT NULL,
account_type TEXT NOT NULL,
label TEXT NOT NULL,
amount INTEGER NOT NULL,
timestamp DATETIME NOT NULL,
+2
View File
@@ -9,6 +9,7 @@
<tr>
<th></th>
<th>id</th>
<th>Label</th>
<th>Amount</th>
<th>Account Type</th>
<th>Last Update</th>
@@ -21,6 +22,7 @@
<tr>
<td><a href="/accounts/{{ account.id }}"><span uk-icon="info"></span></a></td>
<td>{{ account.id }}</td>
<td>{{ account.label }}</td>
<td>{{ account.amount }}</td>
<td style="font-variant: small-caps;">{{ account.account_type | replace("_", " ") | lower}}</td>
<td>{{ account.last_update }}</td>
+2 -1
View File
@@ -8,7 +8,7 @@
<form>
<fieldset class="uk-fieldset">
<legend class="uk-legend">Create new item</legend>
<br>
<div class="uk-margin">
<select name="event_type"
class="uk-select"
@@ -21,6 +21,7 @@
<option value="create_security">Create Security</option>
<option value="create_account">Create Account</option>
<option value="emit_securities">Emit Securities</option>
<option value="create_wire_transfer">Create Wire Transfer</option>
</select>
</div>
<input type="hidden" name="initiator" value="bob le chef">
+1 -1
View File
@@ -5,7 +5,7 @@
Ledger
</a>
<ul class="uk-navbar-nav">
{% set nav_bar_items = [{'id': 'overview', 'label': 'Overview'},
{% set nav_bar_items = [{'id': 'users', 'label': 'Users'},
{'id': 'accounts', 'label': 'Accounts'},
{'id': 'securities', 'label': 'Securities'},
{'id': 'actions', 'label': 'Actions'}]%}
@@ -6,7 +6,17 @@
<option value="{{ sec.id }}">{{ sec.name }} ({{ sec.symbol }}) </option>
{% endfor %}
</select>
</label>
</div>
<div class="uk-margin">
<label class="uk-form-label"> Label
<input class="uk-input"
type="text"
placeholder="For Bribes"
aria-label="Default"
name="event.CreateNewAccount.label"
required>
</label>
</div>
@@ -0,0 +1,50 @@
<input type="hidden"
name="event.CreateWireTransfer.drawer_user_id"
value="{{ user.id }}">
<div class="uk-margin">
<label class="uk-form-label"> Drawer Account
<select class="uk-select"
name="event.CreateWireTransfer.emitter_account"
id="event.CreateWireTransfer.emitter_account"
hx-get="/partials/actions/accounts-by-security-selectors"
hx-trigger="change,load"
hx-target="#onetwothree"
hx-vals="js:{'sec-id': document.getElementById('event.CreateWireTransfer.emitter_account').options[document.getElementById('event.CreateWireTransfer.emitter_account').selectedIndex].getAttribute('data-sec-id')}"
hx-swap="innerHtml">
{% for account in emitter_accounts %}
<option value="{{ account.id }}" data-sec-id="{{ account.security.id }}">
{{ account.security.name }} ({{ account.security.symbol }})
</option>
{% endfor %}
</select>
</label>
</div>
<div class="uk-margin">
<label class="uk-form-label"> Payee Accounts
<select class="uk-select"
name="event.CreateWireTransfer.payee_account"
id="onetwothree">
</select>
</label>
</div>
<div class="uk-margin">
<label class="uk-form-label"> Amount
<input class="uk-input"
type="number"
step="1"
placeholder="100 000"
aria-label="Default"
name="event.CreateWireTransfer.amount"
required>
</label>
</div>
{%include "partials/actions/submit_button.html"%}
@@ -1,4 +1,4 @@
{% set active_navbar_item = 'overview' %}
{% set active_navbar_item = 'users' %}
{% extends "base.html" %}
+28 -11
View File
@@ -56,6 +56,7 @@ pub struct Account {
timestamp: i64,
security: Security,
account_type: AccountType,
label: String,
}
impl Account {
@@ -70,6 +71,7 @@ impl Account {
timestamp: row.get("acc_timestamp")?,
security: Security::from_row(row)?,
account_type: AccountType::from_row(row)?,
label: row.get("acc_label")?,
});
}
@@ -89,6 +91,10 @@ impl Account {
pub fn account_type(&self) -> &AccountType {
&self.account_type
}
pub fn label(&self) -> &str {
&self.label
}
}
#[derive(Debug)]
@@ -144,6 +150,7 @@ pub fn list_accounts<'a>(conn: &Connection) -> CustomResult<Vec<Account>> {
acc.amount AS acc_amount,
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
account_type AS acc_account_type,
label AS acc_label,
sec.id AS sec_id,
sec.symbol AS sec_symbol,
@@ -167,6 +174,7 @@ pub fn list_user_accounts(conn: &Connection, user_id: & UserId) -> CustomResult<
acc.amount AS acc_amount,
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
account_type AS acc_account_type,
label AS acc_label,
sec.id AS sec_id,
sec.symbol AS sec_symbol,
@@ -192,6 +200,7 @@ pub fn get_account_by_id(tx: &Connection, id: &AccountId) -> CustomResult<Accoun
acc.amount AS acc_amount,
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
account_type AS acc_account_type,
label AS acc_label,
sec.id AS sec_id,
sec.symbol AS sec_symbol,
@@ -211,6 +220,7 @@ pub fn get_checking_accounts_for_sec_id(tx: &Connection, sec_id: &SecurityId) ->
acc.amount AS acc_amount,
UNIXEPOCH(acc.timestamp) AS acc_timestamp,
account_type AS acc_account_type,
label AS acc_label,
sec.id AS sec_id,
sec.symbol AS sec_symbol,
@@ -235,14 +245,15 @@ fn create_account(
sec: &SecurityId,
user: &UserId,
account_type: AccountType,
label: &String,
) -> CustomResult<Account> {
let q = "INSERT INTO accounts(security, version, amount, timestamp, account_type)
VALUES (?1, 0, 0, CURRENT_TIMESTAMP, ?2)";
let q = "INSERT INTO accounts(security, version, amount, timestamp, account_type, label)
VALUES (?1, 0, 0, CURRENT_TIMESTAMP, ?2, ?3)";
let account_type = match account_type {
AccountType::Checking => "checking",
AccountType::SecurityEmitter => "security_emitter",
};
tx.execute(q, (sec, account_type))
tx.execute(q, (sec, account_type, label))
.map_err(Error::SqlError)?;
let id = AccountId::from_id(tx.last_insert_rowid() as i64);
@@ -277,16 +288,18 @@ pub fn create_checking_account(
tx: &Connection,
sec: &SecurityId,
user: &UserId,
label: &String,
) -> CustomResult<Account> {
create_account(tx, sec, user, AccountType::Checking)
create_account(tx, sec, user, AccountType::Checking, label)
}
pub fn create_security_emitter_account(
tx: &Connection,
sec: &SecurityId,
user: &UserId,
label: &String,
) -> CustomResult<Account> {
create_account(tx, sec, user, AccountType::SecurityEmitter)
create_account(tx, sec, user, AccountType::SecurityEmitter, label)
}
pub fn is_owner(tx: &Connection, account_id: &AccountId, user_id: &UserId) -> CustomResult<bool> {
@@ -392,7 +405,8 @@ mod test_accounts {
let mut ret: Vec<Account> = Vec::new();
for sec in secs.iter() {
for user in users.iter() {
ret.push(create_checking_account(&tx, &sec.id(), &user.id())?);
let label = &format!("Account for {:} in {:}", user.id(), sec.id());
ret.push(create_checking_account(&tx, &sec.id(), &user.id(), label)?);
}
}
return Ok(ret);
@@ -418,7 +432,10 @@ mod test_accounts {
let sec = secs.get(0).unwrap();
let tx = conn.transaction().unwrap();
let acc = create_checking_account(&tx, &sec.id(), &user.id())?;
let acc = create_checking_account(&tx,
&sec.id(),
&user.id(),
&format!("Account for {:} in {:}", sec.id(), user.id()))?;
assert_eq!(acc.account_type, AccountType::Checking);
assert_eq!(0, acc.raw_amount());
@@ -437,7 +454,7 @@ mod test_accounts {
let sec = secs.get(0).unwrap();
let tx = conn.transaction().unwrap();
let acc = create_security_emitter_account(&tx, &sec.id(), &user.id())?;
let acc = create_security_emitter_account(&tx, &sec.id(), &user.id(), &"acc".to_string())?;
assert_eq!(acc.account_type, AccountType::SecurityEmitter);
assert_eq!(0, acc.raw_amount());
@@ -476,7 +493,7 @@ mod test_accounts {
let user_2 = users.get(1).unwrap();
let sec = secs.get(0).unwrap();
let acc = create_checking_account(&tx, &sec.id(), &user_1.id())?;
let acc = create_checking_account(&tx, &sec.id(), &user_1.id(), &"acc".to_string())?;
assert!(is_owner(tx, &acc.id(), &user_1.id())?);
assert!(!is_owner(tx, &acc.id(), &user_2.id())?);
@@ -490,7 +507,7 @@ mod test_accounts {
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).unwrap();
let acc = create_checking_account(&tx, &sec, &user, & "acc".to_string()).unwrap();
let receipt = add_to_account(&tx, &acc.id(), 33)?;
assert_eq!(receipt.account_id, acc.id());
assert_eq!(receipt.amount, 33);
@@ -514,7 +531,7 @@ mod test_accounts {
let tx = conn.transaction().unwrap();
let sec = secs.get(0).unwrap().id();
let user = users.get(0).unwrap().id();
let acc = create_security_emitter_account(&tx, &sec, &user)?;
let acc = create_security_emitter_account(&tx, &sec, &user, & "acc".to_string())?;
let receipt = add_to_account(&tx, &acc.id(), -33)?;
assert_eq!(receipt.account_id, acc.id());
+2
View File
@@ -51,11 +51,13 @@ pub fn setup_basic_environment(
tx,
&securities.get(0).unwrap().id(),
&users.get(0).unwrap().id(),
&"acc1".to_string()
)?;
let acc2 = create_checking_account(
tx,
&securities.get(1).unwrap().id(),
&users.get(1).unwrap().id(),
&"acc2".to_string()
)?;
Ok((users, securities, vec![acc1, acc2]))
}
+2 -2
View File
@@ -15,7 +15,7 @@ fn process_create_new_security_event(
) -> CustomResult<()> {
let r = event.to_create_new_security().unwrap();
let sec = create_security(tx, &r)?;
create_security_emitter_account(&tx, &sec.id(), &event.user_id)?;
create_security_emitter_account(&tx, &sec.id(), &event.user_id, & "acc".to_string())?;
Ok(())
}
@@ -25,7 +25,7 @@ fn process_create_new_user_event(tx: &mut Transaction, event: CreateNewUser) ->
}
fn process_create_new_account_event(tx: &mut Transaction, event: CreateNewAccount) -> CustomResult<()> {
create_checking_account(&tx, &event.securityId, &event.userId)?;
create_checking_account(&tx, &event.securityId, &event.userId, &event.label)?;
Ok(())
}
+1
View File
@@ -42,6 +42,7 @@ pub struct CreateNewUser {
pub struct CreateNewAccount {
pub securityId: SecurityId,
pub userId: UserId,
pub label: String,
}
#[derive(Deserialize, Serialize, Debug)]
+3 -1
View File
@@ -18,6 +18,7 @@ pub struct AccountDTO {
pub account_type: String,
pub last_update: i64,
pub security: SecurityDTO,
pub label: String,
}
impl AccountDTO {
@@ -27,7 +28,8 @@ impl AccountDTO {
amount: account.amount(),
account_type: account.account_type().to_string(),
last_update: account.timestamp(),
security: SecurityDTO::from_entity(account.security())
security: SecurityDTO::from_entity(account.security()),
label: account.label().to_string(),
}
}
}
+81 -39
View File
@@ -28,11 +28,9 @@ fn _transfer_securities_from_one_account_to_another(
if drawer_receipt.security_id != drawer_receipt.security_id {
let m = format!(
"Incompatible security type between accounts {:} (drawer) and {:} (payee).",
drawer_account_id,
payee_account_id
drawer_account_id, payee_account_id
);
return Err(Error::InvalidAmount(m));
}
Ok((drawer_receipt, payee_receipt))
}
@@ -45,11 +43,14 @@ fn _transfer_cheque_securities(
accounts::AccountChangeReceipt,
accounts::AccountChangeReceipt,
)> {
_transfer_securities_from_one_account_to_another(tx, &cheque.drawer_account_id, account_id, cheque.amount)
_transfer_securities_from_one_account_to_another(
tx,
&cheque.drawer_account_id,
account_id,
cheque.amount,
)
}
fn _transfer_wire_transfer_securities(
tx: &Savepoint,
wire_transfer_request: &WireTransferRequest,
@@ -57,16 +58,20 @@ fn _transfer_wire_transfer_securities(
accounts::AccountChangeReceipt,
accounts::AccountChangeReceipt,
)> {
_transfer_securities_from_one_account_to_another(tx,
_transfer_securities_from_one_account_to_another(
tx,
&wire_transfer_request.drawer_account_id,
&wire_transfer_request.payee_account_id,
wire_transfer_request.amount)
wire_transfer_request.amount,
)
}
fn _add_wire_transfer_request_to_wire_transfers_log(tx: &Connection,
fn _add_wire_transfer_request_to_wire_transfers_log(
tx: &Connection,
cheque: &WireTransferRequest,
drawer_receipt: &accounts::AccountChangeReceipt,
payee_receipt: &accounts::AccountChangeReceipt) -> Result<(), Error> {
payee_receipt: &accounts::AccountChangeReceipt,
) -> Result<(), Error> {
let q = "
INSERT INTO cached_wire_transfers(
drawer_account_id,
@@ -183,23 +188,28 @@ fn cash_cheque_unsafe(
)));
}
let drawer_account_sec = accounts::get_account_by_id(&savepoint, &cheque.drawer_account_id)?.security().id();
if drawer_account_sec != cheque.security_id{
let drawer_account_sec = accounts::get_account_by_id(&savepoint, &cheque.drawer_account_id)?
.security()
.id();
if drawer_account_sec != cheque.security_id {
return Err(Error::InvalidDrawer(format!(
"Impossible to use an account containing {:} to emit a cheque in {:}",
drawer_account_sec, cheque.security_id
)));
}
let payee_account_sec = accounts::get_account_by_id(&savepoint, &payee_account_id)?.security().id();
if payee_account_sec != cheque.security_id{
let payee_account_sec = accounts::get_account_by_id(&savepoint, &payee_account_id)?
.security()
.id();
if payee_account_sec != cheque.security_id {
return Err(Error::InvalidPayee(format!(
"Impossible to cache a {:} cheque in {:} account .",
cheque.security_id, payee_account_sec
)));
}
let (drawer_receipt, payee_receipt) = _transfer_cheque_securities(&savepoint, cheque, payee_account_id)?;
let (drawer_receipt, payee_receipt) =
_transfer_cheque_securities(&savepoint, cheque, payee_account_id)?;
_add_cheque_to_cashed_cheques_log(&savepoint, cheque, &drawer_receipt, &payee_receipt)?;
Ok(())
}
@@ -214,8 +224,14 @@ fn cash_wire_transfer_request_unsafe(
// wire_transfer
// )));
// }
let (drawer_receipt, payee_receipt) = _transfer_wire_transfer_securities(&savepoint, wire_transfer)?;
_add_wire_transfer_request_to_wire_transfers_log(&savepoint, wire_transfer, &drawer_receipt, &payee_receipt)?;
let (drawer_receipt, payee_receipt) =
_transfer_wire_transfer_securities(&savepoint, wire_transfer)?;
_add_wire_transfer_request_to_wire_transfers_log(
&savepoint,
wire_transfer,
&drawer_receipt,
&payee_receipt,
)?;
Ok(())
}
@@ -240,7 +256,7 @@ pub fn cash_wire_transfer_request(
wire_transfer: &WireTransferRequest,
) -> CustomResult<()> {
let mut savepoint = tx.savepoint().map_err(Error::SqlError)?;
let ret = cash_wire_transfer_request_unsafe(& savepoint, wire_transfer);
let ret = cash_wire_transfer_request_unsafe(&savepoint, wire_transfer);
match ret {
Err(_) => {
savepoint.rollback().map_err(Error::SqlError)?;
@@ -277,9 +293,16 @@ mod test_utils {
let sec = securities.get(0).unwrap();
let drawer = users.get(0).unwrap();
let payee = users.get(1).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 security_emitter_account = create_security_emitter_account(
&tx,
&sec.id(),
&drawer.id(),
&"sec emitter".to_string(),
)
.unwrap();
let payee_account =
create_checking_account(&tx, &sec.id(), &payee.id(), &"checking account".to_string())
.unwrap();
tx.commit().unwrap();
let security_emitter: Drawer = (drawer.id(), security_emitter_account.id(), sec.id());
@@ -295,15 +318,21 @@ mod test_utils {
let sec_2 = securities.get(1).unwrap();
let drawer = users.get(0).unwrap();
let payee = users.get(1).unwrap();
let security_emitter_account =
create_security_emitter_account(&tx, &sec_1.id(), &drawer.id()).unwrap();
let payee_account = create_checking_account(&tx, &sec_2.id(), &payee.id()).unwrap();
let security_emitter_account = create_security_emitter_account(
&tx,
&sec_1.id(),
&drawer.id(),
&"sec emitter account".to_string(),
)
.unwrap();
let payee_account =
create_checking_account(&tx, &sec_2.id(), &payee.id(), &"payee account".to_string())
.unwrap();
tx.commit().unwrap();
let security_emitter: Drawer = (drawer.id(), security_emitter_account.id(), sec_1.id());
let payee: Payee = (payee.id(), sec_1.id());
(conn, security_emitter, payee, payee_account.id())
}
pub fn create_100_security_units_in_payee_account(
@@ -326,9 +355,19 @@ mod test_utils {
let mut tx = conn.transaction().unwrap();
create_100_security_units_in_payee_account(&mut tx, &security_emitter, &payee, &payee_account);
let reg_account =
create_checking_account(&tx, &security_emitter.2, &security_emitter.0).unwrap();
create_100_security_units_in_payee_account(
&mut tx,
&security_emitter,
&payee,
&payee_account,
);
let reg_account = create_checking_account(
&tx,
&security_emitter.2,
&security_emitter.0,
&"reg account".to_string(),
)
.unwrap();
tx.commit().unwrap();
@@ -351,11 +390,12 @@ mod test_caching_cheques {
use crate::db::test_utils;
use crate::migration;
use super::*;
use super::test_utils::*;
use super::*;
#[test]
fn test_happy_path_currency_emitter() -> CustomResult<()> { // done
fn test_happy_path_currency_emitter() -> CustomResult<()> {
// done
let (mut conn, security_emitter, payee, payee_account) = setup_security_emitter();
let mut tx = conn.transaction().unwrap();
@@ -372,7 +412,8 @@ mod test_caching_cheques {
}
#[test]
fn test_happy_path_regular_cheque() -> CustomResult<()> { // done
fn test_happy_path_regular_cheque() -> CustomResult<()> {
// done
let (mut conn, drawer, payee, payee_account) = setup_basic();
let mut tx = conn.transaction().unwrap();
@@ -531,24 +572,23 @@ mod test_caching_cheques {
}
#[test]
fn test_mismatch_securities() -> CustomResult<()>{
fn test_mismatch_securities() -> CustomResult<()> {
let (mut conn, drawer, payee, payee_account) = setup_security_emitter_mismatched_accounts();
let cheque = Cheque::new(drawer.clone(), payee.clone(), 1000, 99);
let mut tx = conn.transaction().unwrap();
match cash_cheque(&mut tx, &cheque, & payee_account) {
match cash_cheque(&mut tx, &cheque, &payee_account) {
Err(Error::InvalidPayee(_)) => Ok(()),
_ => Err(Error::Testing("Expected invalid payee here.".to_string()))
_ => Err(Error::Testing("Expected invalid payee here.".to_string())),
}
}
}
#[cfg(test)]
mod test_wire_transfers {
use crate::db::accounts::get_account_by_id;
use super::*;
use super::test_utils::*;
use super::*;
use crate::db::accounts::get_account_by_id;
#[test]
fn test_happy_path_currency_emitter() -> CustomResult<()> {
@@ -556,7 +596,7 @@ mod test_wire_transfers {
let mut tx = conn.transaction().unwrap();
// let cheque = Cheque::new(security_emitter, payee, 1000, 99);
let wire_transfer = WireTransferRequest::new(security_emitter, &payee_account, 1000, 777);;
let wire_transfer = WireTransferRequest::new(security_emitter, &payee_account, 1000, 777);
cash_wire_transfer_request(&mut tx, &wire_transfer)?;
tx.commit().unwrap();
@@ -599,7 +639,9 @@ mod test_wire_transfers {
let ret = cash_wire_transfer_request(&mut tx, &wire_transfer);
match ret {
Err(Error::DoubleSpendingError(_)) => Ok(()),
_ => Err(Error::Testing("Expected DoubleSpendingError error".to_string()))
_ => Err(Error::Testing(
"Expected DoubleSpendingError error".to_string(),
)),
}
}
}
+1 -1
View File
@@ -70,7 +70,7 @@ fn create_views_scope() -> Scope {
.service(views::account)
.service(views::securities)
.service(views::security)
.service(views::overview)
.service(views::users)
.service(views::overview_submit_button)
.service(views::actions_partials)
.service(views::get_accounts_by_security_selectors)
+3 -3
View File
@@ -62,8 +62,8 @@ pub async fn security(tmpl_env: MiniJinjaRenderer,
tmpl_env.render("security.html", ctx)
}
#[actix_web::get("/overview")]
pub async fn overview(
#[actix_web::get("/users")]
pub async fn users(
tmpl_env: MiniJinjaRenderer,
service_factory: web::Data<ServicesFactory>,
) -> actix_web::Result<impl Responder> {
@@ -74,7 +74,7 @@ pub async fn overview(
let ctx = context! {
users => users,
};
tmpl_env.render("overview.html", ctx)
tmpl_env.render("users.html", ctx)
}
#[actix_web::get("/partials/overview/submit_button")]