diff --git a/docs/decorators.md b/docs/decorators.md index 1d0fdff9..1cd5ddd3 100644 --- a/docs/decorators.md +++ b/docs/decorators.md @@ -10,12 +10,12 @@ Most decorators are combination of multiple decorators to make code more lesser | `@IsNumberField(options?:IsNumberFieldOptions)` | Checks if given value is number, is required and has supplied min and max value | | `@IsStringField(options?: IsStringFieldOptions)` | Checks if given value is string, is required and has supplied minlength and maxlength | | `@IsEnumField(options?: IsEnumFieldOptions)` | Checks if value is an enum and is required | -| `@IsAfter(value)` | Checks if given date is after the passed date | +| `@IsAfterDateField(value)` | Checks if given date is after the passed date | | `@IsDateInFormat(format)` | Checks if date string is in provided date format | -| `@IsEqualTo(value)` | Checks if value is in equal to the passed value | +| `@IsEqualToField(value)` | Checks if value is in equal to the passed value | | `@IsGreaterThan(values)` | Checks if value is greater than the passed number value | -| `@IsPassword()` | Checks if value is a valid password | -| `@IsUsername()` | Checks if value is a valid username | +| `@IsPasswordField()` | Checks if value is a valid password | +| `@IsUsernameField()` | Checks if value is a valid username | | `@IsProfane()` | Checks if value has curse words | | `@IsUnique()` | Checks if value is unique. Queries database to do so. | | `@UUIDParam(value)` | Checks if passed param is a valid uuid v4 | diff --git a/package.json b/package.json index f14cc9a5..c340fed5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ultimate-nest", - "version": "1.20.0", + "version": "1.23.0", "description": "NestJS + MikroORM blog example with batteries included", "license": "MIT", "author": { @@ -120,7 +120,7 @@ "passport-magic-login": "^1.2.2", "pino-http": "^8.5.0", "pino-pretty": "^10.2.0", - "poolifier": "^2.7.1", + "poolifier": "^2.7.2", "preview-email": "^3.0.19", "prom-client": "^14.2.0", "pug": "^3.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 59b1bad0..00e74107 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -210,8 +210,8 @@ dependencies: specifier: ^10.2.0 version: 10.2.0 poolifier: - specifier: ^2.7.1 - version: 2.7.1 + specifier: ^2.7.2 + version: 2.7.2 preview-email: specifier: ^3.0.19 version: 3.0.19 @@ -11074,8 +11074,8 @@ packages: resolution: {integrity: sha512-3IKLNXclQgkU++2fSi93sQ6BznFuxSLB11HdvZQ6JW/spahf/P1pAHBQEahr20rs0htZW0UDkM1HmA+nZkXKsw==} engines: {node: '>=12.0.0'} - /poolifier@2.7.1: - resolution: {integrity: sha512-GUo7sdUMvHyjMGHE5oMmmzlBYTASMQGznS6Nohl5eVsuLCZTIM5UyozRH47WYMAk9cqzEeoVzKKWpsQes/pCtg==} + /poolifier@2.7.2: + resolution: {integrity: sha512-PazWvHDY+xnSK23QF1jTqo+nMPF1VWI8sS9Q8z8rAkAid6vnHJleMwfofMvN97F9kCSdCLsaL2QMekb/YxF/qA==} engines: {node: '>=16.14.0', pnpm: '>=8.6.0'} requiresBuild: true dev: false diff --git a/src/common/@types/interfaces/validator.interface.ts b/src/common/@types/interfaces/validator.interface.ts index f2545627..15927f13 100644 --- a/src/common/@types/interfaces/validator.interface.ts +++ b/src/common/@types/interfaces/validator.interface.ts @@ -24,14 +24,16 @@ export interface NumberFieldOptions extends BaseValidator, BaseArrayValidator { positive?: boolean } -export type MinMaxLengthOptions = Pick; - export interface FileValidator { fileType?: RegExp | string fileSize?: number required?: boolean } +export type MinMaxLengthOptions = Pick; + export type DateFieldOptions = BaseValidator & BaseArrayValidator; export type EnumFieldOptions = DateFieldOptions; +export type EmailFieldOptions = DateFieldOptions; +export type UUIDFieldOptions = DateFieldOptions; diff --git a/src/common/decorators/validation/is-after.validator.spec.ts b/src/common/decorators/validation/is-after.validator.spec.ts index df22257a..bd7d4dae 100644 --- a/src/common/decorators/validation/is-after.validator.spec.ts +++ b/src/common/decorators/validation/is-after.validator.spec.ts @@ -1,5 +1,5 @@ import {Validator} from 'class-validator'; -import {IsAfter} from './is-after.validator'; +import {IsAfterField} from './is-after.validator'; const validator = new Validator(); @@ -7,7 +7,7 @@ describe('IsAfter', () => { class MyClass { startDate!: Date; - @IsAfter('startDate') + @IsAfterField('startDate') endDate!: Date; } diff --git a/src/common/decorators/validation/is-after.validator.ts b/src/common/decorators/validation/is-after.validator.ts index f6352ef1..b66c2b4b 100644 --- a/src/common/decorators/validation/is-after.validator.ts +++ b/src/common/decorators/validation/is-after.validator.ts @@ -40,3 +40,15 @@ export const IsAfter = ( }); }; }; + + +// // add typesafe property string + +// export const IsAfterField = (property: keyof T, validationOptions?: ValidationOptions) => { +// return applyDecorators( +// IsNotEmpty({ +// message: validationI18nMessage("validation.isNotEmpty"), +// }), +// IsAfter(String(property), validationOptions), +// ); +// }; diff --git a/src/common/decorators/validation/is-email.validator.ts b/src/common/decorators/validation/is-email.validator.ts index c08338eb..8468b613 100644 --- a/src/common/decorators/validation/is-email.validator.ts +++ b/src/common/decorators/validation/is-email.validator.ts @@ -1,7 +1,7 @@ import { applyDecorators } from "@nestjs/common"; import { ArrayNotEmpty, IsArray, IsEmail, IsNotEmpty, IsOptional } from "class-validator"; import { Transform } from "class-transformer"; -import type { EnumFieldOptions as EmailFieldOptions } from "@common/@types"; +import type { EmailFieldOptions } from "@common/@types"; import { validationI18nMessage } from "@lib/i18n"; export const IsEmailField = (options_?: EmailFieldOptions) => { diff --git a/src/common/decorators/validation/is-equal-to.validator.spec.ts b/src/common/decorators/validation/is-equal-to.validator.spec.ts index b6338188..078d9340 100644 --- a/src/common/decorators/validation/is-equal-to.validator.spec.ts +++ b/src/common/decorators/validation/is-equal-to.validator.spec.ts @@ -1,13 +1,13 @@ import {Validator} from 'class-validator'; -import {IsEqualTo} from './is-equal-to.validator'; +import {IsEqualToField} from './is-equal-to.validator'; const validator = new Validator(); -describe('IsEqualTo', () => { +describe('IsEqualToField', () => { class MyClass { password!: string; - @IsEqualTo('password') + @IsEqualToField('password') confirmPassword!: string; } diff --git a/src/common/decorators/validation/is-equal-to.validator.ts b/src/common/decorators/validation/is-equal-to.validator.ts index 32b5774f..27855888 100644 --- a/src/common/decorators/validation/is-equal-to.validator.ts +++ b/src/common/decorators/validation/is-equal-to.validator.ts @@ -25,7 +25,7 @@ class IsEqualToConstraint implements ValidatorConstraintInterface { } } -export const IsEqualTo = ( +export const IsEqualToField = ( property: string, validationOptions?: ValidationOptions, ): PropertyDecorator => { diff --git a/src/common/decorators/validation/is-password.validator.spec.ts b/src/common/decorators/validation/is-password.validator.spec.ts index cf6d1306..10422d7c 100644 --- a/src/common/decorators/validation/is-password.validator.spec.ts +++ b/src/common/decorators/validation/is-password.validator.spec.ts @@ -1,11 +1,11 @@ -import {Validator} from 'class-validator'; -import {IsPassword} from './is-password.validator'; +import { Validator } from 'class-validator'; +import { IsPasswordField } from './is-password.validator'; const validator = new Validator(); describe('IsPassword', () => { class MyClass { - @IsPassword() + @IsPasswordField() password: string; } diff --git a/src/common/decorators/validation/is-password.validator.ts b/src/common/decorators/validation/is-password.validator.ts index 6ad18a47..2d015c9e 100644 --- a/src/common/decorators/validation/is-password.validator.ts +++ b/src/common/decorators/validation/is-password.validator.ts @@ -4,10 +4,14 @@ import type { ValidatorConstraintInterface, } from "class-validator"; import { + IsNotEmpty, ValidatorConstraint, registerDecorator, } from "class-validator"; +import { applyDecorators } from "@nestjs/common"; +import { MinMaxLength } from "./min-max-length.decorator"; import { PASSWORD_REGEX } from "@common/constant"; +import { validationI18nMessage } from "@lib/i18n"; /** * @@ -43,3 +47,17 @@ export const IsPassword = (validationOptions?: ValidationOptions): PropertyDecor }); }; }; + + +export const IsPasswordField = (validationOptions?: ValidationOptions & { minLength?: number; maxLength?: number }) => { + return applyDecorators( + IsNotEmpty({ + message: validationI18nMessage("validation.isNotEmpty"), + }), + MinMaxLength({ + minLength: validationOptions.minLength ?? 8, + maxLength: validationOptions.maxLength ?? 40, + }), + IsPassword(validationOptions), + ); +}; diff --git a/src/common/decorators/validation/is-username.validator.spec.ts b/src/common/decorators/validation/is-username.validator.spec.ts index 3118cc12..98a8e5dc 100644 --- a/src/common/decorators/validation/is-username.validator.spec.ts +++ b/src/common/decorators/validation/is-username.validator.spec.ts @@ -1,11 +1,11 @@ import {Validator} from 'class-validator'; -import {IsUsername} from './is-username.validator'; +import {IsUsernameField} from './is-username.validator'; const validator = new Validator(); describe('IsUserName', () => { class MyClass { - @IsUsername() + @IsUsernameField() username: string; } diff --git a/src/common/decorators/validation/is-username.validator.ts b/src/common/decorators/validation/is-username.validator.ts index 67d7547a..8f928fd9 100644 --- a/src/common/decorators/validation/is-username.validator.ts +++ b/src/common/decorators/validation/is-username.validator.ts @@ -50,14 +50,14 @@ export const IsUsername = (validationOptions?: ValidationOptions): PropertyDecor }; }; -export const IsUsernameField = (validationOptions?: ValidationOptions) => { +export const IsUsernameField = (validationOptions?: ValidationOptions & { minLength?: number; maxLength?: number } ) => { return applyDecorators( IsNotEmpty({ message: validationI18nMessage("validation.isNotEmpty"), }), MinMaxLength({ - minLength: 5, - maxLength: 20, + minLength: validationOptions.minLength ?? 5, + maxLength: validationOptions.maxLength ?? 50, }), IsUsername(validationOptions), ); diff --git a/src/common/decorators/validation/is-uuid.validator.ts b/src/common/decorators/validation/is-uuid.validator.ts index bf0a3ad3..471fc557 100644 --- a/src/common/decorators/validation/is-uuid.validator.ts +++ b/src/common/decorators/validation/is-uuid.validator.ts @@ -1,10 +1,10 @@ import { applyDecorators } from "@nestjs/common"; import { ArrayNotEmpty, IsArray, IsNotEmpty, IsOptional, IsUUID } from "class-validator"; -import type { EnumFieldOptions as UUIDFieldOptions } from "@common/@types"; +import type { UUIDFieldOptions } from "@common/@types"; import { validationI18nMessage } from "@lib/i18n"; /** - * It's a decorator that validates that the field is an uuid value + * It's a decorator that validates that the field is an uuid value (v4) or an array of uuid values (v4) * @param options_ - UUIDFieldOptions * @returns A decorator function that takes in a target, propertyKey, and descriptor. */ diff --git a/src/modules/auth/dtos/reset-password.dto.ts b/src/modules/auth/dtos/reset-password.dto.ts index 28d997e2..ec7fc0c3 100644 --- a/src/modules/auth/dtos/reset-password.dto.ts +++ b/src/modules/auth/dtos/reset-password.dto.ts @@ -1,6 +1,6 @@ import { PickType } from "@nestjs/swagger"; import { IsNotEmpty } from "class-validator"; -import { IsEqualTo, IsPassword, IsStringField } from "@common/decorators"; +import { IsEqualToField, IsPasswordField, IsStringField } from "@common/decorators"; import { validationI18nMessage } from "@lib/i18n"; export class ResetPasswordDto { @@ -18,8 +18,7 @@ export class ResetPasswordDto { * New password of user * @example SomeThingNew7^#% */ - @IsStringField({ minLength: 8, maxLength: 50 }) - @IsPassword({ message: validationI18nMessage("validation.isPassword") }) + @IsPasswordField({ message: validationI18nMessage("validation.isPassword") }) password!: string; /** @@ -28,7 +27,7 @@ export class ResetPasswordDto { */ @IsNotEmpty({ message: validationI18nMessage("validation.isNotEmpty") }) - @IsEqualTo("password") + @IsEqualToField("password") confirmPassword!: string; } @@ -40,6 +39,6 @@ export class ChangePasswordDto extends PickType(ResetPasswordDto, [ * Password of user * @example SomeThingNew7^#% */ - @IsNotEmpty({ message: validationI18nMessage("validation.isNotEmpty") }) + @IsPasswordField({ message: validationI18nMessage("validation.isPassword") }) oldPassword!: string; } diff --git a/src/modules/user/dtos/create-user.dto.ts b/src/modules/user/dtos/create-user.dto.ts index be05000e..6c1353e6 100644 --- a/src/modules/user/dtos/create-user.dto.ts +++ b/src/modules/user/dtos/create-user.dto.ts @@ -4,7 +4,7 @@ import { Roles } from "@common/@types"; import { IsEmailField, IsEnumField, - IsPassword, + IsPasswordField, IsStringField, IsUnique, IsUsernameField, @@ -85,8 +85,7 @@ export class CreateUserDto { * @example SomePassword@123 */ - @IsStringField({ minLength: 8, maxLength: 50 }) - @IsPassword({ message: validationI18nMessage("validation.isPassword") }) + @IsPasswordField({ message: validationI18nMessage("validation.isPassword") }) password!: string; /**