diff --git a/src/db/_account_history.rs b/src/db/_account_history.rs new file mode 100644 index 0000000..8ad6742 --- /dev/null +++ b/src/db/_account_history.rs @@ -0,0 +1,99 @@ +use rusqlite::{Connection, OptionalExtension, params}; +use crate::db::accounts; +use crate::db::accounts::Account; +use crate::db::external_id::AccountId; +use crate::util::{CustomResult, Error}; + +pub struct AccountHistory { + account: Account, + timestamps: Vec, + amounts: Vec, +} + +impl AccountHistory { + pub fn account(&self) -> &Account { + &self.account + } + pub fn timestamps(&self) -> &Vec { + &self.timestamps + } + pub fn amount(&self) -> &Vec { + &self.amounts + } +} + +pub fn get_account_history(tx: &Connection, account_id: &AccountId) -> CustomResult { + let q = " + SELECT amount, CAST(timestamp AS INT) AS timestamp + FROM accounts_history + WHERE account_id = ?1 + ORDER BY account_version ASC + "; + + let mut stmt = tx.prepare(q).map_err(Error::SqlError)?; + let rows = stmt + .query_map(params![account_id], |row| Ok((row.get("amount")?, row.get("timestamp")?))) + .optional() + .map_err(Error::SqlError)?; + + let ret: Vec<(i64, i64)> = rows.map(|x| + x.map(Result::unwrap) + .collect()).unwrap_or(vec![]); + + let account = accounts::get_account_by_id(tx, account_id)?; + let mut amounts: Vec = ret.iter().map(|r| r.0).collect(); + let mut timestamps: Vec = ret.iter().map(|r| r.1).collect(); + amounts.push(account.amount()); + timestamps.push(account.timestamp()); + + Ok(AccountHistory{account, timestamps, amounts }) +} + +#[cfg(test)] +mod test_account_history { + use crate::db::test_utils::setup_basic_environment; + use crate::migration; + use crate::util::CustomResult; + use super::*; + + fn setup() -> CustomResult<(Connection, Account)> { + let mut conn = Connection::open_in_memory().unwrap(); + + migration::apply_migrations(&mut conn).unwrap(); + let ret = { + let mut tx = conn.transaction().unwrap(); + let (_, _, accounts) = setup_basic_environment(&mut tx)?; + tx.commit().unwrap(); + accounts.get(0).unwrap().clone() + }; + Ok((conn, ret)) + } + + #[test] + fn test_get_account_history_trivial() -> CustomResult<()>{ + let (mut conn, acc) = setup()?; + let acc_hist = get_account_history(&conn, &acc.id())?; + assert_eq!(acc_hist.amounts, vec![0, 0]); + assert_eq!(acc_hist.timestamps.len(), 2); + assert_eq!(acc_hist.account.label(), "acc1"); + Ok(()) + } + + #[test] + fn test_get_account_history_with_transactions() -> CustomResult<()>{ + let (mut conn, acc) = setup()?; + { + let tx = conn.transaction().unwrap(); + accounts::add_to_account(&tx, &acc.id(), 1)?; + tx.commit().unwrap(); + } + + let tx = conn.transaction().unwrap(); + let acc_hist = get_account_history(&tx, &acc.id())?; + assert_eq!(acc_hist.amounts, vec![0, 1, 1]); + assert_eq!(acc_hist.timestamps.len(), 3); + assert_eq!(acc_hist.account.label(), "acc1"); + Ok(()) + } + +} \ No newline at end of file diff --git a/src/db/_accounts.rs b/src/db/_accounts.rs index 69303ed..8aed5d6 100644 --- a/src/db/_accounts.rs +++ b/src/db/_accounts.rs @@ -50,6 +50,15 @@ impl ToString for AccountType{ } } +impl Clone for AccountType { + fn clone(&self) -> Self { + match self { + AccountType::Checking => AccountType::Checking, + AccountType::SecurityEmitter => AccountType::SecurityEmitter, + } + } +} + pub struct Account { id: i64, amount: i64, @@ -97,6 +106,19 @@ impl Account { } } +impl Clone for Account{ + fn clone(&self) -> Self { + Account{ + id: self.id, + amount: self.amount, + timestamp: self.timestamp, + security: self.security.clone(), + account_type: self.account_type.clone(), + label: self.label.clone(), + } + + } +} #[derive(Debug)] struct AccountSnapshot { amount: i64, diff --git a/src/db/_external_id.rs b/src/db/_external_id.rs index d5a2be4..0a589a7 100644 --- a/src/db/_external_id.rs +++ b/src/db/_external_id.rs @@ -233,7 +233,7 @@ pub mod test_external_ids { #[test] fn test_micro_id() { let sid = SecurityId::from_id(33); - assert_eq!(sid.micro_id(), "s-ea676268") + assert_eq!(sid.micro_id(), "s-ea6762") } #[test] diff --git a/src/db/_securities.rs b/src/db/_securities.rs index ae73364..6ad6aa1 100644 --- a/src/db/_securities.rs +++ b/src/db/_securities.rs @@ -87,6 +87,17 @@ impl Security { } } +impl Clone for Security { + fn clone(&self) -> Self { + Security{ + id: self.id, + security_type: self.security_type.clone(), + symbol: self.symbol.clone(), + name: self.name.clone(), + } + } +} + pub struct NewSecurity { pub security_type: SecurityType, pub symbol: String, diff --git a/src/db/mod.rs b/src/db/mod.rs index 04730e6..799f65b 100644 --- a/src/db/mod.rs +++ b/src/db/mod.rs @@ -6,6 +6,7 @@ mod _ledger_internal; mod _securities; mod _test_utils; mod _users; +mod _account_history; pub mod ledger_internal { pub use super::_ledger_internal::*; @@ -33,6 +34,10 @@ pub mod accounts { }; } +pub mod account_history{ + pub use super::_account_history::get_account_history; +} + pub mod cheques { pub use super::_cheques::{Cheque, Drawer, Payee, WireTransferRequest}; } diff --git a/src/services/_accounts.rs b/src/services/_accounts.rs index 867dc6d..2559ff0 100644 --- a/src/services/_accounts.rs +++ b/src/services/_accounts.rs @@ -4,7 +4,7 @@ use crate::db::external_id::{AccountId, Id, SecurityId, UserId}; use crate::util::{CustomResult, Error}; use serde::Deserialize; use serde::Serialize; -use crate::db::accounts; +use crate::db::{account_history, accounts}; use crate::db::securities::Security; use crate::services::_securities::SecurityDTO; @@ -22,6 +22,11 @@ pub struct AccountDTO { pub label: String, } +#[derive(Deserialize, Serialize, Debug)] +pub struct AccountHistoryDTO { + +} + impl AccountDTO { fn from_entity(account: & Account) -> AccountDTO { AccountDTO{ @@ -66,4 +71,10 @@ impl AccountService<'_> { let ret = &accounts::get_account_by_id(self.conn, account_id)?; Ok(AccountDTO::from_entity(ret)) } + + pub fn get_account_history_by_id(&self, account_id: &AccountId, user_id: &UserId) -> CustomResult<()> { + let ret = account_history::get_account_history(self.conn, account_id)?; + + Ok(()) + } } \ No newline at end of file diff --git a/src/webui/mod.rs b/src/webui/mod.rs index 8aeb540..e62ee0d 100644 --- a/src/webui/mod.rs +++ b/src/webui/mod.rs @@ -70,8 +70,8 @@ fn create_views_scope() -> Scope { .service(index_views::login) .service(index_views::logout) .service(views::actions) - .service(views::accounts) - .service(views::account) + .service(views::get_accounts) + .service(views::get_account) .service(views::securities) .service(views::security) .service(views::users) diff --git a/src/webui/views.rs b/src/webui/views.rs index 254bd51..4b5eed0 100644 --- a/src/webui/views.rs +++ b/src/webui/views.rs @@ -21,9 +21,9 @@ pub async fn actions(tmpl_env: MiniJinjaRenderer) -> actix_web::Result) -> actix_web::Result { +pub async fn get_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(&conn)?; let user = auth_service.get_login(&req); @@ -34,9 +34,9 @@ pub async fn accounts(tmpl_env: MiniJinjaRenderer, } #[actix_web::get("/accounts/{account_id}")] -pub async fn account(tmpl_env: MiniJinjaRenderer, - req: HttpRequest, - service_factory: web::Data) -> actix_web::Result { +pub async fn get_account(tmpl_env: MiniJinjaRenderer, + req: HttpRequest, + service_factory: web::Data) -> actix_web::Result { let path_event_type = req.match_info().get("account_id").unwrap(); let account_id = AccountId::from_string(&path_event_type.to_string()).unwrap(); let conn = service_factory.get_connection()?;