MongoDB and class-validator unique validation - NESTJS - javascript

TL;DR
I am trying to run mongoose query in my validator
Hello, I am trying to make a custom decorator which throws an error if a value for that field already exists. I am trying to use the mongoose model inside the class that validates the route. Unlike in resolver/controller, #InjectModel() does not work in validator class. My validator is like this
import { getModelToken, InjectModel } from "#nestjs/mongoose";
import {
ValidationArguments,
ValidatorConstraint,
ValidatorConstraintInterface,
} from "class-validator";
import { Model } from "mongoose";
import { User } from "../schema/user.schema";
#ValidatorConstraint({ name: "IsUniqueUser", async: true })
export class UniqueValidator implements ValidatorConstraintInterface {
constructor(
#InjectModel(User.name)
private readonly userModel: Model<User>,
) {}
async validate(value: any, args: ValidationArguments) {
const filter = {};
console.log(this.userModel);
console.log(getModelToken(User.name));
filter[args.property] = value;
const count = await this.userModel.count(filter);
return !count;
}
defaultMessage(args: ValidationArguments) {
return "$(value) is already taken";
}
}
and my DTO that uses the above decorator is
#InputType({})
export class UserCreateDTO {
#IsString()
name: string;
#IsUniqueUser({
message: "Phone number is already taken",
})
#Field(() => String)
phone: string;
}
The console says
cannot read value count of undefined implying that userModel is undefined.
InShort
I want to run the query in my validator. How can I do so?

According to this issue (you can't inject a dependency)
You should to add in your main.ts
import { useContainer } from 'class-validator';
useContainer(app.select(AppModule), {fallbackOnErrors: true});
Then you need to add your UniqueValidator to your module like an #Injectable() class
so
...
providers: [UniqueValidator],
...
Then, in your DTO you can add:
#Validate(UniqueValidator, ['email'], {
message: 'emailAlreadyExists',
})

Related

ERROR [ExceptionHandler] Cannot read property 'switchToHttp' of undefined TypeError: Cannot read property 'switchToHttp' of undefined

what I am trying to do?
I am trying to implement a logger to log queries from TypeOrm and also add some information to the logs to identify the user
how I am trying to achieve this?
I have implemented the Logger interface from TypeOrm and used Winston for logging.
To identify the user I want to extract the JWT token for the particular request that is executing the query, from which I will have the user.id key.
To get the JWT I am using the ExecutionContext class provided by NestJs to get the request body and then extract the JWT token.
what issue I am facing?
I have given the implementation below and the error for it, I can see that the execution context is undefined and I am not sure how to pass the value to it, I am new to nest, and if I am not wrong, nest takes cares of the assignment of the ExecutionContext class.
Also, I am open to suggestions to implement it in a different way.
The Error:
[Nest] 11968 - ERROR [TypeOrmModule] Unable to connect to the database. Retrying (9)...
TypeError: Cannot read property 'switchToHttp' of undefined
The Implementation:
/* eslint-disable prettier/prettier */
import { ExecutionContext } from '#nestjs/common';
import { Logger, QueryRunner } from 'typeorm';
import { createLogger, Logger as winstonLogger, transports, format } from 'winston';
import { Format } from 'LogForm';
import { Request } from 'express';
export class CustomLogger implements Logger {
private readonly queryLogger: winstonLogger;
private readonly winstonFormat: Format;
private readonly ctt: ExecutionContext;
healthcheck2(ctx: ExecutionContext): any {
const payload = ctx.switchToHttp().getRequest<Request>();
console.log(payload);
}
constructor(){
this.winstonFormat= format.printf(({message})=> {
return `${message}`
});
const options = (filename: string) => ({
transports: [new transports.Console({level:'debug'})],
format: this.winstonFormat
});
this.queryLogger = createLogger(options('query.log'))
}
logQuery(query: string, _parameters?: any[], _queryRunner?: QueryRunner) {
this.healthcheck2(this.ctt);
this.queryLogger.log({
level: 'debug',
message: 'query logged',
timestamp: Date.now(),
label: 'query'
})
}
logQueryError(error: string | Error, query: string, parameters?: any[], queryRunner?: QueryRunner) {
console.log('query error');
}
logQuerySlow(time: number, query: string, parameters?: any[], queryRunner?: QueryRunner) {
console.log('other error');
}
logSchemaBuild(message: string, queryRunner?: QueryRunner) {
console.log('schema build');
}
logMigration(message: string, queryRunner?: QueryRunner) {
console.log('other error');
}
log(level: 'log' | 'info' | 'warn', message: any, queryRunner?: QueryRunner) {
console.log('normal log')
}
}

