summaryrefslogtreecommitdiff
path: root/backend
diff options
context:
space:
mode:
authorJoe Carstairs <me@joeac.net>2024-10-16 12:51:21 +0100
committerJoe Carstairs <me@joeac.net>2024-10-16 12:51:21 +0100
commit27b95c9186bd45edd4c982a7cc89cf69cf2c5d46 (patch)
treebf104ff9d64e601bfec5516eb51ef298f3095e9b /backend
parent76974785294089965efc3f55b2a08d4089029c52 (diff)
All backend stuff lives in backend folder
Diffstat (limited to 'backend')
-rw-r--r--backend/app/2024-08-31-084439_initial_setup/down.sql (renamed from backend/migrations/2024-08-31-084439_initial_setup/down.sql)0
-rw-r--r--backend/app/2024-08-31-084439_initial_setup/up.sql (renamed from backend/migrations/2024-08-31-084439_initial_setup/up.sql)0
-rw-r--r--backend/app/Cargo.lock (renamed from backend/Cargo.lock)0
-rw-r--r--backend/app/Cargo.toml (renamed from backend/Cargo.toml)0
-rw-r--r--backend/app/Rocket.toml (renamed from backend/Rocket.toml)0
-rw-r--r--backend/app/diesel.toml (renamed from backend/diesel.toml)0
-rw-r--r--backend/app/src/databases.rs (renamed from backend/src/databases.rs)0
-rw-r--r--backend/app/src/main.rs (renamed from backend/src/main.rs)0
-rw-r--r--backend/app/src/models/account.rs (renamed from backend/src/models/account.rs)0
-rw-r--r--backend/app/src/models/budget.rs (renamed from backend/src/models/budget.rs)0
-rw-r--r--backend/app/src/models/budget_update.rs (renamed from backend/src/models/budget_update.rs)0
-rw-r--r--backend/app/src/models/category.rs (renamed from backend/src/models/category.rs)0
-rw-r--r--backend/app/src/models/category_balance.rs (renamed from backend/src/models/category_balance.rs)0
-rw-r--r--backend/app/src/models/category_budget.rs (renamed from backend/src/models/category_budget.rs)0
-rw-r--r--backend/app/src/models/category_transfer.rs (renamed from backend/src/models/category_transfer.rs)0
-rw-r--r--backend/app/src/models/date_time_utc.rs (renamed from backend/src/models/date_time_utc.rs)0
-rw-r--r--backend/app/src/models/mod.rs (renamed from backend/src/models/mod.rs)0
-rw-r--r--backend/app/src/models/transaction.rs (renamed from backend/src/models/transaction.rs)0
-rw-r--r--backend/app/src/models/transaction_categorisation.rs (renamed from backend/src/models/transaction_categorisation.rs)0
-rw-r--r--backend/app/src/queries/accounts/get_all.rs (renamed from backend/src/queries/accounts/get_all.rs)0
-rw-r--r--backend/app/src/queries/accounts/insert.rs (renamed from backend/src/queries/accounts/insert.rs)0
-rw-r--r--backend/app/src/queries/accounts/mod.rs (renamed from backend/src/queries/accounts/mod.rs)0
-rw-r--r--backend/app/src/queries/budget_updates/get_all.rs (renamed from backend/src/queries/budget_updates/get_all.rs)0
-rw-r--r--backend/app/src/queries/budget_updates/get_first_with_date.rs (renamed from backend/src/queries/budget_updates/get_first_with_date.rs)0
-rw-r--r--backend/app/src/queries/budget_updates/get_most_recent_with_category_id.rs (renamed from backend/src/queries/budget_updates/get_most_recent_with_category_id.rs)0
-rw-r--r--backend/app/src/queries/budget_updates/mod.rs (renamed from backend/src/queries/budget_updates/mod.rs)0
-rw-r--r--backend/app/src/queries/categories/get_all.rs (renamed from backend/src/queries/categories/get_all.rs)0
-rw-r--r--backend/app/src/queries/categories/get_all_ids.rs (renamed from backend/src/queries/categories/get_all_ids.rs)0
-rw-r--r--backend/app/src/queries/categories/mod.rs (renamed from backend/src/queries/categories/mod.rs)0
-rw-r--r--backend/app/src/queries/category_balances/get_all.rs (renamed from backend/src/queries/category_balances/get_all.rs)0
-rw-r--r--backend/app/src/queries/category_balances/mod.rs (renamed from backend/src/queries/category_balances/mod.rs)0
-rw-r--r--backend/app/src/queries/category_budgets/get_most_recent_per_category_id.rs (renamed from backend/src/queries/category_budgets/get_most_recent_per_category_id.rs)0
-rw-r--r--backend/app/src/queries/category_budgets/mod.rs (renamed from backend/src/queries/category_budgets/mod.rs)0
-rw-r--r--backend/app/src/queries/category_transfers/mod.rs (renamed from backend/src/queries/category_transfers/mod.rs)0
-rw-r--r--backend/app/src/queries/category_transfers/sum_quantity_per_from_category_id.rs (renamed from backend/src/queries/category_transfers/sum_quantity_per_from_category_id.rs)0
-rw-r--r--backend/app/src/queries/category_transfers/sum_quantity_per_to_category_id.rs (renamed from backend/src/queries/category_transfers/sum_quantity_per_to_category_id.rs)0
-rw-r--r--backend/app/src/queries/mod.rs (renamed from backend/src/queries/mod.rs)0
-rw-r--r--backend/app/src/queries/transaction_categorisations/mod.rs (renamed from backend/src/queries/transaction_categorisations/mod.rs)0
-rw-r--r--backend/app/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs (renamed from backend/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs)0
-rw-r--r--backend/app/src/queries/transaction_categorisations/sum_quantity_per_to_category_id.rs (renamed from backend/src/queries/transaction_categorisations/sum_quantity_per_to_category_id.rs)0
-rw-r--r--backend/app/src/routes/account.rs (renamed from backend/src/routes/account.rs)0
-rw-r--r--backend/app/src/routes/balance.rs (renamed from backend/src/routes/balance.rs)0
-rw-r--r--backend/app/src/routes/budget.rs (renamed from backend/src/routes/budget.rs)0
-rw-r--r--backend/app/src/routes/budget_update.rs (renamed from backend/src/routes/budget_update.rs)0
-rw-r--r--backend/app/src/routes/category.rs (renamed from backend/src/routes/category.rs)0
-rw-r--r--backend/app/src/routes/mod.rs (renamed from backend/src/routes/mod.rs)0
-rw-r--r--backend/app/src/schema.rs (renamed from backend/src/schema.rs)0
-rw-r--r--backend/app/src/utils/calculate_budgets_accrual.rs (renamed from backend/src/utils/calculate_budgets_accrual.rs)0
-rw-r--r--backend/app/src/utils/calculate_category_balance.rs (renamed from backend/src/utils/calculate_category_balance.rs)0
-rw-r--r--backend/app/src/utils/find_by_id_or.rs (renamed from backend/src/utils/find_by_id_or.rs)0
-rw-r--r--backend/app/src/utils/mod.rs (renamed from backend/src/utils/mod.rs)0
-rw-r--r--backend/importers/actualbudget/Cargo.lock356
-rw-r--r--backend/importers/actualbudget/Cargo.toml8
-rw-r--r--backend/importers/actualbudget/src/getters.rs61
-rw-r--r--backend/importers/actualbudget/src/inserters.rs120
-rw-r--r--backend/importers/actualbudget/src/main.rs57
-rw-r--r--backend/importers/actualbudget/src/models/actualbudget/account.rs15
-rw-r--r--backend/importers/actualbudget/src/models/actualbudget/category.rs15
-rw-r--r--backend/importers/actualbudget/src/models/actualbudget/date.rs69
-rw-r--r--backend/importers/actualbudget/src/models/actualbudget/mod.rs5
-rw-r--r--backend/importers/actualbudget/src/models/actualbudget/transaction.rs43
-rw-r--r--backend/importers/actualbudget/src/models/actualbudget/zero_budget.rs30
-rw-r--r--backend/importers/actualbudget/src/models/budgeting_app/DateTimeUtc.rs57
-rw-r--r--backend/importers/actualbudget/src/models/budgeting_app/account.rs8
-rw-r--r--backend/importers/actualbudget/src/models/budgeting_app/budget_update.rs10
-rw-r--r--backend/importers/actualbudget/src/models/budgeting_app/category.rs4
-rw-r--r--backend/importers/actualbudget/src/models/budgeting_app/category_transfer.rs7
-rw-r--r--backend/importers/actualbudget/src/models/budgeting_app/date_time_utc.rs20
-rw-r--r--backend/importers/actualbudget/src/models/budgeting_app/mod.rs7
-rw-r--r--backend/importers/actualbudget/src/models/budgeting_app/transaction.rs11
-rw-r--r--backend/importers/actualbudget/src/models/budgeting_app/transaction_categorisation.rs7
-rw-r--r--backend/importers/actualbudget/src/models/mod.rs2
-rw-r--r--backend/importers/actualbudget/src/transformers.rs221
-rw-r--r--backend/migrations/.keep0
74 files changed, 1133 insertions, 0 deletions
diff --git a/backend/migrations/2024-08-31-084439_initial_setup/down.sql b/backend/app/2024-08-31-084439_initial_setup/down.sql
index 8cf4b1e..8cf4b1e 100644
--- a/backend/migrations/2024-08-31-084439_initial_setup/down.sql
+++ b/backend/app/2024-08-31-084439_initial_setup/down.sql
diff --git a/backend/migrations/2024-08-31-084439_initial_setup/up.sql b/backend/app/2024-08-31-084439_initial_setup/up.sql
index 3584ae7..3584ae7 100644
--- a/backend/migrations/2024-08-31-084439_initial_setup/up.sql
+++ b/backend/app/2024-08-31-084439_initial_setup/up.sql
diff --git a/backend/Cargo.lock b/backend/app/Cargo.lock
index f453ebe..f453ebe 100644
--- a/backend/Cargo.lock
+++ b/backend/app/Cargo.lock
diff --git a/backend/Cargo.toml b/backend/app/Cargo.toml
index af1c8ef..af1c8ef 100644
--- a/backend/Cargo.toml
+++ b/backend/app/Cargo.toml
diff --git a/backend/Rocket.toml b/backend/app/Rocket.toml
index bb43355..bb43355 100644
--- a/backend/Rocket.toml
+++ b/backend/app/Rocket.toml
diff --git a/backend/diesel.toml b/backend/app/diesel.toml
index a0d61bf..a0d61bf 100644
--- a/backend/diesel.toml
+++ b/backend/app/diesel.toml
diff --git a/backend/src/databases.rs b/backend/app/src/databases.rs
index 7202e78..7202e78 100644
--- a/backend/src/databases.rs
+++ b/backend/app/src/databases.rs
diff --git a/backend/src/main.rs b/backend/app/src/main.rs
index c3e02e5..c3e02e5 100644
--- a/backend/src/main.rs
+++ b/backend/app/src/main.rs
diff --git a/backend/src/models/account.rs b/backend/app/src/models/account.rs
index c10bd5f..c10bd5f 100644
--- a/backend/src/models/account.rs
+++ b/backend/app/src/models/account.rs
diff --git a/backend/src/models/budget.rs b/backend/app/src/models/budget.rs
index cc18f2e..cc18f2e 100644
--- a/backend/src/models/budget.rs
+++ b/backend/app/src/models/budget.rs
diff --git a/backend/src/models/budget_update.rs b/backend/app/src/models/budget_update.rs
index c63e957..c63e957 100644
--- a/backend/src/models/budget_update.rs
+++ b/backend/app/src/models/budget_update.rs
diff --git a/backend/src/models/category.rs b/backend/app/src/models/category.rs
index 5cb2609..5cb2609 100644
--- a/backend/src/models/category.rs
+++ b/backend/app/src/models/category.rs
diff --git a/backend/src/models/category_balance.rs b/backend/app/src/models/category_balance.rs
index 796b40b..796b40b 100644
--- a/backend/src/models/category_balance.rs
+++ b/backend/app/src/models/category_balance.rs
diff --git a/backend/src/models/category_budget.rs b/backend/app/src/models/category_budget.rs
index 9796261..9796261 100644
--- a/backend/src/models/category_budget.rs
+++ b/backend/app/src/models/category_budget.rs
diff --git a/backend/src/models/category_transfer.rs b/backend/app/src/models/category_transfer.rs
index 6cbda0e..6cbda0e 100644
--- a/backend/src/models/category_transfer.rs
+++ b/backend/app/src/models/category_transfer.rs
diff --git a/backend/src/models/date_time_utc.rs b/backend/app/src/models/date_time_utc.rs
index 03d5802..03d5802 100644
--- a/backend/src/models/date_time_utc.rs
+++ b/backend/app/src/models/date_time_utc.rs
diff --git a/backend/src/models/mod.rs b/backend/app/src/models/mod.rs
index b790fcb..b790fcb 100644
--- a/backend/src/models/mod.rs
+++ b/backend/app/src/models/mod.rs
diff --git a/backend/src/models/transaction.rs b/backend/app/src/models/transaction.rs
index 36f33f8..36f33f8 100644
--- a/backend/src/models/transaction.rs
+++ b/backend/app/src/models/transaction.rs
diff --git a/backend/src/models/transaction_categorisation.rs b/backend/app/src/models/transaction_categorisation.rs
index d2867f1..d2867f1 100644
--- a/backend/src/models/transaction_categorisation.rs
+++ b/backend/app/src/models/transaction_categorisation.rs
diff --git a/backend/src/queries/accounts/get_all.rs b/backend/app/src/queries/accounts/get_all.rs
index 12941ac..12941ac 100644
--- a/backend/src/queries/accounts/get_all.rs
+++ b/backend/app/src/queries/accounts/get_all.rs
diff --git a/backend/src/queries/accounts/insert.rs b/backend/app/src/queries/accounts/insert.rs
index c20cf60..c20cf60 100644
--- a/backend/src/queries/accounts/insert.rs
+++ b/backend/app/src/queries/accounts/insert.rs
diff --git a/backend/src/queries/accounts/mod.rs b/backend/app/src/queries/accounts/mod.rs
index 94fa360..94fa360 100644
--- a/backend/src/queries/accounts/mod.rs
+++ b/backend/app/src/queries/accounts/mod.rs
diff --git a/backend/src/queries/budget_updates/get_all.rs b/backend/app/src/queries/budget_updates/get_all.rs
index 97f2f2f..97f2f2f 100644
--- a/backend/src/queries/budget_updates/get_all.rs
+++ b/backend/app/src/queries/budget_updates/get_all.rs
diff --git a/backend/src/queries/budget_updates/get_first_with_date.rs b/backend/app/src/queries/budget_updates/get_first_with_date.rs
index a2824be..a2824be 100644
--- a/backend/src/queries/budget_updates/get_first_with_date.rs
+++ b/backend/app/src/queries/budget_updates/get_first_with_date.rs
diff --git a/backend/src/queries/budget_updates/get_most_recent_with_category_id.rs b/backend/app/src/queries/budget_updates/get_most_recent_with_category_id.rs
index 4753092..4753092 100644
--- a/backend/src/queries/budget_updates/get_most_recent_with_category_id.rs
+++ b/backend/app/src/queries/budget_updates/get_most_recent_with_category_id.rs
diff --git a/backend/src/queries/budget_updates/mod.rs b/backend/app/src/queries/budget_updates/mod.rs
index 3b514dd..3b514dd 100644
--- a/backend/src/queries/budget_updates/mod.rs
+++ b/backend/app/src/queries/budget_updates/mod.rs
diff --git a/backend/src/queries/categories/get_all.rs b/backend/app/src/queries/categories/get_all.rs
index db5a073..db5a073 100644
--- a/backend/src/queries/categories/get_all.rs
+++ b/backend/app/src/queries/categories/get_all.rs
diff --git a/backend/src/queries/categories/get_all_ids.rs b/backend/app/src/queries/categories/get_all_ids.rs
index 7c7c912..7c7c912 100644
--- a/backend/src/queries/categories/get_all_ids.rs
+++ b/backend/app/src/queries/categories/get_all_ids.rs
diff --git a/backend/src/queries/categories/mod.rs b/backend/app/src/queries/categories/mod.rs
index 80356b3..80356b3 100644
--- a/backend/src/queries/categories/mod.rs
+++ b/backend/app/src/queries/categories/mod.rs
diff --git a/backend/src/queries/category_balances/get_all.rs b/backend/app/src/queries/category_balances/get_all.rs
index 713fbeb..713fbeb 100644
--- a/backend/src/queries/category_balances/get_all.rs
+++ b/backend/app/src/queries/category_balances/get_all.rs
diff --git a/backend/src/queries/category_balances/mod.rs b/backend/app/src/queries/category_balances/mod.rs
index 3fc1be2..3fc1be2 100644
--- a/backend/src/queries/category_balances/mod.rs
+++ b/backend/app/src/queries/category_balances/mod.rs
diff --git a/backend/src/queries/category_budgets/get_most_recent_per_category_id.rs b/backend/app/src/queries/category_budgets/get_most_recent_per_category_id.rs
index 6b76efd..6b76efd 100644
--- a/backend/src/queries/category_budgets/get_most_recent_per_category_id.rs
+++ b/backend/app/src/queries/category_budgets/get_most_recent_per_category_id.rs
diff --git a/backend/src/queries/category_budgets/mod.rs b/backend/app/src/queries/category_budgets/mod.rs
index 29fc246..29fc246 100644
--- a/backend/src/queries/category_budgets/mod.rs
+++ b/backend/app/src/queries/category_budgets/mod.rs
diff --git a/backend/src/queries/category_transfers/mod.rs b/backend/app/src/queries/category_transfers/mod.rs
index 85eb1c8..85eb1c8 100644
--- a/backend/src/queries/category_transfers/mod.rs
+++ b/backend/app/src/queries/category_transfers/mod.rs
diff --git a/backend/src/queries/category_transfers/sum_quantity_per_from_category_id.rs b/backend/app/src/queries/category_transfers/sum_quantity_per_from_category_id.rs
index ae3a51a..ae3a51a 100644
--- a/backend/src/queries/category_transfers/sum_quantity_per_from_category_id.rs
+++ b/backend/app/src/queries/category_transfers/sum_quantity_per_from_category_id.rs
diff --git a/backend/src/queries/category_transfers/sum_quantity_per_to_category_id.rs b/backend/app/src/queries/category_transfers/sum_quantity_per_to_category_id.rs
index fe6f3a7..fe6f3a7 100644
--- a/backend/src/queries/category_transfers/sum_quantity_per_to_category_id.rs
+++ b/backend/app/src/queries/category_transfers/sum_quantity_per_to_category_id.rs
diff --git a/backend/src/queries/mod.rs b/backend/app/src/queries/mod.rs
index 43328e2..43328e2 100644
--- a/backend/src/queries/mod.rs
+++ b/backend/app/src/queries/mod.rs
diff --git a/backend/src/queries/transaction_categorisations/mod.rs b/backend/app/src/queries/transaction_categorisations/mod.rs
index d4217cc..d4217cc 100644
--- a/backend/src/queries/transaction_categorisations/mod.rs
+++ b/backend/app/src/queries/transaction_categorisations/mod.rs
diff --git a/backend/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs b/backend/app/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs
index 7f93e0c..7f93e0c 100644
--- a/backend/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs
+++ b/backend/app/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs
diff --git a/backend/src/queries/transaction_categorisations/sum_quantity_per_to_category_id.rs b/backend/app/src/queries/transaction_categorisations/sum_quantity_per_to_category_id.rs
index e69de29..e69de29 100644
--- a/backend/src/queries/transaction_categorisations/sum_quantity_per_to_category_id.rs
+++ b/backend/app/src/queries/transaction_categorisations/sum_quantity_per_to_category_id.rs
diff --git a/backend/src/routes/account.rs b/backend/app/src/routes/account.rs
index 7bbdb4f..7bbdb4f 100644
--- a/backend/src/routes/account.rs
+++ b/backend/app/src/routes/account.rs
diff --git a/backend/src/routes/balance.rs b/backend/app/src/routes/balance.rs
index f423c90..f423c90 100644
--- a/backend/src/routes/balance.rs
+++ b/backend/app/src/routes/balance.rs
diff --git a/backend/src/routes/budget.rs b/backend/app/src/routes/budget.rs
index 67aae8e..67aae8e 100644
--- a/backend/src/routes/budget.rs
+++ b/backend/app/src/routes/budget.rs
diff --git a/backend/src/routes/budget_update.rs b/backend/app/src/routes/budget_update.rs
index 7732c7b..7732c7b 100644
--- a/backend/src/routes/budget_update.rs
+++ b/backend/app/src/routes/budget_update.rs
diff --git a/backend/src/routes/category.rs b/backend/app/src/routes/category.rs
index 01fd8d1..01fd8d1 100644
--- a/backend/src/routes/category.rs
+++ b/backend/app/src/routes/category.rs
diff --git a/backend/src/routes/mod.rs b/backend/app/src/routes/mod.rs
index bf0ab4a..bf0ab4a 100644
--- a/backend/src/routes/mod.rs
+++ b/backend/app/src/routes/mod.rs
diff --git a/backend/src/schema.rs b/backend/app/src/schema.rs
index 372f8f3..372f8f3 100644
--- a/backend/src/schema.rs
+++ b/backend/app/src/schema.rs
diff --git a/backend/src/utils/calculate_budgets_accrual.rs b/backend/app/src/utils/calculate_budgets_accrual.rs
index f14260d..f14260d 100644
--- a/backend/src/utils/calculate_budgets_accrual.rs
+++ b/backend/app/src/utils/calculate_budgets_accrual.rs
diff --git a/backend/src/utils/calculate_category_balance.rs b/backend/app/src/utils/calculate_category_balance.rs
index 3c1c62f..3c1c62f 100644
--- a/backend/src/utils/calculate_category_balance.rs
+++ b/backend/app/src/utils/calculate_category_balance.rs
diff --git a/backend/src/utils/find_by_id_or.rs b/backend/app/src/utils/find_by_id_or.rs
index f5add30..f5add30 100644
--- a/backend/src/utils/find_by_id_or.rs
+++ b/backend/app/src/utils/find_by_id_or.rs
diff --git a/backend/src/utils/mod.rs b/backend/app/src/utils/mod.rs
index 84a27af..84a27af 100644
--- a/backend/src/utils/mod.rs
+++ b/backend/app/src/utils/mod.rs
diff --git a/backend/importers/actualbudget/Cargo.lock b/backend/importers/actualbudget/Cargo.lock
new file mode 100644
index 0000000..63a53b9
--- /dev/null
+++ b/backend/importers/actualbudget/Cargo.lock
@@ -0,0 +1,356 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 3
+
+[[package]]
+name = "actualbudget"
+version = "0.1.0"
+dependencies = [
+ "chrono",
+ "sqlite",
+]
+
+[[package]]
+name = "android-tzdata"
+version = "0.1.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
+
+[[package]]
+name = "android_system_properties"
+version = "0.1.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
+dependencies = [
+ "libc",
+]
+
+[[package]]
+name = "autocfg"
+version = "1.3.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0"
+
+[[package]]
+name = "bumpalo"
+version = "3.16.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+
+[[package]]
+name = "cc"
+version = "1.1.16"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e9d013ecb737093c0e86b151a7b837993cf9ec6c502946cfb44bedc392421e0b"
+dependencies = [
+ "shlex",
+]
+
+[[package]]
+name = "cfg-if"
+version = "1.0.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
+
+[[package]]
+name = "chrono"
+version = "0.4.38"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
+dependencies = [
+ "android-tzdata",
+ "iana-time-zone",
+ "js-sys",
+ "num-traits",
+ "serde",
+ "wasm-bindgen",
+ "windows-targets",
+]
+
+[[package]]
+name = "core-foundation-sys"
+version = "0.8.7"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
+
+[[package]]
+name = "iana-time-zone"
+version = "0.1.60"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141"
+dependencies = [
+ "android_system_properties",
+ "core-foundation-sys",
+ "iana-time-zone-haiku",
+ "js-sys",
+ "wasm-bindgen",
+ "windows-core",
+]
+
+[[package]]
+name = "iana-time-zone-haiku"
+version = "0.1.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
+dependencies = [
+ "cc",
+]
+
+[[package]]
+name = "js-sys"
+version = "0.3.70"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a"
+dependencies = [
+ "wasm-bindgen",
+]
+
+[[package]]
+name = "libc"
+version = "0.2.158"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d8adc4bb1803a324070e64a98ae98f38934d91957a99cfb3a43dcbc01bc56439"
+
+[[package]]
+name = "log"
+version = "0.4.22"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24"
+
+[[package]]
+name = "num-traits"
+version = "0.2.19"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
+dependencies = [
+ "autocfg",
+]
+
+[[package]]
+name = "once_cell"
+version = "1.19.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
+
+[[package]]
+name = "pkg-config"
+version = "0.3.30"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec"
+
+[[package]]
+name = "proc-macro2"
+version = "1.0.86"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77"
+dependencies = [
+ "unicode-ident",
+]
+
+[[package]]
+name = "quote"
+version = "1.0.37"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
+dependencies = [
+ "proc-macro2",
+]
+
+[[package]]
+name = "serde"
+version = "1.0.210"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c8e3592472072e6e22e0a54d5904d9febf8508f65fb8552499a1abc7d1078c3a"
+dependencies = [
+ "serde_derive",
+]
+
+[[package]]
+name = "serde_derive"
+version = "1.0.210"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "243902eda00fad750862fc144cea25caca5e20d615af0a81bee94ca738f1df1f"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+]
+
+[[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"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "5dfe6fb16f2bee6452feeb4d12bfa404fbcd3cfc121b2950e501d1ae9cae718e"
+dependencies = [
+ "sqlite3-sys",
+]
+
+[[package]]
+name = "sqlite3-src"
+version = "0.6.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "174d4a6df77c27db281fb23de1a6d968f3aaaa4807c2a1afa8056b971f947b4a"
+dependencies = [
+ "cc",
+ "pkg-config",
+]
+
+[[package]]
+name = "sqlite3-sys"
+version = "0.17.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "3901ada7090c3c3584dc92ec7ef1b7091868d13bfe6d7de9f0bcaffee7d0ade5"
+dependencies = [
+ "sqlite3-src",
+]
+
+[[package]]
+name = "syn"
+version = "2.0.77"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9f35bcdf61fd8e7be6caf75f429fdca8beb3ed76584befb503b1569faee373ed"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "unicode-ident",
+]
+
+[[package]]
+name = "unicode-ident"
+version = "1.0.13"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe"
+
+[[package]]
+name = "wasm-bindgen"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5"
+dependencies = [
+ "cfg-if",
+ "once_cell",
+ "wasm-bindgen-macro",
+]
+
+[[package]]
+name = "wasm-bindgen-backend"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b"
+dependencies = [
+ "bumpalo",
+ "log",
+ "once_cell",
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-macro"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf"
+dependencies = [
+ "quote",
+ "wasm-bindgen-macro-support",
+]
+
+[[package]]
+name = "wasm-bindgen-macro-support"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn",
+ "wasm-bindgen-backend",
+ "wasm-bindgen-shared",
+]
+
+[[package]]
+name = "wasm-bindgen-shared"
+version = "0.2.93"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484"
+
+[[package]]
+name = "windows-core"
+version = "0.52.0"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
+dependencies = [
+ "windows-targets",
+]
+
+[[package]]
+name = "windows-targets"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
+dependencies = [
+ "windows_aarch64_gnullvm",
+ "windows_aarch64_msvc",
+ "windows_i686_gnu",
+ "windows_i686_gnullvm",
+ "windows_i686_msvc",
+ "windows_x86_64_gnu",
+ "windows_x86_64_gnullvm",
+ "windows_x86_64_msvc",
+]
+
+[[package]]
+name = "windows_aarch64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
+
+[[package]]
+name = "windows_aarch64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
+
+[[package]]
+name = "windows_i686_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b"
+
+[[package]]
+name = "windows_i686_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
+
+[[package]]
+name = "windows_i686_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
+
+[[package]]
+name = "windows_x86_64_gnu"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
+
+[[package]]
+name = "windows_x86_64_gnullvm"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
+
+[[package]]
+name = "windows_x86_64_msvc"
+version = "0.52.6"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec"
diff --git a/backend/importers/actualbudget/Cargo.toml b/backend/importers/actualbudget/Cargo.toml
new file mode 100644
index 0000000..7cc3e1b
--- /dev/null
+++ b/backend/importers/actualbudget/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "actualbudget"
+version = "0.1.0"
+edition = "2021"
+
+[dependencies]
+sqlite = "0.36.1"
+chrono = { version = "0.4.38", features = ["serde"] }
diff --git a/backend/importers/actualbudget/src/getters.rs b/backend/importers/actualbudget/src/getters.rs
new file mode 100644
index 0000000..88a660d
--- /dev/null
+++ b/backend/importers/actualbudget/src/getters.rs
@@ -0,0 +1,61 @@
+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
new file mode 100644
index 0000000..179de97
--- /dev/null
+++ b/backend/importers/actualbudget/src/inserters.rs
@@ -0,0 +1,120 @@
+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
new file mode 100644
index 0000000..495216e
--- /dev/null
+++ b/backend/importers/actualbudget/src/main.rs
@@ -0,0 +1,57 @@
+pub mod getters;
+pub mod inserters;
+pub mod models;
+pub mod transformers;
+
+use getters::*;
+use inserters::*;
+use sqlite::Connection;
+
+fn main() {
+ let connection_src = sqlite::open("in.sqlite").unwrap();
+ let connection_dest = sqlite::open("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 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 (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);
+}
+
+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();
+}
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..727c082
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/actualbudget/account.rs
@@ -0,0 +1,15 @@
+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
new file mode 100644
index 0000000..21fdf80
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/actualbudget/category.rs
@@ -0,0 +1,15 @@
+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
new file mode 100644
index 0000000..bd37c46
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/actualbudget/date.rs
@@ -0,0 +1,69 @@
+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
new file mode 100644
index 0000000..5ca472f
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/actualbudget/mod.rs
@@ -0,0 +1,5 @@
+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
new file mode 100644
index 0000000..0a21cfe
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/actualbudget/transaction.rs
@@ -0,0 +1,43 @@
+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
new file mode 100644
index 0000000..87c88e7
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/actualbudget/zero_budget.rs
@@ -0,0 +1,30 @@
+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/budgeting_app/DateTimeUtc.rs b/backend/importers/actualbudget/src/models/budgeting_app/DateTimeUtc.rs
new file mode 100644
index 0000000..e44fa8c
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/budgeting_app/DateTimeUtc.rs
@@ -0,0 +1,57 @@
+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
new file mode 100644
index 0000000..165b55c
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/budgeting_app/account.rs
@@ -0,0 +1,8 @@
+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
new file mode 100644
index 0000000..9958446
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/budgeting_app/budget_update.rs
@@ -0,0 +1,10 @@
+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
new file mode 100644
index 0000000..3f51dfd
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/budgeting_app/category.rs
@@ -0,0 +1,4 @@
+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
new file mode 100644
index 0000000..0097429
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/budgeting_app/category_transfer.rs
@@ -0,0 +1,7 @@
+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
new file mode 100644
index 0000000..69cdfe2
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/budgeting_app/date_time_utc.rs
@@ -0,0 +1,20 @@
+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
new file mode 100644
index 0000000..bca0f99
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/budgeting_app/mod.rs
@@ -0,0 +1,7 @@
+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
new file mode 100644
index 0000000..e679e1e
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/budgeting_app/transaction.rs
@@ -0,0 +1,11 @@
+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
new file mode 100644
index 0000000..ca7e7f5
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/budgeting_app/transaction_categorisation.rs
@@ -0,0 +1,7 @@
+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
new file mode 100644
index 0000000..cf1b71a
--- /dev/null
+++ b/backend/importers/actualbudget/src/models/mod.rs
@@ -0,0 +1,2 @@
+pub mod actualbudget;
+pub mod budgeting_app;
diff --git a/backend/importers/actualbudget/src/transformers.rs b/backend/importers/actualbudget/src/transformers.rs
new file mode 100644
index 0000000..678ea05
--- /dev/null
+++ b/backend/importers/actualbudget/src/transformers.rs
@@ -0,0 +1,221 @@
+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,
+ },
+};
+
+pub fn transform_date(actualbudget_date: &actualbudget::date::Date) -> DateTimeUtc {
+ DateTimeUtc::new(&actualbudget_date.to_iso())
+}
+
+pub fn transform_accounts(
+ actualbudget_accounts: &[actualbudget::account::Account],
+ first_transaction_date: &DateTimeUtc,
+) -> Vec<Account> {
+ let mut accounts = Vec::<Account>::new();
+ for account in actualbudget_accounts {
+ accounts.push(transform_account(account, first_transaction_date));
+ }
+ accounts
+}
+
+fn transform_account(
+ actualbudget_account: &actualbudget::account::Account,
+ opening_date: &DateTimeUtc,
+) -> Account {
+ Account {
+ id: transform_id(&actualbudget_account.id.clone()),
+ name: actualbudget_account.name.clone(),
+ opening_balance: 0,
+ opening_date: opening_date.clone(),
+ }
+}
+
+pub fn transform_categories(
+ actualbudget_categories: &[actualbudget::category::Category],
+) -> Vec<Category> {
+ let mut categories = Vec::<Category>::new();
+ for actualbudget_category in actualbudget_categories {
+ categories.push(Category {
+ id: transform_id(&actualbudget_category.id.clone()),
+ name: actualbudget_category.name.clone(),
+ })
+ }
+ categories
+}
+
+fn transform_id(actualbudget_id: &str) -> i32 {
+ let mut hasher = DefaultHasher::new();
+ actualbudget_id.hash(&mut hasher);
+ (hasher.finish() % std::convert::TryInto::<u64>::try_into(i32::MAX).unwrap())
+ .try_into()
+ .unwrap()
+}
+
+pub fn transform_budget_updates(
+ actualbudget_zero_budgets: &[actualbudget::zero_budget::ZeroBudget],
+) -> Vec<BudgetUpdate> {
+ 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(
+ actualbudget_zero_budgets,
+ &zero_budget.category_id.clone(),
+ );
+ if first_zero_budget_in_category.is_some_and(|zb| zb.id == zero_budget.id) {
+ continue;
+ }
+ 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()),
+ new_budget: zero_budget.amount.try_into().unwrap(),
+ new_period: 30,
+ })
+ }
+ budget_updates
+}
+
+fn get_first_zero_budget_in_category(
+ actualbudget_zero_budgets: &[actualbudget::zero_budget::ZeroBudget],
+ actualbudget_category_id: &str,
+) -> Option<actualbudget::zero_budget::ZeroBudget> {
+ let mut category_zero_budgets = actualbudget_zero_budgets.to_vec();
+ category_zero_budgets.retain(|zb| zb.category_id == actualbudget_category_id);
+ category_zero_budgets
+ .iter()
+ .min_by_key(|zb| zb.month.clone())
+ .cloned()
+}
+
+pub fn transform_transactions(
+ actualbudget_transactions: &[actualbudget::transaction::Transaction],
+ actualbudget_category_transfers_account: &actualbudget::account::Account,
+) -> (Vec<Transaction>, Vec<TransactionCategorisation>) {
+ let mut transactions = Vec::<Transaction>::new();
+ let mut transaction_categorisations = Vec::<TransactionCategorisation>::new();
+
+ let mut actualbudget_transactions = actualbudget_transactions.to_vec();
+ actualbudget_transactions
+ .retain(|t| t.category_id.clone() != actualbudget_category_transfers_account.id.clone());
+ let mut parent_actualbudget_transactions = actualbudget_transactions.to_vec();
+ parent_actualbudget_transactions.retain(|t| t.is_parent);
+ let mut child_actualbudget_transactions = actualbudget_transactions.to_vec();
+ child_actualbudget_transactions.retain(|t| t.is_child);
+ let mut atomic_actualbudget_transactions = actualbudget_transactions.to_vec();
+ atomic_actualbudget_transactions.retain(|t| !t.is_parent && !t.is_child);
+
+ for atomic_actualbudget_transaction in atomic_actualbudget_transactions {
+ let transaction_id = transform_id(&atomic_actualbudget_transaction.id);
+
+ transactions.push(Transaction {
+ id: transaction_id,
+ 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),
+ account_id: transform_id(&atomic_actualbudget_transaction.account_id),
+ });
+
+ transaction_categorisations.push(TransactionCategorisation {
+ id: transaction_id,
+ description: String::new(),
+ quantity: atomic_actualbudget_transaction.amount.try_into().unwrap(),
+ transaction_id,
+ category_id: transform_id(&atomic_actualbudget_transaction.category_id),
+ });
+ }
+
+ for parent_actualbudget_transaction in parent_actualbudget_transactions {
+ let mut child_actualbudget_transactions = child_actualbudget_transactions.to_vec();
+ child_actualbudget_transactions.retain(|t| {
+ t.parent_id
+ .clone()
+ .is_some_and(|parent_id| parent_id == parent_actualbudget_transaction.id.clone())
+ });
+
+ let transaction_id = transform_id(&parent_actualbudget_transaction.id);
+
+ transactions.push(Transaction {
+ id: transaction_id,
+ 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),
+ account_id: transform_id(&parent_actualbudget_transaction.account_id),
+ });
+
+ for child_actualbudget_transaction in child_actualbudget_transactions {
+ transaction_categorisations.push(TransactionCategorisation {
+ id: transform_id(&child_actualbudget_transaction.id),
+ description: child_actualbudget_transaction.notes,
+ quantity: child_actualbudget_transaction.amount.try_into().unwrap(),
+ transaction_id,
+ category_id: transform_id(&child_actualbudget_transaction.category_id),
+ });
+ }
+ }
+
+ (transactions, transaction_categorisations)
+}
+
+pub fn transform_category_transfers(
+ actualbudget_transactions: &[actualbudget::transaction::Transaction],
+ actualbudget_category_transfers_account: &actualbudget::account::Account,
+) -> Vec<CategoryTransfer> {
+ let mut category_transfers = Vec::<CategoryTransfer>::new();
+
+ let mut actualbudget_category_transfers = actualbudget_transactions.to_vec();
+ actualbudget_category_transfers
+ .retain(|t| t.category_id.clone() == actualbudget_category_transfers_account.id.clone());
+ let mut actualbudget_parent_category_transfers = actualbudget_category_transfers.to_vec();
+ actualbudget_parent_category_transfers.retain(|t| t.is_parent);
+ let mut actualbudget_child_category_transfers = actualbudget_category_transfers.to_vec();
+ actualbudget_child_category_transfers.retain(|t| t.is_child);
+
+ for actualbudget_parent_category_transfer in actualbudget_parent_category_transfers {
+ let mut actualbudget_child_category_transfers =
+ actualbudget_child_category_transfers.to_vec();
+ actualbudget_child_category_transfers.retain(|t| {
+ t.parent_id
+ .clone()
+ .is_some_and(|parent_id| parent_id == actualbudget_parent_category_transfer.id)
+ });
+
+ if actualbudget_child_category_transfers.len() != 2 {
+ panic!("Category transfers should always have exactly two children.");
+ }
+
+ let category_1 = actualbudget_child_category_transfers[0].clone();
+ let category_2 = actualbudget_child_category_transfers[1].clone();
+
+ // To avoid duplicates
+ if category_1.amount >= 0 {
+ continue;
+ }
+
+ if actualbudget_child_category_transfers[0].amount
+ != -actualbudget_child_category_transfers[1].amount
+ {
+ panic!("Category transfer children should sum to zero.");
+ }
+
+ category_transfers.push(CategoryTransfer {
+ id: transform_id(&actualbudget_parent_category_transfer.id),
+ description: actualbudget_parent_category_transfer.notes,
+ quantity: actualbudget_child_category_transfers[0]
+ .amount
+ .try_into()
+ .unwrap(),
+ from_category_id: transform_id(&category_1.id),
+ to_category_id: transform_id(&category_2.id),
+ });
+ }
+
+ category_transfers
+}
diff --git a/backend/migrations/.keep b/backend/migrations/.keep
deleted file mode 100644
index e69de29..0000000
--- a/backend/migrations/.keep
+++ /dev/null