Initial commit

This is definitely not functional yet.

Signed-off-by: Jacob Kiers <code@kiers.eu>
This commit is contained in:
2024-10-04 20:27:23 +02:00
commit f0b8df90b9
582 changed files with 43994 additions and 0 deletions

6
bank2ff/.env.example Normal file
View File

@@ -0,0 +1,6 @@
FIREFLY_III_URL=
FIREFLY_III_API_KEY=
FIREFLY_III_CLIENT_ID=
GOCARDLESS_KEY=
GOCARDLESS_ID=

2
bank2ff/.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
../.env

11
bank2ff/Cargo.toml Normal file
View File

@@ -0,0 +1,11 @@
[package]
name = "bank2ff"
version = "0.1.0"
edition = "2021"
[dependencies]
firefly-iii-api = { path = "../firefly-iii-api", version = "2.1.0" }
gocardless-bankaccount-data-api = { path = '../gocardless-bankaccount-data-api', version = "2.0.0" }
dotenv = "0.15.0"
serde = { version = "1.0.210", features = ["derive"] }
tokio = { workspace = true, features = ["rt", "rt-multi-thread", "macros"] }

View File

@@ -0,0 +1,44 @@
{
"version": 3,
"source": "ff3-importer-1.5.2",
"created_at": "2024-09-07T10:07:05+02:00",
"date": "",
"default_account": 1142,
"delimiter": "comma",
"headers": false,
"rules": true,
"skip_form": false,
"add_import_tag": true,
"roles": [],
"do_mapping": [],
"mapping": [],
"duplicate_detection_method": "classic",
"ignore_duplicate_lines": false,
"unique_column_index": 0,
"unique_column_type": "external-id",
"flow": "nordigen",
"content_type": "unknown",
"custom_tag": "",
"identifier": "0",
"connection": "0",
"ignore_spectre_categories": false,
"grouped_transaction_handling": "",
"use_entire_opposing_address": false,
"map_all_data": false,
"accounts": {
"4cda1369-178c-485b-b3d8-1892afdbfb6c": 1142
},
"date_range": "range",
"date_range_number": 30,
"date_range_unit": "d",
"date_not_before": "2024-06-29",
"date_not_after": "2024-09-06",
"nordigen_country": "NL",
"nordigen_bank": "ASN_BANK_ASNBNL21",
"nordigen_requisitions": {
"b2e6fd94-fc45-484c-abc1-5f410a58a220": "c5006758-135b-4770-8715-2a047a426973"
},
"nordigen_max_days": "90",
"conversion": false,
"ignore_duplicate_transactions": true
}

29
bank2ff/src/config.rs Normal file
View File

@@ -0,0 +1,29 @@
use serde::Deserialize;
#[derive(Deserialize, Debug)]
pub(super) struct AppConfiguration {
pub(crate) firefly_iii_url: String,
pub(crate) firefly_iii_api_key: String,
pub(crate) go_cardless_key: String,
pub(crate) go_cardless_id: String,
}
impl AppConfiguration {
pub(super) fn from_env() -> Result<Self, dotenv::Error> {
use dotenv::var;
let firefly_iii_url = var("FIREFLY_III_URL")?;
let firefly_iii_api_key = var("FIREFLY_III_API_KEY")?;
let go_cardless_key = var("GOCARDLESS_KEY")?;
let go_cardless_id = var("GOCARDLESS_ID")?;
Ok(Self {
firefly_iii_url,
firefly_iii_api_key,
go_cardless_key,
go_cardless_id,
})
}
}

108
bank2ff/src/firefly.rs Normal file
View File

