Added nice security page.

This commit is contained in:
didier amyot committed 2023-04-11 09:05:31 -04:00
1 parent 18e7b0df1c
commit 509794f02d
12 files changed
+103 -25

No files matched your search

+5 -2
View File
@@ -39,13 +39,16 @@ nix develop
#### Architecture
* [x] put all the ressources (migrations, templates and statics) into a ressources directory
* [x] put all the resources (migrations, templates and statics) into a resources directory
#### Backend
* [ ] Parametrize `CURRENT_TIMESTAMP`
* [ ] Make certain the user that creates the event is allowed to do it.
* [ ] sd
* [ ] Add interest rates to currency accounts
* [ ] Add market cap table to be able to track market cap by time
* [ ] Add a minimum amount of security to exchange
#### Front end
+2 -2
View File
@@ -8,8 +8,8 @@
<table class="uk-table uk-table-divider uk-table-striped uk-table-hover uk-table-small">
<caption>Summary</caption>
<tr><th>Micro ID</th><td>{{ account.id | micro_id }}</td></tr>
<tr><th>Account ID</th><td>{{ account.id }}</td></tr>
<tr><th>Micro ID</th><td style="font-family: monospace;">{{ account.id | micro_id }}</td></tr>
<tr><th>Account ID</th><td style="font-family: monospace;">{{ account.id }}</td></tr>
<tr><th>Label</th><td>{{ account.label }}</td></tr>
<tr><th>Amount</th><td>{{ account.amount | thousand_separator }}</td></tr>
<tr><th>Account Type</th><td>{{ account.account_type | replace("_", " ") | lower}}</td></tr>
+1 -1
View File
@@ -21,7 +21,7 @@
{% for account in accounts %}
<tr>
<td><a href="/accounts/{{ account.id }}"><span uk-icon="info"></span></a></td>
<td>{{ account.id | micro_id}}</td>
<td style="font-family: monospace;">{{ account.id | micro_id}}</td>
<td>{{ account.label }}</td>
<td>{{ account.amount | thousand_separator }}</td>
<td style="font-variant: small-caps;">{{ account.account_type | replace("_", " ") | lower}}</td>
+3 -3
View File
@@ -20,10 +20,10 @@
{% for sec in securities %}
<tr>
<td><a href="/securities/{{ sec.id }}"><span uk-icon="info"></span></a></td>
<td>{{ sec.id | micro_id }}</td>
<td style="font-family: monospace;">{{ sec.id | micro_id }}</td>
<td>{{ sec.name }}</td>
<td>{{ sec.symbol }}</td>
<td>{{ sec.security_type }}</td>
<td style="font-variant: small-caps;">{{ sec.symbol | lower }}</td>
<td style="font-variant: small-caps;">{{ sec.security_type }}</td>
</tr>
{% endfor %}
</tbody>
+12 -1
View File
@@ -4,7 +4,18 @@
{% block body %}
<h1>Security!</h1>
<table class="uk-table uk-table-divider uk-table-striped uk-table-hover uk-table-small">
<caption>Summary</caption>
<tr><th>Micro ID</th><td style="font-family: monospace;">{{ security.id | micro_id }}</td></tr>
<tr><th>Account ID</th><td style="font-family: monospace;">{{ security.id }}</td></tr>
<tr><th>Name</th><td>{{ security.name }}</td></tr>
<tr><th>Symbol</th><td style="font-variant: small-caps;">{{ security.symbol | lower }}</td></tr>
<tr><th>Security Type</th><td style="font-variant: small-caps;">{{ security.security_type }}</td></tr>
<tr><th>Number of Checking Accounts</th><td>{{ account_numbers.checking }}</td></tr>
<tr><th>Number of Security Emitter Accounts</th><td>{{ account_numbers.security_emitter }}</td></tr>
<tr><th>Market Cap</th><td>{{ market_cap | thousand_separator}}</td></tr>
</table>
{% endblock %}
+25
View File
@@ -397,6 +397,31 @@ pub fn add_to_account(
return ret;
}
pub fn get_number_of_accounts(tx: &Connection,
sec: &SecurityId,
account_type: AccountType) -> CustomResult<i64>{
let q = "
SELECT COUNT(*) AS count
FROM accounts
WHERE security = ?
AND account_type = ?
";
let params = params![sec, account_type.to_string()];
tx.query_row(q, params, |row| row.get("count")).map_err(Error::SqlError)
}
pub fn get_security_market_cap(tx: &Connection,
sec: &SecurityId) -> CustomResult<i64> {
let q = "
SELECT -1 * SUM(amount) AS count
FROM accounts
WHERE security = ?
AND account_type = ?
";
let params = params![sec, AccountType::SecurityEmitter.to_string()];
tx.query_row(q, params, |row| row.get("count")).map_err(Error::SqlError)
}
#[cfg(test)]
mod test_accounts {
use rusqlite::{Connection, Transaction};
+8 -5
View File
@@ -1,4 +1,4 @@
use rusqlite::{params, Connection, Result, Row, ToSql, Transaction};
use rusqlite::{params, Connection, Result, Row, ToSql, Transaction, OptionalExtension};
use serde::Serialize;
use crate::db::_external_id::{Id, SecurityId};
@@ -104,18 +104,19 @@ pub struct NewSecurity {
pub name: String,
}
pub fn create_security(tx: &Transaction, sec: &NewSecurity) -> CustomResult<Security> {
pub fn create_security(tx: &Connection, sec: &NewSecurity) -> CustomResult<Security> {
let q = "INSERT INTO
securities(name, symbol, security_type)
VALUES(?1, ?2, ?3)";
let params = (&sec.name, &sec.symbol, &sec.security_type);
tx.execute(q, params).map_err(Error::SqlError)?;
let new_sec_id = tx.last_insert_rowid() as i32;
let new_sec_id:i64 = tx.last_insert_rowid();
return get_security_by_id(tx, new_sec_id);
let ret = get_security_by_id(tx, &SecurityId::from_id(new_sec_id))?;
Ok(ret.unwrap())
}
pub fn get_security_by_id(tx: &Transaction, id: i32) -> CustomResult<Security> {
pub fn get_security_by_id(tx: &Connection, id: & SecurityId) -> CustomResult<Option<Security>> {
let q = "SELECT id AS sec_id,
security_type AS sec_security_type,
symbol AS sec_symbol,
@@ -123,7 +124,9 @@ pub fn get_security_by_id(tx: &Transaction, id: i32) -> CustomResult<Security> {
FROM securities
WHERE id = ?1";
tx.query_row(q, params![id], Security::from_row)
.optional()
.map_err(Error::SqlError)
}
pub fn list_securities(conn: &Connection) -> CustomResult<Vec<Security>> {
+2 -1
View File
@@ -30,7 +30,8 @@ pub mod accounts {
pub use super::_accounts::{
add_to_account, create_checking_account, create_security_emitter_account,
get_account_by_id, is_owner, list_accounts, Account, AccountChangeReceipt,
list_user_accounts, AccountType, get_checking_accounts_for_sec_id
list_user_accounts, AccountType, get_checking_accounts_for_sec_id, get_number_of_accounts,
get_security_market_cap
};
}
+11 -3
View File
@@ -11,6 +11,11 @@ use crate::services::_securities::SecurityDTO;
pub struct AccountService<'a> {
conn: &'a Connection,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct AccountNumbers {
checking: i64,
security_emitter: i64,
}
#[derive(Deserialize, Serialize, Debug)]
pub struct AccountDTO {
@@ -42,8 +47,6 @@ impl AccountDTO {
}
impl AccountService<'_> {
pub fn new(conn: & Connection) -> AccountService{
@@ -80,7 +83,12 @@ impl AccountService<'_> {
amounts: ac.amount().clone(),
timestamps: ac.timestamps().clone(),
};
Ok(ret)
}
pub fn get_number_of_accounts(&self, sec_id: &SecurityId) -> CustomResult<AccountNumbers>{
let checking = accounts::get_number_of_accounts(self.conn, sec_id, AccountType::Checking)?;
let security_emitter = accounts::get_number_of_accounts(self.conn, sec_id, AccountType::SecurityEmitter)?;
Ok(AccountNumbers{checking, security_emitter})
}
}
+14 -3
View File
@@ -1,7 +1,9 @@
use crate::db::external_id::SecurityId;
use crate::db::securities::{list_securities, Security};
use crate::db::securities::{Security};
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
use crate::db::{accounts, securities};
use crate::util::CustomResult;
#[derive(Serialize, Deserialize, Debug)]
pub struct SecurityDTO {
@@ -31,11 +33,20 @@ impl SecurityService<'_> {
SecurityService { conn }
}
pub fn list_securities(self) -> Vec<SecurityDTO> {
list_securities(self.conn)
pub fn list_securities(&self) -> Vec<SecurityDTO> {
securities::list_securities(self.conn)
.unwrap()
.iter()
.map(SecurityDTO::from_entity)
.collect()
}
pub fn get_security_by_id(&self, sec_id: &SecurityId) -> CustomResult<Option<SecurityDTO>> {
let ret = securities::get_security_by_id(self.conn, sec_id)?;
Ok(ret.map(|sec|SecurityDTO::from_entity(&sec)))
}
pub fn get_security_market_cap(&self, sec_id: &SecurityId)-> CustomResult<i64> {
accounts::get_security_market_cap(self.conn, sec_id)
}
}
-1
View File
@@ -65,6 +65,5 @@ impl ServicesFactory {
conn: &'a PooledConnection<SqliteConnectionManager>,
)-> Result<AccountService> {
Ok(AccountService::new(conn))
}
}
+20 -3
View File
@@ -65,10 +65,27 @@ pub async fn securities(tmpl_env: MiniJinjaRenderer,
}
#[actix_web::get("/securities/{sec_id}")]
pub async fn security(tmpl_env: MiniJinjaRenderer,
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
pub async fn security(req: HttpRequest,
tmpl_env: MiniJinjaRenderer,
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
let conn = service_factory.get_connection()?;
let security_service = service_factory.get_security_service(& conn)?;
let account_service = service_factory.get_account_service(& conn)?;
let sec_id = &req.match_info()
.get("sec_id")
.and_then(|param|SecurityId::from_string(&param.to_string()))
.ok_or(error::ErrorNotFound("Invalid sec-id parameters"))?;
let ctx = context!{};
let sec = security_service.get_security_by_id(sec_id).map_err(error::ErrorInternalServerError)?;
if sec.is_none() {
return Err(error::ErrorNotFound(format!("No security with id {:}", sec_id)))
}
let ctx = context!{
security => sec.unwrap(),
account_numbers => account_service.get_number_of_accounts(sec_id).map_err(error::ErrorInternalServerError)?,
market_cap => security_service.get_security_market_cap(sec_id).map_err(error::ErrorInternalServerError)?,
};
tmpl_env.render("security.html", ctx)
}