diff --git a/cooked/src/filters.rs b/cooked/src/filters.rs index 9115acd..6198a81 100644 --- a/cooked/src/filters.rs +++ b/cooked/src/filters.rs @@ -22,3 +22,6 @@ pub fn is_checked<'s>((id, slice): &(i32, &[String])) -> ::askama::Result .filter_map(|s| s.parse::().ok()) .any(|s| s == *id)) } +pub fn some_or_default<'s>(s: &Option) -> ::askama::Result { + Ok(s.as_ref().cloned().unwrap_or_default()) +} diff --git a/cooked/src/main.rs b/cooked/src/main.rs index 6242ad5..665c996 100644 --- a/cooked/src/main.rs +++ b/cooked/src/main.rs @@ -1,4 +1,4 @@ -#![feature(iterator_try_collect, duration_constructors)] +#![feature(iterator_try_collect, duration_constructors, string_remove_matches)] use actix::SyncArbiter; use actix_files::Files; @@ -7,7 +7,7 @@ use actix_session::{storage::RedisSessionStore, SessionMiddleware}; use actix_web::cookie::Key; use actix_web::{error, HttpResponse}; use actix_web::{web::Data, App, HttpServer}; -use actix_web_prom::{PrometheusMetrics, PrometheusMetricsBuilder}; +use actix_web_prom::PrometheusMetricsBuilder; use sea_orm::Database; use serde_qs as qs; use serde_qs::actix::QsQueryConfig; diff --git a/cooked/src/routes.rs b/cooked/src/routes.rs index 0d544cf..fcd76e7 100644 --- a/cooked/src/routes.rs +++ b/cooked/src/routes.rs @@ -1,5 +1,6 @@ use crate::actors::search::{Find, Search}; use crate::types::*; +use crate::utils::ParsedIngredient; use crate::{entities, entities::prelude::*, filters}; use actix_files::Files; use actix_identity::Identity; @@ -300,7 +301,7 @@ struct ImageUpload { #[template(path = "recipies/create_form.jinja", ext = "html")] struct RecipeForm { title: String, - summary: String, + summary: Option, ingredients: String, steps: String, tags: String, @@ -324,7 +325,7 @@ struct RecipeForm { struct EditRecipeForm { id: i32, title: String, - summary: String, + summary: Option, ingredients: String, steps: String, tags: String, @@ -648,6 +649,7 @@ async fn show( name: ingredients.get(&ri.ingredient_id)?.to_owned(), qty: ri.qty, unit: ri.unit, + sidenote: ri.sidenote, }) }) .collect::>(); @@ -674,7 +676,7 @@ async fn recipe_form(admin: Identity, db: Data) -> RecipeFor let ingredients = Ingredients::find().all(&**db).await.unwrap_or_default(); RecipeForm { title: "".into(), - summary: "".into(), + summary: None, ingredients: "".into(), steps: "".into(), tags: "".into(), @@ -741,7 +743,8 @@ async fn save_recipe(form: RecipeForm, t: &mut DatabaseTransaction) -> Result>(), ), ) @@ -808,11 +811,13 @@ async fn create_ingredients( .collect::>(); let missing = ingredients .iter() - .filter(|(name, ..)| !known.contains(name)) - .map(|(name, ..)| entities::ingredients::ActiveModel { - name: Set(name.to_owned()), - ..Default::default() - }) + .filter(|ParsedIngredient { name, .. }| !known.contains(name)) + .map( + |ParsedIngredient { name, .. }| entities::ingredients::ActiveModel { + name: Set(name.to_owned()), + ..Default::default() + }, + ) .collect::>(); let mut v = Vec::with_capacity(missing.len()); for missing in missing { @@ -835,15 +840,23 @@ async fn create_ingredients( RecipeIngredients::insert_many( ingredients .into_iter() - .filter_map(|(name, unit, qty)| { - Some(entities::recipe_ingredients::ActiveModel { - ingredient_id: Set(*map.get(&name)?), - qty: Set(qty), - unit: Set(unit.unwrap_or_default()), - recipe_id: Set(recipe_id), - ..Default::default() - }) - }) + .filter_map( + |ParsedIngredient { + name, + unit, + qty, + sidenote, + }| { + Some(entities::recipe_ingredients::ActiveModel { + ingredient_id: Set(*map.get(&name)?), + qty: Set(qty), + unit: Set(unit.unwrap_or_default()), + recipe_id: Set(recipe_id), + sidenote: Set(sidenote), + ..Default::default() + }) + }, + ) .collect::>(), ) .exec(&mut *t) @@ -1082,7 +1095,7 @@ async fn edit_recipe( Ok(EditRecipeForm { id, title: recipe.title, - summary: recipe.summary.unwrap_or_default(), + summary: recipe.summary, ingredients: recipe_ingredients .into_iter() .filter_map(|ingredient| { @@ -1168,7 +1181,8 @@ async fn save_recipe_changes( use sea_orm::ActiveValue::*; let mut summary = String::new(); - let parser = pulldown_cmark::Parser::new(&form.summary); + let raw_summary = form.summary.unwrap_or_default(); + let parser = pulldown_cmark::Parser::new(&raw_summary); pulldown_cmark::html::push_html(&mut summary, parser); let time = match form.time { diff --git a/cooked/src/types.rs b/cooked/src/types.rs index f08e574..aceed0f 100644 --- a/cooked/src/types.rs +++ b/cooked/src/types.rs @@ -11,6 +11,7 @@ pub struct Ingredient { pub qty: String, pub name: String, pub unit: String, + pub sidenote: Option, } #[derive(Debug, thiserror::Error)] diff --git a/cooked/src/utils.rs b/cooked/src/utils.rs index 5dce03c..94d1651 100644 --- a/cooked/src/utils.rs +++ b/cooked/src/utils.rs @@ -32,12 +32,32 @@ pub fn parse_steps(s: &str) -> Result)>, Error> { }) } -pub fn parse_ingredients(s: &str) -> Result, String)>, Error> { +#[derive(Debug, PartialEq)] +pub struct ParsedIngredient { + pub qty: String, + pub name: String, + pub unit: Option, + pub sidenote: Option, +} + +pub fn parse_ingredients(s: &str) -> Result, Error> { s.lines() .map(|s| s.trim()) .filter(|s| !s.is_empty()) .map(|s| { - let mut pieces = s + let sidenote = s + .chars() + .skip_while(|c| *c != '(') + .skip_while(|c| *c == '(') + .take_while(|c| *c != ')') + .collect::() + .trim() + .to_string(); + + let d = s.chars().take_while(|c| *c != '('); + let pieces = d.collect::(); + let mut pieces = pieces + .trim() .split_whitespace() .filter(|s| !s.is_empty()) .collect::>(); @@ -46,12 +66,22 @@ pub fn parse_ingredients(s: &str) -> Result, String) // no unit 2 => { let qty = pieces.remove(0); - Ok((pieces.join(" "), None, qty.to_string())) + Ok(ParsedIngredient { + name: pieces.join(" "), + unit: None, + qty: qty.to_string(), + sidenote: Some(sidenote).filter(|s| !s.is_empty()), + }) } _ => { let qty = pieces.remove(0); let unit = pieces.remove(0); - Ok((pieces.join(" "), Some(unit.to_string()), qty.to_string())) + Ok(ParsedIngredient { + name: pieces.join(" "), + unit: Some(unit.to_string()), + qty: qty.to_string(), + sidenote: Some(sidenote).filter(|s| !s.is_empty()), + }) } } }) @@ -75,3 +105,27 @@ pub async fn refresh_records(db: Data, search: Data) .collect::>(); let _ = search.send(Refresh { records }).await; } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn ingredient_without_unit() {} + + #[test] + fn ingredient_with_unit() {} + + #[test] + fn ingredient_with_summary() { + let s = "1 szt pomarańcza (może być czerwona)"; + let ingredient = parse_ingredients(s).unwrap_or_default(); + let expected = vec![ParsedIngredient { + qty: "1".into(), + unit: Some("szt".into()), + name: "pomarańcza".into(), + sidenote: Some("może być czerwona".into()), + }]; + assert_eq!(ingredient, expected); + } +} diff --git a/cooked/templates/recipies/create_form.jinja b/cooked/templates/recipies/create_form.jinja index 4da6a9f..de58ed2 100644 --- a/cooked/templates/recipies/create_form.jinja +++ b/cooked/templates/recipies/create_form.jinja @@ -27,7 +27,7 @@ diff --git a/cooked/templates/recipies/show.jinja b/cooked/templates/recipies/show.jinja index d7863b7..30d2193 100644 --- a/cooked/templates/recipies/show.jinja +++ b/cooked/templates/recipies/show.jinja @@ -44,6 +44,13 @@ {{ ingredient.name }} + {% match ingredient.sidenote %} + {% when Some with (sidenote) %} + +  ({{ sidenote }}) + + {% when None %} + {% endmatch %} {% endfor %} diff --git a/cooked/templates/recipies/update_form.jinja b/cooked/templates/recipies/update_form.jinja index fbf3a5e..2288ea4 100644 --- a/cooked/templates/recipies/update_form.jinja +++ b/cooked/templates/recipies/update_form.jinja @@ -34,7 +34,7 @@ diff --git a/entities/src/ingredients.rs b/entities/src/ingredients.rs index 30f5330..b708c51 100644 --- a/entities/src/ingredients.rs +++ b/entities/src/ingredients.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/entities/src/lib.rs b/entities/src/lib.rs index 2aeecf5..85a3ddc 100644 --- a/entities/src/lib.rs +++ b/entities/src/lib.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 pub mod prelude; diff --git a/entities/src/prelude.rs b/entities/src/prelude.rs index 4a6dc2c..89fe208 100644 --- a/entities/src/prelude.rs +++ b/entities/src/prelude.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 pub use super::ingredients::Entity as Ingredients; pub use super::recipe_ingredients::Entity as RecipeIngredients; diff --git a/entities/src/recipe_ingredients.rs b/entities/src/recipe_ingredients.rs index fa3eb90..37b0835 100644 --- a/entities/src/recipe_ingredients.rs +++ b/entities/src/recipe_ingredients.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; @@ -12,6 +12,7 @@ pub struct Model { pub qty: String, pub unit: String, pub recipe_id: i32, + pub sidenote: Option, } #[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)] diff --git a/entities/src/recipe_steps.rs b/entities/src/recipe_steps.rs index bdfc8b5..5aec606 100644 --- a/entities/src/recipe_steps.rs +++ b/entities/src/recipe_steps.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/entities/src/recipe_tags.rs b/entities/src/recipe_tags.rs index eb536b4..d91268a 100644 --- a/entities/src/recipe_tags.rs +++ b/entities/src/recipe_tags.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/entities/src/recipies.rs b/entities/src/recipies.rs index 556bd72..8b5aa1a 100644 --- a/entities/src/recipies.rs +++ b/entities/src/recipies.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/entities/src/tags.rs b/entities/src/tags.rs index 8b63f0a..2dfc408 100644 --- a/entities/src/tags.rs +++ b/entities/src/tags.rs @@ -1,4 +1,4 @@ -//! `SeaORM` Entity. Generated by sea-orm-codegen 0.12.11 +//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0 use sea_orm::entity::prelude::*; use serde::{Deserialize, Serialize}; diff --git a/migration/src/lib.rs b/migration/src/lib.rs index 1e75471..f9b45e3 100644 --- a/migration/src/lib.rs +++ b/migration/src/lib.rs @@ -2,6 +2,7 @@ pub use sea_orm_migration::prelude::*; mod m20220101_000001_create_table; mod m20220101_000002_change_quantity_type; +mod m20220101_000003_add_sidenote_to_recipe_ingredient; pub struct Migrator; @@ -11,6 +12,7 @@ impl MigratorTrait for Migrator { vec![ Box::new(m20220101_000001_create_table::Migration), Box::new(m20220101_000002_change_quantity_type::Migration), + Box::new(m20220101_000003_add_sidenote_to_recipe_ingredient::Migration), ] } } diff --git a/migration/src/m20220101_000002_change_quantity_type.rs b/migration/src/m20220101_000002_change_quantity_type.rs index 7983791..687fc6b 100644 --- a/migration/src/m20220101_000002_change_quantity_type.rs +++ b/migration/src/m20220101_000002_change_quantity_type.rs @@ -33,9 +33,4 @@ impl MigrationTrait for Migration { #[derive(DeriveIden)] enum RecipeIngredient { RecipeIngredients, - Id, - IngredientId, - Qty, - Unit, - RecipeId, }