diff --git a/Cargo.lock b/Cargo.lock index 5afc5d7..0659a49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -414,9 +414,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.9.1" +version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" +checksum = "5c8214115b7bf84099f1309324e63141d4c5d7cc26862f97a0a857dbefe165bd" dependencies = [ "serde", ] @@ -499,9 +499,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.2.23" +version = "1.2.22" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5f4ac86a9e5bc1e2b3449ab9d7d3a6a405e3d1bb28d7b9be8614f55846ae3766" +checksum = "32db95edf998450acc7881c932f94cd9b05c87b4b2599e8bab064753da4acfd1" dependencies = [ "shlex", ] @@ -949,9 +949,9 @@ checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" [[package]] name = "errno" -version = "0.3.12" +version = "0.3.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cea14ef9355e3beab063703aa9dab15afd25f0667c341310c1e5274bb1d0da18" +checksum = "976dd42dc7e85965fe702eb8164f21f450704bdde31faefd6471dba214cb594e" dependencies = [ "libc", "windows-sys 0.59.0", @@ -1502,9 +1502,9 @@ dependencies = [ [[package]] name = "hyper-util" -version = "0.1.12" +version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9f1e950e0d9d1d3c47184416723cf29c0d1f93bd8cccf37e4beb6b44f31710" +checksum = "497bbc33a26fdd4af9ed9c70d63f61cf56a938375fbb32df34db9b1cd6d643f2" dependencies = [ "bytes", "futures-channel", @@ -1593,9 +1593,9 @@ checksum = "00210d6893afc98edb752b664b8890f0ef174c8adbb8d0be9710fa66fbbf72d3" [[package]] name = "icu_properties" -version = "2.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "016c619c1eeb94efb86809b015c58f479963de65bdb6253345c1a1276f22e32b" +checksum = "2549ca8c7241c82f59c80ba2a6f415d931c5b58d24fb8412caa1a1f02c49139a" dependencies = [ "displaydoc", "icu_collections", @@ -1609,9 +1609,9 @@ dependencies = [ [[package]] name = "icu_properties_data" -version = "2.0.1" +version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "298459143998310acd25ffe6810ed544932242d3f07083eee1084d83a71bd632" +checksum = "8197e866e47b68f8f7d95249e172903bec06004b18b2937f1095d40a0c57de04" [[package]] name = "icu_provider" @@ -2448,7 +2448,7 @@ version = "0.5.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "928fca9cf2aa042393a8325b9ead81d2f0df4cb12e1e24cef072922ccd99c5af" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", ] [[package]] @@ -2525,7 +2525,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "beceb6f7bf81c73e73aeef6dd1356d9a1b2b4909e1f0fc3e59b034f9572d7b7f" dependencies = [ "base64", - "bitflags 2.9.1", + "bitflags 2.9.0", "serde", "serde_derive", "unicode-ident", @@ -2584,7 +2584,7 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c71e83d6afe7ff64890ec6b71d6a69bb8a610ab78ce364b3352876bb4c801266" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "errno", "libc", "linux-raw-sys", @@ -2674,7 +2674,7 @@ version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd568a4c9bb598e291a08244a5c1f5a8a6650bee243b5b0f8dbb3d9cc1d87fe8" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "cssparser", "derive_more", "fxhash", @@ -2983,7 +2983,7 @@ checksum = "0afdd3aa7a629683c2d750c2df343025545087081ab5942593a5288855b1b7a7" dependencies = [ "atoi", "base64", - "bitflags 2.9.1", + "bitflags 2.9.0", "byteorder", "bytes", "chrono", @@ -3026,7 +3026,7 @@ checksum = "a0bedbe1bbb5e2615ef347a5e9d8cd7680fb63e77d9dafc0f29be15e53f1ebe6" dependencies = [ "atoi", "base64", - "bitflags 2.9.1", + "bitflags 2.9.0", "byteorder", "chrono", "crc", @@ -3479,7 +3479,7 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fdb0c213ca27a9f57ab69ddb290fd80d970922355b83ae380b395d3986b8a2e" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", "bytes", "futures-util", "http 1.3.1", @@ -3904,9 +3904,9 @@ checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" [[package]] name = "windows-core" -version = "0.61.2" +version = "0.61.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" +checksum = "4763c1de310c86d75a878046489e2e5ba02c649d185f21c67d4cf8a56d098980" dependencies = [ "windows-implement", "windows-interface", @@ -3945,18 +3945,18 @@ checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" [[package]] name = "windows-result" -version = "0.3.4" +version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" +checksum = "c64fd11a4fd95df68efcfee5f44a294fe71b8bc6a91993e2791938abcc712252" dependencies = [ "windows-link", ] [[package]] name = "windows-strings" -version = "0.4.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" +checksum = "7a2ba9642430ee452d5a7aa78d72907ebe8cfda358e8cb7918a2050581322f97" dependencies = [ "windows-link", ] @@ -4124,7 +4124,7 @@ version = "0.39.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1" dependencies = [ - "bitflags 2.9.1", + "bitflags 2.9.0", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index a72e16d..c97d7ad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -12,10 +12,6 @@ debug = true # To generate complete PDB file used by flamegraph. [profile.release.package.frontend] codegen-units = 1 - -# Commented out: it generates some error message like: -# "[wasm-validator error in function 707] unexpected false: memory.copy operations require bulk memory operations [--enable-bulk-memory-opt], on..." -# strip = true - +strip = true # To reduce the 'wasm' file size. opt-level = "z" diff --git a/backend/sql/version_1.sql b/backend/sql/version_1.sql index c73ab94..21b1a89 100644 --- a/backend/sql/version_1.sql +++ b/backend/sql/version_1.sql @@ -111,17 +111,15 @@ CREATE TABLE [Image] ( CREATE TABLE [Tag] ( [id] INTEGER PRIMARY KEY, [name] TEXT NOT NULL, - - -- Not needed, the lang is defined by the recipes linked to it - -- (more than one language can be associaded to a tag). - -- [lang] TEXT NOT NULL DEFAULT 'en', + [lang] TEXT NOT NULL DEFAULT 'en', CHECK ( - length([name]) <= 31 + length([name]) <= 31 AND + length([lang]) = 2 ) ) STRICT; -CREATE UNIQUE INDEX [Tag_name_index] ON [Tag]([name]); +CREATE UNIQUE INDEX [Tag_name_lang_index] ON [Tag]([name], [lang]); CREATE TABLE [RecipeTag] ( [id] INTEGER PRIMARY KEY, @@ -135,18 +133,6 @@ CREATE TABLE [RecipeTag] ( FOREIGN KEY([tag_id]) REFERENCES [Tag]([id]) ON DELETE CASCADE ) STRICT; -CREATE INDEX [RecipeTag_tag_id_index] ON [RecipeTag]([tag_id]); - --- Delete all tags without references. -CREATE TRIGGER [RecipeTag_trigger_delete] -AFTER DELETE -ON [RecipeTag] -BEGIN - DELETE FROM [Tag] WHERE - [id] = OLD.[tag_id] AND - (SELECT COUNT(*) = 0 FROM [RecipeTag] WHERE [tag_id] = OLD.[tag_id]); -END; - CREATE TABLE [Group] ( [id] INTEGER PRIMARY KEY, diff --git a/backend/src/app.rs b/backend/src/app.rs index d720b70..4973419 100644 --- a/backend/src/app.rs +++ b/backend/src/app.rs @@ -133,99 +133,92 @@ 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/{id}/title", - patch(services::ron::recipe::set_title), - ) - .route( - "/recipe/{id}/description", + "/recipe/description", patch(services::ron::recipe::set_description), ) .route( - "/recipe/{id}/servings", + "/recipe/servings", patch(services::ron::recipe::set_servings), ) .route( - "/recipe/{id}/estimated_time", + "/recipe/estimated_time", patch(services::ron::recipe::set_estimated_time), ) .route( - "/recipe/{id}/tags", + "/recipe/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/{id}/difficulty", + "/recipe/difficulty", patch(services::ron::recipe::set_difficulty), ) .route( - "/recipe/{id}/language", + "/recipe/language", patch(services::ron::recipe::set_language), ) .route( - "/recipe/{id}/is_public", + "/recipe/is_public", patch(services::ron::recipe::set_is_public), ) - .route("/recipe/{id}", delete(services::ron::recipe::rm)) + .route("/recipe", delete(services::ron::recipe::rm)) + .route("/recipe/groups", get(services::ron::recipe::get_groups)) .route( - "/recipe/{id}/groups", - get(services::ron::recipe::get_groups), + "/recipe/group", + post(services::ron::recipe::add_group).delete(services::ron::recipe::rm_group), ) - .route("/recipe/{id}/group", post(services::ron::recipe::add_group)) .route( - "/groups/order", - patch(services::ron::recipe::set_groups_order), - ) - .route("/group/{id}", delete(services::ron::recipe::rm_group)) - .route( - "/group/{id}/name", + "/recipe/group_name", patch(services::ron::recipe::set_group_name), ) .route( - "/group/{id}/comment", + "/recipe/group_comment", patch(services::ron::recipe::set_group_comment), ) - .route("/group/{id}/step", post(services::ron::recipe::add_step)) .route( - "/steps/order", - patch(services::ron::recipe::set_steps_order), + "/recipe/groups_order", + patch(services::ron::recipe::set_groups_order), ) - .route("/step/{id}", delete(services::ron::recipe::rm_step)) .route( - "/step/{id}/action", + "/recipe/step", + post(services::ron::recipe::add_step).delete(services::ron::recipe::rm_step), + ) + .route( + "/recipe/step_action", patch(services::ron::recipe::set_step_action), ) .route( - "/step/{id}/ingredient", - post(services::ron::recipe::add_ingredient), + "/recipe/steps_order", + patch(services::ron::recipe::set_steps_order), ) .route( - "/ingredients/order", - patch(services::ron::recipe::set_ingredients_order), + "/recipe/ingredient", + post(services::ron::recipe::add_ingredient) + .delete(services::ron::recipe::rm_ingredient), ) .route( - "/ingredient/{id}", - delete(services::ron::recipe::rm_ingredient), - ) - .route( - "/ingredient/{id}/name", + "/recipe/ingredient_name", patch(services::ron::recipe::set_ingredient_name), ) .route( - "/ingredient/{id}/comment", + "/recipe/ingredient_comment", patch(services::ron::recipe::set_ingredient_comment), ) .route( - "/ingredient/{id}/quantity", + "/recipe/ingredient_quantity", patch(services::ron::recipe::set_ingredient_quantity), ) .route( - "/ingredient/{id}/unit", + "/recipe/ingredient_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), @@ -240,7 +233,7 @@ pub fn make_service( "/shopping_list/checked", patch(services::ron::shopping_list::set_entry_checked), ) - .route("/translation/{id}", get(services::ron::get_translation)) + .route("/translation", 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 b3e21c8..44ca62e 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 = normalize_tag(tag.as_ref()); + let tag = tag.as_ref().trim().to_lowercase(); let tag_id: i64 = if let Some(tag_id) = sqlx::query_scalar("SELECT [id] FROM [Tag] WHERE [name] = $1") .bind(&tag) @@ -471,7 +471,6 @@ 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] @@ -484,7 +483,7 @@ RETURNING [RecipeTag].[tag_id] "#, ) .bind(recipe_id) - .bind(tag) + .bind(tag.as_ref()) .fetch_optional(&mut *tx) .await? { @@ -941,10 +940,6 @@ ORDER BY [date] } } -pub fn normalize_tag(tag: &str) -> String { - tag.to_lowercase() -} - #[cfg(test)] mod tests { use super::*; diff --git a/backend/src/log.rs b/backend/src/log.rs index fe2917c..720b2b2 100644 --- a/backend/src/log.rs +++ b/backend/src/log.rs @@ -94,16 +94,9 @@ impl Log { .with_thread_ids(TRACING_DISPLAY_THREAD) .with_thread_names(TRACING_DISPLAY_THREAD); - if let Err(error) = tracing_subscriber::Registry::default() + tracing_subscriber::Registry::default() .with(layer_stdout) - .try_init() - { - // It happens during the integration tests. - eprintln!( - "Error during initialization of the tracing subscriber: {}", - error - ); - } + .init(); Log::StdoutOnly } diff --git a/backend/src/services/ron/mod.rs b/backend/src/services/ron/mod.rs index e48f7b4..32995b0 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, Path, Query, State}, + extract::{Extension, 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(lang): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result<(CookieJar, StatusCode)> { let mut jar = CookieJar::from_headers(&headers); if let Some(user) = context.user { - connection.set_user_lang(user.id, &lang).await?; + connection.set_user_lang(user.id, &ron.lang).await?; } else { // Only set the cookie if the user is not connected. - let cookie = Cookie::build((consts::COOKIE_LANG_NAME, lang)) + let cookie = Cookie::build((consts::COOKIE_LANG_NAME, ron.lang)) .same_site(SameSite::Lax) .path("/"); jar = jar.add(cookie); @@ -43,9 +43,11 @@ pub async fn set_lang( #[debug_handler] pub async fn get_translation( Extension(context): Extension, - Path(id): Path, + translation_id: Query, ) -> Result { - Ok(ron_response_ok(context.tr.t_from_id(id))) + Ok(ron_response_ok(ron_api::Value { + value: context.tr.t_from_id(translation_id.id), + })) } /*** 404 ***/ diff --git a/backend/src/services/ron/recipe.rs b/backend/src/services/ron/recipe.rs index 52d0473..fafda6c 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, Path, State}, + extract::{Extension, State}, http::StatusCode, response::{IntoResponse, Result}, }; use axum_extra::extract::Query; use common::ron_api; -use tracing::warn; +// use tracing::{event, Level}; use crate::{ app::Context, data::db, data::model, ron_extractor::ExtractRon, ron_utils::ron_response_ok, @@ -19,22 +19,23 @@ use super::rights::*; #[debug_handler] pub async fn get_titles( State(connection): State, - recipe_ids: Query>, + recipe_ids: Query, ) -> Result { - Ok(ron_response_ok( - connection.get_recipe_titles(&recipe_ids).await?, - )) + Ok(ron_response_ok(ron_api::Strings { + strs: connection.get_recipe_titles(&recipe_ids.ids).await?, + })) } #[debug_handler] pub async fn set_title( State(connection): State, Extension(context): Extension, - Path(recipe_id): Path, - ExtractRon(title): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, recipe_id).await?; - connection.set_recipe_title(recipe_id, &title).await?; + check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; + connection + .set_recipe_title(ron.recipe_id, &ron.title) + .await?; Ok(StatusCode::OK) } @@ -42,12 +43,11 @@ pub async fn set_title( pub async fn set_description( State(connection): State, Extension(context): Extension, - Path(recipe_id): Path, - ExtractRon(description): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, recipe_id).await?; + check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; connection - .set_recipe_description(recipe_id, &description) + .set_recipe_description(ron.recipe_id, &ron.description) .await?; Ok(StatusCode::OK) } @@ -56,11 +56,12 @@ pub async fn set_description( pub async fn set_servings( State(connection): State, Extension(context): Extension, - Path(recipe_id): Path, - ExtractRon(servings): ExtractRon>, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, recipe_id).await?; - connection.set_recipe_servings(recipe_id, servings).await?; + check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; + connection + .set_recipe_servings(ron.recipe_id, ron.servings) + .await?; Ok(StatusCode::OK) } @@ -68,12 +69,11 @@ pub async fn set_servings( pub async fn set_estimated_time( State(connection): State, Extension(context): Extension, - Path(recipe_id): Path, - ExtractRon(time): ExtractRon>, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, recipe_id).await?; + check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; connection - .set_recipe_estimated_time(recipe_id, time) + .set_recipe_estimated_time(ron.recipe_id, ron.estimated_time) .await?; Ok(StatusCode::OK) } @@ -81,22 +81,30 @@ pub async fn set_estimated_time( #[debug_handler] pub async fn get_tags( State(connection): State, - Path(recipe_id): Path, + recipe_id: Query, ) -> Result { - Ok(ron_response_ok( - connection.get_recipes_tags(recipe_id).await?, - )) + Ok(ron_response_ok(ron_api::Tags { + recipe_id: recipe_id.id, + tags: connection.get_recipes_tags(recipe_id.id).await?, + })) } #[debug_handler] pub async fn add_tags( State(connection): State, Extension(context): Extension, - Path(recipe_id): Path, - ExtractRon(tags): ExtractRon>, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, recipe_id).await?; - connection.add_recipe_tags(recipe_id, &tags).await?; + 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?; Ok(StatusCode::OK) } @@ -104,11 +112,18 @@ pub async fn add_tags( pub async fn rm_tags( State(connection): State, Extension(context): Extension, - Path(recipe_id): Path, - ExtractRon(tags): ExtractRon>, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, recipe_id).await?; - connection.rm_recipe_tags(recipe_id, &tags).await?; + 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?; Ok(StatusCode::OK) } @@ -116,12 +131,11 @@ pub async fn rm_tags( pub async fn set_difficulty( State(connection): State, Extension(context): Extension, - Path(recipe_id): Path, - ExtractRon(difficulty): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, recipe_id).await?; + check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; connection - .set_recipe_difficulty(recipe_id, difficulty) + .set_recipe_difficulty(ron.recipe_id, ron.difficulty) .await?; Ok(StatusCode::OK) } @@ -130,19 +144,20 @@ pub async fn set_difficulty( pub async fn set_language( State(connection): State, Extension(context): Extension, - Path(recipe_id): Path, - ExtractRon(lang): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { if !crate::translation::available_codes() .iter() - .any(|&l| l == lang) + .any(|&l| l == ron.lang) { - warn!("Can't find language: {}", lang); + // TODO: log? return Ok(StatusCode::BAD_REQUEST); } - check_user_rights_recipe(&connection, &context.user, recipe_id).await?; - connection.set_recipe_language(recipe_id, &lang).await?; + check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; + connection + .set_recipe_language(ron.recipe_id, &ron.lang) + .await?; Ok(StatusCode::OK) } @@ -150,12 +165,11 @@ pub async fn set_language( pub async fn set_is_public( State(connection): State, Extension(context): Extension, - Path(recipe_id): Path, - ExtractRon(is_public): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, recipe_id).await?; + check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; connection - .set_recipe_is_public(recipe_id, is_public) + .set_recipe_is_public(ron.recipe_id, ron.is_public) .await?; Ok(StatusCode::OK) } @@ -164,10 +178,10 @@ pub async fn set_is_public( pub async fn rm( State(connection): State, Extension(context): Extension, - Path(recipe_id): Path, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, recipe_id).await?; - connection.rm_recipe(recipe_id).await?; + check_user_rights_recipe(&connection, &context.user, ron.id).await?; + connection.rm_recipe(ron.id).await?; Ok(StatusCode::OK) } @@ -211,12 +225,12 @@ impl From for ron_api::Ingredient { #[debug_handler] pub async fn get_groups( State(connection): State, - Path(recipe_id): Path, + recipe_id: Query, ) -> Result { // Here we don't check user rights on purpose. Ok(ron_response_ok( connection - .get_groups(recipe_id) + .get_groups(recipe_id.id) .await? .into_iter() .map(ron_api::Group::from) @@ -228,32 +242,22 @@ pub async fn get_groups( pub async fn add_group( State(connection): State, Extension(context): Extension, - Path(recipe_id): Path, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe(&connection, &context.user, recipe_id).await?; - let id = connection.add_recipe_group(recipe_id).await?; - Ok(ron_response_ok(id)) -} + check_user_rights_recipe(&connection, &context.user, ron.id).await?; + let id = connection.add_recipe_group(ron.id).await?; -#[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) + Ok(ron_response_ok(ron_api::Id { id })) } #[debug_handler] pub async fn rm_group( State(connection): State, Extension(context): Extension, - Path(groupe_id): Path, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe_group(&connection, &context.user, groupe_id).await?; - connection.rm_recipe_group(groupe_id).await?; + check_user_rights_recipe_group(&connection, &context.user, ron.id).await?; + connection.rm_recipe_group(ron.id).await?; Ok(StatusCode::OK) } @@ -261,11 +265,10 @@ pub async fn rm_group( pub async fn set_group_name( State(connection): State, Extension(context): Extension, - Path(group_id): Path, - ExtractRon(name): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe_group(&connection, &context.user, group_id).await?; - connection.set_group_name(group_id, &name).await?; + check_user_rights_recipe_group(&connection, &context.user, ron.group_id).await?; + connection.set_group_name(ron.group_id, &ron.name).await?; Ok(StatusCode::OK) } @@ -273,11 +276,23 @@ pub async fn set_group_name( pub async fn set_group_comment( State(connection): State, Extension(context): Extension, - Path(group_id): Path, - ExtractRon(comment): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe_group(&connection, &context.user, group_id).await?; - connection.set_group_comment(group_id, &comment).await?; + 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?; Ok(StatusCode::OK) } @@ -285,32 +300,22 @@ pub async fn set_group_comment( pub async fn add_step( State(connection): State, Extension(context): Extension, - Path(group_id): Path, + ExtractRon(ron): ExtractRon, ) -> Result { - 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)) -} + check_user_rights_recipe_group(&connection, &context.user, ron.id).await?; + let id = connection.add_recipe_step(ron.id).await?; -#[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) + Ok(ron_response_ok(ron_api::Id { id })) } #[debug_handler] pub async fn rm_step( State(connection): State, Extension(context): Extension, - Path(step_id): Path, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe_step(&connection, &context.user, step_id).await?; - connection.rm_recipe_step(step_id).await?; + check_user_rights_recipe_step(&connection, &context.user, ron.id).await?; + connection.rm_recipe_step(ron.id).await?; Ok(StatusCode::OK) } @@ -318,11 +323,21 @@ pub async fn rm_step( pub async fn set_step_action( State(connection): State, Extension(context): Extension, - Path(step_id): Path, - ExtractRon(action): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe_step(&connection, &context.user, step_id).await?; - connection.set_step_action(step_id, &action).await?; + 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?; Ok(StatusCode::OK) } @@ -330,32 +345,21 @@ pub async fn set_step_action( pub async fn add_ingredient( State(connection): State, Extension(context): Extension, - Path(step_id): Path, + ExtractRon(ron): ExtractRon, ) -> Result { - 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) + 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 })) } #[debug_handler] pub async fn rm_ingredient( State(connection): State, Extension(context): Extension, - Path(ingredient_id): Path, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe_ingredient(&connection, &context.user, ingredient_id).await?; - connection.rm_recipe_ingredient(ingredient_id).await?; + check_user_rights_recipe_ingredient(&connection, &context.user, ron.id).await?; + connection.rm_recipe_ingredient(ron.id).await?; Ok(StatusCode::OK) } @@ -363,11 +367,12 @@ pub async fn rm_ingredient( pub async fn set_ingredient_name( State(connection): State, Extension(context): Extension, - Path(ingredient_id): Path, - ExtractRon(name): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe_ingredient(&connection, &context.user, ingredient_id).await?; - connection.set_ingredient_name(ingredient_id, &name).await?; + check_user_rights_recipe_ingredient(&connection, &context.user, ron.ingredient_id).await?; + connection + .set_ingredient_name(ron.ingredient_id, &ron.name) + .await?; Ok(StatusCode::OK) } @@ -375,12 +380,11 @@ pub async fn set_ingredient_name( pub async fn set_ingredient_comment( State(connection): State, Extension(context): Extension, - Path(ingredient_id): Path, - ExtractRon(comment): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe_ingredient(&connection, &context.user, ingredient_id).await?; + check_user_rights_recipe_ingredient(&connection, &context.user, ron.ingredient_id).await?; connection - .set_ingredient_comment(ingredient_id, &comment) + .set_ingredient_comment(ron.ingredient_id, &ron.comment) .await?; Ok(StatusCode::OK) } @@ -389,12 +393,11 @@ pub async fn set_ingredient_comment( pub async fn set_ingredient_quantity( State(connection): State, Extension(context): Extension, - Path(ingredient_id): Path, - ExtractRon(quantity): ExtractRon>, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe_ingredient(&connection, &context.user, ingredient_id).await?; + check_user_rights_recipe_ingredient(&connection, &context.user, ron.ingredient_id).await?; connection - .set_ingredient_quantity(ingredient_id, quantity) + .set_ingredient_quantity(ron.ingredient_id, ron.quantity) .await?; Ok(StatusCode::OK) } @@ -403,10 +406,22 @@ pub async fn set_ingredient_quantity( pub async fn set_ingredient_unit( State(connection): State, Extension(context): Extension, - Path(ingredient_id): Path, - ExtractRon(unit): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { - check_user_rights_recipe_ingredient(&connection, &context.user, ingredient_id).await?; - connection.set_ingredient_unit(ingredient_id, &unit).await?; + 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?; Ok(StatusCode::OK) } diff --git a/backend/tests/http.rs b/backend/tests/http.rs index 0af4e20..5e07b6c 100644 --- a/backend/tests/http.rs +++ b/backend/tests/http.rs @@ -257,62 +257,107 @@ async fn create_recipe_and_edit_it() -> Result<(), Box> { response.assert_status_ok(); let response = server - .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")) + .patch("/ron-api/recipe/title") .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) .bytes( - ron_api::to_string(ron_api::Difficulty::Hard) - .unwrap() - .into(), + ron_api::to_string(ron_api::SetRecipeTitle { + recipe_id, + title: "AAA".into(), + }) + .unwrap() + .into(), ) .await; response.assert_status_ok(); let response = server - .patch(&format!("/ron-api/recipe/{recipe_id}/language")) + .patch("/ron-api/recipe/description") .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes(ron_api::to_string("fr").unwrap().into()) + .bytes( + ron_api::to_string(ron_api::SetRecipeDescription { + recipe_id, + description: "BBB".into(), + }) + .unwrap() + .into(), + ) .await; response.assert_status_ok(); let response = server - .patch(&format!("/ron-api/recipe/{recipe_id}/is_public")) + .patch("/ron-api/recipe/servings") .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes(ron_api::to_string(true).unwrap().into()) + .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(), + ) .await; response.assert_status_ok(); @@ -385,61 +430,70 @@ async fn recipe_tags() -> Result<(), Box> { // Tags list must be empty. let response = server - .get(&format!("/ron-api/recipe/{recipe_id}/tags")) + .get("/ron-api/recipe/tags") .add_cookie(cookie.clone()) + .add_query_param("id", recipe_id) .await; response.assert_status_ok(); - let tags: Vec = ron::de::from_bytes(response.as_bytes()).unwrap(); - assert!(tags.is_empty()); + let tags: ron_api::Tags = ron::de::from_bytes(response.as_bytes()).unwrap(); + assert!(tags.tags.is_empty()); // Act. // Add some tags. let response = server - .post(&format!("/ron-api/recipe/{recipe_id}/tags")) + .post("/ron-api/recipe/tags") .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) .bytes( - ron_api::to_string(vec!["ABC".to_string(), "xyz".to_string()]) - .unwrap() - .into(), + ron_api::to_string(ron_api::Tags { + recipe_id, + tags: vec!["ABC".into(), "xyz".into()], + }) + .unwrap() + .into(), ) .await; // Assert. response.assert_status_ok(); let response = server - .get(&format!("/ron-api/recipe/{recipe_id}/tags")) + .get("/ron-api/recipe/tags") .add_cookie(cookie.clone()) + .add_query_param("id", recipe_id) .await; response.assert_status_ok(); - 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())); + 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())); // Act. // Remove some tags. let response = server - .delete(&format!("/ron-api/recipe/{recipe_id}/tags")) + .delete("/ron-api/recipe/tags") .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) .bytes( - ron_api::to_string(vec!["XYZ".to_string(), "qwe".to_string()]) - .unwrap() - .into(), + ron_api::to_string(ron_api::Tags { + recipe_id, + tags: vec!["XYZ".into(), "qwe".into()], + }) + .unwrap() + .into(), ) .await; // Assert. response.assert_status_ok(); let response = server - .get(&format!("/ron-api/recipe/{recipe_id}/tags")) + .get("/ron-api/recipe/tags") .add_cookie(cookie.clone()) + .add_query_param("id", recipe_id) .await; response.assert_status_ok(); - let tags: Vec = ron::de::from_bytes(response.as_bytes()).unwrap(); - assert_eq!(tags.len(), 1); - assert_eq!(tags[0], "abc".to_string()); + 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()); Ok(()) } diff --git a/common/src/ron_api.rs b/common/src/ron_api.rs index a741552..175f48e 100644 --- a/common/src/ron_api.rs +++ b/common/src/ron_api.rs @@ -2,8 +2,29 @@ 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 { @@ -11,6 +32,11 @@ 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, @@ -19,6 +45,30 @@ 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 { @@ -46,6 +96,72 @@ 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 5f2a69c..ce7f79b 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -1,9 +1,6 @@ - + diff --git a/frontend/src/calendar.rs b/frontend/src/calendar.rs index c0cb8bd..dd77168 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", Some(body)).await; + let _ = request::delete::<(), _>("calendar/scheduled_recipe", body).await; window().location().reload().unwrap(); } }); diff --git a/frontend/src/lib.rs b/frontend/src/lib.rs index 1904ed3..c0567c3 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -1,3 +1,4 @@ +use common::ron_api; use gloo::{console::log, events::EventListener, utils::window}; use utils::by_id; use wasm_bindgen::prelude::*; @@ -86,17 +87,18 @@ pub fn main() -> Result<(), JsValue> { // Request the message to display. spawn_local(async move { - let translation: String = request::get(&format!("translation/{mess_id}")) - .await - .unwrap(); + let translation: ron_api::Value = + request::get("translation", ron_api::Id { id: 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, + &translation.value, ); } else { - toast::show_message(&translation); + toast::show_message(&translation.value); } }); } @@ -105,10 +107,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", &lang).await; + let _ = request::put::<(), _>("lang", body).await; window() .location() diff --git a/frontend/src/pages/recipe_edit.rs b/frontend/src/pages/recipe_edit.rs index 2ff7491..4524e0d 100644 --- a/frontend/src/pages/recipe_edit.rs +++ b/frontend/src/pages/recipe_edit.rs @@ -30,14 +30,18 @@ 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 title = title.value(); + let body = ron_api::SetRecipeTitle { + recipe_id, + title: title.value(), + }; spawn_local(async move { - let _ = - request::patch::<(), _>(&format!("recipe/{recipe_id}/title"), title).await; + let _ = request::patch::<(), _>("recipe/title", body).await; reload_recipes_list(recipe_id).await; }); } @@ -53,13 +57,12 @@ pub fn setup_page(recipe_id: i64) { EventListener::new(&description.clone(), "blur", move |_event| { if description.value() != current_description { current_description = description.value(); - let description = description.value(); + let body = ron_api::SetRecipeDescription { + recipe_id, + description: description.value(), + }; spawn_local(async move { - let _ = request::patch::<(), _>( - &format!("recipe/{recipe_id}/description"), - description, - ) - .await; + let _ = request::patch::<(), _>("recipe/description", body).await; }); } }) @@ -84,10 +87,12 @@ 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::<(), _>(&format!("recipe/{recipe_id}/servings"), servings) - .await; + let _ = request::patch::<(), _>("recipe/servings", body).await; }); } }) @@ -113,12 +118,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::<(), _>( - &format!("recipe/{recipe_id}/estimated_time"), - time, - ) - .await; + let _ = request::patch::<(), _>("recipe/estimated_time", body).await; }); } }) @@ -133,14 +138,16 @@ pub fn setup_page(recipe_id: i64) { EventListener::new(&difficulty.clone(), "blur", move |_event| { if difficulty.value() != current_difficulty { current_difficulty = difficulty.value(); - let difficulty = - ron_api::Difficulty::try_from(difficulty.value().parse::().unwrap()); - spawn_local(async move { - let _ = request::patch::<(), _>( - &format!("recipe/{recipe_id}/difficulty"), - difficulty, + + let body = ron_api::SetRecipeDifficulty { + recipe_id, + difficulty: ron_api::Difficulty::try_from( + current_difficulty.parse::().unwrap(), ) - .await; + .unwrap(), + }; + spawn_local(async move { + let _ = request::patch::<(), _>("recipe/difficulty", body).await; }); } }) @@ -150,21 +157,24 @@ pub fn setup_page(recipe_id: i64) { // Tags. { spawn_local(async move { - let tags: Vec = request::get(&format!("recipe/{recipe_id}/tags")) + let tags: ron_api::Tags = request::get("recipe/tags", ron_api::Id { id: recipe_id }) .await .unwrap(); - create_tag_elements(recipe_id, &tags); + create_tag_elements(recipe_id, &tags.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(); - let _ = - request::post::<(), _>(&format!("recipe/{recipe_id}/tags"), Some(&tag_list)) - .await; - create_tag_elements(recipe_id, &tag_list); - + 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); + } by_id::("input-tags").set_value(""); }); } @@ -204,11 +214,13 @@ pub fn setup_page(recipe_id: i64) { EventListener::new(&language.clone(), "blur", move |_event| { if language.value() != current_language { current_language = language.value(); - let language = language.value(); + + let body = ron_api::SetRecipeLanguage { + recipe_id, + lang: language.value(), + }; spawn_local(async move { - let _ = - request::patch::<(), _>(&format!("recipe/{recipe_id}/language"), language) - .await; + let _ = request::patch::<(), _>("recipe/language", body).await; }); } }) @@ -219,11 +231,12 @@ 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 is_public = is_public.checked(); + let body = ron_api::SetRecipeIsPublic { + recipe_id, + is_public: is_public.checked(), + }; spawn_local(async move { - let _ = - request::patch::<(), _>(&format!("recipe/{recipe_id}/is_public"), is_public) - .await; + let _ = request::patch::<(), _>("recipe/is_public", body).await; reload_recipes_list(recipe_id).await; }); }) @@ -248,8 +261,8 @@ pub fn setup_page(recipe_id: i64) { .await .is_some() { - if let Ok(()) = request::delete::<_, ()>(&format!("recipe/{recipe_id}"), None).await - { + let body = ron_api::Id { id: recipe_id }; + if let Ok(()) = request::delete::<(), _>("recipe", body).await { window() .location() .set_href(&format!( @@ -271,7 +284,7 @@ pub fn setup_page(recipe_id: i64) { { spawn_local(async move { let groups: Vec = - request::get(&format!("recipe/{recipe_id}/groups")) + request::get("recipe/groups", ron_api::Id { id: recipe_id }) .await .unwrap(); @@ -297,12 +310,11 @@ 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 id: i64 = request::post::<_, ()>(&format!("recipe/{recipe_id}/group"), None) - .await - .unwrap(); + let response: ron_api::Id = request::post("recipe/group", body).await.unwrap(); create_group_element(&ron_api::Group { - id, + id: response.id, name: "".to_string(), comment: "".to_string(), steps: vec![], @@ -323,13 +335,14 @@ fn create_group_element(group: &ron_api::Group) -> Element { set_draggable(&group_element, "group", |_element| { spawn_local(async move { - let ids: Vec = by_id::("groups-container") + let ids = by_id::("groups-container") .selector_all::(".group") .into_iter() .map(|e| e.id()[6..].parse::().unwrap()) .collect(); - let _ = request::patch::<(), _>("groups/order", ids).await; + let body = ron_api::Ids { ids }; + let _ = request::patch::<(), _>("recipe/groups_order", body).await; }); }); @@ -340,9 +353,12 @@ 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 name = name.value(); + let body = ron_api::SetGroupName { + group_id, + name: name.value(), + }; spawn_local(async move { - let _ = request::patch::<(), _>(&format!("group/{group_id}/name"), name).await; + let _ = request::patch::<(), _>("recipe/group_name", body).await; }) } }) @@ -355,10 +371,12 @@ 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 comment = comment.value(); + let body = ron_api::SetGroupComment { + group_id, + comment: comment.value(), + }; spawn_local(async move { - let _ = - request::patch::<(), _>(&format!("group/{group_id}/comment"), comment).await; + let _ = request::patch::<(), _>("recipe/group_comment", body).await; }); } }) @@ -383,8 +401,9 @@ fn create_group_element(group: &ron_api::Group) -> Element { .await .is_some() { - let _ = request::delete::<(), ()>(&format!("group/{group_id}"), None).await; - let group_element = by_id::(&format!("group-{group_id}")); + let body = ron_api::Id { id: group_id }; + let _ = request::delete::<(), _>("recipe/group", body).await; + let group_element = by_id::(&format!("group-{}", group_id)); group_element.next_element_sibling().unwrap().remove(); group_element.remove(); } @@ -396,13 +415,12 @@ 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 id: i64 = request::post::<_, ()>(&format!("group/{group_id}/step"), None) - .await - .unwrap(); + let body = ron_api::Id { id: group_id }; + let response: ron_api::Id = request::post("recipe/step", body).await.unwrap(); create_step_element( - &selector::(&format!("#group-{group_id} .steps")), + &selector::(&format!("#group-{} .steps", group_id)), &ron_api::Step { - id, + id: response.id, action: "".to_string(), ingredients: vec![], }, @@ -456,9 +474,11 @@ where let tag_span = tag_span.clone(); let tag = tag.clone(); spawn_local(async move { - let _ = - request::delete::<(), _>(&format!("recipe/{recipe_id}/tags"), Some(vec![tag])) - .await; + let body = ron_api::Tags { + recipe_id, + tags: vec![tag], + }; + let _ = request::delete::<(), _>("recipe/tags", body).await; tag_span.remove(); }); }) @@ -475,7 +495,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: Vec = element + let ids = element .parent_element() .unwrap() .selector_all::(".step") @@ -483,7 +503,8 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element .map(|e| e.id()[5..].parse::().unwrap()) .collect(); - let _ = request::patch::<(), _>("/steps/order", ids).await; + let body = ron_api::Ids { ids }; + let _ = request::patch::<(), _>("recipe/steps_order", body).await; }); }); @@ -494,9 +515,12 @@ 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 action = action.value(); + let body = ron_api::SetStepAction { + step_id, + action: action.value(), + }; spawn_local(async move { - let _ = request::patch::<(), _>(&format!("/step/{step_id}/action"), action).await; + let _ = request::patch::<(), _>("recipe/step_action", body).await; }); } }) @@ -521,8 +545,9 @@ fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element .await .is_some() { - let _ = request::delete::<(), ()>(&format!("step/{step_id}"), None).await; - let step_element = by_id::(&format!("step-{step_id}")); + let body = ron_api::Id { id: step_id }; + let _ = request::delete::<(), _>("recipe/step", body).await; + let step_element = by_id::(&format!("step-{}", step_id)); step_element.next_element_sibling().unwrap().remove(); step_element.remove(); } @@ -534,13 +559,12 @@ 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 id: i64 = request::post::<_, ()>(&format!("step/{step_id}/ingredient"), None) - .await - .unwrap(); + let body = ron_api::Id { id: step_id }; + let response: ron_api::Id = request::post("recipe/ingredient", body).await.unwrap(); create_ingredient_element( &selector::(&format!("#step-{} .ingredients", step_id)), &ron_api::Ingredient { - id, + id: response.id, name: "".to_string(), comment: "".to_string(), quantity_value: None, @@ -563,7 +587,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: Vec = element + let ids = element .parent_element() .unwrap() .selector_all::(".ingredient") @@ -571,7 +595,8 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre .map(|e| e.id()[11..].parse::().unwrap()) .collect(); - let _ = request::patch::<(), _>("ingredients/order", ids).await; + let body = ron_api::Ids { ids }; + let _ = request::patch::<(), _>("recipe/ingredients_order", body).await; }); }); @@ -582,10 +607,12 @@ 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 name = name.value(); + let body = ron_api::SetIngredientName { + ingredient_id, + name: name.value(), + }; spawn_local(async move { - let _ = request::patch::<(), _>(&format!("ingredient/{ingredient_id}/name"), name) - .await; + let _ = request::patch::<(), _>("recipe/ingredient_name", body).await; }); } }) @@ -598,13 +625,12 @@ 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 comment = comment.value(); + let body = ron_api::SetIngredientComment { + ingredient_id, + comment: comment.value(), + }; spawn_local(async move { - let _ = request::patch::<(), _>( - &format!("ingredient/{ingredient_id}/comment"), - comment, - ) - .await; + let _ = request::patch::<(), _>("recipe/ingredient_comment", body).await; }); } }) @@ -626,9 +652,12 @@ 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::<(), _>(&format!("ingredient/{ingredient_id}/quantity"), q) - .await; + let _ = request::patch::<(), _>("recipe/ingredient_quantity", body).await; }); } }) @@ -641,10 +670,12 @@ 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 unit = unit.value(); + let body = ron_api::SetIngredientUnit { + ingredient_id, + unit: unit.value(), + }; spawn_local(async move { - let _ = request::patch::<(), _>(&format!("ingredient/{ingredient_id}/unit"), unit) - .await; + let _ = request::patch::<(), _>("recipe/ingredient_unit", body).await; }); } }) @@ -669,9 +700,9 @@ fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingre .await .is_some() { - let _ = - request::delete::<(), ()>(&format!("ingredient/{ingredient_id}"), None).await; - let ingredient_element = by_id::(&format!("ingredient-{ingredient_id}")); + let body = ron_api::Id { id: ingredient_id }; + let _ = request::delete::<(), _>("recipe/ingredient", body).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 dd34016..63a4672 100644 --- a/frontend/src/recipe_scheduler.rs +++ b/frontend/src/recipe_scheduler.rs @@ -80,22 +80,24 @@ impl RecipeScheduler { return Ok(vec![]); } - let titles: Vec = request::get_with_params( + let titles: ron_api::Strings = request::get( "recipe/titles", - recipe_ids_and_dates - .iter() - .map(|r| r.recipe_id) - .collect::>(), + ron_api::Ids { + ids: recipe_ids_and_dates + .iter() + .map(|r| r.recipe_id) + .collect::>(), + }, ) .await?; Ok(recipe_ids_and_dates .iter() - .zip(titles.into_iter()) + .zip(titles.strs.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_with_params( + let scheduled_recipes: ron_api::ScheduledRecipes = request::get( "calendar/scheduled_recipes", ron_api::DateRange { start_date, @@ -129,12 +131,12 @@ impl RecipeScheduler { } else { request::post::( "calendar/scheduled_recipe", - Some(ron_api::ScheduleRecipe { + 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 c702e54..68fe5a8 100644 --- a/frontend/src/request.rs +++ b/frontend/src/request.rs @@ -63,15 +63,6 @@ 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, @@ -123,36 +114,23 @@ where req_with_body(api_name, body, Request::patch).await } -pub async fn post(api_name: &str, body: Option) -> Result +pub async fn post(api_name: &str, body: U) -> Result where T: DeserializeOwned, U: Serialize, { - match body { - Some(body) => req_with_body(api_name, body, Request::post).await, - None => req(api_name, Request::post).await, - } + req_with_body(api_name, body, Request::post).await } -pub async fn delete(api_name: &str, body: Option) -> Result +pub async fn delete(api_name: &str, body: U) -> Result where T: DeserializeOwned, U: Serialize, { - match body { - Some(body) => req_with_body(api_name, body, Request::delete).await, - None => req(api_name, Request::delete).await, - } + req_with_body(api_name, body, Request::delete).await } -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 +pub async fn get(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 b9251cf..9674b1d 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?) } }