diff --git a/README.md b/README.md
index 6c822bd..8fc7ae1 100644
--- a/README.md
+++ b/README.md
@@ -26,6 +26,15 @@ The rust source must be `/nix/store/pmih7jv9r5xfaif92bza8w05876qvnvc-rust-src/`
nix develop
```
+## Technologies
+
+### Front-end
+
+* htmx
+* https://dc-js.github.io/dc.js/
+* https://dygraphs.com/
+* https://github.com/aftertheflood/sparks
+
## Todos
#### Architecture
@@ -35,10 +44,12 @@ nix develop
#### Backend
* [ ] Parametrize `CURRENT_TIMESTAMP`
+* [ ] Make certain the user that creates the event is allowed to do it.
+* [ ] sd
#### Front end
-* [ ] Serve static files using actix
-* [ ] Hack https://github.com/bigskysoftware/htmx/blob/master/src/ext/json-enc.js to all embedded fields.
-* [ ] Create UI to create user
-* [ ] Create UI to create security
+* [x] Serve static files using actix
+* [x] Hack https://github.com/bigskysoftware/htmx/blob/master/src/ext/json-enc.js to all embedded fields.
+* [x] Create UI to create user
+* [x] Create UI to create security
diff --git a/resources/templates/account.html b/resources/templates/account.html
new file mode 100644
index 0000000..27670f8
--- /dev/null
+++ b/resources/templates/account.html
@@ -0,0 +1,10 @@
+{% set active_navbar_item = 'accounts' %}
+
+{% extends "base.html" %}
+
+{% block body %}
+
+
Account!
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/resources/templates/accounts.html b/resources/templates/accounts.html
index 5dbabf9..5f5feb1 100644
--- a/resources/templates/accounts.html
+++ b/resources/templates/accounts.html
@@ -22,9 +22,9 @@
|
{{ account.id }} |
{{ account.amount }} |
- {{ account.account_type }} |
+ {{ account.account_type | replace("_", " ") | lower}} |
{{ account.last_update }} |
- {{ account.security_symbol }} |
+ {{ account.security.symbol }} |
{% endfor %}
diff --git a/resources/templates/actions.html b/resources/templates/actions.html
index dc3b270..7793d9e 100644
--- a/resources/templates/actions.html
+++ b/resources/templates/actions.html
@@ -16,10 +16,11 @@
hx-get="/partials/actions/action-form"
hx-trigger="change,load"
hx-target="#action-form"
- hx-swap="innerHTML">>
+ hx-swap="innerHTML">
+
diff --git a/resources/templates/components/navbar.html b/resources/templates/components/navbar.html
index 065bf28..ce1d57a 100644
--- a/resources/templates/components/navbar.html
+++ b/resources/templates/components/navbar.html
@@ -10,7 +10,7 @@
{'id': 'securities', 'label': 'Securities'},
{'id': 'actions', 'label': 'Actions'}]%}
{% for itm in nav_bar_items %}
- {{ itm.label }}
+ {{ itm.label }}
{% endfor %}
diff --git a/resources/templates/partials/actions/accounts-by-security-selectors.html b/resources/templates/partials/actions/accounts-by-security-selectors.html
new file mode 100644
index 0000000..795b00e
--- /dev/null
+++ b/resources/templates/partials/actions/accounts-by-security-selectors.html
@@ -0,0 +1,3 @@
+{% for account in accounts %}
+
+{% endfor %}
\ No newline at end of file
diff --git a/resources/templates/partials/actions/emit_securities.html b/resources/templates/partials/actions/emit_securities.html
new file mode 100644
index 0000000..10daa07
--- /dev/null
+++ b/resources/templates/partials/actions/emit_securities.html
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+{%include "partials/actions/submit_button.html"%}
diff --git a/resources/templates/security.html b/resources/templates/security.html
new file mode 100644
index 0000000..a9eb4d9
--- /dev/null
+++ b/resources/templates/security.html
@@ -0,0 +1,10 @@
+{% set active_navbar_item = 'securities' %}
+
+{% extends "base.html" %}
+
+{% block body %}
+
+Security!
+
+
+{% endblock %}
\ No newline at end of file
diff --git a/src/db/_accounts.rs b/src/db/_accounts.rs
index 676b483..898875c 100644
--- a/src/db/_accounts.rs
+++ b/src/db/_accounts.rs
@@ -195,6 +195,31 @@ pub fn get_account_by_id(tx: &Connection, id: &AccountId) -> CustomResult CustomResult> {
+ let q = "
+ SELECT acc.id AS acc_id,
+ acc.amount AS acc_amount,
+ UNIXEPOCH(acc.timestamp) AS acc_timestamp,
+ account_type AS acc_account_type,
+
+ sec.id AS sec_id,
+ sec.symbol AS sec_symbol,
+ sec.security_type AS sec_security_type,
+ sec.name AS sec_name
+ FROM accounts acc
+ JOIN securities sec ON acc.security = sec.id
+ WHERE sec.id = ?1";
+ let mut stmt = tx.prepare(q).map_err(Error::SqlError)?;
+ let ret: Vec = stmt
+ .query_map(params![&sec_id], &Account::from_row)
+ .map_err(Error::SqlError)?
+ .filter(Result::is_ok)
+ .map(Result::unwrap)
+ .collect();
+ return Ok(ret);
+
+}
+
fn create_account(
tx: &Connection,
sec: &SecurityId,
diff --git a/src/db/_external_id.rs b/src/db/_external_id.rs
index 4804da5..aabb68e 100644
--- a/src/db/_external_id.rs
+++ b/src/db/_external_id.rs
@@ -125,7 +125,6 @@ macro_rules! impl_ids {
#[derive(PartialEq, Debug, Clone)]
pub struct AccountId(SafeId);
-
#[derive(PartialEq, Debug, Clone)]
pub struct UserId(SafeId);
@@ -137,7 +136,7 @@ impl_ids!(UserId, "u");
impl_ids!(SecurityId, "s");
#[derive(PartialEq, Debug)]
-pub enum ExternalId { // todo: perhaps this abstraction is useless.. duno
+enum ExternalId { // todo: perhaps this abstraction is useless.. duno
AccountId(AccountId),
UserId(UserId),
SecurityId(SecurityId),
diff --git a/src/db/mod.rs b/src/db/mod.rs
index be0f22d..7edfb4d 100644
--- a/src/db/mod.rs
+++ b/src/db/mod.rs
@@ -29,7 +29,7 @@ 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
+ list_user_accounts, AccountType, get_checking_accounts_for_sec_id
};
}
diff --git a/src/event_processor.rs b/src/event_processor.rs
index 8dfa67b..e0e1333 100644
--- a/src/event_processor.rs
+++ b/src/event_processor.rs
@@ -3,7 +3,7 @@ use crate::db::accounts::{create_checking_account, create_security_emitter_accou
use crate::db::securities::create_security;
use crate::db::users::create_user;
-use crate::events::{CreateNewAccount, CreateNewSecurity, CreateNewUser, Event};
+use crate::events::{CreateNewAccount, CreateNewSecurity, CreateNewUser, EmitSecurities, Event};
use crate::util::{CustomResult, Error};
fn process_create_new_security_event(
@@ -27,12 +27,19 @@ fn process_create_new_account_event(tx: &mut Transaction, event: CreateNewAccoun
Ok(())
}
+fn process_emit_securities_event(tx: &mut Transaction, event: EmitSecurities) -> CustomResult<()> {
+ // create_checking_account(&tx, &event.securityId, &event.userId)?;
+ println!("Here it goes > {:?}", event);
+ Ok(())
+}
+
pub fn process_event(conn: &mut Connection, event: Event) -> CustomResult<()> {
let mut tx = conn.transaction().map_err(Error::SqlError)?;
match event {
Event::CreateNewSecurity(e) => process_create_new_security_event(&mut tx, e)?,
Event::CreateNewUser(e) => process_create_new_user_event(&mut tx, e)?,
Event::CreateNewAccount(e) => process_create_new_account_event(&mut tx, e)?,
+ Event::EmitSecurities(e) => process_emit_securities_event(&mut tx, e)?,
};
tx.commit().map_err(Error::SqlError)
}
diff --git a/src/events.rs b/src/events.rs
index 9a09f1d..f8a1111 100644
--- a/src/events.rs
+++ b/src/events.rs
@@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
-use crate::db::external_id::{SecurityId, UserId};
+use crate::db::external_id::{AccountId, SecurityId, UserId};
use crate::db::securities::{NewSecurity, SecurityType};
@@ -44,11 +44,19 @@ pub struct CreateNewAccount {
pub userId: UserId,
}
+#[derive(Deserialize, Serialize, Debug)]
+pub struct EmitSecurities {
+ pub amount: i64,
+ pub emitter_account: AccountId,
+ pub payee_accounts: Vec,
+}
+
#[derive(Deserialize, Serialize, Debug)]
pub enum Event {
CreateNewSecurity(CreateNewSecurity),
CreateNewUser(CreateNewUser),
CreateNewAccount(CreateNewAccount),
+ EmitSecurities(EmitSecurities),
}
impl Event {
diff --git a/src/services/_accounts.rs b/src/services/_accounts.rs
index b97fcac..d9fa84f 100644
--- a/src/services/_accounts.rs
+++ b/src/services/_accounts.rs
@@ -1,9 +1,12 @@
use rusqlite::Connection;
-use crate::db::accounts::{Account, list_user_accounts};
-use crate::db::external_id::{Id, UserId};
+use crate::db::accounts::{Account, AccountType, get_checking_accounts_for_sec_id, list_user_accounts};
+use crate::db::external_id::{Id, SecurityId, UserId};
use crate::util::{CustomResult, Error};
use serde::Deserialize;
use serde::Serialize;
+use crate::db::securities::Security;
+use crate::services::_securities::SecurityDTO;
+
pub struct AccountService<'a> {
conn: &'a Connection,
}
@@ -14,7 +17,7 @@ pub struct AccountDTO {
pub amount: i64,
pub account_type: String,
pub last_update: i64,
- pub security_symbol: String,
+ pub security: SecurityDTO,
}
impl AccountDTO {
@@ -24,7 +27,7 @@ impl AccountDTO {
amount: account.amount(),
account_type: account.account_type().to_string(),
last_update: account.timestamp(),
- security_symbol: account.security().symbol()
+ security: SecurityDTO::from_entity(account.security())
}
}
}
@@ -43,4 +46,17 @@ impl AccountService<'_> {
Ok(ret.iter().map(AccountDTO::from_entity).collect())
}
+ pub fn get_user_emitter_accounts(& self, user_id: &UserId) -> CustomResult> {
+ let accounts = self.get_user_accounts(user_id)?;
+ let ret = accounts.into_iter()
+ .filter(|acc| acc.account_type == AccountType::SecurityEmitter.to_string())
+ .collect();
+ Ok(ret)
+ }
+
+ pub fn get_checking_accounts_for_security(& self, sec_id: & SecurityId) -> CustomResult>{
+ let ret = get_checking_accounts_for_sec_id(self.conn, sec_id)?;
+ Ok(ret.iter().map(AccountDTO::from_entity).collect())
+
+ }
}
\ No newline at end of file
diff --git a/src/services/_securities.rs b/src/services/_securities.rs
index fce6d90..849fc10 100644
--- a/src/services/_securities.rs
+++ b/src/services/_securities.rs
@@ -3,7 +3,7 @@ use crate::db::securities::{list_securities, Security};
use rusqlite::Connection;
use serde::{Deserialize, Serialize};
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, Debug)]
pub struct SecurityDTO {
id: SecurityId,
symbol: String,
@@ -12,7 +12,7 @@ pub struct SecurityDTO {
}
impl SecurityDTO {
- fn from_entity(sec: &Security) -> SecurityDTO {
+ pub fn from_entity(sec: &Security) -> SecurityDTO {
SecurityDTO {
id: sec.id(),
symbol: sec.symbol(),
diff --git a/src/webui/mod.rs b/src/webui/mod.rs
index 08c34ec..b87024f 100644
--- a/src/webui/mod.rs
+++ b/src/webui/mod.rs
@@ -67,10 +67,13 @@ fn create_views_scope() -> Scope {
.service(index_views::logout)
.service(views::actions)
.service(views::accounts)
+ .service(views::account)
.service(views::securities)
+ .service(views::security)
.service(views::overview)
.service(views::overview_submit_button)
.service(views::actions_partials)
+ .service(views::get_accounts_by_security_selectors)
}
#[tokio::main]
diff --git a/src/webui/views.rs b/src/webui/views.rs
index 16a8216..fee1ee2 100644
--- a/src/webui/views.rs
+++ b/src/webui/views.rs
@@ -8,6 +8,7 @@ use minijinja::context;
use rusqlite::params;
use serde::Deserialize;
use uuid::Uuid;
+use crate::db::external_id::{Id, SecurityId};
use crate::events::Event;
@@ -32,6 +33,14 @@ pub async fn accounts(tmpl_env: MiniJinjaRenderer,
tmpl_env.render("accounts.html", context!{accounts => accounts})
}
+#[actix_web::get("/accounts/{account_id}")]
+pub async fn account(tmpl_env: MiniJinjaRenderer,
+ req: HttpRequest,
+ service_factory: web::Data) -> actix_web::Result {
+
+ tmpl_env.render("account.html", context!{})
+}
+
#[actix_web::get("/securities")]
@@ -45,6 +54,14 @@ pub async fn securities(tmpl_env: MiniJinjaRenderer,
tmpl_env.render("securities.html", ctx)
}
+#[actix_web::get("/securities/{sec_id}")]
+pub async fn security(tmpl_env: MiniJinjaRenderer,
+ service_factory: web::Data) -> actix_web::Result {
+
+ let ctx = context!{};
+ tmpl_env.render("security.html", ctx)
+}
+
#[actix_web::get("/overview")]
pub async fn overview(
tmpl_env: MiniJinjaRenderer,
@@ -71,6 +88,37 @@ pub async fn overview_submit_button(
tmpl_env.render("partials/overview/submit_button.html", ctx)
}
+
+#[actix_web::get("/partials/actions/accounts-by-security-selectors")]
+pub async fn get_accounts_by_security_selectors(
+ req: HttpRequest,
+ tmpl_env: MiniJinjaRenderer,
+ query: web::Query>,
+ service_factory: web::Data,
+) -> actix_web::Result {
+ let sec_id = query
+ .get("sec-id")
+ .and_then(SecurityId::from_string)
+ .ok_or(error::ErrorBadRequest("Invalid sec-id parameters"))?;
+ let conn = service_factory.get_connection()?;
+ let account_service = service_factory.get_account_service(&conn)?;
+ let authentication_service = service_factory.get_authentication_service(&conn)?;
+
+ let maybe_user = authentication_service.get_login(&req);
+ if maybe_user.is_none() {
+ return Err(error::ErrorForbidden(""))
+ }
+ let user_id = maybe_user.unwrap();
+
+ let ctx = context! {
+ sec_id => &sec_id,
+ user_id => &user_id,
+ accounts => account_service.get_checking_accounts_for_security(&sec_id).map_err(error::ErrorInternalServerError)?,
+ };
+ tmpl_env.render("partials/actions/accounts-by-security-selectors.html", ctx)
+}
+
+
#[actix_web::get("/partials/actions/action-form")]
pub async fn actions_partials(
req: HttpRequest,
@@ -84,11 +132,19 @@ pub async fn actions_partials(
.to_string();
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 authentication_service = service_factory.get_authentication_service(&conn)?;
+ let maybeUser = authentication_service.get_login(&req);
+ if maybeUser.is_none() {
+ return Err(actix_web::error::ErrorForbidden(""))
+ }
+ let user = maybeUser.unwrap();
let ctx = context! {
event_type => event_type,
securities => security_service.list_securities(),
- user => authentication_service.get_logged_in_user(&req).map_err(actix_web::error::ErrorInternalServerError)?,
+ emitter_accounts => account_service.get_user_emitter_accounts(&user).map_err(error::ErrorInternalServerError)?,
+ accounts => account_service.get_user_emitter_accounts(&user).map_err(error::ErrorInternalServerError)?,
+ user => authentication_service.get_logged_in_user(&req).map_err(error::ErrorInternalServerError)?,
};
let template_location = format!("partials/actions/{:}.html", event_type);
info!("Getting template at: {:?}", template_location);