From 8dd3695d0f55ae74a4d196db077e493360f675b1 Mon Sep 17 00:00:00 2001 From: nnh53 Date: Thu, 23 Nov 2023 21:19:07 +0700 Subject: [PATCH 1/5] refactor: reformat middleware --- .vscode/bookmarks.json | 2 +- src/constants/message.ts | 1 + src/middlewares/users.middlewares.ts | 830 +++++++++++++-------------- src/utils/commons.ts | 6 + src/utils/validation.ts | 52 +- 5 files changed, 444 insertions(+), 447 deletions(-) diff --git a/.vscode/bookmarks.json b/.vscode/bookmarks.json index 2a5adad..f29f062 100644 --- a/.vscode/bookmarks.json +++ b/.vscode/bookmarks.json @@ -4,7 +4,7 @@ "path": "src/middlewares/users.middlewares.ts", "bookmarks": [ { - "line": 243, + "line": 236, "column": 5, "label": "decoded_authorization_Type" } diff --git a/src/constants/message.ts b/src/constants/message.ts index adf1b10..804f9e1 100644 --- a/src/constants/message.ts +++ b/src/constants/message.ts @@ -1,4 +1,5 @@ export const USERS_MESSAGES = { + INVALID_PROPERTIES: 'Invalid properties', UPLOAD_SUCCESS: 'Upload success', EMAIL_NOT_VERIFIED: 'Email not verified', CHANGE_PASSWORD_SUCCESS: 'Change password success', diff --git a/src/middlewares/users.middlewares.ts b/src/middlewares/users.middlewares.ts index 84ca4e6..d5deccf 100644 --- a/src/middlewares/users.middlewares.ts +++ b/src/middlewares/users.middlewares.ts @@ -1,7 +1,7 @@ //************ Request / Response : CHÚ Ý LÀ XÀI CỦA EXPRESS *******************/ import { NextFunction, Request, Response } from 'express' import { USERS_MESSAGES } from '~/constants/message' -import { ParamSchema, checkSchema } from 'express-validator' +import { ParamSchema, Schema, checkSchema } from 'express-validator' import { ErrorWithStatus } from '~/models/Errors' import usersService from '~/services/users.services' import { validate } from '~/utils/validation' @@ -153,92 +153,85 @@ const userIdSchema: ParamSchema = { } } -export const loginValidator = validate( - checkSchema( - { - email: { - trim: true, - notEmpty: { - errorMessage: USERS_MESSAGES.EMAIL_IS_REQUIRED - }, - isEmail: { - errorMessage: USERS_MESSAGES.EMAIL_IS_INVALID - }, - custom: { - options: async (value, { req }) => { - // dựa vào email và password tìm đối tượng tương ứng - const user = await databaseService.users.findOne({ - email: value, - password: hashPassword(req.body.password) - }) - if (user === null) { - throw new Error(USERS_MESSAGES.EMAIL_OR_PASSWORD_IS_INCORRECT) - } - req.user = user // lưu user vào req để dùng ở loginController - return true - } - } - }, - password: { - trim: true, - notEmpty: { - errorMessage: USERS_MESSAGES.PASSWORD_IS_REQUIRED - }, - isString: { - errorMessage: USERS_MESSAGES.PASSWORD_MUST_BE_A_STRING - }, - isLength: { - options: { - min: 8, - max: 50 - }, - errorMessage: USERS_MESSAGES.PASSWORD_LENGTH_MUST_BE_FROM_8_TO_50 - }, - isStrongPassword: { - options: { - minLength: 8, - minLowercase: 1, - minUppercase: 1, - minNumbers: 1, - minSymbols: 1 - }, - errorMessage: USERS_MESSAGES.PASSWORD_MUST_BE_STRONG +const loginValidatorSchema: Schema = { + email: { + trim: true, + notEmpty: { + errorMessage: USERS_MESSAGES.EMAIL_IS_REQUIRED + }, + isEmail: { + errorMessage: USERS_MESSAGES.EMAIL_IS_INVALID + }, + custom: { + options: async (value, { req }) => { + // dựa vào email và password tìm đối tượng tương ứng + const user = await databaseService.users.findOne({ + email: value, + password: hashPassword(req.body.password) + }) + if (user === null) { + throw new Error(USERS_MESSAGES.EMAIL_OR_PASSWORD_IS_INCORRECT) } + req.user = user // lưu user vào req để dùng ở loginController + return true } + } + }, + password: { + trim: true, + notEmpty: { + errorMessage: USERS_MESSAGES.PASSWORD_IS_REQUIRED }, - ['body'] - ) -) - -export const registerValidator = validate( - checkSchema( - { - name: nameSchema, - email: { - notEmpty: { - errorMessage: USERS_MESSAGES.EMAIL_IS_REQUIRED - }, - isEmail: { - errorMessage: USERS_MESSAGES.EMAIL_IS_INVALID - }, - trim: true, - custom: { - options: async (value, { req }) => { - const isExist = await usersService.checkEmailExist(value) - if (isExist) { - throw new Error(USERS_MESSAGES.EMAIL_ALREADY_EXISTS) - } - return true - } - } + isString: { + errorMessage: USERS_MESSAGES.PASSWORD_MUST_BE_A_STRING + }, + isLength: { + options: { + min: 8, + max: 50 }, - password: passwordSchema, - confirm_password: confirmPasswordSchema, - date_of_birth: dateOfBirthSchema + errorMessage: USERS_MESSAGES.PASSWORD_LENGTH_MUST_BE_FROM_8_TO_50 }, - ['body'] - ) -) + isStrongPassword: { + options: { + minLength: 8, + minLowercase: 1, + minUppercase: 1, + minNumbers: 1, + minSymbols: 1 + }, + errorMessage: USERS_MESSAGES.PASSWORD_MUST_BE_STRONG + } + } +} +export const loginValidator = validate(loginValidatorSchema, ['body']) + +const registerValidatorSchema: Schema = { + name: nameSchema, + email: { + notEmpty: { + errorMessage: USERS_MESSAGES.EMAIL_IS_REQUIRED + }, + isEmail: { + errorMessage: USERS_MESSAGES.EMAIL_IS_INVALID + }, + trim: true, + custom: { + options: async (value, { req }) => { + console.log('đã chạy tới checkSchema của register') + const isExist = await usersService.checkEmailExist(value) + if (isExist) { + throw new Error(USERS_MESSAGES.EMAIL_ALREADY_EXISTS) + } + return true + } + } + }, + password: passwordSchema, + confirm_password: confirmPasswordSchema, + date_of_birth: dateOfBirthSchema +} +export const registerValidator = validate(registerValidatorSchema, ['body']) // ??????? làm sao để implement type decoded_authorization_Type = { @@ -249,238 +242,213 @@ type decoded_authorization_Type = { exp: number } -export const accessTokenValidator = validate( - // nếu là lỗi của 422 thì validate sẽ sử xứ, còn ko thì quăng ra default handler - checkSchema( - { - Authorization: { - trim: true, - custom: { - options: async (value, { req }) => { - const accessToken = value.split(' ')[1] - if (!accessToken) { - throw new ErrorWithStatus({ - message: USERS_MESSAGES.ACCESS_TOKEN_IS_REQUIRED, - status: 401 - }) - } - // nếu xuống đc đây thì tức là access_token có rồi - // cần verify access_token và lấy payload ra lưu lại trong req - try { - const decoded_authorization = await verifyToken({ - token: accessToken, - publicOrSecretKey: process.env.JWT_SECRET_ACCESS_TOKEN as string - }) - // sau khi verify thành công ta đc payload của access_token: decoded_authorization - ;(req as Request).decoded_authorization = decoded_authorization - // console.log(decoded_authorization) - } catch (error) { - throw new ErrorWithStatus({ - message: capitalize((error as JsonWebTokenError).message), - status: HTTP_STATUS.UNAUTHORIZED - }) - } - return true - } +const accessTokenValidatorSchema: Schema = { + Authorization: { + trim: true, + custom: { + options: async (value, { req }) => { + const accessToken = value.split(' ')[1] + if (!accessToken) { + throw new ErrorWithStatus({ + message: USERS_MESSAGES.ACCESS_TOKEN_IS_REQUIRED, + status: HTTP_STATUS.UNAUTHORIZED + }) + } + // nếu xuống đc đây thì tức là access_token có rồi + // cần verify access_token và lấy payload ra lưu lại trong req + try { + const decoded_authorization = await verifyToken({ + token: accessToken, + publicOrSecretKey: process.env.JWT_SECRET_ACCESS_TOKEN as string + }) + // sau khi verify thành công ta đc payload của access_token: decoded_authorization + ;(req as Request).decoded_authorization = decoded_authorization + // console.log(decoded_authorization) + } catch (error) { + throw new ErrorWithStatus({ + message: capitalize((error as JsonWebTokenError).message), + status: HTTP_STATUS.UNAUTHORIZED + }) } + return true } + } + } +} +export const accessTokenValidator = validate(accessTokenValidatorSchema, ['headers']) + +const refreshTokenValidatorSchema: Schema = { + refresh_token: { + notEmpty: { + errorMessage: USERS_MESSAGES.REFRESH_TOKEN_IS_REQUIRED }, - ['headers'] - ) -) - -export const refreshTokenValidator = validate( - checkSchema( - { - refresh_token: { - notEmpty: { - errorMessage: USERS_MESSAGES.REFRESH_TOKEN_IS_REQUIRED - }, - trim: true, - custom: { - options: async (value, { req }) => { - // verify refresh_token gửi lên và lấy payload ra lưu lại trong req - try { - const [decoded_refresh_token, refresh_token] = await Promise.all([ - verifyToken({ - token: value, - publicOrSecretKey: process.env.JWT_SECRET_REFRESH_TOKEN as string - }), - databaseService.refreshTokens.findOne({ - token: value - }) - ]) - - if (refresh_token === null) { - throw new ErrorWithStatus({ - message: USERS_MESSAGES.REFRESH_TOKEN_IS_NOT_EXIST, - status: HTTP_STATUS.UNAUTHORIZED - }) - } - ;(req as Request).decoded_refresh_token = decoded_refresh_token - } catch (error) { - // chỗ này chủ yếu để xử lý lỗi của jwt "do không có status" - if (error instanceof JsonWebTokenError) { - throw new ErrorWithStatus({ - message: capitalize((error as JsonWebTokenError).message), - status: HTTP_STATUS.UNAUTHORIZED - }) - } - // nếu ko phải lỗi của jwt thì vẫn quăng ra validate bth - throw error - } - return true + trim: true, + custom: { + options: async (value, { req }) => { + // verify refresh_token gửi lên và lấy payload ra lưu lại trong req + try { + const [decoded_refresh_token, refresh_token] = await Promise.all([ + verifyToken({ + token: value, + publicOrSecretKey: process.env.JWT_SECRET_REFRESH_TOKEN as string + }), + databaseService.refreshTokens.findOne({ + token: value + }) + ]) + + if (refresh_token === null) { + throw new ErrorWithStatus({ + message: USERS_MESSAGES.REFRESH_TOKEN_IS_NOT_EXIST, + status: HTTP_STATUS.UNAUTHORIZED + }) } + ;(req as Request).decoded_refresh_token = decoded_refresh_token + } catch (error) { + // chỗ này chủ yếu để xử lý lỗi của jwt "do không có status" + if (error instanceof JsonWebTokenError) { + throw new ErrorWithStatus({ + message: capitalize((error as JsonWebTokenError).message), + status: HTTP_STATUS.UNAUTHORIZED + }) + } + // nếu ko phải lỗi của jwt thì vẫn quăng ra validate bth + throw error } + return true } - }, - ['body'] - ) -) - -export const emailVerifyTokenValidator = validate( - checkSchema( - { - email_verify_token: { - trim: true, - custom: { - options: async (value, { req }) => { - // kiểm tra ng dùng có truyền lên email_verify_token hay ko - if (!value) { - throw new ErrorWithStatus({ - message: USERS_MESSAGES.EMAIL_VERIFY_TOKEN_IS_REQUIRED, - status: HTTP_STATUS.UNAUTHORIZED - }) - } - // verify email_verify_token để lấy decoded_email_verify_token - try { - const decoded_email_verify_token = await verifyToken({ - token: value, - publicOrSecretKey: process.env.JWT_SECRET_EMAIL_VERIFY_TOKEN as string - }) - - // sau khi verify thành công ta đc payload của email_verify_token: decoded_email_verify_token - ;(req as Request).decoded_email_verify_token = decoded_email_verify_token - } catch (error) { - // chỗ này chủ yếu để xử lý lỗi của jwt "do không có status" - if (error instanceof JsonWebTokenError) { - throw new ErrorWithStatus({ - message: capitalize((error as JsonWebTokenError).message), - status: HTTP_STATUS.UNAUTHORIZED - }) - } - //nếu ko phải lỗi của jwt thì vẫn quăng ra validate bth - throw error - } - return true + } + } +} +export const refreshTokenValidator = validate(refreshTokenValidatorSchema, ['body']) + +const emailVerifyTokenValidatorSchema: Schema = { + email_verify_token: { + trim: true, + custom: { + options: async (value, { req }) => { + // kiểm tra ng dùng có truyền lên email_verify_token hay ko + if (!value) { + throw new ErrorWithStatus({ + message: USERS_MESSAGES.EMAIL_VERIFY_TOKEN_IS_REQUIRED, + status: HTTP_STATUS.UNAUTHORIZED + }) + } + // verify email_verify_token để lấy decoded_email_verify_token + try { + const decoded_email_verify_token = await verifyToken({ + token: value, + publicOrSecretKey: process.env.JWT_SECRET_EMAIL_VERIFY_TOKEN as string + }) + + // sau khi verify thành công ta đc payload của email_verify_token: decoded_email_verify_token + ;(req as Request).decoded_email_verify_token = decoded_email_verify_token + } catch (error) { + // chỗ này chủ yếu để xử lý lỗi của jwt "do không có status" + if (error instanceof JsonWebTokenError) { + throw new ErrorWithStatus({ + message: capitalize((error as JsonWebTokenError).message), + status: HTTP_STATUS.UNAUTHORIZED + }) } + //nếu ko phải lỗi của jwt thì vẫn quăng ra validate bth + throw error } + return true } + } + } +} +export const emailVerifyTokenValidator = validate(emailVerifyTokenValidatorSchema, ['body']) + +const forgotPasswordValidatorSchema: Schema = { + email: { + notEmpty: { + errorMessage: USERS_MESSAGES.EMAIL_IS_REQUIRED }, - ['body'] - ) -) - -export const forgotPasswordValidator = validate( - checkSchema( - { - email: { - notEmpty: { - errorMessage: USERS_MESSAGES.EMAIL_IS_REQUIRED - }, - isEmail: { - errorMessage: USERS_MESSAGES.EMAIL_IS_INVALID - }, - trim: true, - custom: { - options: async (value, { req }) => { - const user = await databaseService.users.findOne({ email: value }) - if (user === null) { - throw new ErrorWithStatus({ - message: USERS_MESSAGES.USER_NOT_FOUND, - status: HTTP_STATUS.NOT_FOUND - }) - } - req.user = user - return true - } + isEmail: { + errorMessage: USERS_MESSAGES.EMAIL_IS_INVALID + }, + trim: true, + custom: { + options: async (value, { req }) => { + const user = await databaseService.users.findOne({ email: value }) + if (user === null) { + throw new ErrorWithStatus({ + message: USERS_MESSAGES.USER_NOT_FOUND, + status: HTTP_STATUS.NOT_FOUND + }) } + req.user = user + return true } - }, - ['body'] - ) -) - -export const verifyForgotPasswordTokenValidator = validate( - checkSchema( - { - forgot_password_token: { - trim: true, - custom: { - options: async (forgotPasswordTokenFromRequest, { req }) => { - // kiểm tra ng dùng có truyền lên forgot_password_token hay ko - if (!forgotPasswordTokenFromRequest) { - throw new ErrorWithStatus({ - message: USERS_MESSAGES.FORGOT_PASSWORD_TOKEN_IS_REQUIRED, - status: HTTP_STATUS.UNAUTHORIZED - }) - } - // verify forgot_password_token để lấy decoded_forgot_password_token - try { - const decoded_forgot_password_token = await verifyToken({ - token: forgotPasswordTokenFromRequest, - publicOrSecretKey: process.env.JWT_SECRET_FORGOT_PASSWORD_TOKEN as string - }) - - // sau khi verify ta đc payload của forgot_password_token: decoded_forgot_password_token - ;(req as Request).decoded_forgot_password_token = decoded_forgot_password_token - const { user_id } = decoded_forgot_password_token - - // dựa vào user_id để tìm user - const user = await databaseService.users.findOne({ _id: new ObjectId(user_id) }) - // nếu user === null thì ném lỗi user not found - if (user === null) { - throw new ErrorWithStatus({ - message: USERS_MESSAGES.USER_NOT_FOUND, - status: HTTP_STATUS.NOT_FOUND - }) - } - - if (user.forgot_password_token !== forgotPasswordTokenFromRequest) { - throw new ErrorWithStatus({ - message: USERS_MESSAGES.FORGOT_PASSWORD_TOKEN_IS_INCORRECT, - status: HTTP_STATUS.UNAUTHORIZED - }) - } - } catch (error) { - // chỗ này chủ yếu để xử lý lỗi của jwt "do không có status" - if (error instanceof JsonWebTokenError) { - throw new ErrorWithStatus({ - message: capitalize((error as JsonWebTokenError).message), - status: HTTP_STATUS.UNAUTHORIZED - }) - } - //nếu ko phải lỗi của jwt thì vẫn quăng ra validate bth - throw error - } - return true + } + } +} +export const forgotPasswordValidator = validate(forgotPasswordValidatorSchema, ['body']) + +const verifyForgotPasswordTokenValidatorSchema: Schema = { + forgot_password_token: { + trim: true, + custom: { + options: async (forgotPasswordTokenFromRequest, { req }) => { + // kiểm tra ng dùng có truyền lên forgot_password_token hay ko + if (!forgotPasswordTokenFromRequest) { + throw new ErrorWithStatus({ + message: USERS_MESSAGES.FORGOT_PASSWORD_TOKEN_IS_REQUIRED, + status: HTTP_STATUS.UNAUTHORIZED + }) + } + // verify forgot_password_token để lấy decoded_forgot_password_token + try { + const decoded_forgot_password_token = await verifyToken({ + token: forgotPasswordTokenFromRequest, + publicOrSecretKey: process.env.JWT_SECRET_FORGOT_PASSWORD_TOKEN as string + }) + + // sau khi verify ta đc payload của forgot_password_token: decoded_forgot_password_token + ;(req as Request).decoded_forgot_password_token = decoded_forgot_password_token + const { user_id } = decoded_forgot_password_token + + // dựa vào user_id để tìm user + const user = await databaseService.users.findOne({ _id: new ObjectId(user_id) }) + // nếu user === null thì ném lỗi user not found + if (user === null) { + throw new ErrorWithStatus({ + message: USERS_MESSAGES.USER_NOT_FOUND, + status: HTTP_STATUS.NOT_FOUND + }) } + + if (user.forgot_password_token !== forgotPasswordTokenFromRequest) { + throw new ErrorWithStatus({ + message: USERS_MESSAGES.FORGOT_PASSWORD_TOKEN_IS_INCORRECT, + status: HTTP_STATUS.UNAUTHORIZED + }) + } + } catch (error) { + // chỗ này chủ yếu để xử lý lỗi của jwt "do không có status" + if (error instanceof JsonWebTokenError) { + throw new ErrorWithStatus({ + message: capitalize((error as JsonWebTokenError).message), + status: HTTP_STATUS.UNAUTHORIZED + }) + } + //nếu ko phải lỗi của jwt thì vẫn quăng ra validate bth + throw error } + return true } - }, - ['body'] - ) -) - -export const resetPasswordValidator = validate( - checkSchema( - { - password: passwordSchema, - confirm_password: confirmPasswordSchema - }, - ['body'] - ) -) + } + } +} +export const verifyForgotPasswordTokenValidator = validate(verifyForgotPasswordTokenValidatorSchema, ['body']) + +const resetPasswordValidatorSchema: Schema = { + password: passwordSchema, + confirm_password: confirmPasswordSchema +} +export const resetPasswordValidator = validate(resetPasswordValidatorSchema, ['body']) export const verifiedUserValidator = (req: Request, res: Response, next: NextFunction) => { // kiểm tra xem user đã verify hay chưa @@ -494,152 +462,136 @@ export const verifiedUserValidator = (req: Request, res: Response, next: NextFun next() } -export const updateMeValidator = validate( - checkSchema( - { - name: { - optional: true, //đc phép có hoặc k - ...nameSchema, //phân rã nameSchema ra - notEmpty: undefined //ghi đè lên notEmpty của nameSchema - }, - date_of_birth: { - optional: true, //đc phép có hoặc k - ...dateOfBirthSchema, //phân rã nameSchema ra - notEmpty: undefined //ghi đè lên notEmpty của nameSchema +const updateMeValidatorSchema: Schema = { + name: { + optional: true, //đc phép có hoặc k + ...nameSchema, //phân rã nameSchema ra + notEmpty: undefined //ghi đè lên notEmpty của nameSchema + }, + date_of_birth: { + optional: true, //đc phép có hoặc k + ...dateOfBirthSchema, //phân rã nameSchema ra + notEmpty: undefined //ghi đè lên notEmpty của nameSchema + }, + bio: { + optional: true, + isString: { + errorMessage: USERS_MESSAGES.BIO_MUST_BE_A_STRING ////messages.ts thêm BIO_MUST_BE_A_STRING: 'Bio must be a string' + }, + trim: true, //trim phát đặt cuối, nếu k thì nó sẽ lỗi validator + isLength: { + options: { + min: 1, + max: 200 }, - bio: { - optional: true, - isString: { - errorMessage: USERS_MESSAGES.BIO_MUST_BE_A_STRING ////messages.ts thêm BIO_MUST_BE_A_STRING: 'Bio must be a string' - }, - trim: true, //trim phát đặt cuối, nếu k thì nó sẽ lỗi validator - isLength: { - options: { - min: 1, - max: 200 - }, - errorMessage: USERS_MESSAGES.BIO_LENGTH_MUST_BE_LESS_THAN_200 //messages.ts thêm BIO_LENGTH_MUST_BE_LESS_THAN_200: 'Bio length must be less than 200' - } + errorMessage: USERS_MESSAGES.BIO_LENGTH_MUST_BE_LESS_THAN_200 //messages.ts thêm BIO_LENGTH_MUST_BE_LESS_THAN_200: 'Bio length must be less than 200' + } + }, + //giống bio + location: { + optional: true, + isString: { + errorMessage: USERS_MESSAGES.LOCATION_MUST_BE_A_STRING ////messages.ts thêm LOCATION_MUST_BE_A_STRING: 'Location must be a string' + }, + trim: true, + isLength: { + options: { + min: 1, + max: 200 }, - //giống bio - location: { - optional: true, - isString: { - errorMessage: USERS_MESSAGES.LOCATION_MUST_BE_A_STRING ////messages.ts thêm LOCATION_MUST_BE_A_STRING: 'Location must be a string' - }, - trim: true, - isLength: { - options: { - min: 1, - max: 200 - }, - errorMessage: USERS_MESSAGES.LOCATION_LENGTH_MUST_BE_LESS_THAN_200 //messages.ts thêm LOCATION_LENGTH_MUST_BE_LESS_THAN_200: 'Location length must be less than 200' - } + errorMessage: USERS_MESSAGES.LOCATION_LENGTH_MUST_BE_LESS_THAN_200 //messages.ts thêm LOCATION_LENGTH_MUST_BE_LESS_THAN_200: 'Location length must be less than 200' + } + }, + //giống location + website: { + optional: true, + isString: { + errorMessage: USERS_MESSAGES.WEBSITE_MUST_BE_A_STRING ////messages.ts thêm WEBSITE_MUST_BE_A_STRING: 'Website must be a string' + }, + trim: true, + isLength: { + options: { + min: 1, + max: 200 }, - //giống location - website: { - optional: true, - isString: { - errorMessage: USERS_MESSAGES.WEBSITE_MUST_BE_A_STRING ////messages.ts thêm WEBSITE_MUST_BE_A_STRING: 'Website must be a string' - }, - trim: true, - isLength: { - options: { - min: 1, - max: 200 - }, - - errorMessage: USERS_MESSAGES.WEBSITE_LENGTH_MUST_BE_LESS_THAN_200 //messages.ts thêm WEBSITE_LENGTH_MUST_BE_LESS_THAN_200: 'Website length must be less than 200' + + errorMessage: USERS_MESSAGES.WEBSITE_LENGTH_MUST_BE_LESS_THAN_200 //messages.ts thêm WEBSITE_LENGTH_MUST_BE_LESS_THAN_200: 'Website length must be less than 200' + } + }, + username: { + optional: true, + isString: { + errorMessage: USERS_MESSAGES.USERNAME_MUST_BE_A_STRING ////messages.ts thêm USERNAME_MUST_BE_A_STRING: 'Username must be a string' + }, + trim: true, + custom: { + options: async (value, { req }) => { + if (REGEX_USERNAME.test(value) === false) { + throw new Error(USERS_MESSAGES.USERNAME_IS_INVALID) } - }, - username: { - optional: true, - isString: { - errorMessage: USERS_MESSAGES.USERNAME_MUST_BE_A_STRING ////messages.ts thêm USERNAME_MUST_BE_A_STRING: 'Username must be a string' - }, - trim: true, - custom: { - options: async (value, { req }) => { - if (REGEX_USERNAME.test(value) === false) { - throw new Error(USERS_MESSAGES.USERNAME_IS_INVALID) - } - // tìm user bằng cái username người dùng muốn cập nhật - const user = await databaseService.users.findOne({ username: value }) - - if (user) { - throw new Error(USERS_MESSAGES.USERNAME_ALREADY_EXISTS) - } - return true - } + // tìm user bằng cái username người dùng muốn cập nhật + const user = await databaseService.users.findOne({ username: value }) + + if (user) { + throw new Error(USERS_MESSAGES.USERNAME_ALREADY_EXISTS) } - }, - avatar: imageSchema, - cover_photo: imageSchema - }, - ['body'] - ) -) - -export const followValidator = validate( - checkSchema( - { - followed_user_id: { - notEmpty: { - errorMessage: USERS_MESSAGES.FOLLOWED_USER_ID_IS_REQUIRED - }, - trim: true, - custom: userIdSchema + return true } + } + }, + avatar: imageSchema, + cover_photo: imageSchema +} +export const updateMeValidator = validate(updateMeValidatorSchema, ['body']) + +const followValidatorSchema: Schema = { + followed_user_id: { + notEmpty: { + errorMessage: USERS_MESSAGES.FOLLOWED_USER_ID_IS_REQUIRED }, - ['body'] - ) -) - -export const unfollowValidator = validate( - checkSchema( - { - user_id: userIdSchema - }, - ['params'] - ) -) - -export const changePasswordValidator = validate( - checkSchema( - { - old_password: { - ...passwordSchema, - custom: { - // Ở ĐÂY OPTIONS LÀ 1 HÀM ASYNC CALLBACK NÊN KHI ĐC GỌI LẠI THÌ value đại diện cho old_password - options: async (value, { req }) => { - const { user_id } = req.decoded_authorization as ITokenPayload - - // tìm user bằng user_id và password - const user = await databaseService.users.findOne({ - _id: new ObjectId(user_id) - }) + trim: true, + custom: userIdSchema + } +} +export const followValidator = validate(followValidatorSchema, ['body']) - if (user === null) { - throw new ErrorWithStatus({ - message: USERS_MESSAGES.USER_NOT_FOUND, - status: HTTP_STATUS.NOT_FOUND - }) - } - // nếu user tồn tại thì kiểm tra old_password có đúng với password trong db hay không - const { password } = user - if (password !== hashPassword(value)) { - throw new ErrorWithStatus({ - message: USERS_MESSAGES.OLD_PASSWORD_IS_INCORRECT, - status: HTTP_STATUS.UNAUTHORIZED - }) - } - return true - } +const unfollowValidatorSchema: Schema = { + user_id: userIdSchema +} +export const unfollowValidator = validate(unfollowValidatorSchema, ['params']) + +const changePasswordValidatorSchema: Schema = { + old_password: { + ...passwordSchema, + custom: { + // Ở ĐÂY OPTIONS LÀ 1 HÀM ASYNC CALLBACK NÊN KHI ĐC GỌI LẠI THÌ value đại diện cho old_password + options: async (value, { req }) => { + const { user_id } = req.decoded_authorization as ITokenPayload + + // tìm user bằng user_id và password + const user = await databaseService.users.findOne({ + _id: new ObjectId(user_id) + }) + + if (user === null) { + throw new ErrorWithStatus({ + message: USERS_MESSAGES.USER_NOT_FOUND, + status: HTTP_STATUS.NOT_FOUND + }) } - }, - password: passwordSchema, - confirm_password: confirmPasswordSchema - }, - ['body'] - ) -) + // nếu user tồn tại thì kiểm tra old_password có đúng với password trong db hay không + const { password } = user + if (password !== hashPassword(value)) { + throw new ErrorWithStatus({ + message: USERS_MESSAGES.OLD_PASSWORD_IS_INCORRECT, + status: HTTP_STATUS.UNAUTHORIZED + }) + } + return true + } + } + }, + password: passwordSchema, + confirm_password: confirmPasswordSchema +} +export const changePasswordValidator = validate(changePasswordValidatorSchema, ['body']) diff --git a/src/utils/commons.ts b/src/utils/commons.ts index e534c1d..446ea4b 100644 --- a/src/utils/commons.ts +++ b/src/utils/commons.ts @@ -1,4 +1,10 @@ +import { Schema } from 'express-validator' + //hàm xử lý enum đc mảng các số của enum export const numberEnumToArray = (numberEnum: { [key: string]: string | number }) => { return Object.values(numberEnum).filter((value) => typeof value === 'number') as number[] } + +export function schemaToArray(schema: Schema) { + return Object.keys(schema) +} diff --git a/src/utils/validation.ts b/src/utils/validation.ts index 1b202e0..32d26c2 100644 --- a/src/utils/validation.ts +++ b/src/utils/validation.ts @@ -1,7 +1,9 @@ import { Request, Response, NextFunction } from 'express' -import { validationResult, ValidationChain } from 'express-validator' -import { RunnableValidationChains } from 'express-validator/src/middlewares/schema' +import { validationResult, ValidationChain, Location, FieldValidationError } from 'express-validator' +import { RunnableValidationChains, Schema, checkSchema } from 'express-validator/src/middlewares/schema' import { EntityError, ErrorWithStatus } from '~/models/Errors' +import { schemaToArray } from './commons' +import { USERS_MESSAGES } from '~/constants/message' type Middleware = { (req: Request, res: Response, next: NextFunction): Promise @@ -14,24 +16,31 @@ type Middleware = { * @param validation * @returns Middleware */ -export const validate = (validation: RunnableValidationChains): Middleware => { +export const validate = (schema: Schema, defaultLocations?: Location[] | undefined): Middleware => { return async (req: Request, res: Response, next: NextFunction) => { + const validation: RunnableValidationChains = checkSchema(schema, defaultLocations) + // chạy qua tất cả các ValidationChain trong validation // NẾU TỪNG CHAIN CÓ LỖI THÌ SẼ TRUYỀN VÀO REQ và đi tiếp await validation.run(req) const allErrors = validationResult(req) + console.log(allErrors) + + // map lại error của tất cả các chain lại (KỂ CẢ KHÔNG PHẢI TỪ validation chain) + const allErrorMappedObject = allErrors.mapped() + + const extraPropsErrorObject = getExtraPropsErrorObject(schema, req.body) + if (extraPropsErrorObject !== undefined) allErrorMappedObject.extra_props = extraPropsErrorObject if (allErrors.isEmpty()) { return next() } - // map lại error của tất cả các chain lại (KỂ CẢ KHÔNG PHẢI TỪ validation chain) - const allErrorMappedObject = allErrors.mapped() - + // declare const entityError = new EntityError({ errors: {} }) - // xử lý những error gửi từ validation chain (KHÔNG CÓ STATUS 422) + // xử lý những error gửi từ validation chain (HIỆN TẠI KHÔNG CÓ STATUS NHƯNG ĐÚNG LÀ 422) // duyệt qua từng key của allErrorMappedObject for (const key in allErrorMappedObject) { // vào trong từng key của allErrorMappedObject tìm kiếm và destructuring ra msg @@ -50,3 +59,32 @@ export const validate = (validation: RunnableValidationChains): next(entityError) } } + +/** + * Hàm này trả về một object chứa các error của các properties không được phép gửi lên + * @returns JSON || undefined + * mới chỉ hỗ trợ ở body !!! + */ +const getExtraPropsErrorObject = (schema: Schema, requestBody: any, defaultLocations?: Location | 'body') => { + const arraySchema = schemaToArray(schema) + + // check xem có những properties nào không được phép gửi lên không + const extraPropsArray = Object.keys(requestBody).filter((prop) => !arraySchema.includes(prop)) + + let extraPropsErrorMessage = undefined + + if (extraPropsArray.length) { + extraPropsErrorMessage = `${USERS_MESSAGES.INVALID_PROPERTIES} ${extraPropsArray.join(', ')}` + } + + if (extraPropsErrorMessage === undefined) { + return undefined + } + + const extraPropsErrorObject = { + msg: extraPropsErrorMessage, + location: defaultLocations + } as FieldValidationError + + return extraPropsErrorObject +} From 43da24ac0567e88c8e88eb1e65fbbefdcbb178eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Nam=20Ho=C3=A0ng?= Date: Thu, 23 Nov 2023 22:01:44 +0700 Subject: [PATCH 2/5] Create enforcer.yml --- .github/workflows/enforcer.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/enforcer.yml diff --git a/.github/workflows/enforcer.yml b/.github/workflows/enforcer.yml new file mode 100644 index 0000000..a7c4066 --- /dev/null +++ b/.github/workflows/enforcer.yml @@ -0,0 +1,14 @@ +name: "Check Branch" + +on: + pull_request: + +jobs: + check_branch: + runs-on: ubuntu-latest + steps: + - name: Check branch + if: github.base_ref == 'main' && github.head_ref != 'dev' + run: | + echo "ERROR: You can only merge to main from dev." + exit 1 From 84d41df1269d55348654a7e74f85690889a9107c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nguy=E1=BB=85n=20Nam=20Ho=C3=A0ng?= Date: Thu, 23 Nov 2023 22:10:32 +0700 Subject: [PATCH 3/5] Create enforcer.yml MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit tạo github action check main chỉ đc merge từ develop --- .github/workflows/enforcer.yml | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 .github/workflows/enforcer.yml diff --git a/.github/workflows/enforcer.yml b/.github/workflows/enforcer.yml new file mode 100644 index 0000000..661e3ed --- /dev/null +++ b/.github/workflows/enforcer.yml @@ -0,0 +1,14 @@ +name: "Check Branch" + +on: + pull_request: + +jobs: + check_branch: + runs-on: ubuntu-latest + steps: + - name: Check branch + if: github.base_ref == 'main' && github.head_ref != 'develop' + run: | + echo "ERROR: You can only merge to main from dev." + exit 1 From fad0118aefc84be4b72e70b8a94a05a0c2c8d690 Mon Sep 17 00:00:00 2001 From: nnh53 Date: Thu, 23 Nov 2023 22:32:57 +0700 Subject: [PATCH 4/5] =?UTF-8?q?feature/no-ref/ghi=20th=C3=AAm=20readme?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index f7d1662..830cdce 100644 --- a/README.md +++ b/README.md @@ -35,3 +35,5 @@ We are very grateful for having Lê Điệp as our teacher in our lives. Without // ... } ``` + +# Phải viết branching guideline cho team From 583a3636489dc0a7b49e6a0561c2e67d3e8b898f Mon Sep 17 00:00:00 2001 From: nnh53 Date: Thu, 23 Nov 2023 22:44:24 +0700 Subject: [PATCH 5/5] =?UTF-8?q?Revert=20"feature/no-ref/ghi=20th=C3=AAm=20?= =?UTF-8?q?readme"?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This reverts commit fad0118aefc84be4b72e70b8a94a05a0c2c8d690. --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index 830cdce..f7d1662 100644 --- a/README.md +++ b/README.md @@ -35,5 +35,3 @@ We are very grateful for having Lê Điệp as our teacher in our lives. Without // ... } ``` - -# Phải viết branching guideline cho team