-///// RECIPE /////
-
-#[debug_handler]
-pub async fn create_recipe(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
-) -> Result<Response> {
- if let Some(user) = user {
- let recipe_id = connection.create_recipe(user.id).await?;
- Ok(Redirect::to(&format!("/recipe/edit/{}", recipe_id)).into_response())
- } else {
- Ok(MessageTemplate::new("Not logged in").into_response())
- }
-}
-
-// #[debug_handler]
-// pub async fn edit_recipe(
-// State(connection): State<db::Connection>,
-// Extension(user): Extension<Option<model::User>>,
-// Path(recipe_id): Path<i64>,
-// ) -> Result<Response> {
-// if let Some(user) = user {
-// Ok(RecipeEditTemplate { user }.into_response())
-// } else {
-// Ok(MessageTemplate::new("Not logged in").into_response())
-// }
-// }
-
-#[debug_handler]
-pub async fn view_recipe(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- Path(recipe_id): Path<i64>,
-) -> Result<Response> {
- let recipes = connection.get_all_recipe_titles().await?;
- match connection.get_recipe(recipe_id).await? {
- Some(recipe) => Ok(RecipeViewTemplate {
- user,
- recipes: Recipes {
- list: recipes,
- current_id: Some(recipe.id),
- },
- recipe,
- }
- .into_response()),
- None => Ok(MessageTemplate::new_with_user(
- &format!("Cannot find the recipe {}", recipe_id),
- user,
- )
- .into_response()),
- }
-}
-
-//// SIGN UP /////
-
-#[debug_handler]
-pub async fn sign_up_get(
- Extension(user): Extension<Option<model::User>>,
-) -> Result<impl IntoResponse> {
- Ok(SignUpFormTemplate {
- user,
- email: String::new(),
- message: String::new(),
- message_email: String::new(),
- message_password: String::new(),
- })
-}
-
-#[derive(Deserialize, Debug)]
-pub struct SignUpFormData {
- email: String,
- password_1: String,
- password_2: String,
-}
-
-enum SignUpError {
- InvalidEmail,
- PasswordsNotEqual,
- InvalidPassword,
- UserAlreadyExists,
- DatabaseError,
- UnableSendEmail,
-}
-
-#[debug_handler(state = AppState)]
-pub async fn sign_up_post(
- Host(host): Host,
- State(connection): State<db::Connection>,
- State(config): State<Config>,
- Extension(user): Extension<Option<model::User>>,
- Form(form_data): Form<SignUpFormData>,
-) -> Result<Response> {
- fn error_response(
- error: SignUpError,
- form_data: &SignUpFormData,
- user: Option<model::User>,
- ) -> Result<Response> {
- Ok(SignUpFormTemplate {
- user,
- email: form_data.email.clone(),
- message_email: match error {
- SignUpError::InvalidEmail => "Invalid email",
- _ => "",
- }
- .to_string(),
- message_password: match error {
- SignUpError::PasswordsNotEqual => "Passwords don't match",
- SignUpError::InvalidPassword => "Password must have at least eight characters",
- _ => "",
- }
- .to_string(),
- message: match error {
- SignUpError::UserAlreadyExists => "This email is not available",
- SignUpError::DatabaseError => "Database error",
- SignUpError::UnableSendEmail => "Unable to send the validation email",
- _ => "",
- }
- .to_string(),
- }
- .into_response())
- }
-
- // Validation of email and password.
- if let common::utils::EmailValidation::NotValid =
- common::utils::validate_email(&form_data.email)
- {
- return error_response(SignUpError::InvalidEmail, &form_data, user);
- }
-
- if form_data.password_1 != form_data.password_2 {
- return error_response(SignUpError::PasswordsNotEqual, &form_data, user);
- }
-
- if let common::utils::PasswordValidation::TooShort =
- common::utils::validate_password(&form_data.password_1)
- {
- return error_response(SignUpError::InvalidPassword, &form_data, user);
- }
-
- match connection
- .sign_up(&form_data.email, &form_data.password_1)
- .await
- {
- Ok(db::user::SignUpResult::UserAlreadyExists) => {
- error_response(SignUpError::UserAlreadyExists, &form_data, user)
- }
- Ok(db::user::SignUpResult::UserCreatedWaitingForValidation(token)) => {
- let url = utils::get_url_from_host(&host);
- let email = form_data.email.clone();
- match email::send_email(
- &email,
- &format!(
- "Follow this link to confirm your inscription: {}/validation?validation_token={}",
- url, token
- ),
- &config.smtp_relay_address,
- &config.smtp_login,
- &config.smtp_password,
- )
- .await
- {
- 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)
- }
- }
- }
- Err(_) => {
- // error!("Signup database error: {}", error); // TODO: log
- error_response(SignUpError::DatabaseError, &form_data, user)
- }
- }
-}
-
-#[debug_handler]
-pub async fn sign_up_validation(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ConnectInfo(addr): ConnectInfo<SocketAddr>,
- Query(query): Query<HashMap<String, String>>,
- headers: HeaderMap,
-) -> Result<(CookieJar, impl IntoResponse)> {
- let mut jar = CookieJar::from_headers(&headers);
- if user.is_some() {
- return Ok((
- jar,
- MessageTemplate::new_with_user("User already exists", user),
- ));
- }
- let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(&headers, addr);
- match query.get("validation_token") {
- // 'validation_token' exists only when a user tries to validate a new account.
- Some(token) => {
- match connection
- .validation(
- token,
- Duration::seconds(consts::VALIDATION_TOKEN_DURATION),
- &client_ip,
- &client_user_agent,
- )
- .await?
- {
- db::user::ValidationResult::Ok(token, user_id) => {
- let cookie = Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, token);
- jar = jar.add(cookie);
- let user = connection.load_user(user_id).await?;
- Ok((
- jar,
- MessageTemplate::new_with_user(
- "Email validation successful, your account has been created",
- user,
- ),
- ))
- }
- db::user::ValidationResult::ValidationExpired => Ok((
- jar,
- MessageTemplate::new_with_user(
- "The validation has expired. Try to sign up again",
- user,
- ),
- )),
- db::user::ValidationResult::UnknownUser => Ok((
- jar,
- MessageTemplate::new_with_user("Validation error. Try to sign up again", user),
- )),
- }
- }
- None => Ok((
- jar,
- MessageTemplate::new_with_user("Validation error", user),
- )),
- }
-}
-
-///// SIGN IN /////
-
-#[debug_handler]
-pub async fn sign_in_get(
- Extension(user): Extension<Option<model::User>>,
-) -> Result<impl IntoResponse> {
- Ok(SignInFormTemplate {
- user,
- email: String::new(),
- message: String::new(),
- })
-}
-
-#[derive(Deserialize, Debug)]
-pub struct SignInFormData {
- email: String,
- password: String,
-}
-
-#[debug_handler]
-pub async fn sign_in_post(
- ConnectInfo(addr): ConnectInfo<SocketAddr>,
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- headers: HeaderMap,
- Form(form_data): Form<SignInFormData>,
-) -> Result<(CookieJar, Response)> {
- let jar = CookieJar::from_headers(&headers);
- let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(&headers, addr);
-
- match connection
- .sign_in(
- &form_data.email,
- &form_data.password,
- &client_ip,
- &client_user_agent,
- )
- .await?
- {
- db::user::SignInResult::AccountNotValidated => Ok((
- jar,
- SignInFormTemplate {
- user,
- email: form_data.email,
- message: "This account must be validated first".to_string(),
- }
- .into_response(),
- )),
- db::user::SignInResult::UserNotFound | db::user::SignInResult::WrongPassword => Ok((
- jar,
- SignInFormTemplate {
- user,
- email: form_data.email,
- message: "Wrong email or password".to_string(),
- }
- .into_response(),
- )),
- db::user::SignInResult::Ok(token, _user_id) => {
- let cookie = Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, token);
- Ok((jar.add(cookie), Redirect::to("/").into_response()))
- }
- }
-}
-
-///// SIGN OUT /////
-
-#[debug_handler]
-pub async fn sign_out(
- State(connection): State<db::Connection>,
- req: Request<Body>,
-) -> Result<(CookieJar, Redirect)> {
- let mut jar = CookieJar::from_headers(req.headers());
- if let Some(token_cookie) = jar.get(consts::COOKIE_AUTH_TOKEN_NAME) {
- let token = token_cookie.value().to_string();
- jar = jar.remove(consts::COOKIE_AUTH_TOKEN_NAME);
- connection.sign_out(&token).await?;
- }
- Ok((jar, Redirect::to("/")))
-}
-
-///// RESET PASSWORD /////
-
-#[debug_handler]
-pub async fn ask_reset_password_get(
- Extension(user): Extension<Option<model::User>>,
-) -> Result<Response> {
- if user.is_some() {
- Ok(MessageTemplate::new_with_user(
- "Can't ask to reset password when already logged in",
- user,
- )
- .into_response())
- } else {
- Ok(AskResetPasswordTemplate {
- user,
- email: String::new(),
- message: String::new(),
- message_email: String::new(),
- }
- .into_response())
- }
-}
-
-#[derive(Deserialize, Debug)]
-pub struct AskResetPasswordForm {
- email: String,
-}
-
-enum AskResetPasswordError {
- InvalidEmail,
- EmailAlreadyReset,
- EmailUnknown,
- UnableSendEmail,
- DatabaseError,
-}
-
-#[debug_handler(state = AppState)]
-pub async fn ask_reset_password_post(
- Host(host): Host,
- State(connection): State<db::Connection>,
- State(config): State<Config>,
- Extension(user): Extension<Option<model::User>>,
- Form(form_data): Form<AskResetPasswordForm>,
-) -> Result<Response> {
- fn error_response(
- error: AskResetPasswordError,
- email: &str,
- user: Option<model::User>,
- ) -> Result<Response> {
- Ok(AskResetPasswordTemplate {
- user,
- email: email.to_string(),
- message_email: match error {
- AskResetPasswordError::InvalidEmail => "Invalid email",
- _ => "",
- }
- .to_string(),
- message: match error {
- AskResetPasswordError::EmailAlreadyReset => {
- "The password has already been reset for this email"
- }
- AskResetPasswordError::EmailUnknown => "Email unknown",
- AskResetPasswordError::UnableSendEmail => "Unable to send the reset password email",
- AskResetPasswordError::DatabaseError => "Database error",
- _ => "",
- }
- .to_string(),
- }
- .into_response())
- }
-
- // Validation of email.
- if let common::utils::EmailValidation::NotValid =
- common::utils::validate_email(&form_data.email)
- {
- return error_response(AskResetPasswordError::InvalidEmail, &form_data.email, user);
- }
-
- match connection
- .get_token_reset_password(
- &form_data.email,
- Duration::seconds(consts::VALIDATION_PASSWORD_RESET_TOKEN_DURATION),
- )
- .await
- {
- Ok(db::user::GetTokenResetPasswordResult::PasswordAlreadyReset) => error_response(
- AskResetPasswordError::EmailAlreadyReset,
- &form_data.email,
- user,
- ),
- Ok(db::user::GetTokenResetPasswordResult::EmailUnknown) => {
- error_response(AskResetPasswordError::EmailUnknown, &form_data.email, user)
- }
- Ok(db::user::GetTokenResetPasswordResult::Ok(token)) => {
- let url = utils::get_url_from_host(&host);
- match email::send_email(
- &form_data.email,
- &format!(
- "Follow this link to reset your password: {}/reset_password?reset_token={}",
- url, token
- ),
- &config.smtp_relay_address,
- &config.smtp_login,
- &config.smtp_password,
- )
- .await
- {
- Ok(()) => Ok(MessageTemplate::new_with_user(
- "An email has been sent, follow the link to reset your password.",
- user,
- )
- .into_response()),
- Err(_) => {
- // error!("Email validation error: {}", error); // TODO: log
- error_response(
- AskResetPasswordError::UnableSendEmail,
- &form_data.email,
- user,
- )
- }
- }
- }
- Err(error) => {
- event!(Level::ERROR, "{}", error);
- error_response(AskResetPasswordError::DatabaseError, &form_data.email, user)
- }
- }
-}
-
-#[debug_handler]
-pub async fn reset_password_get(
- Extension(user): Extension<Option<model::User>>,
- Query(query): Query<HashMap<String, String>>,
-) -> Result<Response> {
- if let Some(reset_token) = query.get("reset_token") {
- Ok(ResetPasswordTemplate {
- user,
- reset_token: reset_token.to_string(),
- message: String::new(),
- message_password: String::new(),
- }
- .into_response())
- } else {
- Ok(MessageTemplate::new_with_user("Reset token missing", user).into_response())
- }
-}
-
-#[derive(Deserialize, Debug)]
-pub struct ResetPasswordForm {
- password_1: String,
- password_2: String,
- reset_token: String,
-}
-
-enum ResetPasswordError {
- PasswordsNotEqual,
- InvalidPassword,
- TokenExpired,
- DatabaseError,
-}
-
-#[debug_handler]
-pub async fn reset_password_post(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- Form(form_data): Form<ResetPasswordForm>,
-) -> Result<Response> {
- fn error_response(
- error: ResetPasswordError,
- form_data: &ResetPasswordForm,
- user: Option<model::User>,
- ) -> Result<Response> {
- Ok(ResetPasswordTemplate {
- user,
- reset_token: form_data.reset_token.clone(),
- message_password: match error {
- ResetPasswordError::PasswordsNotEqual => "Passwords don't match",
- ResetPasswordError::InvalidPassword => {
- "Password must have at least eight characters"
- }
- _ => "",
- }
- .to_string(),
- message: match error {
- ResetPasswordError::TokenExpired => "Token expired, try to reset password again",
- ResetPasswordError::DatabaseError => "Database error",
- _ => "",
- }
- .to_string(),
- }
- .into_response())
- }
-
- if form_data.password_1 != form_data.password_2 {
- return error_response(ResetPasswordError::PasswordsNotEqual, &form_data, user);
- }
-
- if let common::utils::PasswordValidation::TooShort =
- common::utils::validate_password(&form_data.password_1)
- {
- return error_response(ResetPasswordError::InvalidPassword, &form_data, user);
- }
-
- match connection
- .reset_password(
- &form_data.password_1,
- &form_data.reset_token,
- Duration::seconds(consts::VALIDATION_PASSWORD_RESET_TOKEN_DURATION),
- )
- .await
- {
- Ok(db::user::ResetPasswordResult::Ok) => Ok(MessageTemplate::new_with_user(
- "Your password has been reset",
- user,
- )
- .into_response()),
- Ok(db::user::ResetPasswordResult::ResetTokenExpired) => {
- error_response(ResetPasswordError::TokenExpired, &form_data, user)
- }
- Err(_) => error_response(ResetPasswordError::DatabaseError, &form_data, user),
- }
-}
-
-///// EDIT PROFILE /////
-
-#[debug_handler]
-pub async fn edit_user_get(Extension(user): Extension<Option<model::User>>) -> Response {
- if let Some(user) = user {
- ProfileTemplate {
- username: user.name.clone(),
- email: user.email.clone(),
- user: Some(user),
- message: String::new(),
- message_email: String::new(),
- message_password: String::new(),
- }
- .into_response()
- } else {
- MessageTemplate::new("Not logged in").into_response()
- }
-}
-
-#[derive(Deserialize, Debug)]
-pub struct EditUserForm {
- name: String,
- email: String,
- password_1: String,
- password_2: String,
-}
-enum ProfileUpdateError {
- InvalidEmail,
- EmailAlreadyTaken,
- PasswordsNotEqual,
- InvalidPassword,
- DatabaseError,
- UnableSendEmail,
-}
-
-// TODO: A lot of code are similar to 'sign_up_post', maybe find a way to factorize some.
-#[debug_handler(state = AppState)]
-pub async fn edit_user_post(
- Host(host): Host,
- State(connection): State<db::Connection>,
- State(config): State<Config>,
- Extension(user): Extension<Option<model::User>>,
- Form(form_data): Form<EditUserForm>,
-) -> Result<Response> {
- if let Some(user) = user {
- fn error_response(
- error: ProfileUpdateError,
- form_data: &EditUserForm,
- user: model::User,
- ) -> Result<Response> {
- Ok(ProfileTemplate {
- user: Some(user),
- username: form_data.name.clone(),
- email: form_data.email.clone(),
- message_email: match error {
- ProfileUpdateError::InvalidEmail => "Invalid email",
- ProfileUpdateError::EmailAlreadyTaken => "Email already taken",
- _ => "",
- }
- .to_string(),
- message_password: match error {
- ProfileUpdateError::PasswordsNotEqual => "Passwords don't match",
- ProfileUpdateError::InvalidPassword => {
- "Password must have at least eight characters"
- }
- _ => "",
- }
- .to_string(),
- message: match error {
- ProfileUpdateError::DatabaseError => "Database error",
- ProfileUpdateError::UnableSendEmail => "Unable to send the validation email",
- _ => "",
- }
- .to_string(),
- }
- .into_response())
- }
-
- if let common::utils::EmailValidation::NotValid =
- common::utils::validate_email(&form_data.email)
- {
- return error_response(ProfileUpdateError::InvalidEmail, &form_data, user);
- }
-
- let new_password = if !form_data.password_1.is_empty() || !form_data.password_2.is_empty() {
- if form_data.password_1 != form_data.password_2 {
- return error_response(ProfileUpdateError::PasswordsNotEqual, &form_data, user);
- }
- if let common::utils::PasswordValidation::TooShort =
- common::utils::validate_password(&form_data.password_1)
- {
- return error_response(ProfileUpdateError::InvalidPassword, &form_data, user);
- }
- Some(form_data.password_1.as_ref())
- } else {
- None
- };
-
- let email_trimmed = form_data.email.trim();
- let message: &str;
-
- match connection
- .update_user(
- user.id,
- Some(&email_trimmed),
- Some(&form_data.name),
- new_password,
- )
- .await
- {
- Ok(db::user::UpdateUserResult::EmailAlreadyTaken) => {
- return error_response(ProfileUpdateError::EmailAlreadyTaken, &form_data, user);
- }
- Ok(db::user::UpdateUserResult::UserUpdatedWaitingForRevalidation(token)) => {
- let url = utils::get_url_from_host(&host);
- let email = form_data.email.clone();
- match email::send_email(
- &email,
- &format!(
- "Follow this link to validate this email address: {}/revalidation?validation_token={}",
- url, token
- ),
- &config.smtp_relay_address,
- &config.smtp_login,
- &config.smtp_password,
- )
- .await
- {
- Ok(()) => {
- message =
- "An email has been sent, follow the link to validate your new email";
- }
- Err(_) => {
- // error!("Email validation error: {}", error); // TODO: log
- return error_response(ProfileUpdateError::UnableSendEmail, &form_data, user);
- }
- }
- }
- Ok(db::user::UpdateUserResult::Ok) => {
- message = "Profile saved";
- }
- Err(_) => return error_response(ProfileUpdateError::DatabaseError, &form_data, user),
- }
-
- // Reload after update.
- let user = connection.load_user(user.id).await?;
-
- Ok(ProfileTemplate {
- user,
- username: form_data.name,
- email: form_data.email,
- message: message.to_string(),
- message_email: String::new(),
- message_password: String::new(),
- }
- .into_response())
- } else {
- Ok(MessageTemplate::new("Not logged in").into_response())
- }
-}
-
-#[debug_handler]
-pub async fn email_revalidation(
- State(connection): State<db::Connection>,
- Extension(user): Extension<Option<model::User>>,
- ConnectInfo(addr): ConnectInfo<SocketAddr>,
- Query(query): Query<HashMap<String, String>>,
- headers: HeaderMap,
-) -> Result<(CookieJar, impl IntoResponse)> {
- let mut jar = CookieJar::from_headers(&headers);
- if user.is_some() {
- return Ok((
- jar,
- MessageTemplate::new_with_user("User already exists", user),
- ));
- }
- let (client_ip, client_user_agent) = utils::get_ip_and_user_agent(&headers, addr);
- match query.get("validation_token") {
- // 'validation_token' exists only when a user must validate a new email.
- Some(token) => {
- match connection
- .validation(
- token,
- Duration::seconds(consts::VALIDATION_TOKEN_DURATION),
- &client_ip,
- &client_user_agent,
- )
- .await?
- {
- db::user::ValidationResult::Ok(token, user_id) => {
- let cookie = Cookie::new(consts::COOKIE_AUTH_TOKEN_NAME, token);
- jar = jar.add(cookie);
- let user = connection.load_user(user_id).await?;
- Ok((
- jar,
- MessageTemplate::new_with_user("Email validation successful", user),
- ))
- }
- db::user::ValidationResult::ValidationExpired => Ok((
- jar,
- MessageTemplate::new_with_user(
- "The validation has expired. Try to sign up again with the same email",
- user,
- ),
- )),
- db::user::ValidationResult::UnknownUser => Ok((
- jar,
- MessageTemplate::new_with_user(
- "Validation error. Try to sign up again with the same email",
- user,
- ),
- )),
- }
- }
- None => Ok((
- jar,
- MessageTemplate::new_with_user("Validation error", user),
- )),
- }
-}
-