Started to implement accounts in the UI.
This commit is contained in:
1 parent
ce92916942
commit
76e1e8cb53
7 files changed
+172
-9
No files matched your search
+86
-5
@@ -6,6 +6,7 @@ use crate::db::_securities::Security;
|
|||||||
|
|
||||||
use crate::db::external_id::SecurityId;
|
use crate::db::external_id::SecurityId;
|
||||||
use crate::util::{CustomResult, Error};
|
use crate::util::{CustomResult, Error};
|
||||||
|
use crate::util::Error::FuckingError;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum AccountType {
|
pub enum AccountType {
|
||||||
@@ -14,13 +15,28 @@ pub enum AccountType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl AccountType {
|
impl AccountType {
|
||||||
|
|
||||||
|
fn from_string(s: & String) -> Option<AccountType>{
|
||||||
|
if s == "security_emitter" {
|
||||||
|
Some(AccountType::SecurityEmitter)
|
||||||
|
} else if s == "checking"{
|
||||||
|
Some(AccountType::Checking)
|
||||||
|
}else{
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
pub fn from_row(row: &Row) -> Result<AccountType> {
|
pub fn from_row(row: &Row) -> Result<AccountType> {
|
||||||
let s: String = row.get("acc_account_type")?;
|
let s: String = row.get("acc_account_type")?;
|
||||||
if s == "security_emitter" {
|
AccountType::from_string(& s).ok_or(rusqlite::Error::ExecuteReturnedResults)
|
||||||
Ok(AccountType::SecurityEmitter)
|
}
|
||||||
} else {
|
}
|
||||||
Ok(AccountType::Checking)
|
|
||||||
}
|
impl ToString for AccountType{
|
||||||
|
fn to_string(&self) -> String {
|
||||||
|
match self {
|
||||||
|
AccountType::Checking => "checking",
|
||||||
|
AccountType::SecurityEmitter => "security_emitter",
|
||||||
|
}.to_string()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,6 +66,19 @@ impl Account {
|
|||||||
pub fn raw_amount(&self) -> i64 {
|
pub fn raw_amount(&self) -> i64 {
|
||||||
self.amount
|
self.amount
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn amount(&self) -> i64 {
|
||||||
|
self.amount
|
||||||
|
}
|
||||||
|
pub fn timestamp(&self) -> i64 {
|
||||||
|
self.timestamp
|
||||||
|
}
|
||||||
|
pub fn security(&self) -> &Security {
|
||||||
|
&self.security
|
||||||
|
}
|
||||||
|
pub fn account_type(&self) -> &AccountType {
|
||||||
|
&self.account_type
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@@ -123,6 +152,31 @@ pub fn list_accounts<'a>(conn: &Connection) -> CustomResult<Vec<Account>> {
|
|||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn list_user_accounts(conn: &Connection, user_id: & UserId) -> CustomResult<Vec<Account>> {
|
||||||
|
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
|
||||||
|
JOIN account_user_permissions ON account_user_permissions.account_id = acc.id
|
||||||
|
WHERE account_user_permissions.user_id = ?1
|
||||||
|
ORDER BY acc.id ASC";
|
||||||
|
let mut stmt = conn.prepare(q).map_err(Error::SqlError)?;
|
||||||
|
let ret: Vec<Account> = stmt
|
||||||
|
.query_map(params![user_id], &Account::from_row)
|
||||||
|
.map_err(Error::SqlError)?
|
||||||
|
.filter(Result::is_ok)
|
||||||
|
.map(Result::unwrap)
|
||||||
|
.collect();
|
||||||
|
return Ok(ret);
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_account_by_id(tx: &Connection, id: &AccountId) -> CustomResult<Account> {
|
pub fn get_account_by_id(tx: &Connection, id: &AccountId) -> CustomResult<Account> {
|
||||||
let q = "SELECT acc.id AS acc_id,
|
let q = "SELECT acc.id AS acc_id,
|
||||||
acc.amount AS acc_amount,
|
acc.amount AS acc_amount,
|
||||||
@@ -270,6 +324,7 @@ pub fn add_to_account(
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod test_accounts {
|
mod test_accounts {
|
||||||
use rusqlite::{Connection, Transaction};
|
use rusqlite::{Connection, Transaction};
|
||||||
|
use serde_json::to_string;
|
||||||
|
|
||||||
use crate::db::_test_utils::setup_users_and_securities;
|
use crate::db::_test_utils::setup_users_and_securities;
|
||||||
use crate::db::users::User;
|
use crate::db::users::User;
|
||||||
@@ -302,6 +357,18 @@ mod test_accounts {
|
|||||||
return Ok(ret);
|
return Ok(ret);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assert_test_account_type_to_string_and_from_row_works(account_type: AccountType){
|
||||||
|
let serialized = account_type.to_string();
|
||||||
|
assert_eq!(AccountType::from_string(&serialized).unwrap(), account_type);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_account_type_to_string_and_from_row(){
|
||||||
|
assert_test_account_type_to_string_and_from_row_works(AccountType::Checking);
|
||||||
|
assert_test_account_type_to_string_and_from_row_works(AccountType::SecurityEmitter);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_simplest_checking_account() -> CustomResult<()> {
|
fn test_simplest_checking_account() -> CustomResult<()> {
|
||||||
let (mut conn, users, secs) = setup();
|
let (mut conn, users, secs) = setup();
|
||||||
@@ -422,4 +489,18 @@ mod test_accounts {
|
|||||||
assert_eq!(get_account_history(&tx2, &acc.id())?.len(), 2);
|
assert_eq!(get_account_history(&tx2, &acc.id())?.len(), 2);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_list_user_accounts() -> CustomResult<()> {
|
||||||
|
let (mut conn, users, secs) = setup();
|
||||||
|
{
|
||||||
|
let tx = conn.transaction().unwrap();
|
||||||
|
create_all_accounts(& tx, &users, &secs)?;
|
||||||
|
tx.commit().map_err(Error::SqlError)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let res = list_user_accounts(& conn, & users[0].id())?;
|
||||||
|
assert_eq!(res.len(), 2);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -29,6 +29,7 @@ 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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
use rusqlite::Connection;
|
||||||
|
use crate::db::accounts::{Account, list_user_accounts};
|
||||||
|
use crate::db::external_id::{Id, UserId};
|
||||||
|
use crate::util::{CustomResult, Error};
|
||||||
|
use serde::Deserialize;
|
||||||
|
use serde::Serialize;
|
||||||
|
pub struct AccountService<'a> {
|
||||||
|
conn: &'a Connection,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct AccountDTO {
|
||||||
|
pub id: String,
|
||||||
|
pub amount: i64,
|
||||||
|
pub account_type: String,
|
||||||
|
pub last_update: i64,
|
||||||
|
pub security_symbol: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountDTO {
|
||||||
|
fn from_entity(account: & Account) -> AccountDTO {
|
||||||
|
AccountDTO{
|
||||||
|
id: account.id().to_string(),
|
||||||
|
amount: account.amount(),
|
||||||
|
account_type: account.account_type().to_string(),
|
||||||
|
last_update: account.timestamp(),
|
||||||
|
security_symbol: account.security().symbol()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
impl AccountService<'_> {
|
||||||
|
|
||||||
|
pub fn new(conn: & Connection) -> 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())?;
|
||||||
|
Ok(ret.iter().map(AccountDTO::from_entity).collect())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,10 +1,12 @@
|
|||||||
pub use crate::services::_authentication::SessionService;
|
pub use crate::services::_authentication::SessionService;
|
||||||
pub use crate::services::_securities::SecurityService;
|
pub use crate::services::_securities::SecurityService;
|
||||||
pub use crate::services::_users::UserService;
|
pub use crate::services::_users::UserService;
|
||||||
|
pub use crate::services::_accounts::AccountService;
|
||||||
|
|
||||||
mod _authentication;
|
mod _authentication;
|
||||||
mod _securities;
|
mod _securities;
|
||||||
mod _transactions;
|
mod _transactions;
|
||||||
mod _users;
|
mod _users;
|
||||||
|
mod _accounts;
|
||||||
|
|
||||||
pub use crate::db::ledger_internal::LedgerInternal;
|
pub use crate::db::ledger_internal::LedgerInternal;
|
||||||
+11
-1
@@ -1,5 +1,6 @@
|
|||||||
use std::io;
|
use std::fmt::{Display, Formatter, Pointer};
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::{fmt, io};
|
||||||
|
|
||||||
use rusqlite::{Connection, OpenFlags};
|
use rusqlite::{Connection, OpenFlags};
|
||||||
|
|
||||||
@@ -16,6 +17,15 @@ pub enum Error {
|
|||||||
Testing(String),
|
Testing(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Display for Error {
|
||||||
|
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Error::SqlError(e) => write!(f, "SqlError: {:?}", e),
|
||||||
|
_ => write!(f, "Unknown error.. fuck mf"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub type CustomResult<T, E = Error> = Result<T, E>;
|
pub type CustomResult<T, E = Error> = Result<T, E>;
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::services::{SecurityService, SessionService, UserService};
|
use crate::services::{SecurityService, SessionService, UserService, AccountService};
|
||||||
|
|
||||||
use actix_web::{error, Result};
|
use actix_web::{error, Result};
|
||||||
use r2d2::PooledConnection;
|
use r2d2::PooledConnection;
|
||||||
@@ -55,4 +55,12 @@ impl ServicesFactory {
|
|||||||
) -> Result<SecurityService> {
|
) -> Result<SecurityService> {
|
||||||
Ok(SecurityService::new(conn))
|
Ok(SecurityService::new(conn))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_account_service<'a>(
|
||||||
|
&'a self,
|
||||||
|
conn: &'a PooledConnection<SqliteConnectionManager>,
|
||||||
|
)-> Result<AccountService> {
|
||||||
|
Ok(AccountService::new(conn))
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+11
-2
@@ -5,6 +5,7 @@ use actix_web_lab::respond::Html;
|
|||||||
|
|
||||||
use log::info;
|
use log::info;
|
||||||
use minijinja::context;
|
use minijinja::context;
|
||||||
|
use rusqlite::params;
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
@@ -19,8 +20,16 @@ pub async fn actions(tmpl_env: MiniJinjaRenderer) -> actix_web::Result<impl Resp
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::get("/accounts")]
|
#[actix_web::get("/accounts")]
|
||||||
pub async fn accounts(tmpl_env: MiniJinjaRenderer) -> actix_web::Result<impl Responder> {
|
pub async fn accounts(tmpl_env: MiniJinjaRenderer,
|
||||||
tmpl_env.render("accounts.html", ())
|
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 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)?;
|
||||||
|
tmpl_env.render("accounts.html", context!{accounts => accounts})
|
||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::get("/overview")]
|
#[actix_web::get("/overview")]
|
||||||
|
|||||||
Reference in new issue
Block a user