mirror of
https://github.com/zaphar/kitchen.git
synced 2025-07-21 19:29:49 -04:00
recipe categorization
This commit is contained in:
parent
3f1e79b001
commit
e02fcc82e1
@ -0,0 +1,2 @@
|
||||
-- Add down migration script here
|
||||
alter table recipes drop column category;
|
@ -0,0 +1,2 @@
|
||||
-- Add up migration script here
|
||||
alter table recipes add column category TEXT;
|
@ -1,5 +1,15 @@
|
||||
{
|
||||
"db": "SQLite",
|
||||
"05a9f963e3f18b8ceb787c33b6dbdac993f999ff32bb5155f2dff8dc18d840bf": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"parameters": {
|
||||
"Right": 4
|
||||
}
|
||||
},
|
||||
"query": "insert into recipes (user_id, recipe_id, recipe_text, category) values (?, ?, ?, ?)\n on conflict(user_id, recipe_id) do update set recipe_text=excluded.recipe_text, category=excluded.category"
|
||||
},
|
||||
"104f07472670436d3eee1733578bbf0c92dc4f965d3d13f9bf4bfbc92958c5b6": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@ -62,30 +72,6 @@
|
||||
},
|
||||
"query": "insert into filtered_ingredients(user_id, name, form, measure_type, plan_date)\n values (?, ?, ?, ?, date()) on conflict(user_id, name, form, measure_type, plan_date) DO NOTHING"
|
||||
},
|
||||
"196e289cbd65224293c4213552160a0cdf82f924ac597810fe05102e247b809d": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "recipe_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "recipe_text",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
}
|
||||
},
|
||||
"query": "select recipe_id, recipe_text from recipes where user_id = ? and recipe_id = ?"
|
||||
},
|
||||
"19832e3582c05ed49c676fde33cde64274379a83a8dd130f6eec96c1d7250909": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
@ -136,6 +122,36 @@
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"1cc4412dfc3d4acdf257e839b50d6c9abbb6e74e7af606fd12da20f0aedde3de": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "recipe_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "recipe_text",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "category",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
}
|
||||
},
|
||||
"query": "select recipe_id, recipe_text, category from recipes where user_id = ? and recipe_id = ?"
|
||||
},
|
||||
"23beb05e40cf011170182d4e98cdf1faa3d8df6e5956e471245e666f32e56962": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
@ -256,15 +272,35 @@
|
||||
},
|
||||
"query": "with latest_dates as (\n select user_id, max(date(plan_date)) as plan_date from plan_recipes\n where user_id = ?\n group by user_id\n)\n\nselect\n modified_amts.name,\n modified_amts.form,\n modified_amts.measure_type,\n modified_amts.amt\nfrom latest_dates\ninner join modified_amts on\n latest_dates.user_id = modified_amts.user_id\n and latest_dates.plan_date = modified_amts.plan_date"
|
||||
},
|
||||
"3fd4017569dca4fe73e97e0e2bd612027a8c1b17b0b10faabd6459f56ca1c0bb": {
|
||||
"40c589d8cb88d7ed723c8651833fe8541756ef0c57bf6296a4dfbda7d504dca8": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"nullable": [],
|
||||
"columns": [
|
||||
{
|
||||
"name": "recipe_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "recipe_text",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "category",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
true,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"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": "select recipe_id, recipe_text, category from recipes where user_id = ?"
|
||||
},
|
||||
"4237ff804f254c122a36a14135b90434c6576f48d3a83245503d702552ea9f30": {
|
||||
"describe": {
|
||||
@ -500,30 +536,6 @@
|
||||
},
|
||||
"query": "delete from plan_recipes where user_id = ? and plan_date = ?"
|
||||
},
|
||||
"95fbc362a2e17add05218a2dac431275b5cc55bd7ac8f4173ee10afefceafa3b": {
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "recipe_id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "recipe_text",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"nullable": [
|
||||
false,
|
||||
true
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
}
|
||||
},
|
||||
"query": "select recipe_id, recipe_text from recipes where user_id = ?"
|
||||
},
|
||||
"9ad4acd9b9d32c9f9f441276aa71a17674fe4d65698848044778bd4aef77d42d": {
|
||||
"describe": {
|
||||
"columns": [],
|
||||
|
@ -98,7 +98,7 @@ impl AsyncFileStore {
|
||||
let file_name = entry.file_name().to_string_lossy().to_string();
|
||||
debug!("adding recipe file {}", file_name);
|
||||
let recipe_contents = read_to_string(entry.path()).await?;
|
||||
entry_vec.push(RecipeEntry(file_name, recipe_contents));
|
||||
entry_vec.push(RecipeEntry(file_name, recipe_contents, None));
|
||||
} else {
|
||||
warn!(
|
||||
file = %entry.path().to_string_lossy(),
|
||||
@ -118,7 +118,11 @@ impl AsyncFileStore {
|
||||
if recipe_path.exists().await && recipe_path.is_file().await {
|
||||
debug!("Found recipe file {}", recipe_path.to_string_lossy());
|
||||
let recipe_contents = read_to_string(recipe_path).await?;
|
||||
return Ok(Some(RecipeEntry(id.as_ref().to_owned(), recipe_contents)));
|
||||
return Ok(Some(RecipeEntry(
|
||||
id.as_ref().to_owned(),
|
||||
recipe_contents,
|
||||
None,
|
||||
)));
|
||||
} else {
|
||||
return Ok(None);
|
||||
}
|
||||
|
@ -436,12 +436,13 @@ impl APIStore for SqliteStore {
|
||||
struct RecipeRow {
|
||||
pub recipe_id: String,
|
||||
pub recipe_text: Option<String>,
|
||||
pub category: Option<String>,
|
||||
}
|
||||
let id = id.as_ref();
|
||||
let user_id = user_id.as_ref();
|
||||
let entry = sqlx::query_as!(
|
||||
RecipeRow,
|
||||
"select recipe_id, recipe_text from recipes where user_id = ? and recipe_id = ?",
|
||||
"select recipe_id, recipe_text, category from recipes where user_id = ? and recipe_id = ?",
|
||||
user_id,
|
||||
id,
|
||||
)
|
||||
@ -452,6 +453,7 @@ impl APIStore for SqliteStore {
|
||||
RecipeEntry(
|
||||
row.recipe_id.clone(),
|
||||
row.recipe_text.clone().unwrap_or_else(|| String::new()),
|
||||
row.category.clone()
|
||||
)
|
||||
})
|
||||
.nth(0);
|
||||
@ -466,10 +468,11 @@ impl APIStore for SqliteStore {
|
||||
struct RecipeRow {
|
||||
pub recipe_id: String,
|
||||
pub recipe_text: Option<String>,
|
||||
pub category: Option<String>,
|
||||
}
|
||||
let rows = sqlx::query_as!(
|
||||
RecipeRow,
|
||||
"select recipe_id, recipe_text from recipes where user_id = ?",
|
||||
"select recipe_id, recipe_text, category from recipes where user_id = ?",
|
||||
user_id,
|
||||
)
|
||||
.fetch_all(self.pool.as_ref())
|
||||
@ -479,6 +482,7 @@ impl APIStore for SqliteStore {
|
||||
RecipeEntry(
|
||||
row.recipe_id.clone(),
|
||||
row.recipe_text.clone().unwrap_or_else(|| String::new()),
|
||||
row.category.clone(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
@ -493,12 +497,14 @@ impl APIStore for SqliteStore {
|
||||
for entry in recipes {
|
||||
let recipe_id = entry.recipe_id().to_owned();
|
||||
let recipe_text = entry.recipe_text().to_owned();
|
||||
let category = entry.category();
|
||||
sqlx::query!(
|
||||
"insert into recipes (user_id, recipe_id, recipe_text) values (?, ?, ?)
|
||||
on conflict(user_id, recipe_id) do update set recipe_text=excluded.recipe_text",
|
||||
"insert into recipes (user_id, recipe_id, recipe_text, category) values (?, ?, ?, ?)
|
||||
on conflict(user_id, recipe_id) do update set recipe_text=excluded.recipe_text, category=excluded.category",
|
||||
user_id,
|
||||
recipe_id,
|
||||
recipe_text,
|
||||
category,
|
||||
)
|
||||
.execute(self.pool.as_ref())
|
||||
.await?;
|
||||
|
@ -50,11 +50,11 @@ impl Mealplan {
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct RecipeEntry(pub String, pub String);
|
||||
pub struct RecipeEntry(pub String, pub String, pub Option<String>);
|
||||
|
||||
impl RecipeEntry {
|
||||
pub fn new<IS: Into<String>, TS: Into<String>>(recipe_id: IS, text: TS) -> Self {
|
||||
Self(recipe_id.into(), text.into())
|
||||
Self(recipe_id.into(), text.into(), None)
|
||||
}
|
||||
|
||||
pub fn set_recipe_id<S: Into<String>>(&mut self, id: S) {
|
||||
@ -72,6 +72,14 @@ impl RecipeEntry {
|
||||
pub fn recipe_text(&self) -> &str {
|
||||
self.1.as_str()
|
||||
}
|
||||
|
||||
pub fn set_category<S: Into<String>>(&mut self, cat: S) {
|
||||
self.2 = Some(cat.into());
|
||||
}
|
||||
|
||||
pub fn category(&self) -> Option<&String> {
|
||||
self.2.as_ref()
|
||||
}
|
||||
}
|
||||
|
||||
/// A Recipe with a title, description, and a series of steps.
|
||||
|
@ -31,10 +31,17 @@ Instructions here
|
||||
#[component]
|
||||
pub fn AddRecipe<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View<G> {
|
||||
let recipe_title = create_signal(cx, String::new());
|
||||
let category = create_signal(cx, String::new());
|
||||
let create_recipe_signal = create_signal(cx, ());
|
||||
let dirty = create_signal(cx, false);
|
||||
|
||||
let entry = create_memo(cx, || {
|
||||
let category = category.get().as_ref().to_owned();
|
||||
let category = if category.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(category)
|
||||
};
|
||||
RecipeEntry(
|
||||
recipe_title
|
||||
.get()
|
||||
@ -45,6 +52,7 @@ pub fn AddRecipe<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View
|
||||
STARTER_RECIPE
|
||||
.replace("TITLE_PLACEHOLDER", recipe_title.get().as_str())
|
||||
.replace("\r", ""),
|
||||
category,
|
||||
)
|
||||
});
|
||||
|
||||
@ -53,6 +61,9 @@ pub fn AddRecipe<'ctx, G: Html>(cx: Scope<'ctx>, sh: StateHandler<'ctx>) -> View
|
||||
input(bind:value=recipe_title, type="text", name="recipe_title", id="recipe_title", on:change=move |_| {
|
||||
dirty.set(true);
|
||||
})
|
||||
input(bind:value=category, type="text", name="recipe_title", id="recipe_title", on:change=move |_| {
|
||||
dirty.set(true);
|
||||
})
|
||||
button(on:click=move |_| {
|
||||
create_recipe_signal.trigger_subscribers();
|
||||
if !*dirty.get_untracked() {
|
||||
|
@ -72,20 +72,26 @@ pub fn Editor<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>)
|
||||
let id = create_memo(cx, || recipe.get().recipe_id().to_owned());
|
||||
let dirty = create_signal(cx, false);
|
||||
let ts = create_signal(cx, js_lib::get_ms_timestamp());
|
||||
let category = create_signal(cx, recipe.get().category().cloned().unwrap_or_default());
|
||||
|
||||
debug!("creating editor view");
|
||||
view! {cx,
|
||||
label(for="recipe_category") { "Category" }
|
||||
input(name="recipe_category", bind:value=category, on:change=move |_| dirty.set(true))
|
||||
div(class="grid") {
|
||||
textarea(bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
||||
dirty.set(true);
|
||||
check_recipe_parses(text.get_untracked().as_str(), error_text, aria_hint);
|
||||
}, on:input=move |_| {
|
||||
let current_ts = js_lib::get_ms_timestamp();
|
||||
if (current_ts - *ts.get_untracked()) > 100 {
|
||||
div {
|
||||
label(for="recipe_text") { "Recipe" }
|
||||
textarea(name="recipe_text", bind:value=text, aria-invalid=aria_hint.get(), rows=20, on:change=move |_| {
|
||||
dirty.set(true);
|
||||
check_recipe_parses(text.get_untracked().as_str(), error_text, aria_hint);
|
||||
ts.set(current_ts);
|
||||
}
|
||||
})
|
||||
}, on:input=move |_| {
|
||||
let current_ts = js_lib::get_ms_timestamp();
|
||||
if (current_ts - *ts.get_untracked()) > 100 {
|
||||
check_recipe_parses(text.get_untracked().as_str(), error_text, aria_hint);
|
||||
ts.set(current_ts);
|
||||
}
|
||||
})
|
||||
}
|
||||
div(class="parse") { (error_text.get()) }
|
||||
}
|
||||
span(role="button", on:click=move |_| {
|
||||
@ -99,12 +105,19 @@ pub fn Editor<'ctx, G: Html>(cx: Scope<'ctx>, props: RecipeComponentProps<'ctx>)
|
||||
debug!("Recipe text is changed");
|
||||
spawn_local_scoped(cx, {
|
||||
let store = crate::api::HttpStore::get_from_context(cx);
|
||||
let category = category.get_untracked();
|
||||
let category = if category.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(category.as_ref().clone())
|
||||
};
|
||||
async move {
|
||||
debug!("Attempting to save recipe");
|
||||
if let Err(e) = store
|
||||
.store_recipes(vec![RecipeEntry(
|
||||
id.get_untracked().as_ref().clone(),
|
||||
text.get_untracked().as_ref().clone(),
|
||||
category,
|
||||
)])
|
||||
.await
|
||||
{
|
||||
|
Loading…
x
Reference in New Issue
Block a user