Inject service into NestJS guard

We use ThrottlerGuard in our NestJS application from #nestjs/throttler package to rate limit connections and it's implemented like this:
#Injectable()
export class RateLimiter extends ThrottlerGuard {
RATE_LIMIT_EXCEEDED = 'Rate limit exceeded.';
async handleRequest(context: ExecutionContext, limit: number, ttl: number) {
const wsData = context.switchToWs().getData();
const metadata: MetadataObject = wsData.internalRepr;
const clientTokenMetadata = metadata.get(TOKEN_NAME) as MetadataValue[];
const clientToken = clientTokenMetadata[0].toString();
const tokensArray = await this.storageService.getRecord(clientToken);
if (tokensArray.length >= limit) {
throw new RpcException({
code: Status.UNAVAILABLE,
message: this.RATE_LIMIT_EXCEEDED,
});
}
await this.storageService.addRecord(clientToken, ttl);
return true;
}
}
Now, I need to inject another service, let's say ConfigService with the usage of constructor, but because it is an extended class, I need to call super(); method which required three more original arguments inherited from ThrottlerGuard, but they are not exported from the package. Is there any other way to make it work besides the method with DI in constructor?
What you can do, instead of extending ThrottleGuard is injecting it in your constructor and call canActivate() in handleRequest(). Benefit when not extending you don'
I haven't tested this but you can try the following:
import {ExecutionContext, Injectable} from "#nestjs/common";
import {ConfigService} from "#nestjs/config";
import {InjectThrottlerStorage, ThrottlerGuard, ThrottlerStorage} from '#nestjs/throttler'
#Injectable()
export class RateLimiter {
constructor(
private throttle: ThrottlerGuard,
private configService: ConfigService,
#InjectThrottlerStorage() private storage: ThrottlerStorage) {
}
async handleRequest(context: ExecutionContext, limit: number, ttl: number) {
// TODO something with ConfigService
return this.throttle.canActivate(context);
}
}
ThrottleGuard source code

How to pass a generic type in the type?

