class-validator case-insensitive enum validation? - node.js

I have anum like this:
export enum UserRole {
USER,
ADMIN,
BLOGGER
}
and create.user.dto like this
import { IsEmail, IsEnum, IsNotEmpty, IsOptional } from 'class-validator';
import { UserRole } from './user.entity';
export class CreateUserDto {
#IsEmail()
email: string;
#IsNotEmpty()
firstName: string;
#IsNotEmpty()
lastName: string;
#IsOptional()
username: string;
#IsOptional()
#IsEnum(UserRole)
role: UserRole;
#IsNotEmpty()
password: string;
}
Now role validation does not fail if I only post the role uppercase('ADMIN','USER') or 'BLOGGER'.
How to make class-validator not case sensitive? I mean, validate true also for 'admin' 'aDmIn'.

then you need a regexp validation via #Matches.
#IsOptional()
#Matches(`^${Object.values(UserRole).filter(v => typeof v !== "number").join('|')}$`, 'i')
role: UserRole;
the final rule is /^USER|ADMIN|BLOGGER$/i, where i ignores the case.

your enum should like this
export enum UserRole {
USER = "USER",
ADMIN = "ADMIN",
BLOGGER = "BLOGGER"
}
is proven works in case-insensitive or vice versa

Related

How to make one of two fields required in Nestjs DTO with its proper API documentation?

For example:
{
phone: string
email: string
....
....
otherFields
....
....
}
Either phone or email should be required in request body. I found solution only for validation, here is:
export class MyDto {
#IsString()
#ValidateIf((obj, value) => !obj.email || value)
phone: string;
#IsString()
#ValidateIf((obj, value) => !obj.phone || value)
phone: string;
..... other fields .....
}
However, how to apply swagger documentation for this?
There are oneOf and anyOf in OAS3 docs. Here is suitable solution in terms of swagger:
link
How is it possible to implement both validation and swagger docs at the same time?
The only alternative that I see for your problem is this.
export class EmailDto {
#ApiProperty()
#IsString()
email: string;
}
export class PhoneDto {
#ApiProperty()
#IsString()
phone: string;
}
export class CreateCatDto {
#ApiProperty({
oneOf: [
{
type: 'object',
required: ['phoneNumber'],
properties: {
phoneNumber: { type: 'string' },
},
},
{
type: 'object',
required: ['email'],
properties: { email: { type: 'string' } },
},
],
})
#ValidateNested()
#Type((type) => (type.object.phoneOrEmail.email ? EmailDto : PhoneDto))
phoneOrEmail: PhoneDto | EmailDto;
}
Here you have the documentation for anyOf in NestJs
I didn't use getSchemaPath because my schema wasn't initialised by Swagger so I create manually my schema in #ApiProperty
And for the class-validator I ask to validate the DTO for the right Type with #ValidateNested() and #Type().
The problem is. If they sent you both email and phoneNumber class-validator will only validate email because:
(type.object.phoneOrEmail.email ? EmailDto : PhoneDto)
If there is an email, it will validate the class EmailDto first. So you need to tell if there are Email and PhoneNumber you need to validate a DTO with both.
Go check on Google if there is an anyOf like for class-validaor

NestJS - TypeORM save() not updating document in the database

I'm in the process of learning NestJS and TypeORM with a simple project where users can book offices. I have defined the office and bookings types and entities, and written a function that searches for an existing office and adds a booking subdocument to its bookings array. Save() returns the office document with the updated bookings field, but for some reason the database is not updated.
The office type looks like this:
import { Field, ID, ObjectType } from '#nestjs/graphql';
import { BookingDto } from '../dto/booking.dto';
#ObjectType()
export class OfficeType {
#Field(() => ID)
id: string;
#Field()
title: string;
#Field()
description: string;
#Field(() => [BookingDto], { nullable: true })
bookings: string[];
#Field()
price: number;
#Field()
owner: string;
}
The booking type inside the 'bookings' field:
import { Field, ObjectType } from '#nestjs/graphql';
import { IsString, IsUUID } from 'class-validator';
#ObjectType()
export class BookingDto {
#IsUUID()
#Field()
id: string;
#IsUUID()
#Field()
officeId: string;
#IsUUID()
#Field()
userId: string;
#IsString()
#Field()
date_from: string;
#IsString()
#Field()
date_until: string;
}
And the function for booking an office:
async bookOffice(bookingInput: BookingInput): Promise<Office> {
const { officeId, userId, date_from, date_until } = bookingInput;
const booking = { id: uuid(), officeId, userId, date_from, date_until };
const office = await this.officeRepository.findOne({
id: officeId,
});
if (!office) {
throw new NotFoundException(`Office not found`);
}
office.bookings
? office.bookings.push(booking)
: (office.bookings = [booking]);
return await this.officeRepository.save(office);
}
According to the TypeORM docs, save() can be used to update documents, so I'm not sure what the problem is here.
You have to save first the booking object and then save the office with the bookings.
booking = await this.bookingRepository.save(booking);
office.bookings ? office.bookings.push(booking)
: (office.bookings = [booking]);
this.officeRepository.save(office);

Exclude() method is not working for post method in Nestjs

I have tried adding toJSON(){return classtoPlain(this)} in my entity still its not working.
Here is my controller
#Controller('users')
#UseInterceptors(ClassSerializerInterceptor)
export class UsersController {
constructor(private readonly usersService: UsersService) {}
#HttpCode(200)
#Post()
async create(#Body() user: User): Promise<User> {
return await this.usersService.create(user).catch((err) => {
throw new HttpException({ message: err.message }, HttpStatus.BAD_REQUEST);
});
}
Here is my Entity
import { Entity, Column, PrimaryGeneratedColumn } from 'typeorm';
import { Gender } from '../Constants/enum';
import { Exclude, instanceToPlain } from 'class-transformer';
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName?: string;
#Column({
type: 'enum',
enum: Gender,
})
gender: Gender;
#Column({ unique: true })
email: string;
#Column()
#Exclude({ toPlainOnly: true })
password: string;
constructor(partial: Partial<User>) {
Object.assign(this, partial);
}
toJSON() {
return instanceToPlain(this);
}
}
Here is my service
import { Injectable } from '#nestjs/common';
import { InjectRepository } from '#nestjs/typeorm';
import { Repository } from 'typeorm';
import { UpdateUserDto } from './dto/update-user.dto';
import { User } from './entities/user.entity';
#Injectable()
export class UsersService {
constructor(
#InjectRepository(User)
private readonly user_detailsRepository: Repository<User>,
) {}
create(user: User) {
return this.user_detailsRepository.save(user);
}
I have tried many solutions but still nothing works.
For GET its working but for post its showing the password field.If any one can provide any solution it would be great.
You're mixing between Entity definition/validation, which typeORM functions/decorators (in your case) should handle. And DTO definition, which class-transformer/validator should handle
So you should define a createUserDTO.ts file like below, and use it as in the controller/service file:
export class createUserDTO {
#IsNotEmpty()
#IsString()
firstName: string;
#IsNotEmpty()
#IsString()
lastName?: string;
#IsNotEmpty()
#IsEnum(Gender)
gender: Gender;
#IsNotEmpty()
email: string;
#Exclude({ toPlainOnly: true })
password: string;
}
The entity file shouldn't have the password column (just omit the field completely), should look like this:
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
firstName: string;
#Column()
lastName?: string;
#Column({
type: 'enum',
enum: Gender,
})
gender: Gender;
#Column({ unique: true })
email: string;
}
Then, when you call return this.user_detailsRepository.save(userDto);, it should work fine

Typeorm Insert Foreign Key by String

When user registers, I want to him to send his account name, so I will have "Accounts" table with reference to the user entity. I'm using Nest.js.
I'm looking for alternative to the following logic in my users.service.ts Register method:
Find Account by Name
If Account not found, Create It
Create User with the Account found above
Here is my Account Entity:
#Entity()
export class Account extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ unique: true })
name: string;
#Column()
#CreateDateColumn()
createdAt: Date;
}
And my User Entity:
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ unique: true })
email: string;
#Column()
password: string;
#ManyToOne(() => Account, (Account) => Account.name, { cascade:true })
#JoinColumn({name: 'name'})
account: Account;
}
My CreateUserDTO:
export class CreateUserDto {
#IsEmail()
email: string;
#IsNotEmpty()
password: string;
#IsNotEmpty()
account: string;
}
And this is the error when I try to do User.create(dto):
Type 'string' is not assignable to type 'Account | DeepPartial<Account>'.
In addition, for some reason, the User.create(dto) returns array of users and not single user, and I don't understand why.
You can proceed like this :
// create-user.dto.ts
export class CreateUserDto {
#IsEmail()
email: string;
#IsString()
#IsNotEmpty()
password: string;
#IsString()
#IsNotEmpty()
accountName: string;
}
#Entity()
export class User extends BaseEntity {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column({ unique: true })
email: string;
#Column()
password: string;
#Column()
name: string; // This is your relations between user and account defined in JoinColumn decorator
#ManyToOne(() => Account, (Account) => Account.name, { cascade: true })
#JoinColumn({ name: 'name' })
account: Account;
}
// user.service.ts
// user.service.ts
#Injectable()
export class UserService {
constructor(
#InjectRepository(Account)
private readonly accountRepository: Repository<Account>, // Or your custom repository
#InjectRepository(User)
private readonly userRepository: Repository<User> // Or your custom repository
) {}
public async register(dto: CreateUserDto): Promise<void> {
let accountToSaveWithUser: Account;
// First check if account exist
const account = await this.accountRepository.findOne({
where: {
name: dto.accountName,
},
});
if (isNil(account)) {
const accountToSave = this.accountRepository.create({
name: dto.accountName,
});
accountToSaveWithUser = await this.accountRepository.save(accountToSave);
} else {
accountToSaveWithUser = account;
}
await this.userRepository.save({
email: dto.email,
password: hash(dto.password), // Use your package for hash password
name: accountToSaveWithUser.name,
});
}
}

class-validator doesn't validate entity

Can I use class-validator to validate Entity columns?
This doesn't validate the columns:
import { IsEmail } from 'class-validator';
#Entity()
export class Admin extends BaseEntity {
#Column({ unique: true })
#IsEmail()
email: string;
}
However when I use the class-validator anywhere else in the code other than entities it validates properly and doesn't allow for bad inputs.
This works:
#InputType()
export class RegisterInput {
#Field()
#IsEmail()
email: string;
}
The Entity should be clean
#Entity()
export class Admin extends BaseEntity {
#Column({ unique: true })
email: string;
}
While you define a DTO for checking the incoming request.
export class AdminDto {
#IsEmail()
email: string;
}
In your controller, you would check the incoming request with your AdminDto.
#Controlller('route')
export class AdminController {
constructor(private yourService: yourserviceClass)
#Post('/api')
async createSomething(#Body('email', ValidationPipe) email: string){ //Your request body //would be checked against your DTO
this.yourService.createMethod(email)
}
Hope this answers your question.

Resources