Started to implement accounts in the UI.
This commit is contained in:
1 parent
ce92916942
commit
76e1e8cb53
7 files changed
+171
-8
No files matched your search
+85
-4
@@ -6,6 +6,7 @@ use crate::db::_securities::Security;
|
||||
|
||||
use crate::db::external_id::SecurityId;
|
||||
use crate::util::{CustomResult, Error};
|
||||
use crate::util::Error::FuckingError;
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub enum AccountType {
|
||||
@@ -14,13 +15,28 @@ pub enum 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> {
|
||||
let s: String = row.get("acc_account_type")?;
|
||||
if s == "security_emitter" {
|
||||
Ok(AccountType::SecurityEmitter)
|
||||
} else {
|
||||
Ok(AccountType::Checking)
|
||||
AccountType::from_string(& s).ok_or(rusqlite::Error::ExecuteReturnedResults)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
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)]
|
||||
@@ -123,6 +152,31 @@ pub fn list_accounts<'a>(conn: &Connection) -> CustomResult<Vec<Account>> {
|
||||
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> {
|
||||
let q = "SELECT acc.id AS acc_id,
|
||||
acc.amount AS acc_amount,
|
||||
@@ -270,6 +324,7 @@ pub fn add_to_account(
|
||||
#[cfg(test)]
|
||||
mod test_accounts {
|
||||
use rusqlite::{Connection, Transaction};
|
||||
use serde_json::to_string;
|
||||
|
||||
use crate::db::_test_utils::setup_users_and_securities;
|
||||
use crate::db::users::User;
|
||||
@@ -302,6 +357,18 @@ mod test_accounts {
|
||||
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]
|
||||
fn test_simplest_checking_account() -> CustomResult<()> {
|
||||
let (mut conn, users, secs) = setup();
|
||||
@@ -422,4 +489,18 @@ mod test_accounts {
|
||||
assert_eq!(get_account_history(&tx2, &acc.id())?.len(), 2);
|
||||
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::{
|
||||
add_to_account, create_checking_account, create_security_emitter_account,
|
||||
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::_securities::SecurityService;
|
||||
pub use crate::services::_users::UserService;
|
||||
pub use crate::services::_accounts::AccountService;
|
||||
|
||||
mod _authentication;
|
||||
mod _securities;
|
||||
mod _transactions;
|
||||
mod _users;
|
||||
mod _accounts;
|
||||
|
||||
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::{fmt, io};
|
||||
|
||||
use rusqlite::{Connection, OpenFlags};
|
||||
|
||||
@@ -16,6 +17,15 @@ pub enum Error {
|
||||
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>;
|
||||
|
||||
#[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 r2d2::PooledConnection;
|
||||
@@ -55,4 +55,12 @@ impl ServicesFactory {
|
||||
) -> Result<SecurityService> {
|
||||
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 minijinja::context;
|
||||
use rusqlite::params;
|
||||
use serde::Deserialize;
|
||||
use uuid::Uuid;
|
||||
|
||||
@@ -19,8 +20,16 @@ pub async fn actions(tmpl_env: MiniJinjaRenderer) -> actix_web::Result<impl Resp
|
||||
}
|
||||
|
||||
#[actix_web::get("/accounts")]
|
||||
pub async fn accounts(tmpl_env: MiniJinjaRenderer) -> actix_web::Result<impl Responder> {
|
||||
tmpl_env.render("accounts.html", ())
|
||||
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 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")]
|
||||
|
||||
Reference in new issue
Block a user