From 7adaace61a4fe983eba5ded3aaaf624ce6fb9014 Mon Sep 17 00:00:00 2001 From: Joe Carstairs Date: Wed, 16 Oct 2024 18:22:55 +0100 Subject: Refactors some backend stuff to core lib --- .../app/2024-08-31-084439_initial_setup/down.sql | 6 - backend/app/2024-08-31-084439_initial_setup/up.sql | 70 --- backend/app/Cargo.lock | 10 + backend/app/Cargo.toml | 2 +- backend/app/Rocket.toml | 2 +- backend/app/src/main.rs | 6 +- backend/app/src/models/account.rs | 15 - backend/app/src/models/budget.rs | 44 -- backend/app/src/models/budget_update.rs | 17 - backend/app/src/models/category.rs | 9 - backend/app/src/models/category_balance.rs | 7 - backend/app/src/models/category_budget.rs | 9 - backend/app/src/models/category_transfer.rs | 11 - backend/app/src/models/date_time_utc.rs | 131 ----- backend/app/src/models/mod.rs | 10 - backend/app/src/models/transaction.rs | 17 - .../app/src/models/transaction_categorisation.rs | 11 - backend/app/src/queries/accounts/get_all.rs | 3 +- backend/app/src/queries/accounts/insert.rs | 7 +- backend/app/src/queries/budget_updates/get_all.rs | 2 +- .../queries/budget_updates/get_first_with_date.rs | 2 +- .../get_most_recent_with_category_id.rs | 2 +- backend/app/src/queries/categories/get_all.rs | 2 +- backend/app/src/queries/categories/get_all_ids.rs | 2 +- .../app/src/queries/category_balances/get_all.rs | 2 +- .../get_most_recent_per_category_id.rs | 12 +- .../sum_quantity_per_from_category_id.rs | 8 +- .../sum_quantity_per_to_category_id.rs | 8 +- .../sum_quantity_per_category_id.rs | 2 +- .../sum_quantity_per_to_category_id.rs | 0 backend/app/src/routes/account.rs | 6 +- backend/app/src/routes/balance.rs | 3 +- backend/app/src/routes/budget.rs | 2 +- backend/app/src/routes/budget_update.rs | 2 +- backend/app/src/routes/category.rs | 2 +- backend/app/src/schema.rs | 72 --- backend/app/src/utils/calculate_budgets_accrual.rs | 11 +- .../app/src/utils/calculate_category_balance.rs | 11 +- .../core/2024-08-31-084439_initial_setup/down.sql | 6 + .../core/2024-08-31-084439_initial_setup/up.sql | 70 +++ backend/core/Cargo.lock | 628 +++++++++++++++++++++ backend/core/Cargo.toml | 10 + backend/core/src/lib.rs | 6 + backend/core/src/models/account.rs | 15 + backend/core/src/models/budget.rs | 44 ++ backend/core/src/models/budget_update.rs | 17 + backend/core/src/models/category.rs | 9 + backend/core/src/models/category_balance.rs | 7 + backend/core/src/models/category_budget.rs | 9 + backend/core/src/models/category_transfer.rs | 11 + backend/core/src/models/date_time_utc.rs | 131 +++++ backend/core/src/models/mod.rs | 10 + backend/core/src/models/transaction.rs | 17 + .../core/src/models/transaction_categorisation.rs | 11 + backend/core/src/schema.rs | 72 +++ 55 files changed, 1132 insertions(+), 479 deletions(-) delete mode 100644 backend/app/2024-08-31-084439_initial_setup/down.sql delete mode 100644 backend/app/2024-08-31-084439_initial_setup/up.sql delete mode 100644 backend/app/src/models/account.rs delete mode 100644 backend/app/src/models/budget.rs delete mode 100644 backend/app/src/models/budget_update.rs delete mode 100644 backend/app/src/models/category.rs delete mode 100644 backend/app/src/models/category_balance.rs delete mode 100644 backend/app/src/models/category_budget.rs delete mode 100644 backend/app/src/models/category_transfer.rs delete mode 100644 backend/app/src/models/date_time_utc.rs delete mode 100644 backend/app/src/models/mod.rs delete mode 100644 backend/app/src/models/transaction.rs delete mode 100644 backend/app/src/models/transaction_categorisation.rs delete mode 100644 backend/app/src/queries/transaction_categorisations/sum_quantity_per_to_category_id.rs delete mode 100644 backend/app/src/schema.rs create mode 100644 backend/core/2024-08-31-084439_initial_setup/down.sql create mode 100644 backend/core/2024-08-31-084439_initial_setup/up.sql create mode 100644 backend/core/Cargo.lock create mode 100644 backend/core/Cargo.toml create mode 100644 backend/core/src/lib.rs create mode 100644 backend/core/src/models/account.rs create mode 100644 backend/core/src/models/budget.rs create mode 100644 backend/core/src/models/budget_update.rs create mode 100644 backend/core/src/models/category.rs create mode 100644 backend/core/src/models/category_balance.rs create mode 100644 backend/core/src/models/category_budget.rs create mode 100644 backend/core/src/models/category_transfer.rs create mode 100644 backend/core/src/models/date_time_utc.rs create mode 100644 backend/core/src/models/mod.rs create mode 100644 backend/core/src/models/transaction.rs create mode 100644 backend/core/src/models/transaction_categorisation.rs create mode 100644 backend/core/src/schema.rs diff --git a/backend/app/2024-08-31-084439_initial_setup/down.sql b/backend/app/2024-08-31-084439_initial_setup/down.sql deleted file mode 100644 index 8cf4b1e..0000000 --- a/backend/app/2024-08-31-084439_initial_setup/down.sql +++ /dev/null @@ -1,6 +0,0 @@ -DROP TABLE accounts; -DROP TABLE budget_updates; -DROP TABLE categories; -DROP TABLE transaction_categorisations; -DROP TABLE category_transfers; -DROP TABLE transactions; diff --git a/backend/app/2024-08-31-084439_initial_setup/up.sql b/backend/app/2024-08-31-084439_initial_setup/up.sql deleted file mode 100644 index 3584ae7..0000000 --- a/backend/app/2024-08-31-084439_initial_setup/up.sql +++ /dev/null @@ -1,70 +0,0 @@ -PRAGMA foreign_keys = ON; - -CREATE TABLE accounts( - id INTEGER NOT NULL PRIMARY KEY, - name TEXT NOT NULL, - opening_balance INTEGER NOT NULL, - opening_date TEXT NOT NULL -); - -CREATE TABLE budget_updates( - id INTEGER NOT NULL PRIMARY KEY, - category_id INTEGER NOT NULL, - date TEXT NOT NULL, - new_budget INTEGER NOT NULL, - new_period INTEGER NOT NULL, - FOREIGN KEY (category_id) - REFERENCES categories (id) - ON UPDATE CASCADE - ON DELETE RESTRICT -); - -CREATE TABLE categories( - id INTEGER NOT NULL PRIMARY KEY, - name TEXT NOT NULL -); - -CREATE TABLE category_transfers( - id INTEGER NOT NULL PRIMARY KEY, - description TEXT NOT NULL, - quantity INTEGER NOT NULL, - from_category_id INTEGER NOT NULL, - to_category_id INTEGER NOT NULL, - FOREIGN KEY (from_category_id) - REFERENCES categories (id) - ON UPDATE CASCADE - ON DELETE RESTRICT, - FOREIGN KEY (to_category_id) - REFERENCES categories (id) - ON UPDATE CASCADE - ON DELETE RESTRICT -); - -CREATE TABLE transactions( - id INTEGER NOT NULL PRIMARY KEY, - description TEXT NOT NULL, - payee TEXT NOT NULL, - quantity INTEGER NOT NULL, - date TEXT NOT NULL, - account_id INTEGER NOT NULL, - FOREIGN KEY (account_id) - REFERENCES accounts (id) - ON UPDATE CASCADE - ON DELETE RESTRICT -); - -CREATE TABLE transaction_categorisations( - id INTEGER NOT NULL PRIMARY KEY, - description TEXT NOT NULL, - quantity INTEGER NOT NULL, - transaction_id INTEGER NOT NULL, - category_id INTEGER NOT NULL, - FOREIGN KEY (transaction_id) - REFERENCES transactions (id) - ON UPDATE CASCADE - ON DELETE RESTRICT, - FOREIGN KEY (category_id) - REFERENCES categories (id) - ON UPDATE CASCADE - ON DELETE RESTRICT -); diff --git a/backend/app/Cargo.lock b/backend/app/Cargo.lock index f453ebe..0f6e6d4 100644 --- a/backend/app/Cargo.lock +++ b/backend/app/Cargo.lock @@ -138,6 +138,7 @@ checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" name = "budgeting_app_backend" version = "0.1.0" dependencies = [ + "budgeting_app_core", "cfg-if", "chrono", "diesel", @@ -146,6 +147,15 @@ dependencies = [ "rocket", "rocket_sync_db_pools", "rusqlite", +] + +[[package]] +name = "budgeting_app_core" +version = "0.1.0" +dependencies = [ + "chrono", + "diesel", + "diesel_migrations", "serde", ] diff --git a/backend/app/Cargo.toml b/backend/app/Cargo.toml index af1c8ef..69e1ed1 100644 --- a/backend/app/Cargo.toml +++ b/backend/app/Cargo.toml @@ -4,6 +4,7 @@ version = "0.1.0" edition = "2021" [dependencies] +budgeting_app_core = { path = "../core" } chrono = { version = "0.4.38", features = ["serde"] } cfg-if = "1.0.0" diesel = { version = "2.2.0", features = ["sqlite"] } @@ -12,4 +13,3 @@ dotenvy = "0.15.7" rocket = { version = "0.5.1", features = ["json"] } rocket_sync_db_pools = { version = "0.1.0", features = ["diesel_sqlite_pool"] } rusqlite = { version = "0.32.1", features = ["bundled"] } -serde = "1.0.209" diff --git a/backend/app/Rocket.toml b/backend/app/Rocket.toml index bb43355..088d632 100644 --- a/backend/app/Rocket.toml +++ b/backend/app/Rocket.toml @@ -1,2 +1,2 @@ [global.databases] -user_data = { url = "/home/joeac/src/budgeting-app/backend/user_data.sqlite" } +user_data = { url = "/home/joeac/src/budgeting-app/backend/core/user_data.sqlite" } diff --git a/backend/app/src/main.rs b/backend/app/src/main.rs index c3e02e5..152f958 100644 --- a/backend/app/src/main.rs +++ b/backend/app/src/main.rs @@ -1,6 +1,6 @@ +use budgeting_app_core::MIGRATIONS; use diesel::SqliteConnection; use diesel_migrations::MigrationHarness; -use diesel_migrations::{embed_migrations, EmbeddedMigrations}; use rocket::fairing::AdHoc; use rocket::http::ContentType; use rocket::{launch, routes, Build, Rocket}; @@ -8,14 +8,10 @@ use rocket::{launch, routes, Build, Rocket}; use crate::databases::UserDataDb; pub mod databases; -pub mod models; pub mod queries; pub mod routes; -pub mod schema; pub mod utils; -pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); - fn run_database_migrations(connection: &mut SqliteConnection) { connection .run_pending_migrations(MIGRATIONS) diff --git a/backend/app/src/models/account.rs b/backend/app/src/models/account.rs deleted file mode 100644 index c10bd5f..0000000 --- a/backend/app/src/models/account.rs +++ /dev/null @@ -1,15 +0,0 @@ -use diesel::prelude::*; -use serde::{Deserialize, Serialize}; - -use super::date_time_utc::DateTimeUtc; - -#[derive( - Insertable, Queryable, Identifiable, Selectable, Debug, PartialEq, Clone, Serialize, Deserialize, -)] -#[diesel(table_name = crate::schema::accounts)] -pub struct Account { - pub id: i32, - pub name: String, - pub opening_balance: i32, - pub opening_date: DateTimeUtc, -} diff --git a/backend/app/src/models/budget.rs b/backend/app/src/models/budget.rs deleted file mode 100644 index cc18f2e..0000000 --- a/backend/app/src/models/budget.rs +++ /dev/null @@ -1,44 +0,0 @@ -use serde::Serialize; - -use super::budget_update::BudgetUpdate; - -#[derive(Debug, Eq, PartialEq, Serialize)] -pub struct Budget { - pub quantity: i32, - pub period: i32, -} - -impl From<&BudgetUpdate> for Budget { - fn from(budget_update: &BudgetUpdate) -> Self { - Self { - quantity: budget_update.new_budget, - period: budget_update.new_period, - } - } -} - -#[cfg(test)] -mod test { - use chrono::Utc; - - use crate::models::date_time_utc::DateTimeUtc; - - use super::*; - - #[test] - fn when_from_budget_update_then_returns_budget() { - let quantity = 2; - let period = 3; - let budget_update = BudgetUpdate { - id: 0, - category_id: 1, - date: DateTimeUtc::new(Utc::now()), - new_budget: quantity, - new_period: period, - }; - - let budget = Budget::from(&budget_update); - - assert_eq!(budget, Budget { quantity, period }); - } -} diff --git a/backend/app/src/models/budget_update.rs b/backend/app/src/models/budget_update.rs deleted file mode 100644 index c63e957..0000000 --- a/backend/app/src/models/budget_update.rs +++ /dev/null @@ -1,17 +0,0 @@ -use diesel::prelude::*; -use serde::Serialize; - -use crate::models::category::Category; - -use super::date_time_utc::DateTimeUtc; - -#[derive(Clone, Serialize, Queryable, Identifiable, Selectable, Associations, Debug, PartialEq)] -#[diesel(table_name = crate::schema::budget_updates)] -#[diesel(belongs_to(Category))] -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/app/src/models/category.rs b/backend/app/src/models/category.rs deleted file mode 100644 index 5cb2609..0000000 --- a/backend/app/src/models/category.rs +++ /dev/null @@ -1,9 +0,0 @@ -use diesel::prelude::*; -use serde::{Deserialize, Serialize}; - -#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq, Serialize, Deserialize)] -#[diesel(table_name = crate::schema::categories)] -pub struct Category { - pub id: i32, - pub name: String, -} diff --git a/backend/app/src/models/category_balance.rs b/backend/app/src/models/category_balance.rs deleted file mode 100644 index 796b40b..0000000 --- a/backend/app/src/models/category_balance.rs +++ /dev/null @@ -1,7 +0,0 @@ -use serde::Serialize; - -#[derive(Serialize)] -pub struct CategoryBalance { - pub category_id: i32, - pub balance: i32, -} diff --git a/backend/app/src/models/category_budget.rs b/backend/app/src/models/category_budget.rs deleted file mode 100644 index 9796261..0000000 --- a/backend/app/src/models/category_budget.rs +++ /dev/null @@ -1,9 +0,0 @@ -use serde::Serialize; - -use super::budget::Budget; - -#[derive(Serialize)] -pub struct CategoryBudget { - pub category_id: i32, - pub budget: Option, -} diff --git a/backend/app/src/models/category_transfer.rs b/backend/app/src/models/category_transfer.rs deleted file mode 100644 index 6cbda0e..0000000 --- a/backend/app/src/models/category_transfer.rs +++ /dev/null @@ -1,11 +0,0 @@ -use diesel::prelude::*; - -#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq)] -#[diesel(table_name = crate::schema::category_transfers)] -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/app/src/models/date_time_utc.rs b/backend/app/src/models/date_time_utc.rs deleted file mode 100644 index 03d5802..0000000 --- a/backend/app/src/models/date_time_utc.rs +++ /dev/null @@ -1,131 +0,0 @@ -use chrono::{DateTime, Datelike, Utc}; -use diesel::{ - backend::Backend, - deserialize::{self, FromSql, FromSqlRow}, - expression::AsExpression, - serialize::{self, 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); - -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 to_day_str(&self) -> String { - self.0 - .to_rfc3339() - .split_at_checked(10) - .map(|(day_str, _)| String::from(day_str)) - .unwrap_or_else(|| String::from("")) - } - - pub fn new(date_time_utc: DateTime) -> Self { - Self(date_time_utc) - } - - pub fn now() -> Self { - Self(Utc::now()) - } -} - -impl ToSql for DateTimeUtc { - fn to_sql<'b>( - &'b self, - out: &mut diesel::serialize::Output<'b, '_, Sqlite>, - ) -> serialize::Result { - out.set_value(self.to_day_str()); - Ok(diesel::serialize::IsNull::No) - } -} - -impl FromSql for DateTimeUtc { - fn from_sql(bytes: ::RawValue<'_>) -> deserialize::Result { - let str: String = FromSql::::from_sql(bytes)?; - let date_time = str.as_str().parse::>()?; - Ok(DateTimeUtc(date_time)) - } -} - -#[cfg(test)] -mod test { - use chrono::TimeZone; - - use super::*; - - #[test] - fn given_midnight_when_day_diff_next_midnight_less_1s_then_return_0() { - let start = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().to_utc()); - let end = DateTimeUtc::new( - Utc.with_ymd_and_hms(2000, 1, 1, 23, 59, 59) - .unwrap() - .to_utc(), - ); - - let day_diff = start.day_diff(&end); - let neg_day_diff = end.day_diff(&start); - - assert_eq!(day_diff, 0); - assert_eq!(day_diff, neg_day_diff); - } - - #[test] - fn given_midnight_when_day_diff_next_midnight_then_return_1() { - let start = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().to_utc()); - let end = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 2, 0, 0, 0).unwrap().to_utc()); - - let day_diff = start.day_diff(&end); - let neg_day_diff = end.day_diff(&start); - - assert_eq!(day_diff, 1); - assert_eq!(day_diff, neg_day_diff); - } - - #[test] - fn given_midnight_less_1s_when_day_diff_midnight_then_return_1() { - let start = DateTimeUtc::new( - Utc.with_ymd_and_hms(2000, 1, 1, 23, 59, 59) - .unwrap() - .to_utc(), - ); - let end = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 2, 0, 0, 0).unwrap().to_utc()); - - let day_diff = start.day_diff(&end); - let neg_day_diff = end.day_diff(&start); - - assert_eq!(day_diff, 1); - assert_eq!(day_diff, neg_day_diff); - } - - #[test] - fn given_23_nov_1970_when_to_day_str_then_return_iso_day_str() { - let date_time_utc = DateTimeUtc::new( - Utc.with_ymd_and_hms(1970, 11, 23, 0, 0, 0) - .unwrap() - .to_utc(), - ); - let day_str = date_time_utc.to_day_str(); - - assert_eq!(day_str, "1970-11-23"); - } -} diff --git a/backend/app/src/models/mod.rs b/backend/app/src/models/mod.rs deleted file mode 100644 index b790fcb..0000000 --- a/backend/app/src/models/mod.rs +++ /dev/null @@ -1,10 +0,0 @@ -pub mod account; -pub mod budget; -pub mod budget_update; -pub mod category; -pub mod category_balance; -pub mod category_budget; -pub mod category_transfer; -pub mod date_time_utc; -pub mod transaction; -pub mod transaction_categorisation; diff --git a/backend/app/src/models/transaction.rs b/backend/app/src/models/transaction.rs deleted file mode 100644 index 36f33f8..0000000 --- a/backend/app/src/models/transaction.rs +++ /dev/null @@ -1,17 +0,0 @@ -use diesel::prelude::*; - -use crate::models::account::Account; - -use super::date_time_utc::DateTimeUtc; - -#[derive(Queryable, Identifiable, Selectable, Associations, Debug, PartialEq)] -#[diesel(table_name = crate::schema::transactions)] -#[diesel(belongs_to(Account))] -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/app/src/models/transaction_categorisation.rs b/backend/app/src/models/transaction_categorisation.rs deleted file mode 100644 index d2867f1..0000000 --- a/backend/app/src/models/transaction_categorisation.rs +++ /dev/null @@ -1,11 +0,0 @@ -use diesel::prelude::*; - -#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq)] -#[diesel(table_name = crate::schema::transaction_categorisations)] -pub struct TransactionCategorisations { - pub id: i32, - pub description: String, - pub quantity: i32, - pub transaction_id: i32, - pub category_id: i32, -} diff --git a/backend/app/src/queries/accounts/get_all.rs b/backend/app/src/queries/accounts/get_all.rs index 12941ac..bc67f77 100644 --- a/backend/app/src/queries/accounts/get_all.rs +++ b/backend/app/src/queries/accounts/get_all.rs @@ -1,7 +1,6 @@ +use budgeting_app_core::{models::account::Account, schema::accounts::dsl::accounts}; use diesel::{result::Error, QueryDsl, RunQueryDsl, SelectableHelper, SqliteConnection}; -use crate::{models::account::Account, schema::accounts::dsl::accounts}; - pub fn get_all_accounts(connection: &mut SqliteConnection) -> Result, 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 index c20cf60..1d1a323 100644 --- a/backend/app/src/queries/accounts/insert.rs +++ b/backend/app/src/queries/accounts/insert.rs @@ -1,9 +1,10 @@ +use budgeting_app_core::{ + models::account::Account, + schema::accounts::dsl::accounts as accounts_table, +}; use diesel::prelude::*; use diesel::result::Error; -use crate::models::account::Account; -use crate::schema::accounts::dsl::accounts as accounts_table; - pub fn insert_account( accounts: &[Account], connection: &mut SqliteConnection, diff --git a/backend/app/src/queries/budget_updates/get_all.rs b/backend/app/src/queries/budget_updates/get_all.rs index 97f2f2f..e6e4b59 100644 --- a/backend/app/src/queries/budget_updates/get_all.rs +++ b/backend/app/src/queries/budget_updates/get_all.rs @@ -1,4 +1,4 @@ -use crate::{ +use budgeting_app_core::{ models::budget_update::BudgetUpdate, schema::budget_updates::dsl::budget_updates as budget_updates_table, }; diff --git a/backend/app/src/queries/budget_updates/get_first_with_date.rs b/backend/app/src/queries/budget_updates/get_first_with_date.rs index a2824be..3856557 100644 --- a/backend/app/src/queries/budget_updates/get_first_with_date.rs +++ b/backend/app/src/queries/budget_updates/get_first_with_date.rs @@ -1,4 +1,4 @@ -use crate::{ +use budgeting_app_core::{ models::budget_update::BudgetUpdate, schema::budget_updates::{ self as budget_updates_schema, dsl::budget_updates as budget_updates_table, diff --git a/backend/app/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..55012f5 100644 --- a/backend/app/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 @@ -1,4 +1,4 @@ -use crate::{ +use budgeting_app_core::{ models::date_time_utc::DateTimeUtc, schema::budget_updates::{ self as budget_updates_schema, dsl::budget_updates as budget_updates_table, diff --git a/backend/app/src/queries/categories/get_all.rs b/backend/app/src/queries/categories/get_all.rs index db5a073..b6ec756 100644 --- a/backend/app/src/queries/categories/get_all.rs +++ b/backend/app/src/queries/categories/get_all.rs @@ -1,4 +1,4 @@ -use crate::{models::category::Category, schema::categories::dsl::categories as categories_table}; +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, Error> { diff --git a/backend/app/src/queries/categories/get_all_ids.rs b/backend/app/src/queries/categories/get_all_ids.rs index 7c7c912..48d5e57 100644 --- a/backend/app/src/queries/categories/get_all_ids.rs +++ b/backend/app/src/queries/categories/get_all_ids.rs @@ -1,4 +1,4 @@ -use crate::schema::categories::{self as categories_schema, dsl::categories as categories_table}; +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, Error> { diff --git a/backend/app/src/queries/category_balances/get_all.rs b/backend/app/src/queries/category_balances/get_all.rs index 713fbeb..7ee474a 100644 --- a/backend/app/src/queries/category_balances/get_all.rs +++ b/backend/app/src/queries/category_balances/get_all.rs @@ -1,5 +1,5 @@ +use budgeting_app_core::models::category_balance::CategoryBalance; use crate::{ - models::category_balance::CategoryBalance, queries::{ budget_updates::get_all_budget_updates, categories::get_all_categories, diff --git a/backend/app/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..b71a5fb 100644 --- a/backend/app/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 @@ -1,11 +1,9 @@ -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 budgeting_app_core::models::{budget::Budget, category_budget::CategoryBudget}; +use crate::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}; diff --git a/backend/app/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..3c077aa 100644 --- a/backend/app/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 @@ -1,8 +1,8 @@ -use diesel::{dsl::sum, result::Error, QueryDsl, RunQueryDsl, SqliteConnection}; - -use crate::schema::category_transfers::{ - self as category_transfers_schema, dsl::category_transfers as category_transfers_table, +use budgeting_app_core::schema::category_transfers::{ + self as category_transfers_schema, + dsl::category_transfers as category_transfers_table, }; +use diesel::{dsl::sum, result::Error, QueryDsl, RunQueryDsl, SqliteConnection}; pub fn sum_category_transfer_quantity_per_from_category_id( connection: &mut SqliteConnection, diff --git a/backend/app/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..6e81d7d 100644 --- a/backend/app/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 @@ -1,8 +1,8 @@ -use diesel::{dsl::sum, result::Error, QueryDsl, RunQueryDsl, SqliteConnection}; - -use crate::schema::category_transfers::{ - self as category_transfers_schema, dsl::category_transfers as category_transfers_table, +use budgeting_app_core::schema::category_transfers::{ + self as category_transfers_schema, + dsl::category_transfers as category_transfers_table, }; +use diesel::{dsl::sum, result::Error, QueryDsl, RunQueryDsl, SqliteConnection}; pub fn sum_category_transfer_quantity_per_to_category_id( connection: &mut SqliteConnection, diff --git a/backend/app/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..90dc990 100644 --- a/backend/app/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs +++ b/backend/app/src/queries/transaction_categorisations/sum_quantity_per_category_id.rs @@ -1,4 +1,4 @@ -use crate::schema::transaction_categorisations::{ +use budgeting_app_core::schema::transaction_categorisations::{ self as transaction_categorisations_schema, dsl::transaction_categorisations as transaction_categorisations_table, }; diff --git a/backend/app/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 deleted file mode 100644 index e69de29..0000000 diff --git a/backend/app/src/routes/account.rs b/backend/app/src/routes/account.rs index 7bbdb4f..7c29233 100644 --- a/backend/app/src/routes/account.rs +++ b/backend/app/src/routes/account.rs @@ -1,19 +1,19 @@ +use budgeting_app_core::models::account::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::models::account::Account; 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>, Status> { +pub async fn get_all_accounts(user_data_db: UserDataDb) -> Result>, (Status, String)> { user_data_db .run(get_all_accounts_query) .await .map(Json) - .map_err(|_| Status { code: 500 }) + .map_err(|err| (Status::InternalServerError, err.to_string())) } #[derive(Responder)] diff --git a/backend/app/src/routes/balance.rs b/backend/app/src/routes/balance.rs index f423c90..642aff1 100644 --- a/backend/app/src/routes/balance.rs +++ b/backend/app/src/routes/balance.rs @@ -1,7 +1,8 @@ +use budgeting_app_core::models::category_balance::CategoryBalance; use rocket::{get, http::Status, serde::json::Json}; use crate::{ - databases::UserDataDb, models::category_balance::CategoryBalance, + databases::UserDataDb, queries::category_balances::get_all_category_balances as get_all_category_balances_query, }; diff --git a/backend/app/src/routes/budget.rs b/backend/app/src/routes/budget.rs index 67aae8e..64b9e06 100644 --- a/backend/app/src/routes/budget.rs +++ b/backend/app/src/routes/budget.rs @@ -1,9 +1,9 @@ +use budgeting_app_core::models::category_budget::CategoryBudget; use rocket::get; use rocket::http::Status; use rocket::serde::json::Json; use crate::databases::UserDataDb; -use crate::models::category_budget::CategoryBudget; use crate::queries; #[get("/budget/category?current")] diff --git a/backend/app/src/routes/budget_update.rs b/backend/app/src/routes/budget_update.rs index 7732c7b..6def31a 100644 --- a/backend/app/src/routes/budget_update.rs +++ b/backend/app/src/routes/budget_update.rs @@ -1,9 +1,9 @@ +use budgeting_app_core::models::budget_update::BudgetUpdate; use rocket::get; use rocket::http::Status; use rocket::serde::json::Json; use crate::databases::UserDataDb; -use crate::models::budget_update::BudgetUpdate; use crate::queries; #[get("/budget-update")] diff --git a/backend/app/src/routes/category.rs b/backend/app/src/routes/category.rs index 01fd8d1..43810e9 100644 --- a/backend/app/src/routes/category.rs +++ b/backend/app/src/routes/category.rs @@ -1,9 +1,9 @@ +use budgeting_app_core::models::category::Category; use rocket::get; use rocket::http::Status; use rocket::serde::json::Json; use crate::databases::UserDataDb; -use crate::models::category::Category; use crate::queries::categories::get_all_categories as get_all_categories_query; #[get("/category")] diff --git a/backend/app/src/schema.rs b/backend/app/src/schema.rs deleted file mode 100644 index 372f8f3..0000000 --- a/backend/app/src/schema.rs +++ /dev/null @@ -1,72 +0,0 @@ -// @generated automatically by Diesel CLI. - -diesel::table! { - accounts (id) { - id -> Integer, - name -> Text, - opening_balance -> Integer, - opening_date -> Text, - } -} - -diesel::table! { - budget_updates (id) { - id -> Integer, - category_id -> Integer, - date -> Text, - new_budget -> Integer, - new_period -> Integer, - } -} - -diesel::table! { - categories (id) { - id -> Integer, - name -> Text, - } -} - -diesel::table! { - category_transfers (id) { - id -> Integer, - description -> Text, - quantity -> Integer, - from_category_id -> Integer, - to_category_id -> Integer, - } -} - -diesel::table! { - transaction_categorisations (id) { - id -> Integer, - description -> Text, - quantity -> Integer, - transaction_id -> Integer, - category_id -> Integer, - } -} - -diesel::table! { - transactions (id) { - id -> Integer, - description -> Text, - payee -> Text, - quantity -> Integer, - date -> Text, - account_id -> Integer, - } -} - -diesel::joinable!(budget_updates -> categories (category_id)); -diesel::joinable!(transaction_categorisations -> categories (category_id)); -diesel::joinable!(transaction_categorisations -> transactions (transaction_id)); -diesel::joinable!(transactions -> accounts (account_id)); - -diesel::allow_tables_to_appear_in_same_query!( - accounts, - budget_updates, - categories, - category_transfers, - transaction_categorisations, - transactions, -); diff --git a/backend/app/src/utils/calculate_budgets_accrual.rs b/backend/app/src/utils/calculate_budgets_accrual.rs index f14260d..c0d3d3a 100644 --- a/backend/app/src/utils/calculate_budgets_accrual.rs +++ b/backend/app/src/utils/calculate_budgets_accrual.rs @@ -1,4 +1,8 @@ -use crate::models::{budget_update::BudgetUpdate, category::Category, date_time_utc::DateTimeUtc}; +use budgeting_app_core::models::{ + budget_update::BudgetUpdate, + category::Category, + date_time_utc::DateTimeUtc +}; pub fn calculate_budgets_accrual(category: &Category, budget_updates: &[BudgetUpdate]) -> i64 { let mut budget_updates = budget_updates @@ -26,11 +30,10 @@ pub fn calculate_budgets_accrual(category: &Category, budget_updates: &[BudgetUp #[cfg(test)] mod test { - use chrono::{Days, Timelike, Utc}; - - use crate::models::{ + use budgeting_app_core::models::{ budget_update::BudgetUpdate, category::Category, date_time_utc::DateTimeUtc, }; + use chrono::{Days, Timelike, Utc}; use super::calculate_budgets_accrual; diff --git a/backend/app/src/utils/calculate_category_balance.rs b/backend/app/src/utils/calculate_category_balance.rs index 3c1c62f..e1de57e 100644 --- a/backend/app/src/utils/calculate_category_balance.rs +++ b/backend/app/src/utils/calculate_category_balance.rs @@ -1,4 +1,7 @@ -use crate::models::{budget_update::BudgetUpdate, category::Category}; +use budgeting_app_core::models::{ + budget_update::BudgetUpdate, + category::Category +}; use super::{calculate_budgets_accrual, find_by_id_or}; @@ -19,12 +22,10 @@ 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::{ - models::{budget_update::BudgetUpdate, category::Category, date_time_utc::DateTimeUtc}, - utils::calculate_category_balance, - }; + use crate::utils::calculate_category_balance; #[test] fn when_no_data_then_returns_zero() { diff --git a/backend/core/2024-08-31-084439_initial_setup/down.sql b/backend/core/2024-08-31-084439_initial_setup/down.sql new file mode 100644 index 0000000..8cf4b1e --- /dev/null +++ b/backend/core/2024-08-31-084439_initial_setup/down.sql @@ -0,0 +1,6 @@ +DROP TABLE accounts; +DROP TABLE budget_updates; +DROP TABLE categories; +DROP TABLE transaction_categorisations; +DROP TABLE category_transfers; +DROP TABLE transactions; diff --git a/backend/core/2024-08-31-084439_initial_setup/up.sql b/backend/core/2024-08-31-084439_initial_setup/up.sql new file mode 100644 index 0000000..3584ae7 --- /dev/null +++ b/backend/core/2024-08-31-084439_initial_setup/up.sql @@ -0,0 +1,70 @@ +PRAGMA foreign_keys = ON; + +CREATE TABLE accounts( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + opening_balance INTEGER NOT NULL, + opening_date TEXT NOT NULL +); + +CREATE TABLE budget_updates( + id INTEGER NOT NULL PRIMARY KEY, + category_id INTEGER NOT NULL, + date TEXT NOT NULL, + new_budget INTEGER NOT NULL, + new_period INTEGER NOT NULL, + FOREIGN KEY (category_id) + REFERENCES categories (id) + ON UPDATE CASCADE + ON DELETE RESTRICT +); + +CREATE TABLE categories( + id INTEGER NOT NULL PRIMARY KEY, + name TEXT NOT NULL +); + +CREATE TABLE category_transfers( + id INTEGER NOT NULL PRIMARY KEY, + description TEXT NOT NULL, + quantity INTEGER NOT NULL, + from_category_id INTEGER NOT NULL, + to_category_id INTEGER NOT NULL, + FOREIGN KEY (from_category_id) + REFERENCES categories (id) + ON UPDATE CASCADE + ON DELETE RESTRICT, + FOREIGN KEY (to_category_id) + REFERENCES categories (id) + ON UPDATE CASCADE + ON DELETE RESTRICT +); + +CREATE TABLE transactions( + id INTEGER NOT NULL PRIMARY KEY, + description TEXT NOT NULL, + payee TEXT NOT NULL, + quantity INTEGER NOT NULL, + date TEXT NOT NULL, + account_id INTEGER NOT NULL, + FOREIGN KEY (account_id) + REFERENCES accounts (id) + ON UPDATE CASCADE + ON DELETE RESTRICT +); + +CREATE TABLE transaction_categorisations( + id INTEGER NOT NULL PRIMARY KEY, + description TEXT NOT NULL, + quantity INTEGER NOT NULL, + transaction_id INTEGER NOT NULL, + category_id INTEGER NOT NULL, + FOREIGN KEY (transaction_id) + REFERENCES transactions (id) + ON UPDATE CASCADE + ON DELETE RESTRICT, + FOREIGN KEY (category_id) + REFERENCES categories (id) + ON UPDATE CASCADE + ON DELETE RESTRICT +); diff --git a/backend/core/Cargo.lock b/backend/core/Cargo.lock new file mode 100644 index 0000000..0a4e0fd --- /dev/null +++ b/backend/core/Cargo.lock @@ -0,0 +1,628 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[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.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" + +[[package]] +name = "budgeting_app_core" +version = "0.1.0" +dependencies = [ + "chrono", + "diesel", + "diesel_migrations", + "serde", +] + +[[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.30" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b16803a61b81d9eabb7eae2588776c4c1e584b738ede45fdbb4c972cec1e9945" +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 = "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.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +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 = "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.72" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" +dependencies = [ + "wasm-bindgen", +] + +[[package]] +name = "libc" +version = "0.2.159" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" + +[[package]] +name = "libsqlite3-sys" +version = "0.30.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149" +dependencies = [ + "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" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "once_cell" +version = "1.20.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" + +[[package]] +name = "pkg-config" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2" + +[[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.87" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3e4daa0dcf6feba26f985457cdf104d4b4256fc5a09547140f3631bb076b19a" +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 = "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 = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + +[[package]] +name = "syn" +version = "2.0.79" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89132cd0bf050864e1d38dc3bbc07a0eb8e7530af26344d3d2bbbef83499f590" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "time" +version = "0.3.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" +dependencies = [ + "deranged", + "itoa", + "num-conv", + "powerfmt", + "serde", + "time-core", + "time-macros", +] + +[[package]] +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 = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" +dependencies = [ + "num-conv", + "time-core", +] + +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "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]] +name = "unicode-ident" +version = "1.0.13" +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.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" +dependencies = [ + "cfg-if", + "once_cell", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" +dependencies = [ + "proc-macro2", + "quote", + "syn", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.95" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" + +[[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" + +[[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/core/Cargo.toml b/backend/core/Cargo.toml new file mode 100644 index 0000000..09b1ebf --- /dev/null +++ b/backend/core/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "budgeting_app_core" +version = "0.1.0" +edition = "2021" + +[dependencies] +chrono = { version = "0.4.38", features = ["serde"] } +diesel = { version = "2.2.0", features = ["sqlite"] } +diesel_migrations = "2.2.0" +serde = { version = "1.0.209", features = ["derive"] } diff --git a/backend/core/src/lib.rs b/backend/core/src/lib.rs new file mode 100644 index 0000000..7a56560 --- /dev/null +++ b/backend/core/src/lib.rs @@ -0,0 +1,6 @@ +use diesel_migrations::{embed_migrations, EmbeddedMigrations}; + +pub mod models; +pub mod schema; + +pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("migrations"); diff --git a/backend/core/src/models/account.rs b/backend/core/src/models/account.rs new file mode 100644 index 0000000..c10bd5f --- /dev/null +++ b/backend/core/src/models/account.rs @@ -0,0 +1,15 @@ +use diesel::prelude::*; +use serde::{Deserialize, Serialize}; + +use super::date_time_utc::DateTimeUtc; + +#[derive( + Insertable, Queryable, Identifiable, Selectable, Debug, PartialEq, Clone, Serialize, Deserialize, +)] +#[diesel(table_name = crate::schema::accounts)] +pub struct Account { + pub id: i32, + pub name: String, + pub opening_balance: i32, + pub opening_date: DateTimeUtc, +} diff --git a/backend/core/src/models/budget.rs b/backend/core/src/models/budget.rs new file mode 100644 index 0000000..cc18f2e --- /dev/null +++ b/backend/core/src/models/budget.rs @@ -0,0 +1,44 @@ +use serde::Serialize; + +use super::budget_update::BudgetUpdate; + +#[derive(Debug, Eq, PartialEq, Serialize)] +pub struct Budget { + pub quantity: i32, + pub period: i32, +} + +impl From<&BudgetUpdate> for Budget { + fn from(budget_update: &BudgetUpdate) -> Self { + Self { + quantity: budget_update.new_budget, + period: budget_update.new_period, + } + } +} + +#[cfg(test)] +mod test { + use chrono::Utc; + + use crate::models::date_time_utc::DateTimeUtc; + + use super::*; + + #[test] + fn when_from_budget_update_then_returns_budget() { + let quantity = 2; + let period = 3; + let budget_update = BudgetUpdate { + id: 0, + category_id: 1, + date: DateTimeUtc::new(Utc::now()), + new_budget: quantity, + new_period: period, + }; + + let budget = Budget::from(&budget_update); + + assert_eq!(budget, Budget { quantity, period }); + } +} diff --git a/backend/core/src/models/budget_update.rs b/backend/core/src/models/budget_update.rs new file mode 100644 index 0000000..c63e957 --- /dev/null +++ b/backend/core/src/models/budget_update.rs @@ -0,0 +1,17 @@ +use diesel::prelude::*; +use serde::Serialize; + +use crate::models::category::Category; + +use super::date_time_utc::DateTimeUtc; + +#[derive(Clone, Serialize, Queryable, Identifiable, Selectable, Associations, Debug, PartialEq)] +#[diesel(table_name = crate::schema::budget_updates)] +#[diesel(belongs_to(Category))] +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/core/src/models/category.rs b/backend/core/src/models/category.rs new file mode 100644 index 0000000..5cb2609 --- /dev/null +++ b/backend/core/src/models/category.rs @@ -0,0 +1,9 @@ +use diesel::prelude::*; +use serde::{Deserialize, Serialize}; + +#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq, Serialize, Deserialize)] +#[diesel(table_name = crate::schema::categories)] +pub struct Category { + pub id: i32, + pub name: String, +} diff --git a/backend/core/src/models/category_balance.rs b/backend/core/src/models/category_balance.rs new file mode 100644 index 0000000..796b40b --- /dev/null +++ b/backend/core/src/models/category_balance.rs @@ -0,0 +1,7 @@ +use serde::Serialize; + +#[derive(Serialize)] +pub struct CategoryBalance { + pub category_id: i32, + pub balance: i32, +} diff --git a/backend/core/src/models/category_budget.rs b/backend/core/src/models/category_budget.rs new file mode 100644 index 0000000..9796261 --- /dev/null +++ b/backend/core/src/models/category_budget.rs @@ -0,0 +1,9 @@ +use serde::Serialize; + +use super::budget::Budget; + +#[derive(Serialize)] +pub struct CategoryBudget { + pub category_id: i32, + pub budget: Option, +} diff --git a/backend/core/src/models/category_transfer.rs b/backend/core/src/models/category_transfer.rs new file mode 100644 index 0000000..6cbda0e --- /dev/null +++ b/backend/core/src/models/category_transfer.rs @@ -0,0 +1,11 @@ +use diesel::prelude::*; + +#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq)] +#[diesel(table_name = crate::schema::category_transfers)] +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/core/src/models/date_time_utc.rs b/backend/core/src/models/date_time_utc.rs new file mode 100644 index 0000000..03d5802 --- /dev/null +++ b/backend/core/src/models/date_time_utc.rs @@ -0,0 +1,131 @@ +use chrono::{DateTime, Datelike, Utc}; +use diesel::{ + backend::Backend, + deserialize::{self, FromSql, FromSqlRow}, + expression::AsExpression, + serialize::{self, 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); + +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 to_day_str(&self) -> String { + self.0 + .to_rfc3339() + .split_at_checked(10) + .map(|(day_str, _)| String::from(day_str)) + .unwrap_or_else(|| String::from("")) + } + + pub fn new(date_time_utc: DateTime) -> Self { + Self(date_time_utc) + } + + pub fn now() -> Self { + Self(Utc::now()) + } +} + +impl ToSql for DateTimeUtc { + fn to_sql<'b>( + &'b self, + out: &mut diesel::serialize::Output<'b, '_, Sqlite>, + ) -> serialize::Result { + out.set_value(self.to_day_str()); + Ok(diesel::serialize::IsNull::No) + } +} + +impl FromSql for DateTimeUtc { + fn from_sql(bytes: ::RawValue<'_>) -> deserialize::Result { + let str: String = FromSql::::from_sql(bytes)?; + let date_time = str.as_str().parse::>()?; + Ok(DateTimeUtc(date_time)) + } +} + +#[cfg(test)] +mod test { + use chrono::TimeZone; + + use super::*; + + #[test] + fn given_midnight_when_day_diff_next_midnight_less_1s_then_return_0() { + let start = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().to_utc()); + let end = DateTimeUtc::new( + Utc.with_ymd_and_hms(2000, 1, 1, 23, 59, 59) + .unwrap() + .to_utc(), + ); + + let day_diff = start.day_diff(&end); + let neg_day_diff = end.day_diff(&start); + + assert_eq!(day_diff, 0); + assert_eq!(day_diff, neg_day_diff); + } + + #[test] + fn given_midnight_when_day_diff_next_midnight_then_return_1() { + let start = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 1, 0, 0, 0).unwrap().to_utc()); + let end = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 2, 0, 0, 0).unwrap().to_utc()); + + let day_diff = start.day_diff(&end); + let neg_day_diff = end.day_diff(&start); + + assert_eq!(day_diff, 1); + assert_eq!(day_diff, neg_day_diff); + } + + #[test] + fn given_midnight_less_1s_when_day_diff_midnight_then_return_1() { + let start = DateTimeUtc::new( + Utc.with_ymd_and_hms(2000, 1, 1, 23, 59, 59) + .unwrap() + .to_utc(), + ); + let end = DateTimeUtc::new(Utc.with_ymd_and_hms(2000, 1, 2, 0, 0, 0).unwrap().to_utc()); + + let day_diff = start.day_diff(&end); + let neg_day_diff = end.day_diff(&start); + + assert_eq!(day_diff, 1); + assert_eq!(day_diff, neg_day_diff); + } + + #[test] + fn given_23_nov_1970_when_to_day_str_then_return_iso_day_str() { + let date_time_utc = DateTimeUtc::new( + Utc.with_ymd_and_hms(1970, 11, 23, 0, 0, 0) + .unwrap() + .to_utc(), + ); + let day_str = date_time_utc.to_day_str(); + + assert_eq!(day_str, "1970-11-23"); + } +} diff --git a/backend/core/src/models/mod.rs b/backend/core/src/models/mod.rs new file mode 100644 index 0000000..b790fcb --- /dev/null +++ b/backend/core/src/models/mod.rs @@ -0,0 +1,10 @@ +pub mod account; +pub mod budget; +pub mod budget_update; +pub mod category; +pub mod category_balance; +pub mod category_budget; +pub mod category_transfer; +pub mod date_time_utc; +pub mod transaction; +pub mod transaction_categorisation; diff --git a/backend/core/src/models/transaction.rs b/backend/core/src/models/transaction.rs new file mode 100644 index 0000000..36f33f8 --- /dev/null +++ b/backend/core/src/models/transaction.rs @@ -0,0 +1,17 @@ +use diesel::prelude::*; + +use crate::models::account::Account; + +use super::date_time_utc::DateTimeUtc; + +#[derive(Queryable, Identifiable, Selectable, Associations, Debug, PartialEq)] +#[diesel(table_name = crate::schema::transactions)] +#[diesel(belongs_to(Account))] +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/core/src/models/transaction_categorisation.rs b/backend/core/src/models/transaction_categorisation.rs new file mode 100644 index 0000000..60c2b98 --- /dev/null +++ b/backend/core/src/models/transaction_categorisation.rs @@ -0,0 +1,11 @@ +use diesel::prelude::*; + +#[derive(Queryable, Identifiable, Selectable, Debug, PartialEq)] +#[diesel(table_name = crate::schema::transaction_categorisations)] +pub struct TransactionCategorisation { + pub id: i32, + pub description: String, + pub quantity: i32, + pub transaction_id: i32, + pub category_id: i32, +} diff --git a/backend/core/src/schema.rs b/backend/core/src/schema.rs new file mode 100644 index 0000000..372f8f3 --- /dev/null +++ b/backend/core/src/schema.rs @@ -0,0 +1,72 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + accounts (id) { + id -> Integer, + name -> Text, + opening_balance -> Integer, + opening_date -> Text, + } +} + +diesel::table! { + budget_updates (id) { + id -> Integer, + category_id -> Integer, + date -> Text, + new_budget -> Integer, + new_period -> Integer, + } +} + +diesel::table! { + categories (id) { + id -> Integer, + name -> Text, + } +} + +diesel::table! { + category_transfers (id) { + id -> Integer, + description -> Text, + quantity -> Integer, + from_category_id -> Integer, + to_category_id -> Integer, + } +} + +diesel::table! { + transaction_categorisations (id) { + id -> Integer, + description -> Text, + quantity -> Integer, + transaction_id -> Integer, + category_id -> Integer, + } +} + +diesel::table! { + transactions (id) { + id -> Integer, + description -> Text, + payee -> Text, + quantity -> Integer, + date -> Text, + account_id -> Integer, + } +} + +diesel::joinable!(budget_updates -> categories (category_id)); +diesel::joinable!(transaction_categorisations -> categories (category_id)); +diesel::joinable!(transaction_categorisations -> transactions (transaction_id)); +diesel::joinable!(transactions -> accounts (account_id)); + +diesel::allow_tables_to_appear_in_same_query!( + accounts, + budget_updates, + categories, + category_transfers, + transaction_categorisations, + transactions, +); -- cgit v1.2.3