Irvin Blais rock!
This commit is contained in:
1 parent
f2c6c8d65c
commit
0d0337a6aa
17 files changed
+175
-39
No files matched your search
@@ -4,7 +4,31 @@
|
||||
|
||||
{% block body %}
|
||||
|
||||
<div>bob le chef</div>
|
||||
<table class="uk-table uk-table-divider uk-table-striped uk-table-hover uk-table-small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>id</th>
|
||||
<th>Amount</th>
|
||||
<th>Account Type</th>
|
||||
<th>Last Update</th>
|
||||
<th>Security</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for account in accounts %}
|
||||
<tr>
|
||||
<td><a href="/accounts/{{ account.id }}"><span uk-icon="info"></span></a></td>
|
||||
<td>{{ account.id }}</td>
|
||||
<td>{{ account.amount }}</td>
|
||||
<td>{{ account.account_type }}</td>
|
||||
<td>{{ account.last_update }}</td>
|
||||
<td>{{ account.security_symbol }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
{% endblock %}
|
||||
@@ -7,6 +7,7 @@
|
||||
<ul class="uk-navbar-nav">
|
||||
{% set nav_bar_items = [{'id': 'overview', 'label': 'Overview'},
|
||||
{'id': 'accounts', 'label': 'Accounts'},
|
||||
{'id': 'securities', 'label': 'Securities'},
|
||||
{'id': 'actions', 'label': 'Actions'}]%}
|
||||
{% for itm in nav_bar_items %}
|
||||
<li class="{% if active_navbar_item == itm.id %}uk-active"{% endif %}"><a href="{{ itm.id }}">{{ itm.label }}</a></li>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
|
||||
<div class="uk-margin">
|
||||
<label class="uk-form-label"> Security
|
||||
<select class="uk-select">
|
||||
<select class="uk-select" name="event.CreateNewAccount.securityId">
|
||||
{% for sec in securities %}
|
||||
<option value="{{ sec.id }}">{{ sec.name }} ({{ sec.symbol }}) </option>
|
||||
{% endfor %}
|
||||
@@ -10,4 +10,7 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="event.CreateNewAccount.userId" value="{{ user.id }}">
|
||||
|
||||
|
||||
{%include "partials/actions/submit_button.html"%}
|
||||
@@ -30,4 +30,6 @@
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<input type="hidden" name="event.CreateNewSecurity.userId" value="{{ user.id }}">
|
||||
|
||||
{%include "partials/actions/submit_button.html"%}
|
||||
@@ -0,0 +1,34 @@
|
||||
{% set active_navbar_item = 'securities' %}
|
||||
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block body %}
|
||||
|
||||
|
||||
<table class="uk-table uk-table-divider uk-table-striped uk-table-hover uk-table-small">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>Id</th>
|
||||
<th>Name</th>
|
||||
<th>Symbol</th>
|
||||
<th>Type</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{% for sec in securities %}
|
||||
<tr>
|
||||
<td><a href="/securities/{{ sec.id }}"><span uk-icon="info"></span></a></td>
|
||||
<td>{{ sec.id }}</td>
|
||||
<td>{{ sec.name }}</td>
|
||||
<td>{{ sec.symbol }}</td>
|
||||
<td>{{ sec.security_type }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
|
||||
</table>
|
||||
|
||||
|
||||
{% endblock %}
|
||||
+24
-5
@@ -1,7 +1,7 @@
|
||||
use std::fmt;
|
||||
|
||||
use regex::{Captures, Regex};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::{Deserialize, Deserializer, Serialize, Serializer};
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
struct SafeId {
|
||||
@@ -101,16 +101,35 @@ macro_rules! impl_ids {
|
||||
self.0.get_id()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl Serialize for $T {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> where S: Serializer {
|
||||
serializer.serialize_str(self.to_string().as_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'de> Deserialize<'de> for $T {
|
||||
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: Deserializer<'de>{
|
||||
let s = String::deserialize(deserializer)?;
|
||||
$T::from_string(&s).ok_or(serde::de::Error::custom("invalid string format"))
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct AccountId(SafeId);
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct UserId(SafeId);
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(PartialEq, Debug, Clone)]
|
||||
pub struct SecurityId(SafeId);
|
||||
|
||||
impl_ids!(AccountId, "a");
|
||||
@@ -118,7 +137,7 @@ impl_ids!(UserId, "u");
|
||||
impl_ids!(SecurityId, "s");
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum ExternalId {
|
||||
pub enum ExternalId { // todo: perhaps this abstraction is useless.. duno
|
||||
AccountId(AccountId),
|
||||
UserId(UserId),
|
||||
SecurityId(SecurityId),
|
||||
|
||||
+1
-1
@@ -38,7 +38,7 @@ fn _user_from_row(row: &Row) -> Result<User> {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_user_by_id(tx: &Transaction, id: & UserId) -> CustomResult<Option<User>> {
|
||||
pub fn get_user_by_id(tx: &Connection, id: & UserId) -> CustomResult<Option<User>> {
|
||||
let q = "SELECT id, name, public_key
|
||||
FROM users
|
||||
WHERE id = ?1";
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@ pub mod securities {
|
||||
}
|
||||
|
||||
pub mod users {
|
||||
pub use super::_users::{create_user, list_users, get_user_by_name, User};
|
||||
pub use super::_users::{create_user, list_users, get_user_by_name, get_user_by_id, User};
|
||||
}
|
||||
|
||||
pub mod accounts {
|
||||
|
||||
+11
-2
@@ -1,8 +1,9 @@
|
||||
use rusqlite::{Connection, Transaction};
|
||||
use crate::db::accounts::{create_checking_account, create_security_emitter_account};
|
||||
|
||||
use crate::db::securities::create_security;
|
||||
use crate::db::users::create_user;
|
||||
use crate::events::{CreateNewSecurity, CreateNewUser, Event};
|
||||
use crate::events::{CreateNewAccount, CreateNewSecurity, CreateNewUser, Event};
|
||||
use crate::util::{CustomResult, Error};
|
||||
|
||||
fn process_create_new_security_event(
|
||||
@@ -10,7 +11,9 @@ fn process_create_new_security_event(
|
||||
event: CreateNewSecurity,
|
||||
) -> CustomResult<()> {
|
||||
let r = event.to_create_new_security().unwrap();
|
||||
create_security(tx, &r)?;
|
||||
let sec = create_security(tx, &r)?;
|
||||
create_security_emitter_account(&tx, &sec.id(), &event.userId)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -19,11 +22,17 @@ fn process_create_new_user_event(tx: &mut Transaction, event: CreateNewUser) ->
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_create_new_account_event(tx: &mut Transaction, event: CreateNewAccount) -> CustomResult<()> {
|
||||
create_checking_account(&tx, &event.securityId, &event.userId)?;
|
||||
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)?,
|
||||
};
|
||||
tx.commit().map_err(Error::SqlError)
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use crate::db::external_id::{SecurityId, UserId};
|
||||
|
||||
use crate::db::securities::{NewSecurity, SecurityType};
|
||||
|
||||
@@ -7,6 +8,7 @@ pub struct CreateNewSecurity {
|
||||
pub security_type: String,
|
||||
pub symbol: String,
|
||||
pub name: String,
|
||||
pub userId: UserId,
|
||||
}
|
||||
|
||||
impl CreateNewSecurity {
|
||||
@@ -36,10 +38,17 @@ pub struct CreateNewUser {
|
||||
pub public_key: String,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub struct CreateNewAccount {
|
||||
pub securityId: SecurityId,
|
||||
pub userId: UserId,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
pub enum Event {
|
||||
CreateNewSecurity(CreateNewSecurity),
|
||||
CreateNewUser(CreateNewUser),
|
||||
CreateNewAccount(CreateNewAccount),
|
||||
}
|
||||
|
||||
impl Event {
|
||||
|
||||
@@ -38,14 +38,8 @@ impl AccountService<'_> {
|
||||
AccountService{conn}
|
||||
}
|
||||
|
||||
pub fn get_user_accounts(& self, user_id: String) -> CustomResult<Vec<AccountDTO>>{
|
||||
let p = UserId::from_string(&user_id);
|
||||
if p.is_none() {
|
||||
let msg = format!("{} is an invalid user id.", &user_id);
|
||||
return Err(Error::UserInputError(msg));
|
||||
}
|
||||
|
||||
let ret = list_user_accounts(&self.conn, &p.unwrap())?;
|
||||
pub fn get_user_accounts(& self, user_id: &UserId) -> CustomResult<Vec<AccountDTO>>{
|
||||
let ret = list_user_accounts(&self.conn, user_id)?;
|
||||
Ok(ret.iter().map(AccountDTO::from_entity).collect())
|
||||
}
|
||||
|
||||
|
||||
@@ -4,10 +4,13 @@ use actix_web::HttpRequest;
|
||||
use cookie::time::OffsetDateTime;
|
||||
use cookie::{Cookie, CookieJar, Key};
|
||||
use rusqlite::Connection;
|
||||
use crate::db::external_id::{UserId, Id};
|
||||
use crate::services::_users::UserDTO;
|
||||
use crate::services::UserService;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SessionService<'a> {
|
||||
key: &'a Key,
|
||||
user_service: UserService<'a>,
|
||||
}
|
||||
|
||||
impl SessionService<'_> {
|
||||
@@ -18,8 +21,8 @@ impl SessionService<'_> {
|
||||
Ok(Key::derive_from(&secret))
|
||||
}
|
||||
|
||||
pub fn from_key(key: &Key) -> SessionService {
|
||||
SessionService{key}
|
||||
pub fn create<'a>(key: &'a Key, user_service: UserService<'a>) -> SessionService<'a> {
|
||||
SessionService{key, user_service}
|
||||
}
|
||||
|
||||
pub fn create_login_cookie(&self, user_id: &String) -> Cookie {
|
||||
@@ -39,10 +42,25 @@ impl SessionService<'_> {
|
||||
// todo: this service should not be aware of http requests.
|
||||
// basically, it should be easy to migrate of
|
||||
// web framework
|
||||
pub fn get_login(&self, req: &HttpRequest) -> Option<String> {
|
||||
pub fn get_login(&self, req: &HttpRequest) -> Option<UserId> {
|
||||
req.cookie("ledger-user-id")
|
||||
.and_then(|c| self._decrypt_cookie(c))
|
||||
.map(|c| c.value().to_string())
|
||||
.and_then(|s| UserId::from_string(& s))
|
||||
}
|
||||
|
||||
pub fn get_logged_in_user(&self, req: &HttpRequest) -> CustomResult<Option<UserDTO>> {
|
||||
let ret = req.cookie("ledger-user-id")
|
||||
.and_then(|c| self._decrypt_cookie(c))
|
||||
.map(|c| c.value().to_string())
|
||||
.and_then(|s| UserId::from_string(& s))
|
||||
.map(|x| self.user_service.get_user(& x));
|
||||
if ret.is_none() {
|
||||
Ok(None)
|
||||
}else {
|
||||
ret.unwrap()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
pub fn logout(&self) -> Cookie {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use rusqlite::Connection;
|
||||
use serde::Deserialize;
|
||||
use serde::Serialize;
|
||||
use crate::db::external_id::UserId;
|
||||
|
||||
use crate::db::users::{get_user_by_name, list_users, User};
|
||||
use crate::db::users::{get_user_by_id, get_user_by_name, list_users, User};
|
||||
use crate::util::CustomResult;
|
||||
|
||||
#[derive(Serialize, Debug)]
|
||||
@@ -57,6 +58,10 @@ impl UserService<'_> {
|
||||
Ok(ret.as_ref().map(UserDTO::from_entity))
|
||||
}
|
||||
|
||||
pub fn get_user(&self, id: &UserId) -> CustomResult<Option<UserDTO>> {
|
||||
let ret = get_user_by_id(self.conn, id)?;
|
||||
Ok(ret.as_ref().map(UserDTO::from_entity))
|
||||
}
|
||||
pub fn list_users(self) -> CustomResult<Page<UserDTO>> {
|
||||
let users = list_users(self.conn)?
|
||||
.iter()
|
||||
|
||||
@@ -28,7 +28,7 @@ pub async fn login(
|
||||
return Ok(tmpl_env.render("partials/index/failure.html", context!{})?.customize());
|
||||
}
|
||||
let user = maybeUser.unwrap();
|
||||
let authentication_service = service_factory.get_authentication_service()?;
|
||||
let authentication_service = service_factory.get_authentication_service(&conn)?;
|
||||
let cookie = authentication_service.create_login_cookie(&user.id);
|
||||
let ctx = context! {
|
||||
user => & user,
|
||||
@@ -44,7 +44,8 @@ pub async fn logout(
|
||||
service_factory: web::Data<ServicesFactory>,
|
||||
tmpl_env: MiniJinjaRenderer,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let authentication_service = service_factory.get_authentication_service()?;
|
||||
let conn = service_factory.get_connection()?;
|
||||
let authentication_service = service_factory.get_authentication_service(&conn)?;
|
||||
let res = tmpl_env.render("partials/index/form.html", context! {})?;
|
||||
Ok(res
|
||||
.customize()
|
||||
@@ -58,15 +59,12 @@ pub async fn index(
|
||||
tmpl_env: MiniJinjaRenderer,
|
||||
) -> actix_web::Result<impl Responder> {
|
||||
let conn = service_factory.get_connection()?;
|
||||
let authentication_service = service_factory.get_authentication_service()?;
|
||||
let user_service = service_factory.get_user_service(&conn)?;
|
||||
let authentication_service = service_factory.get_authentication_service(&conn)?;
|
||||
let is_logged_in = authentication_service.get_login(&req).is_some();
|
||||
|
||||
let userid = authentication_service.get_login(&req);
|
||||
|
||||
let username = userid.clone().unwrap_or("".to_string());
|
||||
let ctx = context! {
|
||||
is_logged_in => userid.is_some(),
|
||||
// user => user_service.get_user_for_authentication(userid.unwrap(),
|
||||
is_logged_in => is_logged_in,
|
||||
user => authentication_service.get_logged_in_user(&req).unwrap(),
|
||||
};
|
||||
tmpl_env.render("index.html", ctx)
|
||||
}
|
||||
@@ -67,6 +67,7 @@ fn create_views_scope() -> Scope {
|
||||
.service(index_views::logout)
|
||||
.service(views::actions)
|
||||
.service(views::accounts)
|
||||
.service(views::securities)
|
||||
.service(views::overview)
|
||||
.service(views::overview_submit_button)
|
||||
.service(views::actions_partials)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::services::{SecurityService, SessionService, UserService, AccountService};
|
||||
|
||||
use actix_web::{error, Result};
|
||||
use actix_web::{error, Error, Result};
|
||||
use r2d2::PooledConnection;
|
||||
use r2d2_sqlite::SqliteConnectionManager;
|
||||
|
||||
@@ -39,8 +39,11 @@ impl ServicesFactory {
|
||||
.map_err(|_e| error::ErrorBadRequest("Cannot get DB connection."))
|
||||
}
|
||||
|
||||
pub fn get_authentication_service(&self) -> Result<SessionService> {
|
||||
Ok(SessionService::from_key(& self.cache.session_service_key))
|
||||
pub fn get_authentication_service<'a>(
|
||||
&'a self,
|
||||
_conn: &'a PooledConnection<SqliteConnectionManager>,) -> Result<SessionService> {
|
||||
let user_service = self.get_user_service(_conn)?;
|
||||
Ok(SessionService::create(& self.cache.session_service_key, user_service))
|
||||
}
|
||||
|
||||
pub fn get_user_service<'a>(
|
||||
|
||||
+19
-3
@@ -24,14 +24,27 @@ pub async fn accounts(tmpl_env: MiniJinjaRenderer,
|
||||
req: HttpRequest,
|
||||
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
|
||||
let conn = service_factory.get_connection()?;
|
||||
let auth_service = service_factory.get_authentication_service()?;
|
||||
let auth_service = service_factory.get_authentication_service(&conn)?;
|
||||
let user = auth_service.get_login(&req);
|
||||
let account_service = service_factory.get_account_service(& conn)?;
|
||||
let accounts = account_service.get_user_accounts(user.unwrap())
|
||||
.map_err(actix_web::error::ErrorInternalServerError)?;
|
||||
let accounts = account_service.get_user_accounts(&user.unwrap())
|
||||
.map_err(error::ErrorInternalServerError)?;
|
||||
tmpl_env.render("accounts.html", context!{accounts => accounts})
|
||||
}
|
||||
|
||||
|
||||
|
||||
#[actix_web::get("/securities")]
|
||||
pub async fn securities(tmpl_env: MiniJinjaRenderer,
|
||||
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
|
||||
let conn = service_factory.get_connection()?;
|
||||
let account_service = service_factory.get_security_service(& conn)?;
|
||||
let ctx = context!{
|
||||
securities => account_service.list_securities()
|
||||
};
|
||||
tmpl_env.render("securities.html", ctx)
|
||||
}
|
||||
|
||||
#[actix_web::get("/overview")]
|
||||
pub async fn overview(
|
||||
tmpl_env: MiniJinjaRenderer,
|
||||
@@ -60,6 +73,7 @@ pub async fn overview_submit_button(
|
||||
|
||||
#[actix_web::get("/partials/actions/action-form")]
|
||||
pub async fn actions_partials(
|
||||
req: HttpRequest,
|
||||
tmpl_env: MiniJinjaRenderer,
|
||||
query: web::Query<HashMap<String, String>>,
|
||||
service_factory: web::Data<ServicesFactory>,
|
||||
@@ -70,9 +84,11 @@ pub async fn actions_partials(
|
||||
.to_string();
|
||||
let conn = service_factory.get_connection()?;
|
||||
let security_service = service_factory.get_security_service(&conn)?;
|
||||
let authentication_service = service_factory.get_authentication_service(&conn)?;
|
||||
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)?,
|
||||
};
|
||||
let template_location = format!("partials/actions/{:}.html", event_type);
|
||||
info!("Getting template at: {:?}", template_location);
|
||||
|
||||
Reference in new issue
Block a user