diff options
Diffstat (limited to 'backend')
100 files changed, 998 insertions, 701 deletions
diff --git a/backend/app/src/queries/accounts/get_all.rs b/backend/app/src/queries/accounts/get_all.rs deleted file mode 100644 index bc67f77..0000000 --- a/backend/app/src/queries/accounts/get_all.rs +++ /dev/null @@ -1,6 +0,0 @@ -use budgeting_app_core::{models::account::Account, schema::accounts::dsl::accounts}; -use diesel::{result::Error, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; - -pub fn get_all_accounts(connection: &mut SqliteConnection) -> Result<Vec<Account>, Error> { - accounts.select(Account::as_select()).load(connection) -} diff --git a/backend/app/src/queries/accounts/insert.rs b/backend/app/src/queries/accounts/insert.rs deleted file mode 100644 index 1d1a323..0000000 --- a/backend/app/src/queries/accounts/insert.rs +++ /dev/null @@ -1,15 +0,0 @@ -use budgeting_app_core::{ - models::account::Account, - schema::accounts::dsl::accounts as accounts_table, -}; -use diesel::prelude::*; -use diesel::result::Error; - -pub fn insert_account( - accounts: &[Account], - connection: &mut SqliteConnection, -) -> Result<usize, Error> { - diesel::insert_into(accounts_table) - .values(accounts) - .execute(connection) -} diff --git a/backend/app/src/queries/accounts/mod.rs b/backend/app/src/queries/accounts/mod.rs deleted file mode 100644 index 94fa360..0000000 --- a/backend/app/src/queries/accounts/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod get_all; -mod insert; - -pub use get_all::get_all_accounts; -pub use insert::insert_account; diff --git a/backend/app/src/queries/budget_updates/get_all.rs b/backend/app/src/queries/budget_updates/get_all.rs deleted file mode 100644 index e6e4b59..0000000 --- a/backend/app/src/queries/budget_updates/get_all.rs +++ /dev/null @@ -1,13 +0,0 @@ -use budgeting_app_core::{ - models::budget_update::BudgetUpdate, - schema::budget_updates::dsl::budget_updates as budget_updates_table, -}; -use diesel::{result::Error, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; - -pub fn get_all_budget_updates( - connection: &mut SqliteConnection, -) -> Result<Vec<BudgetUpdate>, Error> { - budget_updates_table - .select(BudgetUpdate::as_select()) - .load::<BudgetUpdate>(connection) -} diff --git a/backend/app/src/queries/categories/get_all.rs b/backend/app/src/queries/categories/get_all.rs deleted file mode 100644 index b6ec756..0000000 --- a/backend/app/src/queries/categories/get_all.rs +++ /dev/null @@ -1,8 +0,0 @@ -use budgeting_app_core::{models::category::Category, schema::categories::dsl::categories as categories_table}; -use diesel::{result::Error, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; - -pub fn get_all_categories(connection: &mut SqliteConnection) -> Result<Vec<Category>, Error> { - categories_table - .select(Category::as_select()) - .load(connection) -} diff --git a/backend/app/src/queries/categories/get_all_ids.rs b/backend/app/src/queries/categories/get_all_ids.rs deleted file mode 100644 index 48d5e57..0000000 --- a/backend/app/src/queries/categories/get_all_ids.rs +++ /dev/null @@ -1,8 +0,0 @@ -use budgeting_app_core::schema::categories::{self as categories_schema, dsl::categories as categories_table}; -use diesel::{result::Error, QueryDsl, RunQueryDsl, SqliteConnection}; - -pub fn get_all_category_ids(connection: &mut SqliteConnection) -> Result<Vec<i32>, Error> { - categories_table - .select(categories_schema::id) - .load(connection) -} diff --git a/backend/app/src/queries/categories/mod.rs b/backend/app/src/queries/categories/mod.rs deleted file mode 100644 index 80356b3..0000000 --- a/backend/app/src/queries/categories/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod get_all; -mod get_all_ids; - -pub use self::get_all::get_all_categories; -pub use self::get_all_ids::get_all_category_ids; diff --git a/backend/app/src/queries/category_balances/mod.rs b/backend/app/src/queries/category_balances/mod.rs deleted file mode 100644 index 3fc1be2..0000000 --- a/backend/app/src/queries/category_balances/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod get_all; - -pub use get_all::get_all_category_balances; diff --git a/backend/app/src/queries/category_budgets/mod.rs b/backend/app/src/queries/category_budgets/mod.rs deleted file mode 100644 index 29fc246..0000000 --- a/backend/app/src/queries/category_budgets/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod get_most_recent_per_category_id; - -pub use self::get_most_recent_per_category_id::get_most_recent_category_budget_per_category_id; diff --git a/backend/app/src/queries/transaction_categorisations/mod.rs b/backend/app/src/queries/transaction_categorisations/mod.rs deleted file mode 100644 index d4217cc..0000000 --- a/backend/app/src/queries/transaction_categorisations/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod sum_quantity_per_category_id; - -pub use sum_quantity_per_category_id::sum_transaction_categorisation_quantity_per_category_id; diff --git a/backend/app/src/routes/account.rs b/backend/app/src/routes/account.rs index 7c29233..e4eb153 100644 --- a/backend/app/src/routes/account.rs +++ b/backend/app/src/routes/account.rs @@ -1,11 +1,13 @@ -use budgeting_app_core::models::account::Account; +use budgeting_app_core::{ + models::account::Account, + queries::accounts::{get_all_accounts as get_all_accounts_query, insert_account}, +}; use diesel::result::DatabaseErrorKind; use rocket::http::Status; use rocket::serde::json::Json; use rocket::{get, post, Responder}; use crate::databases::UserDataDb; -use crate::queries::accounts::{get_all_accounts as get_all_accounts_query, insert_account}; #[get("/account")] pub async fn get_all_accounts(user_data_db: UserDataDb) -> Result<Json<Vec<Account>>, (Status, String)> { diff --git a/backend/app/src/routes/balance.rs b/backend/app/src/routes/balance.rs index 642aff1..2dcae7b 100644 --- a/backend/app/src/routes/balance.rs +++ b/backend/app/src/routes/balance.rs @@ -1,9 +1,11 @@ -use budgeting_app_core::models::category_balance::CategoryBalance; +use budgeting_app_core::{ + models::category_balance::CategoryBalance, + queries::category_balances::get_all_category_balances as get_all_category_balances_query, +}; use rocket::{get, http::Status, serde::json::Json}; use crate::{ databases::UserDataDb, - queries::category_balances::get_all_category_balances as get_all_category_balances_query, }; #[get("/balance/category")] diff --git a/backend/app/src/routes/budget.rs b/backend/app/src/routes/budget.rs index 64b9e06..746635b 100644 --- a/backend/app/src/routes/budget.rs +++ b/backend/app/src/routes/budget.rs @@ -1,17 +1,19 @@ -use budgeting_app_core::models::category_budget::CategoryBudget; +use budgeting_app_core::{ + models::category_budget::CategoryBudget, + queries::category_budgets::get_most_recent_category_budget_per_category_id as get_most_recent_category_budget_per_category_id_query, +}; use rocket::get; use rocket::http::Status; use rocket::serde::json::Json; use crate::databases::UserDataDb; -use crate::queries; #[get("/budget/category?current")] pub async fn get_current_category_budget_per_category( user_data_db: UserDataDb, ) -> Result<Json<Vec<CategoryBudget>>, Status> { user_data_db - .run(queries::category_budgets::get_most_recent_category_budget_per_category_id) + .run(get_most_recent_category_budget_per_category_id_query) .await .map(Json) .map_err(|_| Status { code: 500 }) diff --git a/backend/app/src/routes/budget_update.rs b/backend/app/src/routes/budget_update.rs index 6def31a..86dda9f 100644 --- a/backend/app/src/routes/budget_update.rs +++ b/backend/app/src/routes/budget_update.rs @@ -1,17 +1,19 @@ -use budgeting_app_core::models::budget_update::BudgetUpdate; +use budgeting_app_core::{ + models::budget_update::BudgetUpdate, + queries::budget_updates::get_all_budget_updates as get_all_budget_updates_query, +}; use rocket::get; use rocket::http::Status; use rocket::serde::json::Json; use crate::databases::UserDataDb; -use crate::queries; #[get("/budget-update")] pub async fn get_all_budget_updates( user_data_db: UserDataDb, ) -> Result<Json<Vec<BudgetUpdate>>, Status> { user_data_db - .run(queries::budget_updates::get_all_budget_updates) + .run(get_all_budget_updates_query) .await .map(Json) .map_err(|_| Status { code: 500 }) diff --git a/backend/app/src/routes/category.rs b/backend/app/src/routes/category.rs index 43810e9..ca021fc 100644 --- a/backend/app/src/routes/category.rs +++ b/backend/app/src/routes/category.rs @@ -1,10 +1,12 @@ -use budgeting_app_core::models::category::Category; +use budgeting_app_core::{ + models::category::Category, + queries::categories::get_all_categories as get_all_categories_query, +}; use rocket::get; use rocket::http::Status; use rocket::serde::json::Json; use crate::databases::UserDataDb; -use crate::queries::categories::get_all_categories as get_all_categories_query; #[get("/category")] pub async fn get_all_categories(user_data_db: UserDataDb) -> Result<Json<Vec<Category>>, Status> { diff --git a/backend/core/Cargo.lock b/backend/core/Cargo.lock index 0a4e0fd..a889422 100644 --- a/backend/core/Cargo.lock +++ b/backend/core/Cargo.lock @@ -18,6 +18,12 @@ dependencies = [ ] [[package]] +name = "anyhow" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" + +[[package]] name = "autocfg" version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -27,6 +33,7 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" name = "budgeting_app_core" version = "0.1.0" dependencies = [ + "anyhow", "chrono", "diesel", "diesel_migrations", diff --git a/backend/core/Cargo.toml b/backend/core/Cargo.toml index 09b1ebf..bf87568 100644 --- a/backend/core/Cargo.toml +++ b/backend/core/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +anyhow = { version = "1.0.0" } chrono = { version = "0.4.38", features = ["serde"] } diesel = { version = "2.2.0", features = ["sqlite"] } diesel_migrations = "2.2.0" diff --git a/backend/core/2024-08-31-084439_initial_setup/down.sql b/backend/core/migrations/2024-08-31-084439_initial_setup/down.sql index 8cf4b1e..8cf4b1e 100644 --- a/backend/core/2024-08-31-084439_initial_setup/down.sql +++ b/backend/core/migrations/2024-08-31-084439_initial_setup/down.sql diff --git a/backend/core/2024-08-31-084439_initial_setup/up.sql b/backend/core/migrations/2024-08-31-084439_initial_setup/up.sql index 3584ae7..3584ae7 100644 --- a/backend/core/2024-08-31-084439_initial_setup/up.sql +++ b/backend/core/migrations/2024-08-31-084439_initial_setup/up.sql diff --git a/backend/core/src/lib.rs b/backend/core/src/lib.rs index 7a56560..cb65fe1 100644 --- a/backend/core/src/lib.rs +++ b/backend/core/src/lib.rs @@ -1,6 +1,9 @@ use diesel_migrations::{embed_migrations, EmbeddedMigrations}; pub mod models; +pub mod queries; pub mod schema; +mod utils; + pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); diff --git a/backend/core/src/models/budget_update.rs b/backend/core/src/models/budget_update.rs index c63e957..8600419 100644 --- a/backend/core/src/models/budget_update.rs +++ b/backend/core/src/models/budget_update.rs @@ -5,7 +5,7 @@ use crate::models::category::Category; use super::date_time_utc::DateTimeUtc; -#[derive(Clone, Serialize, Queryable, Identifiable, Selectable, Associations, Debug, PartialEq)] +#[derive(Clone, Serialize, Queryable, Identifiable, Selectable, Associations, Debug, PartialEq, Insertable)] #[diesel(table_name = crate::schema::budget_updates)] #[diesel(belongs_to(Category))] pub struct BudgetUpdate { diff --git a/backend/core/src/models/category.rs b/backend/core/src/models/category.rs index 5cb2609..595cff6 100644 --- a/backend/core/src/models/category.rs +++ b/backend/core/src/models/category.rs @@ -1,7 +1,7 @@ use diesel::prelude::*; use serde::{Deserialize, Serialize}; -#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq, Serialize, Deserialize)] +#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq, Serialize, Deserialize, Insertable)] #[diesel(table_name = crate::schema::categories)] pub struct Category { pub id: i32, diff --git a/backend/core/src/models/category_transfer.rs b/backend/core/src/models/category_transfer.rs index 6cbda0e..a048a07 100644 --- a/backend/core/src/models/category_transfer.rs +++ b/backend/core/src/models/category_transfer.rs @@ -1,6 +1,6 @@ use diesel::prelude::*; -#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq)] +#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq, Insertable)] #[diesel(table_name = crate::schema::category_transfers)] pub struct CategoryTransfer { pub id: i32, diff --git a/backend/core/src/models/date_time_utc.rs b/backend/core/src/models/date_time_utc.rs index 03d5802..52c0cc6 100644 --- a/backend/core/src/models/date_time_utc.rs +++ b/backend/core/src/models/date_time_utc.rs @@ -1,3 +1,4 @@ +use anyhow::Result; use chrono::{DateTime, Datelike, Utc}; use diesel::{ backend::Backend, @@ -44,6 +45,10 @@ impl DateTimeUtc { Self(date_time_utc) } + pub fn from_date_str(date_time_utc: &str) -> Result<Self> { + Ok(Self(date_time_utc.parse::<DateTime<Utc>>()?)) + } + pub fn now() -> Self { Self(Utc::now()) } diff --git a/backend/core/src/models/transaction.rs b/backend/core/src/models/transaction.rs index 36f33f8..5f85689 100644 --- a/backend/core/src/models/transaction.rs +++ b/backend/core/src/models/transaction.rs @@ -4,7 +4,7 @@ use crate::models::account::Account; use super::date_time_utc::DateTimeUtc; -#[derive(Queryable, Identifiable, Selectable, Associations, Debug, PartialEq)] +#[derive(Queryable, Identifiable, Selectable, Insertable, Associations, Debug, PartialEq)] #[diesel(table_name = crate::schema::transactions)] #[diesel(belongs_to(Account))] pub struct Transaction { diff --git a/backend/core/src/models/transaction_categorisation.rs b/backend/core/src/models/transaction_categorisation.rs index 60c2b98..45591a5 100644 --- a/backend/core/src/models/transaction_categorisation.rs +++ b/backend/core/src/models/transaction_categorisation.rs @@ -1,6 +1,6 @@ use diesel::prelude::*; -#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq)] +#[derive(Queryable, Identifiable, Selectable, Insertable, Debug, PartialEq)] #[diesel(table_name = crate::schema::transaction_categorisations)] pub struct TransactionCategorisation { pub id: i32, diff --git a/backend/core/src/queries/accounts/delete_all.rs b/backend/core/src/queries/accounts/delete_all.rs new file mode 100644 index 0000000..84d7bf6 --- /dev/null +++ b/backend/core/src/queries/accounts/delete_all.rs @@ -0,0 +1,11 @@ +use anyhow::{Context, Result}; +use diesel::{RunQueryDsl, SqliteConnection}; + +use crate::schema::accounts::dsl::accounts; + +pub fn delete_all_accounts(connection: &mut SqliteConnection) -> Result<usize> { + let num_rows_deleted = diesel::delete(accounts) + .execute(connection) + .with_context(|| "failed to delete all accounts")?; + Ok(num_rows_deleted) +} diff --git a/backend/core/src/queries/accounts/get_all.rs b/backend/core/src/queries/accounts/get_all.rs new file mode 100644 index 0000000..ab53729 --- /dev/null +++ b/backend/core/src/queries/accounts/get_all.rs @@ -0,0 +1,10 @@ +use anyhow::{Context, Result}; +use crate::{models::account::Account, schema::accounts::dsl::accounts}; +use diesel::{QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; + +pub fn get_all_accounts(connection: &mut SqliteConnection) -> Result<Vec<Account>> { + let all_accounts = accounts.select(Account::as_select()) + .load(connection) + .with_context(|| "failed to get all accounts")?; + Ok(all_accounts) +} diff --git a/backend/core/src/queries/accounts/insert.rs b/backend/core/src/queries/accounts/insert.rs new file mode 100644 index 0000000..7020b68 --- /dev/null +++ b/backend/core/src/queries/accounts/insert.rs @@ -0,0 +1,28 @@ +use anyhow::{Context, Result}; +use crate::{ + models::account::Account, + schema::accounts::dsl::accounts as accounts_table, +}; +use diesel::prelude::*; + +pub fn insert_accounts( + accounts: &[Account], + connection: &mut SqliteConnection, +) -> Result<usize> { + let num_accounts_inserted = diesel::insert_into(accounts_table) + .values(accounts) + .execute(connection) + .with_context(|| err_msg(accounts))?; + Ok(num_accounts_inserted) +} + +fn err_msg(accounts: &[Account]) -> String { + format!( + "failed to insert accounts: [{}]", + accounts + .iter() + .map(|a| format!("\"{}\": \"{}\"", a.id, a.name)) + .collect::<Vec<String>>() + .join(", ") + ) +} diff --git a/backend/core/src/queries/accounts/mod.rs b/backend/core/src/queries/accounts/mod.rs new file mode 100644 index 0000000..222b180 --- /dev/null +++ b/backend/core/src/queries/accounts/mod.rs @@ -0,0 +1,7 @@ +mod delete_all; +mod get_all; +mod insert; + +pub use delete_all::delete_all_accounts; +pub use get_all::get_all_accounts; +pub use insert::insert_accounts; diff --git a/backend/core/src/queries/budget_updates/delete_all.rs b/backend/core/src/queries/budget_updates/delete_all.rs new file mode 100644 index 0000000..b7ad082 --- /dev/null +++ b/backend/core/src/queries/budget_updates/delete_all.rs @@ -0,0 +1,11 @@ +use anyhow::{Context, Result}; +use diesel::{RunQueryDsl, SqliteConnection}; + +use crate::schema::budget_updates::dsl::budget_updates; + +pub fn delete_all_budget_updates(connection: &mut SqliteConnection) -> Result<usize> { + let num_rows_deleted = diesel::delete(budget_updates) + .execute(connection) + .with_context(|| "failed to delete all budget updates")?; + Ok(num_rows_deleted) +} diff --git a/backend/core/src/queries/budget_updates/get_all.rs b/backend/core/src/queries/budget_updates/get_all.rs new file mode 100644 index 0000000..9c9310b --- /dev/null +++ b/backend/core/src/queries/budget_updates/get_all.rs @@ -0,0 +1,16 @@ +use anyhow::{Context, Result}; +use crate::{ + models::budget_update::BudgetUpdate, + schema::budget_updates::dsl::budget_updates as budget_updates_table, +}; +use diesel::{QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; + +pub fn get_all_budget_updates( + connection: &mut SqliteConnection, +) -> Result<Vec<BudgetUpdate>> { + let all_budget_updates = budget_updates_table + .select(BudgetUpdate::as_select()) + .load::<BudgetUpdate>(connection) + .with_context(|| "failed to get all budget updates")?; + Ok(all_budget_updates) +} diff --git a/backend/app/src/queries/budget_updates/get_first_with_date.rs b/backend/core/src/queries/budget_updates/get_first_with_date.rs index 3856557..1df3ae2 100644 --- a/backend/app/src/queries/budget_updates/get_first_with_date.rs +++ b/backend/core/src/queries/budget_updates/get_first_with_date.rs @@ -1,19 +1,22 @@ -use budgeting_app_core::{ +use anyhow::{Context, Result}; +use crate::{ models::budget_update::BudgetUpdate, schema::budget_updates::{ self as budget_updates_schema, dsl::budget_updates as budget_updates_table, }, }; use diesel::{ - result::Error, ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection, + ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection, }; pub fn get_first_budget_update_with_date( date: &str, connection: &mut SqliteConnection, -) -> Result<BudgetUpdate, Error> { - budget_updates_table +) -> Result<BudgetUpdate> { + let first_budget_update = budget_updates_table .select(BudgetUpdate::as_select()) .filter(budget_updates_schema::date.eq(date)) .first(connection) + .with_context(|| format!("failed to get first budget update with date {}", date))?; + Ok(first_budget_update) } diff --git a/backend/app/src/queries/budget_updates/get_most_recent_with_category_id.rs b/backend/core/src/queries/budget_updates/get_most_recent_with_category_id.rs index 55012f5..4e13c50 100644 --- a/backend/app/src/queries/budget_updates/get_most_recent_with_category_id.rs +++ b/backend/core/src/queries/budget_updates/get_most_recent_with_category_id.rs @@ -1,18 +1,21 @@ -use budgeting_app_core::{ +use anyhow::{Context, Result}; +use crate::{ models::date_time_utc::DateTimeUtc, schema::budget_updates::{ self as budget_updates_schema, dsl::budget_updates as budget_updates_table, }, }; -use diesel::{dsl::max, result::Error, ExpressionMethods, QueryDsl, RunQueryDsl, SqliteConnection}; +use diesel::{dsl::max, ExpressionMethods, QueryDsl, RunQueryDsl, SqliteConnection}; pub fn get_most_recent_budget_update_date_with_category_id( category_id: i32, connection: &mut SqliteConnection, -) -> Result<Option<String>, Error> { - budget_updates_table +) -> Result<Option<String>> { + let most_recent_date = budget_updates_table .select(max(budget_updates_schema::date)) .filter(budget_updates_schema::category_id.eq(category_id)) .filter(budget_updates_schema::date.le(DateTimeUtc::now().to_day_str())) .first(connection) + .with_context(|| format!("failed to get most recent budget update date with category ID {}", category_id))?; + Ok(most_recent_date) } diff --git a/backend/core/src/queries/budget_updates/insert.rs b/backend/core/src/queries/budget_updates/insert.rs new file mode 100644 index 0000000..e8ae271 --- /dev/null +++ b/backend/core/src/queries/budget_updates/insert.rs @@ -0,0 +1,28 @@ +use anyhow::{Context, Result}; +use crate::{ + models::budget_update::BudgetUpdate, + schema::budget_updates::dsl::budget_updates as budget_updates_table, +}; +use diesel::prelude::*; + +pub fn insert_budget_updates( + budget_updates: &[BudgetUpdate], + connection: &mut SqliteConnection, +) -> Result<usize> { + let num_rows_inserted = diesel::insert_into(budget_updates_table) + .values(budget_updates) + .execute(connection) + .with_context(|| err_msg(budget_updates))?; + Ok(num_rows_inserted) +} + +fn err_msg(budget_updates: &[BudgetUpdate]) -> String { + format!( + "failed to insert budget updates: [{}]", + budget_updates + .iter() + .map(|a| a.id.to_string()) + .collect::<Vec<String>>() + .join(", ") + ) +} diff --git a/backend/app/src/queries/budget_updates/mod.rs b/backend/core/src/queries/budget_updates/mod.rs index 3b514dd..337197e 100644 --- a/backend/app/src/queries/budget_updates/mod.rs +++ b/backend/core/src/queries/budget_updates/mod.rs @@ -1,7 +1,11 @@ +mod delete_all; mod get_all; mod get_first_with_date; mod get_most_recent_with_category_id; +mod insert; +pub use delete_all::delete_all_budget_updates; pub use get_all::get_all_budget_updates; pub use get_first_with_date::get_first_budget_update_with_date; pub use get_most_recent_with_category_id::get_most_recent_budget_update_date_with_category_id; +pub use insert::insert_budget_updates; diff --git a/backend/core/src/queries/categories/delete_all.rs b/backend/core/src/queries/categories/delete_all.rs new file mode 100644 index 0000000..1fd2581 --- /dev/null +++ b/backend/core/src/queries/categories/delete_all.rs @@ -0,0 +1,11 @@ +use anyhow::{Context, Result}; +use diesel::{RunQueryDsl, SqliteConnection}; + +use crate::schema::categories::dsl::categories; + +pub fn delete_all_categories(connection: &mut SqliteConnection) -> Result<usize> { + let num_rows_deleted = diesel::delete(categories) + .execute(connection) + .with_context(|| "failed to delete all categories")?; + Ok(num_rows_deleted) +} diff --git a/backend/core/src/queries/categories/get_all.rs b/backend/core/src/queries/categories/get_all.rs new file mode 100644 index 0000000..431ed9c --- /dev/null +++ b/backend/core/src/queries/categories/get_all.rs @@ -0,0 +1,11 @@ +use anyhow::{Context, Result}; +use crate::{models::category::Category, schema::categories::dsl::categories as categories_table}; +use diesel::{QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; + +pub fn get_all_categories(connection: &mut SqliteConnection) -> Result<Vec<Category>> { + let all_categories = categories_table + .select(Category::as_select()) + .load(connection) + .with_context(|| "failed to get all categories")?; + Ok(all_categories) +} diff --git a/backend/app/src/queries/category_balances/get_all.rs b/backend/core/src/queries/categories/get_all_balances.rs index 7ee474a..da4b0cd 100644 --- a/backend/app/src/queries/category_balances/get_all.rs +++ b/backend/core/src/queries/categories/get_all_balances.rs @@ -1,6 +1,5 @@ -use budgeting_app_core::models::category_balance::CategoryBalance; -use crate::{ - queries::{ +use anyhow::{Context, Result}; +use crate::{models::category_balance::CategoryBalance, queries::{ budget_updates::get_all_budget_updates, categories::get_all_categories, category_transfers::{ @@ -8,14 +7,19 @@ use crate::{ sum_category_transfer_quantity_per_to_category_id, }, transaction_categorisations::sum_transaction_categorisation_quantity_per_category_id, - }, - utils::calculate_category_balance, -}; -use diesel::{result::Error, SqliteConnection}; + }, utils::calculate_category_balance}; +use diesel::SqliteConnection; pub fn get_all_category_balances( connection: &mut SqliteConnection, -) -> Result<Vec<CategoryBalance>, Error> { +) -> Result<Vec<CategoryBalance>> { + get_all_category_balances_without_context(connection) + .with_context(|| "failed to get all category balances") +} + +fn get_all_category_balances_without_context( + connection: &mut SqliteConnection, +) -> Result<Vec<CategoryBalance>> { let categories = get_all_categories(connection)?; let transaction_sums = sum_transaction_categorisation_quantity_per_category_id(connection)?; let category_transfer_from_sums = diff --git a/backend/core/src/queries/categories/get_all_ids.rs b/backend/core/src/queries/categories/get_all_ids.rs new file mode 100644 index 0000000..8505e71 --- /dev/null +++ b/backend/core/src/queries/categories/get_all_ids.rs @@ -0,0 +1,11 @@ +use anyhow::{Context, Result}; +use crate::schema::categories::{self as categories_schema, dsl::categories as categories_table}; +use diesel::{QueryDsl, RunQueryDsl, SqliteConnection}; + +pub fn get_all_category_ids(connection: &mut SqliteConnection) -> Result<Vec<i32>> { + let all_category_ids = categories_table + .select(categories_schema::id) + .load(connection) + .with_context(|| "failed to get all category IDs")?; + Ok(all_category_ids) +} diff --git a/backend/app/src/queries/category_budgets/get_most_recent_per_category_id.rs b/backend/core/src/queries/categories/get_most_recent_budget_per_category_id.rs index b71a5fb..b8f1a76 100644 --- a/backend/app/src/queries/category_budgets/get_most_recent_per_category_id.rs +++ b/backend/core/src/queries/categories/get_most_recent_budget_per_category_id.rs @@ -1,15 +1,22 @@ -use budgeting_app_core::models::{budget::Budget, category_budget::CategoryBudget}; -use crate::queries::{ +use anyhow::{Context, Result}; +use crate::{models::{budget::Budget, category_budget::CategoryBudget}, queries::{ budget_updates::{ get_first_budget_update_with_date, get_most_recent_budget_update_date_with_category_id, }, categories::get_all_category_ids, -}; -use diesel::{result::Error, SqliteConnection}; +}}; +use diesel::SqliteConnection; pub fn get_most_recent_category_budget_per_category_id( connection: &mut SqliteConnection, -) -> Result<Vec<CategoryBudget>, Error> { +) -> Result<Vec<CategoryBudget>> { + get_most_recent_category_budget_per_category_id_without_context(connection) + .with_context(|| "failed to get most recent category budget per category ID") +} + +fn get_most_recent_category_budget_per_category_id_without_context( + connection: &mut SqliteConnection, +) -> Result<Vec<CategoryBudget>> { let category_ids: Vec<i32> = get_all_category_ids(connection)?; let mut budgets = Vec::<CategoryBudget>::new(); diff --git a/backend/core/src/queries/categories/insert.rs b/backend/core/src/queries/categories/insert.rs new file mode 100644 index 0000000..bccba72 --- /dev/null +++ b/backend/core/src/queries/categories/insert.rs @@ -0,0 +1,28 @@ +use anyhow::{Context, Result}; +use crate::{ + models::category::Category, + schema::categories::dsl::categories as categories_table, +}; +use diesel::prelude::*; + +pub fn insert_categories( + categories: &[Category], + connection: &mut SqliteConnection, +) -> Result<usize> { + let num_categories_inserted = diesel::insert_into(categories_table) + .values(categories) + .execute(connection) + .with_context(|| err_msg(categories))?; + Ok(num_categories_inserted) +} + +fn err_msg(categories: &[Category]) -> String { + format!( + "failed to insert categories: [{}]", + categories + .iter() + .map(|a| format!("\"{}\": \"{}\"", a.id, a.name)) + .collect::<Vec<String>>() + .join(", ") + ) +} diff --git a/backend/core/src/queries/categories/mod.rs b/backend/core/src/queries/categories/mod.rs new file mode 100644 index 0000000..edeeebf --- /dev/null +++ b/backend/core/src/queries/categories/mod.rs @@ -0,0 +1,13 @@ +mod delete_all; +mod get_all; +mod get_all_balances; +mod get_all_ids; +mod get_most_recent_budget_per_category_id; +mod insert; + +pub use delete_all::delete_all_categories; +pub use get_all::get_all_categories; +pub use get_all_balances::get_all_category_balances; +pub use get_all_ids::get_all_category_ids; +pub use get_most_recent_budget_per_category_id::get_most_recent_category_budget_per_category_id; +pub use insert::insert_categories; diff --git a/backend/core/src/queries/category_transfers/delete_all.rs b/backend/core/src/queries/category_transfers/delete_all.rs new file mode 100644 index 0000000..406b80b --- /dev/null +++ b/backend/core/src/queries/category_transfers/delete_all.rs @@ -0,0 +1,7 @@ +use diesel::{result::Error, RunQueryDsl, SqliteConnection}; + +use crate::schema::category_transfers::dsl::category_transfers; + +pub fn delete_all_category_transfers(connection: &mut SqliteConnection) -> Result<usize, Error> { + diesel::delete(category_transfers).execute(connection) +} diff --git a/backend/core/src/queries/category_transfers/insert.rs b/backend/core/src/queries/category_transfers/insert.rs new file mode 100644 index 0000000..e1db167 --- /dev/null +++ b/backend/core/src/queries/category_transfers/insert.rs @@ -0,0 +1,15 @@ +use crate::{ + models::category_transfer::CategoryTransfer, + schema::category_transfers::dsl::category_transfers as category_transfers_table, +}; +use diesel::prelude::*; +use diesel::result::Error; + +pub fn insert_category_transfers( + category_transfers: &[CategoryTransfer], + connection: &mut SqliteConnection, +) -> Result<usize, Error> { + diesel::insert_into(category_transfers_table) + .values(category_transfers) + .execute(connection) +} diff --git a/backend/app/src/queries/category_transfers/mod.rs b/backend/core/src/queries/category_transfers/mod.rs index 85eb1c8..68f32f4 100644 --- a/backend/app/src/queries/category_transfers/mod.rs +++ b/backend/core/src/queries/category_transfers/mod.rs @@ -1,5 +1,9 @@ +mod delete_all; +mod insert; mod sum_quantity_per_from_category_id; mod sum_quantity_per_to_category_id; +pub use delete_all::delete_all_category_transfers; +pub use insert::insert_category_transfers; pub use sum_quantity_per_from_category_id::sum_category_transfer_quantity_per_from_category_id; pub use sum_quantity_per_to_category_id::sum_category_transfer_quantity_per_to_category_id; diff --git a/backend/app/src/queries/category_transfers/sum_quantity_per_from_category_id.rs b/backend/core/src/queries/category_transfers/sum_quantity_per_from_category_id.rs index 3c077aa..cd4137d 100644 --- a/backend/app/src/queries/category_transfers/sum_quantity_per_from_category_id.rs +++ b/backend/core/src/queries/category_transfers/sum_quantity_per_from_category_id.rs @@ -1,4 +1,4 @@ -use budgeting_app_core::schema::category_transfers::{ +use crate::schema::category_transfers::{ self as category_transfers_schema, dsl::category_transfers as category_transfers_table, }; diff --git a/backend/app/src/queries/category_transfers/sum_quantity_per_to_category_id.rs b/backend/core/src/queries/category_transfers/sum_quantity_per_to_category_id.rs index 6e81d7d..f5fb561 100644 --- a/backend/app/src/queries/category_transfers/sum_quantity_per_to_category_id.rs +++ b/backend/core/src/queries/category_transfers/sum_quantity_per_to_category_id.rs @@ -1,4 +1,4 @@ -use budgeting_app_core::schema::category_transfers::{ +use crate::schema::category_transfers::{ self as category_transfers_schema, dsl::category_transfers as category_transfers_table, }; diff --git a/backend/core/src/queries/clear.rs b/backend/core/src/queries/clear.rs new file mode 100644 index 0000000..dbe0f84 --- /dev/null +++ b/backend/core/src/queries/clear.rs @@ -0,0 +1,19 @@ +use crate::queries::{ + accounts::delete_all_accounts, + budget_updates::delete_all_budget_updates, + categories::delete_all_categories, + category_transfers::delete_all_category_transfers, + transactions::delete_all_transactions, + transaction_categorisations::delete_all_transaction_categorisations, +}; +use diesel::{result::Error, SqliteConnection}; + +pub fn clear(connection: &mut SqliteConnection) -> Result<(), Error> { + delete_all_accounts(connection)?; + delete_all_budget_updates(connection)?; + delete_all_categories(connection)?; + delete_all_category_transfers(connection)?; + delete_all_transactions(connection)?; + delete_all_transaction_categorisations(connection)?; + Ok(()) +} diff --git a/backend/app/src/queries/mod.rs b/backend/core/src/queries/mod.rs index 43328e2..f0852ce 100644 --- a/backend/app/src/queries/mod.rs +++ b/backend/core/src/queries/mod.rs @@ -1,7 +1,7 @@ pub mod accounts; pub mod budget_updates; pub mod categories; -pub mod category_balances; -pub mod category_budgets; pub mod category_transfers; +pub mod clear; +pub mod transactions; pub mod transaction_categorisations; diff --git a/backend/core/src/queries/transaction_categorisations/delete_all.rs b/backend/core/src/queries/transaction_categorisations/delete_all.rs new file mode 100644 index 0000000..e80a0ca --- /dev/null +++ b/backend/core/src/queries/transaction_categorisations/delete_all.rs @@ -0,0 +1,7 @@ +use diesel::{result::Error, RunQueryDsl, SqliteConnection}; + +use crate::schema::transaction_categorisations::dsl::transaction_categorisations; + +pub fn delete_all_transaction_categorisations(connection: &mut SqliteConnection) -> Result<usize, Error> { + diesel::delete(transaction_categorisations).execute(connection) +} diff --git a/backend/core/src/queries/transaction_categorisations/insert.rs b/backend/core/src/queries/transaction_categorisations/insert.rs new file mode 100644 index 0000000..3da1c33 --- /dev/null +++ b/backend/core/src/queries/transaction_categorisations/insert.rs @@ -0,0 +1,15 @@ +use crate::{ + models::transaction_categorisation::TransactionCategorisation, + schema::transaction_categorisations::dsl::transaction_categorisations as transaction_categorisations_table, +}; +use diesel::prelude::*; +use diesel::result::Error; + +pub fn insert_transaction_categorisations( + transaction_categorisations: &[TransactionCategorisation], + connection: &mut SqliteConnection, +) -> Result<usize, Error> { + diesel::insert_into(transaction_categorisations_table) + .values(transaction_categorisations) + .execute(connection) +} diff --git a/backend/core/src/queries/transaction_categorisations/mod.rs b/backend/core/src/queries/transaction_categorisations/mod.rs new file mode 100644 index 0000000..3664fa6 --- /dev/null +++ b/backend/core/src/queries/transaction_categorisations/mod.rs @@ -0,0 +1,7 @@ +mod delete_all; +mod insert; +mod sum_quantity_per_category_id; + +pub use delete_all::delete_all_transaction_categorisations; +pub use insert::insert_transaction_categorisations; +pub use sum_quantity_per_category_id::sum_transaction_categorisation_quantity_per_category_id; diff --git a/backend/app/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs b/backend/core/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs index 90dc990..7f93e0c 100644 --- a/backend/app/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs +++ b/backend/core/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs @@ -1,4 +1,4 @@ -use budgeting_app_core::schema::transaction_categorisations::{ +use crate::schema::transaction_categorisations::{ self as transaction_categorisations_schema, dsl::transaction_categorisations as transaction_categorisations_table, }; diff --git a/backend/core/src/queries/transactions/delete_all.rs b/backend/core/src/queries/transactions/delete_all.rs new file mode 100644 index 0000000..43b7976 --- /dev/null +++ b/backend/core/src/queries/transactions/delete_all.rs @@ -0,0 +1,7 @@ +use diesel::{result::Error, RunQueryDsl, SqliteConnection}; + +use crate::schema::transactions::dsl::transactions; + +pub fn delete_all_transactions(connection: &mut SqliteConnection) -> Result<usize, Error> { + diesel::delete(transactions).execute(connection) +} diff --git a/backend/core/src/queries/transactions/insert.rs b/backend/core/src/queries/transactions/insert.rs new file mode 100644 index 0000000..32f3dc2 --- /dev/null +++ b/backend/core/src/queries/transactions/insert.rs @@ -0,0 +1,15 @@ +use crate::{ + models::transaction::Transaction, + schema::transactions::dsl::transactions as transactions_table, +}; +use diesel::prelude::*; +use diesel::result::Error; + +pub fn insert_transactions( + transactions: &[Transaction], + connection: &mut SqliteConnection, +) -> Result<usize, Error> { + diesel::insert_into(transactions_table) + .values(transactions) + .execute(connection) +} diff --git a/backend/core/src/queries/transactions/mod.rs b/backend/core/src/queries/transactions/mod.rs new file mode 100644 index 0000000..40855f3 --- /dev/null +++ b/backend/core/src/queries/transactions/mod.rs @@ -0,0 +1,5 @@ +mod delete_all; +mod insert; + +pub use delete_all::delete_all_transactions; +pub use insert::insert_transactions; diff --git a/backend/app/src/utils/calculate_budgets_accrual.rs b/backend/core/src/utils/calculate_budgets_accrual.rs index c0d3d3a..bbc197c 100644 --- a/backend/app/src/utils/calculate_budgets_accrual.rs +++ b/backend/core/src/utils/calculate_budgets_accrual.rs @@ -1,4 +1,4 @@ -use budgeting_app_core::models::{ +use crate::models::{ budget_update::BudgetUpdate, category::Category, date_time_utc::DateTimeUtc @@ -30,12 +30,12 @@ pub fn calculate_budgets_accrual(category: &Category, budget_updates: &[BudgetUp #[cfg(test)] mod test { - use budgeting_app_core::models::{ - budget_update::BudgetUpdate, category::Category, date_time_utc::DateTimeUtc, - }; use chrono::{Days, Timelike, Utc}; - use super::calculate_budgets_accrual; + use crate::{ + models::{budget_update::BudgetUpdate, category::Category, date_time_utc::DateTimeUtc}, + utils::calculate_budgets_accrual, + }; #[test] fn when_no_budget_updates_then_return_zero() { diff --git a/backend/app/src/utils/calculate_category_balance.rs b/backend/core/src/utils/calculate_category_balance.rs index e1de57e..5eca05b 100644 --- a/backend/app/src/utils/calculate_category_balance.rs +++ b/backend/core/src/utils/calculate_category_balance.rs @@ -1,10 +1,8 @@ -use budgeting_app_core::models::{ - budget_update::BudgetUpdate, - category::Category +use crate::{ + models::{budget_update::BudgetUpdate, category::Category}, + utils::{calculate_budgets_accrual, find_by_id_or}, }; -use super::{calculate_budgets_accrual, find_by_id_or}; - pub fn calculate_category_balance( category: &Category, transaction_sums: &[(i32, Option<i64>)], @@ -22,10 +20,12 @@ pub fn calculate_category_balance( #[cfg(test)] mod test { - use budgeting_app_core::models::{budget_update::BudgetUpdate, category::Category, date_time_utc::DateTimeUtc}; use chrono::{Days, Utc}; - use crate::utils::calculate_category_balance; + use crate::{ + models::{budget_update::BudgetUpdate, category::Category, date_time_utc::DateTimeUtc}, + utils::calculate_category_balance + }; #[test] fn when_no_data_then_returns_zero() { diff --git a/backend/app/src/utils/find_by_id_or.rs b/backend/core/src/utils/find_by_id_or.rs index f5add30..f5add30 100644 --- a/backend/app/src/utils/find_by_id_or.rs +++ b/backend/core/src/utils/find_by_id_or.rs diff --git a/backend/app/src/utils/mod.rs b/backend/core/src/utils/mod.rs index 84a27af..84a27af 100644 --- a/backend/app/src/utils/mod.rs +++ b/backend/core/src/utils/mod.rs diff --git a/backend/importers/actualbudget/Cargo.lock b/backend/importers/actualbudget/Cargo.lock index 63a53b9..1a461d5 100644 --- a/backend/importers/actualbudget/Cargo.lock +++ b/backend/importers/actualbudget/Cargo.lock @@ -6,8 +6,11 @@ version = 3 name = "actualbudget" version = "0.1.0" dependencies = [ + "anyhow", + "budgeting_app_core", "chrono", - "sqlite", + "diesel", + "libsqlite3-sys", ] [[package]] @@ -26,12 +29,29 @@ dependencies = [ ] [[package]] +name = "anyhow" +version = "1.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37bf3594c4c988a53154954629820791dde498571819ae4ca50ca811e060cc95" + +[[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] +name = "budgeting_app_core" +version = "0.1.0" +dependencies = [ + "anyhow", + "chrono", + "diesel", + "diesel_migrations", + "serde", +] + +[[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -74,6 +94,138 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + +[[package]] +name = "deranged" +version = "0.3.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" +dependencies = [ + "powerfmt", +] + +[[package]] +name = "diesel" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158fe8e2e68695bd615d7e4f3227c0727b151330d3e253b525086c348d055d5e" +dependencies = [ + "diesel_derives", + "libsqlite3-sys", + "time", +] + +[[package]] +name = "diesel_derives" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" +dependencies = [ + "diesel_table_macro_syntax", + "dsl_auto_type", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diesel_migrations" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" +dependencies = [ + "diesel", + "migrations_internals", + "migrations_macros", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +dependencies = [ + "syn", +] + +[[package]] +name = "dsl_auto_type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" +dependencies = [ + "darling", + "either", + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + +[[package]] +name = "hashbrown" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" + +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] name = "iana-time-zone" version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -97,6 +249,28 @@ dependencies = [ ] [[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "indexmap" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "itoa" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" + +[[package]] name = "js-sys" version = "0.3.70" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -112,12 +286,56 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439" [[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "cc", + "pkg-config", + "vcpkg", +] + +[[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "migrations_internals" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "migrations_macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" +dependencies = [ + "migrations_internals", + "proc-macro2", + "quote", +] + +[[package]] +name = "num-conv" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" + +[[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -139,6 +357,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] +name = "powerfmt" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" + +[[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -177,48 +401,100 @@ dependencies = [ ] [[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + +[[package]] name = "shlex" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" [[package]] -name = "sqlite" -version = "0.36.1" +name = "strsim" +version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfe6fb16f2bee6452feeb4d12bfa404fbcd3cfc121b2950e501d1ae9cae718e" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.77" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" dependencies = [ - "sqlite3-sys", + "proc-macro2", + "quote", + "unicode-ident", ] [[package]] -name = "sqlite3-src" -version = "0.6.1" +name = "time" +version = "0.3.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "174d4a6df77c27db281fb23de1a6d968f3aaaa4807c2a1afa8056b971f947b4a" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" dependencies = [ - "cc", - "pkg-config", + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", ] [[package]] -name = "sqlite3-sys" -version = "0.17.0" +name = "time-core" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" + +[[package]] +name = "time-macros" +version = "0.2.18" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3901ada7090c3c3584dc92ec7ef1b7091868d13bfe6d7de9f0bcaffee7d0ade5" +checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" dependencies = [ - "sqlite3-src", + "num-conv", + "time-core", ] [[package]] -name = "syn" -version = "2.0.77" +name = "toml" +version = "0.8.19" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", ] [[package]] @@ -228,6 +504,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" [[package]] +name = "vcpkg" +version = "0.2.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" + +[[package]] name = "wasm-bindgen" version = "0.2.93" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -354,3 +636,12 @@ name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] diff --git a/backend/importers/actualbudget/Cargo.toml b/backend/importers/actualbudget/Cargo.toml index 7cc3e1b..6453e0a 100644 --- a/backend/importers/actualbudget/Cargo.toml +++ b/backend/importers/actualbudget/Cargo.toml @@ -4,5 +4,8 @@ version = "0.1.0" edition = "2021" [dependencies] -sqlite = "0.36.1" +anyhow = { version = "1.0.0" } +budgeting_app_core = { path = "../../core" } chrono = { version = "0.4.38", features = ["serde"] } +diesel = { version = "2.2.0", features = ["sqlite"] } +libsqlite3-sys = { version = "0.30.1", features = ["bundled"] } diff --git a/backend/importers/actualbudget/src/getters.rs b/backend/importers/actualbudget/src/getters.rs deleted file mode 100644 index 88a660d..0000000 --- a/backend/importers/actualbudget/src/getters.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::models::actualbudget::{ - account::Account, category::Category, date::Date, transaction::Transaction, - zero_budget::ZeroBudget, -}; -use sqlite::Connection; - -pub fn get_first_transaction_date(connection: &Connection) -> Date { - let mut statement = connection - .prepare("select min(date) as min_date from transactions;") - .unwrap(); - match statement.next() { - Ok(sqlite::State::Row) => Date::from_date_row(&statement, "min_date"), - err => panic!("Could not find first transaction date: {:?}", err), - } -} - -pub fn get_accounts(connection: &Connection) -> Vec<Account> { - let mut statement = connection.prepare("select * from accounts;").unwrap(); - let mut accounts = Vec::<Account>::new(); - while let Ok(sqlite::State::Row) = statement.next() { - accounts.push(Account::from_row(&statement)); - } - accounts -} - -pub fn get_categories(connection: &Connection) -> Vec<Category> { - let mut statement = connection.prepare("select * from categories;").unwrap(); - let mut categories = Vec::<Category>::new(); - while let Ok(sqlite::State::Row) = statement.next() { - categories.push(Category::from_row(&statement)); - } - categories -} - -pub fn get_zero_budgets(connection: &Connection) -> Vec<ZeroBudget> { - let mut statement = connection.prepare("select * from zero_budgets;").unwrap(); - let mut zero_budgets = Vec::<ZeroBudget>::new(); - while let Ok(sqlite::State::Row) = statement.next() { - zero_budgets.push(ZeroBudget::from_row(&statement)); - } - zero_budgets -} - -pub fn get_category_transfers_account(connection: &Connection) -> Account { - let mut statement = connection - .prepare("select * from accounts where name = 'Category transfers';") - .unwrap(); - match statement.next() { - Ok(sqlite::State::Row) => Account::from_row(&statement), - _ => panic!("Could not determine category transfers account"), - } -} - -pub fn get_transactions(connection: &Connection) -> Vec<Transaction> { - let mut statement = connection.prepare("select * from transactions;").unwrap(); - let mut transactions = Vec::<Transaction>::new(); - while let Ok(sqlite::State::Row) = statement.next() { - transactions.push(Transaction::from_row(&statement)); - } - transactions -} diff --git a/backend/importers/actualbudget/src/inserters.rs b/backend/importers/actualbudget/src/inserters.rs deleted file mode 100644 index 179de97..0000000 --- a/backend/importers/actualbudget/src/inserters.rs +++ /dev/null @@ -1,120 +0,0 @@ -use crate::models::budgeting_app::{ - account::Account, budget_update::BudgetUpdate, category::Category, - category_transfer::CategoryTransfer, transaction::Transaction, - transaction_categorisation::TransactionCategorisation, -}; -use sqlite::Connection; - -pub fn insert_accounts(connection: &Connection, accounts: &[Account]) { - for account in accounts { - let str = format!( - " - insert into - accounts(id, name, opening_balance, opening_date) - values({0}, '{1}', {2}, '{3}'); - ", - account.id, - escape_str(&account.name), - account.opening_balance, - account.opening_date - ); - connection.execute(str).unwrap(); - } -} - -pub fn insert_categories(connection: &Connection, categories: &[Category]) { - for category in categories { - let str = format!( - " - insert into - categories(id, name) - values({0}, '{1}'); - ", - category.id, - escape_str(&category.name), - ); - connection.execute(str).unwrap(); - } -} - -pub fn insert_budget_updates(connection: &Connection, budget_updates: &[BudgetUpdate]) { - for budget_update in budget_updates { - let str = format!( - " - insert into - budget_updates(id, category_id, date, new_budget, new_period) - values({0}, {1}, '{2}', {3}, {4}); - ", - budget_update.id, - budget_update.category_id, - budget_update.date, - budget_update.new_budget, - budget_update.new_period, - ); - connection.execute(str).unwrap(); - } -} - -pub fn insert_transactions(connection: &Connection, transactions: &[Transaction]) { - for transaction in transactions { - let str = format!( - " - insert into - transactions(id, description, payee, quantity, date, account_id) - values({0}, '{1}', '{2}', {3}, '{4}', {5}); - ", - transaction.id, - escape_str(&transaction.description), - escape_str(&transaction.payee), - transaction.quantity, - transaction.date, - transaction.account_id, - ); - connection - .execute(str.clone()) - .unwrap_or_else(|_| panic!("{}", &str.clone())); - } -} - -pub fn insert_transaction_categorisations( - connection: &Connection, - transaction_categorisations: &[TransactionCategorisation], -) { - for transaction_categorisation in transaction_categorisations { - let str = format!( - " - insert into - transaction_categorisations(id, description, quantity, transaction_id, category_id) - values({0}, '{1}', {2}, {3}, {4}); - ", - transaction_categorisation.id, - escape_str(&transaction_categorisation.description), - transaction_categorisation.quantity, - transaction_categorisation.transaction_id, - transaction_categorisation.category_id, - ); - connection.execute(str).unwrap(); - } -} - -pub fn insert_category_transfers(connection: &Connection, category_transfers: &[CategoryTransfer]) { - for category_transfer in category_transfers { - let str = format!( - " - insert into - category_transfers(id, description, quantity, from_category_id, to_category_id) - values({0}, '{1}', {2}, {3}, {4}); - ", - category_transfer.id, - escape_str(&category_transfer.description), - category_transfer.quantity, - category_transfer.from_category_id, - category_transfer.to_category_id, - ); - connection.execute(str).unwrap(); - } -} - -fn escape_str(str: &str) -> String { - str.replace("'", "''") -} diff --git a/backend/importers/actualbudget/src/main.rs b/backend/importers/actualbudget/src/main.rs index 495216e..84f5102 100644 --- a/backend/importers/actualbudget/src/main.rs +++ b/backend/importers/actualbudget/src/main.rs @@ -1,57 +1,45 @@ -pub mod getters; -pub mod inserters; pub mod models; +pub mod queries; +pub mod schema; pub mod transformers; -use getters::*; -use inserters::*; -use sqlite::Connection; +use anyhow::Result; +use budgeting_app_core::queries as budgeting_app_queries; +use diesel::{Connection, SqliteConnection}; +use queries::{actualbudget_accounts::{get_all_actualbudget_accounts, get_category_transfers_account}, actualbudget_categories::get_all_actualbudget_categories, actualbudget_transactions::{get_all_actualbudget_transactions, get_first_actualbudget_transaction_date}, actualbudget_zero_budgets::get_all_actualbudget_zero_budgets}; -fn main() { - let connection_src = sqlite::open("in.sqlite").unwrap(); - let connection_dest = sqlite::open("out.sqlite").unwrap(); +fn main() -> Result<()> { + let connection_src = &mut SqliteConnection::establish("in.sqlite").unwrap(); + let connection_dest = &mut SqliteConnection::establish("out.sqlite").unwrap(); - let actualbudget_first_transaction_date = get_first_transaction_date(&connection_src); - let actualbudget_accounts = get_accounts(&connection_src); - let actualbudget_categories = get_categories(&connection_src); - let actualbudget_zero_budgets = get_zero_budgets(&connection_src); - let actualbudget_category_transfers_account = get_category_transfers_account(&connection_src); - let actualbudget_transactions = get_transactions(&connection_src); + let actualbudget_first_transaction_date = get_first_actualbudget_transaction_date(connection_src).map_err(|e| e.to_string())?; + let actualbudget_accounts = get_all_actualbudget_accounts(connection_src).map_err(|e| e.to_string())?; + let actualbudget_categories = get_all_actualbudget_categories(connection_src).map_err(|e| e.to_string())?; + let actualbudget_zero_budgets = get_all_actualbudget_zero_budgets(connection_src).map_err(|e| e.to_string())?; + let actualbudget_category_transfers_account = get_category_transfers_account(connection_src).map_err(|e| e.to_string())?; + let actualbudget_transactions = get_all_actualbudget_transactions(connection_src).map_err(|e| e.to_string())?; - let first_transaction_date = transformers::transform_date(&actualbudget_first_transaction_date); + let first_transaction_date = transformers::transform_date(&actualbudget_first_transaction_date)?; let accounts = transformers::transform_accounts(&actualbudget_accounts, &first_transaction_date); let categories = transformers::transform_categories(&actualbudget_categories); - let budget_updates = transformers::transform_budget_updates(&actualbudget_zero_budgets); + let budget_updates = transformers::transform_budget_updates(&actualbudget_zero_budgets)?; let (transactions, transaction_categorisations) = transformers::transform_transactions( &actualbudget_transactions, &actualbudget_category_transfers_account, - ); + )?; let category_transfers = transformers::transform_category_transfers( &actualbudget_transactions, &actualbudget_category_transfers_account, ); - clear_destination(&connection_dest); - insert_accounts(&connection_dest, &accounts); - insert_categories(&connection_dest, &categories); - insert_budget_updates(&connection_dest, &budget_updates); - insert_transactions(&connection_dest, &transactions); - insert_transaction_categorisations(&connection_dest, &transaction_categorisations); - insert_category_transfers(&connection_dest, &category_transfers); -} + budgeting_app_queries::clear::clear(connection_dest).map_err(|e| e.to_string())?; + budgeting_app_queries::accounts::insert_accounts(&accounts, connection_dest).map_err(|e| e.to_string())?; + budgeting_app_queries::categories::insert_categories(&categories, connection_dest).map_err(|e| e.to_string())?; + budgeting_app_queries::budget_updates::insert_budget_updates(&budget_updates, connection_dest).map_err(|e| e.to_string())?; + budgeting_app_queries::transactions::insert_transactions(&transactions, connection_dest).map_err(|e| e.to_string())?; + budgeting_app_queries::transaction_categorisations::insert_transaction_categorisations(&transaction_categorisations, connection_dest).map_err(|e| e.to_string())?; + budgeting_app_queries::category_transfers::insert_category_transfers(&category_transfers, connection_dest).map_err(|e| e.to_string())?; -fn clear_destination(connection: &Connection) { - connection - .execute( - " - delete from accounts; - delete from budget_updates; - delete from categories; - delete from transaction_categorisations; - delete from category_transfers; - delete from transactions; - ", - ) - .unwrap(); + Ok(()) } diff --git a/backend/importers/actualbudget/src/models/actualbudget/account.rs b/backend/importers/actualbudget/src/models/actualbudget/account.rs deleted file mode 100644 index 727c082..0000000 --- a/backend/importers/actualbudget/src/models/actualbudget/account.rs +++ /dev/null @@ -1,15 +0,0 @@ -use sqlite::Statement; - -pub struct Account { - pub id: String, - pub name: String, -} - -impl Account { - pub fn from_row(statement: &Statement) -> Account { - Account { - id: statement.read("id").unwrap(), - name: statement.read("name").unwrap(), - } - } -} diff --git a/backend/importers/actualbudget/src/models/actualbudget/category.rs b/backend/importers/actualbudget/src/models/actualbudget/category.rs deleted file mode 100644 index 21fdf80..0000000 --- a/backend/importers/actualbudget/src/models/actualbudget/category.rs +++ /dev/null @@ -1,15 +0,0 @@ -use sqlite::Statement; - -pub struct Category { - pub id: String, - pub name: String, -} - -impl Category { - pub fn from_row(statement: &Statement) -> Category { - Category { - id: statement.read("id").unwrap(), - name: statement.read("name").unwrap(), - } - } -} diff --git a/backend/importers/actualbudget/src/models/actualbudget/date.rs b/backend/importers/actualbudget/src/models/actualbudget/date.rs deleted file mode 100644 index bd37c46..0000000 --- a/backend/importers/actualbudget/src/models/actualbudget/date.rs +++ /dev/null @@ -1,69 +0,0 @@ -use sqlite::Statement; - -#[derive(Clone, PartialEq, Eq)] -pub struct Date { - value: i64, - last_date_part: DatePart, -} - -#[derive(Clone, PartialOrd, PartialEq, Eq)] -enum DatePart { - MonthOfYear, - DayOfMonth, -} - -// actualbudget represents dates as numbers which "look like" the corresponding -// ISO string when written in decimal. E.g. 20240901 represents 1 Sep 2024. It -// also uses month specifiers, e.g. 202409 to represent Sep 2024. In this case, -// we interpret this as the first day of the month, e.g. 2024-09-01. - -impl Date { - pub fn from_date_row(statement: &Statement, column: &str) -> Date { - Date { - value: statement.read::<i64, _>(column).unwrap(), - last_date_part: DatePart::DayOfMonth, - } - } - - pub fn from_month_row(statement: &Statement, column: &str) -> Date { - Date { - value: statement.read::<i64, _>(column).unwrap() * 100 + 1, - last_date_part: DatePart::MonthOfYear, - } - } - - pub fn to_iso(&self) -> String { - format!("{0}-{1}-{2}", self.year(), self.month(), self.day()) - } - - pub fn year(&self) -> String { - self.value.to_string()[0..4].to_string() - } - - pub fn month(&self) -> String { - self.value.to_string()[4..6].to_string() - } - - pub fn day(&self) -> String { - self.dayified_value().to_string()[6..8].to_string() - } - - fn dayified_value(&self) -> i64 { - match self.last_date_part { - DatePart::DayOfMonth => self.value, - DatePart::MonthOfYear => self.value * 100 + 1, - } - } -} - -impl Ord for Date { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - self.dayified_value().cmp(&other.dayified_value()) - } -} - -impl PartialOrd for Date { - fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { - Some(self.cmp(other)) - } -} diff --git a/backend/importers/actualbudget/src/models/actualbudget/mod.rs b/backend/importers/actualbudget/src/models/actualbudget/mod.rs deleted file mode 100644 index 5ca472f..0000000 --- a/backend/importers/actualbudget/src/models/actualbudget/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -pub mod account; -pub mod category; -pub mod date; -pub mod transaction; -pub mod zero_budget; diff --git a/backend/importers/actualbudget/src/models/actualbudget/transaction.rs b/backend/importers/actualbudget/src/models/actualbudget/transaction.rs deleted file mode 100644 index 0a21cfe..0000000 --- a/backend/importers/actualbudget/src/models/actualbudget/transaction.rs +++ /dev/null @@ -1,43 +0,0 @@ -use sqlite::Statement; - -use super::date::Date; - -#[derive(Clone)] -pub struct Transaction { - pub id: String, - pub is_parent: bool, - pub is_child: bool, - pub account_id: String, - pub category_id: String, - pub amount: i64, - pub payee: String, - pub notes: String, - pub date: Date, - pub parent_id: Option<String>, -} - -impl Transaction { - pub fn from_row(statement: &Statement) -> Transaction { - Transaction { - id: statement.read("id").unwrap(), - is_parent: statement.read::<i64, &str>("isParent").unwrap() == 1, - is_child: statement.read::<i64, &str>("isChild").unwrap() == 1, - account_id: statement.read("acct").unwrap(), - category_id: statement - .read::<Option<String>, &str>("category") - .map(Option::<String>::unwrap_or_default) - .unwrap(), - amount: statement.read("amount").unwrap(), - payee: statement - .read::<Option<String>, &str>("description") - .map(Option::<String>::unwrap_or_default) - .unwrap(), - notes: statement - .read::<Option<String>, &str>("notes") - .map(Option::<String>::unwrap_or_default) - .unwrap(), - date: Date::from_date_row(statement, "date"), - parent_id: statement.read("parent_id").unwrap(), - } - } -} diff --git a/backend/importers/actualbudget/src/models/actualbudget/zero_budget.rs b/backend/importers/actualbudget/src/models/actualbudget/zero_budget.rs deleted file mode 100644 index 87c88e7..0000000 --- a/backend/importers/actualbudget/src/models/actualbudget/zero_budget.rs +++ /dev/null @@ -1,30 +0,0 @@ -use sqlite::Statement; - -use super::date::Date; - -#[derive(Clone)] -pub struct ZeroBudget { - pub id: String, - pub month: Date, - pub category_id: String, - pub amount: i64, - pub do_carry_over: bool, -} - -impl ZeroBudget { - pub fn from_row(statement: &Statement) -> ZeroBudget { - ZeroBudget { - id: statement.read("id").unwrap(), - month: Date::from_month_row(statement, "month"), - category_id: statement.read("category").unwrap(), - amount: statement.read("amount").unwrap(), - do_carry_over: match statement.read::<i64, &str>("carryover").unwrap() { - 0 => false, - 1 => true, - carryover => { - panic!("carryover column was neither 0 nor 1, but instead {carryover}") - } - }, - } - } -} diff --git a/backend/importers/actualbudget/src/models/actualbudget_account.rs b/backend/importers/actualbudget/src/models/actualbudget_account.rs new file mode 100644 index 0000000..b3d00ac --- /dev/null +++ b/backend/importers/actualbudget/src/models/actualbudget_account.rs @@ -0,0 +1,8 @@ +use diesel::prelude::{Queryable, Selectable}; + +#[derive(Queryable, Selectable)] +#[diesel(table_name = crate::schema::accounts)] +pub struct ActualbudgetAccount { + pub id: String, + pub name: String, +} diff --git a/backend/importers/actualbudget/src/models/actualbudget_category.rs b/backend/importers/actualbudget/src/models/actualbudget_category.rs new file mode 100644 index 0000000..cbea99b --- /dev/null +++ b/backend/importers/actualbudget/src/models/actualbudget_category.rs @@ -0,0 +1,8 @@ +use diesel::{prelude::Queryable, Selectable}; + +#[derive(Queryable, Selectable)] +#[diesel(table_name = crate::schema::categories)] +pub struct ActualbudgetCategory { + pub id: String, + pub name: String, +} diff --git a/backend/importers/actualbudget/src/models/actualbudget_date.rs b/backend/importers/actualbudget/src/models/actualbudget_date.rs new file mode 100644 index 0000000..bef160c --- /dev/null +++ b/backend/importers/actualbudget/src/models/actualbudget_date.rs @@ -0,0 +1,75 @@ +use diesel::{backend::Backend, deserialize::{FromSql, FromSqlRow}, expression::AsExpression, sql_types::Integer, sqlite::Sqlite}; + +#[derive(Clone, PartialEq, Eq, Debug, AsExpression, FromSqlRow)] +#[diesel(sql_type=Integer)] +pub struct ActualbudgetDate(i32); + +#[derive(Clone, PartialOrd, PartialEq, Eq)] +enum DatePart { + MonthOfYear, + DayOfMonth, +} + +// actualbudget represents dates as numbers which "look like" the corresponding +// ISO string when written in decimal. E.g. 20240901 represents 1 Sep 2024. It +// also uses month specifiers, e.g. 202409 to represent Sep 2024. In this case, +// we interpret this as the first day of the month, e.g. 2024-09-01. + +impl ActualbudgetDate { + pub fn to_iso(&self) -> String { + format!("{0}-{1}-{2}", self.year(), self.month(), self.day()) + } + + pub fn year(&self) -> String { + self.0.to_string()[0..4].to_string() + } + + pub fn month(&self) -> String { + self.0.to_string()[4..6].to_string() + } + + pub fn day(&self) -> String { + match self.date_part() { + DatePart::DayOfMonth => self.0.to_string()[6..8].to_string(), + DatePart::MonthOfYear => String::from("01"), + } + } + + pub fn from_i32(i: i32) -> Self { + ActualbudgetDate(i) + } + + fn date_part(&self) -> DatePart { + if self.0 < 10000000 { + DatePart::MonthOfYear + } else { + DatePart::DayOfMonth + } + } + + fn dayified_value(&self) -> i32 { + match self.date_part() { + DatePart::DayOfMonth => self.0, + DatePart::MonthOfYear => self.0 * 100 + 1, + } + } +} + +impl Ord for ActualbudgetDate { + fn cmp(&self, other: &Self) -> std::cmp::Ordering { + self.dayified_value().cmp(&other.dayified_value()) + } +} + +impl PartialOrd for ActualbudgetDate { + fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> { + Some(self.cmp(other)) + } +} + +impl FromSql<Integer, Sqlite> for ActualbudgetDate { + fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> diesel::deserialize::Result<Self> { + let int: i32 = FromSql::<Integer, Sqlite>::from_sql(bytes)?; + Ok(ActualbudgetDate(int)) + } +} diff --git a/backend/importers/actualbudget/src/models/actualbudget_transaction.rs b/backend/importers/actualbudget/src/models/actualbudget_transaction.rs new file mode 100644 index 0000000..be2ca1a --- /dev/null +++ b/backend/importers/actualbudget/src/models/actualbudget_transaction.rs @@ -0,0 +1,18 @@ +use diesel::prelude::{Identifiable, Queryable, QueryableByName, Selectable}; + +use crate::models::actualbudget_date::ActualbudgetDate; + +#[derive(Identifiable, QueryableByName, Queryable, Selectable, Clone)] +#[diesel(table_name = crate::schema::transactions)] +pub struct ActualbudgetTransaction { + pub id: String, + pub is_parent: bool, + pub is_child: bool, + pub account_id: String, + pub category_id: String, + pub amount: i32, + pub payee: String, + pub notes: String, + pub date: ActualbudgetDate, + pub parent_id: Option<String>, +} diff --git a/backend/importers/actualbudget/src/models/actualbudget_zero_budget.rs b/backend/importers/actualbudget/src/models/actualbudget_zero_budget.rs new file mode 100644 index 0000000..6478d34 --- /dev/null +++ b/backend/importers/actualbudget/src/models/actualbudget_zero_budget.rs @@ -0,0 +1,13 @@ +use crate::models::actualbudget_date::ActualbudgetDate; +use diesel::{prelude::{Identifiable, Queryable, QueryableByName, Selectable}, sqlite::Sqlite}; + +#[derive(Identifiable, QueryableByName, Queryable, Selectable, Clone)] +#[diesel(table_name = crate::schema::zero_budgets)] +#[diesel(check_for_backend(Sqlite))] +pub struct ActualbudgetZeroBudget { + pub id: String, + pub month: ActualbudgetDate, + pub category_id: String, + pub amount: i32, + pub do_carry_over: i32, +} diff --git a/backend/importers/actualbudget/src/models/budgeting_app/DateTimeUtc.rs b/backend/importers/actualbudget/src/models/budgeting_app/DateTimeUtc.rs deleted file mode 100644 index e44fa8c..0000000 --- a/backend/importers/actualbudget/src/models/budgeting_app/DateTimeUtc.rs +++ /dev/null @@ -1,57 +0,0 @@ -use chrono::{DateTime, Datelike, Utc}; -use diesel::{ - deserialize::{FromSql, FromSqlRow}, - expression::AsExpression, - serialize::ToSql, - sql_types::Text, - sqlite::Sqlite, -}; -use serde::{Deserialize, Serialize}; - -#[derive( - Eq, - PartialEq, - Ord, - PartialOrd, - Serialize, - Deserialize, - Copy, - Clone, - Debug, - AsExpression, - FromSqlRow, -)] -#[diesel(sql_type=Text)] -pub struct DateTimeUtc(DateTime<Utc>); - -impl DateTimeUtc { - pub fn day_diff(&self, other: &DateTimeUtc) -> u32 { - self.0 - .num_days_from_ce() - .abs_diff(other.0.num_days_from_ce()) - } - - pub fn new(date_time_utc: DateTime<Utc>) -> Self { - Self(date_time_utc) - } -} - -impl ToSql<Text, Sqlite> for DateTimeUtc { - fn to_sql<'b>( - &'b self, - out: &mut diesel::serialize::Output<'b, '_, Sqlite>, - ) -> diesel::serialize::Result { - out.set_value((*self).0.to_rfc3339()); - Ok(diesel::serialize::IsNull::No) - } -} - -impl FromSql<Text, Sqlite> for DateTimeUtc { - fn from_sql( - bytes: <Sqlite as diesel::backend::Backend>::RawValue<'_>, - ) -> diesel::deserialize::Result<Self> { - let str: String = FromSql::<Text, Sqlite>::from_sql(bytes)?; - let date_time = str.as_str().parse::<DateTime<Utc>>()?; - Ok(DateTimeUtc(date_time)) - } -} diff --git a/backend/importers/actualbudget/src/models/budgeting_app/account.rs b/backend/importers/actualbudget/src/models/budgeting_app/account.rs deleted file mode 100644 index 165b55c..0000000 --- a/backend/importers/actualbudget/src/models/budgeting_app/account.rs +++ /dev/null @@ -1,8 +0,0 @@ -use super::date_time_utc::DateTimeUtc; - -pub struct Account { - pub id: i32, - pub name: String, - pub opening_balance: i32, - pub opening_date: DateTimeUtc, -} diff --git a/backend/importers/actualbudget/src/models/budgeting_app/budget_update.rs b/backend/importers/actualbudget/src/models/budgeting_app/budget_update.rs deleted file mode 100644 index 9958446..0000000 --- a/backend/importers/actualbudget/src/models/budgeting_app/budget_update.rs +++ /dev/null @@ -1,10 +0,0 @@ -use super::date_time_utc::DateTimeUtc; - -#[derive(Clone)] -pub struct BudgetUpdate { - pub id: i32, - pub category_id: i32, - pub date: DateTimeUtc, - pub new_budget: i32, - pub new_period: i32, -} diff --git a/backend/importers/actualbudget/src/models/budgeting_app/category.rs b/backend/importers/actualbudget/src/models/budgeting_app/category.rs deleted file mode 100644 index 3f51dfd..0000000 --- a/backend/importers/actualbudget/src/models/budgeting_app/category.rs +++ /dev/null @@ -1,4 +0,0 @@ -pub struct Category { - pub id: i32, - pub name: String, -} diff --git a/backend/importers/actualbudget/src/models/budgeting_app/category_transfer.rs b/backend/importers/actualbudget/src/models/budgeting_app/category_transfer.rs deleted file mode 100644 index 0097429..0000000 --- a/backend/importers/actualbudget/src/models/budgeting_app/category_transfer.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub struct CategoryTransfer { - pub id: i32, - pub description: String, - pub quantity: i32, - pub from_category_id: i32, - pub to_category_id: i32, -} diff --git a/backend/importers/actualbudget/src/models/budgeting_app/date_time_utc.rs b/backend/importers/actualbudget/src/models/budgeting_app/date_time_utc.rs deleted file mode 100644 index 69cdfe2..0000000 --- a/backend/importers/actualbudget/src/models/budgeting_app/date_time_utc.rs +++ /dev/null @@ -1,20 +0,0 @@ -use std::{fmt::Display, str::FromStr}; - -use chrono::{DateTime, Utc}; - -#[derive(Clone)] -pub struct DateTimeUtc(DateTime<Utc>); - -impl DateTimeUtc { - pub fn new(date: &str) -> Self { - let mut date_time = String::from(date); - date_time.push_str("T00:00:00.000Z"); - Self(DateTime::<Utc>::from_str(&date_time).unwrap()) - } -} - -impl Display for DateTimeUtc { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - self.0.fmt(f) - } -} diff --git a/backend/importers/actualbudget/src/models/budgeting_app/mod.rs b/backend/importers/actualbudget/src/models/budgeting_app/mod.rs deleted file mode 100644 index bca0f99..0000000 --- a/backend/importers/actualbudget/src/models/budgeting_app/mod.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub mod account; -pub mod budget_update; -pub mod category; -pub mod category_transfer; -pub mod date_time_utc; -pub mod transaction; -pub mod transaction_categorisation; diff --git a/backend/importers/actualbudget/src/models/budgeting_app/transaction.rs b/backend/importers/actualbudget/src/models/budgeting_app/transaction.rs deleted file mode 100644 index e679e1e..0000000 --- a/backend/importers/actualbudget/src/models/budgeting_app/transaction.rs +++ /dev/null @@ -1,11 +0,0 @@ -use super::date_time_utc::DateTimeUtc; - -#[derive(Clone)] -pub struct Transaction { - pub id: i32, - pub description: String, - pub payee: String, - pub quantity: i32, - pub date: DateTimeUtc, - pub account_id: i32, -} diff --git a/backend/importers/actualbudget/src/models/budgeting_app/transaction_categorisation.rs b/backend/importers/actualbudget/src/models/budgeting_app/transaction_categorisation.rs deleted file mode 100644 index ca7e7f5..0000000 --- a/backend/importers/actualbudget/src/models/budgeting_app/transaction_categorisation.rs +++ /dev/null @@ -1,7 +0,0 @@ -pub struct TransactionCategorisation { - pub id: i32, - pub description: String, - pub quantity: i32, - pub transaction_id: i32, - pub category_id: i32, -} diff --git a/backend/importers/actualbudget/src/models/mod.rs b/backend/importers/actualbudget/src/models/mod.rs index cf1b71a..1c97c65 100644 --- a/backend/importers/actualbudget/src/models/mod.rs +++ b/backend/importers/actualbudget/src/models/mod.rs @@ -1,2 +1,5 @@ -pub mod actualbudget; -pub mod budgeting_app; +pub mod actualbudget_account; +pub mod actualbudget_category; +pub mod actualbudget_date; +pub mod actualbudget_transaction; +pub mod actualbudget_zero_budget; diff --git a/backend/importers/actualbudget/src/queries/actualbudget_accounts/get_all.rs b/backend/importers/actualbudget/src/queries/actualbudget_accounts/get_all.rs new file mode 100644 index 0000000..6f667de --- /dev/null +++ b/backend/importers/actualbudget/src/queries/actualbudget_accounts/get_all.rs @@ -0,0 +1,6 @@ +use crate::{models::actualbudget_account::ActualbudgetAccount, schema::accounts::dsl::accounts}; +use diesel::{result::Error, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; + +pub fn get_all_actualbudget_accounts(connection: &mut SqliteConnection) -> Result<Vec<ActualbudgetAccount>, Error> { + accounts.select(ActualbudgetAccount::as_select()).load(connection) +} diff --git a/backend/importers/actualbudget/src/queries/actualbudget_accounts/get_category_transfers_account.rs b/backend/importers/actualbudget/src/queries/actualbudget_accounts/get_category_transfers_account.rs new file mode 100644 index 0000000..f05dc78 --- /dev/null +++ b/backend/importers/actualbudget/src/queries/actualbudget_accounts/get_category_transfers_account.rs @@ -0,0 +1,13 @@ +use diesel::{result::Error, ExpressionMethods, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; + +use crate::{models::actualbudget_account::ActualbudgetAccount, schema::accounts::{ + self as accounts_schema, + dsl::accounts as accounts_table, +}}; + +pub fn get_category_transfers_account(connection: &mut SqliteConnection) -> Result<ActualbudgetAccount, Error> { + accounts_table + .filter(accounts_schema::name.eq("Category transfers")) + .select(ActualbudgetAccount::as_select()) + .first(connection) +} diff --git a/backend/importers/actualbudget/src/queries/actualbudget_accounts/mod.rs b/backend/importers/actualbudget/src/queries/actualbudget_accounts/mod.rs new file mode 100644 index 0000000..c6d6015 --- /dev/null +++ b/backend/importers/actualbudget/src/queries/actualbudget_accounts/mod.rs @@ -0,0 +1,5 @@ +mod get_all; +mod get_category_transfers_account; + +pub use get_all::get_all_actualbudget_accounts; +pub use get_category_transfers_account::get_category_transfers_account; diff --git a/backend/importers/actualbudget/src/queries/actualbudget_categories/get_all.rs b/backend/importers/actualbudget/src/queries/actualbudget_categories/get_all.rs new file mode 100644 index 0000000..4cb7ebc --- /dev/null +++ b/backend/importers/actualbudget/src/queries/actualbudget_categories/get_all.rs @@ -0,0 +1,6 @@ +use crate::{models::actualbudget_category::ActualbudgetCategory, schema::categories::dsl::categories}; +use diesel::{result::Error, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; + +pub fn get_all_actualbudget_categories(connection: &mut SqliteConnection) -> Result<Vec<ActualbudgetCategory>, Error> { + categories.select(ActualbudgetCategory::as_select()).load(connection) +} diff --git a/backend/importers/actualbudget/src/queries/actualbudget_categories/mod.rs b/backend/importers/actualbudget/src/queries/actualbudget_categories/mod.rs new file mode 100644 index 0000000..0c04e53 --- /dev/null +++ b/backend/importers/actualbudget/src/queries/actualbudget_categories/mod.rs @@ -0,0 +1,3 @@ +mod get_all; + +pub use get_all::get_all_actualbudget_categories; diff --git a/backend/importers/actualbudget/src/queries/actualbudget_transactions/get_all.rs b/backend/importers/actualbudget/src/queries/actualbudget_transactions/get_all.rs new file mode 100644 index 0000000..84c8d08 --- /dev/null +++ b/backend/importers/actualbudget/src/queries/actualbudget_transactions/get_all.rs @@ -0,0 +1,6 @@ +use crate::{models::actualbudget_transaction::ActualbudgetTransaction, schema::transactions::dsl::transactions}; +use diesel::{result::Error, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; + +pub fn get_all_actualbudget_transactions(connection: &mut SqliteConnection) -> Result<Vec<ActualbudgetTransaction>, Error> { + transactions.select(ActualbudgetTransaction::as_select()).load(connection) +} diff --git a/backend/importers/actualbudget/src/queries/actualbudget_transactions/get_first_date.rs b/backend/importers/actualbudget/src/queries/actualbudget_transactions/get_first_date.rs new file mode 100644 index 0000000..dabfbd6 --- /dev/null +++ b/backend/importers/actualbudget/src/queries/actualbudget_transactions/get_first_date.rs @@ -0,0 +1,13 @@ +use crate::{models::actualbudget_date::ActualbudgetDate, schema::transactions::{ + self as transactions_schema, + dsl::transactions as transactions_table, +}}; +use diesel::{dsl::min, result::Error, QueryDsl, RunQueryDsl, SqliteConnection}; + +pub fn get_first_actualbudget_transaction_date(connection: &mut SqliteConnection) -> Result<ActualbudgetDate, Error> { + let date_as_int = transactions_table + .select(min(transactions_schema::date)) + .first::<Option<i32>>(connection) + .map_or_else(|e| Err(e), |o| o.map_or_else(|| Err(Error::NotFound), |i| Ok(i)))?; + Ok(ActualbudgetDate::from_i32(date_as_int)) +} diff --git a/backend/importers/actualbudget/src/queries/actualbudget_transactions/mod.rs b/backend/importers/actualbudget/src/queries/actualbudget_transactions/mod.rs new file mode 100644 index 0000000..b4ed0dd --- /dev/null +++ b/backend/importers/actualbudget/src/queries/actualbudget_transactions/mod.rs @@ -0,0 +1,5 @@ +mod get_all; +mod get_first_date; + +pub use get_all::get_all_actualbudget_transactions; +pub use get_first_date::get_first_actualbudget_transaction_date; diff --git a/backend/importers/actualbudget/src/queries/actualbudget_zero_budgets/get_all.rs b/backend/importers/actualbudget/src/queries/actualbudget_zero_budgets/get_all.rs new file mode 100644 index 0000000..0c3f695 --- /dev/null +++ b/backend/importers/actualbudget/src/queries/actualbudget_zero_budgets/get_all.rs @@ -0,0 +1,6 @@ +use crate::{models::actualbudget_zero_budget::ActualbudgetZeroBudget, schema::zero_budgets::dsl::zero_budgets}; +use diesel::{result::Error, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; + +pub fn get_all_actualbudget_zero_budgets(connection: &mut SqliteConnection) -> Result<Vec<ActualbudgetZeroBudget>, Error> { + zero_budgets.select(ActualbudgetZeroBudget::as_select()).load(connection) +} diff --git a/backend/importers/actualbudget/src/queries/actualbudget_zero_budgets/mod.rs b/backend/importers/actualbudget/src/queries/actualbudget_zero_budgets/mod.rs new file mode 100644 index 0000000..ba7e21b --- /dev/null +++ b/backend/importers/actualbudget/src/queries/actualbudget_zero_budgets/mod.rs @@ -0,0 +1,3 @@ +mod get_all; + +pub use get_all::get_all_actualbudget_zero_budgets; diff --git a/backend/importers/actualbudget/src/queries/mod.rs b/backend/importers/actualbudget/src/queries/mod.rs new file mode 100644 index 0000000..e16dafc --- /dev/null +++ b/backend/importers/actualbudget/src/queries/mod.rs @@ -0,0 +1,4 @@ +pub mod actualbudget_accounts; +pub mod actualbudget_categories; +pub mod actualbudget_transactions; +pub mod actualbudget_zero_budgets; diff --git a/backend/importers/actualbudget/src/schema.rs b/backend/importers/actualbudget/src/schema.rs new file mode 100644 index 0000000..a872231 --- /dev/null +++ b/backend/importers/actualbudget/src/schema.rs @@ -0,0 +1,47 @@ +diesel::table! { + accounts (id) { + id -> Text, + name -> Text, + } +} + +diesel::table! { + categories (id) { + id -> Text, + name -> Text, + } +} + +diesel::table! { + transactions (id) { + id -> Text, + #[sql_name = "isParent"] + is_parent -> Bool, + #[sql_name = "isChild"] + is_child -> Bool, + #[sql_name = "acct"] + account_id -> Text, + #[sql_name = "category"] + category_id -> Text, + amount -> Integer, + #[sql_name = "description"] + payee -> Text, + notes -> Text, + date -> Integer, + parent_id -> Nullable<Text>, + } +} + +diesel::table! { + zero_budgets (id) { + id -> Text, + month -> Integer, + #[sql_name = "category"] + category_id -> Text, + amount -> Integer, + #[sql_name = "carryover"] + do_carry_over -> Integer, + // goal -> Integer, + // long_goal -> Integer, + } +} diff --git a/backend/importers/actualbudget/src/transformers.rs b/backend/importers/actualbudget/src/transformers.rs index 678ea05..76001d1 100644 --- a/backend/importers/actualbudget/src/transformers.rs +++ b/backend/importers/actualbudget/src/transformers.rs @@ -1,21 +1,19 @@ +use budgeting_app_core::models::{ + account::Account, budget_update::BudgetUpdate, category::Category, + category_transfer::CategoryTransfer, date_time_utc::DateTimeUtc, transaction::Transaction, + transaction_categorisation::TransactionCategorisation, +}; use core::panic; use std::hash::{DefaultHasher, Hash, Hasher}; -use crate::models::{ - actualbudget, - budgeting_app::{ - account::Account, budget_update::BudgetUpdate, category::Category, - category_transfer::CategoryTransfer, date_time_utc::DateTimeUtc, transaction::Transaction, - transaction_categorisation::TransactionCategorisation, - }, -}; +use crate::models::{actualbudget_account::ActualbudgetAccount, actualbudget_category::ActualbudgetCategory, actualbudget_date::ActualbudgetDate, actualbudget_transaction::ActualbudgetTransaction, actualbudget_zero_budget::ActualbudgetZeroBudget}; -pub fn transform_date(actualbudget_date: &actualbudget::date::Date) -> DateTimeUtc { - DateTimeUtc::new(&actualbudget_date.to_iso()) +pub fn transform_date(actualbudget_date: &ActualbudgetDate) -> Result<DateTimeUtc, String> { + DateTimeUtc::from_date_str(&actualbudget_date.to_iso()).map_err(|e| e.to_string()) } pub fn transform_accounts( - actualbudget_accounts: &[actualbudget::account::Account], + actualbudget_accounts: &[ActualbudgetAccount], first_transaction_date: &DateTimeUtc, ) -> Vec<Account> { let mut accounts = Vec::<Account>::new(); @@ -26,7 +24,7 @@ pub fn transform_accounts( } fn transform_account( - actualbudget_account: &actualbudget::account::Account, + actualbudget_account: &ActualbudgetAccount, opening_date: &DateTimeUtc, ) -> Account { Account { @@ -38,7 +36,7 @@ fn transform_account( } pub fn transform_categories( - actualbudget_categories: &[actualbudget::category::Category], + actualbudget_categories: &[ActualbudgetCategory], ) -> Vec<Category> { let mut categories = Vec::<Category>::new(); for actualbudget_category in actualbudget_categories { @@ -59,8 +57,8 @@ fn transform_id(actualbudget_id: &str) -> i32 { } pub fn transform_budget_updates( - actualbudget_zero_budgets: &[actualbudget::zero_budget::ZeroBudget], -) -> Vec<BudgetUpdate> { + actualbudget_zero_budgets: &[ActualbudgetZeroBudget], +) -> Result<Vec<BudgetUpdate>, String> { let mut budget_updates = Vec::<BudgetUpdate>::new(); for zero_budget in actualbudget_zero_budgets { let first_zero_budget_in_category = get_first_zero_budget_in_category( @@ -73,18 +71,18 @@ pub fn transform_budget_updates( budget_updates.push(BudgetUpdate { id: transform_id(&zero_budget.id.clone()), category_id: transform_id(&zero_budget.category_id.clone()), - date: transform_date(&zero_budget.month.clone()), + date: transform_date(&zero_budget.month.clone()).map_err(|e| e.to_string())?, new_budget: zero_budget.amount.try_into().unwrap(), new_period: 30, }) } - budget_updates + Ok(budget_updates) } fn get_first_zero_budget_in_category( - actualbudget_zero_budgets: &[actualbudget::zero_budget::ZeroBudget], + actualbudget_zero_budgets: &[ActualbudgetZeroBudget], actualbudget_category_id: &str, -) -> Option<actualbudget::zero_budget::ZeroBudget> { +) -> Option<ActualbudgetZeroBudget> { let mut category_zero_budgets = actualbudget_zero_budgets.to_vec(); category_zero_budgets.retain(|zb| zb.category_id == actualbudget_category_id); category_zero_budgets @@ -94,9 +92,9 @@ fn get_first_zero_budget_in_category( } pub fn transform_transactions( - actualbudget_transactions: &[actualbudget::transaction::Transaction], - actualbudget_category_transfers_account: &actualbudget::account::Account, -) -> (Vec<Transaction>, Vec<TransactionCategorisation>) { + actualbudget_transactions: &[ActualbudgetTransaction], + actualbudget_category_transfers_account: &ActualbudgetAccount, +) -> Result<(Vec<Transaction>, Vec<TransactionCategorisation>), String> { let mut transactions = Vec::<Transaction>::new(); let mut transaction_categorisations = Vec::<TransactionCategorisation>::new(); @@ -118,7 +116,7 @@ pub fn transform_transactions( description: atomic_actualbudget_transaction.notes, payee: atomic_actualbudget_transaction.payee, quantity: atomic_actualbudget_transaction.amount.try_into().unwrap(), - date: transform_date(&atomic_actualbudget_transaction.date), + date: transform_date(&atomic_actualbudget_transaction.date).map_err(|e| e.to_string())?, account_id: transform_id(&atomic_actualbudget_transaction.account_id), }); @@ -146,7 +144,7 @@ pub fn transform_transactions( description: parent_actualbudget_transaction.notes, payee: parent_actualbudget_transaction.payee, quantity: parent_actualbudget_transaction.amount.try_into().unwrap(), - date: transform_date(&parent_actualbudget_transaction.date), + date: transform_date(&parent_actualbudget_transaction.date).map_err(|e| e.to_string())?, account_id: transform_id(&parent_actualbudget_transaction.account_id), }); @@ -161,12 +159,12 @@ pub fn transform_transactions( } } - (transactions, transaction_categorisations) + Ok((transactions, transaction_categorisations)) } pub fn transform_category_transfers( - actualbudget_transactions: &[actualbudget::transaction::Transaction], - actualbudget_category_transfers_account: &actualbudget::account::Account, + actualbudget_transactions: &[ActualbudgetTransaction], + actualbudget_category_transfers_account: &ActualbudgetAccount, ) -> Vec<CategoryTransfer> { let mut category_transfers = Vec::<CategoryTransfer>::new(); |
