Recipe edit (WIP): forms to edit groups, steps and ingredients

This commit is contained in:
Greg Burri 2024-12-26 01:39:07 +01:00
parent dd05a673d9
commit 07b7ff425e
25 changed files with 881 additions and 203 deletions

View file

@ -196,26 +196,10 @@ WHERE [type] = 'table' AND [name] = 'Version'
}
fn load_sql_file<P: AsRef<Path> + fmt::Display>(sql_file: P) -> Result<String> {
let mut file = File::open(&sql_file).map_err(|err| {
DBError::Other(format!(
"Cannot open SQL file ({}): {}",
&sql_file,
err.to_string()
))
})?;
let mut file = File::open(&sql_file)
.map_err(|err| DBError::Other(format!("Cannot open SQL file ({}): {}", &sql_file, err)))?;
let mut sql = String::new();
file.read_to_string(&mut sql).map_err(|err| {
DBError::Other(format!(
"Cannot read SQL file ({}) : {}",
&sql_file,
err.to_string()
))
})?;
file.read_to_string(&mut sql)
.map_err(|err| DBError::Other(format!("Cannot read SQL file ({}) : {}", &sql_file, err)))?;
Ok(sql)
}
// #[cfg(test)]
// mod tests {
// use super::*;
// }

View file

@ -45,6 +45,21 @@ ORDER BY [title]
.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(*)
FROM [Recipe] INNER JOIN [Group] ON [Group].[recipe_id] = [Recipe].[id]
WHERE [Group].[id] = $1 AND [user_id] = $2
"#,
)
.bind(group_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#"
@ -166,6 +181,88 @@ WHERE [Recipe].[user_id] = $1
.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(
r#"
SELECT [id], [name], [comment]
FROM [Group]
WHERE [recipe_id] = $1
ORDER BY [order]
"#,
)
.bind(recipe_id)
.fetch_all(&mut *tx)
.await?;
for group in groups.iter_mut() {
group.steps = sqlx::query_as(
r#"
SELECT [id], [action]
FROM [Step]
WHERE [group_id] = $1
ORDER BY [order]
"#,
)
.bind(group.id)
.fetch_all(&mut *tx)
.await?;
for step in group.steps.iter_mut() {
step.ingredients = sqlx::query_as(
r#"
SELECT [id], [name], [comment], [quantity_value], [quantity_unit]
FROM [Ingredient]
WHERE [step_id] = $1
ORDER BY [name]
"#,
)
.bind(step.id)
.fetch_all(&mut *tx)
.await?;
}
}
Ok(groups)
}
pub async fn add_recipe_group(&self, recipe_id: i64) -> Result<i64> {
let db_result = sqlx::query("INSERT INTO [Group] ([recipe_id]) VALUES ($1)")
.bind(recipe_id)
.execute(&self.pool)
.await?;
Ok(db_result.last_insert_rowid())
}
pub async fn rm_recipe_group(&self, group_id: i64) -> Result<()> {
sqlx::query("DELETE FROM [Group] WHERE [id] = $1")
.bind(group_id)
.execute(&self.pool)
.await
.map(|_| ())
.map_err(DBError::from)
}
pub async fn set_group_name(&self, group_id: i64, name: &str) -> Result<()> {
sqlx::query("UPDATE [Group] SET [name] = $2 WHERE [id] = $1")
.bind(group_id)
.bind(name)
.execute(&self.pool)
.await
.map(|_| ())
.map_err(DBError::from)
}
pub async fn set_group_comment(&self, group_id: i64, comment: &str) -> Result<()> {
sqlx::query("UPDATE [Group] SET [comment] = $2 WHERE [id] = $1")
.bind(group_id)
.bind(comment)
.execute(&self.pool)
.await
.map(|_| ())
.map_err(DBError::from)
}
}
#[cfg(test)]
@ -214,7 +311,7 @@ mod tests {
assert_eq!(recipe.estimated_time, Some(420));
assert_eq!(recipe.difficulty, Difficulty::Medium);
assert_eq!(recipe.lang, "fr");
assert_eq!(recipe.is_published, true);
assert!(recipe.is_published);
Ok(())
}

View file

@ -190,7 +190,7 @@ FROM [User] WHERE [email] = $1
return Ok(SignUpResult::UserAlreadyExists);
}
let token = generate_token();
let hashed_password = hash(password).map_err(|e| DBError::from_dyn_error(e))?;
let hashed_password = hash(password).map_err(DBError::from_dyn_error)?;
sqlx::query(
r#"
UPDATE [User]
@ -208,7 +208,7 @@ WHERE [id] = $1
}
None => {
let token = generate_token();
let hashed_password = hash(password).map_err(|e| DBError::from_dyn_error(e))?;
let hashed_password = hash(password).map_err(DBError::from_dyn_error)?;
sqlx::query(
r#"
INSERT INTO [User]
@ -336,19 +336,18 @@ WHERE [id] = $1
pub async fn sign_out(&self, token: &str) -> Result<()> {
let mut tx = self.tx().await?;
match sqlx::query_scalar::<_, i64>("SELECT [id] FROM [UserLoginToken] WHERE [token] = $1")
.bind(token)
.fetch_optional(&mut *tx)
.await?
if let Some(login_id) =
sqlx::query_scalar::<_, i64>("SELECT [id] FROM [UserLoginToken] WHERE [token] = $1")
.bind(token)
.fetch_optional(&mut *tx)
.await?
{
Some(login_id) => {
sqlx::query("DELETE FROM [UserLoginToken] WHERE [id] = $1")
.bind(login_id)
.execute(&mut *tx)
.await?;
tx.commit().await?;
}
None => (),
sqlx::query("DELETE FROM [UserLoginToken] WHERE [id] = $1")
.bind(login_id)
.execute(&mut *tx)
.await?;
tx.commit().await?;
}
Ok(())
}
@ -429,7 +428,7 @@ WHERE [password_reset_token] = $1
.execute(&mut *tx)
.await?;
let hashed_new_password = hash(new_password).map_err(|e| DBError::from_dyn_error(e))?;
let hashed_new_password = hash(new_password).map_err(DBError::from_dyn_error)?;
sqlx::query(
r#"
@ -853,7 +852,7 @@ VALUES (
};
connection
.reset_password(&new_password, &token, Duration::hours(1))
.reset_password(new_password, &token, Duration::hours(1))
.await?;
// Sign in.

View file

@ -34,20 +34,30 @@ pub struct Recipe {
// pub groups: Vec<Group>,
}
#[derive(FromRow)]
pub struct Group {
pub id: i64,
pub name: String,
pub comment: String,
#[sqlx(skip)]
pub steps: Vec<Step>,
}
#[derive(FromRow)]
pub struct Step {
pub id: i64,
pub action: String,
#[sqlx(skip)]
pub ingredients: Vec<Ingredient>,
}
#[derive(FromRow)]
pub struct Ingredient {
pub id: i64,
pub name: String,
pub comment: String,
pub quantity: i32,
pub quantity_value: f64,
pub quantity_unit: String,
}