From 1a0ffb2d89535f603babc9f3e9407f14f7253b4b Mon Sep 17 00:00:00 2001 From: traxys Date: Sun, 25 Jun 2023 15:13:15 +0200 Subject: [PATCH] app,server: Allow to display a recipe --- api/src/lib.rs | 7 ++++ app/src/main.rs | 2 +- app/src/recipe.rs | 89 +++++++++++++++++++++++++++++++++++++++++++- src/routes/mod.rs | 4 ++ src/routes/recipe.rs | 61 +++++++++++++++++++++++++++++- 5 files changed, 159 insertions(+), 4 deletions(-) diff --git a/api/src/lib.rs b/api/src/lib.rs index 48f86b8..1dbc720 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -101,3 +101,10 @@ pub struct CreateRecipeResponse { pub struct ListRecipesResponse { pub recipes: Vec<(i64, String)>, } + +#[derive(Serialize, Deserialize, Clone, Debug)] +pub struct RecipeInfo { + pub name: String, + pub steps: Vec, + pub ingredients: Vec<(i64, IngredientInfo, f64)>, +} diff --git a/app/src/main.rs b/app/src/main.rs index f9ea807..bfdf73d 100644 --- a/app/src/main.rs +++ b/app/src/main.rs @@ -515,7 +515,7 @@ fn switch(route: Route) -> Html { }, Route::Recipe { id } => html! { - {format!("RECIPE {id}")} + }, Route::SearchRecipe => html!{ diff --git a/app/src/recipe.rs b/app/src/recipe.rs index ec20d49..dd07948 100644 --- a/app/src/recipe.rs +++ b/app/src/recipe.rs @@ -1,3 +1,4 @@ +use api::RecipeInfo; use uuid::Uuid; use yew::{prelude::*, suspense::use_future}; use yew_router::prelude::*; @@ -34,7 +35,7 @@ fn RecipeListInner(props: &RecipeListProps) -> HtmlResult { Ok(match &*state { Ok(l) => html! {
-
+
{for l.recipes.iter().map(|(id, name)| html!{
@@ -82,3 +83,89 @@ pub fn RecipeList() -> Html {
} } + +#[derive(PartialEq, Eq, Clone, Properties)] +pub struct RecipeViewerProps { + pub id: i64, +} + +#[derive(PartialEq, Eq, Clone, Properties)] +struct RecipeViewerInnerProps { + id: i64, + token: String, + household: Uuid, +} + +async fn fetch_recipe(token: String, household: Uuid, id: i64) -> anyhow::Result { + let rsp = gloo_net::http::Request::get(api!("household/{household}/recipe/{id}")) + .header("Authorization", &format!("Bearer {token}")) + .send() + .await?; + + if !rsp.ok() { + let body = rsp.text().await.unwrap_or_default(); + anyhow::bail!("Could not get recipe (code={}): {body}", rsp.status()); + } + + Ok(rsp.json().await?) +} + +#[function_component] +fn RecipeViewerInner(props: &RecipeViewerInnerProps) -> HtmlResult { + let recipe = use_future(|| fetch_recipe(props.token.clone(), props.household, props.id))?; + + Ok(match &*recipe { + Ok(r) => html! {<> +

{&r.name}

+
+
+

{"Ingredients"}

+
    + {for r.ingredients.iter().map(|(id, info, amount)| html!{ +
  • + {format!("{amount}{} {}", info.unit.as_deref().unwrap_or(""), info.name)} +
  • + })} +
+
+
+
+

{"Steps"}

+
    + {for r.steps.iter().map(|text| html!{ +
  • {text}
  • + })} +
+
+ }, + Err(e) => html! { + + }, + }) +} + +#[function_component] +pub fn RecipeViewer(props: &RecipeViewerProps) -> Html { + let global_state = use_state(RegaladeGlobalState::get); + let fallback = html! { +
+ {"Loading ..."} +
+ }; + + html! { +
+
+ + + +
+
+ } +} diff --git a/src/routes/mod.rs b/src/routes/mod.rs index b0c3780..ab5d313 100644 --- a/src/routes/mod.rs +++ b/src/routes/mod.rs @@ -222,4 +222,8 @@ pub(crate) fn router(api_allowed: Option) -> Router { .get(recipe::list_recipes) .layer(mk_service(vec![Method::POST, Method::GET])), ) + .route( + "/household/:house_id/recipe/:recipe_id", + get(recipe::fetch_recipe).layer(mk_service(vec![Method::GET])), + ) } diff --git a/src/routes/recipe.rs b/src/routes/recipe.rs index 186ccda..2fc4a9d 100644 --- a/src/routes/recipe.rs +++ b/src/routes/recipe.rs @@ -1,5 +1,10 @@ -use api::{CreateRecipeRequest, CreateRecipeResponse, ListRecipesResponse}; -use axum::{extract::State, Json}; +use api::{ + CreateRecipeRequest, CreateRecipeResponse, IngredientInfo, ListRecipesResponse, RecipeInfo, +}; +use axum::{ + extract::{Path, State}, + Json, +}; use sea_orm::{prelude::*, ActiveValue, TransactionTrait}; use crate::entity::{ingredient, prelude::*, recipe, recipe_ingerdients, recipe_steps}; @@ -77,3 +82,55 @@ pub(super) async fn list_recipes( } .into()) } + +#[derive(serde::Deserialize)] +pub(super) struct RecipeId { + recipe_id: i64, +} + +pub(super) async fn fetch_recipe( + AuthorizedHousehold(household): AuthorizedHousehold, + State(state): State, + Path(RecipeId { recipe_id }): Path, +) -> JsonResult { + let Some(recipe) = household + .find_related(Recipe) + .filter(recipe::Column::Id.eq(recipe_id)) + .one(&state.db).await? else { + return Err(RouteError::RessourceNotFound); + }; + + let steps = recipe + .find_related(RecipeSteps) + .all(&state.db) + .await? + .into_iter() + .map(|m| m.text) + .collect(); + + let recipe_ingredients = recipe.find_related(Ingredient).all(&state.db).await?; + + let mut ingredients = Vec::new(); + + for ingredient in recipe_ingredients { + ingredients.push(( + ingredient.id, + IngredientInfo { + name: ingredient.name, + unit: ingredient.unit, + }, + RecipeIngerdients::find_by_id((recipe.id, ingredient.id)) + .one(&state.db) + .await? + .expect("Ingredient should exist as it was fetched") + .amount, + )); + } + + Ok(RecipeInfo { + name: recipe.name, + steps, + ingredients, + } + .into()) +}