Added a bunch of things

This commit is contained in:
didier amyot committed 2023-03-10 16:34:56 -05:00
1 parent c365289914
commit ce92916942
15 files changed
+131 -55

No files matched your search

Generated
+1
View File
@@ -2216,6 +2216,7 @@ version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79" checksum = "1674845326ee10d37ca60470760d4288a6f80f304007d92e5c53bab78c9cfd79"
dependencies = [ dependencies = [
"getrandom",
"serde", "serde",
] ]
+4 -1
View File
@@ -33,7 +33,10 @@ async-channel = "1.8.0"
base64 = "0.21.0" base64 = "0.21.0"
r2d2_sqlite = "0.21.0" r2d2_sqlite = "0.21.0"
r2d2 = "0.8.10" r2d2 = "0.8.10"
uuid = "1.3.0"
[dependencies.uuid]
version = "1.3.0"
features = ["v4"]
[dependencies.apache-avro] [dependencies.apache-avro]
version = "0.14.0" version = "0.14.0"
+1 -1
View File
@@ -13,13 +13,13 @@
<select name="event_type" <select name="event_type"
class="uk-select" class="uk-select"
aria-label="Select" aria-label="Select"
class="uk-button uk-button-default"
hx-get="/partials/actions/action-form" hx-get="/partials/actions/action-form"
hx-trigger="change,load" hx-trigger="change,load"
hx-target="#action-form" hx-target="#action-form"
hx-swap="innerHTML">> hx-swap="innerHTML">>
<option value="create_user">Create User</option> <option value="create_user">Create User</option>
<option value="create_security">Create Security</option> <option value="create_security">Create Security</option>
<option value="create_account">Create Account</option>
</select> </select>
</div> </div>
<input type="hidden" name="initiator" value="bob le chef"> <input type="hidden" name="initiator" value="bob le chef">
@@ -0,0 +1,13 @@
<div class="uk-margin">
<label class="uk-form-label"> Security
<select class="uk-select">
{% for sec in securities %}
<option value="{{ sec.id }}">{{ sec.name }} ({{ sec.symbol }}) </option>
{% endfor %}
</select>
</label>
</div>
{%include "partials/actions/submit_button.html"%}
+4 -2
View File
@@ -108,7 +108,8 @@ pub fn list_accounts<'a>(conn: &Connection) -> CustomResult<Vec<Account>> {
sec.id AS sec_id, sec.id AS sec_id,
sec.symbol AS sec_symbol, sec.symbol AS sec_symbol,
sec.security_type AS sec_security_type sec.security_type AS sec_security_type,
sec.name AS sec_name
FROM accounts acc FROM accounts acc
JOIN securities sec ON acc.security = sec.id JOIN securities sec ON acc.security = sec.id
ORDER BY acc.id ASC"; ORDER BY acc.id ASC";
@@ -130,7 +131,8 @@ pub fn get_account_by_id(tx: &Connection, id: &AccountId) -> CustomResult<Accoun
sec.id AS sec_id, sec.id AS sec_id,
sec.symbol AS sec_symbol, sec.symbol AS sec_symbol,
sec.security_type AS sec_security_type sec.security_type AS sec_security_type,
sec.name AS sec_name
FROM accounts acc FROM accounts acc
JOIN securities sec ON acc.security = sec.id JOIN securities sec ON acc.security = sec.id
WHERE acc.id = ?1"; WHERE acc.id = ?1";
+5 -4
View File
@@ -1,8 +1,9 @@
use std::fmt; use std::fmt;
use regex::{Captures, Regex}; use regex::{Captures, Regex};
use serde::{Deserialize, Serialize};
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
struct SafeId { struct SafeId {
encrypted_id: [u8; 8], encrypted_id: [u8; 8],
signature: [u8; 8], signature: [u8; 8],
@@ -103,13 +104,13 @@ macro_rules! impl_ids {
}; };
} }
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct AccountId(SafeId); pub struct AccountId(SafeId);
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct UserId(SafeId); pub struct UserId(SafeId);
#[derive(PartialEq, Debug, Clone)] #[derive(PartialEq, Debug, Clone, Serialize, Deserialize)]
pub struct SecurityId(SafeId); pub struct SecurityId(SafeId);
impl_ids!(AccountId, "a"); impl_ids!(AccountId, "a");
+34 -3
View File
@@ -4,12 +4,21 @@ use serde::Serialize;
use crate::db::_external_id::{Id, SecurityId}; use crate::db::_external_id::{Id, SecurityId};
use crate::util::{CustomResult, Error}; use crate::util::{CustomResult, Error};
#[derive(Debug, Serialize, PartialEq)] #[derive(Debug, Clone, Serialize, PartialEq)]
pub enum SecurityType { pub enum SecurityType {
Currency, Currency,
Bond, Bond,
} }
impl ToString for SecurityType {
fn to_string(&self) -> String {
match self {
SecurityType::Currency => "currency".to_string(),
SecurityType::Bond => "bond".to_string(),
}
}
}
impl SecurityType { impl SecurityType {
fn from_string_unsafe(s: String) -> SecurityType { fn from_string_unsafe(s: String) -> SecurityType {
if s == "currency".to_string() { if s == "currency".to_string() {
@@ -46,12 +55,25 @@ pub struct Security {
id: i64, id: i64,
security_type: SecurityType, security_type: SecurityType,
symbol: String, symbol: String,
name: String,
} }
impl Security { impl Security {
pub fn id(&self) -> SecurityId { pub fn id(&self) -> SecurityId {
SecurityId::from_id(self.id) SecurityId::from_id(self.id)
} }
pub fn name(&self) -> String {
self.name.clone()
}
pub fn symbol(&self) -> String {
self.symbol.clone()
}
pub fn security_type(&self) -> SecurityType {
self.security_type.clone()
}
} }
impl Security { impl Security {
@@ -60,6 +82,7 @@ impl Security {
id: row.get("sec_id")?, id: row.get("sec_id")?,
security_type: SecurityType::from_string_unsafe(row.get("sec_security_type")?), security_type: SecurityType::from_string_unsafe(row.get("sec_security_type")?),
symbol: row.get("sec_symbol")?, symbol: row.get("sec_symbol")?,
name: row.get("sec_name")?,
}) })
} }
} }
@@ -82,7 +105,10 @@ pub fn create_security(tx: &Transaction, sec: &NewSecurity) -> CustomResult<Secu
} }
pub fn get_security_by_id(tx: &Transaction, id: i32) -> CustomResult<Security> { pub fn get_security_by_id(tx: &Transaction, id: i32) -> CustomResult<Security> {
let q = "SELECT id AS sec_id, security_type AS sec_security_type, symbol AS sec_symbol let q = "SELECT id AS sec_id,
security_type AS sec_security_type,
symbol AS sec_symbol,
name AS sec_name
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)
@@ -90,7 +116,10 @@ pub fn get_security_by_id(tx: &Transaction, id: i32) -> CustomResult<Security> {
} }
pub fn list_securities(conn: &Connection) -> CustomResult<Vec<Security>> { pub fn list_securities(conn: &Connection) -> CustomResult<Vec<Security>> {
let q = "SELECT id AS sec_id, security_type AS sec_security_type, symbol AS sec_symbol let q = "SELECT id AS sec_id,
security_type AS sec_security_type,
symbol AS sec_symbol,
name AS sec_name
FROM securities FROM securities
ORDER BY id ASC"; ORDER BY id ASC";
let mut stmt = conn.prepare(q).map_err(Error::SqlError)?; let mut stmt = conn.prepare(q).map_err(Error::SqlError)?;
@@ -166,6 +195,7 @@ mod test_securities {
id: 1, id: 1,
security_type: SecurityType::Currency, security_type: SecurityType::Currency,
symbol: "P".to_string(), symbol: "P".to_string(),
name: "piastre".to_string(),
}; };
assert_eq!(1, ret.len()); assert_eq!(1, ret.len());
assert_eq!(&expt, ret.get(0).unwrap()); assert_eq!(&expt, ret.get(0).unwrap());
@@ -175,6 +205,7 @@ mod test_securities {
id: 2, id: 2,
security_type: SecurityType::Bond, security_type: SecurityType::Bond,
symbol: "B".to_string(), symbol: "B".to_string(),
name: "bong".to_string(),
}; };
let tx = conn.transaction().unwrap(); let tx = conn.transaction().unwrap();
create_security(&tx, &sec)?; create_security(&tx, &sec)?;
+26 -2
View File
@@ -1,6 +1,26 @@
use crate::db::external_id::SecurityId;
use crate::db::securities::{list_securities, Security};
use rusqlite::Connection; use rusqlite::Connection;
use serde::{Deserialize, Serialize};
pub struct SecurityDTO {} #[derive(Serialize, Deserialize)]
pub struct SecurityDTO {
id: SecurityId,
symbol: String,
name: String,
security_type: String,
}
impl SecurityDTO {
fn from_entity(sec: &Security) -> SecurityDTO {
SecurityDTO {
id: sec.id(),
symbol: sec.symbol(),
name: sec.name().to_string(),
security_type: sec.security_type().to_string(),
}
}
}
pub struct SecurityService<'a> { pub struct SecurityService<'a> {
conn: &'a Connection, conn: &'a Connection,
@@ -12,6 +32,10 @@ impl SecurityService<'_> {
} }
pub fn list_securities(self) -> Vec<SecurityDTO> { pub fn list_securities(self) -> Vec<SecurityDTO> {
vec![] list_securities(self.conn)
.unwrap()
.iter()
.map(SecurityDTO::from_entity)
.collect()
} }
} }
+1 -16
View File
@@ -1,6 +1,5 @@
use rusqlite::Connection;
pub use crate::services::_authentication::SessionService; pub use crate::services::_authentication::SessionService;
pub use crate::services::_securities::SecurityService;
pub use crate::services::_users::UserService; pub use crate::services::_users::UserService;
mod _authentication; mod _authentication;
@@ -9,17 +8,3 @@ mod _transactions;
mod _users; mod _users;
pub use crate::db::ledger_internal::LedgerInternal; pub use crate::db::ledger_internal::LedgerInternal;
pub struct ReadOnlyServiceFactory<'a> {
conn: &'a mut Connection,
}
impl ReadOnlyServiceFactory<'_> {
pub fn new(conn: &mut Connection) -> ReadOnlyServiceFactory {
ReadOnlyServiceFactory { conn }
}
pub fn get_user_service(&self) -> UserService {
UserService::new(self.conn)
}
}
+4 -4
View File
@@ -1,4 +1,4 @@
use crate::webui::services::Services; use crate::webui::services::ServicesFactory;
use crate::webui::templates::MiniJinjaRenderer; use crate::webui::templates::MiniJinjaRenderer;
use actix_web::{web, HttpRequest, Responder}; use actix_web::{web, HttpRequest, Responder};
@@ -15,7 +15,7 @@ pub struct LoginPayload {
pub async fn login( pub async fn login(
tmpl_env: MiniJinjaRenderer, tmpl_env: MiniJinjaRenderer,
web::Json(form): web::Json<LoginPayload>, web::Json(form): web::Json<LoginPayload>,
service_factory: web::Data<Services>, service_factory: web::Data<ServicesFactory>,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let authentication_service = service_factory.get_authentication_service()?; let authentication_service = service_factory.get_authentication_service()?;
let cookie = authentication_service.create_login_cookie(&form.username); let cookie = authentication_service.create_login_cookie(&form.username);
@@ -30,7 +30,7 @@ pub async fn login(
#[actix_web::post("/logout")] #[actix_web::post("/logout")]
pub async fn logout( pub async fn logout(
service_factory: web::Data<Services>, service_factory: web::Data<ServicesFactory>,
tmpl_env: MiniJinjaRenderer, tmpl_env: MiniJinjaRenderer,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let authentication_service = service_factory.get_authentication_service()?; let authentication_service = service_factory.get_authentication_service()?;
@@ -43,7 +43,7 @@ pub async fn logout(
#[actix_web::get("/")] #[actix_web::get("/")]
pub async fn index( pub async fn index(
req: HttpRequest, req: HttpRequest,
service_factory: web::Data<Services>, service_factory: web::Data<ServicesFactory>,
tmpl_env: MiniJinjaRenderer, tmpl_env: MiniJinjaRenderer,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let authentication_service = service_factory.get_authentication_service()?; let authentication_service = service_factory.get_authentication_service()?;
+3 -3
View File
@@ -18,7 +18,7 @@ use uuid::Uuid;
use crate::events::Event; use crate::events::Event;
use crate::util::{CustomResult, Error, SQLiteConnectionFactory}; use crate::util::{CustomResult, Error, SQLiteConnectionFactory};
use crate::webui::services::{creat_db_pool, Services}; use crate::webui::services::{creat_db_pool, ServicesFactory};
use crate::webui::templates::MiniJinjaRenderer; use crate::webui::templates::MiniJinjaRenderer;
use crate::webui::worker::{event_processor_loop, EventProcessor}; use crate::webui::worker::{event_processor_loop, EventProcessor};
@@ -80,11 +80,12 @@ pub async fn web_ui_main(
) -> CustomResult<()> { ) -> CustomResult<()> {
let (event_sender, event_receiver) = async_channel::unbounded::<(Uuid, Event)>(); let (event_sender, event_receiver) = async_channel::unbounded::<(Uuid, Event)>();
let pool = creat_db_pool(&conn_fact.path().unwrap()); let pool = creat_db_pool(&conn_fact.path().unwrap());
let services = Services::new(pool); let services = ServicesFactory::new(pool);
let server = HttpServer::new(move || { let server = HttpServer::new(move || {
let template_reloader = create_auto_reloader(enable_template_autoreload); let template_reloader = create_auto_reloader(enable_template_autoreload);
App::new() App::new()
.wrap(Logger::default())
.app_data(web::Data::new(services.clone())) .app_data(web::Data::new(services.clone()))
.app_data(web::Data::new(template_reloader)) .app_data(web::Data::new(template_reloader))
.app_data(web::Data::new(event_sender.clone())) .app_data(web::Data::new(event_sender.clone()))
@@ -92,7 +93,6 @@ pub async fn web_ui_main(
.service(create_api_scope()) .service(create_api_scope())
.service(create_views_scope()) .service(create_views_scope())
.wrap(ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found)) .wrap(ErrorHandlers::new().handler(StatusCode::NOT_FOUND, not_found))
.wrap(Logger::default())
}) })
.workers(2) .workers(2)
.bind(("0.0.0.0", port)) .bind(("0.0.0.0", port))
+12 -5
View File
@@ -1,4 +1,4 @@
use crate::services::{SessionService, UserService}; use crate::services::{SecurityService, SessionService, UserService};
use actix_web::{error, Result}; use actix_web::{error, Result};
use r2d2::PooledConnection; use r2d2::PooledConnection;
@@ -19,17 +19,17 @@ struct ServicesCache {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Services { pub struct ServicesFactory {
pool: DbPool, pool: DbPool,
cache: ServicesCache, cache: ServicesCache,
} }
impl Services { impl ServicesFactory {
pub fn new(pool: DbPool) -> Services { pub fn new(pool: DbPool) -> ServicesFactory {
let mut conn = pool.get().unwrap(); let mut conn = pool.get().unwrap();
let session_service = SessionService::from_conn(&mut conn).unwrap(); let session_service = SessionService::from_conn(&mut conn).unwrap();
let cache = ServicesCache { session_service }; let cache = ServicesCache { session_service };
Services { pool, cache } ServicesFactory { pool, cache }
} }
pub fn get_connection(&self) -> Result<PooledConnection<SqliteConnectionManager>> { pub fn get_connection(&self) -> Result<PooledConnection<SqliteConnectionManager>> {
@@ -48,4 +48,11 @@ impl Services {
) -> Result<UserService> { ) -> Result<UserService> {
Ok(UserService::new(conn)) Ok(UserService::new(conn))
} }
pub fn get_security_service<'a>(
&'a self,
conn: &'a PooledConnection<SqliteConnectionManager>,
) -> Result<SecurityService> {
Ok(SecurityService::new(conn))
}
} }
+17 -8
View File
@@ -1,14 +1,16 @@
use std::collections::HashMap; use std::collections::HashMap;
use actix_web::{error, web, HttpRequest, Responder}; use actix_web::{error, web, HttpRequest, Responder};
use actix_web_lab::respond::Html;
use log::info; use log::info;
use minijinja::context; use minijinja::context;
use serde::Deserialize; use serde::Deserialize;
use uuid::Uuid;
use crate::events::Event; use crate::events::Event;
use crate::webui::services::Services; use crate::webui::services::ServicesFactory;
use crate::webui::templates::MiniJinjaRenderer; use crate::webui::templates::MiniJinjaRenderer;
#[actix_web::get("/actions")] #[actix_web::get("/actions")]
@@ -24,7 +26,7 @@ pub async fn accounts(tmpl_env: MiniJinjaRenderer) -> actix_web::Result<impl Res
#[actix_web::get("/overview")] #[actix_web::get("/overview")]
pub async fn overview( pub async fn overview(
tmpl_env: MiniJinjaRenderer, tmpl_env: MiniJinjaRenderer,
service_factory: web::Data<Services>, service_factory: web::Data<ServicesFactory>,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<impl Responder> {
let conn = service_factory.get_connection()?; let conn = service_factory.get_connection()?;
let user_service = service_factory.get_user_service(&conn)?; let user_service = service_factory.get_user_service(&conn)?;
@@ -40,7 +42,7 @@ pub async fn overview(
pub async fn overview_submit_button( pub async fn overview_submit_button(
tmpl_env: MiniJinjaRenderer, tmpl_env: MiniJinjaRenderer,
query: web::Query<HashMap<String, String>>, query: web::Query<HashMap<String, String>>,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<Html> {
let ctx = context! { let ctx = context! {
event_type => query.get("event_type").unwrap_or(&"".to_string()), event_type => query.get("event_type").unwrap_or(&"".to_string()),
}; };
@@ -51,12 +53,18 @@ pub async fn overview_submit_button(
pub async fn actions_partials( pub async fn actions_partials(
tmpl_env: MiniJinjaRenderer, tmpl_env: MiniJinjaRenderer,
query: web::Query<HashMap<String, String>>, query: web::Query<HashMap<String, String>>,
) -> actix_web::Result<impl Responder> { service_factory: web::Data<ServicesFactory>,
) -> actix_web::Result<Html> {
let event_type = query let event_type = query
.get("event_type") .get("event_type")
.unwrap_or(&"404".to_string()) .unwrap_or(&"404".to_string())
.to_string(); .to_string();
let ctx = context! {event_type => event_type}; let conn = service_factory.get_connection()?;
let security_service = service_factory.get_security_service(&conn)?;
let ctx = context! {
event_type => event_type,
securities => security_service.list_securities(),
};
let template_location = format!("partials/actions/{:}.html", event_type); let template_location = format!("partials/actions/{:}.html", event_type);
info!("Getting template at: {:?}", template_location); info!("Getting template at: {:?}", template_location);
tmpl_env.render(template_location.as_str(), ctx) tmpl_env.render(template_location.as_str(), ctx)
@@ -66,15 +74,16 @@ pub async fn actions_partials(
pub struct CreateEventPayload { pub struct CreateEventPayload {
event: Event, event: Event,
event_type: String, event_type: String,
initiator: String,
} }
#[actix_web::post("/events/{event_type}")] #[actix_web::post("/events/{event_type}")]
pub async fn push_event( pub async fn push_event(
req: HttpRequest, req: HttpRequest,
tmpl_env: MiniJinjaRenderer, tmpl_env: MiniJinjaRenderer,
event_channel: web::Data<async_channel::Sender<Event>>, event_channel: web::Data<async_channel::Sender<(Uuid, Event)>>,
web::Json(form): web::Json<CreateEventPayload>, web::Json(form): web::Json<CreateEventPayload>,
) -> actix_web::Result<impl Responder> { ) -> actix_web::Result<Html> {
let path_event_type = req.match_info().get("event_type").unwrap(); let path_event_type = req.match_info().get("event_type").unwrap();
if path_event_type.to_string() != form.event_type { if path_event_type.to_string() != form.event_type {
return Err(error::ErrorBadRequest("Inconsistent event type.")); return Err(error::ErrorBadRequest("Inconsistent event type."));
@@ -83,7 +92,7 @@ pub async fn push_event(
if validation.is_err() { if validation.is_err() {
return Err(error::ErrorBadRequest(validation.err().unwrap())); return Err(error::ErrorBadRequest(validation.err().unwrap()));
} }
let _ = event_channel.send(form.event).await; let _ = event_channel.send((Uuid::new_v4(), form.event)).await;
let ctx = context! { let ctx = context! {
event_type => "event received.", event_type => "event received.",
}; };
+5 -5
View File
@@ -109,7 +109,7 @@ pub mod test_worker {
use super::*; use super::*;
use crate::events::CreateNewUser; use crate::events::CreateNewUser;
use crate::migration; use crate::migration;
use futures::{future, TryFutureExt}; use futures::future;
use std::sync::Mutex; use std::sync::Mutex;
struct StatefulEventProcessor { struct StatefulEventProcessor {
@@ -122,13 +122,13 @@ pub mod test_worker {
} }
fn call_count(&self) -> i32 { fn call_count(&self) -> i32 {
let mut m = self.m.lock().unwrap(); let m = self.m.lock().unwrap();
m.clone() m.clone()
} }
} }
impl EventProcessor for StatefulEventProcessor { impl EventProcessor for StatefulEventProcessor {
fn process_event(&self, conn: &mut Connection, event: Event) -> CustomResult<()> { fn process_event(&self, _conn: &mut Connection, event: Event) -> CustomResult<()> {
let mut m = self.m.lock().unwrap(); let mut m = self.m.lock().unwrap();
*m += 1; *m += 1;
get_event_processing_result(&event) get_event_processing_result(&event)
@@ -186,7 +186,7 @@ pub mod test_worker {
#[tokio::test] #[tokio::test]
async fn test_on_close_channel() { async fn test_on_close_channel() {
let mut ep = StatefulEventProcessor::new(); let ep = StatefulEventProcessor::new();
let (event_sender, event_receiver) = async_channel::unbounded::<(Uuid, Event)>(); let (event_sender, event_receiver) = async_channel::unbounded::<(Uuid, Event)>();
event_sender.close(); event_sender.close();
let r = event_processor_loop(&mut get_connection(), &ep, event_receiver).await; let r = event_processor_loop(&mut get_connection(), &ep, event_receiver).await;
@@ -196,7 +196,7 @@ pub mod test_worker {
#[tokio::test] #[tokio::test]
async fn test_basic() -> CustomResult<()> { async fn test_basic() -> CustomResult<()> {
let mut ep = StatefulEventProcessor::new(); let ep = StatefulEventProcessor::new();
let mut conn = get_connection(); let mut conn = get_connection();
let (event_sender, event_receiver) = async_channel::unbounded::<(Uuid, Event)>(); let (event_sender, event_receiver) = async_channel::unbounded::<(Uuid, Event)>();
let f1 = send_events(event_sender, get_events()); let f1 = send_events(event_sender, get_events());
+1 -1
View File
@@ -1,2 +1,2 @@
#!/usr/bin/env bash #!/usr/bin/env bash
exec cargo run -- wui run --port 9999 --enable-template-autoreload exec cargo run -- -vvv wui run --port 9999 --enable-template-autoreload