Skip to content

Commit

Permalink
fix: show error message when bad request (#5)
Browse files Browse the repository at this point in the history
* fix: merge-ci apply styling after format the code

* wip: add required validator

* fix: resolve error after use validator

* feat: add validator on send email

* feat: require general token to test email
  • Loading branch information
Lzyct authored Jul 22, 2024
1 parent 78f69bc commit 9a7c5b6
Show file tree
Hide file tree
Showing 14 changed files with 155 additions and 88 deletions.
24 changes: 17 additions & 7 deletions .github/workflows/merge-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,25 +32,34 @@ jobs:
uses: actions-rs/cargo@v1
with:
command: build
args: --release --all-features
args: --verbose --release --all-features

- name: Run tests
uses: actions-rs/cargo@v1
with:
command: test
args: --all-features
args: --verbose --all-features

- name: Check formatting
uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
run: |
cargo fmt --all -- --check
if [ $? -ne 0 ]; then
echo "Formatting issues found. Applying automatic fixes..."
cargo fmt --all
git config --global user.name 'Lzyct-Bot'
git config --global user.email 'lazycatlabs@users.noreply.github.com'
git add .
git commit -m "style: apply automatic formatting"
git push
echo "Formatting issues have been automatically fixed. Please review the changes."
exit 1
fi
- name: Run clippy
uses: actions-rs/cargo@v1
with:
command: clippy
args: -- -D warnings
args: -- -W warnings

- name: Get branch name
id: branch-name
Expand All @@ -72,6 +81,7 @@ jobs:
git push
- name: Delete branch if merged
if: github.event.pull_request.merged == true
uses: actions/github-script@v5
with:
script: |
Expand Down
16 changes: 6 additions & 10 deletions src/core/middlewares/general.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ impl FromRequest for GeneralMiddleware {
// act as auth middleware
fn from_request(request: &HttpRequest, _: &mut Payload) -> Self::Future {
// clone the request headers to avoids lifetime issues
let auth_header = request
.headers()
.get(AUTHORIZATION)
.cloned();
let auth_header = request.headers().get(AUTHORIZATION).cloned();

Box::pin(async move {
let auth_header = auth_header.ok_or_else(|| APIError::UnauthorizedMessage {
Expand All @@ -41,12 +38,11 @@ impl FromRequest for GeneralMiddleware {
});
}

let auth_str =
auth_header
.to_str()
.map_err(|_| APIError::UnauthorizedMessage {
message: "Invalid authorization headers".to_string(),
})?;
let auth_str = auth_header
.to_str()
.map_err(|_| APIError::UnauthorizedMessage {
message: "Invalid authorization headers".to_string(),
})?;

let token = token_extractor(auth_str);
let token_data = decode_token(&token).map_err(|_| APIError::Unauthorized)?;
Expand Down
25 changes: 17 additions & 8 deletions src/features/auth/data/repository/auth.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::borrow::Borrow;

use bcrypt::{verify, DEFAULT_COST};
use chrono::Utc;
use diesel::prelude::*;
Expand Down Expand Up @@ -45,10 +47,10 @@ impl IAuthRepository for AuthRepository {
let login_history_params = LoginHistoryParams {
user_id: user,
ip_addr: login_params.ip_addr.unwrap(),
os_info: login_params.os_info,
device_info: login_params.device_info,
os_info: login_params.os_info.unwrap(),
device_info: login_params.device_info.unwrap(),
login_timestamp: now,
fcm_token: login_params.fcm_token,
fcm_token: login_params.fcm_token.unwrap(),
};

diesel::insert_into(login_history::table)
Expand Down Expand Up @@ -80,11 +82,15 @@ impl IAuthRepository for AuthRepository {
}

fn login(&self, params: LoginParams) -> AppResult<AuthEntity> {
let param = params.clone();
let email_param = param.email.as_deref().unwrap_or("");
let password_param = param.password.as_deref().unwrap_or("");

users::table
.filter(email.eq(&params.email))
.filter(email.eq(&email_param))
.get_result::<User>(&mut self.source.get().unwrap())
.map(|user| {
(!user.password.is_empty() && verify(&params.password, &user.password).unwrap())
(!user.password.is_empty() && verify(&password_param, &user.password).unwrap())
.then(|| {
self.add_user_session(user.id, params)
.map(|login_session| {
Expand Down Expand Up @@ -122,10 +128,13 @@ impl IAuthRepository for AuthRepository {
.filter(user_id.eq(user))
.get_result::<User>(&mut self.source.get().unwrap())
.map(|user| {
if !params.old_password.is_empty()
&& verify(&params.old_password, &user.password).unwrap()
let old_password_param = &params.old_password.unwrap_or("".to_string());
let new_password_param = &params.new_password.unwrap_or("".to_string());

if !&old_password_param.is_empty()
&& verify(&old_password_param, &user.password).unwrap()
{
let new_password = bcrypt::hash(&params.new_password, DEFAULT_COST).unwrap();
let new_password = bcrypt::hash(&new_password_param, DEFAULT_COST).unwrap();
diesel::update(users::table)
.filter(user_id.eq(&user.id))
.set(password.eq(&new_password))
Expand Down
70 changes: 49 additions & 21 deletions src/features/auth/domain/usecase/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,24 +17,47 @@ pub struct LoginHistoryParams {
}

camel_case_struct!(LoginParams {
#[validate(email(message = "Invalid email"))]
email: String,
#[validate(length(min = 3, max = 20))]
password: String,
#[validate(length(min = 0, message = "Can't be empty"))]
#[validate(
required(message = "field is required"),
email(message = "Invalid email"),
)]
email: Option<String>,
#[validate(
required(message = "field is required"),
length(min = 3, max = 20),
)]
password: Option<String>,
#[validate(
length(min = 1, message = "Can't be empty"),
)]
ip_addr: Option<String>,
#[validate(length(min = 0, message = "Can't be empty"))]
device_info: String,
#[validate(length(min = 0, message = "Can't be empty"))]
os_info: String,
#[validate(length(min = 0, message = "Can't be empty"))]
fcm_token: String
#[validate(
required(message = "field is required"),
length(min = 1, message = "Can't be empty"),
)]
device_info: Option<String>,
#[validate(
required(message = "field is required"),
length(min = 1, message = "Can't be empty"),
)]
os_info: Option<String>,
#[validate(
required(message = "field is required"),
length(min = 1, message = "Can't be empty"),
)]
fcm_token: Option<String>
});

camel_case_struct!(GeneralTokenParams {
#[validate(length(min = 0, message = "Can't be empty"))]
#[validate(
required(message = "field is required"),
length(min = 1, message = "Can't be empty"),
)]
client_id: Option<String>,
#[validate(length(min = 0, message = "Can't be empty"))]
#[validate(
required(message = "field is required"),
length(min = 1, message = "Can't be empty"),
)]
client_secret: Option<String>
});

Expand All @@ -47,16 +70,21 @@ impl GeneralTokenParams {
}

camel_case_struct!(UpdatePasswordParams {
#[validate(length(min = 1, message = "Can't be empty"))]
old_password: String,
#[validate(
length(min = 6, message = "Must be at least 6 characters"),
must_match(other = "confirm_password", message = "Password not match")
required(message = "field is required"),
length(min = 1, message = "Can't be empty"),
)]
new_password: String,
old_password: Option<String>,
#[validate(
length(min = 6, message = "Must be at least 6 characters"),
must_match(other = "new_password", message = "Password not match")
required(message = "field is required"),
length(min = 6, message = "Must be at least 6 characters"),
must_match(other = "confirm_password", message = "Password not match")
)]
confirm_password: String
new_password: Option<String>,
#[validate(
required(message = "field is required"),
length(min = 6, message = "Must be at least 6 characters"),
must_match(other = "new_password", message = "Password not match")
)]
confirm_password: Option<String>
});
2 changes: 1 addition & 1 deletion src/features/general/domain/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pub mod usecase;
pub mod usecase;
21 changes: 15 additions & 6 deletions src/features/general/domain/usecase/dto.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,21 @@
use crate::camel_case_struct;

