How to use class-validator validate Date is older than now and unique key in an array? - validation

Using class-validator with Nest.js. I want to validate these two cases:
Validate the input date is older than now, then give a message: Date can't before than now.
#Field(() => Date, { description: 'Due Date' })
dueDate: Date;
Validate if all of the keys are unique in an array. But this way only can check if the ID is uuid. Is it possible to check if the IDs are the same in the array? Ex: ['1234-1234-1234-1234', '1234-1234-1234-1234']
#Field(() => [String], { description: 'product IDs' })
#IsUUID('all', { each: true, message: 'Product ID is not valid.' })
productIds: string[];

I searched and couldn't find a suitable inheriting validation decorators. You can custom validation classes like this:
#ValidatorConstraint()
export class IsAfterNowConstraint implements ValidatorConstraintInterface {
validate(date: Date) {
return Date.now() < date.getTime();
}
defaultMessage(args: ValidationArguments) {
return `Date ${args.property} can not before now.`;
}
}
function IsAfterNow(validationOptions?: ValidationOptions) {
// eslint-disable-next-line #typescript-eslint/ban-types
return function (object: Object, propertyName: string) {
registerDecorator({
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: IsAfterNowConstraint,
});
};
}
#ArrayUnique(identifier?: (o) => any): Checks if all array's values are unique. Comparison for objects is reference-based. Optional function can be speciefied which return value will be used for the comparsion.

You can compare two dates with this function
import { ValidateBy, ValidationOptions, buildMessage, ValidationArguments } from 'class-validator'
export const IsAfter = (property: string, options?: ValidationOptions): PropertyDecorator =>
ValidateBy(
{
name: 'IsAfter',
constraints: [property],
validator: {
validate: (value: Date, args: ValidationArguments): boolean => {
const [relatedPropertyName] = args.constraints
const relatedValue = (args.object as Record<string, unknown>)[relatedPropertyName] as Date
return value.toISOString() > relatedValue.toISOString()
},
defaultMessage: buildMessage((each: string): string => each + '$property must be after $constraint1', options),
},
},
options,
)
in this kind of situations
#IsDate()
#Type(() => Date)
#IsNotEmpty()
readonly from!: Date
#IsAfter('from')
#IsDate()
#Type(() => Date)
#IsNotEmpty()
readonly to!: Date

Related

Class validator with numbers and enum

I want to create a DTO in nestjs using class-validator.
The value can accept a value between 1-24,trial or lifetime
I created an enum like this
export enum PeriodEnum {
"trial" = "trial",
"lifetime" = "lifetime"
}
And I tried to used this validation
#IsNotEmpty()
#ApiProperty(CustomersConfigSwagger.API_PROP_REF_PERIOD)
#Min(0)
#Max(24)
#IsEnum(Object.keys(PeriodEnum))
period: string;
I get an error if I pass 1:
"period must be a valid enum value",
"period must not be greater than 10",
"period must not be less than 0"
I tried to add
"1" = "1"
But the "An enum member cannot have a numeric"
I tried even with RegExp
#IsNotEmpty()
#ApiProperty(CustomersConfigSwagger.API_PROP_REF_PERIOD)
#Matches(`/^(${Object.keys(PeriodEnum)}|[1-9]|1[0-9]|2[0-4])$/`)
period: string;
I edited my DTO like this:
#IsNotEmpty()
#ApiProperty(CustomersConfigSwagger.API_PROP_REF_PERIOD)
#Matches(`^(${Object.values(PeriodEnum).join("|")}|[1-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4])$`)
period: string;
It works as I want now
since you are accepting strings, #Min and #Max no longer make sens. so here you need to deal with your numbers as string.
what I suggest is to create your own validation decorator :
export function IsLifetimeOrTrialOrBetween1and24(
validationOptions?: ValidationOptions
) {
return function (object: Object, propertyName: string) {
registerDecorator({
name: "IsLifetimeOrTrialOrBetween1and24",
target: object.constructor,
propertyName: propertyName,
options: validationOptions,
validator: {
validate(value: any) {
// you manage your logic here then return either TRUE or FALSE
},
},
});
};
}
then use it like this :
#IsLifetimeOrTrialOrBetween1and24({
message: 'should be lifetime trial or a number between 1 and 24',
})
readonly period: string;
learn more here
but if you don't want to custom validation decorator this is an ugly solution but it works fine :
#IsIn(['trial', 'lifetime', '1', '2', '3', '4', '5', '6', '7',...,'23','24'])
readonly period: string;

