diff --git a/kitchen/src/web/auth.rs b/kitchen/src/web/auth.rs index d4a0575..86ef87f 100644 --- a/kitchen/src/web/auth.rs +++ b/kitchen/src/web/auth.rs @@ -74,6 +74,7 @@ pub async fn handler( let cookie = Cookie::build(storage::AXUM_SESSION_COOKIE_NAME, cookie_value) .same_site(SameSite::Strict) .secure(true) + .path("/") .finish(); let parsed_cookie = match cookie.to_string().parse() { Err(err) => { diff --git a/models/recipe.als b/models/recipe.als deleted file mode 100644 index 85d138e..0000000 --- a/models/recipe.als +++ /dev/null @@ -1,42 +0,0 @@ -sig Recipe { - , desc: String - , title: String - , ingredients: set Ingredient -} - -sig Cat { - , name: String -} - -abstract sig Unit { } - -sig Tsp extends Unit {} -sig Tbsp extends Unit {} -sig Cup extends Unit {} -sig Pint extends Unit {} -sig Quart extends Unit {} -sig Gallon extends Unit {} - -abstract sig Wgt extends Unit { - -} -sig MilliGram extends Wgt {} -sig Gram extends Wgt {} -sig KiloGram extends Wgt {} - -abstract sig Amt {} -sig Count extends Amt { - , value: Int -} - -sig Frac extends Amt { - , numerator: Int - , denominator: Int -} - -sig Ingredient { - , category: Cat - , name: String - , unit: Unit - , amt: Amt -} \ No newline at end of file diff --git a/web/src/api.rs b/web/src/api.rs index be44f01..df6f57e 100644 --- a/web/src/api.rs +++ b/web/src/api.rs @@ -94,9 +94,18 @@ pub async fn init_page_state(store: &HttpStore, state: &app_state::State) -> Res } info!("Synchronizing inventory data"); match store.get_inventory_data().await { - Ok((filtered_ingredients, modified_amts)) => { + Ok((filtered_ingredients, modified_amts, mut extra_items)) => { state.reset_modified_amts(modified_amts); state.filtered_ingredients.set(filtered_ingredients); + state.extras.set( + extra_items + .drain(0..) + .enumerate() + .map(|(idx, (amt, name))| { + (idx, (create_rc_signal(amt.clone()), create_rc_signal(name))) + }) + .collect(), + ) } Err(e) => { error!("{:?}", e); @@ -160,10 +169,22 @@ pub struct HttpStore { } impl HttpStore { - pub fn new(root: String) -> Self { + fn new(root: String) -> Self { Self { root } } + pub fn v1_path(&self) -> String { + let mut path = self.root.clone(); + path.push_str("/v1"); + path + } + + pub fn v2_path(&self) -> String { + let mut path = self.root.clone(); + path.push_str("/v2"); + path + } + pub fn provide_context>(cx: Scope, root: S) { provide_context(cx, std::rc::Rc::new(Self::new(root.into()))); } @@ -174,7 +195,7 @@ impl HttpStore { //#[instrument] pub async fn get_categories(&self) -> Result, Error> { - let mut path = self.root.clone(); + let mut path = self.v1_path(); path.push_str("/categories"); let storage = js_lib::get_storage(); let resp = match reqwasm::http::Request::get(&path).send().await { @@ -203,7 +224,7 @@ impl HttpStore { #[instrument] pub async fn get_recipes(&self) -> Result>, Error> { - let mut path = self.root.clone(); + let mut path = self.v1_path(); path.push_str("/recipes"); let storage = js_lib::get_storage(); let resp = match reqwasm::http::Request::get(&path).send().await { @@ -247,7 +268,7 @@ impl HttpStore { &self, id: S, ) -> Result, Error> { - let mut path = self.root.clone(); + let mut path = self.v1_path(); path.push_str("/recipe/"); path.push_str(id.as_ref()); let storage = js_lib::get_storage(); @@ -282,7 +303,7 @@ impl HttpStore { #[instrument(skip(recipes), fields(count=recipes.len()))] pub async fn save_recipes(&self, recipes: Vec) -> Result<(), Error> { - let mut path = self.root.clone(); + let mut path = self.v1_path(); path.push_str("/recipes"); let storage = js_lib::get_storage(); for r in recipes.iter() { @@ -310,7 +331,7 @@ impl HttpStore { #[instrument(skip(categories))] pub async fn save_categories(&self, categories: String) -> Result<(), Error> { - let mut path = self.root.clone(); + let mut path = self.v1_path(); path.push_str("/categories"); let storage = js_lib::get_storage(); storage.set("categories", &categories)?; @@ -327,21 +348,31 @@ impl HttpStore { } } + #[instrument] pub async fn save_state(&self, state: std::rc::Rc) -> Result<(), Error> { let mut plan = Vec::new(); for (key, count) in state.recipe_counts.get_untracked().iter() { plan.push((key.clone(), *count.get_untracked() as i32)); } + debug!("Saving plan data"); self.save_plan(plan).await?; + debug!("Saving inventory data"); self.save_inventory_data( state.filtered_ingredients.get_untracked().as_ref().clone(), state.get_current_modified_amts(), + state + .extras + .get() + .as_ref() + .iter() + .map(|t| (t.1 .0.get().as_ref().clone(), t.1 .1.get().as_ref().clone())) + .collect(), ) .await } pub async fn save_plan(&self, plan: Vec<(String, i32)>) -> Result<(), Error> { - let mut path = self.root.clone(); + let mut path = self.v1_path(); path.push_str("/plan"); let storage = js_lib::get_storage(); let serialized_plan = to_string(&plan).expect("Unable to encode plan as json"); @@ -360,7 +391,7 @@ impl HttpStore { } pub async fn get_plan(&self) -> Result>, Error> { - let mut path = self.root.clone(); + let mut path = self.v1_path(); path.push_str("/plan"); let resp = reqwasm::http::Request::get(&path).send().await?; let storage = js_lib::get_storage(); @@ -380,22 +411,47 @@ impl HttpStore { pub async fn get_inventory_data( &self, - ) -> Result<(BTreeSet, BTreeMap), Error> { - let mut path = self.root.clone(); + ) -> Result< + ( + BTreeSet, + BTreeMap, + Vec<(String, String)>, + ), + Error, + > { + let mut path = self.v2_path(); path.push_str("/inventory"); let storage = js_lib::get_storage(); let resp = reqwasm::http::Request::get(&path).send().await?; if resp.status() != 200 { let err = Err(format!("Status: {}", resp.status()).into()); Ok(match storage.get("inventory") { - Ok(Some(val)) => from_str(&val).expect("Failed to deserialize cached val"), + Ok(Some(val)) => match from_str(&val) { + // TODO(jwall): Once we remove the v1 endpoint this is no longer needed. + Ok((filtered_ingredients, modified_amts)) => { + (filtered_ingredients, modified_amts, Vec::new()) + } + Err(_) => match from_str(&val) { + Ok((filtered_ingredients, modified_amts, extra_items)) => { + (filtered_ingredients, modified_amts, extra_items) + } + Err(_) => { + // Whatever is in storage is corrupted or invalid so we should delete it. + storage + .delete("inventory") + .expect("Unable to delete corrupt data in inventory cache"); + return err; + } + }, + }, Ok(None) | Err(_) => return err, }) } else { debug!("We got a valid response back"); - let (filtered_ingredients, modified_amts): ( + let (filtered_ingredients, modified_amts, extra_items): ( Vec, Vec<(IngredientKey, String)>, + Vec<(String, String)>, ) = resp.json().await.map_err(|e| format!("{}", e))?; let _ = storage.set( "inventory", @@ -405,29 +461,37 @@ impl HttpStore { Ok(( filtered_ingredients.into_iter().collect(), modified_amts.into_iter().collect(), + extra_items, )) } } + #[instrument] pub async fn save_inventory_data( &self, filtered_ingredients: BTreeSet, modified_amts: BTreeMap, + extra_items: Vec<(String, String)>, ) -> Result<(), Error> { - let mut path = self.root.clone(); + let mut path = self.v2_path(); + path.push_str("/inventory"); let filtered_ingredients: Vec = filtered_ingredients.into_iter().collect(); let modified_amts: Vec<(IngredientKey, String)> = modified_amts.into_iter().collect(); - let serialized_inventory = to_string(&(filtered_ingredients, modified_amts)) + let serialized_inventory = to_string(&(filtered_ingredients, modified_amts, extra_items)) .expect("Unable to encode plan as json"); let storage = js_lib::get_storage(); - let _ = storage.set("inventory", &serialized_inventory); - path.push_str("/inventory"); + debug!("Storing inventory data in cache"); + storage + .set("inventory", &serialized_inventory) + .expect("Failed to cache inventory data"); + debug!("Storing inventory data via API"); let resp = reqwasm::http::Request::post(&path) .body(&serialized_inventory) .header("content-type", "application/json") .send() .await?; if resp.status() != 200 { + debug!("Invalid response back"); Err(format!("Status: {}", resp.status()).into()) } else { debug!("We got a valid response back!"); diff --git a/web/src/web.rs b/web/src/web.rs index 9063ba8..c31dbc7 100644 --- a/web/src/web.rs +++ b/web/src/web.rs @@ -21,7 +21,7 @@ use crate::{api, routing::Handler as RouteHandler}; #[component] pub fn UI(cx: Scope) -> View { crate::app_state::State::provide_context(cx); - api::HttpStore::provide_context(cx, "/api/v1".to_owned()); + api::HttpStore::provide_context(cx, "/api".to_owned()); info!("Starting UI"); let view = create_signal(cx, View::empty());