diff --git a/src/db/_accounts.rs b/src/db/_accounts.rs index 460d719..676b483 100644 --- a/src/db/_accounts.rs +++ b/src/db/_accounts.rs @@ -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{ + if s == "security_emitter" { + Some(AccountType::SecurityEmitter) + } else if s == "checking"{ + Some(AccountType::Checking) + }else{ + None + } + } pub fn from_row(row: &Row) -> Result { 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> { return Ok(ret); } +pub fn list_user_accounts(conn: &Connection, user_id: & UserId) -> 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 + 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 = 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 { 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(()) + } } diff --git a/src/db/mod.rs b/src/db/mod.rs index aac6a64..c46f675 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -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 }; } diff --git a/src/services/_accounts.rs b/src/services/_accounts.rs new file mode 100644 index 0000000..99370a3 --- /dev/null +++ b/src/services/_accounts.rs @@ -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>{ + 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()) + } + +} \ No newline at end of file diff --git a/src/services/mod.rs b/src/services/mod.rs index 57d7565..04ef0f4 100644 --- a/src/services/mod.rs +++ b/src/services/mod.rs @@ -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; diff --git a/src/util.rs b/src/util.rs index 37517f9..91ed79f 100644 --- a/src/util.rs +++ b/src/util.rs @@ -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 = Result; #[derive(Clone)] diff --git a/src/webui/services.rs b/src/webui/services.rs index fb90baa..c3d04b4 100644 --- a/src/webui/services.rs +++ b/src/webui/services.rs @@ -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 { Ok(SecurityService::new(conn)) } + + pub fn get_account_service<'a>( + &'a self, + conn: &'a PooledConnection, + )-> Result { + Ok(AccountService::new(conn)) + + } } diff --git a/src/webui/views.rs b/src/webui/views.rs index e3344e1..78a4850 100644 --- a/src/webui/views.rs +++ b/src/webui/views.rs @@ -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 actix_web::Result { - tmpl_env.render("accounts.html", ()) +pub async fn accounts(tmpl_env: MiniJinjaRenderer, + req: HttpRequest, + service_factory: web::Data) -> actix_web::Result { + 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")]