Recipe edit (WIP): forms to edit groups, steps and ingredients
This commit is contained in:
parent
dd05a673d9
commit
07b7ff425e
25 changed files with 881 additions and 203 deletions
|
|
@ -41,22 +41,31 @@ impl fmt::Debug for Config {
|
|||
|
||||
pub fn load() -> Config {
|
||||
match File::open(consts::FILE_CONF) {
|
||||
Ok(file) => from_reader(file).expect(&format!(
|
||||
"Failed to open configuration file {}",
|
||||
consts::FILE_CONF
|
||||
)),
|
||||
Ok(file) => from_reader(file).unwrap_or_else(|error| {
|
||||
panic!(
|
||||
"Failed to open configuration file {}: {}",
|
||||
consts::FILE_CONF,
|
||||
error
|
||||
)
|
||||
}),
|
||||
Err(_) => {
|
||||
let file = File::create(consts::FILE_CONF).expect(&format!(
|
||||
"Failed to create default configuration file {}",
|
||||
consts::FILE_CONF
|
||||
));
|
||||
let file = File::create(consts::FILE_CONF).unwrap_or_else(|error| {
|
||||
panic!(
|
||||
"Failed to create default configuration file {}: {}",
|
||||
consts::FILE_CONF,
|
||||
error
|
||||
)
|
||||
});
|
||||
|
||||
let default_config = Config::default();
|
||||
|
||||
to_writer_pretty(file, &default_config, PrettyConfig::new()).expect(&format!(
|
||||
"Failed to write default configuration file {}",
|
||||
consts::FILE_CONF
|
||||
));
|
||||
to_writer_pretty(file, &default_config, PrettyConfig::new()).unwrap_or_else(|error| {
|
||||
panic!(
|
||||
"Failed to write default configuration file {}: {}",
|
||||
consts::FILE_CONF,
|
||||
error
|
||||
)
|
||||
});
|
||||
|
||||
default_config
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,10 +4,10 @@ pub const FILE_CONF: &str = "conf.ron";
|
|||
pub const DB_DIRECTORY: &str = "data";
|
||||
pub const DB_FILENAME: &str = "recipes.sqlite";
|
||||
pub const SQL_FILENAME: &str = "sql/version_{VERSION}.sql";
|
||||
pub const VALIDATION_TOKEN_DURATION: i64 = 1 * 60 * 60; // [s]. (1 jour).
|
||||
pub const VALIDATION_TOKEN_DURATION: i64 = 60 * 60; // [s]. (1 jour).
|
||||
pub const COOKIE_AUTH_TOKEN_NAME: &str = "auth_token";
|
||||
|
||||
pub const VALIDATION_PASSWORD_RESET_TOKEN_DURATION: i64 = 1 * 60 * 60; // [s]. (1 jour).
|
||||
pub const VALIDATION_PASSWORD_RESET_TOKEN_DURATION: i64 = 60 * 60; // [s]. (1 jour).
|
||||
|
||||
// Number of alphanumeric characters for tokens
|
||||
// (cookie authentication, password reset, validation token).
|
||||
|
|
@ -21,4 +21,4 @@ pub const REVERSE_PROXY_IP_HTTP_FIELD: &str = "x-real-ip"; // Set by the reverse
|
|||
|
||||
pub const MAX_DB_CONNECTION: u32 = 1024;
|
||||
|
||||
pub const LANGUAGES: [(&'static str, &'static str); 2] = [("Français", "fr"), ("English", "en")];
|
||||
pub const LANGUAGES: [(&str, &str); 2] = [("Français", "fr"), ("English", "en")];
|
||||
|
|
|
|||
|
|
@ -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::*;
|
||||
|
||||
// }
|
||||
|
|
|
|||
|
|
@ -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(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,20 +9,20 @@ use crate::consts;
|
|||
|
||||
#[derive(Debug, Display)]
|
||||
pub enum Error {
|
||||
ParseError(lettre::address::AddressError),
|
||||
SmtpError(lettre::transport::smtp::Error),
|
||||
Parse(lettre::address::AddressError),
|
||||
Smtp(lettre::transport::smtp::Error),
|
||||
Email(lettre::error::Error),
|
||||
}
|
||||
|
||||
impl From<lettre::address::AddressError> for Error {
|
||||
fn from(error: lettre::address::AddressError) -> Self {
|
||||
Error::ParseError(error)
|
||||
Error::Parse(error)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<lettre::transport::smtp::Error> for Error {
|
||||
fn from(error: lettre::transport::smtp::Error) -> Self {
|
||||
Error::SmtpError(error)
|
||||
Error::Smtp(error)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use axum::{
|
|||
http::StatusCode,
|
||||
middleware::{self, Next},
|
||||
response::{Response, Result},
|
||||
routing::{get, put},
|
||||
routing::{delete, get, post, put},
|
||||
Router,
|
||||
};
|
||||
use axum_extra::extract::cookie::CookieJar;
|
||||
|
|
@ -101,6 +101,14 @@ async fn main() {
|
|||
"/recipe/set_is_published",
|
||||
put(services::ron::set_is_published),
|
||||
)
|
||||
.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))
|
||||
.route("/recipe/set_group_name", put(services::ron::set_group_name))
|
||||
.route(
|
||||
"/recipe/set_group_comment",
|
||||
put(services::ron::set_group_comment),
|
||||
)
|
||||
.fallback(services::ron::not_found);
|
||||
|
||||
let fragments_routes = Router::new().route(
|
||||
|
|
@ -183,7 +191,7 @@ async fn get_current_user(
|
|||
) -> Option<model::User> {
|
||||
match jar.get(consts::COOKIE_AUTH_TOKEN_NAME) {
|
||||
Some(token_cookie) => match connection
|
||||
.authentication(token_cookie.value(), &client_ip, &client_user_agent)
|
||||
.authentication(token_cookie.value(), client_ip, client_user_agent)
|
||||
.await
|
||||
{
|
||||
Ok(db::user::AuthenticationResult::NotValidToken) => None,
|
||||
|
|
@ -234,12 +242,15 @@ async fn process_args() -> bool {
|
|||
}
|
||||
})
|
||||
.unwrap();
|
||||
std::fs::copy(&db_path, &db_path_bckup).expect(&format!(
|
||||
"Unable to make backup of {:?} to {:?}",
|
||||
&db_path, &db_path_bckup
|
||||
));
|
||||
std::fs::remove_file(&db_path)
|
||||
.expect(&format!("Unable to remove db file: {:?}", &db_path));
|
||||
std::fs::copy(&db_path, &db_path_bckup).unwrap_or_else(|error| {
|
||||
panic!(
|
||||
"Unable to make backup of {:?} to {:?}: {}",
|
||||
&db_path, &db_path_bckup, error
|
||||
)
|
||||
});
|
||||
std::fs::remove_file(&db_path).unwrap_or_else(|error| {
|
||||
panic!("Unable to remove db file {:?}: {}", &db_path, error)
|
||||
});
|
||||
}
|
||||
|
||||
match db::Connection::new().await {
|
||||
|
|
|
|||
|
|
@ -60,10 +60,8 @@ where
|
|||
{
|
||||
match from_bytes::<T>(&body) {
|
||||
Ok(ron) => Ok(ron),
|
||||
Err(error) => {
|
||||
return Err(RonError {
|
||||
error: format!("Ron parsing error: {}", error),
|
||||
});
|
||||
}
|
||||
Err(error) => Err(RonError {
|
||||
error: format!("Ron parsing error: {}", error),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
use axum::{
|
||||
body, debug_handler,
|
||||
extract::{Extension, Request, State},
|
||||
http::header,
|
||||
http::{header, StatusCode},
|
||||
middleware::Next,
|
||||
response::{IntoResponse, Response, Result},
|
||||
};
|
||||
|
|
@ -66,5 +66,8 @@ pub async fn home_page(
|
|||
|
||||
#[debug_handler]
|
||||
pub async fn not_found(Extension(user): Extension<Option<model::User>>) -> impl IntoResponse {
|
||||
MessageTemplate::new_with_user("404: Not found", user)
|
||||
(
|
||||
StatusCode::NOT_FOUND,
|
||||
MessageTemplate::new_with_user("404: Not found", user),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,9 @@
|
|||
use axum::{
|
||||
debug_handler,
|
||||
extract::{Extension, State},
|
||||
extract::{Extension, Query, State},
|
||||
response::{IntoResponse, Result},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
// use tracing::{event, Level};
|
||||
|
||||
use crate::{
|
||||
|
|
@ -10,9 +11,15 @@ use crate::{
|
|||
html_templates::*,
|
||||
};
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct CurrentRecipeId {
|
||||
current_recipe_id: Option<i64>,
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn recipes_list_fragments(
|
||||
State(connection): State<db::Connection>,
|
||||
current_recipe: Query<CurrentRecipeId>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
let recipes = Recipes {
|
||||
|
|
@ -24,8 +31,7 @@ pub async fn recipes_list_fragments(
|
|||
} else {
|
||||
vec![]
|
||||
},
|
||||
current_id: None,
|
||||
current_id: current_recipe.current_recipe_id,
|
||||
};
|
||||
|
||||
Ok(RecipesListFragmentTemplate { user, recipes })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -48,13 +48,19 @@
|
|||
|
||||
use axum::{
|
||||
debug_handler,
|
||||
extract::{Extension, State},
|
||||
extract::{Extension, Query, State},
|
||||
http::StatusCode,
|
||||
response::{ErrorResponse, IntoResponse, Result},
|
||||
};
|
||||
use serde::Deserialize;
|
||||
// use tracing::{event, Level};
|
||||
|
||||
use crate::{data::db, model, ron_extractor::ExtractRon, ron_utils::ron_error};
|
||||
use crate::{
|
||||
data::db,
|
||||
model,
|
||||
ron_extractor::ExtractRon,
|
||||
ron_utils::{ron_error, ron_response},
|
||||
};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[debug_handler]
|
||||
|
|
@ -81,7 +87,7 @@ pub async fn update_user(
|
|||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
async fn check_user_rights(
|
||||
async fn check_user_rights_recipe(
|
||||
connection: &db::Connection,
|
||||
user: &Option<model::User>,
|
||||
recipe_id: i64,
|
||||
|
|
@ -100,13 +106,32 @@ async fn check_user_rights(
|
|||
}
|
||||
}
|
||||
|
||||
async fn check_user_rights_recipe_group(
|
||||
connection: &db::Connection,
|
||||
user: &Option<model::User>,
|
||||
group_id: i64,
|
||||
) -> Result<()> {
|
||||
if user.is_none()
|
||||
|| !connection
|
||||
.can_edit_recipe_group(user.as_ref().unwrap().id, group_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>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeTitle>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_title(ron.recipe_id, &ron.title)
|
||||
.await?;
|
||||
|
|
@ -119,7 +144,7 @@ pub async fn set_recipe_description(
|
|||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeDescription>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_description(ron.recipe_id, &ron.description)
|
||||
.await?;
|
||||
|
|
@ -132,7 +157,7 @@ pub async fn set_estimated_time(
|
|||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeEstimatedTime>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_estimated_time(ron.recipe_id, ron.estimated_time)
|
||||
.await?;
|
||||
|
|
@ -145,7 +170,7 @@ pub async fn set_difficulty(
|
|||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeDifficulty>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_difficulty(ron.recipe_id, ron.difficulty)
|
||||
.await?;
|
||||
|
|
@ -158,7 +183,7 @@ pub async fn set_language(
|
|||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetRecipeLanguage>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_language(ron.recipe_id, &ron.lang)
|
||||
.await?;
|
||||
|
|
@ -171,13 +196,128 @@ pub async fn set_is_published(
|
|||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetIsPublished>,
|
||||
) -> Result<StatusCode> {
|
||||
check_user_rights(&connection, &user, ron.recipe_id).await?;
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
connection
|
||||
.set_recipe_is_published(ron.recipe_id, ron.is_published)
|
||||
.await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
impl From<model::Group> for common::ron_api::Group {
|
||||
fn from(group: model::Group) -> Self {
|
||||
Self {
|
||||
id: group.id,
|
||||
name: group.name,
|
||||
comment: group.comment,
|
||||
steps: group
|
||||
.steps
|
||||
.into_iter()
|
||||
.map(common::ron_api::Step::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<model::Step> for common::ron_api::Step {
|
||||
fn from(step: model::Step) -> Self {
|
||||
Self {
|
||||
id: step.id,
|
||||
action: step.action,
|
||||
ingredients: step
|
||||
.ingredients
|
||||
.into_iter()
|
||||
.map(common::ron_api::Ingredient::from)
|
||||
.collect(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<model::Ingredient> for common::ron_api::Ingredient {
|
||||
fn from(ingredient: model::Ingredient) -> Self {
|
||||
Self {
|
||||
id: ingredient.id,
|
||||
name: ingredient.name,
|
||||
comment: ingredient.comment,
|
||||
quantity_value: ingredient.quantity_value,
|
||||
quantity_unit: ingredient.quantity_unit,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
pub struct RecipeId {
|
||||
#[serde(rename = "recipe_id")]
|
||||
id: i64,
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
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,
|
||||
connection
|
||||
.get_groups(recipe_id.id)
|
||||
.await?
|
||||
.into_iter()
|
||||
.map(common::ron_api::Group::from)
|
||||
.collect::<Vec<_>>(),
|
||||
))
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn add_group(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::AddRecipeGroup>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe(&connection, &user, ron.recipe_id).await?;
|
||||
let group_id = connection.add_recipe_group(ron.recipe_id).await?;
|
||||
|
||||
Ok(ron_response(
|
||||
StatusCode::OK,
|
||||
common::ron_api::AddRecipeGroupResult { group_id },
|
||||
))
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn rm_group(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::RemoveRecipeGroup>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_group(&connection, &user, ron.group_id).await?;
|
||||
connection.rm_recipe_group(ron.group_id).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn set_group_name(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetGroupName>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_group(&connection, &user, ron.group_id).await?;
|
||||
connection.set_group_name(ron.group_id, &ron.name).await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn set_group_comment(
|
||||
State(connection): State<db::Connection>,
|
||||
Extension(user): Extension<Option<model::User>>,
|
||||
ExtractRon(ron): ExtractRon<common::ron_api::SetGroupComment>,
|
||||
) -> Result<impl IntoResponse> {
|
||||
check_user_rights_recipe_group(&connection, &user, ron.group_id).await?;
|
||||
connection
|
||||
.set_group_comment(ron.group_id, &ron.comment)
|
||||
.await?;
|
||||
Ok(StatusCode::OK)
|
||||
}
|
||||
|
||||
///// 404 /////
|
||||
#[debug_handler]
|
||||
pub async fn not_found(Extension(_user): Extension<Option<model::User>>) -> impl IntoResponse {
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ use crate::{
|
|||
utils, AppState,
|
||||
};
|
||||
|
||||
//// SIGN UP /////
|
||||
/// SIGN UP ///
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn sign_up_get(
|
||||
|
|
@ -207,7 +207,7 @@ pub async fn sign_up_validation(
|
|||
}
|
||||
}
|
||||
|
||||
///// SIGN IN /////
|
||||
/// SIGN IN ///
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn sign_in_get(
|
||||
|
|
@ -271,7 +271,7 @@ pub async fn sign_in_post(
|
|||
}
|
||||
}
|
||||
|
||||
///// SIGN OUT /////
|
||||
/// SIGN OUT ///
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn sign_out(
|
||||
|
|
@ -287,7 +287,7 @@ pub async fn sign_out(
|
|||
Ok((jar, Redirect::to("/")))
|
||||
}
|
||||
|
||||
///// RESET PASSWORD /////
|
||||
/// RESET PASSWORD ///
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn ask_reset_password_get(
|
||||
|
|
@ -510,7 +510,7 @@ pub async fn reset_password_post(
|
|||
}
|
||||
}
|
||||
|
||||
///// EDIT PROFILE /////
|
||||
/// EDIT PROFILE ///
|
||||
|
||||
#[debug_handler]
|
||||
pub async fn edit_user_get(Extension(user): Extension<Option<model::User>>) -> Response {
|
||||
|
|
@ -614,7 +614,7 @@ pub async fn edit_user_post(
|
|||
match connection
|
||||
.update_user(
|
||||
user.id,
|
||||
Some(&email_trimmed),
|
||||
Some(email_trimmed),
|
||||
Some(&form_data.name),
|
||||
new_password,
|
||||
)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue