diff options
| author | Joe Carstairs <me@joeac.net> | 2024-10-16 18:22:55 +0100 |
|---|---|---|
| committer | Joe Carstairs <me@joeac.net> | 2024-10-17 06:55:02 +0100 |
| commit | 7adaace61a4fe983eba5ded3aaaf624ce6fb9014 (patch) | |
| tree | 6e0697f9dbb6e249c60af5a92479a86316c55221 /backend/core/src | |
| parent | 27b95c9186bd45edd4c982a7cc89cf69cf2c5d46 (diff) | |
Refactors some backend stuff to core lib
Diffstat (limited to 'backend/core/src')
| -rw-r--r-- | backend/core/src/lib.rs | 6 | ||||
| -rw-r--r-- | backend/core/src/models/account.rs | 15 | ||||
| -rw-r--r-- | backend/core/src/models/budget.rs | 44 | ||||
| -rw-r--r-- | backend/core/src/models/budget_update.rs | 17 | ||||
| -rw-r--r-- | backend/core/src/models/category.rs | 9 | ||||
| -rw-r--r-- | backend/core/src/models/category_balance.rs | 7 | ||||
| -rw-r--r-- | backend/core/src/models/category_budget.rs | 9 | ||||
| -rw-r--r-- | backend/core/src/models/category_transfer.rs | 11 | ||||
| -rw-r--r-- | backend/core/src/models/date_time_utc.rs | 131 | ||||
| -rw-r--r-- | backend/core/src/models/mod.rs | 10 | ||||
| -rw-r--r-- | backend/core/src/models/transaction.rs | 17 | ||||
| -rw-r--r-- | backend/core/src/models/transaction_categorisation.rs | 11 | ||||
| -rw-r--r-- | backend/core/src/schema.rs | 72 |
13 files changed, 359 insertions, 0 deletions
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<Budget>, +} 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<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 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<Utc>) -> Self { + Self(date_time_utc) + } + + pub fn now() -> Self { + Self(Utc::now()) + } +} + +impl ToSql<Text, Sqlite> 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<Text, Sqlite> for DateTimeUtc { + fn from_sql(bytes: <Sqlite as Backend>::RawValue<'_>) -> deserialize::Result<DateTimeUtc> { + let str: String = FromSql::<Text, Sqlite>::from_sql(bytes)?; + let date_time = str.as_str().parse::<DateTime<Utc>>()?; + 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, +); |
