From 997d95e2017c23e570dc2dd90562fa37887196de Mon Sep 17 00:00:00 2001 From: Jeremy Wall Date: Wed, 21 Dec 2022 11:41:41 -0500 Subject: [PATCH] Use the api library on the frontend side --- Cargo.lock | 2 ++ api/Cargo.toml | 3 +++ api/src/lib.rs | 18 ++++++++++++++++++ kitchen/src/web/mod.rs | 27 +++++++++++++-------------- web/Cargo.toml | 1 + web/src/api.rs | 39 ++++++++++++++++++++++++++++----------- web/src/pages/login.rs | 11 ++++++++--- 7 files changed, 73 insertions(+), 28 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d25c4cb..7f602f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,6 +58,7 @@ name = "api" version = "0.1.0" dependencies = [ "axum", + "chrono", "recipes", "serde", ] @@ -1300,6 +1301,7 @@ dependencies = [ name = "kitchen-wasm" version = "0.2.11" dependencies = [ + "api", "async-trait", "base64", "chrono", diff --git a/api/Cargo.toml b/api/Cargo.toml index 07d80b0..d5ef339 100644 --- a/api/Cargo.toml +++ b/api/Cargo.toml @@ -8,11 +8,14 @@ edition = "2021" [dependencies] serde = "1.0.144" recipes = { path = "../recipes" } +chrono = "0.4.22" + [dependencies.axum] version = "0.5.16" optional = true [features] +default = [] server = ["axum"] browser = [] \ No newline at end of file diff --git a/api/src/lib.rs b/api/src/lib.rs index eebde5f..e02715a 100644 --- a/api/src/lib.rs +++ b/api/src/lib.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + // Copyright 2022 Jeremy Wall (Jeremy@marzhilsltudios.com) // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,6 +42,15 @@ impl Response { pub fn success(payload: T) -> Self { Self::Success(payload) } + + #[cfg(feature = "browser")] + pub fn as_success(self) -> Option { + if let Self::Success(val) = self { + Some(val) + } else { + None + } + } } #[cfg(feature = "server")] @@ -57,6 +68,7 @@ where }; (code, axum::Json::from(self)).into_response() } + // TODO(jwall): Perhaps this can show a more useful json payload? Self::NotFound => (StatusCode::NOT_FOUND, axum::Json::from(self)).into_response(), Self::Unauthorized => { (StatusCode::UNAUTHORIZED, axum::Json::from(self)).into_response() @@ -87,6 +99,10 @@ where } } +pub type CategoryResponse = Response; + +pub type EmptyResponse = Response<()>; + #[derive(Serialize, Deserialize)] pub struct UserData { pub user_id: String, @@ -125,6 +141,8 @@ impl From>> for PlanDataResponse { } } +pub type PlanHistoryResponse = Response>>; + #[derive(Serialize, Deserialize)] pub struct InventoryData { pub filtered_ingredients: Vec, diff --git a/kitchen/src/web/mod.rs b/kitchen/src/web/mod.rs index ebda686..69a5c24 100644 --- a/kitchen/src/web/mod.rs +++ b/kitchen/src/web/mod.rs @@ -23,7 +23,6 @@ use axum::{ response::{IntoResponse, Redirect, Response}, routing::{get, Router}, }; -use chrono::NaiveDate; use mime_guess; use recipes::{IngredientKey, RecipeEntry}; use rust_embed::RustEmbed; @@ -146,7 +145,7 @@ async fn api_save_categories( Extension(app_store): Extension>, session: storage::UserIdFromSession, Json(categories): Json, -) -> api::Response { +) -> api::CategoryResponse { use storage::{UserId, UserIdFromSession::FoundUserId}; if let FoundUserId(UserId(id)) = session { if let Err(e) = app_store @@ -158,9 +157,9 @@ async fn api_save_categories( format!("{:?}", e), ); } - api::Response::success("Successfully saved categories".into()) + api::CategoryResponse::success("Successfully saved categories".into()) } else { - api::Response::Unauthorized + api::CategoryResponse::Unauthorized } } @@ -168,7 +167,7 @@ async fn api_save_recipes( Extension(app_store): Extension>, session: storage::UserIdFromSession, Json(recipes): Json>, -) -> api::Response<()> { +) -> api::EmptyResponse { use storage::{UserId, UserIdFromSession::FoundUserId}; if let FoundUserId(UserId(id)) = session { let result = app_store @@ -176,7 +175,7 @@ async fn api_save_recipes( .await; result.map_err(|e| format!("Error: {:?}", e)).into() } else { - api::Response::Unauthorized + api::EmptyResponse::Unauthorized } } @@ -200,7 +199,7 @@ async fn api_plan_since( Extension(app_store): Extension>, session: storage::UserIdFromSession, Path(date): Path, -) -> api::Response>> { +) -> api::PlanHistoryResponse { use storage::{UserId, UserIdFromSession::FoundUserId}; if let FoundUserId(UserId(id)) = session { app_store @@ -209,7 +208,7 @@ async fn api_plan_since( .map_err(|e| format!("Error: {:?}", e)) .into() } else { - api::Response::Unauthorized + api::PlanHistoryResponse::Unauthorized } } @@ -217,7 +216,7 @@ async fn api_save_plan( Extension(app_store): Extension>, session: storage::UserIdFromSession, Json(meal_plan): Json>, -) -> api::Response<()> { +) -> api::EmptyResponse { use storage::{UserId, UserIdFromSession::FoundUserId}; if let FoundUserId(UserId(id)) = session { app_store @@ -226,7 +225,7 @@ async fn api_save_plan( .map_err(|e| format!("{:?}", e)) .into() } else { - api::Response::Unauthorized + api::EmptyResponse::Unauthorized } } @@ -273,7 +272,7 @@ async fn save_inventory_data( filtered_ingredients: BTreeSet, modified_amts: BTreeMap, extra_items: Vec<(String, String)>, -) -> api::Response<()> { +) -> api::EmptyResponse { app_store .save_inventory_data(id, filtered_ingredients, modified_amts, extra_items) .await @@ -289,7 +288,7 @@ async fn api_save_inventory_v2( Vec<(IngredientKey, String)>, Vec<(String, String)>, )>, -) -> api::Response<()> { +) -> api::EmptyResponse { use storage::{UserId, UserIdFromSession::FoundUserId}; if let FoundUserId(UserId(id)) = session { let filtered_ingredients = filtered_ingredients.into_iter().collect(); @@ -304,7 +303,7 @@ async fn api_save_inventory_v2( .await .into() } else { - api::Response::Unauthorized + api::EmptyResponse::Unauthorized } } @@ -315,7 +314,7 @@ async fn api_save_inventory( Vec, Vec<(IngredientKey, String)>, )>, -) -> api::Response<()> { +) -> api::EmptyResponse { use storage::{UserId, UserIdFromSession::FoundUserId}; if let FoundUserId(UserId(id)) = session { let filtered_ingredients = filtered_ingredients.into_iter().collect(); diff --git a/web/Cargo.toml b/web/Cargo.toml index 7f2a627..10d334e 100644 --- a/web/Cargo.toml +++ b/web/Cargo.toml @@ -14,6 +14,7 @@ crate-type = ["cdylib", "rlib"] [dependencies] recipes = { path = "../recipes" } +client-api = { path = "../api", package="api", features = ["browser"] } # This makes debugging panics more tractable. console_error_panic_hook = "0.1.7" serde_json = "1.0.79" diff --git a/web/src/api.rs b/web/src/api.rs index df6f57e..753da77 100644 --- a/web/src/api.rs +++ b/web/src/api.rs @@ -18,6 +18,7 @@ use serde_json::{from_str, to_string}; use sycamore::prelude::*; use tracing::{debug, error, info, instrument, warn}; +use client_api::*; use recipes::{parse, IngredientKey, Recipe, RecipeEntry}; use wasm_bindgen::JsValue; @@ -216,7 +217,7 @@ impl HttpStore { Err(format!("Status: {}", resp.status()).into()) } else { debug!("We got a valid response back!"); - let resp: String = resp.json().await?; + let resp = resp.json::().await?.as_success().unwrap(); storage.set("categories", &resp)?; Ok(Some(resp)) } @@ -250,8 +251,11 @@ impl HttpStore { Err(format!("Status: {}", resp.status()).into()) } else { debug!("We got a valid response back!"); - let entries: Option> = - resp.json().await.map_err(|e| format!("{}", e))?; + let entries = resp + .json::() + .await + .map_err(|e| format!("{}", e))? + .as_success(); if let Some(ref entries) = entries { for r in entries.iter() { storage.set( @@ -292,7 +296,12 @@ impl HttpStore { Ok(None) } else { debug!("We got a valid response back!"); - let entry: Option = resp.json().await.map_err(|e| format!("{}", e))?; + let entry = resp + .json::>>() + .await + .map_err(|e| format!("{}", e))? + .as_success() + .unwrap(); if let Some(ref entry) = entry { let serialized: String = to_string(entry).map_err(|e| format!("{}", e))?; storage.set(&recipe_key(entry.recipe_id()), &serialized)? @@ -399,8 +408,11 @@ impl HttpStore { Err(format!("Status: {}", resp.status()).into()) } else { debug!("We got a valid response back"); - let plan: Option> = - resp.json().await.map_err(|e| format!("{}", e))?; + let plan = resp + .json::() + .await + .map_err(|e| format!("{}", e))? + .as_success(); if let Some(ref entry) = plan { let serialized: String = to_string(entry).map_err(|e| format!("{}", e))?; storage.set("plan", &serialized)? @@ -448,11 +460,16 @@ impl HttpStore { }) } else { debug!("We got a valid response back"); - let (filtered_ingredients, modified_amts, extra_items): ( - Vec, - Vec<(IngredientKey, String)>, - Vec<(String, String)>, - ) = resp.json().await.map_err(|e| format!("{}", e))?; + let InventoryData { + filtered_ingredients, + modified_amts, + extra_items, + } = resp + .json::() + .await + .map_err(|e| format!("{}", e))? + .as_success() + .unwrap(); let _ = storage.set( "inventory", &to_string(&(&filtered_ingredients, &modified_amts)) diff --git a/web/src/pages/login.rs b/web/src/pages/login.rs index 844a382..2f25f56 100644 --- a/web/src/pages/login.rs +++ b/web/src/pages/login.rs @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. use base64; +use client_api::AccountResponse; use reqwasm::http; use sycamore::{futures::spawn_local_scoped, prelude::*}; use tracing::{debug, error, info}; @@ -20,7 +21,7 @@ fn token68(user: String, pass: String) -> String { base64::encode(format!("{}:{}", user, pass)) } -async fn authenticate(user: String, pass: String) -> bool { +async fn authenticate(user: String, pass: String) -> Option { debug!( username = user, password = pass, @@ -35,13 +36,16 @@ async fn authenticate(user: String, pass: String) -> bool { .await; if let Ok(resp) = &result { if resp.status() == 200 { - return true; + return resp + .json() + .await + .expect("Unparseable authentication response"); } error!(status = resp.status(), "Login was unsuccessful") } else { error!(err=?result.unwrap_err(), "Failed to send auth request"); } - return false; + return None; } #[component] @@ -55,6 +59,7 @@ pub fn LoginForm(cx: Scope) -> View { spawn_local_scoped(cx, async move { debug!("authenticating against ui"); // TODO(jwall): Navigate to plan if the below is successful. + // TODO(jwall): Store account data in our app_state. authenticate(username, password).await; }); }