Added nice security page.
This commit is contained in:
1 parent
18e7b0df1c
commit
509794f02d
12 files changed
+102
-24
No files matched your search
@@ -39,13 +39,16 @@ nix develop
|
|||||||
|
|
||||||
#### Architecture
|
#### 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
|
#### Backend
|
||||||
|
|
||||||
* [ ] Parametrize `CURRENT_TIMESTAMP`
|
* [ ] Parametrize `CURRENT_TIMESTAMP`
|
||||||
* [ ] Make certain the user that creates the event is allowed to do it.
|
* [ ] 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
|
#### Front end
|
||||||
|
|
||||||
|
|||||||
@@ -8,8 +8,8 @@
|
|||||||
|
|
||||||
<table class="uk-table uk-table-divider uk-table-striped uk-table-hover uk-table-small">
|
<table class="uk-table uk-table-divider uk-table-striped uk-table-hover uk-table-small">
|
||||||
<caption>Summary</caption>
|
<caption>Summary</caption>
|
||||||
<tr><th>Micro ID</th><td>{{ account.id | micro_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>{{ account.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>Label</th><td>{{ account.label }}</td></tr>
|
||||||
<tr><th>Amount</th><td>{{ account.amount | thousand_separator }}</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>
|
<tr><th>Account Type</th><td>{{ account.account_type | replace("_", " ") | lower}}</td></tr>
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
{% for account in accounts %}
|
{% for account in accounts %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/accounts/{{ account.id }}"><span uk-icon="info"></span></a></td>
|
<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.label }}</td>
|
||||||
<td>{{ account.amount | thousand_separator }}</td>
|
<td>{{ account.amount | thousand_separator }}</td>
|
||||||
<td style="font-variant: small-caps;">{{ account.account_type | replace("_", " ") | lower}}</td>
|
<td style="font-variant: small-caps;">{{ account.account_type | replace("_", " ") | lower}}</td>
|
||||||
|
|||||||
@@ -20,10 +20,10 @@
|
|||||||
{% for sec in securities %}
|
{% for sec in securities %}
|
||||||
<tr>
|
<tr>
|
||||||
<td><a href="/securities/{{ sec.id }}"><span uk-icon="info"></span></a></td>
|
<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.name }}</td>
|
||||||
<td>{{ sec.symbol }}</td>
|
<td style="font-variant: small-caps;">{{ sec.symbol | lower }}</td>
|
||||||
<td>{{ sec.security_type }}</td>
|
<td style="font-variant: small-caps;">{{ sec.security_type }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -4,7 +4,18 @@
|
|||||||
|
|
||||||
{% block body %}
|
{% 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 %}
|
{% endblock %}
|
||||||
@@ -397,6 +397,31 @@ pub fn add_to_account(
|
|||||||
return ret;
|
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)]
|
#[cfg(test)]
|
||||||
mod test_accounts {
|
mod test_accounts {
|
||||||
use rusqlite::{Connection, Transaction};
|
use rusqlite::{Connection, Transaction};
|
||||||
|
|||||||
@@ -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 serde::Serialize;
|
||||||
|
|
||||||
use crate::db::_external_id::{Id, SecurityId};
|
use crate::db::_external_id::{Id, SecurityId};
|
||||||
@@ -104,18 +104,19 @@ pub struct NewSecurity {
|
|||||||
pub name: String,
|
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
|
let q = "INSERT INTO
|
||||||
securities(name, symbol, security_type)
|
securities(name, symbol, security_type)
|
||||||
VALUES(?1, ?2, ?3)";
|
VALUES(?1, ?2, ?3)";
|
||||||
let params = (&sec.name, &sec.symbol, &sec.security_type);
|
let params = (&sec.name, &sec.symbol, &sec.security_type);
|
||||||
tx.execute(q, params).map_err(Error::SqlError)?;
|
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,
|
let q = "SELECT id AS sec_id,
|
||||||
security_type AS sec_security_type,
|
security_type AS sec_security_type,
|
||||||
symbol AS sec_symbol,
|
symbol AS sec_symbol,
|
||||||
@@ -123,7 +124,9 @@ pub fn get_security_by_id(tx: &Transaction, id: i32) -> CustomResult<Security> {
|
|||||||
FROM securities
|
FROM securities
|
||||||
WHERE id = ?1";
|
WHERE id = ?1";
|
||||||
tx.query_row(q, params![id], Security::from_row)
|
tx.query_row(q, params![id], Security::from_row)
|
||||||
|
.optional()
|
||||||
.map_err(Error::SqlError)
|
.map_err(Error::SqlError)
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_securities(conn: &Connection) -> CustomResult<Vec<Security>> {
|
pub fn list_securities(conn: &Connection) -> CustomResult<Vec<Security>> {
|
||||||
|
|||||||
+2
-1
@@ -30,7 +30,8 @@ pub mod accounts {
|
|||||||
pub use super::_accounts::{
|
pub use super::_accounts::{
|
||||||
add_to_account, create_checking_account, create_security_emitter_account,
|
add_to_account, create_checking_account, create_security_emitter_account,
|
||||||
get_account_by_id, is_owner, list_accounts, Account, AccountChangeReceipt,
|
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,6 +11,11 @@ use crate::services::_securities::SecurityDTO;
|
|||||||
pub struct AccountService<'a> {
|
pub struct AccountService<'a> {
|
||||||
conn: &'a Connection,
|
conn: &'a Connection,
|
||||||
}
|
}
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct AccountNumbers {
|
||||||
|
checking: i64,
|
||||||
|
security_emitter: i64,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Deserialize, Serialize, Debug)]
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
pub struct AccountDTO {
|
pub struct AccountDTO {
|
||||||
@@ -42,8 +47,6 @@ impl AccountDTO {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
impl AccountService<'_> {
|
impl AccountService<'_> {
|
||||||
|
|
||||||
pub fn new(conn: & Connection) -> AccountService{
|
pub fn new(conn: & Connection) -> AccountService{
|
||||||
@@ -80,7 +83,12 @@ impl AccountService<'_> {
|
|||||||
amounts: ac.amount().clone(),
|
amounts: ac.amount().clone(),
|
||||||
timestamps: ac.timestamps().clone(),
|
timestamps: ac.timestamps().clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(ret)
|
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})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,7 +1,9 @@
|
|||||||
use crate::db::external_id::SecurityId;
|
use crate::db::external_id::SecurityId;
|
||||||
use crate::db::securities::{list_securities, Security};
|
use crate::db::securities::{Security};
|
||||||
use rusqlite::Connection;
|
use rusqlite::Connection;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
use crate::db::{accounts, securities};
|
||||||
|
use crate::util::CustomResult;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug)]
|
#[derive(Serialize, Deserialize, Debug)]
|
||||||
pub struct SecurityDTO {
|
pub struct SecurityDTO {
|
||||||
@@ -31,11 +33,20 @@ impl SecurityService<'_> {
|
|||||||
SecurityService { conn }
|
SecurityService { conn }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn list_securities(self) -> Vec<SecurityDTO> {
|
pub fn list_securities(&self) -> Vec<SecurityDTO> {
|
||||||
list_securities(self.conn)
|
securities::list_securities(self.conn)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.iter()
|
.iter()
|
||||||
.map(SecurityDTO::from_entity)
|
.map(SecurityDTO::from_entity)
|
||||||
.collect()
|
.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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -65,6 +65,5 @@ impl ServicesFactory {
|
|||||||
conn: &'a PooledConnection<SqliteConnectionManager>,
|
conn: &'a PooledConnection<SqliteConnectionManager>,
|
||||||
)-> Result<AccountService> {
|
)-> Result<AccountService> {
|
||||||
Ok(AccountService::new(conn))
|
Ok(AccountService::new(conn))
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+19
-2
@@ -65,10 +65,27 @@ pub async fn securities(tmpl_env: MiniJinjaRenderer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::get("/securities/{sec_id}")]
|
#[actix_web::get("/securities/{sec_id}")]
|
||||||
pub async fn security(tmpl_env: MiniJinjaRenderer,
|
pub async fn security(req: HttpRequest,
|
||||||
|
tmpl_env: MiniJinjaRenderer,
|
||||||
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
|
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(¶m.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)
|
tmpl_env.render("security.html", ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user