Migrate ingredients
This commit is contained in:
parent
faeaf43f80
commit
129c3c59a4
2 changed files with 247 additions and 0 deletions
245
src/app/ingredients.rs
Normal file
245
src/app/ingredients.rs
Normal file
|
|
@ -0,0 +1,245 @@
|
||||||
|
use axum::{
|
||||||
|
extract::{Path, State},
|
||||||
|
response::Redirect,
|
||||||
|
routing::{get, post},
|
||||||
|
Form, Router,
|
||||||
|
};
|
||||||
|
use maud::Markup;
|
||||||
|
use sea_orm::{prelude::*, ActiveValue, DatabaseConnection, IntoActiveModel, QueryOrder};
|
||||||
|
use serde::Deserialize;
|
||||||
|
|
||||||
|
use crate::entity::{ingredient, prelude::*};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
confirm_danger_modal, error_alert,
|
||||||
|
household::CurrentHousehold,
|
||||||
|
sidebar::{sidebar, SidebarLocation},
|
||||||
|
AppState, AuthenticatedUser, RouteError,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub(super) fn routes() -> Router<AppState> {
|
||||||
|
Router::new()
|
||||||
|
.route("/", get(ingredients))
|
||||||
|
.route("/add", post(add_ingredient))
|
||||||
|
.route("/delete/:ig", post(delete_ingredient))
|
||||||
|
.route("/edit/:ig", post(edit_ingredient))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn edit_ingredient_modal(id: &str, ingredient: &ingredient::Model) -> Markup {
|
||||||
|
maud::html! {
|
||||||
|
.modal.fade #(id) tabindex="-1" aria-labelledby={(id) "Label"} aria-hidden="true" {
|
||||||
|
.modal-dialog.modal-dialog-centered {
|
||||||
|
.modal-content {
|
||||||
|
.modal-header {
|
||||||
|
h1 .modal-title."fs-5" #{(id) "Label"} { "Edit ingredient " (ingredient.name) }
|
||||||
|
input
|
||||||
|
type="reset"
|
||||||
|
form={(id) "Form"}
|
||||||
|
.btn-close
|
||||||
|
data-bs-dismiss="modal"
|
||||||
|
aria-label="Close"
|
||||||
|
value="";
|
||||||
|
}
|
||||||
|
.modal-body {
|
||||||
|
form #{(id) "Form"} method="post" action={"/ingredients/edit/" (ingredient.id)} {
|
||||||
|
.form-floating {
|
||||||
|
input .form-control
|
||||||
|
#{(id) "Name"}
|
||||||
|
placeholder="Ingrendient name"
|
||||||
|
name="name"
|
||||||
|
value=(ingredient.name);
|
||||||
|
label for={(id) "Name"} { "Ingredient name" }
|
||||||
|
}
|
||||||
|
.form-floating {
|
||||||
|
input .form-control
|
||||||
|
#{(id) "Unit"}
|
||||||
|
placeholder="Ingrendient unit"
|
||||||
|
name="unit"
|
||||||
|
value=(ingredient.unit.as_deref().unwrap_or_default());
|
||||||
|
label for={(id) "Unit"} { "Ingredient unit" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.modal-footer {
|
||||||
|
input type="reset" form={(id) "Form"} .btn.btn-danger data-bs-dismiss="modal" value="Cancel";
|
||||||
|
input type="submit" form={(id) "Form"} .btn.btn-primary data-bs-dismiss="modal" value="Edit";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render_ingredients(error: Option<String>, ingredient_list: &[ingredient::Model]) -> Markup {
|
||||||
|
maud::html! {
|
||||||
|
.d-flex.align-items-center.justify-content-center."w-100" {
|
||||||
|
.container.text-center.rounded.border."pt-2"."m-2" {
|
||||||
|
(error_alert(error))
|
||||||
|
form method="post" action="/ingredients/add" {
|
||||||
|
.form-floating {
|
||||||
|
input .form-control name="name" id="newIgName" placeholder="Ingredient name";
|
||||||
|
label for="newIgName" { "Ingredient name" }
|
||||||
|
}
|
||||||
|
.form-floating."my-1" {
|
||||||
|
input .form-control name="unit" id="newIgUnit" placeholder="Ingredient unit";
|
||||||
|
label for="newIgUnit" { "Ingredient unit" }
|
||||||
|
}
|
||||||
|
input type="submit" .btn.btn-primary."mt-2" value="Add Ingredient";
|
||||||
|
}
|
||||||
|
hr;
|
||||||
|
ul .list-group.list-group-flush.text-start {
|
||||||
|
@for ig in ingredient_list {
|
||||||
|
li .list-group-item.d-flex.align-items-center {
|
||||||
|
@let delete_id = format!("deleteIg{}", ig.id);
|
||||||
|
@let add_id = format!("addIg{}", ig.id);
|
||||||
|
p .flex-fill.m-auto { (ig.name) @if let Some(unit) = &ig.unit { {" (unit: " (unit) ")"}} }
|
||||||
|
button .btn.btn-primary
|
||||||
|
type="button" data-bs-toggle="modal" data-bs-target={"#" (add_id)} {
|
||||||
|
i .bi-pencil-fill {}
|
||||||
|
}
|
||||||
|
(edit_ingredient_modal(&add_id, ig))
|
||||||
|
button .btn.btn-danger."ms-1"
|
||||||
|
type="button" data-bs-toggle="modal" data-bs-target={"#" (delete_id)} {
|
||||||
|
i .bi-trash-fill {}
|
||||||
|
}
|
||||||
|
(confirm_danger_modal(
|
||||||
|
&delete_id,
|
||||||
|
&format!("Are you sure you want to delete {}", ig.name),
|
||||||
|
&format!("/ingredients/delete/{}", ig.id),
|
||||||
|
"Delete ingredient",
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ingredients_view(
|
||||||
|
error: Option<String>,
|
||||||
|
household: &CurrentHousehold,
|
||||||
|
user: &AuthenticatedUser,
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
) -> Result<Markup, RouteError> {
|
||||||
|
let list = household
|
||||||
|
.0
|
||||||
|
.find_related(Ingredient)
|
||||||
|
.order_by_asc(ingredient::Column::Id)
|
||||||
|
.all(db)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
Ok(sidebar(
|
||||||
|
SidebarLocation::Ingredients,
|
||||||
|
household,
|
||||||
|
user,
|
||||||
|
render_ingredients(error, &list),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn ingredients(
|
||||||
|
state: State<AppState>,
|
||||||
|
user: AuthenticatedUser,
|
||||||
|
household: CurrentHousehold,
|
||||||
|
) -> Result<Markup, RouteError> {
|
||||||
|
ingredients_view(None, &household, &user, &state.db).await
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn do_ingredient_delete(
|
||||||
|
household: &CurrentHousehold,
|
||||||
|
db: &DatabaseConnection,
|
||||||
|
id: i64,
|
||||||
|
) -> Result<Option<String>, RouteError> {
|
||||||
|
if household
|
||||||
|
.0
|
||||||
|
.find_related(Ingredient)
|
||||||
|
.filter(ingredient::Column::Id.eq(id))
|
||||||
|
.count(db)
|
||||||
|
.await?
|
||||||
|
== 0
|
||||||
|
{
|
||||||
|
return Err(RouteError::RessourceNotFound);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maybe we are racing with another deletion
|
||||||
|
let ingredient = Ingredient::find_by_id(id)
|
||||||
|
.one(db)
|
||||||
|
.await?
|
||||||
|
.ok_or(RouteError::RessourceNotFound)?;
|
||||||
|
|
||||||
|
let recipe_count = ingredient.find_related(Recipe).count(db).await?;
|
||||||
|
if recipe_count != 0 {
|
||||||
|
return Ok(Some(format!(
|
||||||
|
"Could not delete {}, ingredient is used in {recipe_count} recipes",
|
||||||
|
ingredient.name
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
|
||||||
|
ingredient.delete(db).await?;
|
||||||
|
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn delete_ingredient(
|
||||||
|
state: State<AppState>,
|
||||||
|
user: AuthenticatedUser,
|
||||||
|
household: CurrentHousehold,
|
||||||
|
ig: Path<i64>,
|
||||||
|
) -> Result<Markup, RouteError> {
|
||||||
|
ingredients_view(
|
||||||
|
do_ingredient_delete(&household, &state.db, ig.0).await?,
|
||||||
|
&household,
|
||||||
|
&user,
|
||||||
|
&state.db,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct IngredientDesc {
|
||||||
|
name: String,
|
||||||
|
unit: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn edit_ingredient(
|
||||||
|
state: State<AppState>,
|
||||||
|
household: CurrentHousehold,
|
||||||
|
ig: Path<i64>,
|
||||||
|
form: Form<IngredientDesc>,
|
||||||
|
) -> Result<Redirect, RouteError> {
|
||||||
|
let ingredient = household
|
||||||
|
.0
|
||||||
|
.find_related(Ingredient)
|
||||||
|
.filter(ingredient::Column::Id.eq(ig.0))
|
||||||
|
.one(&state.db)
|
||||||
|
.await?
|
||||||
|
.ok_or(RouteError::RessourceNotFound)?;
|
||||||
|
|
||||||
|
let mut ingredient = ingredient.into_active_model();
|
||||||
|
ingredient.name = ActiveValue::Set(form.0.name);
|
||||||
|
ingredient.unit = ActiveValue::Set(match form.0.unit.is_empty() {
|
||||||
|
true => None,
|
||||||
|
false => Some(form.0.unit),
|
||||||
|
});
|
||||||
|
ingredient.update(&state.db).await?;
|
||||||
|
|
||||||
|
Ok(Redirect::to("/ingredients"))
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn add_ingredient(
|
||||||
|
state: State<AppState>,
|
||||||
|
household: CurrentHousehold,
|
||||||
|
form: Form<IngredientDesc>,
|
||||||
|
) -> Result<Redirect, RouteError> {
|
||||||
|
let mut ingredient = ingredient::ActiveModel::new();
|
||||||
|
ingredient.name = ActiveValue::Set(form.0.name);
|
||||||
|
ingredient.unit = ActiveValue::Set(match form.0.unit.is_empty() {
|
||||||
|
true => None,
|
||||||
|
false => Some(form.0.unit),
|
||||||
|
});
|
||||||
|
ingredient.household = ActiveValue::Set(household.0.id);
|
||||||
|
|
||||||
|
ingredient.insert(&state.db).await?;
|
||||||
|
|
||||||
|
Ok(Redirect::to("/ingredients"))
|
||||||
|
}
|
||||||
|
|
@ -23,6 +23,7 @@ use self::{household::CurrentHousehold, sidebar::SidebarLocation};
|
||||||
mod household;
|
mod household;
|
||||||
mod recipe;
|
mod recipe;
|
||||||
mod sidebar;
|
mod sidebar;
|
||||||
|
mod ingredients;
|
||||||
|
|
||||||
type AppState = Arc<crate::AppState>;
|
type AppState = Arc<crate::AppState>;
|
||||||
|
|
||||||
|
|
@ -372,5 +373,6 @@ pub(crate) fn router() -> Router<AppState> {
|
||||||
.route("/login/redirect/:id", get(oidc_login_finish))
|
.route("/login/redirect/:id", get(oidc_login_finish))
|
||||||
.nest("/household", household::routes())
|
.nest("/household", household::routes())
|
||||||
.nest("/recipe", recipe::routes())
|
.nest("/recipe", recipe::routes())
|
||||||
|
.nest("/ingredients", ingredients::routes())
|
||||||
.fallback_service(ServeDir::new(public).fallback((|| async { not_found() }).into_service()))
|
.fallback_service(ServeDir::new(public).fallback((|| async { not_found() }).into_service()))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue