From 6e017e41a31c049ee7f5ed0aa256e58af432ab91 Mon Sep 17 00:00:00 2001 From: Greg Burri Date: Mon, 19 May 2025 22:58:00 +0200 Subject: [PATCH] Simplify web API (put ids in url) --- Cargo.lock | 28 +-- backend/src/app.rs | 77 ++++---- backend/src/data/db/recipe.rs | 9 +- backend/src/services/ron/mod.rs | 14 +- backend/src/services/ron/recipe.rs | 281 ++++++++++++++--------------- backend/tests/http.rs | 174 ++++++------------ common/src/ron_api.rs | 116 ------------ frontend/index.html | 5 +- frontend/src/calendar.rs | 2 +- frontend/src/lib.rs | 16 +- frontend/src/pages/recipe_edit.rs | 215 ++++++++++------------ frontend/src/recipe_scheduler.rs | 20 +- frontend/src/request.rs | 32 +++- frontend/src/shopping_list.rs | 2 +- 14 files changed, 403 insertions(+), 588 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f8373de..5afc5d7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -499,9 +499,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.22" +version = "1.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" +checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" dependencies = [ "shlex", ] @@ -1502,9 +1502,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.11" +version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" +checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" dependencies = [ "bytes", "futures-channel", @@ -1593,9 +1593,9 @@ checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" +checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" dependencies = [ "displaydoc", "icu_collections", @@ -1609,9 +1609,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.0.0" +version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" +checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" [[package]] name = "icu_provider" @@ -3904,9 +3904,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.0" +version = "0.61.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" +checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement", "windows-interface", @@ -3945,18 +3945,18 @@ checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" -version = "0.3.2" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" +checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.0" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" +checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ "windows-link", ] diff --git a/backend/src/app.rs b/backend/src/app.rs index 4973419..d720b70 100644 --- a/backend/src/app.rs +++ b/backend/src/app.rs @@ -133,92 +133,99 @@ pub fn make_service( // .route("/user/update", put(services::ron::update_user)) .route("/lang", put(services::ron::set_lang)) .route("/recipe/titles", get(services::ron::recipe::get_titles)) - .route("/recipe/title", patch(services::ron::recipe::set_title)) .route( - "/recipe/description", + "/recipe/{id}/title", + patch(services::ron::recipe::set_title), + ) + .route( + "/recipe/{id}/description", patch(services::ron::recipe::set_description), ) .route( - "/recipe/servings", + "/recipe/{id}/servings", patch(services::ron::recipe::set_servings), ) .route( - "/recipe/estimated_time", + "/recipe/{id}/estimated_time", patch(services::ron::recipe::set_estimated_time), ) .route( - "/recipe/tags", + "/recipe/{id}/tags", get(services::ron::recipe::get_tags) .post(services::ron::recipe::add_tags) .delete(services::ron::recipe::rm_tags), ) + // TODO + // .route("/recipe/tags".get(services::ron::recipe::get_all_tags)) .route( - "/recipe/difficulty", + "/recipe/{id}/difficulty", patch(services::ron::recipe::set_difficulty), ) .route( - "/recipe/language", + "/recipe/{id}/language", patch(services::ron::recipe::set_language), ) .route( - "/recipe/is_public", + "/recipe/{id}/is_public", patch(services::ron::recipe::set_is_public), ) - .route("/recipe", delete(services::ron::recipe::rm)) - .route("/recipe/groups", get(services::ron::recipe::get_groups)) + .route("/recipe/{id}", delete(services::ron::recipe::rm)) .route( - "/recipe/group", - post(services::ron::recipe::add_group).delete(services::ron::recipe::rm_group), + "/recipe/{id}/groups", + get(services::ron::recipe::get_groups), ) + .route("/recipe/{id}/group", post(services::ron::recipe::add_group)) .route( - "/recipe/group_name", + "/groups/order", + patch(services::ron::recipe::set_groups_order), + ) + .route("/group/{id}", delete(services::ron::recipe::rm_group)) + .route( + "/group/{id}/name", patch(services::ron::recipe::set_group_name), ) .route( - "/recipe/group_comment", + "/group/{id}/comment", patch(services::ron::recipe::set_group_comment), ) + .route("/group/{id}/step", post(services::ron::recipe::add_step)) .route( - "/recipe/groups_order", - patch(services::ron::recipe::set_groups_order), + "/steps/order", + patch(services::ron::recipe::set_steps_order), ) + .route("/step/{id}", delete(services::ron::recipe::rm_step)) .route( - "/recipe/step", - post(services::ron::recipe::add_step).delete(services::ron::recipe::rm_step), - ) - .route( - "/recipe/step_action", + "/step/{id}/action", patch(services::ron::recipe::set_step_action), ) .route( - "/recipe/steps_order", - patch(services::ron::recipe::set_steps_order), + "/step/{id}/ingredient", + post(services::ron::recipe::add_ingredient), ) .route( - "/recipe/ingredient", - post(services::ron::recipe::add_ingredient) - .delete(services::ron::recipe::rm_ingredient), + "/ingredients/order", + patch(services::ron::recipe::set_ingredients_order), ) .route( - "/recipe/ingredient_name", + "/ingredient/{id}", + delete(services::ron::recipe::rm_ingredient), + ) + .route( + "/ingredient/{id}/name", patch(services::ron::recipe::set_ingredient_name), ) .route( - "/recipe/ingredient_comment", + "/ingredient/{id}/comment", patch(services::ron::recipe::set_ingredient_comment), ) .route( - "/recipe/ingredient_quantity", + "/ingredient/{id}/quantity", patch(services::ron::recipe::set_ingredient_quantity), ) .route( - "/recipe/ingredient_unit", + "/ingredient/{id}/unit", patch(services::ron::recipe::set_ingredient_unit), ) - .route( - "/recipe/ingredients_order", - patch(services::ron::recipe::set_ingredients_order), - ) .route( "/calendar/scheduled_recipes", get(services::ron::calendar::get_scheduled_recipes), @@ -233,7 +240,7 @@ pub fn make_service( "/shopping_list/checked", patch(services::ron::shopping_list::set_entry_checked), ) - .route("/translation", get(services::ron::get_translation)) + .route("/translation/{id}", get(services::ron::get_translation)) .fallback(services::ron::not_found); let fragments_routes = Router::new().route( diff --git a/backend/src/data/db/recipe.rs b/backend/src/data/db/recipe.rs index 44ca62e..b3e21c8 100644 --- a/backend/src/data/db/recipe.rs +++ b/backend/src/data/db/recipe.rs @@ -431,7 +431,7 @@ ORDER BY [name] { let mut tx = self.tx().await?; for tag in tags { - let tag = tag.as_ref().trim().to_lowercase(); + let tag = normalize_tag(tag.as_ref()); let tag_id: i64 = if let Some(tag_id) = sqlx::query_scalar("SELECT [id] FROM [Tag] WHERE [name] = $1") .bind(&tag) @@ -471,6 +471,7 @@ ON CONFLICT DO NOTHING { let mut tx = self.tx().await?; for tag in tags { + let tag = normalize_tag(tag.as_ref()); if let Some(tag_id) = sqlx::query_scalar::<_, i64>( r#" DELETE FROM [RecipeTag] @@ -483,7 +484,7 @@ RETURNING [RecipeTag].[tag_id] "#, ) .bind(recipe_id) - .bind(tag.as_ref()) + .bind(tag) .fetch_optional(&mut *tx) .await? { @@ -940,6 +941,10 @@ ORDER BY [date] } } +pub fn normalize_tag(tag: &str) -> String { + tag.to_lowercase() +} + #[cfg(test)] mod tests { use super::*; diff --git a/backend/src/services/ron/mod.rs b/backend/src/services/ron/mod.rs index 32995b0..e48f7b4 100644 --- a/backend/src/services/ron/mod.rs +++ b/backend/src/services/ron/mod.rs @@ -1,6 +1,6 @@ use axum::{ debug_handler, - extract::{Extension, Query, State}, + extract::{Extension, Path, Query, State}, http::{HeaderMap, StatusCode}, response::{IntoResponse, Result}, }; @@ -25,14 +25,14 @@ pub async fn set_lang( State(connection): State, Extension(context): Extension, headers: HeaderMap, - ExtractRon(ron): ExtractRon, + ExtractRon(lang): ExtractRon, ) -> Result<(CookieJar, StatusCode)> { let mut jar = CookieJar::from_headers(&headers); if let Some(user) = context.user { - connection.set_user_lang(user.id, &ron.lang).await?; + connection.set_user_lang(user.id, &lang).await?; } else { // Only set the cookie if the user is not connected. - let cookie = Cookie::build((consts::COOKIE_LANG_NAME, ron.lang)) + let cookie = Cookie::build((consts::COOKIE_LANG_NAME, lang)) .same_site(SameSite::Lax) .path("/"); jar = jar.add(cookie); @@ -43,11 +43,9 @@ pub async fn set_lang( #[debug_handler] pub async fn get_translation( Extension(context): Extension, - translation_id: Query, + Path(id): Path, ) -> Result { - Ok(ron_response_ok(ron_api::Value { - value: context.tr.t_from_id(translation_id.id), - })) + Ok(ron_response_ok(context.tr.t_from_id(id))) } /*** 404 ***/ diff --git a/backend/src/services/ron/recipe.rs b/backend/src/services/ron/recipe.rs index fafda6c..52d0473 100644 --- a/backend/src/services/ron/recipe.rs +++ b/backend/src/services/ron/recipe.rs @@ -1,12 +1,12 @@ use axum::{ debug_handler, - extract::{Extension, State}, + extract::{Extension, Path, State}, http::StatusCode, response::{IntoResponse, Result}, }; use axum_extra::extract::Query; use common::ron_api; -// use tracing::{event, Level}; +use tracing::warn; use crate::{ app::Context, data::db, data::model, ron_extractor::ExtractRon, ron_utils::ron_response_ok, @@ -19,23 +19,22 @@ use super::rights::*; #[debug_handler] pub async fn get_titles( State(connection): State, - recipe_ids: Query, + recipe_ids: Query>, ) -> Result { - Ok(ron_response_ok(ron_api::Strings { - strs: connection.get_recipe_titles(&recipe_ids.ids).await?, - })) + Ok(ron_response_ok( + connection.get_recipe_titles(&recipe_ids).await?, + )) } #[debug_handler] pub async fn set_title( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(recipe_id): Path, + ExtractRon(title): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; - connection - .set_recipe_title(ron.recipe_id, &ron.title) - .await?; + check_user_rights_recipe(&connection, &context.user, recipe_id).await?; + connection.set_recipe_title(recipe_id, &title).await?; Ok(StatusCode::OK) } @@ -43,11 +42,12 @@ pub async fn set_title( pub async fn set_description( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(recipe_id): Path, + ExtractRon(description): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; + check_user_rights_recipe(&connection, &context.user, recipe_id).await?; connection - .set_recipe_description(ron.recipe_id, &ron.description) + .set_recipe_description(recipe_id, &description) .await?; Ok(StatusCode::OK) } @@ -56,12 +56,11 @@ pub async fn set_description( pub async fn set_servings( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(recipe_id): Path, + ExtractRon(servings): ExtractRon>, ) -> Result { - check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; - connection - .set_recipe_servings(ron.recipe_id, ron.servings) - .await?; + check_user_rights_recipe(&connection, &context.user, recipe_id).await?; + connection.set_recipe_servings(recipe_id, servings).await?; Ok(StatusCode::OK) } @@ -69,11 +68,12 @@ pub async fn set_servings( pub async fn set_estimated_time( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(recipe_id): Path, + ExtractRon(time): ExtractRon>, ) -> Result { - check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; + check_user_rights_recipe(&connection, &context.user, recipe_id).await?; connection - .set_recipe_estimated_time(ron.recipe_id, ron.estimated_time) + .set_recipe_estimated_time(recipe_id, time) .await?; Ok(StatusCode::OK) } @@ -81,30 +81,22 @@ pub async fn set_estimated_time( #[debug_handler] pub async fn get_tags( State(connection): State, - recipe_id: Query, + Path(recipe_id): Path, ) -> Result { - Ok(ron_response_ok(ron_api::Tags { - recipe_id: recipe_id.id, - tags: connection.get_recipes_tags(recipe_id.id).await?, - })) + Ok(ron_response_ok( + connection.get_recipes_tags(recipe_id).await?, + )) } #[debug_handler] pub async fn add_tags( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(recipe_id): Path, + ExtractRon(tags): ExtractRon>, ) -> Result { - check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; - connection - .add_recipe_tags( - ron.recipe_id, - &ron.tags - .into_iter() - .map(|tag| tag.to_lowercase()) - .collect::>(), - ) - .await?; + check_user_rights_recipe(&connection, &context.user, recipe_id).await?; + connection.add_recipe_tags(recipe_id, &tags).await?; Ok(StatusCode::OK) } @@ -112,18 +104,11 @@ pub async fn add_tags( pub async fn rm_tags( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(recipe_id): Path, + ExtractRon(tags): ExtractRon>, ) -> Result { - check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; - connection - .rm_recipe_tags( - ron.recipe_id, - &ron.tags - .into_iter() - .map(|tag| tag.to_lowercase()) - .collect::>(), - ) - .await?; + check_user_rights_recipe(&connection, &context.user, recipe_id).await?; + connection.rm_recipe_tags(recipe_id, &tags).await?; Ok(StatusCode::OK) } @@ -131,11 +116,12 @@ pub async fn rm_tags( pub async fn set_difficulty( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(recipe_id): Path, + ExtractRon(difficulty): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; + check_user_rights_recipe(&connection, &context.user, recipe_id).await?; connection - .set_recipe_difficulty(ron.recipe_id, ron.difficulty) + .set_recipe_difficulty(recipe_id, difficulty) .await?; Ok(StatusCode::OK) } @@ -144,20 +130,19 @@ pub async fn set_difficulty( pub async fn set_language( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(recipe_id): Path, + ExtractRon(lang): ExtractRon, ) -> Result { if !crate::translation::available_codes() .iter() - .any(|&l| l == ron.lang) + .any(|&l| l == lang) { - // TODO: log? + warn!("Can't find language: {}", lang); return Ok(StatusCode::BAD_REQUEST); } - check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; - connection - .set_recipe_language(ron.recipe_id, &ron.lang) - .await?; + check_user_rights_recipe(&connection, &context.user, recipe_id).await?; + connection.set_recipe_language(recipe_id, &lang).await?; Ok(StatusCode::OK) } @@ -165,11 +150,12 @@ pub async fn set_language( pub async fn set_is_public( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(recipe_id): Path, + ExtractRon(is_public): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; + check_user_rights_recipe(&connection, &context.user, recipe_id).await?; connection - .set_recipe_is_public(ron.recipe_id, ron.is_public) + .set_recipe_is_public(recipe_id, is_public) .await?; Ok(StatusCode::OK) } @@ -178,10 +164,10 @@ pub async fn set_is_public( pub async fn rm( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(recipe_id): Path, ) -> Result { - check_user_rights_recipe(&connection, &context.user, ron.id).await?; - connection.rm_recipe(ron.id).await?; + check_user_rights_recipe(&connection, &context.user, recipe_id).await?; + connection.rm_recipe(recipe_id).await?; Ok(StatusCode::OK) } @@ -225,12 +211,12 @@ impl From for ron_api::Ingredient { #[debug_handler] pub async fn get_groups( State(connection): State, - recipe_id: Query, + Path(recipe_id): Path, ) -> Result { // Here we don't check user rights on purpose. Ok(ron_response_ok( connection - .get_groups(recipe_id.id) + .get_groups(recipe_id) .await? .into_iter() .map(ron_api::Group::from) @@ -242,22 +228,32 @@ pub async fn get_groups( pub async fn add_group( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(recipe_id): Path, ) -> Result { - check_user_rights_recipe(&connection, &context.user, ron.id).await?; - let id = connection.add_recipe_group(ron.id).await?; + check_user_rights_recipe(&connection, &context.user, recipe_id).await?; + let id = connection.add_recipe_group(recipe_id).await?; + Ok(ron_response_ok(id)) +} - Ok(ron_response_ok(ron_api::Id { id })) +#[debug_handler] +pub async fn set_groups_order( + State(connection): State, + Extension(context): Extension, + ExtractRon(ids): ExtractRon>, +) -> Result { + check_user_rights_recipe_groups(&connection, &context.user, &ids).await?; + connection.set_groups_order(&ids).await?; + Ok(StatusCode::OK) } #[debug_handler] pub async fn rm_group( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(groupe_id): Path, ) -> Result { - check_user_rights_recipe_group(&connection, &context.user, ron.id).await?; - connection.rm_recipe_group(ron.id).await?; + check_user_rights_recipe_group(&connection, &context.user, groupe_id).await?; + connection.rm_recipe_group(groupe_id).await?; Ok(StatusCode::OK) } @@ -265,10 +261,11 @@ pub async fn rm_group( pub async fn set_group_name( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(group_id): Path, + ExtractRon(name): ExtractRon, ) -> Result { - check_user_rights_recipe_group(&connection, &context.user, ron.group_id).await?; - connection.set_group_name(ron.group_id, &ron.name).await?; + check_user_rights_recipe_group(&connection, &context.user, group_id).await?; + connection.set_group_name(group_id, &name).await?; Ok(StatusCode::OK) } @@ -276,23 +273,11 @@ pub async fn set_group_name( pub async fn set_group_comment( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(group_id): Path, + ExtractRon(comment): ExtractRon, ) -> Result { - check_user_rights_recipe_group(&connection, &context.user, ron.group_id).await?; - connection - .set_group_comment(ron.group_id, &ron.comment) - .await?; - Ok(StatusCode::OK) -} - -#[debug_handler] -pub async fn set_groups_order( - State(connection): State, - Extension(context): Extension, - ExtractRon(ron): ExtractRon, -) -> Result { - check_user_rights_recipe_groups(&connection, &context.user, &ron.ids).await?; - connection.set_groups_order(&ron.ids).await?; + check_user_rights_recipe_group(&connection, &context.user, group_id).await?; + connection.set_group_comment(group_id, &comment).await?; Ok(StatusCode::OK) } @@ -300,22 +285,32 @@ pub async fn set_groups_order( pub async fn add_step( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(group_id): Path, ) -> Result { - check_user_rights_recipe_group(&connection, &context.user, ron.id).await?; - let id = connection.add_recipe_step(ron.id).await?; + check_user_rights_recipe_group(&connection, &context.user, group_id).await?; + let id = connection.add_recipe_step(group_id).await?; + Ok(ron_response_ok(id)) +} - Ok(ron_response_ok(ron_api::Id { id })) +#[debug_handler] +pub async fn set_steps_order( + State(connection): State, + Extension(context): Extension, + ExtractRon(ids): ExtractRon>, +) -> Result { + check_user_rights_recipe_steps(&connection, &context.user, &ids).await?; + connection.set_steps_order(&ids).await?; + Ok(StatusCode::OK) } #[debug_handler] pub async fn rm_step( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(step_id): Path, ) -> Result { - check_user_rights_recipe_step(&connection, &context.user, ron.id).await?; - connection.rm_recipe_step(ron.id).await?; + check_user_rights_recipe_step(&connection, &context.user, step_id).await?; + connection.rm_recipe_step(step_id).await?; Ok(StatusCode::OK) } @@ -323,21 +318,11 @@ pub async fn rm_step( pub async fn set_step_action( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(step_id): Path, + ExtractRon(action): ExtractRon, ) -> Result { - check_user_rights_recipe_step(&connection, &context.user, ron.step_id).await?; - connection.set_step_action(ron.step_id, &ron.action).await?; - Ok(StatusCode::OK) -} - -#[debug_handler] -pub async fn set_steps_order( - State(connection): State, - Extension(context): Extension, - ExtractRon(ron): ExtractRon, -) -> Result { - check_user_rights_recipe_steps(&connection, &context.user, &ron.ids).await?; - connection.set_steps_order(&ron.ids).await?; + check_user_rights_recipe_step(&connection, &context.user, step_id).await?; + connection.set_step_action(step_id, &action).await?; Ok(StatusCode::OK) } @@ -345,21 +330,32 @@ pub async fn set_steps_order( pub async fn add_ingredient( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(step_id): Path, ) -> Result { - check_user_rights_recipe_step(&connection, &context.user, ron.id).await?; - let id = connection.add_recipe_ingredient(ron.id).await?; - Ok(ron_response_ok(ron_api::Id { id })) + check_user_rights_recipe_step(&connection, &context.user, step_id).await?; + let id = connection.add_recipe_ingredient(step_id).await?; + Ok(ron_response_ok(id)) +} + +#[debug_handler] +pub async fn set_ingredients_order( + State(connection): State, + Extension(context): Extension, + ExtractRon(ids): ExtractRon>, +) -> Result { + check_user_rights_recipe_ingredients(&connection, &context.user, &ids).await?; + connection.set_ingredients_order(&ids).await?; + Ok(StatusCode::OK) } #[debug_handler] pub async fn rm_ingredient( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(ingredient_id): Path, ) -> Result { - check_user_rights_recipe_ingredient(&connection, &context.user, ron.id).await?; - connection.rm_recipe_ingredient(ron.id).await?; + check_user_rights_recipe_ingredient(&connection, &context.user, ingredient_id).await?; + connection.rm_recipe_ingredient(ingredient_id).await?; Ok(StatusCode::OK) } @@ -367,12 +363,11 @@ pub async fn rm_ingredient( pub async fn set_ingredient_name( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(ingredient_id): Path, + ExtractRon(name): ExtractRon, ) -> Result { - check_user_rights_recipe_ingredient(&connection, &context.user, ron.ingredient_id).await?; - connection - .set_ingredient_name(ron.ingredient_id, &ron.name) - .await?; + check_user_rights_recipe_ingredient(&connection, &context.user, ingredient_id).await?; + connection.set_ingredient_name(ingredient_id, &name).await?; Ok(StatusCode::OK) } @@ -380,11 +375,12 @@ pub async fn set_ingredient_name( pub async fn set_ingredient_comment( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(ingredient_id): Path, + ExtractRon(comment): ExtractRon, ) -> Result { - check_user_rights_recipe_ingredient(&connection, &context.user, ron.ingredient_id).await?; + check_user_rights_recipe_ingredient(&connection, &context.user, ingredient_id).await?; connection - .set_ingredient_comment(ron.ingredient_id, &ron.comment) + .set_ingredient_comment(ingredient_id, &comment) .await?; Ok(StatusCode::OK) } @@ -393,11 +389,12 @@ pub async fn set_ingredient_comment( pub async fn set_ingredient_quantity( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(ingredient_id): Path, + ExtractRon(quantity): ExtractRon>, ) -> Result { - check_user_rights_recipe_ingredient(&connection, &context.user, ron.ingredient_id).await?; + check_user_rights_recipe_ingredient(&connection, &context.user, ingredient_id).await?; connection - .set_ingredient_quantity(ron.ingredient_id, ron.quantity) + .set_ingredient_quantity(ingredient_id, quantity) .await?; Ok(StatusCode::OK) } @@ -406,22 +403,10 @@ pub async fn set_ingredient_quantity( pub async fn set_ingredient_unit( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + Path(ingredient_id): Path, + ExtractRon(unit): ExtractRon, ) -> Result { - check_user_rights_recipe_ingredient(&connection, &context.user, ron.ingredient_id).await?; - connection - .set_ingredient_unit(ron.ingredient_id, &ron.unit) - .await?; - Ok(StatusCode::OK) -} - -#[debug_handler] -pub async fn set_ingredients_order( - State(connection): State, - Extension(context): Extension, - ExtractRon(ron): ExtractRon, -) -> Result { - check_user_rights_recipe_ingredients(&connection, &context.user, &ron.ids).await?; - connection.set_ingredients_order(&ron.ids).await?; + check_user_rights_recipe_ingredient(&connection, &context.user, ingredient_id).await?; + connection.set_ingredient_unit(ingredient_id, &unit).await?; Ok(StatusCode::OK) } diff --git a/backend/tests/http.rs b/backend/tests/http.rs index 5e07b6c..0af4e20 100644 --- a/backend/tests/http.rs +++ b/backend/tests/http.rs @@ -257,107 +257,62 @@ async fn create_recipe_and_edit_it() -> Result<(), Box> { response.assert_status_ok(); let response = server - .patch("/ron-api/recipe/title") + .patch(&format!("/ron-api/recipe/{recipe_id}/title")) + .add_cookie(cookie.clone()) + .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) + .bytes(ron_api::to_string("AAA").unwrap().into()) + .await; + response.assert_status_ok(); + + let response = server + .patch(&format!("/ron-api/recipe/{recipe_id}/description")) + .add_cookie(cookie.clone()) + .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) + .bytes(ron_api::to_string("BBB").unwrap().into()) + .await; + response.assert_status_ok(); + + let response = server + .patch(&format!("/ron-api/recipe/{recipe_id}/servings")) + .add_cookie(cookie.clone()) + .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) + .bytes(ron_api::to_string(Some(42)).unwrap().into()) + .await; + response.assert_status_ok(); + + let response = server + .patch(&format!("/ron-api/recipe/{recipe_id}/estimated_time")) + .add_cookie(cookie.clone()) + .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) + .bytes(ron_api::to_string(Some(420)).unwrap().into()) + .await; + response.assert_status_ok(); + + let response = server + .patch(&format!("/ron-api/recipe/{recipe_id}/difficulty")) .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) .bytes( - ron_api::to_string(ron_api::SetRecipeTitle { - recipe_id, - title: "AAA".into(), - }) - .unwrap() - .into(), + ron_api::to_string(ron_api::Difficulty::Hard) + .unwrap() + .into(), ) .await; response.assert_status_ok(); let response = server - .patch("/ron-api/recipe/description") + .patch(&format!("/ron-api/recipe/{recipe_id}/language")) .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes( - ron_api::to_string(ron_api::SetRecipeDescription { - recipe_id, - description: "BBB".into(), - }) - .unwrap() - .into(), - ) + .bytes(ron_api::to_string("fr").unwrap().into()) .await; response.assert_status_ok(); let response = server - .patch("/ron-api/recipe/servings") + .patch(&format!("/ron-api/recipe/{recipe_id}/is_public")) .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes( - ron_api::to_string(ron_api::SetRecipeServings { - recipe_id, - servings: Some(42), - }) - .unwrap() - .into(), - ) - .await; - response.assert_status_ok(); - - let response = server - .patch("/ron-api/recipe/estimated_time") - .add_cookie(cookie.clone()) - .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes( - ron_api::to_string(ron_api::SetRecipeEstimatedTime { - recipe_id, - estimated_time: Some(420), - }) - .unwrap() - .into(), - ) - .await; - response.assert_status_ok(); - - let response = server - .patch("/ron-api/recipe/difficulty") - .add_cookie(cookie.clone()) - .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes( - ron_api::to_string(ron_api::SetRecipeDifficulty { - recipe_id, - difficulty: ron_api::Difficulty::Hard, - }) - .unwrap() - .into(), - ) - .await; - response.assert_status_ok(); - - let response = server - .patch("/ron-api/recipe/language") - .add_cookie(cookie.clone()) - .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes( - ron_api::to_string(ron_api::SetRecipeLanguage { - recipe_id, - lang: "fr".into(), - }) - .unwrap() - .into(), - ) - .await; - response.assert_status_ok(); - - let response = server - .patch("/ron-api/recipe/is_public") - .add_cookie(cookie.clone()) - .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes( - ron_api::to_string(ron_api::SetRecipeIsPublic { - recipe_id, - is_public: true, - }) - .unwrap() - .into(), - ) + .bytes(ron_api::to_string(true).unwrap().into()) .await; response.assert_status_ok(); @@ -430,70 +385,61 @@ async fn recipe_tags() -> Result<(), Box> { // Tags list must be empty. let response = server - .get("/ron-api/recipe/tags") + .get(&format!("/ron-api/recipe/{recipe_id}/tags")) .add_cookie(cookie.clone()) - .add_query_param("id", recipe_id) .await; response.assert_status_ok(); - let tags: ron_api::Tags = ron::de::from_bytes(response.as_bytes()).unwrap(); - assert!(tags.tags.is_empty()); + let tags: Vec = ron::de::from_bytes(response.as_bytes()).unwrap(); + assert!(tags.is_empty()); // Act. // Add some tags. let response = server - .post("/ron-api/recipe/tags") + .post(&format!("/ron-api/recipe/{recipe_id}/tags")) .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) .bytes( - ron_api::to_string(ron_api::Tags { - recipe_id, - tags: vec!["ABC".into(), "xyz".into()], - }) - .unwrap() - .into(), + ron_api::to_string(vec!["ABC".to_string(), "xyz".to_string()]) + .unwrap() + .into(), ) .await; // Assert. response.assert_status_ok(); let response = server - .get("/ron-api/recipe/tags") + .get(&format!("/ron-api/recipe/{recipe_id}/tags")) .add_cookie(cookie.clone()) - .add_query_param("id", recipe_id) .await; response.assert_status_ok(); - let tags: ron_api::Tags = ron::de::from_bytes(response.as_bytes()).unwrap(); - assert_eq!(tags.tags.len(), 2); - assert!(tags.tags.contains(&"abc".to_string())); // Tags are in lower case. - assert!(tags.tags.contains(&"xyz".to_string())); + let tags: Vec = ron::de::from_bytes(response.as_bytes()).unwrap(); + assert_eq!(tags.len(), 2); + assert!(tags.contains(&"abc".to_string())); // Tags are in lower case. + assert!(tags.contains(&"xyz".to_string())); // Act. // Remove some tags. let response = server - .delete("/ron-api/recipe/tags") + .delete(&format!("/ron-api/recipe/{recipe_id}/tags")) .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) .bytes( - ron_api::to_string(ron_api::Tags { - recipe_id, - tags: vec!["XYZ".into(), "qwe".into()], - }) - .unwrap() - .into(), + ron_api::to_string(vec!["XYZ".to_string(), "qwe".to_string()]) + .unwrap() + .into(), ) .await; // Assert. response.assert_status_ok(); let response = server - .get("/ron-api/recipe/tags") + .get(&format!("/ron-api/recipe/{recipe_id}/tags")) .add_cookie(cookie.clone()) - .add_query_param("id", recipe_id) .await; response.assert_status_ok(); - let tags: ron_api::Tags = ron::de::from_bytes(response.as_bytes()).unwrap(); - assert_eq!(tags.tags.len(), 1); - assert_eq!(tags.tags[0], "abc".to_string()); + let tags: Vec = ron::de::from_bytes(response.as_bytes()).unwrap(); + assert_eq!(tags.len(), 1); + assert_eq!(tags[0], "abc".to_string()); Ok(()) } diff --git a/common/src/ron_api.rs b/common/src/ron_api.rs index 175f48e..a741552 100644 --- a/common/src/ron_api.rs +++ b/common/src/ron_api.rs @@ -2,29 +2,8 @@ use chrono::NaiveDate; use ron::ser::{PrettyConfig, to_string_pretty}; use serde::{Deserialize, Serialize}; -#[derive(Serialize, Deserialize, Clone)] -pub struct SetLang { - pub lang: String, -} - /*** Generic types ***/ -#[derive(Serialize, Deserialize, Clone)] -pub struct Ids { - pub ids: Vec, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct Id { - pub id: i64, -} - -// A simple value. -#[derive(Serialize, Deserialize, Clone)] -pub struct Value { - pub value: T, -} - // A value associated with an id. #[derive(Serialize, Deserialize, Clone)] pub struct KeyValue { @@ -32,11 +11,6 @@ pub struct KeyValue { pub value: T, } -#[derive(Serialize, Deserialize, Clone)] -pub struct Strings { - pub strs: Vec, -} - #[derive(Serialize, Deserialize, Clone)] pub struct DateRange { pub start_date: NaiveDate, @@ -45,30 +19,6 @@ pub struct DateRange { /*** Recipe ***/ -#[derive(Serialize, Deserialize, Clone)] -pub struct SetRecipeTitle { - pub recipe_id: i64, - pub title: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetRecipeDescription { - pub recipe_id: i64, - pub description: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetRecipeServings { - pub recipe_id: i64, - pub servings: Option, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetRecipeEstimatedTime { - pub recipe_id: i64, - pub estimated_time: Option, -} - #[repr(u32)] #[derive(Serialize, Deserialize, Clone, Copy, PartialEq, Debug)] pub enum Difficulty { @@ -96,72 +46,6 @@ impl From for u32 { } } -#[derive(Serialize, Deserialize, Clone)] -pub struct SetRecipeDifficulty { - pub recipe_id: i64, - pub difficulty: Difficulty, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetRecipeLanguage { - pub recipe_id: i64, - pub lang: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetRecipeIsPublic { - pub recipe_id: i64, - pub is_public: bool, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetGroupName { - pub group_id: i64, - pub name: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetGroupComment { - pub group_id: i64, - pub comment: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetStepAction { - pub step_id: i64, - pub action: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetIngredientName { - pub ingredient_id: i64, - pub name: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetIngredientComment { - pub ingredient_id: i64, - pub comment: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetIngredientQuantity { - pub ingredient_id: i64, - pub quantity: Option, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SetIngredientUnit { - pub ingredient_id: i64, - pub unit: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct Tags { - pub recipe_id: i64, - pub tags: Vec, -} - #[derive(Serialize, Deserialize, Clone, Debug)] pub struct Group { pub id: i64, diff --git a/frontend/index.html b/frontend/index.html index ce7f79b..5f2a69c 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,6 +1,9 @@ - + diff --git a/frontend/src/calendar.rs b/frontend/src/calendar.rs index dd77168..c0cb8bd 100644 --- a/frontend/src/calendar.rs +++ b/frontend/src/calendar.rs @@ -172,7 +172,7 @@ pub fn setup( date, remove_ingredients_from_shopping_list, }; - let _ = request::delete::<(), _>("calendar/scheduled_recipe", body).await; + let _ = request::delete::<(), _>("calendar/scheduled_recipe", Some(body)).await; window().location().reload().unwrap(); } }); diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs index c0567c3..1904ed3 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -1,4 +1,3 @@ -use common::ron_api; use gloo::{console::log, events::EventListener, utils::window}; use utils::by_id; use wasm_bindgen::prelude::*; @@ -87,18 +86,17 @@ pub fn main() -> Result<(), JsValue> { // Request the message to display. spawn_local(async move { - let translation: ron_api::Value = - request::get("translation", ron_api::Id { id: mess_id }) - .await - .unwrap(); + let translation: String = request::get(&format!("translation/{mess_id}")) + .await + .unwrap(); if let Some(level_id) = level_id { toast::show_message_level( common::toast::Level::from_repr(level_id) .unwrap_or(common::toast::Level::Unknown), - &translation.value, + &translation, ); } else { - toast::show_message(&translation.value); + toast::show_message(&translation); } }); } @@ -107,10 +105,10 @@ pub fn main() -> Result<(), JsValue> { let select_language: HtmlSelectElement = by_id("select-website-language"); EventListener::new(&select_language.clone(), "input", move |_event| { let lang = select_language.value(); - let body = ron_api::SetLang { lang: lang.clone() }; + // let body = ron_api::SetLang { lang: lang.clone() }; let location_without_lang = location_without_lang.clone(); spawn_local(async move { - let _ = request::put::<(), _>("lang", body).await; + let _ = request::put::<(), _>("lang", &lang).await; window() .location() diff --git a/frontend/src/pages/recipe_edit.rs b/frontend/src/pages/recipe_edit.rs index 4524e0d..2ff7491 100644 --- a/frontend/src/pages/recipe_edit.rs +++ b/frontend/src/pages/recipe_edit.rs @@ -30,18 +30,14 @@ pub fn setup_page(recipe_id: i64) { None => return, }; - // Check if the recipe has been loaded. - let mut current_title = title.value(); EventListener::new(&title.clone(), "blur", move |_event| { if title.value() != current_title { current_title = title.value(); - let body = ron_api::SetRecipeTitle { - recipe_id, - title: title.value(), - }; + let title = title.value(); spawn_local(async move { - let _ = request::patch::<(), _>("recipe/title", body).await; + let _ = + request::patch::<(), _>(&format!("recipe/{recipe_id}/title"), title).await; reload_recipes_list(recipe_id).await; }); } @@ -57,12 +53,13 @@ pub fn setup_page(recipe_id: i64) { EventListener::new(&description.clone(), "blur", move |_event| { if description.value() != current_description { current_description = description.value(); - let body = ron_api::SetRecipeDescription { - recipe_id, - description: description.value(), - }; + let description = description.value(); spawn_local(async move { - let _ = request::patch::<(), _>("recipe/description", body).await; + let _ = request::patch::<(), _>( + &format!("recipe/{recipe_id}/description"), + description, + ) + .await; }); } }) @@ -87,12 +84,10 @@ pub fn setup_page(recipe_id: i64) { Some(n) }; current_servings = n; - let body = ron_api::SetRecipeServings { - recipe_id, - servings, - }; spawn_local(async move { - let _ = request::patch::<(), _>("recipe/servings", body).await; + let _ = + request::patch::<(), _>(&format!("recipe/{recipe_id}/servings"), servings) + .await; }); } }) @@ -118,12 +113,12 @@ pub fn setup_page(recipe_id: i64) { Some(n) }; current_time = n; - let body = ron_api::SetRecipeEstimatedTime { - recipe_id, - estimated_time: time, - }; spawn_local(async move { - let _ = request::patch::<(), _>("recipe/estimated_time", body).await; + let _ = request::patch::<(), _>( + &format!("recipe/{recipe_id}/estimated_time"), + time, + ) + .await; }); } }) @@ -138,16 +133,14 @@ pub fn setup_page(recipe_id: i64) { EventListener::new(&difficulty.clone(), "blur", move |_event| { if difficulty.value() != current_difficulty { current_difficulty = difficulty.value(); - - let body = ron_api::SetRecipeDifficulty { - recipe_id, - difficulty: ron_api::Difficulty::try_from( - current_difficulty.parse::().unwrap(), - ) - .unwrap(), - }; + let difficulty = + ron_api::Difficulty::try_from(difficulty.value().parse::().unwrap()); spawn_local(async move { - let _ = request::patch::<(), _>("recipe/difficulty", body).await; + let _ = request::patch::<(), _>( + &format!("recipe/{recipe_id}/difficulty"), + difficulty, + ) + .await; }); } }) @@ -157,24 +150,21 @@ pub fn setup_page(recipe_id: i64) { // Tags. { spawn_local(async move { - let tags: ron_api::Tags = request::get("recipe/tags", ron_api::Id { id: recipe_id }) + let tags: Vec = request::get(&format!("recipe/{recipe_id}/tags")) .await .unwrap(); - create_tag_elements(recipe_id, &tags.tags); + create_tag_elements(recipe_id, &tags); }); fn add_tags(recipe_id: i64, tags: String) { spawn_local(async move { let tag_list: Vec = tags.split_whitespace().map(str::to_lowercase).collect(); - if !tag_list.is_empty() { - let body = ron_api::Tags { - recipe_id, - tags: tag_list.clone(), - }; - let _ = request::post::<(), _>("recipe/tags", body).await; - create_tag_elements(recipe_id, &tag_list); - } + let _ = + request::post::<(), _>(&format!("recipe/{recipe_id}/tags"), Some(&tag_list)) + .await; + create_tag_elements(recipe_id, &tag_list); + by_id::("input-tags").set_value(""); }); } @@ -214,13 +204,11 @@ pub fn setup_page(recipe_id: i64) { EventListener::new(&language.clone(), "blur", move |_event| { if language.value() != current_language { current_language = language.value(); - - let body = ron_api::SetRecipeLanguage { - recipe_id, - lang: language.value(), - }; + let language = language.value(); spawn_local(async move { - let _ = request::patch::<(), _>("recipe/language", body).await; + let _ = + request::patch::<(), _>(&format!("recipe/{recipe_id}/language"), language) + .await; }); } }) @@ -231,12 +219,11 @@ pub fn setup_page(recipe_id: i64) { { let is_public: HtmlInputElement = by_id("input-is-public"); EventListener::new(&is_public.clone(), "input", move |_event| { - let body = ron_api::SetRecipeIsPublic { - recipe_id, - is_public: is_public.checked(), - }; + let is_public = is_public.checked(); spawn_local(async move { - let _ = request::patch::<(), _>("recipe/is_public", body).await; + let _ = + request::patch::<(), _>(&format!("recipe/{recipe_id}/is_public"), is_public) + .await; reload_recipes_list(recipe_id).await; }); }) @@ -261,8 +248,8 @@ pub fn setup_page(recipe_id: i64) { .await .is_some() { - let body = ron_api::Id { id: recipe_id }; - if let Ok(()) = request::delete::<(), _>("recipe", body).await { + if let Ok(()) = request::delete::<_, ()>(&format!("recipe/{recipe_id}"), None).await + { window() .location() .set_href(&format!( @@ -284,7 +271,7 @@ pub fn setup_page(recipe_id: i64) { { spawn_local(async move { let groups: Vec = - request::get("recipe/groups", ron_api::Id { id: recipe_id }) + request::get(&format!("recipe/{recipe_id}/groups")) .await .unwrap(); @@ -310,11 +297,12 @@ pub fn setup_page(recipe_id: i64) { { let button_add_group: HtmlInputElement = by_id("input-add-group"); EventListener::new(&button_add_group, "click", move |_event| { - let body = ron_api::Id { id: recipe_id }; spawn_local(async move { - let response: ron_api::Id = request::post("recipe/group", body).await.unwrap(); + let id: i64 = request::post::<_, ()>(&format!("recipe/{recipe_id}/group"), None) + .await + .unwrap(); create_group_element(&ron_api::Group { - id: response.id, + id, name: "".to_string(), comment: "".to_string(), steps: vec![], @@ -335,14 +323,13 @@ fn create_group_element(group: &ron_api::Group) -> Element { set_draggable(&group_element, "group", |_element| { spawn_local(async move { - let ids = by_id::("groups-container") + let ids: Vec = by_id::("groups-container") .selector_all::(".group") .into_iter() .map(|e| e.id()[6..].parse::().unwrap()) .collect(); - let body = ron_api::Ids { ids }; - let _ = request::patch::<(), _>("recipe/groups_order", body).await; + let _ = request::patch::<(), _>("groups/order", ids).await; }); }); @@ -353,12 +340,9 @@ fn create_group_element(group: &ron_api::Group) -> Element { EventListener::new(&name.clone(), "blur", move |_event| { if name.value() != current_name { current_name = name.value(); - let body = ron_api::SetGroupName { - group_id, - name: name.value(), - }; + let name = name.value(); spawn_local(async move { - let _ = request::patch::<(), _>("recipe/group_name", body).await; + let _ = request::patch::<(), _>(&format!("group/{group_id}/name"), name).await; }) } }) @@ -371,12 +355,10 @@ fn create_group_element(group: &ron_api::Group) -> Element { EventListener::new(&comment.clone(), "blur", move |_event| { if comment.value() != current_comment { current_comment = comment.value(); - let body = ron_api::SetGroupComment { - group_id, - comment: comment.value(), - }; + let comment = comment.value(); spawn_local(async move { - let _ = request::patch::<(), _>("recipe/group_comment", body).await; + let _ = + request::patch::<(), _>(&format!("group/{group_id}/comment"), comment).await; }); } }) @@ -401,9 +383,8 @@ fn create_group_element(group: &ron_api::Group) -> Element { .await .is_some() { - let body = ron_api::Id { id: group_id }; - let _ = request::delete::<(), _>("recipe/group", body).await; - let group_element = by_id::(&format!("group-{}", group_id)); + let _ = request::delete::<(), ()>(&format!("group/{group_id}"), None).await; + let group_element = by_id::(&format!("group-{group_id}")); group_element.next_element_sibling().unwrap().remove(); group_element.remove(); } @@ -415,12 +396,13 @@ fn create_group_element(group: &ron_api::Group) -> Element { let add_step_button: HtmlInputElement = group_element.selector(".input-add-step"); EventListener::new(&add_step_button, "click", move |_event| { spawn_local(async move { - let body = ron_api::Id { id: group_id }; - let response: ron_api::Id = request::post("recipe/step", body).await.unwrap(); + let id: i64 = request::post::<_, ()>(&format!("group/{group_id}/step"), None) + .await + .unwrap(); create_step_element( - &selector::(&format!("#group-{} .steps", group_id)), + &selector::(&format!("#group-{group_id} .steps")), &ron_api::Step { - id: response.id, + id, action: "".to_string(), ingredients: vec![], }, @@ -474,11 +456,9 @@ where let tag_span = tag_span.clone(); let tag = tag.clone(); spawn_local(async move { - let body = ron_api::Tags { - recipe_id, - tags: vec![tag], - }; - let _ = request::delete::<(), _>("recipe/tags", body).await; + let _ = + request::delete::<(), _>(&format!("recipe/{recipe_id}/tags"), Some(vec![tag])) + .await; tag_span.remove(); }); }) @@ -495,7 +475,7 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element set_draggable(&step_element, "step", |element| { let element = element.clone(); spawn_local(async move { - let ids = element + let ids: Vec = element .parent_element() .unwrap() .selector_all::(".step") @@ -503,8 +483,7 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element .map(|e| e.id()[5..].parse::().unwrap()) .collect(); - let body = ron_api::Ids { ids }; - let _ = request::patch::<(), _>("recipe/steps_order", body).await; + let _ = request::patch::<(), _>("/steps/order", ids).await; }); }); @@ -515,12 +494,9 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element EventListener::new(&action.clone(), "blur", move |_event| { if action.value() != current_action { current_action = action.value(); - let body = ron_api::SetStepAction { - step_id, - action: action.value(), - }; + let action = action.value(); spawn_local(async move { - let _ = request::patch::<(), _>("recipe/step_action", body).await; + let _ = request::patch::<(), _>(&format!("/step/{step_id}/action"), action).await; }); } }) @@ -545,9 +521,8 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element .await .is_some() { - let body = ron_api::Id { id: step_id }; - let _ = request::delete::<(), _>("recipe/step", body).await; - let step_element = by_id::(&format!("step-{}", step_id)); + let _ = request::delete::<(), ()>(&format!("step/{step_id}"), None).await; + let step_element = by_id::(&format!("step-{step_id}")); step_element.next_element_sibling().unwrap().remove(); step_element.remove(); } @@ -559,12 +534,13 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element let add_ingredient_button: HtmlInputElement = step_element.selector(".input-add-ingredient"); EventListener::new(&add_ingredient_button, "click", move |_event| { spawn_local(async move { - let body = ron_api::Id { id: step_id }; - let response: ron_api::Id = request::post("recipe/ingredient", body).await.unwrap(); + let id: i64 = request::post::<_, ()>(&format!("step/{step_id}/ingredient"), None) + .await + .unwrap(); create_ingredient_element( &selector::(&format!("#step-{} .ingredients", step_id)), &ron_api::Ingredient { - id: response.id, + id, name: "".to_string(), comment: "".to_string(), quantity_value: None, @@ -587,7 +563,7 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre set_draggable(&ingredient_element, "ingredient", |element| { let element = element.clone(); spawn_local(async move { - let ids = element + let ids: Vec = element .parent_element() .unwrap() .selector_all::(".ingredient") @@ -595,8 +571,7 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre .map(|e| e.id()[11..].parse::().unwrap()) .collect(); - let body = ron_api::Ids { ids }; - let _ = request::patch::<(), _>("recipe/ingredients_order", body).await; + let _ = request::patch::<(), _>("ingredients/order", ids).await; }); }); @@ -607,12 +582,10 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre EventListener::new(&name.clone(), "blur", move |_event| { if name.value() != current_name { current_name = name.value(); - let body = ron_api::SetIngredientName { - ingredient_id, - name: name.value(), - }; + let name = name.value(); spawn_local(async move { - let _ = request::patch::<(), _>("recipe/ingredient_name", body).await; + let _ = request::patch::<(), _>(&format!("ingredient/{ingredient_id}/name"), name) + .await; }); } }) @@ -625,12 +598,13 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre EventListener::new(&comment.clone(), "blur", move |_event| { if comment.value() != current_comment { current_comment = comment.value(); - let body = ron_api::SetIngredientComment { - ingredient_id, - comment: comment.value(), - }; + let comment = comment.value(); spawn_local(async move { - let _ = request::patch::<(), _>("recipe/ingredient_comment", body).await; + let _ = request::patch::<(), _>( + &format!("ingredient/{ingredient_id}/comment"), + comment, + ) + .await; }); } }) @@ -652,12 +626,9 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre if n != current_quantity { let q = if n.is_nan() { None } else { Some(n) }; current_quantity = n; - let body = ron_api::SetIngredientQuantity { - ingredient_id, - quantity: q, - }; spawn_local(async move { - let _ = request::patch::<(), _>("recipe/ingredient_quantity", body).await; + let _ = request::patch::<(), _>(&format!("ingredient/{ingredient_id}/quantity"), q) + .await; }); } }) @@ -670,12 +641,10 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre EventListener::new(&unit.clone(), "blur", move |_event| { if unit.value() != current_unit { current_unit = unit.value(); - let body = ron_api::SetIngredientUnit { - ingredient_id, - unit: unit.value(), - }; + let unit = unit.value(); spawn_local(async move { - let _ = request::patch::<(), _>("recipe/ingredient_unit", body).await; + let _ = request::patch::<(), _>(&format!("ingredient/{ingredient_id}/unit"), unit) + .await; }); } }) @@ -700,9 +669,9 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre .await .is_some() { - let body = ron_api::Id { id: ingredient_id }; - let _ = request::delete::<(), _>("recipe/ingredient", body).await; - let ingredient_element = by_id::(&format!("ingredient-{}", ingredient_id)); + let _ = + request::delete::<(), ()>(&format!("ingredient/{ingredient_id}"), None).await; + let ingredient_element = by_id::(&format!("ingredient-{ingredient_id}")); ingredient_element.next_element_sibling().unwrap().remove(); ingredient_element.remove(); } diff --git a/frontend/src/recipe_scheduler.rs b/frontend/src/recipe_scheduler.rs index 63a4672..dd34016 100644 --- a/frontend/src/recipe_scheduler.rs +++ b/frontend/src/recipe_scheduler.rs @@ -80,24 +80,22 @@ impl RecipeScheduler { return Ok(vec![]); } - let titles: ron_api::Strings = request::get( + let titles: Vec = request::get_with_params( "recipe/titles", - ron_api::Ids { - ids: recipe_ids_and_dates - .iter() - .map(|r| r.recipe_id) - .collect::>(), - }, + recipe_ids_and_dates + .iter() + .map(|r| r.recipe_id) + .collect::>(), ) .await?; Ok(recipe_ids_and_dates .iter() - .zip(titles.strs.into_iter()) + .zip(titles.into_iter()) .map(|(id_and_date, title)| (id_and_date.date, title, id_and_date.recipe_id)) .collect::>()) } else { - let scheduled_recipes: ron_api::ScheduledRecipes = request::get( + let scheduled_recipes: ron_api::ScheduledRecipes = request::get_with_params( "calendar/scheduled_recipes", ron_api::DateRange { start_date, @@ -131,12 +129,12 @@ impl RecipeScheduler { } else { request::post::( "calendar/scheduled_recipe", - ron_api::ScheduleRecipe { + Some(ron_api::ScheduleRecipe { recipe_id, date, servings, add_ingredients_to_shopping_list, - }, + }), ) .await .map_err(Error::from) diff --git a/frontend/src/request.rs b/frontend/src/request.rs index 68fe5a8..c702e54 100644 --- a/frontend/src/request.rs +++ b/frontend/src/request.rs @@ -63,6 +63,15 @@ where send_req(request_builder.build()?).await } +async fn req(api_name: &str, method_fn: fn(&str) -> RequestBuilder) -> Result +where + T: DeserializeOwned, +{ + let url = format!("/ron-api/{}", api_name); + let request_builder = method_fn(&url); + send_req(request_builder.build()?).await +} + async fn send_req(request: Request) -> Result where T: DeserializeOwned, @@ -114,23 +123,36 @@ where req_with_body(api_name, body, Request::patch).await } -pub async fn post(api_name: &str, body: U) -> Result +pub async fn post(api_name: &str, body: Option) -> Result where T: DeserializeOwned, U: Serialize, { - req_with_body(api_name, body, Request::post).await + match body { + Some(body) => req_with_body(api_name, body, Request::post).await, + None => req(api_name, Request::post).await, + } } -pub async fn delete(api_name: &str, body: U) -> Result +pub async fn delete(api_name: &str, body: Option) -> Result where T: DeserializeOwned, U: Serialize, { - req_with_body(api_name, body, Request::delete).await + match body { + Some(body) => req_with_body(api_name, body, Request::delete).await, + None => req(api_name, Request::delete).await, + } } -pub async fn get(api_name: &str, params: U) -> Result +pub async fn get(api_name: &str) -> Result +where + T: DeserializeOwned, +{ + req(api_name, Request::get).await +} + +pub async fn get_with_params(api_name: &str, params: U) -> Result where T: DeserializeOwned, U: Serialize, diff --git a/frontend/src/shopping_list.rs b/frontend/src/shopping_list.rs index 9674b1d..b9251cf 100644 --- a/frontend/src/shopping_list.rs +++ b/frontend/src/shopping_list.rs @@ -29,7 +29,7 @@ impl ShoppingList { if self.is_local { Ok(vec![]) // TODO } else { - Ok(request::get("shopping_list", ()).await?) + Ok(request::get("shopping_list").await?) } }