I am trying to create a new constraint for Typestack Class Validators. "IsUnique" constraint will take an Entity as the type and it's column as an argument to check if that column does not exist in the database and is unique.
I have tried the code below but somehow I'm not being able to pass a type to the "IsUniqueConstraint" through validator key in the registerDecorator. Since, I'm new to Typescript so I don't understand its concepts well.
Can someone please help me to know how can we do it?
is-unique.constraint.ts
import { registerDecorator, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments, ValidationOptions } from 'class-validator';
import { Injectable } from '#nestjs/common';
import { Repository } from 'typeorm';
#ValidatorConstraint({ name: 'isUnique', async: true })
#Injectable()
export class IsUniqueConstraint<T> implements ValidatorConstraintInterface {
constructor(private readonly repository: Repository<T>) { }
async validate(value: string, args: ValidationArguments) {
const [column] = args.constraints;
const result = await this.repository.findOne({ where: { [column]: value } });
if (result) {
return false;
}
return true;
}
defaultMessage(args: ValidationArguments) {
return `"${args.value}" already exists for ${args.constraints[0]}`;
}
}
export function IsUnique<T>(column: string, validationOptions?: ValidationOptions) {
return (object: object, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [column],
validator: IsUniqueConstraint,
});
};
}
user.dto.ts
import { IsNotEmpty } from 'class-validator';
import { IsUnique } from './../shared/constraints/is-unique.constraint';
import { User } from './user.entity';
export class CreateUserDto {
#IsNotEmpty()
#IsUnique<User>('username')
readonly username: string;
}
Generics are generally a compile-time-only feature. Unless you have some way of emitting the metadata, including the generics (not sure if that is easily possible).
If you need to use a type at run-time, you generally should pass it as a regular argument, so in this case the signature has to change to accommodate this:
#IsUnique(User, 'username')
This is probably why when injecting repositories you do it via #InjectRepository(User), which also takes the entity class as argument. I doubt that IsUniqueConstraint can have the repository injected as is. You would probably need to resolve it from the DI container/connection manager based on the entity type passed by the decorator.
According to the docs you can directly assign an object to validator, not just a class/constructor, so you can create a concrete instance of your validator, manually passing the resolved repository to the constructor.
So, maybe something along those lines:
import { getRepository } from "typeorm";
// ...
export function IsUnique(
entity: Function,
column: string,
validationOptions?: ValidationOptions) {
// Not sure if this works here. Maybe it needs to be
// moved into the returned function or a different resolution
// mechanism is required.
const repository = getRepository(entity);
return (object: object, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [column],
validator: new IsUniqueConstraint(repository),
});
};
}
Okay, after trying a lot I have solved it the other way. Thanks to #H.B. for showing me the path.
To do the same I passed the entity to the validator and generated repository in the class itself. Because Nest JS Injection was working for the classes only.
import { registerDecorator, ValidatorConstraint, ValidatorConstraintInterface, ValidationArguments, ValidationOptions } from 'class-validator';
import { Injectable } from '#nestjs/common';
import { Connection } from 'typeorm';
import { InjectConnection } from '#nestjs/typeorm';
#ValidatorConstraint({ name: 'isUnique', async: true })
#Injectable()
export class IsUniqueConstraint implements ValidatorConstraintInterface {
constructor(#InjectConnection() private readonly connection: Connection) { }
async validate(value: string, args: ValidationArguments) {
const [entity, column] = args.constraints;
const repository = this.connection.getRepository(entity);
const result = await repository.findOne({ where: { [column]: value } });
if (result) {
return false;
}
return true;
}
defaultMessage(args: ValidationArguments) {
return `"${args.value}" already exists for ${args.constraints[1]}`;
}
}
export function IsUnique(entity: Function, column: string, validationOptions?: ValidationOptions) {
return (object: object, propertyName: string) => {
registerDecorator({
target: object.constructor,
propertyName,
options: validationOptions,
constraints: [entity, column],
validator: IsUniqueConstraint,
});
};
}

Serialization: How to exclude Entity columns in json response but not internal queries in Nestjs

Edit:
I have looked at this question/answer How to exclude entity field from controller json
But, as per below - this is excluding that field from all queries (to the porint where when trying to process user validation, the password field is excluded using the findOne repository query on a route/controller method that does not have ClassSerializerInterceptor
I have an entity within nest.js / typeorm; I am trying to exclude the password field from the returned json, but not exclude the password field from any repository queries within my service. For example:
user.entity.ts:
import { Entity, Column, PrimaryGeneratedColumn, CreateDateColumn,
UpdateDateColumn, ManyToOne } from 'typeorm';
import { Exclude } from 'class-transformer';
import { Account } from '../accounts/account.entity';
#Entity()
export class User {
#PrimaryGeneratedColumn('uuid')
id: string;
#Column()
firstName: string;
#Column()
lastName: string;
#Column({
unique: true,
})
email: string;
#Column()
password: string;
}
auth.controller.ts:
import { Controller, Post, Body, Request, Req, Get, UseInterceptors, ClassSerializerInterceptor, UseGuards } from '#nestjs/common';
import { AuthGuard } from '#nestjs/passport';
import { AuthService } from './auth.service';
import { IUserRequest } from '../../interfaces/user-request.interface';
#Controller('auth')
export class AuthController {
constructor(private readonly authService: AuthService) {}
#Post('/login')
async login(#Request() req: Request) {
const user = await this.authService.checkCredentials(req.body);
return this.authService.logUserIn(user.id);
}
#Get('/profile')
#UseGuards(AuthGuard())
#UseInterceptors(ClassSerializerInterceptor)
async profile(#Request() req: IUserRequest) {
const profile = await this.authService.getLoggedInProfile(req.user.id);
return { profile };
}
}
If I add Exclude() to password like so
#Exclude()
#Column()
password: string;
the password is included in the response
If I remove the Column() from password,
#Exclude()
password: string;
Password is excluded from response and all internal queries such as:
const user = await this.userRepository.findOne({ where: { id }, relations: ['account']});
Is this possible in nest.js using the ClassSerializerInterceptor?
If so, would appreciate a pointer in the right direction.
You can skip properties depending on the operation. In your case, you would use:
#Column()
#Exclude({ toPlainOnly: true })
password: string;
This means, that password is only skipped when a class is transformed to json (when you send a response) and not when json is transformed to a class (when you get a request).
Then add the #UseInterceptors(ClassSerializerInterceptor) to your controller or a controller method. This will automatically transform an entity class to json, when you return it.
For the ClassSerializerInterceptor to work, make sure that your entity was transformed to a class first. This can be automatically done by using the ValidationPipe with the { transform: true} option or by returning an entity from a repository (database). Also, you have to return the entity itself:
#Post()
#UseInterceptors(ClassSerializerInterceptor)
addUser(#Body(new ValidationPipe({transform: true})) user: User) {
// Logs user with password
console.log(user);
// Returns user as JSON without password
return user;
}
Otherwise, you have to transform it manually:
async profile(#Request() req: IUserRequest) {
// Profile comes from the database so it will be an entity class instance already
const profile = await this.authService.getLoggedInProfile(req.user.id);
// Since we are not returning the entity directly, we have to transform it manually
return { profile: plainToClass(profile) };
}
Would advice also looking at TypeOrm hidden-columns
Here you have #Column({select: false}) on your password column , all requests using a standard find or query will exclude the password column.
import {Entity, PrimaryGeneratedColumn, Column} from "typeorm";
#Entity()
export class User {
#PrimaryGeneratedColumn()
id: number;
#Column()
name: string;
#Column({select: false})
password: string;
}
Then during validations/cases where you need the password you do
const users = await connection.getRepository(User)
.createQueryBuilder()
.select("user.id", "id")
.addSelect("user.password")
.getMany();