Get ManyToOne field using TypeORM and Graph QL query

I have a problem with a TypeORM entity from my project.
The code for entity is shown below.
import { Field, ObjectType } from "type-graphql";
import { BaseEntity, Column, Entity, ManyToOne, PrimaryColumn, PrimaryGeneratedColumn } from "typeorm";
import { MusicProvider } from "./MusicProvider";
import { User } from "./User";
#ObjectType()
#Entity()
export class UserMusicProvider extends BaseEntity {
#Field()
#PrimaryGeneratedColumn()
id!: number;
#Field(() => User)
#ManyToOne(() => User, (user) => user.id, { onDelete: 'CASCADE', cascade: true })
user: User
#PrimaryColumn()
userId: number
#Field(() => MusicProvider)
#ManyToOne(() => MusicProvider, (musicProvider) => musicProvider.id, { onDelete: 'CASCADE', cascade: true })
musicProvider: MusicProvider
#Column()
musicProviderId: number
#Field()
#Column()
musicProviderUserId: string;
}
As you can see, it has a field for MusicProvider. Into my GraphQL query I want to be able to get the MusicProvider's data, because I want to show this data into my app. I want to do this whitin this query, not using two queries.
The problem is that when I call this query:
query{
getUserMusicProviders(userId: 27){
userMusicProviders{
id
musicProviderUserId
musicProvider{
id
name
}
}
errors{
field
message
}
}
}
i receive an error saying:
Cannot return null for non-nullable field
UserMusicProvider.musicProvider.
which is true, because, as I checked, the MusicProvider returned is undefined. I don't know why this happens and would be really helpful to solve this issue.
The code for my query is this:
#Query(() => UserMusicProvidersResponse)
async getUserMusicProviders(
#Arg("userId") userId: number
): Promise<UserMusicProvidersResponse | null> {
const resp = await UserMusicProvider.find({ where: { userId } })
return {
userMusicProviders: resp
}
}
and the code for the UserMusicProvidersResponse is:
#ObjectType()
export class UserMusicProvidersResponse {
#Field(() => [FieldError], { nullable: true })
errors?: FieldError[];
#Field(() => [UserMusicProvider], { nullable: true })
userMusicProviders?: UserMusicProvider[]
}
Thank you for your time and thank you for trying to help me. If you want to clarify something, feel free to send a comment.

Loopback4 hasMany not return the link array

I just try to use the hasMany relationship according to the loopback4 documentation .but it's not working as expected.
My Bus Model =>
export class Bus extends Entity {
#property({
type: 'number',
id: true,
generated: true,
})
id?: number;
#hasMany(() => BusStation, {keyTo: 'busId'})
stations?: BusStation[];
constructor(data?: Partial<Bus>) {
super(data);
}
}
export interface BusRelations {
// describe navigational properties here
}
export type BusWithRelations = Bus & BusRelations ;
Bus Station Model =>
export class BusStation extends Entity {
#property({
type: 'number',
id: true,
generated: true,
})
id?: number;
#property({
type: 'number',
})
busId: number;
#property({
type: 'string',
required: true,
})
name: string;
constructor(data?: Partial<BusStation>) {
super(data);
}
}
export interface BusStationRelations {
// describe navigational properties here
}
export type BusStationWithRelations = BusStation & BusStationRelations;
Bus Repository =>
export class BusRepository extends DefaultCrudRepository<
Bus,
typeof Bus.prototype.id,
BusRelations
> {
public stations: HasManyRepositoryFactory<
BusStation,
typeof Bus.prototype.id
>;
constructor(
#inject('datasources') dataSource: MyDataSource,
#repository.getter('BusStationRepository')
busStationRepositoryGetter: Getter<BusStationRepository>,
) {
super(Bus, dataSource);
this.stations = this.createHasManyRepositoryFactoryFor(
'stations',
busStationnRepositoryGetter,
);
}
}
My Expected Get Response of Bus =>
{
" id":1,
"stations":[
{
"id":1,
"busId":1,
"name":"Station 1"
}
]
}
I did the exactly same with the documentation but why I can't get the response as I expected. Please May I know what I am missing?
I saw that some solution is to create another controller to connect these two models. Is it the only way? if yes, what is the reason for the hasMany?
in repository you have to include inclusion resolver under
this.stations = this.createHasManyRepositoryFactoryFor('stations',busStationnRepositoryGetter,);
like this
this.registerInclusionResolver('stations', this.stations.inclusionResolver);
on the other hand, you can use relation generator to avoid all this stuff.

