New layout & new seed
This commit is contained in:
parent
b4e307496e
commit
fead4e970a
47
Cargo.lock
generated
47
Cargo.lock
generated
@ -87,6 +87,7 @@ dependencies = [
|
||||
"bytestring",
|
||||
"derive_more 0.99.18",
|
||||
"encoding_rs",
|
||||
"flate2",
|
||||
"futures-core",
|
||||
"h2",
|
||||
"http",
|
||||
@ -104,6 +105,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"tracing",
|
||||
"zstd",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -1039,7 +1041,7 @@ version = "0.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "482aa5695bca086022be453c700a40c02893f1ba7098a2c88351de55341ae894"
|
||||
dependencies = [
|
||||
"entities",
|
||||
"entities 1.0.1",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"regex",
|
||||
@ -1130,6 +1132,7 @@ dependencies = [
|
||||
"askama_actix",
|
||||
"chrono",
|
||||
"derive_more 1.0.0",
|
||||
"entities 0.1.0",
|
||||
"humantime",
|
||||
"humantime-serde",
|
||||
"migration",
|
||||
@ -1519,6 +1522,14 @@ dependencies = [
|
||||
"cfg-if",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "entities"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"sea-orm",
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "entities"
|
||||
version = "1.0.1"
|
||||
@ -1591,6 +1602,16 @@ dependencies = [
|
||||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fake"
|
||||
version = "3.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "38bdb1a5a77008bd1208d3fccd90611d7bee8f6d3296175f69fb056747a220fb"
|
||||
dependencies = [
|
||||
"deunicode",
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fastdivide"
|
||||
version = "0.4.1"
|
||||
@ -1624,6 +1645,16 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flate2"
|
||||
version = "1.0.34"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1b589b4dc103969ad3cf85c950899926ec64300a1a46d76c03a6072957036f0"
|
||||
dependencies = [
|
||||
"crc32fast",
|
||||
"miniz_oxide",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.1"
|
||||
@ -4026,6 +4057,20 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "seed"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"actix-web",
|
||||
"entities 0.1.0",
|
||||
"fake",
|
||||
"migration",
|
||||
"rand",
|
||||
"sea-orm",
|
||||
"serde",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "1.0.23"
|
||||
|
38
Cargo.toml
38
Cargo.toml
@ -1,36 +1,2 @@
|
||||
[package]
|
||||
name = "cooked"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix = "0.13.5"
|
||||
actix-files = { version = "0.6.6", features = ["experimental-io-uring"] }
|
||||
actix-web = { version = "4.9.0", features = ["compress-brotli", "cookies", "experimental-io-uring", "macros", "rustls", "secure-cookies", "unicode"], default-features = false }
|
||||
askama = { version = "0.12.1", features = ["with-actix-web", "serde_json", "mime_guess", "markdown", "comrak", "mime"] }
|
||||
askama_actix = "0.14.0"
|
||||
redis = { version = "0.27.5", features = ["tokio", "json", "uuid", "tokio-comp", "connection-manager"] }
|
||||
sea-orm = { version = "1.1.0", default-features = false, features = ["chrono", "macros", "runtime-actix-rustls", "serde_json", "sqlx-postgres", "with-uuid"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
serde = "1.0.210"
|
||||
serde_json = "1.0.132"
|
||||
uuid = { version = "1.11.0", features = ["serde", "v4", "v8"] }
|
||||
migration = { path = "./migration" }
|
||||
rswind = "0.0.1-alpha.1"
|
||||
rswind_cli = "0.0.1-alpha.1"
|
||||
derive_more = { version = "1.0.0", features = ["deref"] }
|
||||
chrono = "0.4.38"
|
||||
humantime = "2.1.0"
|
||||
humantime-serde = "1.1.1"
|
||||
actix-session = { version = "0.10.1", features = ["redis-session-rustls"] }
|
||||
actix-identity = "0.8.0"
|
||||
tantivy = "0.22.0"
|
||||
tempfile = "3.13.0"
|
||||
pulldown-cmark = "0.12.2"
|
||||
thiserror = "1.0.65"
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
tracing-test = "0.2.5"
|
||||
[workspace]
|
||||
members = ["cooked", "seed", "entities"]
|
||||
|
37
cooked/Cargo.toml
Normal file
37
cooked/Cargo.toml
Normal file
@ -0,0 +1,37 @@
|
||||
[package]
|
||||
name = "cooked"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
actix = "0.13.5"
|
||||
actix-files = { version = "0.6.6", features = ["experimental-io-uring"] }
|
||||
actix-web = { version = "4.9.0", features = ["compress-brotli", "cookies", "experimental-io-uring", "macros", "rustls", "secure-cookies", "unicode"], default-features = false }
|
||||
askama = { version = "0.12.1", features = ["with-actix-web", "serde_json", "mime_guess", "markdown", "comrak", "mime"] }
|
||||
askama_actix = "0.14.0"
|
||||
redis = { version = "0.27.5", features = ["tokio", "json", "uuid", "tokio-comp", "connection-manager"] }
|
||||
sea-orm = { version = "1.1.0", default-features = false, features = ["chrono", "macros", "runtime-actix-rustls", "serde_json", "sqlx-postgres", "with-uuid"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = "0.3.18"
|
||||
serde = "1.0.210"
|
||||
serde_json = "1.0.132"
|
||||
uuid = { version = "1.11.0", features = ["serde", "v4", "v8"] }
|
||||
migration = { path = "../migration" }
|
||||
entities = { path = "../entities" }
|
||||
rswind = "0.0.1-alpha.1"
|
||||
rswind_cli = "0.0.1-alpha.1"
|
||||
derive_more = { version = "1.0.0", features = ["deref"] }
|
||||
chrono = "0.4.38"
|
||||
humantime = "2.1.0"
|
||||
humantime-serde = "1.1.1"
|
||||
actix-session = { version = "0.10.1", features = ["redis-session-rustls"] }
|
||||
actix-identity = "0.8.0"
|
||||
tantivy = "0.22.0"
|
||||
tempfile = "3.13.0"
|
||||
pulldown-cmark = "0.12.2"
|
||||
thiserror = "1.0.65"
|
||||
|
||||
[build-dependencies]
|
||||
|
||||
[dev-dependencies]
|
||||
tracing-test = "0.2.5"
|
@ -11,7 +11,7 @@ use std::str::FromStr;
|
||||
use types::Admins;
|
||||
|
||||
pub mod actors;
|
||||
pub mod entities;
|
||||
pub use entities;
|
||||
pub mod filters;
|
||||
pub mod routes;
|
||||
pub mod types;
|
@ -1,6 +1,6 @@
|
||||
use crate::actors::search::{Find, Search};
|
||||
use crate::types::*;
|
||||
use crate::{entities, filters};
|
||||
use crate::{entities, entities::prelude::*, filters};
|
||||
use actix_identity::Identity;
|
||||
use actix_web::http::header::{CONTENT_LENGTH, CONTENT_TYPE};
|
||||
use actix_web::web::{Data, Form, Path};
|
||||
@ -8,8 +8,9 @@ use actix_web::HttpMessage;
|
||||
use actix_web::{get, post, HttpRequest, HttpResponse, Responder};
|
||||
use askama::Template;
|
||||
use askama_actix::TemplateToResponse;
|
||||
use sea_orm::{prelude::*, QuerySelect};
|
||||
use sea_orm::{prelude::*, DatabaseTransaction, QuerySelect, TransactionTrait};
|
||||
use serde::Deserialize;
|
||||
use std::collections::{BTreeMap, BTreeSet};
|
||||
|
||||
#[derive(Debug, Template, derive_more::Deref)]
|
||||
#[template(path = "recipe_card.jinja")]
|
||||
@ -122,13 +123,6 @@ async fn sign_in(
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct CreateRecipe {
|
||||
// #[serde(default)]
|
||||
// #[serde(with = "humantime_serde")]
|
||||
// time: Option<chrono::Duration>,
|
||||
}
|
||||
|
||||
#[derive(Debug, serde::Deserialize)]
|
||||
struct SignIn {
|
||||
email: String,
|
||||
@ -229,9 +223,9 @@ async fn index_html(
|
||||
#[template(path = "recipies/show.jinja", ext = "html")]
|
||||
struct RecipeDetailTemplate {
|
||||
recipe: entities::recipies::Model,
|
||||
tags: Vec<entities::recipe_tags::Model>,
|
||||
tags: Vec<entities::tags::Model>,
|
||||
steps: Vec<entities::recipe_steps::Model>,
|
||||
ingeredients: Vec<entities::recipe_ingeredients::Model>,
|
||||
ingredients: Vec<Ingredient>,
|
||||
session: Option<User>,
|
||||
page: Page,
|
||||
}
|
||||
@ -253,7 +247,8 @@ async fn show(
|
||||
.append_header(("location", "/"))
|
||||
.finish();
|
||||
};
|
||||
let tags = entities::prelude::RecipeTags::find()
|
||||
let tags = entities::prelude::Tags::find()
|
||||
.left_join(entities::prelude::RecipeTags)
|
||||
.filter(entities::recipe_tags::Column::RecipeId.eq(id))
|
||||
.all(db)
|
||||
.await
|
||||
@ -263,18 +258,43 @@ async fn show(
|
||||
.all(db)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let ingeredients = entities::prelude::RecipeIngeredients::find()
|
||||
.filter(entities::recipe_ingeredients::Column::RecipeId.eq(id))
|
||||
let recipe_ingredients = entities::prelude::RecipeIngredients::find()
|
||||
.filter(entities::recipe_ingredients::Column::RecipeId.eq(recipe.id))
|
||||
.all(db)
|
||||
.await
|
||||
.unwrap_or_default();
|
||||
let ingredients = entities::prelude::Ingredients::find()
|
||||
.filter(
|
||||
entities::ingredients::Column::Id.is_in(
|
||||
recipe_ingredients
|
||||
.iter()
|
||||
.map(|i| i.ingredient_id)
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
)
|
||||
.all(db)
|
||||
.await
|
||||
.unwrap_or_default()
|
||||
.into_iter()
|
||||
.map(|ingredient| (ingredient.id, ingredient.name))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let ingredients = recipe_ingredients
|
||||
.into_iter()
|
||||
.filter_map(|ri| {
|
||||
Some(Ingredient {
|
||||
name: ingredients.get(&ri.ingredient_id)?.to_owned(),
|
||||
qty: ri.qty,
|
||||
unit: ri.unit,
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
HttpResponse::Ok().body(
|
||||
RecipeDetailTemplate {
|
||||
steps,
|
||||
tags,
|
||||
recipe,
|
||||
ingeredients,
|
||||
ingredients,
|
||||
session: admin.and_then(|s| s.id().ok()),
|
||||
page: Page::Recipe,
|
||||
}
|
||||
@ -305,22 +325,31 @@ async fn create_recipe(
|
||||
_admin: Identity,
|
||||
form: Form<RecipeForm>,
|
||||
db: Data<DatabaseConnection>,
|
||||
) -> HttpResponse {
|
||||
) -> Result<HttpResponse, Error> {
|
||||
let form = form.into_inner();
|
||||
let mut failure = form.clone();
|
||||
|
||||
match save_recipe(form, db.into_inner()).await {
|
||||
let mut t = db
|
||||
.begin()
|
||||
.await
|
||||
.inspect_err(|e| tracing::error!("Create recipe transaction: {e}"))
|
||||
.map_err(|_| Error::DatabaseError)?;
|
||||
match save_recipe(form, &mut t).await {
|
||||
Err(e) => {
|
||||
failure.error = Some(e.to_string());
|
||||
failure.to_response()
|
||||
let _ = t.rollback().await;
|
||||
Ok(failure.to_response())
|
||||
}
|
||||
Ok(_) => HttpResponse::TemporaryRedirect()
|
||||
Ok(_) => {
|
||||
let _ = t.commit().await;
|
||||
Ok(HttpResponse::TemporaryRedirect()
|
||||
.append_header(("location", "/"))
|
||||
.finish(),
|
||||
.finish())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn save_recipe(form: RecipeForm, db: DatabaseConnection) -> Result<(), Error> {
|
||||
async fn save_recipe(form: RecipeForm, t: &mut DatabaseTransaction) -> Result<(), Error> {
|
||||
use crate::entities::recipies::ActiveModel as RAM;
|
||||
use sea_orm::ActiveValue::*;
|
||||
|
||||
@ -328,17 +357,107 @@ async fn save_recipe(form: RecipeForm, db: DatabaseConnection) -> Result<(), Err
|
||||
let parser = pulldown_cmark::Parser::new(&form.summary);
|
||||
pulldown_cmark::html::push_html(&mut summary, parser);
|
||||
|
||||
let model = RAM {
|
||||
title: Set(form.title),
|
||||
image_url: Set(form.image_url),
|
||||
author: Set(None),
|
||||
time: Set(None),
|
||||
summary: Set(Some(summary)),
|
||||
..Default::default()
|
||||
let time = match form.time {
|
||||
Some(s) => Some(
|
||||
humantime::parse_duration(&s)
|
||||
.inspect_err(|e| tracing::warn!("Invalid duration format: {e}"))
|
||||
.map_err(|_| Error::InvalidTime)?
|
||||
.as_secs() as i32,
|
||||
),
|
||||
None => None,
|
||||
};
|
||||
|
||||
let steps = crate::utils::parse_steps(&form.steps, 0)?;
|
||||
let ingeredients = crate::utils::parse_steps(&form.ingeredients, 0)?;
|
||||
let recipe_id = Recipies::insert(RAM {
|
||||
title: Set(form.title),
|
||||
image_url: Set(form.image_url),
|
||||
author: Set(form.author),
|
||||
time: Set(time),
|
||||
summary: Set(Some(summary)),
|
||||
..Default::default()
|
||||
})
|
||||
.exec(&mut *t)
|
||||
.await
|
||||
.inspect_err(|e| tracing::error!("Failed to save Recipe: {e}"))
|
||||
.map_err(|_| Error::SaveRecipe)?
|
||||
.last_insert_id;
|
||||
|
||||
let steps = crate::utils::parse_steps(&form.steps)?
|
||||
.into_iter()
|
||||
.map(|(body, hint)| entities::recipe_steps::ActiveModel {
|
||||
body: Set(body),
|
||||
hint: Set(hint),
|
||||
recipe_id: Set(recipe_id),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let _steps = RecipeSteps::insert_many(steps)
|
||||
.exec(&mut *t)
|
||||
.await
|
||||
.inspect_err(|e| tracing::error!("Save steps: {e}"))
|
||||
.map_err(|_| Error::SaveRecipeStep)?;
|
||||
let ingredients = crate::utils::parse_ingenedients(&form.ingeredients)?;
|
||||
let known = Ingredients::find()
|
||||
.filter(
|
||||
entities::ingredients::Column::Name.is_in(
|
||||
ingredients
|
||||
.iter()
|
||||
.map(|(name, ..)| name.to_string())
|
||||
.collect::<Vec<_>>(),
|
||||
),
|
||||
)
|
||||
.all(&mut *t)
|
||||
.await
|
||||
.inspect_err(|e| tracing::warn!("Failed to find ingredients: {e}"))
|
||||
.map_err(|_| Error::SaveRecipeIngeredient)?;
|
||||
let missing = {
|
||||
let known = known
|
||||
.iter()
|
||||
.map(|model| &model.name)
|
||||
.collect::<BTreeSet<_>>();
|
||||
let missing = ingredients
|
||||
.iter()
|
||||
.filter(|(name, ..)| !known.contains(name))
|
||||
.map(|(name, ..)| entities::ingredients::ActiveModel {
|
||||
name: Set(name.to_owned()),
|
||||
..Default::default()
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
let mut v = Vec::with_capacity(missing.len());
|
||||
for missing in missing {
|
||||
v.push(
|
||||
Ingredients::insert(missing)
|
||||
.exec_with_returning(&mut *t)
|
||||
.await
|
||||
.inspect_err(|e| tracing::error!("Failed to create ingredients: {e}"))
|
||||
.map_err(|_| Error::DatabaseError)?,
|
||||
)
|
||||
}
|
||||
v
|
||||
};
|
||||
let map = known
|
||||
.into_iter()
|
||||
.chain(missing.into_iter())
|
||||
.map(|row| (row.name, row.id))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
|
||||
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()
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.exec(&mut *t)
|
||||
.await
|
||||
.inspect_err(|e| tracing::error!("Save ingeredients: {e}"))
|
||||
.map_err(|_| Error::SaveRecipeIngeredient)?;
|
||||
|
||||
Ok(())
|
||||
}
|
87
cooked/src/types.rs
Normal file
87
cooked/src/types.rs
Normal file
@ -0,0 +1,87 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use actix_web::http::StatusCode;
|
||||
use serde::Deserialize;
|
||||
|
||||
pub type User = String;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Ingredient {
|
||||
pub qty: i32,
|
||||
pub name: String,
|
||||
pub unit: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Invalid step list")]
|
||||
InvalidStepList,
|
||||
#[error("Invalid ingeredients list")]
|
||||
InvalidIngeredientList,
|
||||
#[error("Time has invalid format")]
|
||||
InvalidTime,
|
||||
#[error("Internal server error")]
|
||||
SaveRecipe,
|
||||
#[error("Internal server error")]
|
||||
SaveRecipeStep,
|
||||
#[error("Internal server error")]
|
||||
SaveRecipeIngeredient,
|
||||
#[error("Internal server error")]
|
||||
SaveRecipeTag,
|
||||
#[error("Internal server error")]
|
||||
DatabaseError,
|
||||
}
|
||||
|
||||
impl actix_web::error::ResponseError for Error {
|
||||
fn status_code(&self) -> StatusCode {
|
||||
match self {
|
||||
Self::InvalidStepList => StatusCode::BAD_REQUEST,
|
||||
Self::InvalidIngeredientList => StatusCode::BAD_REQUEST,
|
||||
Self::InvalidTime => StatusCode::BAD_REQUEST,
|
||||
Self::SaveRecipe => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Self::SaveRecipeStep => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Self::SaveRecipeIngeredient => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Self::SaveRecipeTag => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
Self::DatabaseError => StatusCode::INTERNAL_SERVER_ERROR,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
|
||||
pub enum Page {
|
||||
Index,
|
||||
Recipe,
|
||||
Search,
|
||||
SignIn,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Admin {
|
||||
pub email: String,
|
||||
pub pass: String,
|
||||
}
|
||||
|
||||
impl FromStr for Admin {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut it = s.split(':');
|
||||
|
||||
Ok(Self {
|
||||
email: it.next().expect("Admin login is required").into(),
|
||||
pass: it.next().expect("Admin password is required").into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, derive_more::Deref)]
|
||||
pub struct Admins(Vec<Admin>);
|
||||
|
||||
impl FromStr for Admins {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(
|
||||
s.trim().split(',').filter_map(|s| s.parse().ok()).collect(),
|
||||
))
|
||||
}
|
||||
}
|
@ -1,11 +1,7 @@
|
||||
use crate::entities::recipe_ingeredients::ActiveModel as RIAM;
|
||||
use crate::entities::recipe_steps::ActiveModel as RSAM;
|
||||
use crate::types::Error;
|
||||
use crate::types::Error::*;
|
||||
use sea_orm::prelude::*;
|
||||
use sea_orm::ActiveValue::*;
|
||||
|
||||
pub fn parse_steps(s: &str, recipe_id: i32) -> Result<Vec<RSAM>, Error> {
|
||||
pub fn parse_steps(s: &str) -> Result<Vec<(String, Option<String>)>, Error> {
|
||||
s.lines()
|
||||
.filter(|s| !s.trim().is_empty())
|
||||
.try_fold(Vec::new(), |mut v, line| {
|
||||
@ -13,15 +9,11 @@ pub fn parse_steps(s: &str, recipe_id: i32) -> Result<Vec<RSAM>, Error> {
|
||||
|
||||
match line.chars().next() {
|
||||
Some('*') => {
|
||||
v.push(RSAM {
|
||||
body: Set(line.replacen("*", "", 1).trim().to_string()),
|
||||
recipe_id: Set(recipe_id),
|
||||
..Default::default()
|
||||
});
|
||||
v.push((line.replacen("*", "", 1).trim().to_string(), None));
|
||||
}
|
||||
Some('>') => {
|
||||
v.last_mut().ok_or(InvalidStepList)?.hint =
|
||||
Set(Some(line.replacen(">", "", 1).trim().to_string()));
|
||||
v.last_mut().ok_or(InvalidStepList)?.1 =
|
||||
Some(line.replacen(">", "", 1).trim().to_string());
|
||||
}
|
||||
_ => return Err(InvalidStepList),
|
||||
};
|
||||
@ -30,7 +22,7 @@ pub fn parse_steps(s: &str, recipe_id: i32) -> Result<Vec<RSAM>, Error> {
|
||||
})
|
||||
}
|
||||
|
||||
pub fn parse_ingenedients(s: &str, recipe_id: i32) -> Result<Vec<RIAM>, Error> {
|
||||
pub fn parse_ingenedients(s: &str) -> Result<Vec<(String, Option<String>, i32)>, Error> {
|
||||
s.lines()
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
@ -47,12 +39,7 @@ pub fn parse_ingenedients(s: &str, recipe_id: i32) -> Result<Vec<RIAM>, Error> {
|
||||
.remove(0)
|
||||
.parse()
|
||||
.map_err(|_| InvalidIngeredientList)?;
|
||||
Ok(RIAM {
|
||||
recipe_id: Set(recipe_id),
|
||||
name: Set(pieces.join(" ")),
|
||||
qty: Set(qty),
|
||||
..Default::default()
|
||||
})
|
||||
Ok((pieces.join(" "), None, qty))
|
||||
}
|
||||
_ => {
|
||||
let qty = pieces
|
||||
@ -60,15 +47,13 @@ pub fn parse_ingenedients(s: &str, recipe_id: i32) -> Result<Vec<RIAM>, Error> {
|
||||
.parse()
|
||||
.map_err(|_| InvalidIngeredientList)?;
|
||||
let unit = pieces.remove(0);
|
||||
Ok(RIAM {
|
||||
recipe_id: Set(recipe_id),
|
||||
name: Set(pieces.join(" ")),
|
||||
unit: Set(unit.to_string()),
|
||||
qty: Set(qty),
|
||||
..Default::default()
|
||||
})
|
||||
Ok((pieces.join(" "), Some(unit.to_string()), qty))
|
||||
}
|
||||
}
|
||||
})
|
||||
.try_collect()
|
||||
}
|
||||
|
||||
pub fn parse_tags(s: &str) -> Result<Vec<String>, Error> {
|
||||
s.lines().map(|line| Ok(line.to_string())).try_collect()
|
||||
}
|
8
entities/Cargo.toml
Normal file
8
entities/Cargo.toml
Normal file
@ -0,0 +1,8 @@
|
||||
[package]
|
||||
name = "entities"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
sea-orm = { version = "1.1.0", default-features = false, features = ["chrono", "macros", "runtime-actix-rustls", "serde_json", "sqlx-postgres", "with-uuid"] }
|
||||
serde = "1.0.210"
|
26
entities/src/ingredients.rs
Normal file
26
entities/src/ingredients.rs
Normal file
@ -0,0 +1,26 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "ingredients")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::recipe_ingredients::Entity")]
|
||||
RecipeIngredients,
|
||||
}
|
||||
|
||||
impl Related<super::recipe_ingredients::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::RecipeIngredients.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
@ -2,7 +2,9 @@
|
||||
|
||||
pub mod prelude;
|
||||
|
||||
pub mod recipe_ingeredients;
|
||||
pub mod ingredients;
|
||||
pub mod recipe_ingredients;
|
||||
pub mod recipe_steps;
|
||||
pub mod recipe_tags;
|
||||
pub mod recipies;
|
||||
pub mod tags;
|
@ -1,6 +1,8 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
pub use super::recipe_ingeredients::Entity as RecipeIngeredients;
|
||||
pub use super::ingredients::Entity as Ingredients;
|
||||
pub use super::recipe_ingredients::Entity as RecipeIngredients;
|
||||
pub use super::recipe_steps::Entity as RecipeSteps;
|
||||
pub use super::recipe_tags::Entity as RecipeTags;
|
||||
pub use super::recipies::Entity as Recipies;
|
||||
pub use super::tags::Entity as Tags;
|
@ -4,11 +4,11 @@ use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "recipe_ingeredients")]
|
||||
#[sea_orm(table_name = "recipe_ingredients")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub ingredient_id: i32,
|
||||
pub qty: i32,
|
||||
pub unit: String,
|
||||
pub recipe_id: i32,
|
||||
@ -16,6 +16,14 @@ pub struct Model {
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(
|
||||
belongs_to = "super::ingredients::Entity",
|
||||
from = "Column::IngredientId",
|
||||
to = "super::ingredients::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "NoAction"
|
||||
)]
|
||||
Ingredients,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::recipies::Entity",
|
||||
from = "Column::RecipeId",
|
||||
@ -26,6 +34,12 @@ pub enum Relation {
|
||||
Recipies,
|
||||
}
|
||||
|
||||
impl Related<super::ingredients::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Ingredients.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::recipies::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Recipies.def()
|
@ -8,7 +8,7 @@ use serde::{Deserialize, Serialize};
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
pub tag_id: i32,
|
||||
pub recipe_id: i32,
|
||||
}
|
||||
|
||||
@ -22,6 +22,14 @@ pub enum Relation {
|
||||
on_delete = "NoAction"
|
||||
)]
|
||||
Recipies,
|
||||
#[sea_orm(
|
||||
belongs_to = "super::tags::Entity",
|
||||
from = "Column::TagId",
|
||||
to = "super::tags::Column::Id",
|
||||
on_update = "NoAction",
|
||||
on_delete = "NoAction"
|
||||
)]
|
||||
Tags,
|
||||
}
|
||||
|
||||
impl Related<super::recipies::Entity> for Entity {
|
||||
@ -30,4 +38,10 @@ impl Related<super::recipies::Entity> for Entity {
|
||||
}
|
||||
}
|
||||
|
||||
impl Related<super::tags::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::Tags.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
@ -17,17 +17,17 @@ pub struct Model {
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::recipe_ingeredients::Entity")]
|
||||
RecipeIngeredients,
|
||||
#[sea_orm(has_many = "super::recipe_ingredients::Entity")]
|
||||
RecipeIngredients,
|
||||
#[sea_orm(has_many = "super::recipe_steps::Entity")]
|
||||
RecipeSteps,
|
||||
#[sea_orm(has_many = "super::recipe_tags::Entity")]
|
||||
RecipeTags,
|
||||
}
|
||||
|
||||
impl Related<super::recipe_ingeredients::Entity> for Entity {
|
||||
impl Related<super::recipe_ingredients::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::RecipeIngeredients.def()
|
||||
Relation::RecipeIngredients.def()
|
||||
}
|
||||
}
|
||||
|
26
entities/src/tags.rs
Normal file
26
entities/src/tags.rs
Normal file
@ -0,0 +1,26 @@
|
||||
//! `SeaORM` Entity, @generated by sea-orm-codegen 1.1.0
|
||||
|
||||
use sea_orm::entity::prelude::*;
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, DeriveEntityModel, Eq, Serialize, Deserialize)]
|
||||
#[sea_orm(table_name = "tags")]
|
||||
pub struct Model {
|
||||
#[sea_orm(primary_key)]
|
||||
pub id: i32,
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, EnumIter, DeriveRelation)]
|
||||
pub enum Relation {
|
||||
#[sea_orm(has_many = "super::recipe_tags::Entity")]
|
||||
RecipeTags,
|
||||
}
|
||||
|
||||
impl Related<super::recipe_tags::Entity> for Entity {
|
||||
fn to() -> RelationDef {
|
||||
Relation::RecipeTags.def()
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveModelBehavior for ActiveModel {}
|
@ -6,6 +6,40 @@ pub struct Migration;
|
||||
#[async_trait::async_trait]
|
||||
impl MigrationTrait for Migration {
|
||||
async fn up(&self, manager: &SchemaManager) -> Result<(), DbErr> {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Tag::Tags)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Tag::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.unique_key()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Tag::Name).string().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(Ingredient::Ingredients)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(Ingredient::Id)
|
||||
.integer()
|
||||
.not_null()
|
||||
.unique_key()
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(Ingredient::Name).string().not_null())
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
@ -56,7 +90,7 @@ impl MigrationTrait for Migration {
|
||||
manager
|
||||
.create_table(
|
||||
Table::create()
|
||||
.table(RecipeIngredient::RecipeIngeredients)
|
||||
.table(RecipeIngredient::RecipeIngredients)
|
||||
.if_not_exists()
|
||||
.col(
|
||||
ColumnDef::new(RecipeIngredient::Id)
|
||||
@ -66,7 +100,11 @@ impl MigrationTrait for Migration {
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(RecipeIngredient::Name).string().not_null())
|
||||
.col(
|
||||
ColumnDef::new(RecipeIngredient::IngredientId)
|
||||
.integer()
|
||||
.not_null(),
|
||||
)
|
||||
.col(ColumnDef::new(RecipeIngredient::Qty).integer().not_null())
|
||||
.col(ColumnDef::new(RecipeIngredient::Unit).string().not_null())
|
||||
.col(
|
||||
@ -78,9 +116,16 @@ impl MigrationTrait for Migration {
|
||||
&mut ForeignKeyCreateStatement::new()
|
||||
.to_tbl(Recipe::Recipies)
|
||||
.to_col(Recipe::Id)
|
||||
.from_tbl(RecipeIngredient::RecipeIngeredients)
|
||||
.from_tbl(RecipeIngredient::RecipeIngredients)
|
||||
.from_col(RecipeIngredient::RecipeId),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKeyCreateStatement::new()
|
||||
.to_tbl(Ingredient::Ingredients)
|
||||
.to_col(Recipe::Id)
|
||||
.from_tbl(RecipeIngredient::RecipeIngredients)
|
||||
.from_col(RecipeIngredient::IngredientId),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@ -97,7 +142,7 @@ impl MigrationTrait for Migration {
|
||||
.auto_increment()
|
||||
.primary_key(),
|
||||
)
|
||||
.col(ColumnDef::new(RecipeTag::Name).string().not_null())
|
||||
.col(ColumnDef::new(RecipeTag::TagId).integer().not_null())
|
||||
.col(ColumnDef::new(RecipeTag::RecipeId).integer().not_null())
|
||||
.foreign_key(
|
||||
&mut ForeignKeyCreateStatement::new()
|
||||
@ -106,6 +151,13 @@ impl MigrationTrait for Migration {
|
||||
.from_tbl(RecipeTag::RecipeTags)
|
||||
.from_col(RecipeTag::RecipeId),
|
||||
)
|
||||
.foreign_key(
|
||||
&mut ForeignKeyCreateStatement::new()
|
||||
.to_tbl(Tag::Tags)
|
||||
.to_col(Tag::Id)
|
||||
.from_tbl(RecipeTag::RecipeTags)
|
||||
.from_col(RecipeTag::TagId),
|
||||
)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@ -116,7 +168,7 @@ impl MigrationTrait for Migration {
|
||||
manager
|
||||
.drop_table(
|
||||
Table::drop()
|
||||
.table(RecipeIngredient::RecipeIngeredients)
|
||||
.table(RecipeIngredient::RecipeIngredients)
|
||||
.to_owned(),
|
||||
)
|
||||
.await?;
|
||||
@ -129,6 +181,12 @@ impl MigrationTrait for Migration {
|
||||
manager
|
||||
.drop_table(Table::drop().table(Recipe::Recipies).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Tag::Tags).to_owned())
|
||||
.await?;
|
||||
manager
|
||||
.drop_table(Table::drop().table(Ingredient::Ingredients).to_owned())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@ -154,19 +212,33 @@ enum RecipeStep {
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum RecipeIngredient {
|
||||
RecipeIngeredients,
|
||||
enum Ingredient {
|
||||
Ingredients,
|
||||
Id,
|
||||
Name,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum RecipeIngredient {
|
||||
RecipeIngredients,
|
||||
Id,
|
||||
IngredientId,
|
||||
Qty,
|
||||
Unit,
|
||||
RecipeId,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum Tag {
|
||||
Tags,
|
||||
Id,
|
||||
Name,
|
||||
}
|
||||
|
||||
#[derive(DeriveIden)]
|
||||
enum RecipeTag {
|
||||
RecipeTags,
|
||||
Id,
|
||||
Name,
|
||||
TagId,
|
||||
RecipeId,
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
mkdir -p src/entities
|
||||
sea-orm-cli generate -v entity --database-url postgres://postgres@localhost/cooked --output-dir ./src/entities --with-serde=both
|
||||
sea-orm-cli generate -v entity --lib --database-url postgres://postgres@localhost/cooked --output-dir ./entities/src --with-serde=both
|
||||
|
7
scripts/migrate
Executable file
7
scripts/migrate
Executable file
@ -0,0 +1,7 @@
|
||||
export DATABASE_URL=postgres://postgres@localhost/cooked
|
||||
|
||||
cargo build -p migration
|
||||
|
||||
./target/debug/migration
|
||||
|
||||
./scripts/seed.sh
|
@ -1,4 +1,4 @@
|
||||
psql cooked postgres -h localhost < ./seed/recipies.sql
|
||||
psql cooked postgres -h localhost < ./seed/recipe_tags.sql
|
||||
psql cooked postgres -h localhost < ./seed/recipe_ingeredients.sql
|
||||
psql cooked postgres -h localhost < ./seed/recipe_steps.sql
|
||||
psql -b cooked postgres -h localhost < ./seed/recipies.sql &&
|
||||
psql -b cooked postgres -h localhost < ./seed/recipe_tags.sql &&
|
||||
psql -b cooked postgres -h localhost < ./seed/recipe_ingeredients.sql &&
|
||||
psql -b cooked postgres -h localhost < ./seed/recipe_steps.sql
|
||||
|
2733
seed/Cargo.lock
generated
Normal file
2733
seed/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
15
seed/Cargo.toml
Normal file
15
seed/Cargo.toml
Normal file
@ -0,0 +1,15 @@
|
||||
[package]
|
||||
name = "seed"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
tokio = "*"
|
||||
sea-orm = { version = "1.1.0", default-features = false, features = ["chrono", "macros", "runtime-actix-rustls", "serde_json", "sqlx-postgres", "with-uuid"] }
|
||||
serde = "1.0.210"
|
||||
actix-web = "*"
|
||||
|
||||
entities = { path = "../entities" }
|
||||
migration = { path = "../migration" }
|
||||
fake = "3.0.0"
|
||||
rand = "0.8.5"
|
265
seed/src/main.rs
Normal file
265
seed/src/main.rs
Normal file
@ -0,0 +1,265 @@
|
||||
use entities::prelude::*;
|
||||
use fake::Fake;
|
||||
use migration::*;
|
||||
use rand::Rng;
|
||||
use sea_orm::Set;
|
||||
use sea_orm::*;
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() {
|
||||
let psql =
|
||||
std::env::var("PSQL").expect("PSQL is required. Please provide postgresql connection url");
|
||||
let db = Database::connect(&psql)
|
||||
.await
|
||||
.expect("Failed to connect to postgresql");
|
||||
{
|
||||
use migration::*;
|
||||
|
||||
Migrator::up(&db, None).await.unwrap();
|
||||
}
|
||||
|
||||
let mut tags = Vec::with_capacity(30);
|
||||
for _ in 0..30 {
|
||||
use entities::tags::ActiveModel;
|
||||
tags.push(
|
||||
Tags::insert(ActiveModel {
|
||||
id: NotSet,
|
||||
name: Set(fake::faker::name::en::FirstName().fake()),
|
||||
})
|
||||
.exec_with_returning(&db)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
let mut ingredients = Vec::with_capacity(INGREDIENTS.len());
|
||||
for name in INGREDIENTS {
|
||||
use entities::tags::ActiveModel;
|
||||
tags.push(
|
||||
Tags::insert(ActiveModel {
|
||||
id: NotSet,
|
||||
name: Set(name.to_string()),
|
||||
})
|
||||
.exec_with_returning(&db)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
let mut recipies = Vec::with_capacity(1_000);
|
||||
for name in DISHES {
|
||||
use entities::recipies::ActiveModel;
|
||||
recipies.push(
|
||||
Recipies::insert(ActiveModel {
|
||||
id: NotSet,
|
||||
title: Set(name.to_string()),
|
||||
image_url: Set(format!(
|
||||
"https://picsum.photos/{w}/{h}",
|
||||
w = fake::rand::random::<i32>() + 200,
|
||||
h = fake::rand::random::<i32>() + 200
|
||||
)),
|
||||
author: Set(Some(fake::faker::name::en::FirstName().fake())),
|
||||
time: Set(Some(fake::rand::random::<i32>() * 15)),
|
||||
summary: Set(fake::faker::lorem::en::Paragraph(2..4).fake()),
|
||||
})
|
||||
.exec_with_returning(&db)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
use rand::seq::SliceRandom;
|
||||
{
|
||||
use entities::recipe_ingredients::ActiveModel;
|
||||
RecipeIngredients::insert_many(
|
||||
recipies
|
||||
.iter()
|
||||
.flat_map(|recipe| {
|
||||
(0..rand::thread_rng().gen_range(3..6))
|
||||
.map(|_| ingredients.choose(&mut rand::thread_rng()))
|
||||
.map(|igredient| ActiveModel {
|
||||
id: NotSet,
|
||||
ingredient_id: Set(ingredient.id),
|
||||
qty: Set(rand::thread_rng().get_range(4..8)),
|
||||
unit: Set(Some(UNIT.choose(&mut rand::thread_rng()))),
|
||||
recipe_id: Set(recipe.id),
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.exec(&db)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
const INGREDIENTS: [&'static str; 69] = [
|
||||
"Almond Meal",
|
||||
"Almonds",
|
||||
"Amaranth",
|
||||
"Apples",
|
||||
"Apricots",
|
||||
"Avocados",
|
||||
"Bananas",
|
||||
"Barley",
|
||||
"Beef",
|
||||
"Beef Chuck",
|
||||
"Beef Ribs",
|
||||
"Beef Tenderloin",
|
||||
"Brisket",
|
||||
"Brown Rice",
|
||||
"Buckwheat",
|
||||
"Bulgur",
|
||||
"Cheese",
|
||||
"Cherries",
|
||||
"Chia Seeds",
|
||||
"Chicken",
|
||||
"Chicken Breasts",
|
||||
"Chicken Legs",
|
||||
"Chicken Thighs",
|
||||
"Chicken Wings",
|
||||
"Chocolate",
|
||||
"Coconut",
|
||||
"Corn Flour",
|
||||
"Cornish Hens",
|
||||
"Cornmeal",
|
||||
"Duck",
|
||||
"Fish",
|
||||
"Flax Seeds",
|
||||
"Goat",
|
||||
"Ground Beef",
|
||||
"Ground Chicken",
|
||||
"Ground Pork",
|
||||
"Ground Turkey",
|
||||
"Lamb",
|
||||
"Mangos",
|
||||
"Millet",
|
||||
"Mushroom",
|
||||
"Nectarines",
|
||||
"Oat Flour",
|
||||
"Oats",
|
||||
"Peaches",
|
||||
"Peanuts",
|
||||
"Pears",
|
||||
"Pineapples",
|
||||
"Plums",
|
||||
"Pomegranates",
|
||||
"Pork",
|
||||
"Pork Ribs",
|
||||
"Pork Shoulder",
|
||||
"Pork Tenderloin",
|
||||
"Prime Rib",
|
||||
"Quinoa",
|
||||
"Sausage",
|
||||
"Seafood",
|
||||
"Shellfish",
|
||||
"Sirloin",
|
||||
"Spelt",
|
||||
"Steak",
|
||||
"Tapioca Flour",
|
||||
"Turkey",
|
||||
"Veal",
|
||||
"Venison",
|
||||
"White Rice Flour",
|
||||
"Wild Game",
|
||||
"Wild Rice",
|
||||
];
|
||||
|
||||
const DISHES: [&'static str; 96] = [
|
||||
"Achari baingan",
|
||||
"Aloo gobi",
|
||||
"Aloo tikki",
|
||||
"Aloo tuk",
|
||||
"Aloo matar",
|
||||
"Aloo kulcha",
|
||||
"Aloo methi",
|
||||
"Aloo shimla mirch",
|
||||
"Amriti with rabdi",
|
||||
"Talit Macchi
|
||||
(Indian fish fry)",
|
||||
"Baati",
|
||||
"Bhatura",
|
||||
"Bhindi masala",
|
||||
"Biryani",
|
||||
"Butter chicken",
|
||||
"Chaat",
|
||||
"Chana masala",
|
||||
"Chapati",
|
||||
"Chicken razala",
|
||||
"Chicken Tikka",
|
||||
"Chicken Tikka masala",
|
||||
"Chole bhature",
|
||||
"Daal baati churma",
|
||||
"Daal puri",
|
||||
"Dal makhani (kali dal)",
|
||||
"Dal fara",
|
||||
"Dal",
|
||||
"Dal fry with tadka",
|
||||
"Dum aloo",
|
||||
"Poha",
|
||||
"Fara",
|
||||
"phirni",
|
||||
"Aloo Phalliyaan",
|
||||
"Gajar Pak[2]",
|
||||
"Gatte ki Sabzi",
|
||||
"Gajar matar aloo",
|
||||
"Gobhi matar",
|
||||
"Imarti",
|
||||
"Hari mutter ka nimona (green peas daal)",
|
||||
"Jalebi",
|
||||
"Jaleba",
|
||||
"Kachori",
|
||||
"Kadai paneer",
|
||||
"Kadhi pakoda",
|
||||
"Karela bharta",
|
||||
"Katha meetha petha / kaddu halwa",
|
||||
"Kheer",
|
||||
"Khichdi",
|
||||
"Kadhi and Khichdi",
|
||||
"Kofta",
|
||||
"Kulfi falooda",
|
||||
"Laapsi",
|
||||
"Lauki ke kofte",
|
||||
"Lauki ki bhaaji",
|
||||
"Litti chokha",
|
||||
"Makhaan ka kheer",
|
||||
"Makki ki roti, sarson ka saag",
|
||||
"Mathura ke pede",
|
||||
"Methi saag, chaulai saag",
|
||||
"Millet Lapsi",
|
||||
"Mirchi Bada",
|
||||
"Missi roti",
|
||||
"Mixed vegetable",
|
||||
"Moong dal ki Lapsi",
|
||||
"Murgh musallam",
|
||||
"Mushroom do pyaza (Kanda Khumb)",
|
||||
"Mushroom matar (Matar Khumb)",
|
||||
"Naan",
|
||||
"Navrattan korma",
|
||||
"Pakhala",
|
||||
"Palak paneer",
|
||||
"Paneer butter masala",
|
||||
"Paneer tikka masala",
|
||||
"Pani puri",
|
||||
"Panjeeri",
|
||||
"Papad",
|
||||
"Paratha",
|
||||
"Pattor",
|
||||
"Phirni",
|
||||
"Pinni",
|
||||
"Rajma chaval",
|
||||
"Rajma",
|
||||
"Ramatori bhaaji",
|
||||
"Lobiya",
|
||||
"Samosa",
|
||||
"Samose",
|
||||
"Sattu ki roti",
|
||||
"Rajwadi Chhena/Paneer[4]",
|
||||
"Shahi tukra",
|
||||
"Singhada Lapsi",
|
||||
"Sooji halwa (Suji Lapsi)",
|
||||
"Sweet pethas / kesar petha / pista petha",
|
||||
"Vegetable jalfrezi",
|
||||
"Tandoori Chicken",
|
||||
"Tamatar Chaat",
|
||||
"Tandoori Fish Tikka",
|
||||
];
|
1
seed/target/.rustc_info.json
Normal file
1
seed/target/.rustc_info.json
Normal file
@ -0,0 +1 @@
|
||||
{"rustc_fingerprint":5966236372496131995,"outputs":{"4614504638168534921":{"success":true,"status":"","code":0,"stdout":"rustc 1.84.0-nightly (3ed6e3cc6 2024-10-17)\nbinary: rustc\ncommit-hash: 3ed6e3cc69857129c1d314daec00119ff47986ed\ncommit-date: 2024-10-17\nhost: x86_64-unknown-linux-gnu\nrelease: 1.84.0-nightly\nLLVM version: 19.1.1\n","stderr":""},"14371922958718593042":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/eraden/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\nfmt_debug=\"full\"\noverflow_checks\npanic=\"unwind\"\nproc_macro\nrelocation_model=\"pic\"\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"unknown\"\nub_checks\nunix\n","stderr":""},"15729799797837862367":{"success":true,"status":"","code":0,"stdout":"___\nlib___.rlib\nlib___.so\nlib___.so\nlib___.a\nlib___.so\n/home/eraden/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu\noff\npacked\nunpacked\n___\ndebug_assertions\nfmt_debug=\"full\"\noverflow_checks\npanic=\"unwind\"\nproc_macro\nrelocation_model=\"pic\"\ntarget_abi=\"\"\ntarget_arch=\"x86_64\"\ntarget_endian=\"little\"\ntarget_env=\"gnu\"\ntarget_family=\"unix\"\ntarget_feature=\"fxsr\"\ntarget_feature=\"sse\"\ntarget_feature=\"sse2\"\ntarget_has_atomic\ntarget_has_atomic=\"16\"\ntarget_has_atomic=\"32\"\ntarget_has_atomic=\"64\"\ntarget_has_atomic=\"8\"\ntarget_has_atomic=\"ptr\"\ntarget_has_atomic_equal_alignment=\"16\"\ntarget_has_atomic_equal_alignment=\"32\"\ntarget_has_atomic_equal_alignment=\"64\"\ntarget_has_atomic_equal_alignment=\"8\"\ntarget_has_atomic_equal_alignment=\"ptr\"\ntarget_has_atomic_load_store\ntarget_has_atomic_load_store=\"16\"\ntarget_has_atomic_load_store=\"32\"\ntarget_has_atomic_load_store=\"64\"\ntarget_has_atomic_load_store=\"8\"\ntarget_has_atomic_load_store=\"ptr\"\ntarget_os=\"linux\"\ntarget_pointer_width=\"64\"\ntarget_thread_local\ntarget_vendor=\"unknown\"\nub_checks\nunix\n","stderr":""}},"successes":{}}
|
3
seed/target/CACHEDIR.TAG
Normal file
3
seed/target/CACHEDIR.TAG
Normal file
@ -0,0 +1,3 @@
|
||||
Signature: 8a477f597d28d172789f06886806bc55
|
||||
# This file is a cache directory tag created by cargo.
|
||||
# For information about cache directory tags see https://bford.info/cachedir/
|
52
src/types.rs
52
src/types.rs
@ -1,52 +0,0 @@
|
||||
use std::str::FromStr;
|
||||
|
||||
use serde::Deserialize;
|
||||
|
||||
pub type User = String;
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum Error {
|
||||
#[error("Invalid step list")]
|
||||
InvalidStepList,
|
||||
#[error("Invalid ingeredients list")]
|
||||
InvalidIngeredientList,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Clone, Copy, Deserialize)]
|
||||
pub enum Page {
|
||||
Index,
|
||||
Recipe,
|
||||
Search,
|
||||
SignIn,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct Admin {
|
||||
pub email: String,
|
||||
pub pass: String,
|
||||
}
|
||||
|
||||
impl FromStr for Admin {
|
||||
type Err = ();
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let mut it = s.split(':');
|
||||
|
||||
Ok(Self {
|
||||
email: it.next().expect("Admin login is required").into(),
|
||||
pass: it.next().expect("Admin password is required").into(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, derive_more::Deref)]
|
||||
pub struct Admins(Vec<Admin>);
|
||||
|
||||
impl FromStr for Admins {
|
||||
type Err = ();
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Self(
|
||||
s.trim().split(',').filter_map(|s| s.parse().ok()).collect(),
|
||||
))
|
||||
}
|
||||
}
|
@ -12,9 +12,13 @@
|
||||
<h1 class="font-young-serif text-desktop-heading-l text-stone-700 sm:text-stone-800 text-center md:text-left">
|
||||
{{ recipe.title }}
|
||||
</h1>
|
||||
{% match recipe.summary %}
|
||||
{% when Some with (summary) %}
|
||||
<p class="font-outfit-regular text-stone-500 sm:text-base">
|
||||
{{ recipe.summary.clone().unwrap_or_default() }}
|
||||
{{ summary }}
|
||||
</p>
|
||||
{% when None %}
|
||||
{% endmatch %}
|
||||
</div>
|
||||
<div class="divider"></div>
|
||||
<div class="flex flex-col gap-7">
|
||||
@ -22,16 +26,16 @@
|
||||
Ingredients
|
||||
</p>
|
||||
<ul class="list-disc marker:text-rose-900 list-inside flex flex-col gap-3">
|
||||
{% for ingeredient in ingeredients %}
|
||||
{% for ingredient in ingredients %}
|
||||
<li class="paragraph">
|
||||
<span class="mr-1">
|
||||
{{ ingeredient.qty }}
|
||||
{{ ingredient.qty }}
|
||||
</span>
|
||||
<span class="mr-4">
|
||||
{{ ingeredient.unit }}
|
||||
{{ ingredient.unit }}
|
||||
</span>
|
||||
<span>
|
||||
{{ ingeredient.name }}
|
||||
{{ ingredient.name }}
|
||||
</span>
|
||||
</li>
|
||||
{% endfor %}
|
||||
@ -44,7 +48,7 @@
|
||||
</p>
|
||||
<ol class="list-decimal list-inside flex flex-col gap-3">
|
||||
{% for step in steps %}
|
||||
<li class="font-outfit-regular text-stone-500 text-base text-base">
|
||||
<li class="font-outfit-regular text-stone-500 text-base">
|
||||
{{ step.body }}
|
||||
<div>{{ step.hint.clone().unwrap_or_default() }}</div>
|
||||
</li>
|
||||
@ -56,7 +60,7 @@
|
||||
<p class="text-desktop-heading-m">
|
||||
Tags
|
||||
</p>
|
||||
<div class="flex gap-4 gap-7">
|
||||
<div class="flex gap-7">
|
||||
{% for tag in tags %}
|
||||
<a class="bg-gray-300 dark:bg-gray-700 text-base text-gray-700 dark:text-white py-2 px-4 rounded-full font-bold hover:bg-gray-400 dark:hover:bg-gray-600 flex justify-center items-center">
|
||||
{{ tag.name }}
|
||||
@ -69,10 +73,10 @@
|
||||
<p class="text-desktop-heading-m">
|
||||
Ingredients
|
||||
</p>
|
||||
<div class="flex gap-4 gap-7">
|
||||
{% for ingeredient in ingeredients %}
|
||||
<div class="flex gap-7">
|
||||
{% for ingredient in ingredients %}
|
||||
<a class="bg-gray-300 dark:bg-gray-700 text-base text-gray-700 dark:text-white py-2 px-4 rounded-full font-bold hover:bg-gray-400 dark:hover:bg-gray-600 flex justify-center items-center">
|
||||
{{ ingeredient.name }}
|
||||
{{ ingredient.name }}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
Loading…
Reference in New Issue
Block a user