Two validators for one single entity in DTO - node.js

Are there any ways or would it be possible to have two validator in one single entity? Like for the given example code below, the identifier would accept an email as its payload but it would also accept
number/mobile number as its payload as well.
#ApiProperty()
#IsString()
#IsNotEmpty()
#IsEmail()
identifier: string;
EDIT:
I have tried,
#ApiProperty()
#IsString()
#IsNotEmpty()
#IsEmail()
#IsPhoneNumber('US')
identifier: string;
But it does not work.
EDIT 2:
I found a reference code based on this previous thread, How to use else condition in validationif decorator nestjs class-validator?, and I copied his validation class.
import { ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments } from "class-validator";
import { IdentifierType } from "../interface/access.interface";
#ValidatorConstraint({ name: 'IdentifierValidation', async: false })
export class IdentifierValidation implements ValidatorConstraintInterface {
validate(identifier: string, args: ValidationArguments) {
if (JSON.parse(JSON.stringify(args.object)).type === IdentifierType.MOBILE) {
var regexp = new RegExp('/^[\+]?[(]?[0-9]{3}[)]?[-\s\.]?[0-9]{3}[-\s\.]?[0-9]{4,6}$/im');
// "regexp" variable now validate phone number.
return regexp.test(identifier);
} else {
regexp = new RegExp("^[a-zA-Z0-9_.+-]+#[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$");
// "regexp" variable now validate email address.
return regexp.test(identifier);
}
}
defaultMessage(args: ValidationArguments) {
if (JSON.parse(JSON.stringify(args.object)).type === IdentifierType.MOBILE) {
return 'Enter a valid phone number.'
} else {
return 'Enter a valid email address.'
}
}
}
DTO -
export class VerifyOtpDto {
#Validate(IdentifierValidation)
#ApiProperty()
#IsNotEmpty()
identifier: string;
#ApiProperty({ enum: IdentifierType })
#IsNotEmpty()
identifierType: IdentifierType;
}
ENUM -
export enum IdentifierType {
EMAIL = 'email',
MOBILE = 'mobile',
}
It does work with email but trying to feed a mobile number still does not work.

You have two ways to do this, first with regex:
#Matches(/YOUR_REGEX/, {message: 'identifier should be email or phone'})
identifier: string;
Or you can get the idea from this:
#IsType(Array<(val: any) => boolean>)
#IsType([
val => typeof val == 'string',
val => typeof val == 'boolean',
])
private readonly foo: boolean | string;

Of course it can get more than one validator in one DTO column.
Did you check https://www.npmjs.com/package/class-validator here?
if you want to check mobile number, you can use to #IsMobilePhone(locale: string).

Related

How to remove Field Name in custom message in class-validator NestJS

I have problems with regards to creating custom messages using the class-validator. It always returns the field name.
export class EditOrderAddressDto implements Partial<CreateOrderAddressDto> {
#IsOptional()
#IsString()
#MinLength(1, { message: 'Last Name should at least have more than 1 character.'})
lastName?: string;
}
export class EditOrderDto implements EditOrderDtoModel {
#ApiProperty({ required: false })
#IsOptional()
#ValidateNested()
#Type(() => EditOrderAddressDto)
billingAddress?: Partial<CreateOrderAddressDto>;
}
Then for my payload I just pass this.
{ deliveryAddress:
{ lastName: "" }
}
And I get this as the error message "deliveryAddress.Last Name should at least have more than 1 character.'
Is there any way I can remove the deliveryAddress and return only the custom message?

Transform the #Body without requiring it in NestJs

What I basically want to do is to parse the date string from the request to a Date object like in this question.
However, this is not my use case because in my case the date is not required. So if I use the solution from the question above it responds with a 400: due must be a Date instance.
This is my DTO:
export class CreateTaskDto {
#IsDefined()
#IsString()
readonly name: string;
#IsDefined()
#IsBoolean()
readonly done: boolean;
#Type(() => Date)
#IsDate()
readonly due: Date;
}
Then in my controller:
#Post('tasks')
async create(
#Body(new ValidationPipe({transform: true}))
createTaskDto: CreateTaskDto
): Promise<TaskResponse> {
const task = await this.taskService.create(createTaskDto);
return this.taskService.fromDb(task);
}
Post request with this payload is working fine:
{
"name":"task 1",
"done":false,
"due": "2021-07-13T17:30:11.517Z"
}
This request however fails:
{
"name":"task 2",
"done":false
}
{
"statusCode":400
"message":["due must be a Date instance"],
"error":"Bad Request"
}
Is it somehow possible to tell nestjs to ignore transformation if there is no date?
#IsOptional()
Checks if given value is empty (=== null, === undefined) and if so, ignores all the validators on the property.
https://github.com/typestack/class-validator#validation-decorators
#Type(() => Date)
#IsDate()
#IsOptional()
readonly due?: Date;

TypeORM getRawOne<T> not returning type T

I'm working on refactoring a koa api to nest and am kinda stuck on refactoring the queries from native psql to typeorm. I have the following table, view and dto.
#Entity()
export class Challenge {
#PrimaryGeneratedColumn()
id!: number;
#Column()
endDate!: Date;
#CreateDateColumn()
createdAt!: Date;
}
#ViewEntity({
expression: (connection: Connection) => connection.createQueryBuilder()
.select('SUM(cp.points)', 'score')
.addSelect('cp.challenge', 'challengeId')
.addSelect('cp.user', 'userId')
.addSelect('RANK() OVER (PARTITION BY cp."challengeId" ORDER BY SUM(cp.points) DESC) AS rank')
.from(ChallengePoint, 'cp')
.groupBy('cp.challenge')
.addGroupBy('cp.user')
})
export class ChallengeRank {
#ViewColumn()
score!: number;
#ViewColumn()
rank!: number;
#ViewColumn()
challenge!: Challenge;
#ViewColumn()
user!: User;
}
export class ChallengeResultReponseDto {
#ApiProperty()
id!: number;
#ApiProperty()
endDate!: Date;
#ApiProperty()
createdAt!: Date;
#ApiProperty()
score: number;
#ApiProperty()
rank: number;
test() {
console.log("test")
}
}
As the object I want to return is not of any entity type, I'm kinda lost on how to select it and return the correct class. I tried the following:
this.challengeRepository.createQueryBuilder('c')
.select('c.id', 'id')
.addSelect('c.endDate', 'endDate')
.addSelect('c.createdAt', 'createdAt')
.addSelect('cr.score', 'score')
.addSelect('cr.rank', 'rank')
.leftJoin(ChallengeRank, 'cr', 'c.id = cr."challengeId" AND cr."userId" = :userId', { userId })
.where('c.id = :id', { id })
.getRawOne<ChallengeResultReponseDto>();
Which returns an object that has the correct fields, but that is not of the class type "ChallengeResultReponseDto". If I try to call the function "test" the application crashes. Further it feels weird to use the challengeRepository but not return a challenge, should I use the connection or entity manager for this instead?
I'm rather certain that getRawOne<T>() returns a JSON that looks like whatever you give the generic (T), but an not instance of that class. You should try using getOne() instead to get the instance of the returned entity

How to store big int in nest js using typeorm

some.entity.ts
amount:number
But when I store a very large data in my postgres it throws error '''integer out of range'''
My question is how can I store Big Int as type in psql using typeorm
Define type bigint in #Column decorator,
#Column({type: 'bigint'})
columnName: string;
Note: that based on TypeOrm documentation bigint is mapped to string.
Great resonse from #Riajul Islam!
As an addition to his answer, if you want to store bigint in PrimaryGeneratedColumn, you should you the following:
#PrimaryGeneratedColumn( 'increment', {type: 'bigint'} )
id: number;
Just add { bigNumberStrings: false } to TypeORM's configuration, such as:
TypeOrmModule.forRoot({
bigNumberStrings: false,
...config.database,
}),
Then the bigint will return number type.
I used column transformer:
export class ColumnNumberTransformer {
public to(data: number): number {
return data;
}
public from(data: string): number {
// output value, you can use Number, parseFloat variations
// also you can add nullable condition:
// if (!Boolean(data)) return 0;
return parseInt(data);
}
}
Then in entity:
#Entity('accounts')
export class AccountEntity extends BaseEntity {
#Column({
type: 'bigint',
nullable: false,
transformer: new ColumnNumberTransformer()
})
public balance: number;
}

Sequelize-typescript 'HasManyCreateAssociationMixin' is not a function

I have a model in sequelize-typescript, Door.ts:
import { Table, Model, Column, AutoIncrement, PrimaryKey, ForeignKey, DataType, AllowNull, BelongsTo, HasMany } from 'sequelize-typescript';
import { Location } from '#modules/location';
import { AkilesServiceV1, AkilesServiceV0, IDoorService } from '#services/DoorService';
import { BelongsToGetAssociationMixin } from 'sequelize/types';
import { DoorLog } from '#modules/door_log';
import { HasManyCreateAssociationMixin } from 'sequelize';
#Table({ tableName: 'door' })
class Door extends Model<Door> {
#PrimaryKey
#AutoIncrement
#Column
id!: number;
#AllowNull(false)
#Column
type!: string;
#Column
button_id!: string;
#Column
gadget_id!: string;
#Column
action_id!: string;
#AllowNull(false)
#Column(DataType.ENUM('vehicular','pedestrian'))
access_type!: 'vehicular' | 'pedestrian';
#AllowNull(false)
#Column
description_tag!: string;
#Column(DataType.VIRTUAL)
description!: string;
#ForeignKey(() => Location)
#AllowNull(false)
#Column
location_id!: number;
#BelongsTo(() => Location)
location!: Location;
#HasMany(() => DoorLog)
door_logs!: DoorLog[];
public getLocation!: BelongsToGetAssociationMixin<Location>;
public createDoorLog!: HasManyCreateAssociationMixin<DoorLog>;
public async open () {
let doorService: IDoorService;
switch(this.type) {
case 'akiles-v0':
doorService = new AkilesServiceV0();
break;
case 'akiles-v1':
doorService = new AkilesServiceV1();
break;
default:
doorService = new AkilesServiceV1();
break;
}
//await doorService.open(this);
return await this.createDoorLog({ door_id: this.id, timestamp: new Date() });
}
public async getParking() {
const location: Location = await this.getLocation();
return await location.getParking();
}
}
export default Door
As you can see it has these two functions associated with Mixins:
public getLocation!: BelongsToGetAssociationMixin<Location>;
public createDoorLog!: HasManyCreateAssociationMixin<DoorLog>;
The first works perfectly using it like this: await this.getLocation(). However, the second when I call it like this: await this.createDoorlog ({door_id: this.id, timestamp: new Date ()}) returns the following error:
TypeError: this.createDoorLog is not a function
I've also tried calling the function without parameters but got the same result. I don't understand why the two functions, while created almost identically, behave differently. Am I missing something with HasManyCreateAssociationMixin?
Thank you.
For when I inevitably come across this question again perplexed by the same problem. The answer Is to ad "as" to the #HasMany mixin. Sequelize appears to have issues with camelcase classes.
So in this case adding
#HasMany(() => DoorLog, options: {as: "doorLog" })
door_logs!: DoorLog[];
or something along these lines should allow you to use this mixin

Resources