Render recipes
This commit is contained in:
parent
55dd953f43
commit
1aecaf9286
4 changed files with 217 additions and 31 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -2953,6 +2953,7 @@ dependencies = [
|
|||
name = "regalade"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"ammonia",
|
||||
"anyhow",
|
||||
"api",
|
||||
"axum",
|
||||
|
|
@ -2963,6 +2964,7 @@ dependencies = [
|
|||
"migration",
|
||||
"openidconnect",
|
||||
"parking_lot",
|
||||
"pulldown-cmark",
|
||||
"sea-orm",
|
||||
"sea-query",
|
||||
"serde",
|
||||
|
|
|
|||
|
|
@ -36,6 +36,8 @@ time = "0.3.31"
|
|||
maud = { git = "https://github.com/lambda-fairy/maud", version = "0.25.0", features = [
|
||||
"axum",
|
||||
] }
|
||||
pulldown-cmark = "0.9.3"
|
||||
ammonia = "3.3.0"
|
||||
|
||||
[dependencies.sea-orm]
|
||||
version = "0.12"
|
||||
|
|
|
|||
|
|
@ -116,6 +116,8 @@ enum RouteError {
|
|||
Session(#[from] session::Error),
|
||||
#[error("Could not extract session")]
|
||||
SessionExtract,
|
||||
#[error("Unexpected internal error")]
|
||||
Internal(String),
|
||||
}
|
||||
|
||||
impl From<DbErr> for Box<RouteError> {
|
||||
|
|
|
|||
|
|
@ -1,17 +1,29 @@
|
|||
use axum::{extract::State, routing::get, Router};
|
||||
use maud::{html, Markup};
|
||||
use sea_orm::prelude::*;
|
||||
use std::borrow::Cow;
|
||||
|
||||
use crate::entity::prelude::*;
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
use maud::{html, Markup, PreEscaped};
|
||||
use pulldown_cmark::Parser;
|
||||
use sea_orm::{prelude::*, QueryOrder};
|
||||
|
||||
use crate::entity::{prelude::*, recipe};
|
||||
|
||||
use super::{
|
||||
base_page,
|
||||
household::CurrentHousehold,
|
||||
sidebar::{sidebar, SidebarLocation},
|
||||
AppState, AuthenticatedUser, RouteError,
|
||||
};
|
||||
|
||||
pub(super) fn routes() -> Router<AppState> {
|
||||
Router::new().route("/", get(list_recipes))
|
||||
Router::new()
|
||||
.route("/", get(list_recipes))
|
||||
.route("/:id", get(view_recipe))
|
||||
.route("/public/:hs/:id", get(view_public_recipe))
|
||||
.route("/public/:hs", get(list_public_recipe))
|
||||
}
|
||||
|
||||
fn recipe_rating(rating: i32) -> Markup {
|
||||
|
|
@ -24,15 +36,14 @@ fn recipe_rating(rating: i32) -> Markup {
|
|||
}
|
||||
}
|
||||
|
||||
async fn list_recipes(
|
||||
state: State<AppState>,
|
||||
user: AuthenticatedUser,
|
||||
household: CurrentHousehold,
|
||||
) -> Result<Markup, RouteError> {
|
||||
let mut recipes = household.0.find_related(Recipe).all(&state.db).await?;
|
||||
recipes.sort_unstable_by(|m1, m2| m1.name.cmp(&m2.name));
|
||||
/// If household is None then the function acts on the private household
|
||||
fn recipe_list(recipes: &[recipe::Model], household: Option<Uuid>) -> Markup {
|
||||
let destination = match household {
|
||||
Some(id) => Cow::from(format!("/recipe/public/{id}/")),
|
||||
None => Cow::from("/recipe/"),
|
||||
};
|
||||
|
||||
let content = html! {
|
||||
html! {
|
||||
.d-flex.align-items-center.justify-content-center."w-100" {
|
||||
.container.text-center.rounded.border."pt-2"."m-2" {
|
||||
h2 { "Recipes" }
|
||||
|
|
@ -42,9 +53,8 @@ async fn list_recipes(
|
|||
.col {
|
||||
."p-3".border.rounded.border-light-subtle."h-100" {
|
||||
a .link-light."link-offset-2"."link-underline-opacity-25"."link-underline-opacity-100-hover"
|
||||
href={"/recipe/" (r.id)}
|
||||
{
|
||||
(r.name) (recipe_rating(r.ranking))
|
||||
href={(destination) (r.id)} {
|
||||
(r.name) @if household.is_none() { (recipe_rating(r.ranking)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -53,12 +63,182 @@ async fn list_recipes(
|
|||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async fn list_recipes(
|
||||
state: State<AppState>,
|
||||
user: AuthenticatedUser,
|
||||
household: CurrentHousehold,
|
||||
) -> Result<Markup, RouteError> {
|
||||
let recipes = household
|
||||
.0
|
||||
.find_related(Recipe)
|
||||
.order_by_asc(recipe::Column::Name)
|
||||
.all(&state.db)
|
||||
.await?;
|
||||
|
||||
Ok(sidebar(
|
||||
SidebarLocation::RecipeList,
|
||||
&household,
|
||||
&user,
|
||||
content,
|
||||
recipe_list(&recipes, None),
|
||||
))
|
||||
}
|
||||
|
||||
async fn list_public_recipe(
|
||||
state: State<AppState>,
|
||||
household: Path<Uuid>,
|
||||
) -> Result<Markup, RouteError> {
|
||||
let household = Household::find_by_id(household.0)
|
||||
.one(&state.db)
|
||||
.await?
|
||||
.ok_or(RouteError::RessourceNotFound)?;
|
||||
|
||||
let recipes = household
|
||||
.find_related(Recipe)
|
||||
.order_by_asc(recipe::Column::Name)
|
||||
.all(&state.db)
|
||||
.await?;
|
||||
|
||||
Ok(base_page(recipe_list(&recipes, Some(household.id))))
|
||||
}
|
||||
|
||||
async fn recipe_view(
|
||||
r: &recipe::Model,
|
||||
private: bool,
|
||||
db: &DatabaseConnection,
|
||||
) -> Result<Markup, RouteError> {
|
||||
let base_ingredients = r.find_related(Ingredient).all(db).await?;
|
||||
|
||||
let mut ingredients = Vec::with_capacity(base_ingredients.len());
|
||||
for ig in base_ingredients {
|
||||
ingredients.push((
|
||||
RecipeIngredients::find_by_id((r.id, ig.id))
|
||||
.one(db)
|
||||
.await?
|
||||
.ok_or_else(|| {
|
||||
RouteError::Internal(format!(
|
||||
"No recipe ingredient found for rcp={}/ig={}",
|
||||
r.id, ig.id
|
||||
))
|
||||
})?
|
||||
.amount,
|
||||
ig,
|
||||
))
|
||||
}
|
||||
|
||||
let mut script = "const base_amount = [".to_string();
|
||||
for (amount, ig) in &ingredients {
|
||||
script += &format!(
|
||||
r#"{{amount: {}, elem: document.querySelector('#rcpIg{} > span > .ig-amount')}},"#,
|
||||
amount, ig.id
|
||||
)
|
||||
}
|
||||
script += r#"];
|
||||
const chg_person = document.querySelector('#rcpPersonCount');
|
||||
chg_person.addEventListener("change", (event) => {
|
||||
base_amount.forEach(({amount, elem}) => {
|
||||
elem.textContent = amount * event.target.value;
|
||||
})
|
||||
});
|
||||
"#;
|
||||
|
||||
let steps = {
|
||||
let parser = Parser::new(&r.steps);
|
||||
|
||||
let mut html_output = String::new();
|
||||
pulldown_cmark::html::push_html(&mut html_output, parser);
|
||||
|
||||
ammonia::clean(&html_output)
|
||||
};
|
||||
|
||||
Ok(html! {
|
||||
.d-flex.align-items-center.justify-content-center."w-100" {
|
||||
.container.text-center.rounded.border."pt-2"."m-2" {
|
||||
h1 { (r.name) @if private { (recipe_rating(r.ranking)) } }
|
||||
@if private { ."mt-2" { "TODO: edit" } }
|
||||
."mt-2".container.text-start {
|
||||
.row {
|
||||
."col-8"[private] {
|
||||
.input-group {
|
||||
input .form-control type="number" #rcpPersonCount min="1" value=(r.person_count) {}
|
||||
span .input-group-text { "people" }
|
||||
}
|
||||
}
|
||||
@if private { .col {"TODO: edit"} }
|
||||
}
|
||||
}
|
||||
hr {}
|
||||
.text-start {
|
||||
h2 { "Ingredients" }
|
||||
ul .list-group."mb-2" {
|
||||
@for (amount, ig) in &ingredients {
|
||||
li .list-group-item.d-flex.justify-content-between.align-items-center #{"rcpIg" (ig.id)} {
|
||||
span {
|
||||
span .ig-amount { ((*amount * r.person_count as f64).round()) }
|
||||
@if let Some(u) = &ig.unit { (u) } " " (ig.name)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@if private { "TODO: add ingredient" }
|
||||
}
|
||||
hr {}
|
||||
.text-start {
|
||||
h2 {"Steps"}
|
||||
div { (PreEscaped(steps)) }
|
||||
@ if private { "TODO: edit steps" }
|
||||
}
|
||||
}
|
||||
}
|
||||
script { (PreEscaped(script)) }
|
||||
})
|
||||
}
|
||||
|
||||
async fn view_recipe(
|
||||
state: State<AppState>,
|
||||
user: AuthenticatedUser,
|
||||
household: CurrentHousehold,
|
||||
id: Path<i32>,
|
||||
) -> Result<Markup, RouteError> {
|
||||
let recipe = household
|
||||
.0
|
||||
.find_related(Recipe)
|
||||
.filter(recipe::Column::Id.eq(id.0))
|
||||
.one(&state.db)
|
||||
.await?
|
||||
.ok_or(RouteError::RessourceNotFound)?;
|
||||
|
||||
Ok(sidebar(
|
||||
SidebarLocation::RecipeList,
|
||||
&household,
|
||||
&user,
|
||||
recipe_view(&recipe, true, &state.db).await?,
|
||||
))
|
||||
}
|
||||
|
||||
#[derive(serde::Deserialize)]
|
||||
struct PublicQuery {
|
||||
hs: Uuid,
|
||||
id: i32,
|
||||
}
|
||||
|
||||
async fn view_public_recipe(
|
||||
state: State<AppState>,
|
||||
info: Path<PublicQuery>,
|
||||
) -> Result<Markup, RouteError> {
|
||||
let household = Household::find_by_id(info.0.hs)
|
||||
.one(&state.db)
|
||||
.await?
|
||||
.ok_or(RouteError::RessourceNotFound)?;
|
||||
|
||||
let recipe = household
|
||||
.find_related(Recipe)
|
||||
.filter(recipe::Column::Id.eq(info.0.id))
|
||||
.one(&state.db)
|
||||
.await?
|
||||
.ok_or(RouteError::RessourceNotFound)?;
|
||||
|
||||
Ok(base_page(recipe_view(&recipe, false, &state.db).await?))
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue