[[package]]
name = "clap"
-version = "4.5.28"
+version = "4.5.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3e77c3243bd94243c03672cb5154667347c457ca271254724f9f393aee1c05ff"
+checksum = "8acebd8ad879283633b343856142139f2da2317c96b05b4dd6181c61e2480184"
dependencies = [
"clap_builder",
"clap_derive",
[[package]]
name = "clap_builder"
-version = "4.5.27"
+version = "4.5.29"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1b26884eb4b57140e4d2d93652abfa49498b938b3c9179f9fc487b0acc3edad7"
+checksum = "f6ba32cbda51c7e1dfd49acc1457ba1a7dec5b64fe360e828acb13ca8dc9c2f9"
dependencies = [
"anstream",
"anstyle",
[[package]]
name = "miniz_oxide"
-version = "0.8.3"
+version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b8402cab7aefae129c6977bb0ff1b8fd9a04eb5b51efc50a70bea51cda0c7924"
+checksum = "b3b1c9bd4fe1f0f8b387f6eb9eb3b4a1aa26185e5750efb9140301703f62cd1b"
dependencies = [
"adler2",
]
[[package]]
name = "rustls"
-version = "0.23.22"
+version = "0.23.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "9fb9263ab4eb695e42321db096e3b8fbd715a59b154d5c88d82db2175b681ba7"
+checksum = "47796c98c480fce5406ef69d1c76378375492c3b0a0de587be0c1d9feb12f395"
dependencies = [
"log",
"once_cell",
[[package]]
name = "stacker"
-version = "0.1.17"
+version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "799c883d55abdb5e98af1a7b3f23b9b6de8ecada0ecac058672d7635eb48ca7b"
+checksum = "1d08feb8f695b465baed819b03c128dc23f57a694510ab1f06c77f763975685e"
dependencies = [
"cc",
"cfg-if",
) -> Result<bool> {
sqlx::query_scalar(
r#"
-SELECT COUNT(*)
+SELECT COUNT(*) = 1
FROM [Recipe]
INNER JOIN [User] ON [User].[id] = [Recipe].[user_id]
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
Ok(query.fetch_one(&self.pool).await? == ingredients_ids.len() as u64)
}
+ pub async fn can_edit_shopping_list_entry(&self, user_id: i64, entry_id: i64) -> Result<bool> {
+ sqlx::query_scalar(
+ r#"
+SELECT COUNT(*) = 1
+FROM [ShoppingEntry]
+WHERE [id] = $1 AND ([user_id] = $2 OR (SELECT [is_admin] FROM [User] WHERE [id] = $2))
+ "#,
+ )
+ .bind(entry_id)
+ .bind(user_id)
+ .fetch_one(&self.pool)
+ .await
+ .map_err(DBError::from)
+ }
+
pub async fn get_recipe(&self, id: i64, complete: bool) -> Result<Option<model::Recipe>> {
match sqlx::query_as::<_, model::Recipe>(
r#"
LEFT JOIN [RecipeScheduled] ON [RecipeScheduled].[id] = [ShoppingEntry].[recipe_scheduled_id]
LEFT JOIN [Recipe] ON [Recipe].[id] = [RecipeScheduled].[recipe_id]
WHERE [ShoppingEntry].[user_id] = $1
-ORDER BY [is_checked], [recipe_id], [name]
+ORDER BY [is_checked], [recipe_id], [name], [ShoppingEntry].[id]
"#,
)
.bind(user_id)
.await
.map_err(DBError::from)
}
+
+ pub async fn set_entry_checked(&self, entry_id: i64, is_checked: bool) -> Result<()> {
+ sqlx::query("UPDATE [ShoppingEntry] SET [is_checked] = $2 WHERE [id] = $1")
+ .bind(entry_id)
+ .bind(is_checked)
+ .execute(&self.pool)
+ .await
+ .map(|_| ())
+ .map_err(DBError::from)
+ }
}
// Disabled: update user profile is now made with a post data ('edit_user_post').
// .route("/user/update", put(services::ron::update_user))
.route("/set_lang", put(services::ron::set_lang))
- .route("/recipe/get_titles", get(services::ron::get_titles))
- .route("/recipe/set_title", put(services::ron::set_recipe_title))
+ .route("/recipe/get_titles", get(services::ron::recipe::get_titles))
+ .route("/recipe/set_title", put(services::ron::recipe::set_title))
.route(
"/recipe/set_description",
- put(services::ron::set_recipe_description),
+ put(services::ron::recipe::set_description),
+ )
+ .route(
+ "/recipe/set_servings",
+ put(services::ron::recipe::set_servings),
)
- .route("/recipe/set_servings", put(services::ron::set_servings))
.route(
"/recipe/set_estimated_time",
- put(services::ron::set_estimated_time),
+ put(services::ron::recipe::set_estimated_time),
+ )
+ .route("/recipe/get_tags", get(services::ron::recipe::get_tags))
+ .route("/recipe/add_tags", post(services::ron::recipe::add_tags))
+ .route("/recipe/rm_tags", delete(services::ron::recipe::rm_tags))
+ .route(
+ "/recipe/set_difficulty",
+ put(services::ron::recipe::set_difficulty),
+ )
+ .route(
+ "/recipe/set_language",
+ put(services::ron::recipe::set_language),
)
- .route("/recipe/get_tags", get(services::ron::get_tags))
- .route("/recipe/add_tags", post(services::ron::add_tags))
- .route("/recipe/rm_tags", delete(services::ron::rm_tags))
- .route("/recipe/set_difficulty", put(services::ron::set_difficulty))
- .route("/recipe/set_language", put(services::ron::set_language))
.route(
"/recipe/set_is_published",
- put(services::ron::set_is_published),
+ put(services::ron::recipe::set_is_published),
+ )
+ .route("/recipe/remove", delete(services::ron::recipe::rm))
+ .route("/recipe/get_groups", get(services::ron::recipe::get_groups))
+ .route("/recipe/add_group", post(services::ron::recipe::add_group))
+ .route(
+ "/recipe/remove_group",
+ delete(services::ron::recipe::rm_group),
+ )
+ .route(
+ "/recipe/set_group_name",
+ put(services::ron::recipe::set_group_name),
)
- .route("/recipe/remove", delete(services::ron::rm))
- .route("/recipe/get_groups", get(services::ron::get_groups))
- .route("/recipe/add_group", post(services::ron::add_group))
- .route("/recipe/remove_group", delete(services::ron::rm_group))
- .route("/recipe/set_group_name", put(services::ron::set_group_name))
.route(
"/recipe/set_group_comment",
- put(services::ron::set_group_comment),
+ put(services::ron::recipe::set_group_comment),
)
.route(
"/recipe/set_groups_order",
- put(services::ron::set_groups_order),
+ put(services::ron::recipe::set_groups_order),
+ )
+ .route("/recipe/add_step", post(services::ron::recipe::add_step))
+ .route(
+ "/recipe/remove_step",
+ delete(services::ron::recipe::rm_step),
)
- .route("/recipe/add_step", post(services::ron::add_step))
- .route("/recipe/remove_step", delete(services::ron::rm_step))
.route(
"/recipe/set_step_action",
- put(services::ron::set_step_action),
+ put(services::ron::recipe::set_step_action),
)
.route(
"/recipe/set_steps_order",
- put(services::ron::set_steps_order),
+ put(services::ron::recipe::set_steps_order),
)
.route(
"/recipe/add_ingredient",
- post(services::ron::add_ingredient),
+ post(services::ron::recipe::add_ingredient),
)
.route(
"/recipe/remove_ingredient",
- delete(services::ron::rm_ingredient),
+ delete(services::ron::recipe::rm_ingredient),
)
.route(
"/recipe/set_ingredient_name",
- put(services::ron::set_ingredient_name),
+ put(services::ron::recipe::set_ingredient_name),
)
.route(
"/recipe/set_ingredient_comment",
- put(services::ron::set_ingredient_comment),
+ put(services::ron::recipe::set_ingredient_comment),
)
.route(
"/recipe/set_ingredient_quantity",
- put(services::ron::set_ingredient_quantity),
+ put(services::ron::recipe::set_ingredient_quantity),
)
.route(
"/recipe/set_ingredient_unit",
- put(services::ron::set_ingredient_unit),
+ put(services::ron::recipe::set_ingredient_unit),
)
.route(
"/recipe/set_ingredients_order",
- put(services::ron::set_ingredients_order),
+ put(services::ron::recipe::set_ingredients_order),
)
.route(
"/calendar/get_scheduled_recipes",
- get(services::ron::get_scheduled_recipes),
+ get(services::ron::calendar::get_scheduled_recipes),
)
.route(
"/calendar/schedule_recipe",
- post(services::ron::schedule_recipe),
+ post(services::ron::calendar::schedule_recipe),
)
.route(
"/calendar/remove_scheduled_recipe",
- delete(services::ron::rm_scheduled_recipe),
+ delete(services::ron::calendar::rm_scheduled_recipe),
)
.route(
"/shopping_list/get_list",
- get(services::ron::get_shopping_list),
+ get(services::ron::shopping_list::get),
+ )
+ .route(
+ "/shopping_list/set_checked",
+ put(services::ron::shopping_list::set_entry_checked),
)
.fallback(services::ron::not_found);
+++ /dev/null
-use axum::{
- debug_handler,
- extract::{Extension, State},
- http::{HeaderMap, StatusCode},
- response::{ErrorResponse, IntoResponse, Response, Result},
-};
-use axum_extra::extract::{
- cookie::{Cookie, CookieJar},
- Query,
-};
-use serde::{Deserialize, Serialize};
-// use tracing::{event, Level};
-
-use crate::{
- consts,
- data::{self, db},
- model,
- ron_extractor::ExtractRon,
- ron_utils::{ron_error, ron_response_ok},
-};
-
-const NOT_AUTHORIZED_MESSAGE: &str = "Action not authorized";
-
-#[derive(Deserialize)]
-pub struct Id {
- id: i64,
-}
-
-#[derive(Deserialize, Serialize)]
-pub struct Ids {
- ids: Vec<i64>,
-}
-
-// #[allow(dead_code)]
-// #[debug_handler]
-// pub async fn update_user(
-// State(connection): State<db::Connection>,
-// Extension(user): Extension<Option<model::User>>,
-// ExtractRon(ron): ExtractRon<common::ron_api::UpdateProfile>,
-// ) -> Result<StatusCode> {
-// if let Some(user) = user {
-// connection
-// .update_user(
-// user.id,
-// ron.email.as_deref().map(str::trim),
-// ron.name.as_deref(),
-// ron.password.as_deref(),
-// )
-// .await?;
-// } else {
-// return Err(ErrorResponse::from(ron_error(
-// StatusCode::UNAUTHORIZED,
-// NOT_AUTHORIZED_MESSAGE,
-// )));
-// }
-// Ok(StatusCode::OK)
-// }
-
-#[debug_handler]
-pub async fn set_lang(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- headers: HeaderMap,
- ExtractRon(ron): ExtractRon<common::ron_api::SetLang>,
-) -> Result<(CookieJar, StatusCode)> {
- let mut jar = CookieJar::from_headers(&headers);
- if let Some(user) = user {
- connection.set_user_lang(user.id, &ron.lang).await?;
- } else {
- let cookie = Cookie::build((consts::COOKIE_LANG_NAME, ron.lang)).path("/");
- jar = jar.add(cookie);
- }
- Ok((jar, StatusCode::OK))
-}
-
-/*** Rights ***/
-
-async fn check_user_rights_recipe(
- connection: &db::Connection,
- user: &Option<model::User>,
- recipe_id: i64,
-) -> Result<()> {
- if user.is_none()
- || !connection
- .can_edit_recipe(user.as_ref().unwrap().id, recipe_id)
- .await?
- {
- Err(ErrorResponse::from(ron_error(
- StatusCode::UNAUTHORIZED,
- NOT_AUTHORIZED_MESSAGE,
- )))
- } else {
- Ok(())
- }
-}
-
-async fn check_user_rights_recipe_group(
- connection: &db::Connection,
- user: &Option<model::User>,
- group_id: i64,
-) -> Result<()> {
- if user.is_none()
- || !connection
- .can_edit_recipe_group(user.as_ref().unwrap().id, group_id)
- .await?
- {
- Err(ErrorResponse::from(ron_error(
- StatusCode::UNAUTHORIZED,
- NOT_AUTHORIZED_MESSAGE,
- )))
- } else {
- Ok(())
- }
-}
-
-async fn check_user_rights_recipe_groups(
- connection: &db::Connection,
- user: &Option<model::User>,
- group_ids: &[i64],
-) -> Result<()> {
- if user.is_none()
- || !connection
- .can_edit_recipe_all_groups(user.as_ref().unwrap().id, group_ids)
- .await?
- {
- Err(ErrorResponse::from(ron_error(
- StatusCode::UNAUTHORIZED,
- NOT_AUTHORIZED_MESSAGE,
- )))
- } else {
- Ok(())
- }
-}
-
-async fn check_user_rights_recipe_step(
- connection: &db::Connection,
- user: &Option<model::User>,
- step_id: i64,
-) -> Result<()> {
- if user.is_none()
- || !connection
- .can_edit_recipe_step(user.as_ref().unwrap().id, step_id)
- .await?
- {
- Err(ErrorResponse::from(ron_error(
- StatusCode::UNAUTHORIZED,
- NOT_AUTHORIZED_MESSAGE,
- )))
- } else {
- Ok(())
- }
-}
-
-async fn check_user_rights_recipe_steps(
- connection: &db::Connection,
- user: &Option<model::User>,
- step_ids: &[i64],
-) -> Result<()> {
- if user.is_none()
- || !connection
- .can_edit_recipe_all_steps(user.as_ref().unwrap().id, step_ids)
- .await?
- {
- Err(ErrorResponse::from(ron_error(
- StatusCode::UNAUTHORIZED,
- NOT_AUTHORIZED_MESSAGE,
- )))
- } else {
- Ok(())
- }
-}
-
-async fn check_user_rights_recipe_ingredient(
- connection: &db::Connection,
- user: &Option<model::User>,
- ingredient_id: i64,
-) -> Result<()> {
- if user.is_none()
- || !connection
- .can_edit_recipe_ingredient(user.as_ref().unwrap().id, ingredient_id)
- .await?
- {
- Err(ErrorResponse::from(ron_error(
- StatusCode::UNAUTHORIZED,
- NOT_AUTHORIZED_MESSAGE,
- )))
- } else {
- Ok(())
- }
-}
-
-async fn check_user_rights_recipe_ingredients(
- connection: &db::Connection,
- user: &Option<model::User>,
- ingredient_ids: &[i64],
-) -> Result<()> {
- if user.is_none()
- || !connection
- .can_edit_recipe_all_ingredients(user.as_ref().unwrap().id, ingredient_ids)
- .await?
- {
- Err(ErrorResponse::from(ron_error(
- StatusCode::UNAUTHORIZED,
- NOT_AUTHORIZED_MESSAGE,
- )))
- } else {
- Ok(())
- }
-}
-
-/*** Recipe ***/
-
-/// 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<db::Connection>,
- recipe_ids: Query<Ids>,
-) -> Result<impl IntoResponse> {
- Ok(ron_response_ok(common::ron_api::Strings {
- strs: connection.get_recipe_titles(&recipe_ids.ids).await?,
- }))
-}
-
-#[debug_handler]
-pub async fn set_recipe_title(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeTitle>,
-) -> Result<StatusCode> {
- check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
- connection
- .set_recipe_title(ron.recipe_id, &ron.title)
- .await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_recipe_description(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeDescription>,
-) -> Result<StatusCode> {
- check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
- connection
- .set_recipe_description(ron.recipe_id, &ron.description)
- .await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_servings(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeServings>,
-) -> Result<StatusCode> {
- check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
- connection
- .set_recipe_servings(ron.recipe_id, ron.servings)
- .await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_estimated_time(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeEstimatedTime>,
-) -> Result<StatusCode> {
- check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
- connection
- .set_recipe_estimated_time(ron.recipe_id, ron.estimated_time)
- .await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn get_tags(
- State(connection): State<db::Connection>,
- recipe_id: Query<Id>,
-) -> Result<impl IntoResponse> {
- Ok(ron_response_ok(common::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<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Tags>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
- connection
- .add_recipe_tags(
- ron.recipe_id,
- &ron.tags
- .into_iter()
- .map(|tag| tag.to_lowercase())
- .collect::<Vec<_>>(),
- )
- .await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn rm_tags(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Tags>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
- connection.rm_recipe_tags(ron.recipe_id, &ron.tags).await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_difficulty(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeDifficulty>,
-) -> Result<StatusCode> {
- check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
- connection
- .set_recipe_difficulty(ron.recipe_id, ron.difficulty)
- .await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_language(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeLanguage>,
-) -> Result<StatusCode> {
- if !crate::translation::available_codes()
- .iter()
- .any(|&l| l == ron.lang)
- {
- // TODO: log?
- return Ok(StatusCode::BAD_REQUEST);
- }
-
- check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
- connection
- .set_recipe_language(ron.recipe_id, &ron.lang)
- .await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_is_published(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetIsPublished>,
-) -> Result<StatusCode> {
- check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
- connection
- .set_recipe_is_published(ron.recipe_id, ron.is_published)
- .await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn rm(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Id>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe(&connection, &user, ron.id).await?;
- connection.rm_recipe(ron.id).await?;
- Ok(StatusCode::OK)
-}
-
-impl From<model::Group> for common::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(common::ron_api::Step::from)
- .collect(),
- }
- }
-}
-
-impl From<model::Step> for common::ron_api::Step {
- fn from(step: model::Step) -> Self {
- Self {
- id: step.id,
- action: step.action,
- ingredients: step
- .ingredients
- .into_iter()
- .map(common::ron_api::Ingredient::from)
- .collect(),
- }
- }
-}
-
-impl From<model::Ingredient> for common::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<db::Connection>,
- recipe_id: Query<Id>,
-) -> Result<impl IntoResponse> {
- // Here we don't check user rights on purpose.
- Ok(ron_response_ok(
- connection
- .get_groups(recipe_id.id)
- .await?
- .into_iter()
- .map(common::ron_api::Group::from)
- .collect::<Vec<_>>(),
- ))
-}
-
-#[debug_handler]
-pub async fn add_group(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Id>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe(&connection, &user, ron.id).await?;
- let id = connection.add_recipe_group(ron.id).await?;
-
- Ok(ron_response_ok(common::ron_api::Id { id }))
-}
-
-#[debug_handler]
-pub async fn rm_group(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Id>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_group(&connection, &user, ron.id).await?;
- connection.rm_recipe_group(ron.id).await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_group_name(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetGroupName>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_group(&connection, &user, ron.group_id).await?;
- connection.set_group_name(ron.group_id, &ron.name).await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_group_comment(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetGroupComment>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_group(&connection, &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<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Ids>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_groups(&connection, &user, &ron.ids).await?;
- connection.set_groups_order(&ron.ids).await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn add_step(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Id>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_group(&connection, &user, ron.id).await?;
- let id = connection.add_recipe_step(ron.id).await?;
-
- Ok(ron_response_ok(common::ron_api::Id { id }))
-}
-
-#[debug_handler]
-pub async fn rm_step(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Id>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_step(&connection, &user, ron.id).await?;
- connection.rm_recipe_step(ron.id).await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_step_action(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetStepAction>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_step(&connection, &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<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Ids>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_steps(&connection, &user, &ron.ids).await?;
- connection.set_steps_order(&ron.ids).await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn add_ingredient(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Id>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_step(&connection, &user, ron.id).await?;
- let id = connection.add_recipe_ingredient(ron.id).await?;
- Ok(ron_response_ok(common::ron_api::Id { id }))
-}
-
-#[debug_handler]
-pub async fn rm_ingredient(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Id>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_ingredient(&connection, &user, ron.id).await?;
- connection.rm_recipe_ingredient(ron.id).await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_ingredient_name(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetIngredientName>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
- connection
- .set_ingredient_name(ron.ingredient_id, &ron.name)
- .await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_ingredient_comment(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetIngredientComment>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
- connection
- .set_ingredient_comment(ron.ingredient_id, &ron.comment)
- .await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_ingredient_quantity(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetIngredientQuantity>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
- connection
- .set_ingredient_quantity(ron.ingredient_id, ron.quantity)
- .await?;
- Ok(StatusCode::OK)
-}
-
-#[debug_handler]
-pub async fn set_ingredient_unit(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::SetIngredientUnit>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_ingredient(&connection, &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<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::Ids>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe_ingredients(&connection, &user, &ron.ids).await?;
- connection.set_ingredients_order(&ron.ids).await?;
- Ok(StatusCode::OK)
-}
-
-/*** Calendar ***/
-
-#[debug_handler]
-pub async fn get_scheduled_recipes(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- date_range: Query<common::ron_api::DateRange>,
-) -> Result<impl IntoResponse> {
- if let Some(user) = user {
- Ok(ron_response_ok(common::ron_api::ScheduledRecipes {
- recipes: connection
- .get_scheduled_recipes(user.id, date_range.start_date, date_range.end_date)
- .await?,
- }))
- } else {
- Err(ErrorResponse::from(ron_error(
- StatusCode::UNAUTHORIZED,
- NOT_AUTHORIZED_MESSAGE,
- )))
- }
-}
-
-impl From<data::db::recipe::AddScheduledRecipeResult> for common::ron_api::ScheduleRecipeResult {
- fn from(db_res: data::db::recipe::AddScheduledRecipeResult) -> Self {
- match db_res {
- db::recipe::AddScheduledRecipeResult::Ok => Self::Ok,
- db::recipe::AddScheduledRecipeResult::RecipeAlreadyScheduledAtThisDate => {
- Self::RecipeAlreadyScheduledAtThisDate
- }
- }
- }
-}
-
-#[debug_handler]
-pub async fn schedule_recipe(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::ScheduleRecipe>,
-) -> Result<Response> {
- check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
- if let Some(user) = user {
- connection
- .add_scheduled_recipe(
- user.id,
- ron.recipe_id,
- ron.date,
- ron.servings,
- ron.add_ingredients_to_shopping_list,
- )
- .await
- .map(|res| {
- ron_response_ok(common::ron_api::ScheduleRecipeResult::from(res)).into_response()
- })
- .map_err(ErrorResponse::from)
- } else {
- Ok(StatusCode::OK.into_response())
- }
-}
-
-#[debug_handler]
-pub async fn rm_scheduled_recipe(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ExtractRon(ron): ExtractRon<common::ron_api::RemoveScheduledRecipe>,
-) -> Result<impl IntoResponse> {
- check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
- if let Some(user) = user {
- connection
- .rm_scheduled_recipe(
- user.id,
- ron.recipe_id,
- ron.date,
- ron.remove_ingredients_from_shopping_list,
- )
- .await?;
- }
- Ok(StatusCode::OK)
-}
-
-/*** Shopping list ***/
-
-impl From<model::ShoppingListItem> for common::ron_api::ShoppingListItem {
- fn from(item: model::ShoppingListItem) -> Self {
- Self {
- id: item.id,
- name: item.name,
- quantity_value: item.quantity_value,
- quantity_unit: item.quantity_unit,
- recipe_id: item.recipe_id,
- recipe_title: item.recipe_title,
- date: item.date,
- is_checked: item.is_checked,
- }
- }
-}
-
-#[debug_handler]
-pub async fn get_shopping_list(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
-) -> Result<impl IntoResponse> {
- if let Some(user) = user {
- Ok(ron_response_ok(
- connection
- .get_shopping_list(user.id)
- .await?
- .into_iter()
- .map(common::ron_api::ShoppingListItem::from)
- .collect::<Vec<_>>(),
- ))
- } else {
- Err(ErrorResponse::from(ron_error(
- StatusCode::UNAUTHORIZED,
- NOT_AUTHORIZED_MESSAGE,
- )))
- }
-}
-
-/*** 404 ***/
-
-#[debug_handler]
-pub async fn not_found(Extension(_user): Extension<Option<model::User>>) -> impl IntoResponse {
- ron_error(StatusCode::NOT_FOUND, "Not found")
-}
--- /dev/null
+use axum::{
+ debug_handler,
+ extract::{Extension, State},
+ http::StatusCode,
+ response::{ErrorResponse, IntoResponse, Response, Result},
+};
+use axum_extra::extract::Query;
+// use tracing::{event, Level};
+
+use crate::{
+ data::{self, db},
+ model,
+ ron_extractor::ExtractRon,
+ ron_utils::{ron_error, ron_response_ok},
+};
+
+use super::rights::*;
+
+#[debug_handler]
+pub async fn get_scheduled_recipes(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ date_range: Query<common::ron_api::DateRange>,
+) -> Result<impl IntoResponse> {
+ if let Some(user) = user {
+ Ok(ron_response_ok(common::ron_api::ScheduledRecipes {
+ recipes: connection
+ .get_scheduled_recipes(user.id, date_range.start_date, date_range.end_date)
+ .await?,
+ }))
+ } else {
+ Err(ErrorResponse::from(ron_error(
+ StatusCode::UNAUTHORIZED,
+ super::NOT_AUTHORIZED_MESSAGE,
+ )))
+ }
+}
+
+impl From<data::db::recipe::AddScheduledRecipeResult> for common::ron_api::ScheduleRecipeResult {
+ fn from(db_res: data::db::recipe::AddScheduledRecipeResult) -> Self {
+ match db_res {
+ db::recipe::AddScheduledRecipeResult::Ok => Self::Ok,
+ db::recipe::AddScheduledRecipeResult::RecipeAlreadyScheduledAtThisDate => {
+ Self::RecipeAlreadyScheduledAtThisDate
+ }
+ }
+ }
+}
+
+#[debug_handler]
+pub async fn schedule_recipe(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<common::ron_api::ScheduleRecipe>,
+) -> Result<Response> {
+ check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
+ if let Some(user) = user {
+ connection
+ .add_scheduled_recipe(
+ user.id,
+ ron.recipe_id,
+ ron.date,
+ ron.servings,
+ ron.add_ingredients_to_shopping_list,
+ )
+ .await
+ .map(|res| {
+ ron_response_ok(common::ron_api::ScheduleRecipeResult::from(res)).into_response()
+ })
+ .map_err(ErrorResponse::from)
+ } else {
+ Ok(StatusCode::OK.into_response())
+ }
+}
+
+#[debug_handler]
+pub async fn rm_scheduled_recipe(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<common::ron_api::RemoveScheduledRecipe>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
+ if let Some(user) = user {
+ connection
+ .rm_scheduled_recipe(
+ user.id,
+ ron.recipe_id,
+ ron.date,
+ ron.remove_ingredients_from_shopping_list,
+ )
+ .await?;
+ }
+ Ok(StatusCode::OK)
+}
--- /dev/null
+use axum::{
+ debug_handler,
+ extract::{Extension, State},
+ http::{HeaderMap, StatusCode},
+ response::{IntoResponse, Result},
+};
+use axum_extra::extract::cookie::{Cookie, CookieJar};
+// use tracing::{event, Level};
+
+use crate::{consts, data::db, model, ron_extractor::ExtractRon, ron_utils::ron_error};
+
+pub mod calendar;
+pub mod recipe;
+mod rights;
+pub mod shopping_list;
+
+const NOT_AUTHORIZED_MESSAGE: &str = "Action not authorized";
+
+#[debug_handler]
+pub async fn set_lang(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ headers: HeaderMap,
+ ExtractRon(ron): ExtractRon<common::ron_api::SetLang>,
+) -> Result<(CookieJar, StatusCode)> {
+ let mut jar = CookieJar::from_headers(&headers);
+ if let Some(user) = user {
+ connection.set_user_lang(user.id, &ron.lang).await?;
+ } else {
+ let cookie = Cookie::build((consts::COOKIE_LANG_NAME, ron.lang)).path("/");
+ jar = jar.add(cookie);
+ }
+ Ok((jar, StatusCode::OK))
+}
+
+/*** 404 ***/
+
+#[debug_handler]
+pub async fn not_found(Extension(_user): Extension<Option<model::User>>) -> impl IntoResponse {
+ ron_error(StatusCode::NOT_FOUND, "Not found")
+}
--- /dev/null
+use axum::{
+ debug_handler,
+ extract::{Extension, State},
+ http::StatusCode,
+ response::{IntoResponse, Result},
+};
+use axum_extra::extract::Query;
+use common::ron_api;
+// use tracing::{event, Level};
+
+use crate::{data::db, model, ron_extractor::ExtractRon, ron_utils::ron_response_ok};
+
+use super::rights::*;
+
+/// 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<db::Connection>,
+ recipe_ids: Query<ron_api::Ids>,
+) -> Result<impl IntoResponse> {
+ 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<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetRecipeTitle>,
+) -> Result<StatusCode> {
+ check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
+ connection
+ .set_recipe_title(ron.recipe_id, &ron.title)
+ .await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_description(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetRecipeDescription>,
+) -> Result<StatusCode> {
+ check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
+ connection
+ .set_recipe_description(ron.recipe_id, &ron.description)
+ .await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_servings(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetRecipeServings>,
+) -> Result<StatusCode> {
+ check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
+ connection
+ .set_recipe_servings(ron.recipe_id, ron.servings)
+ .await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_estimated_time(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetRecipeEstimatedTime>,
+) -> Result<StatusCode> {
+ check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
+ connection
+ .set_recipe_estimated_time(ron.recipe_id, ron.estimated_time)
+ .await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn get_tags(
+ State(connection): State<db::Connection>,
+ recipe_id: Query<ron_api::Id>,
+) -> Result<impl IntoResponse> {
+ 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<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Tags>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
+ connection
+ .add_recipe_tags(
+ ron.recipe_id,
+ &ron.tags
+ .into_iter()
+ .map(|tag| tag.to_lowercase())
+ .collect::<Vec<_>>(),
+ )
+ .await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn rm_tags(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Tags>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
+ connection.rm_recipe_tags(ron.recipe_id, &ron.tags).await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_difficulty(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetRecipeDifficulty>,
+) -> Result<StatusCode> {
+ check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
+ connection
+ .set_recipe_difficulty(ron.recipe_id, ron.difficulty)
+ .await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_language(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetRecipeLanguage>,
+) -> Result<StatusCode> {
+ if !crate::translation::available_codes()
+ .iter()
+ .any(|&l| l == ron.lang)
+ {
+ // TODO: log?
+ return Ok(StatusCode::BAD_REQUEST);
+ }
+
+ check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
+ connection
+ .set_recipe_language(ron.recipe_id, &ron.lang)
+ .await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_is_published(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetIsPublished>,
+) -> Result<StatusCode> {
+ check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
+ connection
+ .set_recipe_is_published(ron.recipe_id, ron.is_published)
+ .await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn rm(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Id>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe(&connection, &user, ron.id).await?;
+ connection.rm_recipe(ron.id).await?;
+ Ok(StatusCode::OK)
+}
+
+impl From<model::Group> 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<model::Step> 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<model::Ingredient> 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<db::Connection>,
+ recipe_id: Query<ron_api::Id>,
+) -> Result<impl IntoResponse> {
+ // Here we don't check user rights on purpose.
+ Ok(ron_response_ok(
+ connection
+ .get_groups(recipe_id.id)
+ .await?
+ .into_iter()
+ .map(ron_api::Group::from)
+ .collect::<Vec<_>>(),
+ ))
+}
+
+#[debug_handler]
+pub async fn add_group(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Id>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe(&connection, &user, ron.id).await?;
+ let id = connection.add_recipe_group(ron.id).await?;
+
+ Ok(ron_response_ok(ron_api::Id { id }))
+}
+
+#[debug_handler]
+pub async fn rm_group(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Id>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_group(&connection, &user, ron.id).await?;
+ connection.rm_recipe_group(ron.id).await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_group_name(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetGroupName>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_group(&connection, &user, ron.group_id).await?;
+ connection.set_group_name(ron.group_id, &ron.name).await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_group_comment(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetGroupComment>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_group(&connection, &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<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Ids>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_groups(&connection, &user, &ron.ids).await?;
+ connection.set_groups_order(&ron.ids).await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn add_step(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Id>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_group(&connection, &user, ron.id).await?;
+ let id = connection.add_recipe_step(ron.id).await?;
+
+ Ok(ron_response_ok(ron_api::Id { id }))
+}
+
+#[debug_handler]
+pub async fn rm_step(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Id>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_step(&connection, &user, ron.id).await?;
+ connection.rm_recipe_step(ron.id).await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_step_action(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetStepAction>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_step(&connection, &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<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Ids>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_steps(&connection, &user, &ron.ids).await?;
+ connection.set_steps_order(&ron.ids).await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn add_ingredient(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Id>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_step(&connection, &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<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Id>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_ingredient(&connection, &user, ron.id).await?;
+ connection.rm_recipe_ingredient(ron.id).await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_ingredient_name(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetIngredientName>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
+ connection
+ .set_ingredient_name(ron.ingredient_id, &ron.name)
+ .await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_ingredient_comment(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetIngredientComment>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
+ connection
+ .set_ingredient_comment(ron.ingredient_id, &ron.comment)
+ .await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_ingredient_quantity(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetIngredientQuantity>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
+ connection
+ .set_ingredient_quantity(ron.ingredient_id, ron.quantity)
+ .await?;
+ Ok(StatusCode::OK)
+}
+
+#[debug_handler]
+pub async fn set_ingredient_unit(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::SetIngredientUnit>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_ingredient(&connection, &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<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Ids>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_recipe_ingredients(&connection, &user, &ron.ids).await?;
+ connection.set_ingredients_order(&ron.ids).await?;
+ Ok(StatusCode::OK)
+}
--- /dev/null
+use axum::{
+ http::StatusCode,
+ response::{ErrorResponse, Result},
+};
+
+use crate::{data::db, model, ron_utils::ron_error};
+
+pub async fn check_user_rights_recipe(
+ connection: &db::Connection,
+ user: &Option<model::User>,
+ recipe_id: i64,
+) -> Result<()> {
+ if user.is_none()
+ || !connection
+ .can_edit_recipe(user.as_ref().unwrap().id, recipe_id)
+ .await?
+ {
+ Err(ErrorResponse::from(ron_error(
+ StatusCode::UNAUTHORIZED,
+ super::NOT_AUTHORIZED_MESSAGE,
+ )))
+ } else {
+ Ok(())
+ }
+}
+
+pub async fn check_user_rights_recipe_group(
+ connection: &db::Connection,
+ user: &Option<model::User>,
+ group_id: i64,
+) -> Result<()> {
+ if user.is_none()
+ || !connection
+ .can_edit_recipe_group(user.as_ref().unwrap().id, group_id)
+ .await?
+ {
+ Err(ErrorResponse::from(ron_error(
+ StatusCode::UNAUTHORIZED,
+ super::NOT_AUTHORIZED_MESSAGE,
+ )))
+ } else {
+ Ok(())
+ }
+}
+
+pub async fn check_user_rights_recipe_groups(
+ connection: &db::Connection,
+ user: &Option<model::User>,
+ group_ids: &[i64],
+) -> Result<()> {
+ if user.is_none()
+ || !connection
+ .can_edit_recipe_all_groups(user.as_ref().unwrap().id, group_ids)
+ .await?
+ {
+ Err(ErrorResponse::from(ron_error(
+ StatusCode::UNAUTHORIZED,
+ super::NOT_AUTHORIZED_MESSAGE,
+ )))
+ } else {
+ Ok(())
+ }
+}
+
+pub async fn check_user_rights_recipe_step(
+ connection: &db::Connection,
+ user: &Option<model::User>,
+ step_id: i64,
+) -> Result<()> {
+ if user.is_none()
+ || !connection
+ .can_edit_recipe_step(user.as_ref().unwrap().id, step_id)
+ .await?
+ {
+ Err(ErrorResponse::from(ron_error(
+ StatusCode::UNAUTHORIZED,
+ super::NOT_AUTHORIZED_MESSAGE,
+ )))
+ } else {
+ Ok(())
+ }
+}
+
+pub async fn check_user_rights_recipe_steps(
+ connection: &db::Connection,
+ user: &Option<model::User>,
+ step_ids: &[i64],
+) -> Result<()> {
+ if user.is_none()
+ || !connection
+ .can_edit_recipe_all_steps(user.as_ref().unwrap().id, step_ids)
+ .await?
+ {
+ Err(ErrorResponse::from(ron_error(
+ StatusCode::UNAUTHORIZED,
+ super::NOT_AUTHORIZED_MESSAGE,
+ )))
+ } else {
+ Ok(())
+ }
+}
+
+pub async fn check_user_rights_recipe_ingredient(
+ connection: &db::Connection,
+ user: &Option<model::User>,
+ ingredient_id: i64,
+) -> Result<()> {
+ if user.is_none()
+ || !connection
+ .can_edit_recipe_ingredient(user.as_ref().unwrap().id, ingredient_id)
+ .await?
+ {
+ Err(ErrorResponse::from(ron_error(
+ StatusCode::UNAUTHORIZED,
+ super::NOT_AUTHORIZED_MESSAGE,
+ )))
+ } else {
+ Ok(())
+ }
+}
+
+pub async fn check_user_rights_recipe_ingredients(
+ connection: &db::Connection,
+ user: &Option<model::User>,
+ ingredient_ids: &[i64],
+) -> Result<()> {
+ if user.is_none()
+ || !connection
+ .can_edit_recipe_all_ingredients(user.as_ref().unwrap().id, ingredient_ids)
+ .await?
+ {
+ Err(ErrorResponse::from(ron_error(
+ StatusCode::UNAUTHORIZED,
+ super::NOT_AUTHORIZED_MESSAGE,
+ )))
+ } else {
+ Ok(())
+ }
+}
+
+pub async fn check_user_rights_shopping_list_entry(
+ connection: &db::Connection,
+ user: &Option<model::User>,
+ entry_id: i64,
+) -> Result<()> {
+ if user.is_none()
+ || !connection
+ .can_edit_shopping_list_entry(user.as_ref().unwrap().id, entry_id)
+ .await?
+ {
+ Err(ErrorResponse::from(ron_error(
+ StatusCode::UNAUTHORIZED,
+ super::NOT_AUTHORIZED_MESSAGE,
+ )))
+ } else {
+ Ok(())
+ }
+}
--- /dev/null
+use axum::{
+ debug_handler,
+ extract::{Extension, State},
+ http::StatusCode,
+ response::{ErrorResponse, IntoResponse, Result},
+};
+use common::ron_api;
+
+use crate::{
+ data::db,
+ model,
+ ron_extractor::ExtractRon,
+ ron_utils::{ron_error, ron_response_ok},
+};
+
+use super::rights::*;
+
+impl From<model::ShoppingListItem> for common::ron_api::ShoppingListItem {
+ fn from(item: model::ShoppingListItem) -> Self {
+ Self {
+ id: item.id,
+ name: item.name,
+ quantity_value: item.quantity_value,
+ quantity_unit: item.quantity_unit,
+ recipe_id: item.recipe_id,
+ recipe_title: item.recipe_title,
+ date: item.date,
+ is_checked: item.is_checked,
+ }
+ }
+}
+
+#[debug_handler]
+pub async fn get(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+) -> Result<impl IntoResponse> {
+ if let Some(user) = user {
+ Ok(ron_response_ok(
+ connection
+ .get_shopping_list(user.id)
+ .await?
+ .into_iter()
+ .map(common::ron_api::ShoppingListItem::from)
+ .collect::<Vec<_>>(),
+ ))
+ } else {
+ Err(ErrorResponse::from(ron_error(
+ StatusCode::UNAUTHORIZED,
+ super::NOT_AUTHORIZED_MESSAGE,
+ )))
+ }
+}
+
+#[debug_handler]
+pub async fn set_entry_checked(
+ State(connection): State<db::Connection>,
+ Extension(user): Extension<Option<model::User>>,
+ ExtractRon(ron): ExtractRon<ron_api::Value<bool>>,
+) -> Result<impl IntoResponse> {
+ check_user_rights_shopping_list_entry(&connection, &user, ron.id).await?;
+ Ok(ron_response_ok(
+ connection.set_entry_checked(ron.id, ron.value).await?,
+ ))
+}
<div id="shopping-list">
</div>
+
+ <div id="shopping-list-checked">
+ </div>
</div>
<div id="hidden-templates">
pub id: i64,
}
+// A value associated with an id.
+#[derive(Serialize, Deserialize, Clone)]
+pub struct Value<T> {
+ pub id: i64,
+ pub value: T,
+}
+
#[derive(Serialize, Deserialize, Clone)]
pub struct Strings {
pub strs: Vec<String>,
use chrono::Locale;
use common::{ron_api, utils::substitute_with_names};
+use futures::TryFutureExt;
use gloo::events::EventListener;
use wasm_bindgen::prelude::*;
use wasm_bindgen_futures::spawn_local;
spawn_local(async move {
let item_template: Element = selector("#hidden-templates .shopping-item");
let container: Element = by_id("shopping-list");
+ let container_checked: Element = by_id("shopping-list-checked");
let date_format =
selector::<Element>("#hidden-templates .calendar-date-format").inner_html();
for item in shopping_list.get_items().await.unwrap() {
let item_element = item_template.deep_clone();
+
+ // item_element.set_id(format!("shopping-item-{}", ));
+
item_element
.selector::<Element>(".item-name")
.set_inner_html(&item.name);
.unwrap();
}
- container.append_child(&item_element).unwrap();
+ EventListener::new(
+ &item_element.selector(".item-is-checked"),
+ "change",
+ move |event| {
+ let input: HtmlInputElement = event.target().unwrap().dyn_into().unwrap();
+ spawn_local(async move {
+ shopping_list
+ .set_item_checked(item.id, input.checked())
+ .await
+ .unwrap();
+ let item_element = input.parent_element().unwrap();
+ item_element.remove();
+ // TODO: Find the correct place to insert the element.
+ if input.checked() {
+ by_id::<Element>("shopping-list-checked")
+ .append_child(&item_element)
+ .unwrap();
+ } else {
+ by_id::<Element>("shopping-list")
+ .append_child(&item_element)
+ .unwrap();
+ }
+ });
+ },
+ )
+ .forget();
+
+ EventListener::new(&item_element, "click", move |event| {
+ let target: Element = event.target().unwrap().dyn_into().unwrap();
+
+ // if target.class_name() == "item-is-checked"
+ })
+ .forget();
+
+ if item.is_checked {
+ item_element
+ .selector::<HtmlInputElement>(".item-is-checked")
+ .set_checked(true);
+ container_checked.append_child(&item_element).unwrap();
+ } else {
+ container.append_child(&item_element).unwrap();
+ }
}
});
Ok(request::get("shopping_list/get_list", ()).await?)
}
}
+
+ pub async fn set_item_checked(&self, item_id: i64, is_checked: bool) -> Result<()> {
+ if self.is_local {
+ todo!();
+ } else {
+ request::put(
+ "shopping_list/set_checked",
+ ron_api::Value {
+ id: item_id,
+ value: is_checked,
+ },
+ )
+ .await
+ .map_err(Error::from)
+ }
+ }
}