Skip to content

Commit

Permalink
reinstate transactions
Browse files Browse the repository at this point in the history
  • Loading branch information
richardjlyon committed Jun 7, 2023
1 parent 8a1ab1b commit 0396893
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 150 deletions.
2 changes: 2 additions & 0 deletions migration/src/m20220101_000001_create_transaction_table.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ impl MigrationTrait for Migration {
.primary_key(),
)
.col(ColumnDef::new(Transaction::Uid).string().not_null())
.col(ColumnDef::new(Transaction::AccountUid).string().not_null())
.col(
ColumnDef::new(Transaction::TransactionTime)
.timestamp()
Expand Down Expand Up @@ -60,6 +61,7 @@ pub enum Transaction {
Table,
Id,
Uid,
AccountUid,
TransactionTime,
CounterpartyID,
Amount,
Expand Down
12 changes: 10 additions & 2 deletions src/bin/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ fn cli() -> Command {
.subcommand(
Command::new("transactions")
.about("get transactions")
.arg(arg!(-d [DAYS] "The days to get").default_value("31")),
.arg(arg!(days: [DAYS] "The days to get").default_value("31")),
)
}

Expand Down Expand Up @@ -79,6 +79,7 @@ async fn main() -> Result<()> {
("list", _) => {
commands::account::list().await?;
}

("balance", _) => {
commands::account::balance().await?;
}
Expand All @@ -91,7 +92,14 @@ async fn main() -> Result<()> {

Some(("transactions", sub_matches)) => {
println!("Processing transactions");
let days = *sub_matches.get_one::<i64>("DAYS").expect("required");
let days = sub_matches
.get_one::<String>("days")
.map(|s| s.as_str())
.unwrap();
let days: i64 = days.parse().unwrap();

println!("Getting {} days", days);

if let Err(e) = commands::transactions::update(days).await {
println!("Application error: {}", e);
process::exit(1);
Expand Down
6 changes: 4 additions & 2 deletions src/commands/admin.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! Command Line Interface `Admin` commands
//!
/*!
Command Line Interface `Admin` commands
*/

use crate::config::Config;
use crate::db::{self};
Expand Down
8 changes: 5 additions & 3 deletions src/commands/transactions.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
//! Command Line Interface `Transaction` commands
//!
/*!
Command Line Interface `Transaction` commands
*/

use crate::db;
use anyhow::Result;

/// Fetch transactions for the specified number of days and save to the database
pub async fn update(days: i64) -> Result<()> {
db::transaction::insert_or_update(days).await;
db::transaction::insert_or_update(days).await?;

Ok(())
}
267 changes: 124 additions & 143 deletions src/db/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,119 +2,98 @@
//!
use super::get_database;
use crate::db;
use crate::entities::counterparty;
use crate::starling::client::{StarlingApiClient, StarlingClient};
use crate::{
config::Config,
entities::{prelude::*, transaction},
starling::{
transaction::StarlingTransaction,
},
starling::transaction::StarlingTransaction,
};
use anyhow::Result;

use chrono::Duration;
use sea_orm::*;

/// Insert or update a list of Starling transactions for the specified account and number of days.
///
/// If the transaction doesn't exist, insert it. If it exists and its status has changed, update it.
pub async fn insert_or_update(_days: i64) {
let _db = get_database().await.unwrap();
let config = Config::new();
config.load();

// for item in config.token.unwrap().iter() {
// for token in item.values() {
// let client = StarlingApiClient::new(token);
// for account in client.accounts().await.iter() {
// // fetch latest transactions
// let transactions = client
// .transactions_since(
// &account.uid,
// &account.default_category,
// chrono::Duration::days(days),
// )
// .await;

// // insert or update tables
// for transaction in transactions {
// println!("{:#?}", transaction);
// match transaction_exists(&db, &transaction.uid).await {
// Some(record) => {
// if transaction_changed(&record, &transaction) {
// // update the transaction
// }
// }
// None => {
// // insert the counterparty
// // insert the transaction
// }
// }
// }
// }
// }
// }

// for transaction in transactions {
// println!("{:#?}", transaction);
// }

// for item in client
// .transactions_since(
// &account.account_uid,
// &account.default_category,
// chrono::Duration::days(days),
// )
// .await
// {
// match feeditem_exists(&db, &item.uid).await {
// // if the feed item doesn't already exist
// None => {
// // insert or get the counterparty id
// let item_counterparty_uid = item.counterparty_uid.clone().unwrap_or_default();
// let counterparty_id = match counterparty_exists(&db, &item_counterparty_uid).await {
// Some(counterparty) => counterparty.id,
// None => {
// let counterparty = counterparty_from_starling_feed_item(&item);
// let record = Counterparty::insert(counterparty)
// .exec(&db)
// .await
// .expect("inserting counterparty");
// record.last_insert_id
// }
// };

// // insert the new feed item
// let record = record_from_starling_feed_item(&item, counterparty_id);
// Transaction::insert(record)
// .exec(&db)
// .await
// .expect("inserting feed item");
// }
// // if the feed item does exist
// Some(record) => {
// // if the feed item status has changed
// if feeditem_has_changed(&record, &item) {
// // update the feed item status
// // TODO : refactor this to update status field only
// let new_status = item.status.to_string();
// let new_spending_category = item.spending_category;
// let new_user_note = item.user_note.clone().unwrap_or_default();

// let record = transaction::ActiveModel {
// status: ActiveValue::set(new_status.to_owned()),
// spending_category: ActiveValue::set(new_spending_category.to_owned()),
// user_note: ActiveValue::set(new_user_note.to_owned()),
// id: ActiveValue::Set(record.id.to_owned()),
// feed_uid: ActiveValue::Set(record.feed_uid.to_owned()),
// transaction_time: ActiveValue::set(record.transaction_time.to_owned()),
// counterparty_id: ActiveValue::set(record.counterparty_id.to_owned()),
// amount: ActiveValue::set(record.amount).to_owned(),
// currency: ActiveValue::set(record.currency.to_owned()),
// reference: ActiveValue::set(record.reference.to_owned()),
// };
// record.update(&db).await.expect("updating feed item");
// }
// }
// }
// }
pub async fn insert_or_update(days: i64) -> Result<()> {
let db = get_database().await.unwrap();
for account in db::account::list().await? {
// fetch the latest transactions

let client = StarlingApiClient::new(&account.token);
let transactions = client
.transactions_since(
&account.uid,
&account.default_category,
Duration::days(days),
)
.await;

for transaction in transactions {
match transaction_exists(&db, &transaction.uid).await {
None => {
// insert or get the counterparty id

let item_counterparty_uid =
transaction.counterparty_uid.clone().unwrap_or_default();

let counterparty_id = match counterparty_exists(&db, &item_counterparty_uid)
.await
{
Some(counterparty) => counterparty.id,

None => {
let counterparty = counterparty_from_starling_feed_item(&transaction);
let record = Counterparty::insert(counterparty)
.exec(&db)
.await
.expect("inserting counterparty");
record.last_insert_id
}
};

// insert the new transaction

let record =
record_from_starling_feed_item(&transaction, counterparty_id, &account.uid);
Transaction::insert(record)
.exec(&db)
.await
.expect("inserting feed item");
}

Some(record) => {
if transaction_changed(&record, &transaction) {
// update the feed item status
// TODO : refactor this to update status field only

let new_status = transaction.status.to_string();
let new_spending_category = transaction.spending_category;
let new_user_note = transaction.user_note.clone().unwrap_or_default();

let record = transaction::ActiveModel {
account_uid: ActiveValue::Set(record.account_uid.to_owned()),
status: ActiveValue::set(new_status.to_owned()),
spending_category: ActiveValue::set(new_spending_category.to_owned()),
user_note: ActiveValue::set(new_user_note.to_owned()),
id: ActiveValue::Set(record.id.to_owned()),
uid: ActiveValue::Set(record.uid.to_owned()),
transaction_time: ActiveValue::set(record.transaction_time.to_owned()),
counterparty_id: ActiveValue::set(record.counterparty_id.to_owned()),
amount: ActiveValue::set(record.amount).to_owned(),
currency: ActiveValue::set(record.currency.to_owned()),
reference: ActiveValue::set(record.reference.to_owned()),
};
record.update(&db).await.expect("updating feed item");
}
}
}
}
}

Ok(())
}

/// Return true if a feed item with the given feed uid exists in the database.
Expand All @@ -132,46 +111,48 @@ async fn transaction_exists(
// Return true if status or spending category has changed
fn transaction_changed(record: &transaction::Model, newitem: &StarlingTransaction) -> bool {
(record.status != newitem.status.to_string())
|| (record.spending_category != newitem.spending_category.to_string())
|| (record.spending_category != newitem.spending_category)
|| (record.user_note != newitem.user_note.clone().unwrap_or_default().to_string())
}

// Return true if a counterparty with the given counterparty uid exists in the database.
// async fn counterparty_exists(
// db: &DatabaseConnection,
// counterparty_uid: &String,
// ) -> Option<counterparty::Model> {
// Counterparty::find()
// .filter(counterparty::Column::Uid.eq(counterparty_uid))
// .one(db)
// .await
// .expect("getting counterparty id")
// }

// fn record_from_starling_feed_item(
// item: &StarlingTransaction,
// counterparty_id: i32,
// ) -> transaction::ActiveModel {
// transaction::ActiveModel {
// uid: ActiveValue::Set(item.uid.to_owned()),
// transaction_time: ActiveValue::Set(item.transaction_time.to_owned()),
// counterparty_id: ActiveValue::Set(counterparty_id),
// amount: ActiveValue::set(item.amount()).to_owned(),
// spending_category: ActiveValue::set(item.spending_category.to_owned()),
// currency: ActiveValue::set(item.currency().to_owned()),
// reference: ActiveValue::set(item.reference.clone().unwrap_or_default().to_owned()),
// user_note: ActiveValue::set(item.user_note.clone().unwrap_or_default().to_owned()),
// status: ActiveValue::set(item.status.to_string()),
// ..Default::default()
// }
// }

// fn counterparty_from_starling_feed_item(item: &StarlingTransaction) -> counterparty::ActiveModel {
// let item_counterparty_uid = item.counterparty_uid.clone().unwrap_or_default();
// counterparty::ActiveModel {
// uid: ActiveValue::Set(item_counterparty_uid.to_owned()),
// name: ActiveValue::Set(item.counterparty_name.to_owned()),
// r#type: ActiveValue::Set(item.counterparty_type.to_owned()),
// ..Default::default()
// }
// }
async fn counterparty_exists(
db: &DatabaseConnection,
counterparty_uid: &String,
) -> Option<counterparty::Model> {
Counterparty::find()
.filter(counterparty::Column::Uid.eq(counterparty_uid))
.one(db)
.await
.expect("getting counterparty id")
}

fn record_from_starling_feed_item(
item: &StarlingTransaction,
counterparty_id: i32,
account_uid: &str,
) -> transaction::ActiveModel {
transaction::ActiveModel {
uid: ActiveValue::Set(item.uid.to_owned()),
account_uid: ActiveValue::Set(account_uid.to_string()),
transaction_time: ActiveValue::Set(item.transaction_time.to_owned()),
counterparty_id: ActiveValue::Set(counterparty_id),
amount: ActiveValue::set(item.amount()),
spending_category: ActiveValue::set(item.spending_category.to_owned()),
currency: ActiveValue::set(item.currency()),
reference: ActiveValue::set(item.reference.clone().unwrap_or_default()),
user_note: ActiveValue::set(item.user_note.clone().unwrap_or_default()),
status: ActiveValue::set(item.status.to_string()),
..Default::default()
}
}

fn counterparty_from_starling_feed_item(item: &StarlingTransaction) -> counterparty::ActiveModel {
let item_counterparty_uid = item.counterparty_uid.clone().unwrap_or_default();
counterparty::ActiveModel {
uid: ActiveValue::Set(item_counterparty_uid),
name: ActiveValue::Set(item.counterparty_name.to_owned()),
r#type: ActiveValue::Set(item.counterparty_type.to_owned()),
..Default::default()
}
}
1 change: 1 addition & 0 deletions src/entities/account.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub name: String,
#[sea_orm(column_type = "Binary(BlobSize::Blob(Some(16)))")]
pub uid: String,
pub created_at: DateTimeUtc,
pub default_category: String,
Expand Down
1 change: 1 addition & 0 deletions src/entities/transaction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ pub struct Model {
#[sea_orm(primary_key)]
pub id: i32,
pub uid: String,
pub account_uid: String,
pub transaction_time: DateTimeUtc,
pub counterparty_id: i32,
#[sea_orm(column_type = "Float")]
Expand Down

0 comments on commit 0396893

Please sign in to comment.