mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-22 19:40:14 -04:00
Handle messages for selecting a plan_date
This commit is contained in:
parent
3f0836dad2
commit
f808ca8585
@ -116,6 +116,16 @@
|
|||||||
},
|
},
|
||||||
"query": "insert into staples (user_id, content) values (?, ?)\n on conflict(user_id) do update set content = excluded.content"
|
"query": "insert into staples (user_id, content) values (?, ?)\n on conflict(user_id) do update set content = excluded.content"
|
||||||
},
|
},
|
||||||
|
"1b6fd91460bef61cf02f210404a4ca57b520c969d1f9613e7101ee6aa7a9962a": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "insert into modified_amts(user_id, name, form, measure_type, amt, plan_date)\n values (?, ?, ?, ?, ?, ?) on conflict (user_id, name, form, measure_type, plan_date) do update set amt=excluded.amt"
|
||||||
|
},
|
||||||
"2582522f8ca9f12eccc70a3b339d9030aee0f52e62d6674cfd3862de2a68a177": {
|
"2582522f8ca9f12eccc70a3b339d9030aee0f52e62d6674cfd3862de2a68a177": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@ -236,6 +246,40 @@
|
|||||||
},
|
},
|
||||||
"query": "insert into recipes (user_id, recipe_id, recipe_text) values (?, ?, ?)\n on conflict(user_id, recipe_id) do update set recipe_text=excluded.recipe_text"
|
"query": "insert into recipes (user_id, recipe_id, recipe_text) values (?, ?, ?)\n on conflict(user_id, recipe_id) do update set recipe_text=excluded.recipe_text"
|
||||||
},
|
},
|
||||||
|
"4237ff804f254c122a36a14135b90434c6576f48d3a83245503d702552ea9f30": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amt",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "select\n name,\n amt\nfrom extra_items\nwhere\n user_id = ?\n and plan_date = ?"
|
||||||
|
},
|
||||||
|
"5883c4a57def93cca45f8f9d81c8bba849547758217cd250e7ab28cc166ab42b": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "insert into filtered_ingredients(user_id, name, form, measure_type, plan_date)\n values (?, ?, ?, ?, ?) on conflict(user_id, name, form, measure_type, plan_date) DO NOTHING"
|
||||||
|
},
|
||||||
"5d743897fb0d8fd54c3708f1b1c6e416346201faa9e28823c1ba5a421472b1fa": {
|
"5d743897fb0d8fd54c3708f1b1c6e416346201faa9e28823c1ba5a421472b1fa": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@ -264,6 +308,42 @@
|
|||||||
},
|
},
|
||||||
"query": "select content from staples where user_id = ?"
|
"query": "select content from staples where user_id = ?"
|
||||||
},
|
},
|
||||||
|
"699ff0f0d4d4c6e26a21c1922a5b5249d89ed1677680a2276899a7f8b26344ee": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "form",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "measure_type",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "amt",
|
||||||
|
"ordinal": 3,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "select\n modified_amts.name,\n modified_amts.form,\n modified_amts.measure_type,\n modified_amts.amt\nfrom modified_amts\nwhere\n user_id = ?\n and plan_date = ?"
|
||||||
|
},
|
||||||
"6c43908d90f229b32ed8b1b076be9b452a995e1b42ba2554e947c515b031831a": {
|
"6c43908d90f229b32ed8b1b076be9b452a995e1b42ba2554e947c515b031831a": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [],
|
"columns": [],
|
||||||
@ -294,6 +374,36 @@
|
|||||||
},
|
},
|
||||||
"query": "delete from sessions where id = ?"
|
"query": "delete from sessions where id = ?"
|
||||||
},
|
},
|
||||||
|
"7695a0602395006f9b76ecd4d0cb5ecd5dee419b71b3b0b9ea4f47a83f3df41a": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [
|
||||||
|
{
|
||||||
|
"name": "name",
|
||||||
|
"ordinal": 0,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "form",
|
||||||
|
"ordinal": 1,
|
||||||
|
"type_info": "Text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "measure_type",
|
||||||
|
"ordinal": 2,
|
||||||
|
"type_info": "Text"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"nullable": [
|
||||||
|
false,
|
||||||
|
false,
|
||||||
|
false
|
||||||
|
],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 2
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "select\n filtered_ingredients.name,\n filtered_ingredients.form,\n filtered_ingredients.measure_type\nfrom filtered_ingredients\nwhere\n user_id = ?\n and plan_date = ?"
|
||||||
|
},
|
||||||
"7f4abc448b16e8b6b2bb74f8e810e245e81b38e1407085a20d28bfddfc06891f": {
|
"7f4abc448b16e8b6b2bb74f8e810e245e81b38e1407085a20d28bfddfc06891f": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
@ -414,6 +524,16 @@
|
|||||||
},
|
},
|
||||||
"query": "with max_date as (\n select user_id, max(date(plan_date)) as plan_date from plan_recipes group by user_id\n)\n\nselect plan_recipes.plan_date as \"plan_date: NaiveDate\", plan_recipes.recipe_id, plan_recipes.count\n from plan_recipes\n inner join max_date on plan_recipes.user_id = max_date.user_id\nwhere\n plan_recipes.user_id = ?\n and plan_recipes.plan_date = max_date.plan_date"
|
"query": "with max_date as (\n select user_id, max(date(plan_date)) as plan_date from plan_recipes group by user_id\n)\n\nselect plan_recipes.plan_date as \"plan_date: NaiveDate\", plan_recipes.recipe_id, plan_recipes.count\n from plan_recipes\n inner join max_date on plan_recipes.user_id = max_date.user_id\nwhere\n plan_recipes.user_id = ?\n and plan_recipes.plan_date = max_date.plan_date"
|
||||||
},
|
},
|
||||||
|
"ba07658eb11f9d6cfdb5dbee4496b2573f1e51f4b4d9ae760eca3b977649b5c7": {
|
||||||
|
"describe": {
|
||||||
|
"columns": [],
|
||||||
|
"nullable": [],
|
||||||
|
"parameters": {
|
||||||
|
"Right": 4
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"query": "insert into extra_items (user_id, name, amt, plan_date)\nvalues (?, ?, ?, ?)\non conflict (user_id, name, plan_date) do update set amt=excluded.amt"
|
||||||
|
},
|
||||||
"c988364f9f83f4fa8bd0e594bab432ee7c9ec47ca40f4d16e5e2a8763653f377": {
|
"c988364f9f83f4fa8bd0e594bab432ee7c9ec47ca40f4d16e5e2a8763653f377": {
|
||||||
"describe": {
|
"describe": {
|
||||||
"columns": [
|
"columns": [
|
||||||
|
@ -293,6 +293,26 @@ async fn api_inventory_v2(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn api_inventory_for_date(
|
||||||
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
|
session: storage::UserIdFromSession,
|
||||||
|
Path(date): Path<chrono::NaiveDate>,
|
||||||
|
) -> api::InventoryResponse {
|
||||||
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
|
if let FoundUserId(UserId(id)) = session {
|
||||||
|
app_store
|
||||||
|
.fetch_inventory_for_date(id, date)
|
||||||
|
.await
|
||||||
|
.map(|d| {
|
||||||
|
let data: api::InventoryData = d.into();
|
||||||
|
data
|
||||||
|
})
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
api::Response::Unauthorized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn api_inventory(
|
async fn api_inventory(
|
||||||
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
session: storage::UserIdFromSession,
|
session: storage::UserIdFromSession,
|
||||||
@ -309,6 +329,35 @@ async fn api_inventory(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn api_save_inventory_for_date(
|
||||||
|
Extension(app_store): Extension<Arc<storage::SqliteStore>>,
|
||||||
|
session: storage::UserIdFromSession,
|
||||||
|
Path(date): Path<NaiveDate>,
|
||||||
|
Json((filtered_ingredients, modified_amts, extra_items)): Json<(
|
||||||
|
Vec<IngredientKey>,
|
||||||
|
Vec<(IngredientKey, String)>,
|
||||||
|
Vec<(String, String)>,
|
||||||
|
)>,
|
||||||
|
) -> api::EmptyResponse {
|
||||||
|
use storage::{UserId, UserIdFromSession::FoundUserId};
|
||||||
|
if let FoundUserId(UserId(id)) = session {
|
||||||
|
let filtered_ingredients = filtered_ingredients.into_iter().collect();
|
||||||
|
let modified_amts = modified_amts.into_iter().collect();
|
||||||
|
app_store
|
||||||
|
.save_inventory_data_for_date(
|
||||||
|
id,
|
||||||
|
&date,
|
||||||
|
filtered_ingredients,
|
||||||
|
modified_amts,
|
||||||
|
extra_items,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.into()
|
||||||
|
} else {
|
||||||
|
api::EmptyResponse::Unauthorized
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async fn save_inventory_data(
|
async fn save_inventory_data(
|
||||||
app_store: Arc<storage::SqliteStore>,
|
app_store: Arc<storage::SqliteStore>,
|
||||||
id: String,
|
id: String,
|
||||||
@ -441,6 +490,10 @@ fn mk_v2_routes() -> Router {
|
|||||||
"/inventory",
|
"/inventory",
|
||||||
get(api_inventory_v2).post(api_save_inventory_v2),
|
get(api_inventory_v2).post(api_save_inventory_v2),
|
||||||
)
|
)
|
||||||
|
.route(
|
||||||
|
"/inventory/at/:date",
|
||||||
|
get(api_inventory_for_date).post(api_save_inventory_for_date),
|
||||||
|
)
|
||||||
// TODO(jwall): This is now deprecated but will still work
|
// TODO(jwall): This is now deprecated but will still work
|
||||||
.route("/categories", get(api_categories).post(api_save_categories))
|
.route("/categories", get(api_categories).post(api_save_categories))
|
||||||
.route(
|
.route(
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
select
|
||||||
|
filtered_ingredients.name,
|
||||||
|
filtered_ingredients.form,
|
||||||
|
filtered_ingredients.measure_type
|
||||||
|
from filtered_ingredients
|
||||||
|
where
|
||||||
|
user_id = ?
|
||||||
|
and plan_date = ?
|
9
kitchen/src/web/storage/fetch_modified_amts_for_date.sql
Normal file
9
kitchen/src/web/storage/fetch_modified_amts_for_date.sql
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
select
|
||||||
|
modified_amts.name,
|
||||||
|
modified_amts.form,
|
||||||
|
modified_amts.measure_type,
|
||||||
|
modified_amts.amt
|
||||||
|
from modified_amts
|
||||||
|
where
|
||||||
|
user_id = ?
|
||||||
|
and plan_date = ?
|
@ -145,6 +145,16 @@ pub trait APIStore {
|
|||||||
date: NaiveDate,
|
date: NaiveDate,
|
||||||
) -> Result<()>;
|
) -> Result<()>;
|
||||||
|
|
||||||
|
async fn fetch_inventory_for_date<S: AsRef<str> + Send>(
|
||||||
|
&self,
|
||||||
|
user_id: S,
|
||||||
|
date: NaiveDate,
|
||||||
|
) -> Result<(
|
||||||
|
Vec<IngredientKey>,
|
||||||
|
Vec<(IngredientKey, String)>,
|
||||||
|
Vec<(String, String)>,
|
||||||
|
)>;
|
||||||
|
|
||||||
async fn fetch_latest_inventory_data<S: AsRef<str> + Send>(
|
async fn fetch_latest_inventory_data<S: AsRef<str> + Send>(
|
||||||
&self,
|
&self,
|
||||||
user_id: S,
|
user_id: S,
|
||||||
@ -154,6 +164,15 @@ pub trait APIStore {
|
|||||||
Vec<(String, String)>,
|
Vec<(String, String)>,
|
||||||
)>;
|
)>;
|
||||||
|
|
||||||
|
async fn save_inventory_data_for_date<S: AsRef<str> + Send>(
|
||||||
|
&self,
|
||||||
|
user_id: S,
|
||||||
|
date: &NaiveDate,
|
||||||
|
filtered_ingredients: BTreeSet<IngredientKey>,
|
||||||
|
modified_amts: BTreeMap<IngredientKey, String>,
|
||||||
|
extra_items: Vec<(String, String)>,
|
||||||
|
) -> Result<()>;
|
||||||
|
|
||||||
async fn save_inventory_data<S: AsRef<str> + Send>(
|
async fn save_inventory_data<S: AsRef<str> + Send>(
|
||||||
&self,
|
&self,
|
||||||
user_id: S,
|
user_id: S,
|
||||||
@ -652,7 +671,89 @@ impl APIStore for SqliteStore {
|
|||||||
Ok(Some(result))
|
Ok(Some(result))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(jwall): Do we need fetch for date variants of this.
|
async fn fetch_inventory_for_date<S: AsRef<str> + Send>(
|
||||||
|
&self,
|
||||||
|
user_id: S,
|
||||||
|
date: NaiveDate,
|
||||||
|
) -> Result<(
|
||||||
|
Vec<IngredientKey>,
|
||||||
|
Vec<(IngredientKey, String)>,
|
||||||
|
Vec<(String, String)>,
|
||||||
|
)> {
|
||||||
|
let user_id = user_id.as_ref();
|
||||||
|
struct FilteredIngredientRow {
|
||||||
|
name: String,
|
||||||
|
form: String,
|
||||||
|
measure_type: String,
|
||||||
|
}
|
||||||
|
let filtered_ingredient_rows: Vec<FilteredIngredientRow> = sqlx::query_file_as!(
|
||||||
|
FilteredIngredientRow,
|
||||||
|
"src/web/storage/fetch_filtered_ingredients_for_date.sql",
|
||||||
|
user_id,
|
||||||
|
date,
|
||||||
|
)
|
||||||
|
.fetch_all(self.pool.as_ref())
|
||||||
|
.await?;
|
||||||
|
let mut filtered_ingredients = Vec::new();
|
||||||
|
for row in filtered_ingredient_rows {
|
||||||
|
filtered_ingredients.push(IngredientKey::new(
|
||||||
|
row.name,
|
||||||
|
if row.form.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(row.form)
|
||||||
|
},
|
||||||
|
row.measure_type,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
struct ModifiedAmtRow {
|
||||||
|
name: String,
|
||||||
|
form: String,
|
||||||
|
measure_type: String,
|
||||||
|
amt: String,
|
||||||
|
}
|
||||||
|
let modified_amt_rows = sqlx::query_file_as!(
|
||||||
|
ModifiedAmtRow,
|
||||||
|
"src/web/storage/fetch_modified_amts_for_date.sql",
|
||||||
|
user_id,
|
||||||
|
date,
|
||||||
|
)
|
||||||
|
.fetch_all(self.pool.as_ref())
|
||||||
|
.await?;
|
||||||
|
let mut modified_amts = Vec::new();
|
||||||
|
for row in modified_amt_rows {
|
||||||
|
modified_amts.push((
|
||||||
|
IngredientKey::new(
|
||||||
|
row.name,
|
||||||
|
if row.form.is_empty() {
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(row.form)
|
||||||
|
},
|
||||||
|
row.measure_type,
|
||||||
|
),
|
||||||
|
row.amt,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
pub struct ExtraItemRow {
|
||||||
|
name: String,
|
||||||
|
amt: String,
|
||||||
|
}
|
||||||
|
let extra_items_rows = sqlx::query_file_as!(
|
||||||
|
ExtraItemRow,
|
||||||
|
"src/web/storage/fetch_extra_items_for_date.sql",
|
||||||
|
user_id,
|
||||||
|
date,
|
||||||
|
)
|
||||||
|
.fetch_all(self.pool.as_ref())
|
||||||
|
.await?;
|
||||||
|
let mut extra_items = Vec::new();
|
||||||
|
for row in extra_items_rows {
|
||||||
|
extra_items.push((row.name, row.amt));
|
||||||
|
}
|
||||||
|
Ok((filtered_ingredients, modified_amts, extra_items))
|
||||||
|
}
|
||||||
|
|
||||||
async fn fetch_latest_inventory_data<S: AsRef<str> + Send>(
|
async fn fetch_latest_inventory_data<S: AsRef<str> + Send>(
|
||||||
&self,
|
&self,
|
||||||
user_id: S,
|
user_id: S,
|
||||||
@ -732,6 +833,66 @@ impl APIStore for SqliteStore {
|
|||||||
Ok((filtered_ingredients, modified_amts, extra_items))
|
Ok((filtered_ingredients, modified_amts, extra_items))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn save_inventory_data_for_date<S: AsRef<str> + Send>(
|
||||||
|
&self,
|
||||||
|
user_id: S,
|
||||||
|
date: &NaiveDate,
|
||||||
|
filtered_ingredients: BTreeSet<IngredientKey>,
|
||||||
|
modified_amts: BTreeMap<IngredientKey, String>,
|
||||||
|
extra_items: Vec<(String, String)>,
|
||||||
|
) -> Result<()> {
|
||||||
|
let user_id = user_id.as_ref();
|
||||||
|
let mut transaction = self.pool.as_ref().begin().await?;
|
||||||
|
// store the filtered_ingredients
|
||||||
|
for key in filtered_ingredients {
|
||||||
|
let name = key.name();
|
||||||
|
let form = key.form();
|
||||||
|
let measure_type = key.measure_type();
|
||||||
|
sqlx::query_file!(
|
||||||
|
"src/web/storage/save_filtered_ingredients_for_date.sql",
|
||||||
|
user_id,
|
||||||
|
name,
|
||||||
|
form,
|
||||||
|
measure_type,
|
||||||
|
date,
|
||||||
|
)
|
||||||
|
.execute(&mut transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
// store the modified amts
|
||||||
|
for (key, amt) in modified_amts {
|
||||||
|
let name = key.name();
|
||||||
|
let form = key.form();
|
||||||
|
let measure_type = key.measure_type();
|
||||||
|
let amt = &amt;
|
||||||
|
sqlx::query_file!(
|
||||||
|
"src/web/storage/save_modified_amts_for_date.sql",
|
||||||
|
user_id,
|
||||||
|
name,
|
||||||
|
form,
|
||||||
|
measure_type,
|
||||||
|
amt,
|
||||||
|
date,
|
||||||
|
)
|
||||||
|
.execute(&mut transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
// Store the extra items
|
||||||
|
for (name, amt) in extra_items {
|
||||||
|
sqlx::query_file!(
|
||||||
|
"src/web/storage/store_extra_items_for_date.sql",
|
||||||
|
user_id,
|
||||||
|
name,
|
||||||
|
amt,
|
||||||
|
date
|
||||||
|
)
|
||||||
|
.execute(&mut transaction)
|
||||||
|
.await?;
|
||||||
|
}
|
||||||
|
transaction.commit().await?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
async fn save_inventory_data<S: AsRef<str> + Send>(
|
async fn save_inventory_data<S: AsRef<str> + Send>(
|
||||||
&self,
|
&self,
|
||||||
user_id: S,
|
user_id: S,
|
||||||
|
@ -0,0 +1,2 @@
|
|||||||
|
insert into filtered_ingredients(user_id, name, form, measure_type, plan_date)
|
||||||
|
values (?, ?, ?, ?, ?) on conflict(user_id, name, form, measure_type, plan_date) DO NOTHING
|
2
kitchen/src/web/storage/save_modified_amts_for_date.sql
Normal file
2
kitchen/src/web/storage/save_modified_amts_for_date.sql
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
insert into modified_amts(user_id, name, form, measure_type, amt, plan_date)
|
||||||
|
values (?, ?, ?, ?, ?, ?) on conflict (user_id, name, form, measure_type, plan_date) do update set amt=excluded.amt
|
3
kitchen/src/web/storage/store_extra_items_for_date.sql
Normal file
3
kitchen/src/web/storage/store_extra_items_for_date.sql
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
insert into extra_items (user_id, name, amt, plan_date)
|
||||||
|
values (?, ?, ?, ?)
|
||||||
|
on conflict (user_id, name, plan_date) do update set amt=excluded.amt
|
@ -257,6 +257,27 @@ impl LocalStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn set_plan_date(&self, date: &NaiveDate) {
|
||||||
|
self.store
|
||||||
|
.set(
|
||||||
|
"plan:date",
|
||||||
|
&to_string(&date).expect("Failed to serialize plan:date"),
|
||||||
|
)
|
||||||
|
.expect("Failed to store plan:date");
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_plan_date(&self) -> Option<NaiveDate> {
|
||||||
|
if let Some(date) = self
|
||||||
|
.store
|
||||||
|
.get("plan:date")
|
||||||
|
.expect("Failed to get plan date")
|
||||||
|
{
|
||||||
|
Some(from_str(&date).expect("Failed to deserialize plan_date"))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn get_inventory_data(
|
pub fn get_inventory_data(
|
||||||
&self,
|
&self,
|
||||||
) -> Option<(
|
) -> Option<(
|
||||||
@ -606,7 +627,7 @@ impl HttpStore {
|
|||||||
|
|
||||||
pub async fn fetch_plan_for_date(
|
pub async fn fetch_plan_for_date(
|
||||||
&self,
|
&self,
|
||||||
date: NaiveDate,
|
date: &NaiveDate,
|
||||||
) -> Result<Option<Vec<(String, i32)>>, Error> {
|
) -> Result<Option<Vec<(String, i32)>>, Error> {
|
||||||
let mut path = self.v2_path();
|
let mut path = self.v2_path();
|
||||||
path.push_str("/plan");
|
path.push_str("/plan");
|
||||||
@ -643,6 +664,48 @@ impl HttpStore {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn fetch_inventory_for_date(
|
||||||
|
&self,
|
||||||
|
date: &NaiveDate,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
BTreeSet<IngredientKey>,
|
||||||
|
BTreeMap<IngredientKey, String>,
|
||||||
|
Vec<(String, String)>,
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
> {
|
||||||
|
let mut path = self.v2_path();
|
||||||
|
path.push_str("/inventory");
|
||||||
|
path.push_str("/at");
|
||||||
|
path.push_str(&format!("/{}", date));
|
||||||
|
let resp = reqwasm::http::Request::get(&path).send().await?;
|
||||||
|
if resp.status() != 200 {
|
||||||
|
let err = Err(format!("Status: {}", resp.status()).into());
|
||||||
|
Ok(match self.local_store.get_inventory_data() {
|
||||||
|
Some(val) => val,
|
||||||
|
None => return err,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
debug!("We got a valid response back");
|
||||||
|
let InventoryData {
|
||||||
|
filtered_ingredients,
|
||||||
|
modified_amts,
|
||||||
|
extra_items,
|
||||||
|
} = resp
|
||||||
|
.json::<InventoryResponse>()
|
||||||
|
.await
|
||||||
|
.map_err(|e| format!("{}", e))?
|
||||||
|
.as_success()
|
||||||
|
.unwrap();
|
||||||
|
Ok((
|
||||||
|
filtered_ingredients.into_iter().collect(),
|
||||||
|
modified_amts.into_iter().collect(),
|
||||||
|
extra_items,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn fetch_inventory_data(
|
pub async fn fetch_inventory_data(
|
||||||
&self,
|
&self,
|
||||||
) -> Result<
|
) -> Result<
|
||||||
|
@ -38,6 +38,7 @@ pub struct AppState {
|
|||||||
pub modified_amts: BTreeMap<IngredientKey, String>,
|
pub modified_amts: BTreeMap<IngredientKey, String>,
|
||||||
pub auth: Option<UserData>,
|
pub auth: Option<UserData>,
|
||||||
pub plan_dates: BTreeSet<NaiveDate>,
|
pub plan_dates: BTreeSet<NaiveDate>,
|
||||||
|
pub selected_plan_date: Option<NaiveDate>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl AppState {
|
impl AppState {
|
||||||
@ -52,6 +53,7 @@ impl AppState {
|
|||||||
modified_amts: BTreeMap::new(),
|
modified_amts: BTreeMap::new(),
|
||||||
auth: None,
|
auth: None,
|
||||||
plan_dates: BTreeSet::new(),
|
plan_dates: BTreeSet::new(),
|
||||||
|
selected_plan_date: None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -73,6 +75,7 @@ pub enum Message {
|
|||||||
SaveState(Option<Box<dyn FnOnce()>>),
|
SaveState(Option<Box<dyn FnOnce()>>),
|
||||||
LoadState(Option<Box<dyn FnOnce()>>),
|
LoadState(Option<Box<dyn FnOnce()>>),
|
||||||
UpdateStaples(String, Option<Box<dyn FnOnce()>>),
|
UpdateStaples(String, Option<Box<dyn FnOnce()>>),
|
||||||
|
SelectPlanDate(NaiveDate),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for Message {
|
impl Debug for Message {
|
||||||
@ -113,6 +116,7 @@ impl Debug for Message {
|
|||||||
Self::SaveState(_) => write!(f, "SaveState"),
|
Self::SaveState(_) => write!(f, "SaveState"),
|
||||||
Self::LoadState(_) => write!(f, "LoadState"),
|
Self::LoadState(_) => write!(f, "LoadState"),
|
||||||
Self::UpdateStaples(arg, _) => f.debug_tuple("UpdateStaples").field(arg).finish(),
|
Self::UpdateStaples(arg, _) => f.debug_tuple("UpdateStaples").field(arg).finish(),
|
||||||
|
Self::SelectPlanDate(arg) => f.debug_tuple("SelectPlanDate").field(arg).finish(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -189,8 +193,15 @@ impl StateMachine {
|
|||||||
debug!(?plan_dates, "meal plan list");
|
debug!(?plan_dates, "meal plan list");
|
||||||
state.plan_dates = BTreeSet::from_iter(plan_dates.drain(0..));
|
state.plan_dates = BTreeSet::from_iter(plan_dates.drain(0..));
|
||||||
}
|
}
|
||||||
|
|
||||||
info!("Synchronizing meal plan");
|
info!("Synchronizing meal plan");
|
||||||
let plan = store.fetch_plan().await?;
|
let plan = if let Some(cached_plan_date) = local_store.get_plan_date() {
|
||||||
|
let plan = store.fetch_plan_for_date(&cached_plan_date).await?;
|
||||||
|
state.selected_plan_date = Some(cached_plan_date);
|
||||||
|
plan
|
||||||
|
} else {
|
||||||
|
store.fetch_plan().await?
|
||||||
|
};
|
||||||
if let Some(plan) = plan {
|
if let Some(plan) = plan {
|
||||||
// set the counts.
|
// set the counts.
|
||||||
let mut plan_map = BTreeMap::new();
|
let mut plan_map = BTreeMap::new();
|
||||||
@ -242,8 +253,13 @@ impl StateMachine {
|
|||||||
error!("{:?}", e);
|
error!("{:?}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let inventory_data = if let Some(cached_plan_date) = &state.selected_plan_date {
|
||||||
|
store.fetch_inventory_for_date(cached_plan_date).await
|
||||||
|
} else {
|
||||||
|
store.fetch_inventory_data().await
|
||||||
|
};
|
||||||
info!("Synchronizing inventory data");
|
info!("Synchronizing inventory data");
|
||||||
match store.fetch_inventory_data().await {
|
match inventory_data {
|
||||||
Ok((filtered_ingredients, modified_amts, extra_items)) => {
|
Ok((filtered_ingredients, modified_amts, extra_items)) => {
|
||||||
local_store.set_inventory_data((
|
local_store.set_inventory_data((
|
||||||
&filtered_ingredients,
|
&filtered_ingredients,
|
||||||
@ -436,6 +452,36 @@ impl MessageMapper<Message, AppState> for StateMachine {
|
|||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
Message::SelectPlanDate(date) => {
|
||||||
|
let store = self.store.clone();
|
||||||
|
let local_store = self.local_store.clone();
|
||||||
|
spawn_local_scoped(cx, async move {
|
||||||
|
if let Some(mut plan) = store
|
||||||
|
.fetch_plan_for_date(&date)
|
||||||
|
.await
|
||||||
|
.expect("Failed to fetch plan for date")
|
||||||
|
{
|
||||||
|
// Note(jwall): This is a little unusual but because this
|
||||||
|
// is async code we can't rely on the set below.
|
||||||
|
original_copy.recipe_counts =
|
||||||
|
BTreeMap::from_iter(plan.drain(0..).map(|(k, v)| (k, v as usize)));
|
||||||
|
}
|
||||||
|
let (filtered, modified, extras) = store
|
||||||
|
.fetch_inventory_for_date(&date)
|
||||||
|
.await
|
||||||
|
.expect("Failed to fetch inventory_data for date");
|
||||||
|
original_copy.modified_amts = modified;
|
||||||
|
original_copy.filtered_ingredients = filtered;
|
||||||
|
original_copy.extras = extras;
|
||||||
|
local_store.set_plan_date(&date);
|
||||||
|
|
||||||
|
original.set(original_copy);
|
||||||
|
});
|
||||||
|
// NOTE(jwall): Because we do our signal set above in the async block
|
||||||
|
// we have to return here to avoid lifetime issues and double setting
|
||||||
|
// the original signal.
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
original.set(original_copy);
|
original.set(original_copy);
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,12 @@ use sycamore::prelude::*;
|
|||||||
pub mod cook;
|
pub mod cook;
|
||||||
pub mod inventory;
|
pub mod inventory;
|
||||||
pub mod plan;
|
pub mod plan;
|
||||||
|
pub mod select;
|
||||||
|
|
||||||
pub use cook::*;
|
pub use cook::*;
|
||||||
pub use inventory::*;
|
pub use inventory::*;
|
||||||
pub use plan::*;
|
pub use plan::*;
|
||||||
|
pub use select::*;
|
||||||
|
|
||||||
#[derive(Props)]
|
#[derive(Props)]
|
||||||
pub struct PageState<'a, G: Html> {
|
pub struct PageState<'a, G: Html> {
|
||||||
@ -33,6 +35,7 @@ pub fn PlanningPage<'a, G: Html>(cx: Scope<'a>, state: PageState<'a, G>) -> View
|
|||||||
let PageState { children, selected } = state;
|
let PageState { children, selected } = state;
|
||||||
let children = children.call(cx);
|
let children = children.call(cx);
|
||||||
let planning_tabs: Vec<(String, &'static str)> = vec![
|
let planning_tabs: Vec<(String, &'static str)> = vec![
|
||||||
|
("/ui/planning/select".to_owned(), "Select"),
|
||||||
("/ui/planning/plan".to_owned(), "Plan"),
|
("/ui/planning/plan".to_owned(), "Plan"),
|
||||||
("/ui/planning/inventory".to_owned(), "Inventory"),
|
("/ui/planning/inventory".to_owned(), "Inventory"),
|
||||||
("/ui/planning/cook".to_owned(), "Cook"),
|
("/ui/planning/cook".to_owned(), "Cook"),
|
||||||
|
26
web/src/pages/planning/select.rs
Normal file
26
web/src/pages/planning/select.rs
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
// Copyright 2023 Jeremy Wall (Jeremy@marzhilsltudios.com)
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// 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 super::PlanningPage;
|
||||||
|
use crate::app_state::StateHandler;
|
||||||
|
|
||||||
|
use sycamore::prelude::*;
|
||||||
|
|
||||||
|
#[component]
|
||||||
|
pub fn SelectPage<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
||||||
|
view! {cx,
|
||||||
|
PlanningPage(
|
||||||
|
selected=Some("Select".to_owned()),
|
||||||
|
) { "TODO(jwall)" }
|
||||||
|
}
|
||||||
|
}
|
@ -62,6 +62,8 @@ pub enum ManageRoutes {
|
|||||||
|
|
||||||
#[derive(Route, Debug)]
|
#[derive(Route, Debug)]
|
||||||
pub enum PlanningRoutes {
|
pub enum PlanningRoutes {
|
||||||
|
#[to("/select")]
|
||||||
|
Select,
|
||||||
#[to("/plan")]
|
#[to("/plan")]
|
||||||
Plan,
|
Plan,
|
||||||
#[to("/inventory")]
|
#[to("/inventory")]
|
||||||
@ -83,6 +85,9 @@ fn route_switch<'ctx, G: Html>(route: &Routes, cx: Scope<'ctx>, sh: StateHandler
|
|||||||
use ManageRoutes::*;
|
use ManageRoutes::*;
|
||||||
use PlanningRoutes::*;
|
use PlanningRoutes::*;
|
||||||
match route {
|
match route {
|
||||||
|
Routes::Planning(Select) => view! {cx,
|
||||||
|
SelectPage(sh)
|
||||||
|
},
|
||||||
Routes::Planning(Plan) => view! {cx,
|
Routes::Planning(Plan) => view! {cx,
|
||||||
PlanPage(sh)
|
PlanPage(sh)
|
||||||
},
|
},
|
||||||
|
Loading…
x
Reference in New Issue
Block a user