camel_case_struct!(SendEmailParams {
#[validate(length(min = 0, message = "Can't be empty"))]
email: String,
#[validate(length(min = 0, message = "Can't be empty"))]
name: String,
#[validate(length(min = 0, message = "Can't be empty"))]
subject: String,
#[validate(
required(message = "field is required"),
length(min = 1, message = "Can't be empty"),
)]
email: Option<String>,
#[validate(
required(message = "field is required"),
length(min = 1, message = "Can't be empty"),
)]
name: Option<String>,
#[validate(
required(message = "field is required"),
length(min = 1, message = "Can't be empty"),
)]
subject: Option<String>,
text_content: Option<String>,
html_content: Option<String>
});
2 changes: 1 addition & 1 deletion src/features/general/domain/usecase/mod.rs
Original file line number Diff line number Diff line change
@@ -1 +1 @@
pub mod dto;
pub mod dto;
23 changes: 17 additions & 6 deletions src/features/general/general_controller.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,26 @@ use actix_web::{
web::{self, Json},
HttpResponse,
};
use validator::Validate;

use crate::utils::mail_sender::send_email;

use crate::core::{middlewares::state::AppState, response::ResponseBody, types::AppResult};
use super::domain::usecase::dto::SendEmailParams;
use crate::{
core::{error::APIError, middlewares::general::GeneralMiddleware},
features::general::domain::usecase::dto::SendEmailParams,
};
use crate::{
core::{middlewares::state::AppState, response::ResponseBody, types::AppResult},
utils::mail_sender::send_email,
};

