mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Caching for recipes in the case of network errors
This commit is contained in:
parent
277ae423cb
commit
5f4b7c3f02
@ -14,13 +14,12 @@
|
|||||||
use std::collections::BTreeMap;
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
use reqwasm;
|
use reqwasm;
|
||||||
use serde_json::to_string;
|
use serde_json::{from_str, to_string};
|
||||||
use sycamore::prelude::*;
|
use sycamore::prelude::*;
|
||||||
use tracing::{debug, error, info, instrument, warn};
|
use tracing::{debug, error, info, instrument, warn};
|
||||||
|
|
||||||
use recipes::{parse, Recipe, RecipeEntry};
|
use recipes::{parse, Recipe, RecipeEntry};
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
use web_sys::ResponseType;
|
|
||||||
|
|
||||||
use crate::{app_state, js_lib};
|
use crate::{app_state, js_lib};
|
||||||
|
|
||||||
@ -126,6 +125,10 @@ impl From<reqwasm::Error> for Error {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn recipe_key<S: std::fmt::Display>(id: S) -> String {
|
||||||
|
format!("recipe:{}", id)
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct HttpStore {
|
pub struct HttpStore {
|
||||||
root: String,
|
root: String,
|
||||||
@ -148,19 +151,23 @@ impl HttpStore {
|
|||||||
pub async fn get_categories(&self) -> Result<Option<String>, Error> {
|
pub async fn get_categories(&self) -> Result<Option<String>, Error> {
|
||||||
let mut path = self.root.clone();
|
let mut path = self.root.clone();
|
||||||
path.push_str("/categories");
|
path.push_str("/categories");
|
||||||
let resp = reqwasm::http::Request::get(&path).send().await?;
|
|
||||||
let storage = js_lib::get_storage();
|
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 {
|
if resp.status() == 404 {
|
||||||
debug!("Categories returned 404");
|
debug!("Categories returned 404");
|
||||||
storage.remove_item("categories")?;
|
storage.remove_item("categories")?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else if resp.status() != 200 {
|
} else if resp.status() != 200 {
|
||||||
if resp.type_() == ResponseType::Error {
|
Err(format!("Status: {}", resp.status()).into())
|
||||||
let categories = storage.get("categories")?;
|
|
||||||
Ok(categories)
|
|
||||||
} else {
|
|
||||||
Err(format!("Status: {}", resp.status()).into())
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
debug!("We got a valid response back!");
|
debug!("We got a valid response back!");
|
||||||
let resp: String = resp.json().await?;
|
let resp: String = resp.json().await?;
|
||||||
@ -173,28 +180,75 @@ impl HttpStore {
|
|||||||
pub async fn get_recipes(&self) -> Result<Option<Vec<RecipeEntry>>, Error> {
|
pub async fn get_recipes(&self) -> Result<Option<Vec<RecipeEntry>>, Error> {
|
||||||
let mut path = self.root.clone();
|
let mut path = self.root.clone();
|
||||||
path.push_str("/recipes");
|
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 {
|
if resp.status() != 200 {
|
||||||
Err(format!("Status: {}", resp.status()).into())
|
Err(format!("Status: {}", resp.status()).into())
|
||||||
} else {
|
} else {
|
||||||
debug!("We got a valid response back!");
|
debug!("We got a valid response back!");
|
||||||
Ok(resp.json().await.map_err(|e| format!("{}", e))?)
|
let entries: Option<Vec<RecipeEntry>> =
|
||||||
|
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<S: AsRef<str>>(
|
pub async fn get_recipe_text<S: AsRef<str> + std::fmt::Display>(
|
||||||
&self,
|
&self,
|
||||||
id: S,
|
id: S,
|
||||||
) -> Result<Option<RecipeEntry>, Error> {
|
) -> Result<Option<RecipeEntry>, Error> {
|
||||||
let mut path = self.root.clone();
|
let mut path = self.root.clone();
|
||||||
path.push_str("/recipe/");
|
path.push_str("/recipe/");
|
||||||
path.push_str(id.as_ref());
|
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 {
|
if resp.status() != 200 {
|
||||||
Err(format!("Status: {}", resp.status()).into())
|
Err(format!("Status: {}", resp.status()).into())
|
||||||
} else {
|
} else {
|
||||||
debug!("We got a valid response back!");
|
debug!("We got a valid response back!");
|
||||||
Ok(resp.json().await.map_err(|e| format!("{}", e))?)
|
let entry: Option<RecipeEntry> = 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<RecipeEntry>) -> Result<(), Error> {
|
pub async fn save_recipes(&self, recipes: Vec<RecipeEntry>) -> Result<(), Error> {
|
||||||
let mut path = self.root.clone();
|
let mut path = self.root.clone();
|
||||||
path.push_str("/recipes");
|
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)
|
let resp = reqwasm::http::Request::post(&path)
|
||||||
.body(to_string(&recipes).expect("Unable to serialize recipe entries"))
|
.body(&serialized)
|
||||||
.header("content-type", "application/json")
|
.header("content-type", "application/json")
|
||||||
.send()
|
.send()
|
||||||
.await?;
|
.await?;
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
use wasm_bindgen::{JsCast, JsValue};
|
use wasm_bindgen::JsCast;
|
||||||
use web_sys::{window, Element, Storage};
|
use web_sys::{window, Element, Storage};
|
||||||
|
|
||||||
pub fn get_element_by_id<E>(id: &str) -> Result<Option<E>, Element>
|
pub fn get_element_by_id<E>(id: &str) -> Result<Option<E>, Element>
|
||||||
@ -36,3 +36,12 @@ pub fn get_storage() -> Storage {
|
|||||||
.expect("Failed to get storage")
|
.expect("Failed to get storage")
|
||||||
.expect("No storage available")
|
.expect("No storage available")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_storage_keys() -> Vec<String> {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user