Update to Axum 0.8

This commit is contained in:
Greg Burri 2025-01-14 15:57:02 +01:00
parent 975d1ceee2
commit e355800f98
20 changed files with 1377 additions and 1199 deletions

View file

@ -7,8 +7,8 @@ edition = "2021"
[dependencies]
common = { path = "../common" }
axum = { version = "0.7", features = ["macros"] }
axum-extra = { version = "0.9", features = ["cookie"] }
axum = { version = "0.8", features = ["macros"] }
axum-extra = { version = "0.10", features = ["cookie"] }
tokio = { version = "1", features = ["full"] }
tower = { version = "0.5", features = ["util"] }
tower-http = { version = "0.6", features = ["fs", "trace"] }

View file

@ -96,6 +96,8 @@ body {
.recipe-item {
padding: 4px;
// Transparent border: to keep same size than '.recipe-item-current'.
border: 0.1em solid rgba(0, 0, 0, 0);
}
.recipe-item-current {
@ -111,6 +113,8 @@ body {
.content {
flex-grow: 1;
margin-left: 0px;
background-color: $color-2;
border: 0.1em solid $color-3;
border-radius: 1em;
@ -122,13 +126,14 @@ body {
}
#recipe-edit {
.drag-handle {
cursor: move;
}
.group {
border: 0.1em solid lighten($color-3, 30%);
margin-top: 0px;
margin-bottom: 0px;
}
.step {
@ -139,9 +144,11 @@ body {
border: 0.1em solid lighten($color-3, 30%);
}
.dropzone-group,
.dropzone-step {
.dropzone {
height: 10px;
margin-top: 0px;
margin-bottom: 0px;
background-color: white;
&.active {

View file

@ -11,7 +11,6 @@ use sqlx::{
sqlite::{SqliteConnectOptions, SqliteJournalMode, SqlitePoolOptions, SqliteSynchronous},
Pool, Sqlite, Transaction,
};
use thiserror::Error;
use tracing::{event, Level};
use crate::consts;
@ -21,7 +20,7 @@ pub mod user;
const CURRENT_DB_VERSION: u32 = 1;
#[derive(Error, Debug)]
#[derive(Debug, thiserror::Error)]
pub enum DBError {
#[error("Sqlx error: {0}")]
Sqlx(#[from] sqlx::Error),

View file

@ -128,6 +128,28 @@ WHERE [Step].[id] = $1 AND [user_id] = $2
.map_err(DBError::from)
}
pub async fn can_edit_recipe_all_steps(&self, user_id: i64, steps_ids: &[i64]) -> Result<bool> {
let params = (0..steps_ids.len())
.map(|n| format!("${}", n + 2))
.join(", ");
let query_str = format!(
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] IN ({}) AND [user_id] = $1
"#,
params
);
let mut query = sqlx::query_scalar::<_, u64>(&query_str).bind(user_id);
for id in steps_ids {
query = query.bind(id);
}
Ok(query.fetch_one(&self.pool).await? == steps_ids.len() as u64)
}
pub async fn can_edit_recipe_ingredient(
&self,
user_id: i64,
@ -475,10 +497,22 @@ ORDER BY [name]
}
pub async fn add_recipe_group(&self, recipe_id: i64) -> Result<i64> {
let db_result = sqlx::query("INSERT INTO [Group] ([recipe_id]) VALUES ($1)")
let mut tx = self.tx().await?;
let last_order = sqlx::query_scalar(
"SELECT [order] FROM [Group] WHERE [recipe_id] = $1 ORDER BY [order] DESC LIMIT 1",
)
.bind(recipe_id)
.fetch_optional(&mut *tx)
.await?
.unwrap_or(-1);
let db_result = sqlx::query("INSERT INTO [Group] ([recipe_id, [order]) VALUES ($1, $2)")
.bind(recipe_id)
.execute(&self.pool)
.bind(last_order + 1)
.execute(&mut *tx)
.await?;
Ok(db_result.last_insert_rowid())
}
@ -554,6 +588,22 @@ ORDER BY [name]
.map_err(DBError::from)
}
pub async fn set_steps_order(&self, step_ids: &[i64]) -> Result<()> {
let mut tx = self.tx().await?;
for (order, id) in step_ids.iter().enumerate() {
sqlx::query("UPDATE [Step] SET [order] = $2 WHERE [id] = $1")
.bind(id)
.bind(order as i64)
.execute(&mut *tx)
.await?;
}
tx.commit().await?;
Ok(())
}
pub async fn add_recipe_ingredient(&self, step_id: i64) -> Result<i64> {
let db_result = sqlx::query("INSERT INTO [Ingredient] ([step_id]) VALUES ($1)")
.bind(step_id)

View file

@ -4,7 +4,7 @@ use axum::{
extract::{ConnectInfo, Extension, FromRef, Request, State},
http::StatusCode,
middleware::{self, Next},
response::{Response, Result},
response::Response,
routing::{delete, get, post, put},
Router,
};
@ -55,6 +55,23 @@ impl axum::response::IntoResponse for db::DBError {
}
}
#[derive(Debug, thiserror::Error)]
enum AppError {
#[error("Database error: {0}")]
Database(#[from] db::DBError),
#[error("Template error: {0}")]
Render(#[from] rinja::Error),
}
type Result<T> = std::result::Result<T, AppError>;
impl axum::response::IntoResponse for AppError {
fn into_response(self) -> Response {
(StatusCode::INTERNAL_SERVER_ERROR, "Template error").into_response()
}
}
#[cfg(debug_assertions)]
const TRACING_LEVEL: tracing::Level = tracing::Level::DEBUG;
@ -183,8 +200,8 @@ async fn main() {
)
// Recipes.
.route("/recipe/new", get(services::recipe::create))
.route("/recipe/edit/:id", get(services::recipe::edit_recipe))
.route("/recipe/view/:id", get(services::recipe::view))
.route("/recipe/edit/{id}", get(services::recipe::edit_recipe))
.route("/recipe/view/{id}", get(services::recipe::view))
// User.
.route(
"/user/edit",

View file

@ -1,5 +1,4 @@
use axum::{
async_trait,
body::Bytes,
extract::{FromRequest, Request},
http::{header, StatusCode},
@ -11,7 +10,6 @@ use crate::ron_utils;
pub struct ExtractRon<T: DeserializeOwned>(pub T);
#[async_trait]
impl<S, T> FromRequest<S> for ExtractRon<T>
where
S: Send + Sync,

View file

@ -1,15 +1,16 @@
use axum::{
debug_handler,
extract::{Extension, Query, State},
response::{IntoResponse, Result},
response::{Html, IntoResponse},
};
use rinja::Template;
use serde::Deserialize;
// use tracing::{event, Level};
use crate::{
data::{db, model},
html_templates::*,
translation,
translation, Result,
};
#[derive(Deserialize)]
@ -37,5 +38,5 @@ pub async fn recipes_list_fragments(
},
current_id: current_recipe.current_recipe_id,
};
Ok(RecipesListFragmentTemplate { tr, recipes })
Ok(Html(RecipesListFragmentTemplate { tr, recipes }.render()?))
}

View file

@ -3,13 +3,14 @@ use axum::{
extract::{Extension, Request, State},
http::{header, StatusCode},
middleware::Next,
response::{IntoResponse, Response, Result},
response::{Html, IntoResponse, Response},
};
use rinja::Template;
use crate::{
data::{db, model},
html_templates::*,
ron_utils, translation,
ron_utils, translation, Result,
};
pub mod fragments;
@ -31,12 +32,15 @@ pub async fn ron_error_to_html(
Ok(bytes) => String::from_utf8(bytes.to_vec()).unwrap_or_default(),
Err(error) => error.to_string(),
};
return Ok(MessageTemplate {
user: None,
message: &message,
as_code: true,
tr,
}
return Ok(Html(
MessageTemplate {
user: None,
message: &message,
as_code: true,
tr,
}
.render()?,
)
.into_response());
}
}
@ -66,7 +70,7 @@ pub async fn home_page(
current_id: None,
};
Ok(HomeTemplate { user, recipes, tr })
Ok(Html(HomeTemplate { user, recipes, tr }.render()?))
}
///// 404 /////
@ -75,9 +79,9 @@ pub async fn home_page(
pub async fn not_found(
Extension(user): Extension<Option<model::User>>,
Extension(tr): Extension<translation::Tr>,
) -> impl IntoResponse {
(
) -> Result<impl IntoResponse> {
Ok((
StatusCode::NOT_FOUND,
MessageTemplate::new_with_user("404: Not found", tr, user),
)
Html(MessageTemplate::new_with_user("404: Not found", tr, user).render()?),
))
}

View file

@ -1,14 +1,16 @@
use axum::{
debug_handler,
extract::{Extension, Path, State},
response::{IntoResponse, Redirect, Response, Result},
response::{Html, IntoResponse, Redirect, Response},
};
use rinja::Template;
// use tracing::{event, Level};
use crate::{
data::{db, model},
html_templates::*,
translation::{self, Sentence},
Result,
};
#[debug_handler]
@ -21,7 +23,7 @@ pub async fn create(
let recipe_id = connection.create_recipe(user.id).await?;
Ok(Redirect::to(&format!("/recipe/edit/{}", recipe_id)).into_response())
} else {
Ok(MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).into_response())
Ok(Html(MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).render()?).into_response())
}
}
@ -45,24 +47,33 @@ pub async fn edit_recipe(
current_id: Some(recipe_id),
};
Ok(RecipeEditTemplate {
user: Some(user),
tr,
recipes,
recipe,
}
Ok(Html(
RecipeEditTemplate {
user: Some(user),
tr,
recipes,
recipe,
}
.render()?,
)
.into_response())
} else {
Ok(
MessageTemplate::new(tr.t(Sentence::RecipeNotAllowedToEdit), tr)
.into_response(),
Html(
MessageTemplate::new(tr.t(Sentence::RecipeNotAllowedToEdit), tr)
.render()?,
)
.into_response(),
)
}
} else {
Ok(MessageTemplate::new(tr.t(Sentence::RecipeNotFound), tr).into_response())
Ok(
Html(MessageTemplate::new(tr.t(Sentence::RecipeNotFound), tr).render()?)
.into_response(),
)
}
} else {
Ok(MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).into_response())
Ok(Html(MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).render()?).into_response())
}
}
@ -78,10 +89,13 @@ pub async fn view(
if !recipe.is_published
&& (user.is_none() || recipe.user_id != user.as_ref().unwrap().id)
{
return Ok(MessageTemplate::new_with_user(
&tr.tp(Sentence::RecipeNotAllowedToView, &[Box::new(recipe_id)]),
tr,
user,
return Ok(Html(
MessageTemplate::new_with_user(
&tr.tp(Sentence::RecipeNotAllowedToView, &[Box::new(recipe_id)]),
tr,
user,
)
.render()?,
)
.into_response());
}
@ -103,17 +117,20 @@ pub async fn view(
current_id: Some(recipe_id),
};
Ok(RecipeViewTemplate {
user,
tr,
recipes,
recipe,
}
Ok(Html(
RecipeViewTemplate {
user,
tr,
recipes,
recipe,
}
.render()?,
)
.into_response())
}
None => Ok(
MessageTemplate::new_with_user(tr.t(Sentence::RecipeNotFound), tr, user)
.into_response(),
),
None => Ok(Html(
MessageTemplate::new_with_user(tr.t(Sentence::RecipeNotFound), tr, user).render()?,
)
.into_response()),
}
}

View file

@ -142,6 +142,25 @@ async fn check_user_rights_recipe_step(
}
}
async fn check_user_rights_recipe_steps(
connection: &db::Connection,
user: &Option<model::User>,
step_ids: &[i64],
) -> Result<()> {
if user.is_none()
|| !connection
.can_edit_recipe_all_steps(user.as_ref().unwrap().id, step_ids)
.await?
{
Err(ErrorResponse::from(ron_error(
StatusCode::UNAUTHORIZED,
NOT_AUTHORIZED_MESSAGE,
)))
} else {
Ok(())
}
}
async fn check_user_rights_recipe_ingredient(
connection: &db::Connection,
user: &Option<model::User>,
@ -463,6 +482,17 @@ pub async fn set_step_action(
Ok(StatusCode::OK)
}
#[debug_handler]
pub async fn set_step_orders(
State(connection): State<db::Connection>,
Extension(user): Extension<Option<model::User>>,
ExtractRon(ron): ExtractRon<common::ron_api::SetStepOrders>,
) -> Result<impl IntoResponse> {
check_user_rights_recipe_steps(&connection, &user, &ron.step_ids).await?;
connection.set_steps_order(&ron.step_ids).await?;
Ok(StatusCode::OK)
}
#[debug_handler]
pub async fn add_ingredient(
State(connection): State<db::Connection>,

View file

@ -3,13 +3,17 @@ use std::{collections::HashMap, net::SocketAddr};
use axum::{
body::Body,
debug_handler,
extract::{ConnectInfo, Extension, Host, Query, Request, State},
extract::{ConnectInfo, Extension, Query, Request, State},
http::HeaderMap,
response::{IntoResponse, Redirect, Response, Result},
response::{Html, IntoResponse, Redirect, Response},
Form,
};
use axum_extra::extract::cookie::{Cookie, CookieJar};
use axum_extra::extract::{
cookie::{Cookie, CookieJar},
Host,
};
use chrono::Duration;
use rinja::Template;
use serde::Deserialize;
use tracing::{event, Level};
@ -20,7 +24,7 @@ use crate::{
email,
html_templates::*,
translation::{self, Sentence},
utils, AppState,
utils, AppState, Result,
};
/// SIGN UP ///
@ -30,14 +34,17 @@ pub async fn sign_up_get(
Extension(user): Extension<Option<model::User>>,
Extension(tr): Extension<translation::Tr>,
) -> Result<impl IntoResponse> {
Ok(SignUpFormTemplate {
user,
tr,
email: String::new(),
message: "",
message_email: "",
message_password: "",
})
Ok(Html(
SignUpFormTemplate {
user,
tr,
email: String::new(),
message: "",
message_email: "",
message_password: "",
}
.render()?,
))
}
#[derive(Deserialize, Debug)]
@ -75,26 +82,29 @@ pub async fn sign_up_post(
Sentence::InvalidPassword,
&[Box::new(common::consts::MIN_PASSWORD_SIZE)],
);
Ok(SignUpFormTemplate {
user,
email: form_data.email.clone(),
message_email: match error {
SignUpError::InvalidEmail => tr.t(Sentence::InvalidEmail),
_ => "",
},
message_password: match error {
SignUpError::PasswordsNotEqual => tr.t(Sentence::PasswordDontMatch),
SignUpError::InvalidPassword => invalid_password_mess,
_ => "",
},
message: match error {
SignUpError::UserAlreadyExists => tr.t(Sentence::EmailAlreadyTaken),
SignUpError::DatabaseError => tr.t(Sentence::DatabaseError),
SignUpError::UnableSendEmail => tr.t(Sentence::UnableToSendEmail),
_ => "",
},
tr,
}
Ok(Html(
SignUpFormTemplate {
user,
email: form_data.email.clone(),
message_email: match error {
SignUpError::InvalidEmail => tr.t(Sentence::InvalidEmail),
_ => "",
},
message_password: match error {
SignUpError::PasswordsNotEqual => tr.t(Sentence::PasswordDontMatch),
SignUpError::InvalidPassword => invalid_password_mess,
_ => "",
},
message: match error {
SignUpError::UserAlreadyExists => tr.t(Sentence::EmailAlreadyTaken),
SignUpError::DatabaseError => tr.t(Sentence::DatabaseError),
SignUpError::UnableSendEmail => tr.t(Sentence::UnableToSendEmail),
_ => "",
},
tr,
}
.render()?,
)
.into_response())
}
@ -140,12 +150,11 @@ pub async fn sign_up_post(
)
.await
{
Ok(()) => {
Ok(
MessageTemplate::new_with_user(tr.t(Sentence::SignUpEmailSent), tr, user)
.into_response(),
)
}
Ok(()) => Ok(Html(
MessageTemplate::new_with_user(tr.t(Sentence::SignUpEmailSent), tr, user)
.render()?,
)
.into_response()),
Err(_) => {
// error!("Email validation error: {}", error); // TODO: log
error_response(SignUpError::UnableSendEmail, &form_data, user, tr)
@ -172,7 +181,14 @@ pub async fn sign_up_validation(
if user.is_some() {
return Ok((
jar,
MessageTemplate::new_with_user(tr.t(Sentence::ValidationUserAlreadyExists), tr, user),
Html(
MessageTemplate::new_with_user(
tr.t(Sentence::ValidationUserAlreadyExists),
tr,
user,
)
.render()?,
),
));
}
let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(&headers, addr);
@ -194,34 +210,46 @@ pub async fn sign_up_validation(
let user = connection.load_user(user_id).await?;
Ok((
jar,
MessageTemplate::new_with_user(
tr.t(Sentence::SignUpEmailValidationSuccess),
tr,
user,
Html(
MessageTemplate::new_with_user(
tr.t(Sentence::SignUpEmailValidationSuccess),
tr,
user,
)
.render()?,
),
))
}
db::user::ValidationResult::ValidationExpired => Ok((
jar,
MessageTemplate::new_with_user(
tr.t(Sentence::SignUpValidationExpired),
tr,
user,
Html(
MessageTemplate::new_with_user(
tr.t(Sentence::SignUpValidationExpired),
tr,
user,
)
.render()?,
),
)),
db::user::ValidationResult::UnknownUser => Ok((
jar,
MessageTemplate::new_with_user(
tr.t(Sentence::SignUpValidationErrorTryAgain),
tr,
user,
Html(
MessageTemplate::new_with_user(
tr.t(Sentence::SignUpValidationErrorTryAgain),
tr,
user,
)
.render()?,
),
)),
}
}
None => Ok((
jar,
MessageTemplate::new_with_user(tr.t(Sentence::ValidationError), tr, user),
Html(
MessageTemplate::new_with_user(tr.t(Sentence::ValidationError), tr, user)
.render()?,
),
)),
}
}
@ -233,12 +261,15 @@ pub async fn sign_in_get(
Extension(user): Extension<Option<model::User>>,
Extension(tr): Extension<translation::Tr>,
) -> Result<impl IntoResponse> {
Ok(SignInFormTemplate {
user,
tr,
email: "",
message: "",
})
Ok(Html(
SignInFormTemplate {
user,
tr,
email: "",
message: "",
}
.render()?,
))
}
#[derive(Deserialize, Debug)]
@ -270,22 +301,28 @@ pub async fn sign_in_post(
{
db::user::SignInResult::AccountNotValidated => Ok((
jar,
SignInFormTemplate {
user,
email: &form_data.email,
message: tr.t(Sentence::AccountMustBeValidatedFirst),
tr,
}
Html(
SignInFormTemplate {
user,
email: &form_data.email,
message: tr.t(Sentence::AccountMustBeValidatedFirst),
tr,
}
.render()?,
)
.into_response(),
)),
db::user::SignInResult::UserNotFound | db::user::SignInResult::WrongPassword => Ok((
jar,
SignInFormTemplate {
user,
email: &form_data.email,
message: tr.t(Sentence::WrongEmailOrPassword),
tr,
}
Html(
SignInFormTemplate {
user,
email: &form_data.email,
message: tr.t(Sentence::WrongEmailOrPassword),
tr,
}
.render()?,
)
.into_response(),
)),
db::user::SignInResult::Ok(token, _user_id) => {
@ -319,18 +356,22 @@ pub async fn ask_reset_password_get(
Extension(tr): Extension<translation::Tr>,
) -> Result<Response> {
if user.is_some() {
Ok(
Ok(Html(
MessageTemplate::new_with_user(tr.t(Sentence::AskResetAlreadyLoggedInError), tr, user)
.into_response(),
.render()?,
)
.into_response())
} else {
Ok(AskResetPasswordTemplate {
user,
tr,
email: "",
message: "",
message_email: "",
}
Ok(Html(
AskResetPasswordTemplate {
user,
tr,
email: "",
message: "",
message_email: "",
}
.render()?,
)
.into_response())
}
}
@ -363,24 +404,29 @@ pub async fn ask_reset_password_post(
user: Option<model::User>,
tr: translation::Tr,
) -> Result<Response> {
Ok(AskResetPasswordTemplate {
user,
email,
message_email: match error {
AskResetPasswordError::InvalidEmail => tr.t(Sentence::InvalidEmail),
_ => "",
},
message: match error {
AskResetPasswordError::EmailAlreadyReset => {
tr.t(Sentence::AskResetEmailAlreadyResetError)
}
AskResetPasswordError::EmailUnknown => tr.t(Sentence::EmailUnknown),
AskResetPasswordError::UnableSendEmail => tr.t(Sentence::UnableToSendResetEmail),
AskResetPasswordError::DatabaseError => tr.t(Sentence::DatabaseError),
_ => "",
},
tr,
}
Ok(Html(
AskResetPasswordTemplate {
user,
email,
message_email: match error {
AskResetPasswordError::InvalidEmail => tr.t(Sentence::InvalidEmail),
_ => "",
},
message: match error {
AskResetPasswordError::EmailAlreadyReset => {
tr.t(Sentence::AskResetEmailAlreadyResetError)
}
AskResetPasswordError::EmailUnknown => tr.t(Sentence::EmailUnknown),
AskResetPasswordError::UnableSendEmail => {
tr.t(Sentence::UnableToSendResetEmail)
}
AskResetPasswordError::DatabaseError => tr.t(Sentence::DatabaseError),
_ => "",
},
tr,
}
.render()?,
)
.into_response())
}
@ -432,12 +478,11 @@ pub async fn ask_reset_password_post(
)
.await
{
Ok(()) => {
Ok(
MessageTemplate::new_with_user(tr.t(Sentence::AskResetEmailSent), tr, user)
.into_response(),
)
}
Ok(()) => Ok(Html(
MessageTemplate::new_with_user(tr.t(Sentence::AskResetEmailSent), tr, user)
.render()?,
)
.into_response()),
Err(_) => {
// error!("Email validation error: {}", error); // TODO: log
error_response(
@ -477,25 +522,30 @@ pub async fn reset_password_get(
)
.await?
{
Ok(ResetPasswordTemplate {
user,
tr,
reset_token,
message: "",
message_password: "",
}
Ok(Html(
ResetPasswordTemplate {
user,
tr,
reset_token,
message: "",
message_password: "",
}
.render()?,
)
.into_response())
} else {
Ok(
Ok(Html(
MessageTemplate::new_with_user(tr.t(Sentence::AskResetTokenMissing), tr, user)
.into_response(),
.render()?,
)
.into_response())
}
} else {
Ok(
Ok(Html(
MessageTemplate::new_with_user(tr.t(Sentence::AskResetTokenMissing), tr, user)
.into_response(),
.render()?,
)
.into_response())
}
}
@ -530,21 +580,24 @@ pub async fn reset_password_post(
Sentence::InvalidPassword,
&[Box::new(common::consts::MIN_PASSWORD_SIZE)],
);
Ok(ResetPasswordTemplate {
user,
reset_token: &form_data.reset_token,
message_password: match error {
ResetPasswordError::PasswordsNotEqual => tr.t(Sentence::PasswordDontMatch),
ResetPasswordError::InvalidPassword => reset_password_mess,
_ => "",
},
message: match error {
ResetPasswordError::TokenExpired => tr.t(Sentence::AskResetTokenExpired),
ResetPasswordError::DatabaseError => tr.t(Sentence::DatabaseError),
_ => "",
},
tr,
}
Ok(Html(
ResetPasswordTemplate {
user,
reset_token: &form_data.reset_token,
message_password: match error {
ResetPasswordError::PasswordsNotEqual => tr.t(Sentence::PasswordDontMatch),
ResetPasswordError::InvalidPassword => reset_password_mess,
_ => "",
},
message: match error {
ResetPasswordError::TokenExpired => tr.t(Sentence::AskResetTokenExpired),
ResetPasswordError::DatabaseError => tr.t(Sentence::DatabaseError),
_ => "",
},
tr,
}
.render()?,
)
.into_response())
}
@ -566,12 +619,10 @@ pub async fn reset_password_post(
)
.await
{
Ok(db::user::ResetPasswordResult::Ok) => {
Ok(
MessageTemplate::new_with_user(tr.t(Sentence::PasswordReset), tr, user)
.into_response(),
)
}
Ok(db::user::ResetPasswordResult::Ok) => Ok(Html(
MessageTemplate::new_with_user(tr.t(Sentence::PasswordReset), tr, user).render()?,
)
.into_response()),
Ok(db::user::ResetPasswordResult::ResetTokenExpired) => {
error_response(ResetPasswordError::TokenExpired, &form_data, user, tr)
}
@ -585,21 +636,24 @@ pub async fn reset_password_post(
pub async fn edit_user_get(
Extension(user): Extension<Option<model::User>>,
Extension(tr): Extension<translation::Tr>,
) -> Response {
if let Some(user) = user {
ProfileTemplate {
username: &user.name,
email: &user.email,
message: "",
message_email: "",
message_password: "",
user: Some(user.clone()),
tr,
}
) -> Result<Response> {
Ok(if let Some(user) = user {
Html(
ProfileTemplate {
username: &user.name,
email: &user.email,
message: "",
message_email: "",
message_password: "",
user: Some(user.clone()),
tr,
}
.render()?,
)
.into_response()
} else {
MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).into_response()
}
Html(MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).render()?).into_response()
})
}
#[derive(Deserialize, Debug)]
@ -640,27 +694,30 @@ pub async fn edit_user_post(
Sentence::InvalidPassword,
&[Box::new(common::consts::MIN_PASSWORD_SIZE)],
);
Ok(ProfileTemplate {
user: Some(user),
username: &form_data.name,
email: &form_data.email,
message_email: match error {
ProfileUpdateError::InvalidEmail => tr.t(Sentence::InvalidEmail),
ProfileUpdateError::EmailAlreadyTaken => tr.t(Sentence::EmailAlreadyTaken),
_ => "",
},
message_password: match error {
ProfileUpdateError::PasswordsNotEqual => tr.t(Sentence::PasswordDontMatch),
ProfileUpdateError::InvalidPassword => invalid_password_mess,
_ => "",
},
message: match error {
ProfileUpdateError::DatabaseError => tr.t(Sentence::DatabaseError),
ProfileUpdateError::UnableSendEmail => tr.t(Sentence::UnableToSendEmail),
_ => "",
},
tr,
}
Ok(Html(
ProfileTemplate {
user: Some(user),
username: &form_data.name,
email: &form_data.email,
message_email: match error {
ProfileUpdateError::InvalidEmail => tr.t(Sentence::InvalidEmail),
ProfileUpdateError::EmailAlreadyTaken => tr.t(Sentence::EmailAlreadyTaken),
_ => "",
},
message_password: match error {
ProfileUpdateError::PasswordsNotEqual => tr.t(Sentence::PasswordDontMatch),
ProfileUpdateError::InvalidPassword => invalid_password_mess,
_ => "",
},
message: match error {
ProfileUpdateError::DatabaseError => tr.t(Sentence::DatabaseError),
ProfileUpdateError::UnableSendEmail => tr.t(Sentence::UnableToSendEmail),
_ => "",
},
tr,
}
.render()?,
)
.into_response())
}
@ -742,18 +799,21 @@ pub async fn edit_user_post(
// Reload after update.
let user = connection.load_user(user.id).await?;
Ok(ProfileTemplate {
user,
username: &form_data.name,
email: &form_data.email,
message,
message_email: "",
message_password: "",
tr,
}
Ok(Html(
ProfileTemplate {
user,
username: &form_data.name,
email: &form_data.email,
message,
message_email: "",
message_password: "",
tr,
}
.render()?,
)
.into_response())
} else {
Ok(MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).into_response())
Ok(Html(MessageTemplate::new(tr.t(Sentence::NotLoggedIn), tr).render()?).into_response())
}
}
@ -770,7 +830,14 @@ pub async fn email_revalidation(
if user.is_some() {
return Ok((
jar,
MessageTemplate::new_with_user(tr.t(Sentence::ValidationUserAlreadyExists), tr, user),
Html(
MessageTemplate::new_with_user(
tr.t(Sentence::ValidationUserAlreadyExists),
tr,
user,
)
.render()?,
),
));
}
let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(&headers, addr);
@ -792,30 +859,42 @@ pub async fn email_revalidation(
let user = connection.load_user(user_id).await?;
Ok((
jar,
MessageTemplate::new_with_user(
tr.t(Sentence::ValidationSuccessful),
tr,
user,
Html(
MessageTemplate::new_with_user(
tr.t(Sentence::ValidationSuccessful),
tr,
user,
)
.render()?,
),
))
}
db::user::ValidationResult::ValidationExpired => Ok((
jar,
MessageTemplate::new_with_user(tr.t(Sentence::ValidationExpired), tr, user),
Html(
MessageTemplate::new_with_user(tr.t(Sentence::ValidationExpired), tr, user)
.render()?,
),
)),
db::user::ValidationResult::UnknownUser => Ok((
jar,
MessageTemplate::new_with_user(
tr.t(Sentence::ValidationErrorTryToSignUpAgain),
tr,
user,
Html(
MessageTemplate::new_with_user(
tr.t(Sentence::ValidationErrorTryToSignUpAgain),
tr,
user,
)
.render()?,
),
)),
}
}
None => Ok((
jar,
MessageTemplate::new_with_user(tr.t(Sentence::ValidationError), tr, user),
Html(
MessageTemplate::new_with_user(tr.t(Sentence::ValidationError), tr, user)
.render()?,
),
)),
}
}

View file

@ -24,6 +24,7 @@ pub enum Sentence {
NotLoggedIn,
DatabaseError,
TemplateError,
// Sign in page.
SignInMenu,

View file

@ -80,8 +80,8 @@
<input id="input-delete" type="button" value="{{ tr.t(Sentence::RecipeDelete) }}" />
<div id="groups-container">
<div class="dropzone-group"></div>
</div>
<input id="input-add-group" type="button" value="{{ tr.t(Sentence::RecipeAddAGroup) }}" />
<div id="hidden-templates">
@ -97,7 +97,6 @@
<input class="input-group-delete" type="button" value="{{ tr.t(Sentence::RecipeRemoveGroup) }}" />
<div class="steps">
<div class="dropzone-step"></div>
</div>
<input class="input-add-step" type="button" value="{{ tr.t(Sentence::RecipeAddAStep) }}" />
@ -131,6 +130,8 @@
<input class="input-ingredient-delete" type="button" value="{{ tr.t(Sentence::RecipeRemoveIngredient) }}" />
</div>
<div class="dropzone"></div>
</div>
</div>

View file

@ -17,6 +17,7 @@
(NotLoggedIn, "No logged in"),
(DatabaseError, "Database error"),
(TemplateError, "Template error"),
(SignInMenu, "Sign in"),
(SignInTitle, "Sign in"),
@ -112,7 +113,8 @@
(Save, "Sauvegarder"),
(NotLoggedIn, "Pas connecté"),
(DatabaseError, "Erreur de la base de données"),
(DatabaseError, "Erreur de la base de données (Database error)"),
(TemplateError, "Erreur du moteur de modèles (Template error)"),
(SignInMenu, "Se connecter"),
(SignInTitle, "Se connecter"),