Recipe edit (WIP): Buttons to add steps and inrgedients
authorGreg Burri <greg.burri@gmail.com>
Fri, 27 Dec 2024 11:51:29 +0000 (12:51 +0100)
committerGreg Burri <greg.burri@gmail.com>
Fri, 27 Dec 2024 11:51:29 +0000 (12:51 +0100)
Cargo.lock
backend/sql/version_1.sql
backend/src/data/db/recipe.rs
backend/src/data/model.rs
backend/src/main.rs
backend/src/services/ron.rs
backend/templates/recipe_edit.html
common/src/ron_api.rs
frontend/src/handles.rs

index ca1f318..de94fa4 100644 (file)
@@ -391,9 +391,9 @@ checksum = "325918d6fe32f23b19878fe4b34794ae41fc19ddbe53b10571a4874d44ffd39b"
 
 [[package]]
 name = "cc"
-version = "1.2.5"
+version = "1.2.6"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c31a0499c1dc64f458ad13872de75c0eb7e3fdb0e67964610c914b034fc5956e"
+checksum = "8d6dbb628b8f8555f86d0323c2eb39e3ec81901f4b83e091db8a6a76d316a333"
 dependencies = [
  "shlex",
 ]
index 1efde53..48b063c 100644 (file)
@@ -121,7 +121,7 @@ CREATE TABLE [Ingredient] (
 
     [step_id] INTEGER NOT NULL,
 
-    [name] TEXT NOT NULL,
+    [name] TEXT NOT NULL DEFAULT '',
     [comment] TEXT NOT NULL DEFAULT '',
     [quantity_value] REAL,
     [quantity_unit] TEXT NOT NULL DEFAULT '',
index d112b12..e97c28b 100644 (file)
@@ -304,6 +304,23 @@ ORDER BY [name]
             .map_err(DBError::from)
     }
 
+    pub async fn add_recipe_step(&self, group_id: i64) -> Result<i64> {
+        let db_result = sqlx::query("INSERT INTO [Step] ([group_id]) VALUES ($1)")
+            .bind(group_id)
+            .execute(&self.pool)
+            .await?;
+        Ok(db_result.last_insert_rowid())
+    }
+
+    pub async fn rm_recipe_step(&self, step_id: i64) -> Result<()> {
+        sqlx::query("DELETE FROM [Step] WHERE [id] = $1")
+            .bind(step_id)
+            .execute(&self.pool)
+            .await
+            .map(|_| ())
+            .map_err(DBError::from)
+    }
+
     pub async fn set_step_action(&self, step_id: i64, action: &str) -> Result<()> {
         sqlx::query("UPDATE [Step] SET [action] = $2 WHERE [id] = $1")
             .bind(step_id)
@@ -314,6 +331,23 @@ ORDER BY [name]
             .map_err(DBError::from)
     }
 
+    pub async fn add_recipe_ingredient(&self, step_id: i64) -> Result<i64> {
+        let db_result = sqlx::query("INSERT INTO [Ingredient] ([step_id]) VALUES ($1)")
+            .bind(step_id)
+            .execute(&self.pool)
+            .await?;
+        Ok(db_result.last_insert_rowid())
+    }
+
+    pub async fn rm_recipe_ingredient(&self, ingredient_id: i64) -> Result<()> {
+        sqlx::query("DELETE FROM [Ingredient] WHERE [id] = $1")
+            .bind(ingredient_id)
+            .execute(&self.pool)
+            .await
+            .map(|_| ())
+            .map_err(DBError::from)
+    }
+
     pub async fn set_ingredient_name(&self, ingredient_id: i64, name: &str) -> Result<()> {
         sqlx::query("UPDATE [Ingredient] SET [name] = $2 WHERE [id] = $1")
             .bind(ingredient_id)
index 84bad3c..d837393 100644 (file)
@@ -58,6 +58,6 @@ pub struct Ingredient {
     pub id: i64,
     pub name: String,
     pub comment: String,
-    pub quantity_value: f64,
+    pub quantity_value: Option<f64>,
     pub quantity_unit: String,
 }
index 46ebe06..96f9066 100644 (file)
@@ -109,10 +109,20 @@ async fn main() {
             "/recipe/set_group_comment",
             put(services::ron::set_group_comment),
         )
+        .route("/recipe/add_step", post(services::ron::add_step))
+        .route("/recipe/remove_step", delete(services::ron::rm_step))
         .route(
             "/recipe/set_step_action",
             put(services::ron::set_step_action),
         )
+        .route(
+            "/recipe/add_ingredient",
+            post(services::ron::add_ingredient),
+        )
+        .route(
+            "/recipe/remove_ingredient",
+            delete(services::ron::rm_ingredient),
+        )
         .route(
             "/recipe/set_ingredient_name",
             put(services::ron::set_ingredient_name),
index 12c0f76..8f8ea96 100644 (file)
@@ -355,6 +355,32 @@ pub async fn set_group_comment(
     Ok(StatusCode::OK)
 }
 
+#[debug_handler]
+pub async fn add_step(
+    State(connection): State<db::Connection>,
+    Extension(user): Extension<Option<model::User>>,
+    ExtractRon(ron): ExtractRon<common::ron_api::AddRecipeStep>,
+) -> Result<impl IntoResponse> {
+    check_user_rights_recipe_group(&connection, &user, ron.group_id).await?;
+    let step_id = connection.add_recipe_step(ron.group_id).await?;
+
+    Ok(ron_response(
+        StatusCode::OK,
+        common::ron_api::AddRecipeStepResult { step_id },
+    ))
+}
+
+#[debug_handler]
+pub async fn rm_step(
+    State(connection): State<db::Connection>,
+    Extension(user): Extension<Option<model::User>>,
+    ExtractRon(ron): ExtractRon<common::ron_api::RemoveRecipeStep>,
+) -> Result<impl IntoResponse> {
+    check_user_rights_recipe_step(&connection, &user, ron.step_id).await?;
+    connection.rm_recipe_step(ron.step_id).await?;
+    Ok(StatusCode::OK)
+}
+
 #[debug_handler]
 pub async fn set_step_action(
     State(connection): State<db::Connection>,
@@ -366,6 +392,32 @@ pub async fn set_step_action(
     Ok(StatusCode::OK)
 }
 
+#[debug_handler]
+pub async fn add_ingredient(
+    State(connection): State<db::Connection>,
+    Extension(user): Extension<Option<model::User>>,
+    ExtractRon(ron): ExtractRon<common::ron_api::AddRecipeIngredient>,
+) -> Result<impl IntoResponse> {
+    check_user_rights_recipe_step(&connection, &user, ron.step_id).await?;
+    let ingredient_id = connection.add_recipe_ingredient(ron.step_id).await?;
+
+    Ok(ron_response(
+        StatusCode::OK,
+        common::ron_api::AddRecipeIngredientResult { ingredient_id },
+    ))
+}
+
+#[debug_handler]
+pub async fn rm_ingredient(
+    State(connection): State<db::Connection>,
+    Extension(user): Extension<Option<model::User>>,
+    ExtractRon(ron): ExtractRon<common::ron_api::RemoveRecipeIngredient>,
+) -> Result<impl IntoResponse> {
+    check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
+    connection.rm_recipe_ingredient(ron.ingredient_id).await?;
+    Ok(StatusCode::OK)
+}
+
 #[debug_handler]
 pub async fn set_ingredient_name(
     State(connection): State<db::Connection>,
index 8b3c888..5438d3c 100644 (file)
@@ -64,7 +64,7 @@
     <div id="groups-container">
 
     </div>
-    <input id="button-add-group" type="button" value="Add a group" />
+    <input id="input-add-group" type="button" value="Add a group" />
 
     <div id="hidden-templates">
         <div class="group">
@@ -78,7 +78,7 @@
 
             <div class="steps"></div>
 
-            <input class="button-add-step" type="button" value="Add a step" />
+            <input class="input-add-step" type="button" value="Add a step" />
         </div>
 
         <div class="step">
@@ -89,7 +89,7 @@
 
             <div class="ingredients"></div>
 
-            <input class="button-add-ingedient" type="button" value="Add an ingredient"/>
+            <input class="input-add-ingredient" type="button" value="Add an ingredient"/>
         </div>
 
         <div class="ingredient">
index 2b8b9cb..cdaba4d 100644 (file)
@@ -92,12 +92,42 @@ pub struct SetGroupComment {
     pub comment: String,
 }
 
+#[derive(Serialize, Deserialize, Clone)]
+pub struct AddRecipeStep {
+    pub group_id: i64,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct AddRecipeStepResult {
+    pub step_id: i64,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct RemoveRecipeStep {
+    pub step_id: i64,
+}
+
 #[derive(Serialize, Deserialize, Clone)]
 pub struct SetStepAction {
     pub step_id: i64,
     pub action: String,
 }
 
+#[derive(Serialize, Deserialize, Clone)]
+pub struct AddRecipeIngredient {
+    pub step_id: i64,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct AddRecipeIngredientResult {
+    pub ingredient_id: i64,
+}
+
+#[derive(Serialize, Deserialize, Clone)]
+pub struct RemoveRecipeIngredient {
+    pub ingredient_id: i64,
+}
+
 #[derive(Serialize, Deserialize, Clone)]
 pub struct SetIngredientName {
     pub ingredient_id: i64,
@@ -142,7 +172,7 @@ pub struct Ingredient {
     pub id: i64,
     pub name: String,
     pub comment: String,
-    pub quantity_value: f64,
+    pub quantity_value: Option<f64>,
     pub quantity_unit: String,
 }
 
index 36e0de1..20e70ae 100644 (file)
@@ -220,6 +220,25 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> {
         })
         .forget();
 
+        // Add step button.
+        let add_step_button: HtmlInputElement = group_element.select(".input-add-step");
+        EventListener::new(&add_step_button, "click", move |_event| {
+            spawn_local(async move {
+                let body = ron_api::AddRecipeStep { group_id };
+                let response: ron_api::AddRecipeStepResult =
+                    request::post("recipe/add_step", body).await.unwrap();
+                create_step_element(
+                    &by_id::<Element>(&format!("group-{}", group_id)),
+                    &ron_api::Step {
+                        id: response.step_id,
+                        action: "".to_string(),
+                        ingredients: vec![],
+                    },
+                );
+            });
+        })
+        .forget();
+
         group_element
     }
 
@@ -249,6 +268,27 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> {
         })
         .forget();
 
+        // Add ingredient button.
+        let add_ingredient_button: HtmlInputElement = step_element.select(".input-add-ingredient");
+        EventListener::new(&add_ingredient_button, "click", move |_event| {
+            spawn_local(async move {
+                let body = ron_api::AddRecipeIngredient { step_id };
+                let response: ron_api::AddRecipeIngredientResult =
+                    request::post("recipe/add_ingredient", body).await.unwrap();
+                create_ingredient_element(
+                    &by_id::<Element>(&format!("step-{}", step_id)),
+                    &ron_api::Ingredient {
+                        id: response.ingredient_id,
+                        name: "".to_string(),
+                        comment: "".to_string(),
+                        quantity_value: None,
+                        quantity_unit: "".to_string(),
+                    },
+                );
+            });
+        })
+        .forget();
+
         step_element
     }
 
@@ -259,7 +299,7 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> {
         let ingredient_id = ingredient.id;
         let ingredient_element: Element = select_and_clone("#hidden-templates .ingredient");
         ingredient_element
-            .set_attribute("id", &format!("step-{}", ingredient.id))
+            .set_attribute("id", &format!("ingredient-{}", ingredient.id))
             .unwrap();
         step_element.append_child(&ingredient_element).unwrap();
 
@@ -301,8 +341,12 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> {
 
         // Ingredient quantity.
         let quantity: HtmlInputElement = ingredient_element.select(".input-ingredient-quantity");
-        quantity.set_value(&ingredient.quantity_value.to_string());
-        let mut current_quantity = ingredient.quantity_value;
+        quantity.set_value(
+            &ingredient
+                .quantity_value
+                .map_or("".to_string(), |q| q.to_string()),
+        );
+        let mut current_quantity = quantity.value_as_number();
         EventListener::new(&quantity.clone(), "blur", move |_event| {
             let n = quantity.value_as_number();
             if n.is_nan() {
@@ -367,11 +411,9 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> {
 
     // Add a new group.
     {
-        let button_add_group = document().get_element_by_id("button-add-group").unwrap();
+        let button_add_group: HtmlInputElement = by_id("input-add-group");
         let on_click_add_group = EventListener::new(&button_add_group, "click", move |_event| {
-            log!("Click!");
             let body = ron_api::AddRecipeGroup { recipe_id };
-
             spawn_local(async move {
                 let response: ron_api::AddRecipeGroupResult =
                     request::post("recipe/add_group", body).await.unwrap();
@@ -381,7 +423,6 @@ pub fn recipe_edit(recipe_id: i64) -> Result<(), JsValue> {
                     comment: "".to_string(),
                     steps: vec![],
                 });
-                // group_html.set_attribute("id", "test").unwrap();
             });
         });
         on_click_add_group.forget();