How to set default values for DTO in nest js? - node.js

I want to set default values for a node in a DTO. So if the value for that node is not passed, a default value will be used. Although this is working, I want the node should be present, the values is optional.
import { IsNotEmpty, IsDefined } from "class-validator";
export class IssueSearch
{
#IsDefined()
search: string;
#IsNotEmpty()
length: number = 10;
#IsNotEmpty()
lastId: string = "0"
}
This doesn't serve my purpose. I want the url to be searched like so
http://base-url/folder?search=value
If the value is not passed it should not throw an error.
But if the param search is not there it should throw an error.

If you want to set a default value go to entity and set in the field for example in mongo db
export class DumpDoc extends Document {
#Prop()
title: string;
#Prop({ default: new Date() }) //set as default
createdAt: string;
}

Related

NestJs class-validator accepts empty payload

I'm trying to create validator that accepts 2 values as strings (must exist, min/max length etc).
The issue I am facing is that when I POST empty payload the validation passes and TypeORM tries to insert null values and I end up with HTTP status 500.
When I POST with invalid payload the validation works properly.
I want to get proper validation errors as a response when payload is empty (existence of name property, min/max length etc...) ...
I tried adding various class annotations and global settings but no luck...
Global validation enabled:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
forbidUnknownValues: true,
skipMissingProperties: false, //Thought this would check for missing properties
transform: true,
}),
);
Entity:
#Entity('r_cat')
export class ResearchCategory {
[...]
#PrimaryGeneratedColumn()
id: number;
#ApiProperty({
description: 'Name of the category',
example: 'Analytics, Integration',
})
#Column('text')
#IsString()
#ApiProperty()
#Length(2, 30)
#IsNotEmpty()
#IsDefined()
name: string;
[...]
My request object (DTO):
export class CreateResearchCategoryRequest extends PartialType(
OmitType(ResearchCategory, ['created_at', 'updated_at', 'deleted_at'] as const),
) {}
Controller:
#Post()
public async create(
#Req() req,
#Body() researchCategory: CreateResearchCategoryRequest,
): Promise<ResearchCategory> {
return await this.service.createNew(researchCategory, req.user);
}
I'm not sure this is the correct way to create a DTO based on the repository class. The usage of the mapper is different from what you've done since Nest examples show is all based on another DTO class, not a repository class.
However, I think the following snippet should work in your situation:
export class CreateResearchCategoryRequest extends OmitType(
ResearchCategory, ['created_at', 'updated_at', 'deleted_at'] as const
) {}

NestJS: How to convert Mongoose document to regular class?