typescript interface extends mock

I have a typescript interface cRequest that's being passed as a type in a class method.
This interface also extends express Request type. What's the correct way to mock this in jest or typemoq?
import { Request } from 'express';
import { User } from '.';
export interface cRequest extends Request {
context: {
ID?: string;
user?: string;
nonUser?: string;
};
user?: User;
}
export default cRequest;
This is being used in a class method as follows
import { Response } from 'express';
public getData = async (req: cRequest, res: Response) => {}
And if i try to test it as follows it fails
const expRespMock: TypeMoq.IMock<Response> = TypeMoq.Mock.ofType<Response>();
const cReqMock: TypeMoq.IMock<cRequest> = TypeMoq.Mock.ofType<cRequest>();
await classObj.getData(cReqMock, expRespMock);
with the following message
Argument of type 'IMock<cRequest>' is not assignable to parameter of type 'cRequest'.
Property 'context' is missing in type 'IMock<cRequest>'.
What's the correct way to inject this interface mock into the method in tests?
I was able to overcome this problem as follows
const cRequestStub= <cRequest> {};
And also it's now possible to selectively inject params as i need without any errors
const cRequestStub= <cRequest> {user: undefined}; or
const cRequestStub= <cRequest> {context: {ID: '', user: '', nonUser: ''}};
await classObj.getData(cRequestStub, expRespMock);
Why to invent the wheel?
Jest has an interface that "decorates" given interface with the additional methods.
For example:
interface IRequest {
req: string;
}
requestMock: jest.Mocked<IRequest>;
requestMock will have the IRequest interface + the mocks interface.
The simplest way to learn about new jest types is to check the source-code of the #types/jest lib, for example I've checked that is the return type of jest.spyOn and from that I've learned about jest.Mocked<>.

Categories

Resources