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 recipe;
|
||||
mod sidebar;
|
||||
mod ingredients;
|
||||
|
||||
type AppState = Arc<crate::AppState>;
|
||||
|
||||
|
|
@ -372,5 +373,6 @@ pub(crate) fn router() -> Router<AppState> {
|
|||
.route("/login/redirect/:id", get(oidc_login_finish))
|
||||
.nest("/household", household::routes())
|
||||
.nest("/recipe", recipe::routes())
|
||||
.nest("/ingredients", ingredients::routes())
|
||||
.fallback_service(ServeDir::new(public).fallback((|| async { not_found() }).into_service()))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue