mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Use the api library on the frontend side
This commit is contained in:
parent
db63deb319
commit
997d95e201
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -58,6 +58,7 @@ name = "api"
|
|||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"axum",
|
"axum",
|
||||||
|
"chrono",
|
||||||
"recipes",
|
"recipes",
|
||||||
"serde",
|
"serde",
|
||||||
]
|
]
|
||||||
@ -1300,6 +1301,7 @@ dependencies = [
|
|||||||
name = "kitchen-wasm"
|
name = "kitchen-wasm"
|
||||||
version = "0.2.11"
|
version = "0.2.11"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
|
"api",
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64",
|
"base64",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
@ -8,11 +8,14 @@ edition = "2021"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
serde = "1.0.144"
|
serde = "1.0.144"
|
||||||
recipes = { path = "../recipes" }
|
recipes = { path = "../recipes" }
|
||||||
|
chrono = "0.4.22"
|
||||||
|
|
||||||
|
|
||||||
[dependencies.axum]
|
[dependencies.axum]
|
||||||
version = "0.5.16"
|
version = "0.5.16"
|
||||||
optional = true
|
optional = true
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
|
default = []
|
||||||
server = ["axum"]
|
server = ["axum"]
|
||||||
browser = []
|
browser = []
|
@ -1,3 +1,5 @@
|
|||||||
|
use std::collections::BTreeMap;
|
||||||
|
|
||||||
// Copyright 2022 Jeremy Wall (Jeremy@marzhilsltudios.com)
|
// Copyright 2022 Jeremy Wall (Jeremy@marzhilsltudios.com)
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -40,6 +42,15 @@ impl<T> Response<T> {
|
|||||||
pub fn success(payload: T) -> Self {
|
pub fn success(payload: T) -> Self {
|
||||||
Self::Success(payload)
|
Self::Success(payload)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "browser")]
|
||||||
|
pub fn as_success(self) -> Option<T> {
|
||||||
|
if let Self::Success(val) = self {
|
||||||
|
Some(val)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "server")]
|
#[cfg(feature = "server")]
|
||||||
@ -57,6 +68,7 @@ where
|
|||||||
};
|
};
|
||||||
(code, axum::Json::from(self)).into_response()
|
(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::NotFound => (StatusCode::NOT_FOUND, axum::Json::from(self)).into_response(),
|
||||||
Self::Unauthorized => {
|
Self::Unauthorized => {
|
||||||
(StatusCode::UNAUTHORIZED, axum::Json::from(self)).into_response()
|
(StatusCode::UNAUTHORIZED, axum::Json::from(self)).into_response()
|
||||||
@ -87,6 +99,10 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type CategoryResponse = Response<String>;
|
||||||
|
|
||||||
|
pub type EmptyResponse = Response<()>;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct UserData {
|
pub struct UserData {
|
||||||
pub user_id: String,
|
pub user_id: String,
|
||||||
@ -125,6 +141,8 @@ impl From<Option<Vec<(String, i32)>>> for PlanDataResponse {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub type PlanHistoryResponse = Response<BTreeMap<chrono::NaiveDate, Vec<(String, i32)>>>;
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub struct InventoryData {
|
pub struct InventoryData {
|
||||||
pub filtered_ingredients: Vec<IngredientKey>,
|
pub filtered_ingredients: Vec<IngredientKey>,
|
||||||
|
@ -23,7 +23,6 @@ use axum::{
|
|||||||
response::{IntoResponse, Redirect, Response},
|
response::{IntoResponse, Redirect, Response},
|
||||||
routing::{get, Router},
|
routing::{get, Router},
|
||||||
};
|
};
|
||||||
use chrono::NaiveDate;
|
|
||||||
use mime_guess;
|
use mime_guess;
|
||||||
use recipes::{IngredientKey, RecipeEntry};
|
use recipes::{IngredientKey, RecipeEntry};
|
||||||
use rust_embed::RustEmbed;
|
use rust_embed::RustEmbed;
|
||||||
@ -146,7 +145,7 @@ async fn api_save_categories(
|
|||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
Json(categories): Json<String>,
|
Json(categories): Json<String>,
|
||||||
) -> api::Response<String> {
|
) -> api::CategoryResponse {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
if let Err(e) = app_store
|
if let Err(e) = app_store
|
||||||
@ -158,9 +157,9 @@ async fn api_save_categories(
|
|||||||
format!("{:?}", e),
|
format!("{:?}", e),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
api::Response::success("Successfully saved categories".into())
|
api::CategoryResponse::success("Successfully saved categories".into())
|
||||||
} else {
|
} else {
|
||||||
api::Response::Unauthorized
|
api::CategoryResponse::Unauthorized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,7 +167,7 @@ async fn api_save_recipes(
|
|||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
Json(recipes): Json<Vec<RecipeEntry>>,
|
Json(recipes): Json<Vec<RecipeEntry>>,
|
||||||
) -> api::Response<()> {
|
) -> api::EmptyResponse {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
let result = app_store
|
let result = app_store
|
||||||
@ -176,7 +175,7 @@ async fn api_save_recipes(
|
|||||||
.await;
|
.await;
|
||||||
result.map_err(|e| format!("Error: {:?}", e)).into()
|
result.map_err(|e| format!("Error: {:?}", e)).into()
|
||||||
} else {
|
} else {
|
||||||
api::Response::Unauthorized
|
api::EmptyResponse::Unauthorized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,7 +199,7 @@ async fn api_plan_since(
|
|||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
Path(date): Path<chrono::NaiveDate>,
|
Path(date): Path<chrono::NaiveDate>,
|
||||||
) -> api::Response<BTreeMap<NaiveDate, Vec<(String, i32)>>> {
|
) -> api::PlanHistoryResponse {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
app_store
|
app_store
|
||||||
@ -209,7 +208,7 @@ async fn api_plan_since(
|
|||||||
.map_err(|e| format!("Error: {:?}", e))
|
.map_err(|e| format!("Error: {:?}", e))
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
api::Response::Unauthorized
|
api::PlanHistoryResponse::Unauthorized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -217,7 +216,7 @@ async fn api_save_plan(
|
|||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
Json(meal_plan): Json<Vec<(String, i32)>>,
|
Json(meal_plan): Json<Vec<(String, i32)>>,
|
||||||
) -> api::Response<()> {
|
) -> api::EmptyResponse {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
app_store
|
app_store
|
||||||
@ -226,7 +225,7 @@ async fn api_save_plan(
|
|||||||
.map_err(|e| format!("{:?}", e))
|
.map_err(|e| format!("{:?}", e))
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
api::Response::Unauthorized
|
api::EmptyResponse::Unauthorized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -273,7 +272,7 @@ async fn save_inventory_data(
|
|||||||
filtered_ingredients: BTreeSet<IngredientKey>,
|
filtered_ingredients: BTreeSet<IngredientKey>,
|
||||||
modified_amts: BTreeMap<IngredientKey, String>,
|
modified_amts: BTreeMap<IngredientKey, String>,
|
||||||
extra_items: Vec<(String, String)>,
|
extra_items: Vec<(String, String)>,
|
||||||
) -> api::Response<()> {
|
) -> api::EmptyResponse {
|
||||||
app_store
|
app_store
|
||||||
.save_inventory_data(id, filtered_ingredients, modified_amts, extra_items)
|
.save_inventory_data(id, filtered_ingredients, modified_amts, extra_items)
|
||||||
.await
|
.await
|
||||||
@ -289,7 +288,7 @@ async fn api_save_inventory_v2(
|
|||||||
Vec<(IngredientKey, String)>,
|
Vec<(IngredientKey, String)>,
|
||||||
Vec<(String, String)>,
|
Vec<(String, String)>,
|
||||||
)>,
|
)>,
|
||||||
) -> api::Response<()> {
|
) -> api::EmptyResponse {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
let filtered_ingredients = filtered_ingredients.into_iter().collect();
|
let filtered_ingredients = filtered_ingredients.into_iter().collect();
|
||||||
@ -304,7 +303,7 @@ async fn api_save_inventory_v2(
|
|||||||
.await
|
.await
|
||||||
.into()
|
.into()
|
||||||
} else {
|
} else {
|
||||||
api::Response::Unauthorized
|
api::EmptyResponse::Unauthorized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -315,7 +314,7 @@ async fn api_save_inventory(
|
|||||||
Vec<IngredientKey>,
|
Vec<IngredientKey>,
|
||||||
Vec<(IngredientKey, String)>,
|
Vec<(IngredientKey, String)>,
|
||||||
)>,
|
)>,
|
||||||
) -> api::Response<()> {
|
) -> api::EmptyResponse {
|
||||||
use storage::{UserId, UserIdFromSession::FoundUserId};
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
if let FoundUserId(UserId(id)) = session {
|
if let FoundUserId(UserId(id)) = session {
|
||||||
let filtered_ingredients = filtered_ingredients.into_iter().collect();
|
let filtered_ingredients = filtered_ingredients.into_iter().collect();
|
||||||
|
@ -14,6 +14,7 @@ crate-type = ["cdylib", "rlib"]
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
recipes = { path = "../recipes" }
|
recipes = { path = "../recipes" }
|
||||||
|
client-api = { path = "../api", package="api", features = ["browser"] }
|
||||||
# This makes debugging panics more tractable.
|
# This makes debugging panics more tractable.
|
||||||
console_error_panic_hook = "0.1.7"
|
console_error_panic_hook = "0.1.7"
|
||||||
serde_json = "1.0.79"
|
serde_json = "1.0.79"
|
||||||
|
@ -18,6 +18,7 @@ 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 client_api::*;
|
||||||
use recipes::{parse, IngredientKey, Recipe, RecipeEntry};
|
use recipes::{parse, IngredientKey, Recipe, RecipeEntry};
|
||||||
use wasm_bindgen::JsValue;
|
use wasm_bindgen::JsValue;
|
||||||
|
|
||||||
@ -216,7 +217,7 @@ impl HttpStore {
|
|||||||
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!");
|
||||||
let resp: String = resp.json().await?;
|
let resp = resp.json::<CategoryResponse>().await?.as_success().unwrap();
|
||||||
storage.set("categories", &resp)?;
|
storage.set("categories", &resp)?;
|
||||||
Ok(Some(resp))
|
Ok(Some(resp))
|
||||||
}
|
}
|
||||||
@ -250,8 +251,11 @@ impl HttpStore {
|
|||||||
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!");
|
||||||
let entries: Option<Vec<RecipeEntry>> =
|
let entries = resp
|
||||||
resp.json().await.map_err(|e| format!("{}", e))?;
|
.json::<RecipeEntryResponse>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("{}", e))?
|
||||||
|
.as_success();
|
||||||
if let Some(ref entries) = entries {
|
if let Some(ref entries) = entries {
|
||||||
for r in entries.iter() {
|
for r in entries.iter() {
|
||||||
storage.set(
|
storage.set(
|
||||||
@ -292,7 +296,12 @@ impl HttpStore {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
debug!("We got a valid response back!");
|
debug!("We got a valid response back!");
|
||||||
let entry: Option<RecipeEntry> = resp.json().await.map_err(|e| format!("{}", e))?;
|
let entry = resp
|
||||||
|
.json::<Response<Option<RecipeEntry>>>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("{}", e))?
|
||||||
|
.as_success()
|
||||||
|
.unwrap();
|
||||||
if let Some(ref entry) = entry {
|
if let Some(ref entry) = entry {
|
||||||
let serialized: String = to_string(entry).map_err(|e| format!("{}", e))?;
|
let serialized: String = to_string(entry).map_err(|e| format!("{}", e))?;
|
||||||
storage.set(&recipe_key(entry.recipe_id()), &serialized)?
|
storage.set(&recipe_key(entry.recipe_id()), &serialized)?
|
||||||
@ -399,8 +408,11 @@ impl HttpStore {
|
|||||||
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");
|
||||||
let plan: Option<Vec<(String, i32)>> =
|
let plan = resp
|
||||||
resp.json().await.map_err(|e| format!("{}", e))?;
|
.json::<PlanDataResponse>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("{}", e))?
|
||||||
|
.as_success();
|
||||||
if let Some(ref entry) = plan {
|
if let Some(ref entry) = plan {
|
||||||
let serialized: String = to_string(entry).map_err(|e| format!("{}", e))?;
|
let serialized: String = to_string(entry).map_err(|e| format!("{}", e))?;
|
||||||
storage.set("plan", &serialized)?
|
storage.set("plan", &serialized)?
|
||||||
@ -448,11 +460,16 @@ impl HttpStore {
|
|||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
debug!("We got a valid response back");
|
debug!("We got a valid response back");
|
||||||
let (filtered_ingredients, modified_amts, extra_items): (
|
let InventoryData {
|
||||||
Vec<IngredientKey>,
|
filtered_ingredients,
|
||||||
Vec<(IngredientKey, String)>,
|
modified_amts,
|
||||||
Vec<(String, String)>,
|
extra_items,
|
||||||
) = resp.json().await.map_err(|e| format!("{}", e))?;
|
} = resp
|
||||||
|
.json::<InventoryResponse>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("{}", e))?
|
||||||
|
.as_success()
|
||||||
|
.unwrap();
|
||||||
let _ = storage.set(
|
let _ = storage.set(
|
||||||
"inventory",
|
"inventory",
|
||||||
&to_string(&(&filtered_ingredients, &modified_amts))
|
&to_string(&(&filtered_ingredients, &modified_amts))
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
// 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 base64;
|
use base64;
|
||||||
|
use client_api::AccountResponse;
|
||||||
use reqwasm::http;
|
use reqwasm::http;
|
||||||
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
use sycamore::{futures::spawn_local_scoped, prelude::*};
|
||||||
use tracing::{debug, error, info};
|
use tracing::{debug, error, info};
|
||||||
@ -20,7 +21,7 @@ fn token68(user: String, pass: String) -> String {
|
|||||||
base64::encode(format!("{}:{}", user, pass))
|
base64::encode(format!("{}:{}", user, pass))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn authenticate(user: String, pass: String) -> bool {
|
async fn authenticate(user: String, pass: String) -> Option<AccountResponse> {
|
||||||
debug!(
|
debug!(
|
||||||
username = user,
|
username = user,
|
||||||
password = pass,
|
password = pass,
|
||||||
@ -35,13 +36,16 @@ async fn authenticate(user: String, pass: String) -> bool {
|
|||||||
.await;
|
.await;
|
||||||
if let Ok(resp) = &result {
|
if let Ok(resp) = &result {
|
||||||
if resp.status() == 200 {
|
if resp.status() == 200 {
|
||||||
return true;
|
return resp
|
||||||
|
.json()
|
||||||
|
.await
|
||||||
|
.expect("Unparseable authentication response");
|
||||||
}
|
}
|
||||||
error!(status = resp.status(), "Login was unsuccessful")
|
error!(status = resp.status(), "Login was unsuccessful")
|
||||||
} else {
|
} else {
|
||||||
error!(err=?result.unwrap_err(), "Failed to send auth request");
|
error!(err=?result.unwrap_err(), "Failed to send auth request");
|
||||||
}
|
}
|
||||||
return false;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
@ -55,6 +59,7 @@ pub fn LoginForm<G: Html>(cx: Scope) -> View<G> {
|
|||||||
spawn_local_scoped(cx, async move {
|
spawn_local_scoped(cx, async move {
|
||||||
debug!("authenticating against ui");
|
debug!("authenticating against ui");
|
||||||
// TODO(jwall): Navigate to plan if the below is successful.
|
// TODO(jwall): Navigate to plan if the below is successful.
|
||||||
|
// TODO(jwall): Store account data in our app_state.
|
||||||
authenticate(username, password).await;
|
authenticate(username, password).await;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user