@@ -0,0 +1,108 @@
use firefly_iii_api::apis::configuration::Configuration;
use firefly_iii_api::apis::{accounts_api, transactions_api};
use firefly_iii_api::models::{AccountRead, TransactionRead};
pub(super) async fn load_all_transactions(ff: &mut FFTransactions) -> Result<(), ()> {
let mut has_more = true;
let mut page = None;
while has_more {
match transactions_api::list_transaction(
&ff.config,
None,
Some(500),
page,
None,
None,
None,
)
.await
{
Ok(transactions) => {
has_more = transactions.links.next.is_some();
let pagination = transactions.meta.pagination.clone().unwrap();
let next = pagination.current_page.unwrap() + 1;
page = Some(next);
println!(
"Page {} of {}",
pagination.current_page.unwrap(),
pagination.total_pages.unwrap()
);
transactions
}
Err(e) => {
dbg!(e);
return Ok(());
}
}
.data
.iter()
.for_each(|tx| {
ff.transactions.push(tx.to_owned());
});
}
Ok(())
}
pub(super) async fn load_all_accounts(ff: &mut FFTransactions) -> Result<(), ()> {
let mut has_more = true;
let mut page = None;
while has_more {
match accounts_api::list_account(&ff.config, None, Some(500), page, None, None).await {
Ok(accounts) => {
let pagination = accounts.meta.pagination.clone().unwrap();
has_more = pagination.current_page < pagination.total_pages;
let next = pagination.current_page.unwrap() + 1;
page = Some(next);
println!(
"Page {} of {}",
pagination.current_page.unwrap(),
pagination.total_pages.unwrap()
);
accounts.data
}
Err(e) => {
dbg!(e);
return Ok(());
}
}
.iter()
.for_each(|a| ff.accounts.push(a.to_owned()));
}
Ok(())
}
pub(super) struct FFTransactions {
accounts: Vec<AccountRead>,
transactions: Vec<TransactionRead>,
config: Configuration,
}
impl FFTransactions {
pub(super) fn new(config: Configuration) -> Self {
Self {
accounts: Vec::with_capacity(1000),
transactions: Vec::with_capacity(10000),
config,
}
}
pub(super) fn accounts(&self) -> &Vec<AccountRead> {
&self.accounts
}
pub(super) fn transactions(&self) -> &Vec<TransactionRead> {
&self.transactions
}
pub fn find_account_by_iban(&self, iban: &str) -> Option<&AccountRead> {
let to_check = Some(Some(iban.to_owned()));
self.accounts.iter().find(|a| a.attributes.iban == to_check)
}
}

98
bank2ff/src/main.rs Normal file
View File

@@ -0,0 +1,98 @@
mod config;
mod firefly;
use crate::config::AppConfiguration;
use crate::firefly::{load_all_accounts, load_all_transactions, FFTransactions};
use firefly_iii_api::apis::configuration;
use tokio::io::AsyncReadExt;
use gocardless_bankaccount_data_api::models::{JwtObtainPairRequest, StatusEnum};
#[tokio::main]
async fn main() -> Result<(), dotenv::Error> {
dotenv::dotenv().ok();
let config = AppConfiguration::from_env()?;
// let mut ff = FFTransactions::new(configuration::Configuration {
// base_path: config.firefly_iii_url,
// user_agent: None,
// client: Default::default(),
// basic_auth: None,
// oauth_access_token: None,
// bearer_access_token: Some(config.firefly_iii_api_key),
// api_key: None,
// });
//
// let _ = load_all_accounts(&mut ff).await;
// println!("#Accounts:\t{}", ff.accounts().len());
// let _ = load_all_transactions(&mut ff).await;
// println!("#Transactions:\t{}", ff.transactions().len());
let mut gocardless_config = gocardless_bankaccount_data_api::apis::configuration::Configuration::new();
let gc_token_pair = gocardless_bankaccount_data_api::apis::token_api::obtain_new_access_slash_refresh_token_pair(
&gocardless_config,JwtObtainPairRequest::new(config.go_cardless_id, config.go_cardless_key)).await.unwrap();
gocardless_config.bearer_access_token = gc_token_pair.access;
// let institutions = gocardless_bankaccount_data_api::apis::institutions_api::retrieve_all_supported_institutions_in_a_given_country(
// &gocardless_config,
// None,
// None,
// None,
// None,
// None,
// Some("NL"),
// None,
// None,
// None,
// None,
// None,
// None,
// None
// ).await.unwrap();
let gc_reqs = gocardless_bankaccount_data_api::apis::requisitions_api::retrieve_all_requisitions(&gocardless_config, None, None)
.await
.unwrap();
dbg!("# of requisitions:{} ", &gc_reqs.results.len());
let active_reqs = gc_reqs
.results.iter()
.filter(|req| req.status == Some(StatusEnum::Ln) && req.institution_id != "BUNQ_BUNQNL2A").collect::<Vec<_>>();
dbg!("# of active requisitions:{} ", &active_reqs.len());
for req in active_reqs {
if req.accounts.is_none() {
dbg!("No active accounts for requisition {}", req.id);
continue;
}
let accounts = req.accounts.as_ref().unwrap();
for account in accounts {
let transactions_resp = gocardless_bankaccount_data_api::apis::accounts_api::retrieve_account_transactions(
&gocardless_config,
&account.to_string(),
Some("2024-09-01".to_string()),
Some("2024-09-30".to_string())
).await;
if transactions_resp.is_err() {
dbg!("{:?}", transactions_resp.unwrap_err());
// TODO: Do something smarter here, if possible
continue;
}
let transactions = transactions_resp.unwrap();
dbg!(&transactions);
}
}
let _ = tokio::io::stdin().read_u8().await;
Ok(())
}