TypeGraphql Field and Arg decorators using custom type

I'm trying to build a resolver using type-graphql library and found that I can't define custom argument type. Here is my code:
type Hits = { [K: string]: string | number }
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
#UseMiddleware(isAuthenticated)
async searchAssetList(#Arg('hits') hits: Hits) {
return [];
}
}
I got an error:
NoExplicitTypeError: Unable to infer GraphQL type from TypeScript reflection system. You need to provide explicit type for argument named 'hits' of 'searchAssetList' of 'SearchResolver' class.
I also tried to define an input class:
type Hits = { [K: string]: string | number }
#ObjectType()
class SearchListInput {
#Field(() => GraphQLObjectType)
hits: Hits;
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: SearchListInput) {
return []
}
}
and got another error:
UnhandledPromiseRejectionWarning: Error: Cannot determine GraphQL input type for argument named 'input' of 'searchAssetList' of 'SearchResolver' class. Is the value, that is used as its TS type or explicit type, decorated with a proper decorator or is it a proper input value?
Replacing #ObjectType with #InputType also doesn't help. How to define decorators like #Field, #Arg correctly?
Any help is appreciated. Thanks.
I think you are trying to have an Arg of an array whit numbers and strings in that case you should do
#InputType()
class HitsInput
{
#Field()
Hits: [number | string];
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: HitsInput) {
return []
}
}
if this is not the case and you want an object whit dynamic fields you need to define the object, let say if hits have Name and Id_Hits,
#InputType()
class HitsInput
{
#Field()
Id_Hits: number;
#Field()
Name: string;
}
#Resolver()
export default class SearchResolver {
#Query(() => [String], { nullable: true })
async searchAssetList(#Arg('input') input: HitsInput) {
return []
}
}
and if you want to have a dynamic args base in the user request I am not quite sure that it is possible, but it is possible to do this
#InputType()
class HitsInput
{
[key: string]: any;
}
I had exactly the same issue, a couple of days ago, and I solve it using GraphQLScalarType by creating my own custom type
here how to do it
import { GraphQLScalarType} from "graphql";
export const GraphQLAny = new GraphQLScalarType({
name: 'Any',
serialize: (value) => value,
parseValue: (value) => value,
parseLiteral: (ast) => ast
});
and in your class you can use your customTpe like this:
#ObjectType()
class SearchListInput {
#Field(() => GraphQLAny)
hits: typeof GraphQLAny;
}

type-graphql make exception from Field Resolver

I have a following Entity and Resolver for example,
export class SampleA extends BaseEntity {
#Field()
#PrimaryGeneratedColumn()
id!: number;
#Field()
#Column()
title!: string
#Field()
#Column()
answer!: string;
}
#Resolver(SampleA)
export class SampleAResolver {
#FieldResolver(() => String)
async answer(#Root() sampleA: SampleA, #Ctx() {req}: MyContext) {
const msg = "answer is not public";
if(!req.session.userId) return msg;
return sampleA.answer;
}
}
It worked out well. As long as the condition does not meet, the answer field will be always "answer is not public" across the entire app. However, if sometimes I want to make a exception out of it,
For example, if I have the following
#ObjectType()
class ExceptedResponse {
#Field(() => String, {nullable: true})
errors?: string;
#Field(() => SampleA, {nullable: true})
result?: SampleA;
}
When I try to return the ExceptedResponse, the SampleA field will always be filtered by the answer #FieldResolver() so I cannot get the real answer.
So I wonder if I can make it to an exception out of it without manually re-create a whole new field like
#ObjectType()
class ExceptedResponse {
#Field(() => String, {nullable: true})
errors?: string;
#Field(() => {id: Number, title: String, answer: String }, {nullable: true})
result?: {id: number, title: string, answer: string };
}
//then I just manually assign each value inside my graphql method
Or ideally if I can make a SpecialSampleA extends from SampleA without that limitation.
After some random tests, it seems like it's extremely simple to achieve my goal.
Just simply create a new class and then assign the SampleA result to it. It's auto handled by type-graphql and escape the resolve field check because Class is changed;
#ObjectType()
class ExceptedSampleA {
#Field()
inspiration: string;
#Field()
answer: string;
}
//inside response
#Field(() => ExceptedSampleA, {nullable: true})
result?: ExceptedSampleA;
//inside graphql method like Mutation() then return response
const sampleA = SampleA.findOne();
return { result: sampleA, error: someError }

Resources