* Create a minimalistic toast

* Profile editing (WIP)
This commit is contained in:
Greg Burri 2024-12-04 17:39:56 +01:00
parent 327b2d0a5b
commit 1c79cc890d
25 changed files with 1133 additions and 575 deletions

View file

@ -1,11 +1,11 @@
use std::{collections::HashMap, net::SocketAddr};
use askama::Template;
use axum::{
body::Body,
body::{self, Body},
debug_handler,
extract::{ConnectInfo, Extension, Host, Path, Query, Request, State},
http::{HeaderMap, StatusCode},
http::{header, HeaderMap},
middleware::Next,
response::{IntoResponse, Redirect, Response, Result},
Form,
};
@ -14,30 +14,36 @@ use chrono::Duration;
use serde::Deserialize;
use tracing::{event, Level};
use crate::{config::Config, consts, data::db, email, model, utils, AppState};
use crate::{
config::Config, consts, data::db, email, html_templates::*, model, ron_utils, utils, AppState,
};
pub mod ron;
impl axum::response::IntoResponse for db::DBError {
fn into_response(self) -> Response {
let body = MessageTemplate {
user: None,
message: &self.to_string(),
};
(StatusCode::INTERNAL_SERVER_ERROR, body).into_response()
// Will embed RON error in HTML page.
pub async fn ron_error_to_html(req: Request, next: Next) -> Result<Response> {
let response = next.run(req).await;
if let Some(content_type) = response.headers().get(header::CONTENT_TYPE) {
if content_type == ron_utils::RON_CONTENT_TYPE {
let message = match body::to_bytes(response.into_body(), usize::MAX).await {
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,
}
.into_response());
}
}
Ok(response)
}
///// HOME /////
#[derive(Template)]
#[template(path = "home.html")]
struct HomeTemplate {
user: Option<model::User>,
recipes: Vec<(i64, String)>,
current_recipe_id: Option<i64>,
}
#[debug_handler]
pub async fn home_page(
State(connection): State<db::Connection>,
@ -54,15 +60,6 @@ pub async fn home_page(
///// VIEW RECIPE /////
#[derive(Template)]
#[template(path = "view_recipe.html")]
struct ViewRecipeTemplate {
user: Option<model::User>,
recipes: Vec<(i64, String)>,
current_recipe_id: Option<i64>,
current_recipe: model::Recipe,
}
#[debug_handler]
pub async fn view_recipe(
State(connection): State<db::Connection>,
@ -78,121 +75,36 @@ pub async fn view_recipe(
current_recipe: recipe,
}
.into_response()),
None => Ok(MessageTemplate {
None => Ok(MessageTemplate::new_with_user(
&format!("Cannot find the recipe {}", recipe_id),
user,
message: &format!("Cannot find the recipe {}", recipe_id),
}
)
.into_response()),
}
}
///// EDIT/NEW RECIPE /////
// #[derive(Template)]
// #[template(path = "edit_recipe.html")]
// struct EditRecipeTemplate {
// user: Option<model::User>,
// recipes: Vec<(i64, String)>,
// current_recipe_id: Option<i64>,
// current_recipe: model::Recipe,
// }
// #[get("/recipe/edit/{id}")]
// pub async fn edit_recipe(
// req: HttpRequest,
// path: web::Path<(i64,)>,
// connection: web::Data<db::Connection>,
// ) -> Result<HttpResponse> {
// let (id,) = path.into_inner();
// let user = match get_current_user(&req, connection.clone()).await {
// Some(u) => u,
// None => {
// return Ok(MessageTemplate {
// user: None,
// message: "Cannot edit a recipe without being logged in",
// }
// .to_response())
// }
// };
// let recipe = connection.get_recipe_async(id).await?;
// if recipe.user_id != user.id {
// return Ok(MessageTemplate {
// message: "Cannot edit a recipe you don't own",
// user: Some(user),
// }
// .to_response());
// }
// let recipes = connection.get_all_recipe_titles_async().await?;
// Ok(EditRecipeTemplate {
// user: Some(user),
// current_recipe_id: Some(recipe.id),
// recipes,
// current_recipe: recipe,
// }
// .to_response())
// }
// #[get("/recipe/new")]
// pub async fn new_recipe(
// req: HttpRequest,
// connection: web::Data<db::Connection>,
// ) -> Result<HttpResponse> {
// let user = match get_current_user(&req, connection.clone()).await {
// Some(u) => u,
// None => {
// return Ok(MessageTemplate {
// message: "Cannot create a recipe without being logged in",
// user: None,
// }
// .to_response())
// }
// };
// let recipe_id = connection.create_recipe_async(user.id).await?;
// let recipes = connection.get_all_recipe_titles_async().await?;
// let user_id = user.id;
// Ok(EditRecipeTemplate {
// user: Some(user),
// current_recipe_id: Some(recipe_id),
// recipes,
// current_recipe: model::Recipe::empty(recipe_id, user_id),
// }
// .to_response())
// }
///// MESSAGE /////
#[derive(Template)]
#[template(path = "message_without_user.html")]
struct MessageWithoutUser<'a> {
message: &'a str,
}
impl<'a> MessageTemplate<'a> {
pub fn new(message: &'a str) -> MessageTemplate<'a> {
MessageTemplate {
user: None,
message,
as_code: false,
}
}
#[derive(Template)]
#[template(path = "message.html")]
struct MessageTemplate<'a> {
user: Option<model::User>,
message: &'a str,
pub fn new_with_user(message: &'a str, user: Option<model::User>) -> MessageTemplate<'a> {
MessageTemplate {
user,
message,
as_code: false,
}
}
}
//// SIGN UP /////
#[derive(Template)]
#[template(path = "sign_up_form.html")]
struct SignUpFormTemplate {
user: Option<model::User>,
email: String,
message: String,
message_email: String,
message_password: String,
}
#[debug_handler]
pub async fn sign_up_get(
Extension(user): Extension<Option<model::User>>,
@ -300,11 +212,10 @@ pub async fn sign_up_post(
)
.await
{
Ok(()) => Ok(MessageTemplate {
user,
message: "An email has been sent, follow the link to validate your account.",
}
.into_response()),
Ok(()) => Ok(
MessageTemplate::new_with_user(
"An email has been sent, follow the link to validate your account.",
user).into_response()),
Err(_) => {
// error!("Email validation error: {}", error); // TODO: log
error_response(SignUpError::UnableSendEmail, &form_data, user)
@ -330,10 +241,7 @@ pub async fn sign_up_validation(
if user.is_some() {
return Ok((
jar,
MessageTemplate {
user,
message: "User already exists",
},
MessageTemplate::new_with_user("User already exists", user),
));
}
let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(&headers, addr);
@ -355,48 +263,34 @@ pub async fn sign_up_validation(
let user = connection.load_user(user_id).await?;
Ok((
jar,
MessageTemplate {
MessageTemplate::new_with_user(
"Email validation successful, your account has been created",
user,
message: "Email validation successful, your account has been created",
},
),
))
}
db::ValidationResult::ValidationExpired => Ok((
jar,
MessageTemplate {
MessageTemplate::new_with_user(
"The validation has expired. Try to sign up again",
user,
message: "The validation has expired. Try to sign up again",
},
),
)),
db::ValidationResult::UnknownUser => Ok((
jar,
MessageTemplate {
user,
message: "Validation error. Try to sign up again",
},
MessageTemplate::new_with_user("Validation error. Try to sign up again", user),
)),
}
}
None => Ok((
jar,
MessageTemplate {
user,
message: "Validation error",
},
MessageTemplate::new_with_user("Validation error", user),
)),
}
}
///// SIGN IN /////
#[derive(Template)]
#[template(path = "sign_in_form.html")]
struct SignInFormTemplate {
user: Option<model::User>,
email: String,
message: String,
}
#[debug_handler]
pub async fn sign_in_get(
Extension(user): Extension<Option<model::User>>,
@ -477,24 +371,15 @@ pub async fn sign_out(
///// RESET PASSWORD /////
#[derive(Template)]
#[template(path = "ask_reset_password.html")]
struct AskResetPasswordTemplate {
user: Option<model::User>,
email: String,
message: String,
message_email: String,
}
#[debug_handler]
pub async fn ask_reset_password_get(
Extension(user): Extension<Option<model::User>>,
) -> Result<Response> {
if user.is_some() {
Ok(MessageTemplate {
Ok(MessageTemplate::new_with_user(
"Can't ask to reset password when already logged in",
user,
message: "Can't ask to reset password when already logged in",
}
)
.into_response())
} else {
Ok(AskResetPasswordTemplate {
@ -591,10 +476,10 @@ pub async fn ask_reset_password_post(
)
.await
{
Ok(()) => Ok(MessageTemplate {
Ok(()) => Ok(MessageTemplate::new_with_user(
"An email has been sent, follow the link to reset your password.",
user,
message: "An email has been sent, follow the link to reset your password.",
}
)
.into_response()),
Err(_) => {
// error!("Email validation error: {}", error); // TODO: log
@ -613,15 +498,6 @@ pub async fn ask_reset_password_post(
}
}
#[derive(Template)]
#[template(path = "reset_password.html")]
struct ResetPasswordTemplate {
user: Option<model::User>,
reset_token: String,
message: String,
message_password: String,
}
#[debug_handler]
pub async fn reset_password_get(
Extension(user): Extension<Option<model::User>>,
@ -636,11 +512,7 @@ pub async fn reset_password_get(
}
.into_response())
} else {
Ok(MessageTemplate {
user,
message: "Reset token missing",
}
.into_response())
Ok(MessageTemplate::new_with_user("Reset token missing", user).into_response())
}
}
@ -708,10 +580,10 @@ pub async fn reset_password_post(
)
.await
{
Ok(db::ResetPasswordResult::Ok) => Ok(MessageTemplate {
Ok(db::ResetPasswordResult::Ok) => Ok(MessageTemplate::new_with_user(
"Your password has been reset",
user,
message: "Your password has been reset",
}
)
.into_response()),
Ok(db::ResetPasswordResult::ResetTokenExpired) => {
error_response(ResetPasswordError::TokenExpired, &form_data, user)
@ -722,12 +594,6 @@ pub async fn reset_password_post(
///// EDIT PROFILE /////
#[derive(Template)]
#[template(path = "profile.html")]
struct ProfileTemplate {
user: Option<model::User>,
}
#[debug_handler]
pub async fn edit_user(
State(connection): State<db::Connection>,
@ -736,18 +602,12 @@ pub async fn edit_user(
if user.is_some() {
ProfileTemplate { user }.into_response()
} else {
MessageTemplate {
user: None,
message: "Not logged in",
}
.into_response()
MessageTemplate::new("Not logged in").into_response()
}
}
///// 404 /////
#[debug_handler]
pub async fn not_found() -> Result<impl IntoResponse> {
Ok(MessageWithoutUser {
message: "404: Not found",
})
pub async fn not_found(Extension(user): Extension<Option<model::User>>) -> impl IntoResponse {
MessageTemplate::new_with_user("404: Not found", user)
}