Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: fixed expense update. #6

Merged
merged 1 commit into from
Dec 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion src/constant.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ macro_rules! EDITABLE_TABLE_ROW {
<button class=\"btn btn-danger\" hx-get=\"/expenses/{}\">
Cancel
</button>
<button class=\"btn btn-danger\" hx-put=\"/expenses/{}\" hx-include=\"closest tr\">
<button class=\"btn btn-danger\" hx-put=\"/expenses/{}\" hx-ext=\"json-enc\" hx-include=\"closest tr\">
Save
</button>
</td>
Expand Down
120 changes: 81 additions & 39 deletions src/hypermedia_router.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate::{
constant::{EDITABLE_TABLE_ROW, TABLE_ROW},
schema::{Expense, ExpenseType, GetExpense},
util::{get_first_day_from_month, get_last_day_from_month},
schema::{Expense, ExpenseType, GetExpense, UpdateExpense},
util::{get_first_day_from_month_or_none, get_last_day_from_month_or_none},
AppState, ExpensesTemplate,
};
use std::sync::Arc;
Expand All @@ -11,7 +11,7 @@ use axum::{
extract::{Path, Query, State},
response::Html,
routing::get,
Router,
Json, Router,
};

pub fn hypermedia_router() -> Router<Arc<AppState>> {
Expand All @@ -32,30 +32,46 @@ pub async fn get_expenses(
State(shared_state): State<Arc<AppState>>,
Query(get_expense_input): Query<GetExpense>,
) -> impl IntoResponse {
let expenses: Vec<Expense> = match get_expense_input.month {
Some(month) => {
sqlx::query_as!(
Expense,
r#"SELECT id, description, price, expense_type as "expense_type: ExpenseType", is_essencial, date
FROM expenses WHERE date BETWEEN $1 AND $2 ORDER BY date ASC"#,
get_first_day_from_month(month.clone() as u32 + 1),
get_last_day_from_month(month as u32 + 1)
)
.fetch_all(&shared_state.pool)
.await
.unwrap()
}
None => {
sqlx::query_as!(
Expense,
r#"SELECT id, description, price, expense_type as "expense_type: ExpenseType", is_essencial, date
FROM expenses ORDER BY date ASC"#
)
.fetch_all(&shared_state.pool)
.await
.unwrap()
}
};
//let expenses: Vec<Expense> = match get_expense_input.month.clone() {
// Some(month) => {
// sqlx::query_as!(
// Expense,
// r#"SELECT id, description, price, expense_type as "expense_type: ExpenseType", is_essencial, date
// FROM expenses WHERE date BETWEEN $1 AND $2 ORDER BY date ASC"#,
// get_first_day_from_month(month.clone() as u32 + 1),
// get_last_day_from_month(month as u32 + 1)
// )
// .fetch_all(&shared_state.pool)
// .await
// .unwrap()
// }
// None => {
// sqlx::query_as!(
// Expense,
// r#"SELECT id, description, price, expense_type as "expense_type: ExpenseType", is_essencial, date
// FROM expenses ORDER BY date ASC"#
// )
// .fetch_all(&shared_state.pool)
// .await
// .unwrap()
// }
//};

let expenses = sqlx::query_as!(
Expense,
r#"SELECT id, description, price, expense_type as "expense_type: ExpenseType", is_essencial, date
FROM expenses
WHERE ((date >= $1) OR ($1 IS NULL))
AND ((date <= $2) OR ($2 IS NULL))
ORDER BY date ASC"#,
get_first_day_from_month_or_none(get_expense_input.month.clone()),
get_last_day_from_month_or_none(get_expense_input.month)
)
.fetch_all(&shared_state.pool)
.await
.unwrap();

tracing::info!("expenses: {:?}", expenses);

Html(
expenses
Expand All @@ -80,8 +96,12 @@ pub async fn edit_expense(
Path(id): Path<i32>,
State(shared_state): State<Arc<AppState>>,
) -> impl IntoResponse {
let expense: Expense = sqlx::query_as("SELECT * FROM expenses WHERE id = $1")
.bind(id)
let expense = sqlx::query_as!(
Expense,
r#"SELECT id, description, price, expense_type as "expense_type: ExpenseType", is_essencial, date
FROM expenses WHERE id = $1"#,
id
)
.fetch_one(&shared_state.pool)
.await
.unwrap();
Expand All @@ -102,11 +122,15 @@ pub async fn get_expense(
Path(id): Path<i32>,
State(shared_state): State<Arc<AppState>>,
) -> impl IntoResponse {
let expense: Expense = sqlx::query_as("SELECT * FROM expenses WHERE id = $1")
.bind(id)
.fetch_one(&shared_state.pool)
.await
.unwrap();
let expense = sqlx::query_as!(
Expense,
r#"SELECT id, description, price, expense_type as "expense_type: ExpenseType", is_essencial, date
FROM expenses WHERE id = $1"#,
id
)
.fetch_one(&shared_state.pool)
.await
.unwrap();

Html(format!(
TABLE_ROW!(),
Expand All @@ -122,12 +146,30 @@ pub async fn get_expense(
pub async fn update_expense(
Path(id): Path<i32>,
State(shared_state): State<Arc<AppState>>,
Json(update_expense): Json<UpdateExpense>,
) -> impl IntoResponse {
let expense: Expense = sqlx::query_as("SELECT * FROM expenses WHERE id = $1")
.bind(id)
.fetch_one(&shared_state.pool)
.await
.unwrap();
let expense = sqlx::query_as!(
Expense,
r#"
UPDATE expenses SET
description = COALESCE($1, description),
price = COALESCE($2, price),
expense_type = COALESCE($3 :: expense_type, expense_type),
is_essencial = COALESCE($4, is_essencial),
date = COALESCE($5, date)
WHERE id = $6
RETURNING id, description, price, expense_type as "expense_type: ExpenseType", is_essencial, date
"#,
update_expense.description,
update_expense.price,
update_expense.expense_type as Option<ExpenseType>,
update_expense.is_essencial,
update_expense.date,
id
)
.fetch_one(&shared_state.pool)
.await
.unwrap();

Html(format!(
TABLE_ROW!(),
Expand Down
26 changes: 24 additions & 2 deletions src/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use serde::{Deserialize, Serialize};
use sqlx::FromRow;
use strum::EnumIter;

#[derive(Serialize, Clone, EnumIter, Deserialize, sqlx::Type)]
#[derive(Debug, Serialize, Clone, EnumIter, Deserialize, sqlx::Type)]
#[sqlx(type_name = "expense_type", rename_all = "lowercase")]
pub enum ExpenseType {
Food,
Expand Down Expand Up @@ -46,7 +46,7 @@ impl Display for ExpenseType {
}
}

#[derive(FromRow, Serialize)]
#[derive(FromRow, Serialize, Debug)]
pub struct Expense {
pub id: i32,
pub description: String,
Expand All @@ -62,6 +62,16 @@ pub struct GetExpense {
pub month: Option<Months>,
}

#[derive(Deserialize, Debug)]
pub struct UpdateExpense {
pub description: Option<String>,
#[serde(deserialize_with = "de_string_to_option_f32")]
pub price: Option<f32>,
pub expense_type: Option<ExpenseType>,
pub is_essencial: Option<bool>,
pub date: Option<NaiveDate>,
}

fn empty_string_to_none<'de, D>(deserializer: D) -> Result<Option<Months>, D::Error>
where
D: serde::Deserializer<'de>,
Expand All @@ -73,3 +83,15 @@ where
Ok(Some(Months::from_str(&s).unwrap()))
}
}

fn de_string_to_option_f32<'de, D>(deserializer: D) -> Result<Option<f32>, D::Error>
where
D: serde::Deserializer<'de>,
{
let s: String = serde::Deserialize::deserialize(deserializer)?;
if s.is_empty() {
Ok(None)
} else {
Ok(Some(s.parse::<f32>().unwrap()))
}
}
24 changes: 24 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use chrono::{Datelike, NaiveDate, Utc};

use crate::data::Months;

pub fn get_first_day_from_month(month: u32) -> NaiveDate {
Utc::now()
.with_day(1)
Expand All @@ -10,6 +12,17 @@ pub fn get_first_day_from_month(month: u32) -> NaiveDate {
.unwrap()
}

pub fn get_first_day_from_month_or_none(month: Option<Months>) -> Option<NaiveDate> {
match month {
Some(month) => {
let day = Some(get_first_day_from_month(month as u32 + 1));
tracing::info!("first day: {:?}", day);
day
}
None => None,
}
}

pub fn get_last_day_from_month(month: u32) -> NaiveDate {
let first_day_of_month = get_first_day_from_month(month);

Expand All @@ -18,3 +31,14 @@ pub fn get_last_day_from_month(month: u32) -> NaiveDate {
}
first_day_of_month.with_month(month + 1).unwrap() - chrono::Duration::days(1)
}

pub fn get_last_day_from_month_or_none(month: Option<Months>) -> Option<NaiveDate> {
match month {
Some(month) => {
let day = Some(get_last_day_from_month(month as u32 + 1));
tracing::info!("last day: {:?}", day);
day
}
None => None,
}
}
1 change: 1 addition & 0 deletions templates/base.html
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
})
</script>
<script src="https://unpkg.com/htmx.org/dist/ext/response-targets.js"></script>
<script src="https://unpkg.com/htmx.org/dist/ext/json-enc.js"></script>
<script src="https://unpkg.com/[email protected]"></script>

<!-- Allow any inheriting page to extend head with additional assets -->
Expand Down
Loading