pub async fn test_email(
_: web::Data<AppState>,
_: GeneralMiddleware,
__: web::Data<AppState>,
params: Json<SendEmailParams>,
) -> AppResult<HttpResponse> {
send_email(params.0).await.map(|_| ResponseBody::<()>::success(None).into())
params.validate().map_err(|e| APIError::BadRequest {
message: e.to_string(),
})?;
send_email(params.0)
.await
.map(|_| ResponseBody::<()>::success(None).into())
}
4 changes: 2 additions & 2 deletions src/features/general/mod.rs
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
pub mod general_controller;
pub mod domain;
pub mod domain;
pub mod general_controller;
2 changes: 1 addition & 1 deletion src/features/mod.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
pub mod auth;
pub mod general;
pub mod user;
pub mod general;
31 changes: 18 additions & 13 deletions src/features/user/domain/usecase/dto.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,30 @@ use uuid::Uuid;
use crate::{camel_case_struct, features::user::data::models::user::User, schema::users};

camel_case_struct!(RegisterParams {
#[validate(email(message = "Invalid email"))]
email: String,
#[validate(length(min = 0, message = "Can't be empty"))]
name: String,
#[validate(length(
min = 3,
max = 20,
message = "Password must be between 3 and 20 characters"
))]
password: String
#[validate(
required(message = "field is required"),
email(message = "Invalid email"),
)]
email: Option<String>,
#[validate(
required(message = "field is required"),
length(min = 1, message = "Can't be empty"),
)]
name: Option<String>,
#[validate(
required(message = "field is required"),
length(min = 3,max = 20,message = "Password must be between 3 and 20 characters"),
)]
password: Option<String>
});

impl From<RegisterParams> for User {
fn from(params: RegisterParams) -> Self {
User {
id: Uuid::new_v4(),
email: params.email,
name: params.name,
password: params.password,
email: params.email.unwrap(),
name: params.name.unwrap(),
password: params.password.unwrap(),
role: String::from("user"),
created_at: Utc::now().naive_utc(),
updated_at: Utc::now().naive_utc(),
Expand Down
1 change: 0 additions & 1 deletion src/features/user/user_controller.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
use actix_web::web::Json;
use actix_web::{web, HttpResponse};

use crate::core::error::APIError;
use crate::core::middlewares::general::GeneralMiddleware;
use crate::{
core::{
Expand Down
4 changes: 2 additions & 2 deletions src/utils/macros.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#[macro_export]
macro_rules! camel_case_struct {
($name:ident { $( $field:ident: $type:ty ),* }) => {
#[derive(serde::Serialize, serde::Deserialize, Debug)]
#[derive(serde::Serialize, serde::Deserialize, Debug,Clone)]
#[serde(rename_all = "camelCase")]
pub struct $name {
$( pub $field: $type ),*
Expand All @@ -16,7 +16,7 @@ macro_rules! camel_case_struct {
),+ $(,)?
}
) => {
#[derive(serde::Serialize, serde::Deserialize, validator::Validate, Debug)]
#[derive(serde::Serialize, serde::Deserialize, validator::Validate, Debug,Clone)]
#[serde(rename_all = "camelCase")]
pub struct $name {
$(
Expand Down
Loading

0 comments on commit 9a7c5b6

Please sign in to comment.