diff --git a/src/data_structs.rs b/src/data_structs.rs index 08660a0..42bab50 100644 --- a/src/data_structs.rs +++ b/src/data_structs.rs @@ -61,30 +61,28 @@ impl Display for Months { } } -impl TryInto for Months { - type Error = &'static str; - - fn try_into(self) -> Result { - match self { - Self::January => Ok(1), - Self::February => Ok(2), - Self::March => Ok(3), - Self::April => Ok(4), - Self::May => Ok(5), - Self::June => Ok(6), - Self::July => Ok(7), - Self::August => Ok(8), - Self::September => Ok(9), - Self::October => Ok(10), - Self::November => Ok(11), - Self::December => Ok(12), +impl From for u32 { + fn from(value: Months) -> Self { + match value { + Months::January => 1, + Months::February => 2, + Months::March => 3, + Months::April => 4, + Months::May => 5, + Months::June => 6, + Months::July => 7, + Months::August => 8, + Months::September => 9, + Months::October => 10, + Months::November => 11, + Months::December => 12, } } } -impl Months { - pub const fn from_chrono_month(month: Month) -> Self { - match month { +impl From for Months { + fn from(value: chrono::Month) -> Self { + match value { Month::January => Self::January, Month::February => Self::February, Month::March => Self::March, diff --git a/src/main.rs b/src/main.rs index 2ea56d0..6cd1989 100644 --- a/src/main.rs +++ b/src/main.rs @@ -12,7 +12,7 @@ mod templates; /// Module containing time and crypto utility functions. mod util; -use crate::{auth::Backend, data_structs::Months}; +use crate::auth::Backend; use std::{net::SocketAddr, sync::Arc, time::Duration}; use anyhow::bail; diff --git a/src/schema.rs b/src/schema.rs index 42ce036..9d98d15 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,4 +1,3 @@ -use crate::Months; use std::{ fmt::{self, Display}, str::FromStr, @@ -66,16 +65,17 @@ pub struct Expense { #[derive(Deserialize, Debug)] /// `GetExpense` is a struct with the fields of an expense that can be retrieved. -/// Currently, only the month is supported and it is optional. -/// If no month is passed, all expenses are retrieved. pub struct GetExpense { #[serde(deserialize_with = "empty_string_to_none")] - pub month: Option, + pub from: Option, + #[serde(deserialize_with = "empty_string_to_none")] + pub to: Option, } #[derive(Deserialize, Debug)] /// `UpdateExpense` is a struct with the fields of an expense that can be updated. /// All fields are optional. +/// This is part of the contract of the Data API. pub struct UpdateExpenseApi { pub description: Option, pub price: Option, @@ -84,10 +84,20 @@ pub struct UpdateExpenseApi { pub date: Option, } -#[derive(Deserialize, Debug, Default)] +#[derive(Deserialize, Debug)] +pub struct CreateExpense { + pub description: String, + pub price: f32, + pub category: ExpenseCategory, + pub is_essential: bool, + pub date: NaiveDate, +} + +#[derive(Deserialize, Debug)] /// `UpdateExpense` is a struct with the fields of an expense that can be updated. /// All fields are optional. -pub struct UpdateExpense { +/// This is part of the contract of the Hypermedia API. +pub struct UpdateExpenseHypr { pub description: Option, #[serde(deserialize_with = "de_string_to_option_f32")] pub price: Option, @@ -101,11 +111,10 @@ pub struct UpdateExpense { } /// Function to set the default value for `is_essential` in `UpdateExpense` to be Some(false). -#[allow(clippy::unnecessary_wraps)] -//#[allow( -// clippy::unnecessary_wraps, -// reason = "Needs to return option for custom deserializer" -//)] +#[expect( + clippy::unnecessary_wraps, + reason = "Needs to return option for custom deserializer" +)] const fn default_is_essential_to_false() -> Option { return Some(false); } @@ -130,7 +139,7 @@ where /// If the string is empty, returns None. /// If the string is a valid month, returns Some(Months). /// If the string is not a valid month, returns an error. -fn empty_string_to_none<'de, D>(deserializer: D) -> Result, D::Error> +fn empty_string_to_none<'de, D>(deserializer: D) -> Result, D::Error> where D: serde::Deserializer<'de>, { @@ -138,7 +147,9 @@ where if str.is_empty() { return Ok(None); } - return Ok(Some(Months::from_str(&str).map_err(de::Error::custom)?)); + return Ok(Some( + NaiveDate::parse_from_str(&str, "%Y-%m-%d").map_err(de::Error::custom)?, + )); } /// Deserialization serde function that parses a f32 from a string. diff --git a/src/templates.rs b/src/templates.rs index 009bec2..eb421a0 100644 --- a/src/templates.rs +++ b/src/templates.rs @@ -1,12 +1,11 @@ use crate::{ - data_structs::{Months, MonthsIter}, schema::{ExpenseCategory, ExpenseCategoryIter}, util::{add_csp_to_response, generate_otp_token}, }; use askama_axum::{IntoResponse, Template}; use axum::{body::Body, http::Response}; -use chrono::{Datelike, Month, NaiveDate, Utc}; +use chrono::NaiveDate; use strum::IntoEnumIterator; use uuid::Uuid; @@ -14,12 +13,8 @@ use uuid::Uuid; #[template(path = "expenses.html")] /// The askama template for the expenses page. pub struct ExpensesTemplate { - /// The current month to be displayed in English in the dropdown. - pub current_month: Months, /// The expense types to be displayed in the dropdown. pub expense_categories: ExpenseCategoryIter, - /// The months to be displayed in the dropdown. - pub months: MonthsIter, /// The username of the logged in user. pub username: String, /// CSP nonce @@ -29,19 +24,7 @@ pub struct ExpensesTemplate { impl Default for ExpensesTemplate { fn default() -> Self { return Self { - current_month: { - Months::from_chrono_month( - Month::try_from(u8::try_from(Utc::now().month()).unwrap_or_else(|_| { - tracing::error!( - "Failed to convert chrono month to u8, defaulting to 1(January)" - ); - return 1; - })) - .unwrap_or(Month::January), - ) - }, expense_categories: ExpenseCategory::iter(), - months: Months::iter(), username: String::new(), nonce: String::new(), }; @@ -56,9 +39,7 @@ impl ExpensesTemplate { let nonce_str = format!("'nonce-{nonce}'"); let mut response = Self { - current_month: self.current_month, expense_categories: self.expense_categories, - months: self.months, username: self.username, nonce, } diff --git a/src/util.rs b/src/util.rs index 9e6379c..35ff1f0 100644 --- a/src/util.rs +++ b/src/util.rs @@ -1,67 +1,7 @@ -use crate::data_structs::Months; - use axum::{body::Body, http::Response}; use axum_helmet::ContentSecurityPolicy; -use chrono::{Datelike, NaiveDate, Utc}; use rand::{distributions::Alphanumeric, thread_rng, Rng}; -/// Returns the first day of a month passed as an unsigned integer as a `NaiveDate`. -/// Example: `get_first_day_from_month(1)` returns 2023-01-01. -pub fn get_first_day_from_month(month: u32) -> Option { - return Utc::now() - .with_day(1) - .and_then(|date| return date.naive_utc().date().with_month(month)); -} - -/// If a month is passed, as a Some(Months), returns the first day of that month as a `NaiveDate`. -/// If None is passed, returns None. -/// Months is an enum with the months of the year. -pub fn get_first_day_from_month_or_none( - option_month: Option, -) -> Result, &'static str> { - let Some(month) = option_month else { - return Ok(None); - }; - let month_n: u32 = month - .try_into() - .map_err(|_ignore| return "Could not convert month to u32.")?; - match get_first_day_from_month(month_n) { - Some(date) => return Ok(Some(date)), - None => return Err("Could not get first day from month."), - } -} - -/// Returns the last day of a month passed as an unsigned integer as a `NaiveDate`. -/// Example: `get_last_day_from_month(1)` returns 2023-01-31. -pub fn get_last_day_from_month(month: u32) -> Option { - let first_day_of_month = get_first_day_from_month(month)?; - - if first_day_of_month.month() == 12 { - return first_day_of_month.with_day(31); - } - return first_day_of_month - .with_month(month.checked_add(1)?) - .and_then(|date| return date.checked_sub_days(chrono::Days::new(1))); -} - -/// If a month is passed, as a Some(Months), returns the last day of that month as a `NaiveDate`. -/// If None is passed, returns None. -/// Months is an enum with the months of the year. -pub fn get_last_day_from_month_or_none( - option_month: Option, -) -> Result, &'static str> { - let Some(month) = option_month else { - return Ok(None); - }; - let month_n: u32 = month - .try_into() - .map_err(|_ignore| return "Could not convert month to u32.")?; - match get_last_day_from_month(month_n) { - Some(date) => return Ok(Some(date)), - None => return Err("Could not get last day from month, maybe it underflowed?"), - } -} - /// Generates a random string of 128 characters to be used as an email verification token. pub fn generate_verification_token() -> String { return thread_rng() diff --git a/templates/expenses.html b/templates/expenses.html index e7875f7..af6b3b5 100644 --- a/templates/expenses.html +++ b/templates/expenses.html @@ -28,18 +28,15 @@

{{ username }}'s Expenses

-
+
- + + +
@@ -47,8 +44,8 @@

{{ username }}'s Expenses

@@ -76,8 +73,8 @@

{{ username }}'s Expenses