diff --git a/web/src/api.rs b/web/src/api.rs index 49dc535..a73ed47 100644 --- a/web/src/api.rs +++ b/web/src/api.rs @@ -14,13 +14,12 @@ use std::collections::BTreeMap; use reqwasm; -use serde_json::to_string; +use serde_json::{from_str, to_string}; use sycamore::prelude::*; use tracing::{debug, error, info, instrument, warn}; use recipes::{parse, Recipe, RecipeEntry}; use wasm_bindgen::JsValue; -use web_sys::ResponseType; use crate::{app_state, js_lib}; @@ -126,6 +125,10 @@ impl From for Error { } } +fn recipe_key(id: S) -> String { + format!("recipe:{}", id) +} + #[derive(Clone, Debug)] pub struct HttpStore { root: String, @@ -148,19 +151,23 @@ impl HttpStore { pub async fn get_categories(&self) -> Result, Error> { let mut path = self.root.clone(); path.push_str("/categories"); - let resp = reqwasm::http::Request::get(&path).send().await?; let storage = js_lib::get_storage(); + let resp = match reqwasm::http::Request::get(&path).send().await { + Ok(resp) => resp, + Err(reqwasm::Error::JsError(err)) => { + error!(path, ?err, "Error hitting api"); + return Ok(storage.get("categories")?); + } + Err(err) => { + return Err(err)?; + } + }; if resp.status() == 404 { debug!("Categories returned 404"); storage.remove_item("categories")?; Ok(None) } else if resp.status() != 200 { - if resp.type_() == ResponseType::Error { - let categories = storage.get("categories")?; - Ok(categories) - } else { - Err(format!("Status: {}", resp.status()).into()) - } + Err(format!("Status: {}", resp.status()).into()) } else { debug!("We got a valid response back!"); let resp: String = resp.json().await?; @@ -173,28 +180,75 @@ impl HttpStore { pub async fn get_recipes(&self) -> Result>, Error> { let mut path = self.root.clone(); path.push_str("/recipes"); - let resp = reqwasm::http::Request::get(&path).send().await?; + let storage = js_lib::get_storage(); + let resp = match reqwasm::http::Request::get(&path).send().await { + Ok(resp) => resp, + Err(reqwasm::Error::JsError(err)) => { + error!(path, ?err, "Error hitting api"); + let mut entries = Vec::new(); + for key in js_lib::get_storage_keys() { + if key.starts_with("recipe:") { + let entry = from_str(&storage.get_item(&key)?.unwrap()) + .map_err(|e| format!("{}", e))?; + entries.push(entry); + } + } + return Ok(Some(entries)); + } + Err(err) => { + return Err(err)?; + } + }; + let storage = js_lib::get_storage(); if resp.status() != 200 { Err(format!("Status: {}", resp.status()).into()) } else { debug!("We got a valid response back!"); - Ok(resp.json().await.map_err(|e| format!("{}", e))?) + let entries: Option> = + resp.json().await.map_err(|e| format!("{}", e))?; + if let Some(ref entries) = entries { + for r in entries.iter() { + storage.set( + &recipe_key(r.recipe_id()), + &to_string(&r).expect("Unable to serialize recipe entries"), + )?; + } + } + Ok(entries) } } - pub async fn get_recipe_text>( + pub async fn get_recipe_text + std::fmt::Display>( &self, id: S, ) -> Result, Error> { let mut path = self.root.clone(); path.push_str("/recipe/"); path.push_str(id.as_ref()); - let resp = reqwasm::http::Request::get(&path).send().await?; + let storage = js_lib::get_storage(); + let resp = match reqwasm::http::Request::get(&path).send().await { + Ok(resp) => resp, + Err(reqwasm::Error::JsError(err)) => { + error!(path, ?err, "Error hitting api"); + return match storage.get(&recipe_key(&id))? { + Some(s) => Ok(Some(from_str(&s).map_err(|e| format!("{}", e))?)), + None => Ok(None), + }; + } + Err(err) => { + return Err(err)?; + } + }; if resp.status() != 200 { Err(format!("Status: {}", resp.status()).into()) } else { debug!("We got a valid response back!"); - Ok(resp.json().await.map_err(|e| format!("{}", e))?) + let entry: Option = resp.json().await.map_err(|e| format!("{}", e))?; + 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)? + } + Ok(entry) } } @@ -202,8 +256,16 @@ impl HttpStore { pub async fn save_recipes(&self, recipes: Vec) -> Result<(), Error> { let mut path = self.root.clone(); path.push_str("/recipes"); + let storage = js_lib::get_storage(); + for r in recipes.iter() { + storage.set( + &recipe_key(r.recipe_id()), + &to_string(&r).expect("Unable to serialize recipe entries"), + )?; + } + let serialized = to_string(&recipes).expect("Unable to serialize recipe entries"); let resp = reqwasm::http::Request::post(&path) - .body(to_string(&recipes).expect("Unable to serialize recipe entries")) + .body(&serialized) .header("content-type", "application/json") .send() .await?; diff --git a/web/src/js_lib.rs b/web/src/js_lib.rs index 0ab757b..bc7c16c 100644 --- a/web/src/js_lib.rs +++ b/web/src/js_lib.rs @@ -11,7 +11,7 @@ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. -use wasm_bindgen::{JsCast, JsValue}; +use wasm_bindgen::JsCast; use web_sys::{window, Element, Storage}; pub fn get_element_by_id(id: &str) -> Result, Element> @@ -36,3 +36,12 @@ pub fn get_storage() -> Storage { .expect("Failed to get storage") .expect("No storage available") } + +pub fn get_storage_keys() -> Vec { + let storage = get_storage(); + let mut keys = Vec::new(); + for idx in 0..storage.length().unwrap() { + keys.push(get_storage().key(idx).unwrap().unwrap()) + } + keys +}