I have followed NestJS mongo docs to create a schema (without ID) from a regular class. Since this class has other general uses (unrelated to mongoose), I would like to have regular methods on that class as well:
#Schema({ _id: false })
export class Location {
#Prop()
lat: number;
#Prop()
lon: number;
regularMethod() { return something based on this.lat, this.lon }
}
export const LocationSchema = SchemaFactory.createForClass(Location);
export type CatDocument = HydratedDocument<Cat>;
#Schema()
export class Cat {
#Prop({ type: [LocationSchema] })
locations: Location[];
}
export const CatSchema = SchemaFactory.createForClass(Cat);
The problem is that if I query such an object from the db, regularMethod doesn't exist since the queried object is actually a Document based on Location, rather than Location. The document only exposes methods that were defined by the schema.methods, which is not what I need.
MyService {
constructor(#InjectModel(Cat.name) catModel: Model<CatDocument>) {}
async findLocations(catId: string) {
const cat = await catModel.findOneById(catId);
cat.location.forEach(loc => loc.regularMethod()) // no such method
}
}
Is there some obvious way to "cast" Location to the original class to have access to those methods?

TypeORM with default null value for a timestamp type column generates infinite migrations

I have the following column definition in an entity, within a NestJs project using TypeORM:
#CreateDateColumn({
nullable: true,
type: 'timestamp',
default: () => 'NULL',
})
public succeededAt?: Date;
This generates a migration that looks correct:
export class migration123 implements MigrationInterface {
name = 'migration123';
public async up(queryRunner: QueryRunner): Promise\<void\> {
await queryRunner.query(
`CREATE TABLE [...], "succeededAt" TIMESTAMP DEFAULT NULL, [...]`
);
\[...\]
}
But after running the migration, if I try to generate a new migration with TypeORM CLI, it still detects some differences:
[...]
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "mercadopago_payment_request" ALTER COLUMN "succeededAt" SET DEFAULT null`);
[...]
I'm guessing that Postgres detects the default value as the global default anyways (every column has null as default if not stated otherwise) and doesn't add any special rule for it. Then TypeORM doesn't see the rule and tries to add it... again.
Any ideas on how to prevent this?
I tried removing the default value:
#CreateDateColumn({
nullable: true,
type: 'timestamp',
})
public succeededAt?: Date;
But that generates a migration with a default value as now():
export class migration123 implements MigrationInterface {
name = 'migration123';
public async up(queryRunner: QueryRunner): Promise\<void\> {
await queryRunner.query(
`CREATE TABLE [...], "succeededAt" TIMESTAMP DEFAULT now(), [...]`
);
\[...\]
}
The reason why I'm trying to state specifically null as the default value.
My problem was due to the #CreateDateColumn decorator.
I was able to achieve the correct behavior with a regular date column:
#Column({ nullable: true, type: 'timestamp' })
#IsDate()
public succeededAt: Date | null;

Perform a few custom async validations with class-validator

I would like to have the following dto:
export class SetEntryPasswordDto {
#ApiProperty()
#Validate(EntryBelongsToUser)
#Validate(EntryIsNotLocked)
#Type(() => Number)
#IsNumber()
id: number;
#ApiProperty()
#IsString()
#IsNotEmpty()
#Validate(PasswordMatchValidator)
#Matches(EValidator.PASSWORD, { message: 'password is not strong enough' })
password: string;
#ApiProperty()
#IsNotEmpty()
#IsString()
confirmPassword: string;
#ApiProperty()
#IsOptional()
#IsString()
passwordHint?: string;
#IsNumber()
userId: number;
}
The problem with it is that I need to do a couple of async validations and I would like to use the class-validator lib for this.
My question is: if I do this like in the code snippet above, can I be sure that the first one to complete is EntryIsNotLocked? If not then how to make those validation execute in order?
Thank you.
Additional information:
Seems like there's a bit of information that is of importance.
The EntryBelongsToUser and EntryIsNotLocked are the ValidatorConstraint classes. For instance, one of them looks as follows:
#ValidatorConstraint({ name: 'EntryIsNotLocked', async: false })
#Injectable()
export class EntryIsNotLocked implements ValidatorConstraintInterface {
constructor(
private readonly entryService: EntryService,
) {}
public async validate(val: any, args: ValidationArguments): Promise<boolean> {
// here goes some validation logic
}
public defaultMessage(args: ValidationArguments): string {
return `Unauthorized to execute this action`;
}
}
The second one looks exactly the same. So the question is can I guarantee the order by setting the async option of the ValidatorConstraint decorator to false for both of them?
No, you can't be sure of the sequential order of async functions. Thet's why you have validateSync method in class-validator package. You can use the validateSync method instead of the regular validate method to perform a simple non async validation.
See this for reference.

TypeORM - Update with OneToOne join attempts to delete related record

I have a project which uses TypeORM and I have the following two entities:
#Entity("Email")
export class EmailEntity extends BaseEntity implements IEmail {
#Column({length: 100}) to: string;
#Column({length: 100}) from: string;
#Column({length: 255}) subject: string;
#Column({nullable: true}) html: string;
#Column({nullable: true}) text: string;
}
and
#Entity("QueuedEmail")
export class QueuedEmailEntity extends BaseEntity implements IQueuedEmail {
#OneToOne(email => EmailEntity, {nullable: false, cascadeAll: true})
#JoinColumn()
email: EmailEntity;
#Column() retryCount: number;
#Column() status: QueuedEmailStatus;
constructor() {
super();
this.retryCount = 0;
this.status = QueuedEmailStatus.QueuedForDispatch;
}
}
BaseEntity is an abstract class which has an id column with the #PrimaryGeneratedColumn.
I have the following code which updates the status on a QueuedEmail:
const queuedEmailEntityArray: QueuedEmailEntity[] =
await this.queuedEmailRepository.createQueryBuilder("queuedEmail")
.where("queuedEmail.status = :queuedEmailStatus", {queuedEmailStatus: QueuedEmailStatus.QueuedForDispatch})
.innerJoinAndSelect("queuedEmail.email", "email")
.orderBy("queuedEmail.id", "DESC")
.getMany();
queuedEmailEntityArray.forEach(async (value, index) => {
let queuedEmailEntity: QueuedEmailEntity = queuedEmailEntityArray.pop();
queuedEmailEntity.status = QueuedEmailStatus.AttemptingToDispatch;
await this.queuedEmailRepository.persist(queuedEmailEntity);
});
Now, the select correctly loads the array or QueuedEmailEntity objects, the forEach runs but throws the following exception:
query failed: DELETE FROM "Email" WHERE "id"=$1 -- PARAMETERS: [1]
error during executing query:error: update or delete on table "Email" violates foreign key constraint "fk_d438362cf2adecbcc5b17f45606" on table "QueuedEmail"
The debug query output shows that TypeORM is updating the status field but also updating the emailId and then attempting to delete the EmailEntity record.
UPDATE "QueuedEmail" SET "status"=$1, "emailId"=$2 WHERE "id"=$3 -- PARAMETERS: [1,1,1]
DELETE FROM "Email" WHERE "id"=$1 -- PARAMETERS: [1]
I don't understand why TypeORM would need to update the emailId field when I have not changed the reference, much less why it would try to delete a record that is still referenced. Can anyone see what is wrong with the above code?

Resources