diff --git a/backend/sql/version_1.sql b/backend/sql/version_1.sql index 2da0db8..c73ab94 100644 --- a/backend/sql/version_1.sql +++ b/backend/sql/version_1.sql @@ -42,7 +42,7 @@ CREATE TABLE [User] ( ) ) STRICT; -CREATE INDEX [User_validation_token_index] ON [User]([validation_token]); +CREATE INDEX [validation_token_index] ON [User]([validation_token]); CREATE UNIQUE INDEX [User_email_index] ON [User]([email]); CREATE TABLE [UserLoginToken] ( @@ -93,29 +93,6 @@ CREATE TABLE [Recipe] ( FOREIGN KEY([user_id]) REFERENCES [User]([id]) ON DELETE SET NULL ) STRICT; -CREATE VIRTUAL TABLE [RecipeTitle] USING FTS5( - [title], - CONTENT = [Recipe], - CONTENT_ROWID = [id] -); - -CREATE TRIGGER [Recipe_trigger_insert] AFTER INSERT ON [Recipe] BEGIN - INSERT INTO [RecipeTitle]([rowid], [title]) - VALUES (NEW.[id], NEW.[title]); -END; - -CREATE TRIGGER [Recipe_trigger_delete] AFTER DELETE ON [Recipe] BEGIN - INSERT INTO [RecipeTitle]([RecipeTitle], [rowid], [title]) - VALUES ('delete', OLD.[id], OLD.[title]); -END; - -CREATE TRIGGER [Recipe_trigger_update] AFTER UPDATE ON [Recipe] BEGIN - INSERT INTO [RecipeTitle]([RecipeTitle], [rowid], [title]) - VALUES ('delete', OLD.[id], OLD.[title]); - - INSERT INTO [RecipeTitle]([rowid], [title]) VALUES (NEW.[id], NEW.[title]); -END; - CREATE TABLE [Image] ( [id] INTEGER PRIMARY KEY, [recipe_id] INTEGER NOT NULL, diff --git a/backend/src/app.rs b/backend/src/app.rs index f1257ce..7b4b10c 100644 --- a/backend/src/app.rs +++ b/backend/src/app.rs @@ -132,10 +132,6 @@ pub fn make_service( // Disabled: update user profile is now made with a post data ('edit_user_post'). // .route("/user/update", put(services::ron::update_user)) .route("/lang", put(services::ron::set_lang)) - .route( - "/recipe/search", - get(services::ron::recipe::search_by_title), - ) .route("/recipe/titles", get(services::ron::recipe::get_titles)) .route( "/recipe/{id}/title", diff --git a/backend/src/data/db/recipe.rs b/backend/src/data/db/recipe.rs index 037d00f..4ec6259 100644 --- a/backend/src/data/db/recipe.rs +++ b/backend/src/data/db/recipe.rs @@ -1,7 +1,7 @@ use std::u32; use chrono::prelude::*; -use common::web_api::Difficulty; +use common::ron_api::Difficulty; use itertools::Itertools; use sqlx::{Error, Sqlite}; @@ -263,8 +263,7 @@ WHERE [id] = $1 AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] r#" SELECT [id], [user_id], [title], [lang], - [estimated_time], [description], [difficulty], [servings], - [is_public] + [estimated_time], [description], [difficulty], [servings], [is_public] FROM [Recipe] WHERE [id] = $1 "#, ) @@ -281,29 +280,6 @@ FROM [Recipe] WHERE [id] = $1 } } - /// Search for recipes matching the given term in the recipe title. - /// The search term follows the syntax described here: . - pub async fn search_recipes( - &self, - lang: &str, - term: &str, - ) -> Result> { - sqlx::query_as( - r#" -SELECT [id], [recipe].[title], highlight([RecipeTitle], 0, '', '') [title_highlighted] -FROM [RecipeTitle] -INNER JOIN [Recipe] ON [Recipe].[id] = [RecipeTitle].[rowid] -WHERE [Recipe].[lang] = $1 AND [RecipeTitle] MATCH $2 -ORDER BY RANK, [Recipe].[title] - "#, - ) - .bind(lang) - .bind(term) - .fetch_all(&self.pool) - .await - .map_err(DBError::from) - } - pub async fn create_recipe(&self, user_id: i64) -> Result { let mut tx = self.tx().await?; @@ -410,8 +386,8 @@ WHERE [Recipe].[user_id] = $1 sqlx::query_scalar( r#" SELECT [name], COUNT([name]) as [nb_used] FROM [Tag] -INNER JOIN [RecipeTag] ON [RecipeTag].[tag_id] = [Tag].[id] -INNER JOIN [Recipe] ON [Recipe].[id] = [RecipeTag].[recipe_id] + INNER JOIN [RecipeTag] ON [RecipeTag].[tag_id] = [Tag].[id] + INNER JOIN [Recipe] ON [Recipe].[id] = [RecipeTag].[recipe_id] WHERE [Recipe].[lang] = $1 GROUP BY [Tag].[name] ORDER BY [nb_used] DESC, [name] @@ -994,72 +970,6 @@ mod tests { Ok(()) } - #[tokio::test] - async fn create_some_recipes_then_search_them_by_title() -> Result<()> { - let connection = Connection::new_in_memory().await?; - - let user_id = create_a_user(&connection).await?; - - async fn add_recipe(connection: &Connection, user_id: i64, title: &str) -> Result { - let recipe_id = connection.create_recipe(user_id).await?; - connection.set_recipe_title(recipe_id, title).await?; - Ok(recipe_id) - } - - let id1 = add_recipe(&connection, user_id, "AAA yyy CCC").await?; - let id2 = add_recipe(&connection, user_id, "XXX yyy ZZZ").await?; - let id3 = add_recipe(&connection, user_id, "AAA ZZZ").await?; - - { - let recipes = connection.search_recipes("en", "yyy").await?; - - assert_eq!(recipes.len(), 2); - - assert_eq!(recipes[0].id, id1); - assert_eq!(recipes[0].title, "AAA yyy CCC".to_string()); - assert_eq!( - recipes[0].title_highlighted, - "AAA yyy CCC".to_string() - ); - - assert_eq!(recipes[1].id, id2); - assert_eq!(recipes[1].title, "XXX yyy ZZZ".to_string()); - assert_eq!( - recipes[1].title_highlighted, - "XXX yyy ZZZ".to_string() - ); - } - - { - let recipes = connection.search_recipes("en", "aaa OR zzz").await?; - - assert_eq!(recipes.len(), 3); - - assert_eq!(recipes[0].id, id3); - assert_eq!(recipes[0].title, "AAA ZZZ".to_string()); - assert_eq!( - recipes[0].title_highlighted, - "AAA ZZZ".to_string() - ); - - assert_eq!(recipes[1].id, id1); - assert_eq!(recipes[1].title, "AAA yyy CCC".to_string()); - assert_eq!( - recipes[1].title_highlighted, - "AAA yyy CCC".to_string() - ); - - assert_eq!(recipes[2].id, id2); - assert_eq!(recipes[2].title, "XXX yyy ZZZ".to_string()); - assert_eq!( - recipes[2].title_highlighted, - "XXX yyy ZZZ".to_string() - ); - } - - Ok(()) - } - #[tokio::test] async fn setters() -> Result<()> { let connection = Connection::new_in_memory().await?; diff --git a/backend/src/data/model.rs b/backend/src/data/model.rs index cd30f95..01aae36 100644 --- a/backend/src/data/model.rs +++ b/backend/src/data/model.rs @@ -1,5 +1,5 @@ use chrono::prelude::*; -use common::web_api::Difficulty; +use common::ron_api::Difficulty; use sqlx::{self, FromRow}; #[derive(Debug, Clone, FromRow)] @@ -51,13 +51,6 @@ pub struct Recipe { pub groups: Vec, } -#[derive(Debug, FromRow)] -pub struct RecipeSearchResult { - pub id: i64, - pub title: String, - pub title_highlighted: String, -} - #[derive(Debug, FromRow)] pub struct Group { pub id: i64, diff --git a/backend/src/ron_utils.rs b/backend/src/ron_utils.rs index 33c1239..f9cca7b 100644 --- a/backend/src/ron_utils.rs +++ b/backend/src/ron_utils.rs @@ -3,7 +3,7 @@ use axum::{ http::{StatusCode, header}, response::{ErrorResponse, IntoResponse, Response}, }; -use common::web_api; +use common::ron_api; use ron::de::from_bytes; use serde::{Serialize, de::DeserializeOwned}; @@ -16,7 +16,7 @@ pub struct RonError { impl axum::response::IntoResponse for RonError { fn into_response(self) -> Response { - match web_api::to_string(&self) { + match ron_api::to_string(&self) { Ok(ron_as_str) => ( StatusCode::BAD_REQUEST, [(header::CONTENT_TYPE, common::consts::MIME_TYPE_RON)], @@ -62,7 +62,7 @@ pub fn ron_response(status: StatusCode, ron: T) -> Response where T: Serialize, { - match web_api::to_string(&ron) { + match ron_api::to_string(&ron) { Ok(ron_as_str) => ( status, [(header::CONTENT_TYPE, common::consts::MIME_TYPE_RON)], diff --git a/backend/src/services/fragments.rs b/backend/src/services/fragments.rs index 93d3918..0d32ace 100644 --- a/backend/src/services/fragments.rs +++ b/backend/src/services/fragments.rs @@ -4,7 +4,7 @@ use axum::{ extract::{Extension, Query, State}, response::{Html, IntoResponse}, }; -use common::web_api; +use serde::Deserialize; use crate::{ app::{Context, Result}, @@ -12,10 +12,15 @@ use crate::{ html_templates::*, }; +#[derive(Deserialize)] +pub struct CurrentRecipeId { + current_recipe_id: Option, +} + #[debug_handler] pub async fn recipes_list_fragments( State(connection): State, - params: Query, + current_recipe: Query, Extension(context): Extension, ) -> Result { Ok(Html( @@ -24,7 +29,7 @@ pub async fn recipes_list_fragments( connection, &context.user, context.tr.current_lang_code(), - params.current_recipe_id, + current_recipe.current_recipe_id, ) .await?, context, diff --git a/backend/src/services/mod.rs b/backend/src/services/mod.rs index d514328..792a0ca 100644 --- a/backend/src/services/mod.rs +++ b/backend/src/services/mod.rs @@ -6,7 +6,7 @@ use axum::{ middleware::Next, response::{Html, IntoResponse, Response}, }; -use serde::{self, Deserialize}; +use serde::Deserialize; use crate::{ app::{AppState, Context, Result}, @@ -109,7 +109,7 @@ pub async fn dev_panel( ///// LOGS ///// #[derive(Deserialize)] -pub struct LogsParams { +pub struct LogFile { #[serde(default)] pub log_file: String, } @@ -119,7 +119,7 @@ pub async fn logs( State(connection): State, State(log): State, Extension(context): Extension, - Query(params): Query, + log_file: Query, ) -> Result { if context.user.is_some() && context.user.as_ref().unwrap().is_admin { Ok(Html( @@ -133,11 +133,11 @@ pub async fn logs( .await?, context, current_log_file: match ( - params.log_file.is_empty(), + log_file.log_file.is_empty(), log.file_names().unwrap_or_default(), ) { (true, file_names) if !file_names.is_empty() => file_names[0].clone(), - _ => params.log_file.clone(), + _ => log_file.log_file.clone(), }, log, } diff --git a/backend/src/services/ron/calendar.rs b/backend/src/services/ron/calendar.rs index 1f673c9..4072973 100644 --- a/backend/src/services/ron/calendar.rs +++ b/backend/src/services/ron/calendar.rs @@ -20,10 +20,10 @@ use super::rights::*; pub async fn get_scheduled_recipes( State(connection): State, Extension(context): Extension, - date_range: Query, + date_range: Query, ) -> Result { if let Some(user) = context.user { - Ok(ron_response_ok(common::web_api::ScheduledRecipes { + Ok(ron_response_ok(common::ron_api::ScheduledRecipes { recipes: connection .get_scheduled_recipes(user.id, date_range.start_date, date_range.end_date) .await?, @@ -36,7 +36,7 @@ pub async fn get_scheduled_recipes( } } -impl From for common::web_api::ScheduleRecipeResult { +impl From for common::ron_api::ScheduleRecipeResult { fn from(db_res: data::db::recipe::AddScheduledRecipeResult) -> Self { match db_res { db::recipe::AddScheduledRecipeResult::Ok => Self::Ok, @@ -51,7 +51,7 @@ impl From for common::web_api::Sched pub async fn add_scheduled_recipe( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; if let Some(user) = context.user { @@ -65,7 +65,7 @@ pub async fn add_scheduled_recipe( ) .await .map(|res| { - ron_response_ok(common::web_api::ScheduleRecipeResult::from(res)).into_response() + ron_response_ok(common::ron_api::ScheduleRecipeResult::from(res)).into_response() }) .map_err(ErrorResponse::from) } else { @@ -77,7 +77,7 @@ pub async fn add_scheduled_recipe( pub async fn rm_scheduled_recipe( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon, + ExtractRon(ron): ExtractRon, ) -> Result { check_user_rights_recipe(&connection, &context.user, ron.recipe_id).await?; if let Some(user) = context.user { diff --git a/backend/src/services/ron/mod.rs b/backend/src/services/ron/mod.rs index 36e401b..4d5bafe 100644 --- a/backend/src/services/ron/mod.rs +++ b/backend/src/services/ron/mod.rs @@ -15,7 +15,6 @@ use crate::{ }; pub mod calendar; -mod model_converter; pub mod recipe; mod rights; pub mod shopping_list; diff --git a/backend/src/services/ron/model_converter.rs b/backend/src/services/ron/model_converter.rs deleted file mode 100644 index 7e2d5f1..0000000 --- a/backend/src/services/ron/model_converter.rs +++ /dev/null @@ -1,50 +0,0 @@ -use common::web_api; - -use crate::data::model; - -impl From for web_api::Group { - fn from(group: model::Group) -> Self { - Self { - id: group.id, - name: group.name, - comment: group.comment, - steps: group.steps.into_iter().map(web_api::Step::from).collect(), - } - } -} - -impl From for web_api::Step { - fn from(step: model::Step) -> Self { - Self { - id: step.id, - action: step.action, - ingredients: step - .ingredients - .into_iter() - .map(web_api::Ingredient::from) - .collect(), - } - } -} - -impl From for web_api::Ingredient { - fn from(ingredient: model::Ingredient) -> Self { - Self { - id: ingredient.id, - name: ingredient.name, - comment: ingredient.comment, - quantity_value: ingredient.quantity_value, - quantity_unit: ingredient.quantity_unit, - } - } -} - -impl From for web_api::RecipeSearchResult { - fn from(result: model::RecipeSearchResult) -> Self { - Self { - recipe_id: result.id, - title: result.title, - title_highlighted: result.title_highlighted, - } - } -} diff --git a/backend/src/services/ron/recipe.rs b/backend/src/services/ron/recipe.rs index 663378f..09ba0f4 100644 --- a/backend/src/services/ron/recipe.rs +++ b/backend/src/services/ron/recipe.rs @@ -5,39 +5,24 @@ use axum::{ response::{IntoResponse, Result}, }; use axum_extra::extract::Query; -use common::web_api; -use serde::Deserialize; +use common::ron_api; use tracing::warn; -use crate::{app::Context, data::db, ron_extractor::ExtractRon, ron_utils::ron_response_ok}; +use crate::{ + app::Context, data::db, data::model, ron_extractor::ExtractRon, ron_utils::ron_response_ok, +}; use super::rights::*; -#[debug_handler] -pub async fn search_by_title( - State(connection): State, - Extension(context): Extension, - Query(params): Query, -) -> Result { - Ok(ron_response_ok( - connection - .search_recipes(context.tr.current_lang_code(), ¶ms.search_term) - .await? - .into_iter() - .map(web_api::RecipeSearchResult::from) - .collect::>(), - )) -} - /// Ask recipe titles associated with each given id. The returned titles are in the same order /// as the given ids. #[debug_handler] pub async fn get_titles( State(connection): State, - Query(params): Query, + recipe_ids: Query>, ) -> Result { Ok(ron_response_ok( - connection.get_recipe_titles(¶ms.ids).await?, + connection.get_recipe_titles(&recipe_ids).await?, )) } @@ -103,23 +88,16 @@ pub async fn get_tags( )) } -#[derive(Deserialize)] -pub struct GetAllTagsParams { - nb_max_tags: Option, - lang: Option, -} - #[debug_handler] pub async fn get_all_tags( State(connection): State, Extension(context): Extension, - Query(params): Query, + nb_max_tags: Query>, + lang: Query>, ) -> Result { - let lang = params - .lang - .unwrap_or(context.tr.current_lang_code().to_string()); + let lang = lang.0.unwrap_or(context.tr.current_lang_code().to_string()); Ok(ron_response_ok( - connection.get_all_tags(&lang, params.nb_max_tags).await?, + connection.get_all_tags(&lang, nb_max_tags.0).await?, )) } @@ -152,7 +130,7 @@ pub async fn set_difficulty( State(connection): State, Extension(context): Extension, Path(recipe_id): Path, - ExtractRon(difficulty): ExtractRon, + ExtractRon(difficulty): ExtractRon, ) -> Result { check_user_rights_recipe(&connection, &context.user, recipe_id).await?; connection @@ -206,6 +184,43 @@ pub async fn rm( Ok(StatusCode::OK) } +impl From for ron_api::Group { + fn from(group: model::Group) -> Self { + Self { + id: group.id, + name: group.name, + comment: group.comment, + steps: group.steps.into_iter().map(ron_api::Step::from).collect(), + } + } +} + +impl From for ron_api::Step { + fn from(step: model::Step) -> Self { + Self { + id: step.id, + action: step.action, + ingredients: step + .ingredients + .into_iter() + .map(ron_api::Ingredient::from) + .collect(), + } + } +} + +impl From for ron_api::Ingredient { + fn from(ingredient: model::Ingredient) -> Self { + Self { + id: ingredient.id, + name: ingredient.name, + comment: ingredient.comment, + quantity_value: ingredient.quantity_value, + quantity_unit: ingredient.quantity_unit, + } + } +} + #[debug_handler] pub async fn get_groups( State(connection): State, @@ -217,7 +232,7 @@ pub async fn get_groups( .get_groups(recipe_id) .await? .into_iter() - .map(web_api::Group::from) + .map(ron_api::Group::from) .collect::>(), )) } diff --git a/backend/src/services/ron/shopping_list.rs b/backend/src/services/ron/shopping_list.rs index 2bf38df..ca3b71a 100644 --- a/backend/src/services/ron/shopping_list.rs +++ b/backend/src/services/ron/shopping_list.rs @@ -4,7 +4,7 @@ use axum::{ http::StatusCode, response::{ErrorResponse, IntoResponse, Result}, }; -use common::web_api; +use common::ron_api; use crate::{ app::Context, @@ -17,7 +17,7 @@ use crate::{ use super::rights::*; -impl From for common::web_api::ShoppingListItem { +impl From for common::ron_api::ShoppingListItem { fn from(item: model::ShoppingListItem) -> Self { Self { id: item.id, @@ -43,7 +43,7 @@ pub async fn get( .get_shopping_list(user.id) .await? .into_iter() - .map(common::web_api::ShoppingListItem::from) + .map(common::ron_api::ShoppingListItem::from) .collect::>(), )) } else { @@ -58,7 +58,7 @@ pub async fn get( pub async fn set_entry_checked( State(connection): State, Extension(context): Extension, - ExtractRon(ron): ExtractRon>, + ExtractRon(ron): ExtractRon>, ) -> Result { check_user_rights_shopping_list_entry(&connection, &context.user, ron.id).await?; Ok(ron_response_ok( diff --git a/backend/templates/recipe_edit.html b/backend/templates/recipe_edit.html index 5c8ab90..1365bb3 100644 --- a/backend/templates/recipe_edit.html +++ b/backend/templates/recipe_edit.html @@ -42,10 +42,10 @@
diff --git a/backend/templates/recipe_view.html b/backend/templates/recipe_view.html index 2b81f34..cbf3031 100644 --- a/backend/templates/recipe_view.html +++ b/backend/templates/recipe_view.html @@ -39,12 +39,12 @@ {% match recipe.difficulty %} - {% when common::web_api::Difficulty::Unknown %} - {% when common::web_api::Difficulty::Easy %} + {% when common::ron_api::Difficulty::Unknown %} + {% when common::ron_api::Difficulty::Easy %} {{ context.tr.t(Sentence::RecipeDifficultyEasy) }} - {% when common::web_api::Difficulty::Medium %} + {% when common::ron_api::Difficulty::Medium %} {{ context.tr.t(Sentence::RecipeDifficultyMedium) }} - {% when common::web_api::Difficulty::Hard %} + {% when common::ron_api::Difficulty::Hard %} {{ context.tr.t(Sentence::RecipeDifficultyHard) }} {% endmatch %} diff --git a/backend/tests/http.rs b/backend/tests/http.rs index 3bab834..0af4e20 100644 --- a/backend/tests/http.rs +++ b/backend/tests/http.rs @@ -2,7 +2,7 @@ use std::{error::Error, sync::Arc}; use axum::http; use axum_test::TestServer; -use common::web_api; +use common::ron_api; use cookie::Cookie; use scraper::{ElementRef, Html, Selector}; use serde::Serialize; @@ -221,13 +221,7 @@ async fn sign_in() -> Result<(), Box> { // Assert. response.assert_status_see_other(); // Redirection after successful sign in. response.assert_text(""); - response.assert_header( - "location", - format!( - "/?user_message={}&user_message_icon=0", - common::translation::Sentence::SignInSuccess as i64 - ), - ); + response.assert_header("location", "/?user_message=16&user_message_icon=0"); Ok(()) } @@ -266,7 +260,7 @@ async fn create_recipe_and_edit_it() -> Result<(), Box> { .patch(&format!("/ron-api/recipe/{recipe_id}/title")) .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes(web_api::to_string("AAA").unwrap().into()) + .bytes(ron_api::to_string("AAA").unwrap().into()) .await; response.assert_status_ok(); @@ -274,7 +268,7 @@ async fn create_recipe_and_edit_it() -> Result<(), Box> { .patch(&format!("/ron-api/recipe/{recipe_id}/description")) .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes(web_api::to_string("BBB").unwrap().into()) + .bytes(ron_api::to_string("BBB").unwrap().into()) .await; response.assert_status_ok(); @@ -282,7 +276,7 @@ async fn create_recipe_and_edit_it() -> Result<(), Box> { .patch(&format!("/ron-api/recipe/{recipe_id}/servings")) .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes(web_api::to_string(Some(42)).unwrap().into()) + .bytes(ron_api::to_string(Some(42)).unwrap().into()) .await; response.assert_status_ok(); @@ -290,7 +284,7 @@ async fn create_recipe_and_edit_it() -> Result<(), Box> { .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(web_api::to_string(Some(420)).unwrap().into()) + .bytes(ron_api::to_string(Some(420)).unwrap().into()) .await; response.assert_status_ok(); @@ -299,7 +293,7 @@ async fn create_recipe_and_edit_it() -> Result<(), Box> { .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) .bytes( - web_api::to_string(web_api::Difficulty::Hard) + ron_api::to_string(ron_api::Difficulty::Hard) .unwrap() .into(), ) @@ -310,7 +304,7 @@ async fn create_recipe_and_edit_it() -> Result<(), Box> { .patch(&format!("/ron-api/recipe/{recipe_id}/language")) .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) - .bytes(web_api::to_string("fr").unwrap().into()) + .bytes(ron_api::to_string("fr").unwrap().into()) .await; response.assert_status_ok(); @@ -318,7 +312,7 @@ async fn create_recipe_and_edit_it() -> Result<(), Box> { .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(web_api::to_string(true).unwrap().into()) + .bytes(ron_api::to_string(true).unwrap().into()) .await; response.assert_status_ok(); @@ -405,7 +399,7 @@ async fn recipe_tags() -> Result<(), Box> { .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) .bytes( - web_api::to_string(vec!["ABC".to_string(), "xyz".to_string()]) + ron_api::to_string(vec!["ABC".to_string(), "xyz".to_string()]) .unwrap() .into(), ) @@ -430,7 +424,7 @@ async fn recipe_tags() -> Result<(), Box> { .add_cookie(cookie.clone()) .add_header(http::header::CONTENT_TYPE, common::consts::MIME_TYPE_RON) .bytes( - web_api::to_string(vec!["XYZ".to_string(), "qwe".to_string()]) + ron_api::to_string(vec!["XYZ".to_string(), "qwe".to_string()]) .unwrap() .into(), ) diff --git a/backend/translations/english.ron b/backend/translations/english.ron index 20d1669..9bbc1ac 100644 --- a/backend/translations/english.ron +++ b/backend/translations/english.ron @@ -21,8 +21,6 @@ (DatabaseError, "Database error"), (TemplateError, "Template error"), - (SearchPlaceholder, "Search by title"), - (SignInMenu, "Sign in"), (SignInTitle, "Sign in"), (SignInButton, "Sign in"), diff --git a/backend/translations/french.ron b/backend/translations/french.ron index 6994e3d..9ec817b 100644 --- a/backend/translations/french.ron +++ b/backend/translations/french.ron @@ -21,8 +21,6 @@ (DatabaseError, "Erreur de la base de données (Database error)"), (TemplateError, "Erreur du moteur de modèles (Template error)"), - (SearchPlaceholder, "Recherche par titre"), - (SignInMenu, "Se connecter"), (SignInTitle, "Se connecter"), (SignInButton, "Se connecter"), diff --git a/common/src/lib.rs b/common/src/lib.rs index 4a236f1..940d642 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -1,5 +1,5 @@ pub mod consts; +pub mod ron_api; pub mod toast; -pub mod translation; pub mod utils; -pub mod web_api; +pub mod translation; \ No newline at end of file diff --git a/common/src/web_api.rs b/common/src/ron_api.rs similarity index 85% rename from common/src/web_api.rs rename to common/src/ron_api.rs index 9c2b622..dd9c78e 100644 --- a/common/src/web_api.rs +++ b/common/src/ron_api.rs @@ -20,28 +20,6 @@ pub struct DateRange { /*** Recipe ***/ -#[derive(Serialize, Deserialize, Clone)] -pub struct RecipesListFragmentsParams { - pub current_recipe_id: Option, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct SearchByTitleParams { - pub search_term: String, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct GetTitlesParams { - pub ids: Vec, -} - -#[derive(Serialize, Deserialize, Clone)] -pub struct RecipeSearchResult { - pub recipe_id: i64, - pub title: String, - pub title_highlighted: String, -} - #[repr(u32)] #[derive(Serialize, Deserialize, FromRepr, Clone, Copy, PartialEq, Debug)] pub enum Difficulty { diff --git a/common/src/translation.rs b/common/src/translation.rs index 9bfa63e..787fde5 100644 --- a/common/src/translation.rs +++ b/common/src/translation.rs @@ -22,9 +22,6 @@ pub enum Sentence { DatabaseError, TemplateError, - // Search - SearchPlaceholder, - // Sign in page. SignInMenu, SignInTitle, diff --git a/frontend/src/calendar.rs b/frontend/src/calendar.rs index e4bdb7f..c0cb8bd 100644 --- a/frontend/src/calendar.rs +++ b/frontend/src/calendar.rs @@ -1,7 +1,7 @@ use std::{cell::RefCell, rc::Rc}; use chrono::{Datelike, Days, Months, NaiveDate, Weekday, offset::Local}; -use common::{web_api, utils::substitute_with_names}; +use common::{ron_api, utils::substitute_with_names}; use gloo::{ events::EventListener, utils::{document, window}, @@ -13,7 +13,7 @@ use web_sys::{Element, HtmlInputElement}; use crate::{ modal_dialog, recipe_scheduler::RecipeScheduler, - ron_request, + request, utils::{SelectorExt, by_id, get_locale, selector, selector_all}, }; @@ -167,14 +167,12 @@ pub fn setup( ) .await { - let body = web_api::RemoveScheduledRecipe { + let body = ron_api::RemoveScheduledRecipe { recipe_id, date, remove_ingredients_from_shopping_list, }; - let _ = - ron_request::delete::<(), _>("/ron-api/calendar/scheduled_recipe", Some(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 b349554..1904ed3 100644 --- a/frontend/src/lib.rs +++ b/frontend/src/lib.rs @@ -13,7 +13,6 @@ mod on_click; mod pages; mod recipe_scheduler; mod request; -mod ron_request; mod shopping_list; mod toast; mod utils; @@ -87,7 +86,7 @@ pub fn main() -> Result<(), JsValue> { // Request the message to display. spawn_local(async move { - let translation: String = ron_request::get(&format!("/ron-api/translation/{mess_id}")) + let translation: String = request::get(&format!("translation/{mess_id}")) .await .unwrap(); if let Some(level_id) = level_id { @@ -106,9 +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 location_without_lang = location_without_lang.clone(); spawn_local(async move { - let _ = ron_request::put::<(), _>("/ron-api/lang", &lang).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 9535433..66ac41b 100644 --- a/frontend/src/pages/recipe_edit.rs +++ b/frontend/src/pages/recipe_edit.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, rc, sync::Mutex}; -use common::{web_api, utils::substitute}; +use common::{ron_api, utils::substitute}; use gloo::{ events::{EventListener, EventListenerOptions}, net::http::Request, @@ -14,7 +14,7 @@ use web_sys::{ }; use crate::{ - modal_dialog, request, ron_request, + modal_dialog, request, toast::{self, Level}, utils::{SelectorExt, by_id, get_current_lang, selector, selector_and_clone}, }; @@ -36,11 +36,8 @@ pub fn setup_page(recipe_id: i64) { current_title = title.value(); let title = title.value(); spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/recipe/{recipe_id}/title"), - title, - ) - .await; + let _ = + request::patch::<(), _>(&format!("recipe/{recipe_id}/title"), title).await; reload_recipes_list(recipe_id).await; }); } @@ -58,8 +55,8 @@ pub fn setup_page(recipe_id: i64) { current_description = description.value(); let description = description.value(); spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/recipe/{recipe_id}/description"), + let _ = request::patch::<(), _>( + &format!("recipe/{recipe_id}/description"), description, ) .await; @@ -88,11 +85,9 @@ pub fn setup_page(recipe_id: i64) { }; current_servings = n; spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/recipe/{recipe_id}/servings"), - servings, - ) - .await; + let _ = + request::patch::<(), _>(&format!("recipe/{recipe_id}/servings"), servings) + .await; }); } }) @@ -119,8 +114,8 @@ pub fn setup_page(recipe_id: i64) { }; current_time = n; spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/recipe/{recipe_id}/estimated_time"), + let _ = request::patch::<(), _>( + &format!("recipe/{recipe_id}/estimated_time"), time, ) .await; @@ -139,11 +134,11 @@ pub fn setup_page(recipe_id: i64) { if difficulty.value() != current_difficulty { current_difficulty = difficulty.value(); let difficulty = - web_api::Difficulty::from_repr(difficulty.value().parse::().unwrap()) - .unwrap_or(web_api::Difficulty::Unknown); + ron_api::Difficulty::from_repr(difficulty.value().parse::().unwrap()) + .unwrap_or(ron_api::Difficulty::Unknown); spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/recipe/{recipe_id}/difficulty"), + let _ = request::patch::<(), _>( + &format!("recipe/{recipe_id}/difficulty"), difficulty, ) .await; @@ -156,7 +151,7 @@ pub fn setup_page(recipe_id: i64) { // Tags. { spawn_local(async move { - let tags: Vec = ron_request::get(&format!("/ron-api/recipe/{recipe_id}/tags")) + let tags: Vec = request::get(&format!("recipe/{recipe_id}/tags")) .await .unwrap(); create_tag_elements(recipe_id, &tags); @@ -166,11 +161,9 @@ pub fn setup_page(recipe_id: i64) { spawn_local(async move { let tag_list: Vec = tags.split_whitespace().map(str::to_lowercase).collect(); - let _ = ron_request::post::<(), _>( - &format!("/ron-api/recipe/{recipe_id}/tags"), - Some(&tag_list), - ) - .await; + 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,11 +207,9 @@ pub fn setup_page(recipe_id: i64) { current_language = language.value(); let language = language.value(); spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/recipe/{recipe_id}/language"), - language, - ) - .await; + let _ = + request::patch::<(), _>(&format!("recipe/{recipe_id}/language"), language) + .await; }); } }) @@ -231,11 +222,9 @@ pub fn setup_page(recipe_id: i64) { EventListener::new(&is_public.clone(), "input", move |_event| { let is_public = is_public.checked(); spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/recipe/{recipe_id}/is_public"), - is_public, - ) - .await; + let _ = + request::patch::<(), _>(&format!("recipe/{recipe_id}/is_public"), is_public) + .await; reload_recipes_list(recipe_id).await; }); }) @@ -260,9 +249,7 @@ pub fn setup_page(recipe_id: i64) { .await .is_some() { - if let Ok(()) = - ron_request::delete::<_, ()>(&format!("/ron-api/recipe/{recipe_id}"), None) - .await + if let Ok(()) = request::delete::<_, ()>(&format!("recipe/{recipe_id}"), None).await { window() .location() @@ -284,8 +271,8 @@ pub fn setup_page(recipe_id: i64) { // Load initial groups, steps and ingredients. { spawn_local(async move { - let groups: Vec = - ron_request::get(&format!("/ron-api/recipe/{recipe_id}/groups")) + let groups: Vec = + request::get(&format!("recipe/{recipe_id}/groups")) .await .unwrap(); @@ -312,11 +299,10 @@ 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| { spawn_local(async move { - let id: i64 = - ron_request::post::<_, ()>(&format!("/ron-api/recipe/{recipe_id}/group"), None) - .await - .unwrap(); - create_group_element(&web_api::Group { + let id: i64 = request::post::<_, ()>(&format!("recipe/{recipe_id}/group"), None) + .await + .unwrap(); + create_group_element(&ron_api::Group { id, name: "".to_string(), comment: "".to_string(), @@ -328,7 +314,7 @@ pub fn setup_page(recipe_id: i64) { } } -fn create_group_element(group: &web_api::Group) -> Element { +fn create_group_element(group: &ron_api::Group) -> Element { let group_id = group.id; let group_element: Element = selector_and_clone("#hidden-templates .group"); group_element.set_id(&format!("group-{}", group.id)); @@ -344,7 +330,7 @@ fn create_group_element(group: &web_api::Group) -> Element { .map(|e| e.id()[6..].parse::().unwrap()) .collect(); - let _ = ron_request::patch::<(), _>("/ron-api/groups/order", ids).await; + let _ = request::patch::<(), _>("groups/order", ids).await; }); }); @@ -357,9 +343,7 @@ fn create_group_element(group: &web_api::Group) -> Element { current_name = name.value(); let name = name.value(); spawn_local(async move { - let _ = - ron_request::patch::<(), _>(&format!("/ron-api/group/{group_id}/name"), name) - .await; + let _ = request::patch::<(), _>(&format!("group/{group_id}/name"), name).await; }) } }) @@ -374,11 +358,8 @@ fn create_group_element(group: &web_api::Group) -> Element { current_comment = comment.value(); let comment = comment.value(); spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/group/{group_id}/comment"), - comment, - ) - .await; + let _ = + request::patch::<(), _>(&format!("group/{group_id}/comment"), comment).await; }); } }) @@ -403,8 +384,7 @@ fn create_group_element(group: &web_api::Group) -> Element { .await .is_some() { - let _ = ron_request::delete::<(), ()>(&format!("/ron-api/group/{group_id}"), None) - .await; + 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(); @@ -417,13 +397,12 @@ fn create_group_element(group: &web_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 = - ron_request::post::<_, ()>(&format!("/ron-api/group/{group_id}/step"), None) - .await - .unwrap(); + let id: i64 = request::post::<_, ()>(&format!("group/{group_id}/step"), None) + .await + .unwrap(); create_step_element( &selector::(&format!("#group-{group_id} .steps")), - &web_api::Step { + &ron_api::Step { id, action: "".to_string(), ingredients: vec![], @@ -478,11 +457,9 @@ where let tag_span = tag_span.clone(); let tag = tag.clone(); spawn_local(async move { - let _ = ron_request::delete::<(), _>( - &format!("/ron-api/recipe/{recipe_id}/tags"), - Some(vec![tag]), - ) - .await; + let _ = + request::delete::<(), _>(&format!("recipe/{recipe_id}/tags"), Some(vec![tag])) + .await; tag_span.remove(); }); }) @@ -490,7 +467,7 @@ where } } -fn create_step_element(group_element: &Element, step: &web_api::Step) -> Element { +fn create_step_element(group_element: &Element, step: &ron_api::Step) -> Element { let step_id = step.id; let step_element: Element = selector_and_clone("#hidden-templates .step"); step_element.set_id(&format!("step-{}", step.id)); @@ -507,7 +484,7 @@ fn create_step_element(group_element: &Element, step: &web_api::Step) -> Element .map(|e| e.id()[5..].parse::().unwrap()) .collect(); - let _ = ron_request::patch::<(), _>("/ron-api/steps/order", ids).await; + let _ = request::patch::<(), _>("/steps/order", ids).await; }); }); @@ -520,9 +497,7 @@ fn create_step_element(group_element: &Element, step: &web_api::Step) -> Element current_action = action.value(); let action = action.value(); spawn_local(async move { - let _ = - ron_request::patch::<(), _>(&format!("/ron-api/step/{step_id}/action"), action) - .await; + let _ = request::patch::<(), _>(&format!("/step/{step_id}/action"), action).await; }); } }) @@ -547,8 +522,7 @@ fn create_step_element(group_element: &Element, step: &web_api::Step) -> Element .await .is_some() { - let _ = - ron_request::delete::<(), ()>(&format!("/ron-api/step/{step_id}"), None).await; + 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(); @@ -561,13 +535,12 @@ fn create_step_element(group_element: &Element, step: &web_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 = - ron_request::post::<_, ()>(&format!("/ron-api/step/{step_id}/ingredient"), None) - .await - .unwrap(); + let id: i64 = request::post::<_, ()>(&format!("step/{step_id}/ingredient"), None) + .await + .unwrap(); create_ingredient_element( &selector::(&format!("#step-{} .ingredients", step_id)), - &web_api::Ingredient { + &ron_api::Ingredient { id, name: "".to_string(), comment: "".to_string(), @@ -582,7 +555,7 @@ fn create_step_element(group_element: &Element, step: &web_api::Step) -> Element step_element } -fn create_ingredient_element(step_element: &Element, ingredient: &web_api::Ingredient) -> Element { +fn create_ingredient_element(step_element: &Element, ingredient: &ron_api::Ingredient) -> Element { let ingredient_id = ingredient.id; let ingredient_element: Element = selector_and_clone("#hidden-templates .ingredient"); ingredient_element.set_id(&format!("ingredient-{}", ingredient.id)); @@ -599,7 +572,7 @@ fn create_ingredient_element(step_element: &Element, ingredient: &web_api::Ingre .map(|e| e.id()[11..].parse::().unwrap()) .collect(); - let _ = ron_request::patch::<(), _>("/ron-api/ingredients/order", ids).await; + let _ = request::patch::<(), _>("ingredients/order", ids).await; }); }); @@ -612,11 +585,8 @@ fn create_ingredient_element(step_element: &Element, ingredient: &web_api::Ingre current_name = name.value(); let name = name.value(); spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/ingredient/{ingredient_id}/name"), - name, - ) - .await; + let _ = request::patch::<(), _>(&format!("ingredient/{ingredient_id}/name"), name) + .await; }); } }) @@ -631,8 +601,8 @@ fn create_ingredient_element(step_element: &Element, ingredient: &web_api::Ingre current_comment = comment.value(); let comment = comment.value(); spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/ingredient/{ingredient_id}/comment"), + let _ = request::patch::<(), _>( + &format!("ingredient/{ingredient_id}/comment"), comment, ) .await; @@ -658,11 +628,8 @@ fn create_ingredient_element(step_element: &Element, ingredient: &web_api::Ingre let q = if n.is_nan() { None } else { Some(n) }; current_quantity = n; spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/ingredient/{ingredient_id}/quantity"), - q, - ) - .await; + let _ = request::patch::<(), _>(&format!("ingredient/{ingredient_id}/quantity"), q) + .await; }); } }) @@ -677,11 +644,8 @@ fn create_ingredient_element(step_element: &Element, ingredient: &web_api::Ingre current_unit = unit.value(); let unit = unit.value(); spawn_local(async move { - let _ = ron_request::patch::<(), _>( - &format!("/ron-api/ingredient/{ingredient_id}/unit"), - unit, - ) - .await; + let _ = request::patch::<(), _>(&format!("ingredient/{ingredient_id}/unit"), unit) + .await; }); } }) @@ -706,11 +670,8 @@ fn create_ingredient_element(step_element: &Element, ingredient: &web_api::Ingre .await .is_some() { - let _ = ron_request::delete::<(), ()>( - &format!("/ron-api/ingredient/{ingredient_id}"), - None, - ) - .await; + 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(); @@ -723,17 +684,19 @@ fn create_ingredient_element(step_element: &Element, ingredient: &web_api::Ingre } async fn reload_recipes_list(current_recipe_id: i64) { - let fragment: String = request::get_with_params( - "/fragments/recipes_list", - web_api::RecipesListFragmentsParams { - current_recipe_id: Some(current_recipe_id), - }, - ) - .await - .unwrap(); - - let list = document().get_element_by_id("recipes-list").unwrap(); - list.set_outer_html(&fragment); + match Request::get("/fragments/recipes_list") + .query([("current_recipe_id", current_recipe_id.to_string())]) + .send() + .await + { + Err(error) => { + toast::show_message_level(Level::Error, &format!("Internal server error: {}", error)); + } + Ok(response) => { + let list = document().get_element_by_id("recipes-list").unwrap(); + list.set_outer_html(&response.text().await.unwrap()); + } + } } enum CursorPosition { diff --git a/frontend/src/recipe_scheduler.rs b/frontend/src/recipe_scheduler.rs index 11c6b10..dd34016 100644 --- a/frontend/src/recipe_scheduler.rs +++ b/frontend/src/recipe_scheduler.rs @@ -1,16 +1,16 @@ use chrono::{Datelike, Days, Months, NaiveDate}; -use common::web_api; +use common::ron_api; use gloo::storage::{LocalStorage, Storage}; use ron::ser::{PrettyConfig, to_string_pretty}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::{calendar, ron_request}; +use crate::{calendar, request}; #[derive(Error, Debug)] pub enum Error { #[error("Request error: {0}")] - Request(#[from] ron_request::Error), + Request(#[from] request::Error), } type Result = std::result::Result; @@ -25,11 +25,11 @@ pub enum ScheduleRecipeResult { RecipeAlreadyScheduledAtThisDate, } -impl From for ScheduleRecipeResult { - fn from(api_res: web_api::ScheduleRecipeResult) -> Self { +impl From for ScheduleRecipeResult { + fn from(api_res: ron_api::ScheduleRecipeResult) -> Self { match api_res { - web_api::ScheduleRecipeResult::Ok => Self::Ok, - web_api::ScheduleRecipeResult::RecipeAlreadyScheduledAtThisDate => { + ron_api::ScheduleRecipeResult::Ok => Self::Ok, + ron_api::ScheduleRecipeResult::RecipeAlreadyScheduledAtThisDate => { Self::RecipeAlreadyScheduledAtThisDate } } @@ -80,14 +80,12 @@ impl RecipeScheduler { return Ok(vec![]); } - let titles: Vec = ron_request::get_with_params( - "/ron-api/recipe/titles", - web_api::GetTitlesParams { - ids: recipe_ids_and_dates - .iter() - .map(|r| r.recipe_id) - .collect::>(), - }, + let titles: Vec = request::get_with_params( + "recipe/titles", + recipe_ids_and_dates + .iter() + .map(|r| r.recipe_id) + .collect::>(), ) .await?; @@ -97,9 +95,9 @@ impl RecipeScheduler { .map(|(id_and_date, title)| (id_and_date.date, title, id_and_date.recipe_id)) .collect::>()) } else { - let scheduled_recipes: web_api::ScheduledRecipes = ron_request::get_with_params( - "/ron-api/calendar/scheduled_recipes", - web_api::DateRange { + let scheduled_recipes: ron_api::ScheduledRecipes = request::get_with_params( + "calendar/scheduled_recipes", + ron_api::DateRange { start_date, end_date, }, @@ -129,9 +127,9 @@ impl RecipeScheduler { save_scheduled_recipes(recipe_ids_and_dates, date.year(), date.month0()); Ok(ScheduleRecipeResult::Ok) } else { - ron_request::post::( - "/ron-api/calendar/scheduled_recipe", - Some(web_api::ScheduleRecipe { + request::post::( + "calendar/scheduled_recipe", + Some(ron_api::ScheduleRecipe { recipe_id, date, servings, @@ -140,7 +138,7 @@ impl RecipeScheduler { ) .await .map_err(Error::from) - .map(From::::from) + .map(From::::from) } } diff --git a/frontend/src/request.rs b/frontend/src/request.rs index 4849262..c702e54 100644 --- a/frontend/src/request.rs +++ b/frontend/src/request.rs @@ -1,7 +1,10 @@ -use std::any::TypeId; - -use gloo::net::http::Request; -use serde::Serialize; +/// This module provides a simple API for making HTTP requests to the server. +/// For requests with a body (POST, PUT, PATCH, etc.), it uses the RON format. +/// The RON data structures should come from the `ron_api` module. +/// For requests with parameters (GET), it uses the HTML form format. +use common::ron_api; +use gloo::net::http::{Request, RequestBuilder}; +use serde::{Serialize, de::DeserializeOwned}; use thiserror::Error; use crate::toast::{self, Level}; @@ -11,6 +14,12 @@ pub enum Error { #[error("Gloo error: {0}")] Gloo(#[from] gloo::net::Error), + #[error("RON Spanned error: {0}")] + RonSpanned(#[from] ron::error::SpannedError), + + #[error("RON Error: {0}")] + Ron(#[from] ron::error::Error), + #[error("HTTP error: {0}")] Http(String), @@ -20,26 +29,53 @@ pub enum Error { type Result = std::result::Result; -#[allow(dead_code)] // Not used for the moment. -pub async fn get(url: &str) -> Result { - get_with_params(url, ()).await +const CONTENT_TYPE: &str = "Content-Type"; + +async fn req_with_body( + api_name: &str, + body: U, + method_fn: fn(&str) -> RequestBuilder, +) -> Result +where + T: DeserializeOwned, + U: Serialize, +{ + let url = format!("/ron-api/{}", api_name); + let request_builder = method_fn(&url).header( + CONTENT_TYPE, + common::consts::MIME_TYPE_RON.to_str().unwrap(), + ); + send_req(request_builder.body(ron_api::to_string(body)?)?).await } -pub async fn get_with_params(url: &str, params: U) -> Result +async fn req_with_params( + api_name: &str, + params: U, + method_fn: fn(&str) -> RequestBuilder, +) -> Result where - U: Serialize + 'static, + T: DeserializeOwned, + U: Serialize, { - let request_builder = if TypeId::of::() == TypeId::of::<()>() { - Request::get(url) - } else { - let mut url = url.to_string(); - url.push('?'); - serde_html_form::ser::push_to_string(&mut url, params).unwrap(); - Request::get(&url) - }; + let mut url = format!("/ron-api/{}?", api_name); + serde_html_form::ser::push_to_string(&mut url, params).unwrap(); + let request_builder = method_fn(&url); + send_req(request_builder.build()?).await +} - let request = request_builder.build()?; +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, +{ match request.send().await { Err(error) => { toast::show_message_level(Level::Error, &format!("Internal server error: {}", error)); @@ -53,8 +89,73 @@ where ); Err(Error::Http(response.status_text())) } else { - Ok(response.text().await?) + let mut r = response.binary().await?; + // An empty response is considered to be an unit value. + if r.is_empty() { + r = b"()".to_vec(); + } + Ok(ron::de::from_bytes::(&r)?) } } } } + +/// Sends a request to the server with the given API name and body. +/// # Example +/// ```rust +/// use common::ron_api; +/// let body = ron_api::SetLang { lang : "en".to_string() }; +/// request::put::<(), _>("lang", body).await; +/// ``` +pub async fn put(api_name: &str, body: U) -> Result +where + T: DeserializeOwned, + U: Serialize, +{ + req_with_body(api_name, body, Request::put).await +} + +pub async fn patch(api_name: &str, body: U) -> Result +where + T: DeserializeOwned, + U: Serialize, +{ + req_with_body(api_name, body, Request::patch).await +} + +pub async fn post(api_name: &str, body: Option) -> 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, + } +} + +pub async fn delete(api_name: &str, body: Option) -> 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, + } +} + +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, +{ + req_with_params(api_name, params, Request::get).await +} diff --git a/frontend/src/ron_request.rs b/frontend/src/ron_request.rs deleted file mode 100644 index 7af468b..0000000 --- a/frontend/src/ron_request.rs +++ /dev/null @@ -1,156 +0,0 @@ -/// This module provides a simple API for making HTTP requests to the server. -/// For requests with a body (POST, PUT, PATCH, etc.), it uses the RON format. -/// The RON data structures should come from the `web_api` module. -/// For requests with parameters (GET), it uses the HTML form format. -use common::web_api; -use gloo::net::http::{Request, RequestBuilder}; -use serde::{Serialize, de::DeserializeOwned}; -use thiserror::Error; - -use crate::toast::{self, Level}; - -#[derive(Error, Debug)] -pub enum Error { - #[error("Gloo error: {0}")] - Gloo(#[from] gloo::net::Error), - - #[error("RON Spanned error: {0}")] - RonSpanned(#[from] ron::error::SpannedError), - - #[error("RON Error: {0}")] - Ron(#[from] ron::error::Error), - - #[error("HTTP error: {0}")] - Http(String), - - #[error("Unknown error: {0}")] - Other(String), -} - -type Result = std::result::Result; - -const CONTENT_TYPE: &str = "Content-Type"; // TODO: take it from the http crate. - -async fn req_with_body(url: &str, body: U, method_fn: fn(&str) -> RequestBuilder) -> Result -where - T: DeserializeOwned, - U: Serialize, -{ - let request_builder = method_fn(url).header( - CONTENT_TYPE, - common::consts::MIME_TYPE_RON.to_str().unwrap(), - ); - send_req(request_builder.body(web_api::to_string(body)?)?).await -} - -async fn req_with_params( - url: &str, - params: U, - method_fn: fn(&str) -> RequestBuilder, -) -> Result -where - T: DeserializeOwned, - U: Serialize, -{ - let mut url = url.to_string(); - url.push('?'); - serde_html_form::ser::push_to_string(&mut url, params).unwrap(); - let request_builder = method_fn(&url); - send_req(request_builder.build()?).await -} - -async fn req(url: &str, method_fn: fn(&str) -> RequestBuilder) -> Result -where - T: DeserializeOwned, -{ - let request_builder = method_fn(url); - send_req(request_builder.build()?).await -} - -async fn send_req(request: Request) -> Result -where - T: DeserializeOwned, -{ - match request.send().await { - Err(error) => { - toast::show_message_level(Level::Error, &format!("Internal server error: {}", error)); - Err(Error::Gloo(error)) - } - Ok(response) => { - if !response.ok() { - toast::show_message_level( - Level::Error, - &format!("HTTP error: {}", response.status_text()), - ); - Err(Error::Http(response.status_text())) - } else { - let mut r = response.binary().await?; - // An empty response is considered to be an unit value. - if r.is_empty() { - r = b"()".to_vec(); - } - Ok(ron::de::from_bytes::(&r)?) - } - } - } -} - -/// Sends a request to the server with the given API name and body. -/// # Example -/// ```rust -/// use common::web_api; -/// let body = web_api::SetLang { lang : "en".to_string() }; -/// request::put::<(), _>("lang", body).await; -/// ``` -pub async fn put(url: &str, body: U) -> Result -where - T: DeserializeOwned, - U: Serialize, -{ - req_with_body(url, body, Request::put).await -} - -pub async fn patch(url: &str, body: U) -> Result -where - T: DeserializeOwned, - U: Serialize, -{ - req_with_body(url, body, Request::patch).await -} - -pub async fn post(url: &str, body: Option) -> Result -where - T: DeserializeOwned, - U: Serialize, -{ - match body { - Some(body) => req_with_body(url, body, Request::post).await, - None => req(url, Request::post).await, - } -} - -pub async fn delete(url: &str, body: Option) -> Result -where - T: DeserializeOwned, - U: Serialize, -{ - match body { - Some(body) => req_with_body(url, body, Request::delete).await, - None => req(url, Request::delete).await, - } -} - -pub async fn get(url: &str) -> Result -where - T: DeserializeOwned, -{ - req(url, Request::get).await -} - -pub async fn get_with_params(url: &str, params: U) -> Result -where - T: DeserializeOwned, - U: Serialize, -{ - req_with_params(url, params, Request::get).await -} diff --git a/frontend/src/shopping_list.rs b/frontend/src/shopping_list.rs index cff52ae..b9251cf 100644 --- a/frontend/src/shopping_list.rs +++ b/frontend/src/shopping_list.rs @@ -1,16 +1,16 @@ use chrono::{Datelike, Days, Months, NaiveDate}; -use common::web_api; +use common::ron_api; use gloo::storage::{LocalStorage, Storage}; use ron::ser::{PrettyConfig, to_string_pretty}; use serde::{Deserialize, Serialize}; use thiserror::Error; -use crate::{calendar, ron_request}; +use crate::{calendar, request}; #[derive(Error, Debug)] pub enum Error { #[error("Request error: {0}")] - Request(#[from] ron_request::Error), + Request(#[from] request::Error), } type Result = std::result::Result; @@ -25,11 +25,11 @@ impl ShoppingList { Self { is_local } } - pub async fn get_items(&self) -> Result> { + pub async fn get_items(&self) -> Result> { if self.is_local { Ok(vec![]) // TODO } else { - Ok(ron_request::get("/ron-api/shopping_list").await?) + Ok(request::get("shopping_list").await?) } } @@ -37,9 +37,9 @@ impl ShoppingList { if self.is_local { todo!(); } else { - ron_request::patch( - "/ron-api/shopping_list/checked", - web_api::KeyValue { + request::patch( + "shopping_list/checked", + ron_api::KeyValue { id: item_id, value: is_checked, },