Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We鈥檒l occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Question] How can i get the id sent on mutation inside my custom validator #1503

Closed
pedrodalvy opened this issue Jan 5, 2022 · 6 comments
Closed
Labels
documentation Improvements or additions to documentation

Comments

@pedrodalvy
Copy link

馃摎 Documentation

I'm using graphql and i need to create a validation for unique fields in the db, for example, the user's email.

When registering a new user, I just see if the email already exists in the database. The problem is in the user update, I need to know how to get the id sent in mutation to ignore it in the query.

I'm using the class-validator as in the example below:

Mutation to update user

mutation{
  updateUser(input:{ id:"1", update:{ email: "any@email.com" } })
  { 
    id
  }
}

DTO's

@InputType()
export class CreateUserInput {
  @Field()
  @IsEmail()
  @MyCustomIsUniqueValidator()
  email: string;
}

@InputType()
export class UpdateUserInput {
  @Field()
  @IsEmail()
  @MyCustomIsUniqueValidator()
  email: string;
}

Custom validator

@Injectable()
@ValidatorConstraint({ async: true })
export class MyCustomIsUniqueValidator implements ValidatorConstraintInterface {
  async validate(email: string, args: ValidationArguments): Promise<boolean> {
    const emailExists = await User.findOne({ where: { email } });

    // how to get the id sent on mutation?
    console.log(args)

    return !emailExists;
  }
}

Console output:

{
   targetName: 'MyCustomIsUniqueValidator',
   property: 'email',
   object: UpdateUserInput { email: 'any@email.com' },
   value: 'any@email.com',
   constraints: []
 }

How can i get the id sent on mutation inside my custom validator?

@pedrodalvy pedrodalvy added the documentation Improvements or additions to documentation label Jan 5, 2022
@pedrodalvy pedrodalvy changed the title How can i get the id sent on mutation inside my custom validator [Question] How can i get the id sent on mutation inside my custom validator Jan 5, 2022
@pedrodalvy
Copy link
Author

Thanks for your time @impcyber, but I've tried this way and it didn't work.

Using default update method from nestjs-query, the args.object only returns informations from the mutation "update" field, without the id.

As I still don't quite understand how nestjs-query works with the class-validator, I'm overriding the service class to do this validation.

@impcyber
Copy link

I think you should use @BeforeCreateHook and @BeforeUpdateHook in this case
https://doug-martin.github.io/nestjs-query/docs/graphql/hooks#hook-class
You can get id, input for create and update for update separately from instance property.

Here is an example: https://github.com/doug-martin/nestjs-query/blob/master/examples/hooks/src/hooks.ts#L25

And there is from my code:

import {
  BeforeCreateOneHook,
  CreateOneInputType,
  OperationGroup,
} from '@nestjs-query/query-graphql'
import { BadRequestException, Injectable } from '@nestjs/common'
import { ReturnModelType } from '@typegoose/typegoose'
import assert from 'assert'
import { AccessService } from 'nest-casl'
import { InjectModel } from 'nestjs-typegoose'
import { AccountInputDTO } from '../dto/account'
import { AccountEntity } from '../entities'
import { IAccountContext } from './../interfaces/account-payload.interface'

@Injectable()
export class AccountCreateHook<T>
  implements BeforeCreateOneHook<T, IAccountContext>
{
  public constructor(
    @InjectModel(AccountEntity)
    private readonly accountModel: ReturnModelType<typeof AccountEntity>,
    private readonly accessService: AccessService,
  ) {}

  public async run(
    instance: CreateOneInputType<T>,
    context: IAccountContext,
  ): Promise<CreateOneInputType<T>> {
    const { account } = context.req

    return this.accountModel
      .exists({
        email: (instance.input as unknown as AccountInputDTO).email,
      })
      .then((exists): CreateOneInputType<T> => {
        assert(!exists, new Error())

        const granted = this.accessService.hasAbility(
          account,
          OperationGroup.CREATE,
          instance.input,
        )

        assert(granted, new Error())

        return instance
      })
      .catch(() => {
        throw new BadRequestException()
      })
  }
}

@pedrodalvy
Copy link
Author

This is a very interesting solution, I hadn't thought of using hooks for this. I'm sure it's going to be better than my solution.

Thank you for your help, @impcyber.

@magrinj
Copy link

magrinj commented Dec 5, 2023

In case anyone get here during search, inside args of class-validator the object key only give you the current objet being validated.
I've made a PR#2327 adding an instance key that give you access to the whole object, it's fixing this issue because you can access the id from it.

@magrinj
Copy link

magrinj commented Dec 5, 2023

BTW: The problem I'm facing now is for async custom validator, it seems if I'm editing the input inside the BeforeCreateOne hook, my servie doesn't receive the edited input.
If I change my custom validator to something sync it works fine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation
Projects
None yet
Development

No branches or pull requests

3 participants