Recipe edit (WIP): all form fields are now saved
This commit is contained in:
parent
07b7ff425e
commit
6876a254e1
12 changed files with 563 additions and 210 deletions
|
|
@ -53,7 +53,7 @@ body {
|
|||
font-family: Fira Code, Helvetica Neue, Helvetica, Arial, sans-serif;
|
||||
text-shadow: 2px 2px 2px rgb(0, 0, 0);
|
||||
// line-height: 18px;
|
||||
color: rgb(255, 255, 255);
|
||||
color: lighten($primary, 60%);
|
||||
background-color: $background;
|
||||
margin: 0px;
|
||||
|
||||
|
|
@ -63,7 +63,7 @@ body {
|
|||
|
||||
.recipe-item-current {
|
||||
padding: 3px;
|
||||
border: 1px solid white;
|
||||
border: 1px solid lighten($primary, 30%);
|
||||
}
|
||||
|
||||
.header-container {
|
||||
|
|
@ -87,7 +87,7 @@ body {
|
|||
flex-grow: 1;
|
||||
|
||||
background-color: $background-container;
|
||||
border: 0.1em solid white;
|
||||
border: 0.1em solid lighten($primary, 50%);
|
||||
padding: 0.5em;
|
||||
|
||||
h1 {
|
||||
|
|
@ -95,15 +95,15 @@ body {
|
|||
}
|
||||
|
||||
.group {
|
||||
border: 0.1em solid white;
|
||||
border: 0.1em solid lighten($primary, 30%);
|
||||
}
|
||||
|
||||
.step {
|
||||
border: 0.1em solid white;
|
||||
border: 0.1em solid lighten($primary, 30%);
|
||||
}
|
||||
|
||||
.ingredient {
|
||||
border: 0.1em solid white;
|
||||
border: 0.1em solid lighten($primary, 30%);
|
||||
}
|
||||
|
||||
#hidden-templates {
|
||||
|
|
|
|||
|
|
@ -49,7 +49,8 @@ ORDER BY [title]
|
|||
sqlx::query_scalar(
|
||||
r#"
|
||||
SELECT COUNT(*)
|
||||
FROM [Recipe] INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
||||
FROM [Recipe]
|
||||
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
||||
WHERE [Group].[id] = $1 AND [user_id] = $2
|
||||
"#,
|
||||
)
|
||||
|
|
@ -60,6 +61,45 @@ WHERE [Group].[id] = $1 AND [user_id] = $2
|
|||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn can_edit_recipe_step(&self, user_id: i64, step_id: i64) -> Result<bool> {
|
||||
sqlx::query_scalar(
|
||||
r#"
|
||||
SELECT COUNT(*)
|
||||
FROM [Recipe]
|
||||
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
||||
INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
|
||||
WHERE [Step].[id] = $1 AND [user_id] = $2
|
||||
"#,
|
||||
)
|
||||
.bind(step_id)
|
||||
.bind(user_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn can_edit_recipe_ingredient(
|
||||
&self,
|
||||
user_id: i64,
|
||||
ingredient_id: i64,
|
||||
) -> Result<bool> {
|
||||
sqlx::query_scalar(
|
||||
r#"
|
||||
SELECT COUNT(*)
|
||||
FROM [Recipe]
|
||||
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
||||
INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
|
||||
INNER JOIN [Ingredient] ON [Ingredient].[step_id] = [Step].[id]
|
||||
WHERE [Ingredient].[id] = $1 AND [user_id] = $2
|
||||
"#,
|
||||
)
|
||||
.bind(ingredient_id)
|
||||
.bind(user_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn get_recipe(&self, id: i64) -> Result<Option<model::Recipe>> {
|
||||
sqlx::query_as(
|
||||
r#"
|
||||
|
|
@ -263,6 +303,60 @@ ORDER BY [name]
|
|||
.map(|_| ())
|
||||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn set_step_action(&self, step_id: i64, action: &str) -> Result<()> {
|
||||
sqlx::query("UPDATE [Step] SET [action] = $2 WHERE [id] = $1")
|
||||
.bind(step_id)
|
||||
.bind(action)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn set_ingredient_name(&self, ingredient_id: i64, name: &str) -> Result<()> {
|
||||
sqlx::query("UPDATE [Ingredient] SET [name] = $2 WHERE [id] = $1")
|
||||
.bind(ingredient_id)
|
||||
.bind(name)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn set_ingredient_comment(&self, ingredient_id: i64, comment: &str) -> Result<()> {
|
||||
sqlx::query("UPDATE [Ingredient] SET [comment] = $2 WHERE [id] = $1")
|
||||
.bind(ingredient_id)
|
||||
.bind(comment)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn set_ingredient_quantity(
|
||||
&self,
|
||||
ingredient_id: i64,
|
||||
quantity: Option<f64>,
|
||||
) -> Result<()> {
|
||||
sqlx::query("UPDATE [Ingredient] SET [quantity_value] = $2 WHERE [id] = $1")
|
||||
.bind(ingredient_id)
|
||||
.bind(quantity)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn set_ingredient_unit(&self, ingredient_id: i64, unit: &str) -> Result<()> {
|
||||
sqlx::query("UPDATE [Ingredient] SET [quantity_unit] = $2 WHERE [id] = $1")
|
||||
.bind(ingredient_id)
|
||||
.bind(unit)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(DBError::from)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
|
|||
|
|
@ -61,14 +61,14 @@ const TRACING_LEVEL: tracing::Level = tracing::Level::INFO;
|
|||
// TODO: Should main returns 'Result'?
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
if process_args().await {
|
||||
return;
|
||||
}
|
||||
|
||||
tracing_subscriber::fmt()
|
||||
.with_max_level(TRACING_LEVEL)
|
||||
.init();
|
||||
|
||||
if process_args().await {
|
||||
return;
|
||||
}
|
||||
|
||||
event!(Level::INFO, "Starting Recipes as web server...");
|
||||
|
||||
let config = config::load();
|
||||
|
|
@ -109,6 +109,26 @@ async fn main() {
|
|||
"/recipe/set_group_comment",
|
||||
put(services::ron::set_group_comment),
|
||||
)
|
||||
.route(
|
||||
"/recipe/set_step_action",
|
||||
put(services::ron::set_step_action),
|
||||
)
|
||||
.route(
|
||||
"/recipe/set_ingredient_name",
|
||||
put(services::ron::set_ingredient_name),
|
||||
)
|
||||
.route(
|
||||
"/recipe/set_ingredient_comment",
|
||||
put(services::ron::set_ingredient_comment),
|
||||
)
|
||||
.route(
|
||||
"/recipe/set_ingredient_quantity",
|
||||
put(services::ron::set_ingredient_quantity),
|
||||
)
|
||||
.route(
|
||||
"/recipe/set_ingredient_unit",
|
||||
put(services::ron::set_ingredient_unit),
|
||||
)
|
||||
.fallback(services::ron::not_found);
|
||||
|
||||
let fragments_routes = Router::new().route(
|
||||
|
|
|
|||
|
|
@ -125,6 +125,44 @@ async fn check_user_rights_recipe_group(
|
|||
}
|
||||
}
|
||||
|
||||
async fn check_user_rights_recipe_step(
|
||||
connection: &db::Connection,
|
||||
user: &Option<model::User>,
|
||||
step_id: i64,
|
||||
) -> Result<()> {
|
||||
if user.is_none()
|
||||
|| !connection
|
||||
.can_edit_recipe_step(user.as_ref().unwrap().id, step_id)
|
||||
.await?
|
||||
{
|
||||
Err(ErrorResponse::from(ron_error(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Action not authorized",
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
async fn check_user_rights_recipe_ingredient(
|
||||
connection: &db::Connection,
|
||||
user: &Option<model::User>,
|
||||
ingredient_id: i64,
|
||||
) -> Result<()> {
|
||||
if user.is_none()
|
||||
|| !connection
|
||||
.can_edit_recipe_ingredient(user.as_ref().unwrap().id, ingredient_id)
|
||||
.await?
|
||||
{
|
||||
Err(ErrorResponse::from(ron_error(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Action not authorized",
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn set_recipe_title(
|
||||
State(connection): State<db::Connection>,
|
||||
|
|
@ -255,7 +293,6 @@ pub async fn get_groups(
|
|||
State(connection): State<db::Connection>,
|
||||
recipe_id: Query<RecipeId>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
println!("PROUT");
|
||||
// Here we don't check user rights on purpose.
|
||||
Ok(ron_response(
|
||||
StatusCode::OK,
|
||||
|
|
@ -318,6 +355,69 @@ pub async fn set_group_comment(
|
|||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn set_step_action(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetStepAction>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_step(&connection, &user, ron.step_id).await?;
|
||||
connection.set_step_action(ron.step_id, &ron.action).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn set_ingredient_name(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetIngredientName>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
|
||||
connection
|
||||
.set_ingredient_name(ron.ingredient_id, &ron.name)
|
||||
.await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn set_ingredient_comment(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetIngredientComment>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
|
||||
connection
|
||||
.set_ingredient_comment(ron.ingredient_id, &ron.comment)
|
||||
.await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn set_ingredient_quantity(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetIngredientQuantity>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
|
||||
connection
|
||||
.set_ingredient_quantity(ron.ingredient_id, ron.quantity)
|
||||
.await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn set_ingredient_unit(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetIngredientUnit>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_ingredient(&connection, &user, ron.ingredient_id).await?;
|
||||
connection
|
||||
.set_ingredient_unit(ron.ingredient_id, &ron.unit)
|
||||
.await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
///// 404 /////
|
||||
#[debug_handler]
|
||||
pub async fn not_found(Extension(_user): Extension<Option<model::User>>) -> impl IntoResponse {
|
||||
|
|
|
|||
|
|
@ -20,6 +20,6 @@
|
|||
|
||||
{% block body_container %}{% endblock %}
|
||||
|
||||
<footer class="footer-container">gburri - 2022</footer>
|
||||
<footer class="footer-container">gburri - 2025</footer>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -20,10 +20,11 @@
|
|||
<textarea
|
||||
id="text-area-description">{{ recipe.description }}</textarea>
|
||||
|
||||
<label for="input-estimated-time">Estimated time</label>
|
||||
<label for="input-estimated-time">Estimated time [min]</label>
|
||||
<input
|
||||
id="input-estimated-time"
|
||||
type="number"
|
||||
step="1" min="0" max="1000"
|
||||
value="
|
||||
{% match recipe.estimated_time %}
|
||||
{% when Some with (t) %}
|
||||
|
|
@ -63,7 +64,7 @@
|
|||
<div id="groups-container">
|
||||
|
||||
</div>
|
||||
<input id="button-add-group" type="button" value="Add a group"/>
|
||||
<input id="button-add-group" type="button" value="Add a group" />
|
||||
|
||||
<div id="hidden-templates">
|
||||
<div class="group">
|
||||
|
|
@ -73,15 +74,19 @@
|
|||
<label for="input-group-comment">Comment</label>
|
||||
<input class="input-group-comment" type="text" />
|
||||
|
||||
<input class="input-group-delete" type="button" value="Remove group" />
|
||||
|
||||
<div class="steps"></div>
|
||||
|
||||
<input class="button-add-step" type="button" value="Add a step"/>
|
||||
<input class="button-add-step" type="button" value="Add a step" />
|
||||
</div>
|
||||
|
||||
<div class="step">
|
||||
<label for="text-area-step-action">Action</label>
|
||||
<textarea class="text-area-step-action"></textarea>
|
||||
|
||||
<input class="input-step-delete" type="button" value="Remove step" />
|
||||
|
||||
<div class="ingredients"></div>
|
||||
|
||||
<input class="button-add-ingedient" type="button" value="Add an ingredient"/>
|
||||
|
|
@ -89,13 +94,18 @@
|
|||
|
||||
<div class="ingredient">
|
||||
<label for="input-ingredient-quantity">Quantity</label>
|
||||
<input class="input-ingredient-quantity" type="number" />
|
||||
<input class="input-ingredient-quantity" type="number" step="0.1" min="0" max="10000" />
|
||||
|
||||
<label for="input-ingredient-unit">Unity</label>
|
||||
<label for="input-ingredient-unit">Unit</label>
|
||||
<input class="input-ingredient-unit" type="text" />
|
||||
|
||||
<label for="input-ingredient-name">Name</label>
|
||||
<input class="input-ingredient-name" type="text" />
|
||||
|
||||
<label for="input-ingredient-comment">Comment</label>
|
||||
<input class="input-ingredient-comment" type="text" />
|
||||
|
||||
<input class="input-ingredient-delete" type="button" value="Remove ingredient" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue