Started to implement account history.
This commit is contained in:
1 parent
20b5f32c71
commit
f4593df95b
8 files changed
+158
-10
No files matched your search
@@ -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<i64>,
|
||||||
|
amounts: Vec<i64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AccountHistory {
|
||||||
|
pub fn account(&self) -> &Account {
|
||||||
|
&self.account
|
||||||
|
}
|
||||||
|
pub fn timestamps(&self) -> &Vec<i64> {
|
||||||
|
&self.timestamps
|
||||||
|
}
|
||||||
|
pub fn amount(&self) -> &Vec<i64> {
|
||||||
|
&self.amounts
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_account_history(tx: &Connection, account_id: &AccountId) -> CustomResult<AccountHistory> {
|
||||||
|
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<i64> = ret.iter().map(|r| r.0).collect();
|
||||||
|
let mut timestamps: Vec<i64> = 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(())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -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 {
|
pub struct Account {
|
||||||
id: i64,
|
id: i64,
|
||||||
amount: 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)]
|
#[derive(Debug)]
|
||||||
struct AccountSnapshot {
|
struct AccountSnapshot {
|
||||||
amount: i64,
|
amount: i64,
|
||||||
|
|||||||
@@ -233,7 +233,7 @@ pub mod test_external_ids {
|
|||||||
#[test]
|
#[test]
|
||||||
fn test_micro_id() {
|
fn test_micro_id() {
|
||||||
let sid = SecurityId::from_id(33);
|
let sid = SecurityId::from_id(33);
|
||||||
assert_eq!(sid.micro_id(), "s-ea676268")
|
assert_eq!(sid.micro_id(), "s-ea6762")
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -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 struct NewSecurity {
|
||||||
pub security_type: SecurityType,
|
pub security_type: SecurityType,
|
||||||
pub symbol: String,
|
pub symbol: String,
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ mod _ledger_internal;
|
|||||||
mod _securities;
|
mod _securities;
|
||||||
mod _test_utils;
|
mod _test_utils;
|
||||||
mod _users;
|
mod _users;
|
||||||
|
mod _account_history;
|
||||||
|
|
||||||
pub mod ledger_internal {
|
pub mod ledger_internal {
|
||||||
pub use super::_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 mod cheques {
|
||||||
pub use super::_cheques::{Cheque, Drawer, Payee, WireTransferRequest};
|
pub use super::_cheques::{Cheque, Drawer, Payee, WireTransferRequest};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ use crate::db::external_id::{AccountId, Id, SecurityId, UserId};
|
|||||||
use crate::util::{CustomResult, Error};
|
use crate::util::{CustomResult, Error};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use crate::db::accounts;
|
use crate::db::{account_history, accounts};
|
||||||
use crate::db::securities::Security;
|
use crate::db::securities::Security;
|
||||||
use crate::services::_securities::SecurityDTO;
|
use crate::services::_securities::SecurityDTO;
|
||||||
|
|
||||||
@@ -22,6 +22,11 @@ pub struct AccountDTO {
|
|||||||
pub label: String,
|
pub label: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Serialize, Debug)]
|
||||||
|
pub struct AccountHistoryDTO {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
impl AccountDTO {
|
impl AccountDTO {
|
||||||
fn from_entity(account: & Account) -> AccountDTO {
|
fn from_entity(account: & Account) -> AccountDTO {
|
||||||
AccountDTO{
|
AccountDTO{
|
||||||
@@ -66,4 +71,10 @@ impl AccountService<'_> {
|
|||||||
let ret = &accounts::get_account_by_id(self.conn, account_id)?;
|
let ret = &accounts::get_account_by_id(self.conn, account_id)?;
|
||||||
Ok(AccountDTO::from_entity(ret))
|
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(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
+2
-2
@@ -70,8 +70,8 @@ fn create_views_scope() -> Scope {
|
|||||||
.service(index_views::login)
|
.service(index_views::login)
|
||||||
.service(index_views::logout)
|
.service(index_views::logout)
|
||||||
.service(views::actions)
|
.service(views::actions)
|
||||||
.service(views::accounts)
|
.service(views::get_accounts)
|
||||||
.service(views::account)
|
.service(views::get_account)
|
||||||
.service(views::securities)
|
.service(views::securities)
|
||||||
.service(views::security)
|
.service(views::security)
|
||||||
.service(views::users)
|
.service(views::users)
|
||||||
|
|||||||
+6
-6
@@ -21,9 +21,9 @@ 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,
|
pub async fn get_accounts(tmpl_env: MiniJinjaRenderer,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
|
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
|
||||||
let conn = service_factory.get_connection()?;
|
let conn = service_factory.get_connection()?;
|
||||||
let auth_service = service_factory.get_authentication_service(&conn)?;
|
let auth_service = service_factory.get_authentication_service(&conn)?;
|
||||||
let user = auth_service.get_login(&req);
|
let user = auth_service.get_login(&req);
|
||||||
@@ -34,9 +34,9 @@ pub async fn accounts(tmpl_env: MiniJinjaRenderer,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[actix_web::get("/accounts/{account_id}")]
|
#[actix_web::get("/accounts/{account_id}")]
|
||||||
pub async fn account(tmpl_env: MiniJinjaRenderer,
|
pub async fn get_account(tmpl_env: MiniJinjaRenderer,
|
||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
|
service_factory: web::Data<ServicesFactory>) -> actix_web::Result<impl Responder> {
|
||||||
let path_event_type = req.match_info().get("account_id").unwrap();
|
let path_event_type = req.match_info().get("account_id").unwrap();
|
||||||
let account_id = AccountId::from_string(&path_event_type.to_string()).unwrap();
|
let account_id = AccountId::from_string(&path_event_type.to_string()).unwrap();
|
||||||
let conn = service_factory.get_connection()?;
|
let conn = service_factory.get_connection()?;
|
||||||
|
|||||||
Reference in new issue
Block a user