Add a way to delete recipe
This commit is contained in:
parent
5ce3391466
commit
31bc31035a
10 changed files with 247 additions and 175 deletions
|
|
@ -19,6 +19,6 @@ pub const SEND_EMAIL_TIMEOUT: Duration = Duration::from_secs(60);
|
|||
// Common headers can be found in 'axum::http::header' (which is a re-export of the create 'http').
|
||||
pub const REVERSE_PROXY_IP_HTTP_FIELD: &str = "x-real-ip"; // Set by the reverse proxy (Nginx).
|
||||
|
||||
pub const MAX_DB_CONNECTION: u32 = 1024;
|
||||
pub const MAX_DB_CONNECTION: u32 = 1; // To avoid database lock.
|
||||
|
||||
pub const LANGUAGES: [(&str, &str); 2] = [("Français", "fr"), ("English", "en")];
|
||||
|
|
|
|||
|
|
@ -4,10 +4,11 @@ use std::{
|
|||
io::Read,
|
||||
path::Path,
|
||||
str::FromStr,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use sqlx::{
|
||||
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions},
|
||||
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous},
|
||||
Pool, Sqlite, Transaction,
|
||||
};
|
||||
use thiserror::Error;
|
||||
|
|
@ -75,8 +76,9 @@ impl Connection {
|
|||
))?
|
||||
.journal_mode(SqliteJournalMode::Wal) // TODO: use 'Wal2' when available.
|
||||
.create_if_missing(true)
|
||||
.pragma("foreign_keys", "ON")
|
||||
.pragma("synchronous", "NORMAL");
|
||||
.busy_timeout(Duration::from_secs(10))
|
||||
.foreign_keys(true)
|
||||
.synchronous(SqliteSynchronous::Normal);
|
||||
|
||||
Self::create_connection(
|
||||
SqlitePoolOptions::new()
|
||||
|
|
|
|||
|
|
@ -37,18 +37,20 @@ ORDER BY [title]
|
|||
}
|
||||
|
||||
pub async fn can_edit_recipe(&self, user_id: i64, recipe_id: i64) -> Result<bool> {
|
||||
sqlx::query_scalar(r#"SELECT COUNT(*) FROM [Recipe] WHERE [id] = $1 AND [user_id] = $2"#)
|
||||
.bind(recipe_id)
|
||||
.bind(user_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(DBError::from)
|
||||
sqlx::query_scalar(
|
||||
r#"SELECT COUNT(*) = 1 FROM [Recipe] WHERE [id] = $1 AND [user_id] = $2"#,
|
||||
)
|
||||
.bind(recipe_id)
|
||||
.bind(user_id)
|
||||
.fetch_one(&self.pool)
|
||||
.await
|
||||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn can_edit_recipe_group(&self, user_id: i64, group_id: i64) -> Result<bool> {
|
||||
sqlx::query_scalar(
|
||||
r#"
|
||||
SELECT COUNT(*)
|
||||
SELECT COUNT(*) = 1
|
||||
FROM [Recipe]
|
||||
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
||||
WHERE [Group].[id] = $1 AND [user_id] = $2
|
||||
|
|
@ -64,7 +66,7 @@ WHERE [Group].[id] = $1 AND [user_id] = $2
|
|||
pub async fn can_edit_recipe_step(&self, user_id: i64, step_id: i64) -> Result<bool> {
|
||||
sqlx::query_scalar(
|
||||
r#"
|
||||
SELECT COUNT(*)
|
||||
SELECT COUNT(*) = 1
|
||||
FROM [Recipe]
|
||||
INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
|
||||
INNER JOIN [Step] ON [Step].[group_id] = [Group].[id]
|
||||
|
|
@ -171,6 +173,16 @@ WHERE [Recipe].[user_id] = $1
|
|||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn set_recipe_servings(&self, recipe_id: i64, servings: Option<u32>) -> Result<()> {
|
||||
sqlx::query("UPDATE [Recipe] SET [servings] = $2 WHERE [id] = $1")
|
||||
.bind(recipe_id)
|
||||
.bind(servings)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn set_recipe_estimated_time(
|
||||
&self,
|
||||
recipe_id: i64,
|
||||
|
|
@ -222,6 +234,15 @@ WHERE [Recipe].[user_id] = $1
|
|||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn rm_recipe(&self, recipe_id: i64) -> Result<()> {
|
||||
sqlx::query("DELETE FROM [Recipe] WHERE [id] = $1")
|
||||
.bind(recipe_id)
|
||||
.execute(&self.pool)
|
||||
.await
|
||||
.map(|_| ())
|
||||
.map_err(DBError::from)
|
||||
}
|
||||
|
||||
pub async fn get_groups(&self, recipe_id: i64) -> Result<Vec<model::Group>> {
|
||||
let mut tx = self.tx().await?;
|
||||
let mut groups: Vec<model::Group> = sqlx::query_as(
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ pub struct Recipe {
|
|||
#[sqlx(try_from = "u32")]
|
||||
pub difficulty: Difficulty,
|
||||
|
||||
pub servings: u32,
|
||||
pub servings: Option<u32>,
|
||||
pub is_published: bool,
|
||||
// pub tags: Vec<String>,
|
||||
// pub groups: Vec<Group>,
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ async fn main() {
|
|||
"/recipe/set_description",
|
||||
put(services::ron::set_recipe_description),
|
||||
)
|
||||
.route("/recipe/set_servings", put(services::ron::set_servings))
|
||||
.route(
|
||||
"/recipe/set_estimated_time",
|
||||
put(services::ron::set_estimated_time),
|
||||
|
|
@ -101,6 +102,7 @@ async fn main() {
|
|||
"/recipe/set_is_published",
|
||||
put(services::ron::set_is_published),
|
||||
)
|
||||
.route("/recipe/remove", delete(services::ron::rm))
|
||||
.route("/recipe/get_groups", get(services::ron::get_groups))
|
||||
.route("/recipe/add_group", post(services::ron::add_group))
|
||||
.route("/recipe/remove_group", delete(services::ron::rm_group))
|
||||
|
|
|
|||
|
|
@ -33,25 +33,28 @@ pub async fn edit_recipe(
|
|||
Path(recipe_id): Path<i64>,
|
||||
) -> Result<Response> {
|
||||
if let Some(user) = user {
|
||||
let recipe = connection.get_recipe(recipe_id).await?.unwrap();
|
||||
if recipe.user_id == user.id {
|
||||
let recipes = Recipes {
|
||||
published: connection.get_all_published_recipe_titles().await?,
|
||||
unpublished: connection
|
||||
.get_all_unpublished_recipe_titles(user.id)
|
||||
.await?,
|
||||
current_id: Some(recipe_id),
|
||||
};
|
||||
if let Some(recipe) = connection.get_recipe(recipe_id).await? {
|
||||
if recipe.user_id == user.id {
|
||||
let recipes = Recipes {
|
||||
published: connection.get_all_published_recipe_titles().await?,
|
||||
unpublished: connection
|
||||
.get_all_unpublished_recipe_titles(user.id)
|
||||
.await?,
|
||||
current_id: Some(recipe_id),
|
||||
};
|
||||
|
||||
Ok(RecipeEditTemplate {
|
||||
user: Some(user),
|
||||
recipes,
|
||||
recipe,
|
||||
languages: consts::LANGUAGES,
|
||||
Ok(RecipeEditTemplate {
|
||||
user: Some(user),
|
||||
recipes,
|
||||
recipe,
|
||||
languages: consts::LANGUAGES,
|
||||
}
|
||||
.into_response())
|
||||
} else {
|
||||
Ok(MessageTemplate::new("Not allowed to edit this recipe").into_response())
|
||||
}
|
||||
.into_response())
|
||||
} else {
|
||||
Ok(MessageTemplate::new("Not allowed to edit this recipe").into_response())
|
||||
Ok(MessageTemplate::new("Recipe not found").into_response())
|
||||
}
|
||||
} else {
|
||||
Ok(MessageTemplate::new("Not logged in").into_response())
|
||||
|
|
|
|||
|
|
@ -14,6 +14,8 @@ use crate::{
|
|||
ron_utils::{ron_error, ron_response},
|
||||
};
|
||||
|
||||
const NOT_AUTHORIZED_MESSAGE: &str = "Action not authorized";
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[debug_handler]
|
||||
pub async fn update_user(
|
||||
|
|
@ -33,7 +35,7 @@ pub async fn update_user(
|
|||
} else {
|
||||
return Err(ErrorResponse::from(ron_error(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Action not authorized",
|
||||
NOT_AUTHORIZED_MESSAGE,
|
||||
)));
|
||||
}
|
||||
Ok(StatusCode::OK)
|
||||
|
|
@ -51,7 +53,7 @@ async fn check_user_rights_recipe(
|
|||
{
|
||||
Err(ErrorResponse::from(ron_error(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Action not authorized",
|
||||
NOT_AUTHORIZED_MESSAGE,
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
|
|
@ -70,7 +72,7 @@ async fn check_user_rights_recipe_group(
|
|||
{
|
||||
Err(ErrorResponse::from(ron_error(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Action not authorized",
|
||||
NOT_AUTHORIZED_MESSAGE,
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
|
|
@ -89,7 +91,7 @@ async fn check_user_rights_recipe_step(
|
|||
{
|
||||
Err(ErrorResponse::from(ron_error(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Action not authorized",
|
||||
NOT_AUTHORIZED_MESSAGE,
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
|
|
@ -108,7 +110,7 @@ async fn check_user_rights_recipe_ingredient(
|
|||
{
|
||||
Err(ErrorResponse::from(ron_error(
|
||||
StatusCode::UNAUTHORIZED,
|
||||
"Action not authorized",
|
||||
NOT_AUTHORIZED_MESSAGE,
|
||||
)))
|
||||
} else {
|
||||
Ok(())
|
||||
|
|
@ -141,6 +143,19 @@ pub async fn set_recipe_description(
|
|||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn set_servings(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeServings>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_servings(ron.recipe_id, ron.servings)
|
||||
.await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn set_estimated_time(
|
||||
State(connection): State<db::Connection>,
|
||||
|
|
@ -193,6 +208,17 @@ pub async fn set_is_published(
|
|||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn rm(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::Remove>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
connection.rm_recipe(ron.recipe_id).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
impl From<model::Group> for common::ron_api::Group {
|
||||
fn from(group: model::Group) -> Self {
|
||||
Self {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue