Validate DTO in controller when passing data to service - mongodb

I am trying to insert a validation into PUT request(to update some data stored in MongoDB):
DTO:
export enum reportFields {
'startDate',
'targetDateOfCompletion',
'duration',
}
export class updateScheduleDto {
#IsOptional()
#IsString()
readonly frequency?: string;
#IsOptional()
#IsArray()
#IsEmail({}, { each: true })
#IsString({ each: true })
readonly emails?: string[];
#IsOptional()
#IsEnum(reportFields, { each: true })
#IsArray()
#IsString({ each: true })
readonly reportFields?: string[];
#IsOptional()
#Type(() => Number)
#IsNumber()
updatedAt?: number;
}
Controller:
#Put('reports/:id/schedule')
async updateScheduleData(
#Param('id') id: string,
#Body(new ValidationPipe()) updateData: updateScheduleDto,
) {
return this.reportService.updateScheduleData(id, updateData);
}
Service:
async updateScheduleData(id: string, updateData: updateScheduleDto) {
try {
updateData.updatedAt = this.utils.getCurrentTime();
const newData = await this.reportScheduleModel.findByIdAndUpdate(
id,
updateData,
{
new: true,
},
);
console.log(`Data has been updated to ${newData}`);
return newData;
} catch (error) {
throw new Error('>>>' + error);
}
}
But the validation not working over the keys. If I pass a non-valid key(like below) in the body object, even then the program executes without any error, how do I fix this? What am I missing?
{
"emaaaalls":["randomemail123#gmail.com"]
}

You need to pass the options { forbidUnknownValues: true } to the ValidationPipe. This will make class-validator throw an error when unknown values are passed in. You can read through the options here

You can make whitelist: true in ValidationPipe options.
When set to true, this will automatically remove non-whitelisted properties (those without any decorator in the validation class).
you can read more about this https://docs.nestjs.com/techniques/validation#stripping-properties

Related

ConnectorError Prisma with MongoDB

I am developing an API with Nestjs and MongoDB. Well sometimes I have this problem. It doesn't always happen and that's what I have to solve because this error rarely happens.
This is the error:
{
"status": "error",
"message": "\nInvalid this.prisma.people.findMany() invocation in\nC:\ima\empresas\datec\api-official\src\modules\Events\services\utils.service.ts:57: 28\n\n 54 }),\n 55 \n 56 // Get attendees by id\n→ 57 this.prisma.people.findMany(\nError in batch request 3: Error occurred during query execution:\nConnectorError(ConnectorError { user_facing_error: None, kind: RawDatabaseError { code: "unknown", message: "An existing connection was forced to be terminated by the remote host. (os error 10054)" } })"
}
I really can't find much information to solve this error. I have followed the steps in the documentation on Nestjs when using prisma with nestjs. I need help to solve this error, since it is essential to find a solution as soon as possible. Thanks so much for read this question
UPDATE
The Prisma service
// Librarys
import { Injectable } from '#nestjs/common'
import { PrismaClient } from '#prisma/client'
// Interfaces
import { INestApplication, OnModuleInit, OnModuleDestroy } from '#nestjs/common'
#Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleInit, OnModuleDestroy
{
async onModuleInit() {
await this.$connect()
}
async onModuleDestroy() {
await this.$disconnect()
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close()
})
}
}
File that starts the application
//Libraries
import { NestFactory } from '#nestjs/core'
import { ValidationPipe } from '#nestjs/common'
import { SwaggerModule, DocumentBuilder } from '#nestjs/swagger'
import { useContainer } from 'class-validator'
// Modules
import { MainModule } from './main.module'
// config
import pk from '#root/package.json'
import { corstOptions } from '#config/cors'
import { PORT, APP_NAME, PUBLIC_URL } from '#config/env'
// Exceptions
import { HttpExceptionFilter } from '#utils/HttpExceptionFilter'
import { PrismaService } from '#root/src/services/prisma'
async function bootstrap() {
// Create application
const app = await NestFactory.create(MainModule)
// Enable cors
app.enableCors(corstOptions)
// Use global 'api' prefix, all calls will come after '/api/*'
app.setGlobalPrefix('api')
// Globally define custom response
app.useGlobalFilters(new HttpExceptionFilter())
// Enable prism on custom validations
useContainer(app.select(MainModule), { fallbackOnErrors: true })
// Get service from primsa and enable 'shutdowns'
const prismaService = app.get(PrismaService)
await prismaService.enableShutdownHooks(app)
// Use 'pipe' validation to validate the 'body' structure
app.useGlobalPipes(
newValidationPipe({
whitelist: true,
transform: true,
forbidUnknownValues: true,
forbidNonWhitelisted: true,
transformOptions: { enableImplicitConversion: true }
})
)
// Create API documentation
const config = new DocumentBuilder()
.addBearerAuth()
.setTitle(`API | ${APP_NAME}`)
.setContact(pk.author.name, pk.author.url, pk.author.email)
.setDescription(pk.description)
.setVersion(pk.version)
.build()
const document = SwaggerModule.createDocument(app, config)
SwaggerModule.setup('/', app, document)
// Listen to the application on the PORT defined in the environment variables
await app.listen(PORT || 0, () => {
// eslint-disable-next-line no-console
console.log(
'\x1b[33m%s\x1b[0m',
`[INFO] The server has been started at '${PUBLIC_URL}'`
)
})
}
bootstrap() // Run application
utils.service of Event module
// Librarys
import { Prisma } from '#prisma/client'
import { Inject, Injectable } from '#nestjs/common'
// Services
import { PrismaService } from '#services/prisma'
// DTO
import { EventBodyDTO } from '#modules/Events/events.dto'
// Arguments
import { EventsArgs } from '#modules/Events/events.args'
#Injectable()
export class UtilsService {
#Inject(PrismaService)
private readonly prisma: PrismaService
private readonly selects = {
'id.title': { id: true, title: true },
'id.fullname': { id: true, fullname: true },
'id.title.attributes': { id: true, title: true, attributes: true },
'id.code.description.attributes': {
id: true,
code: true,
description: true,
attributes: true
}
}
/**
* Get reminders, types of activities, case, client and attendees of an event
* #param {EventBodyDTO} payload Event data
*/
async getEventFields(payload: EventBodyDTO) {
const [activityType, eventCase, client, assistants, reminders] =
await Promise.all([
// Get an activity type by id
this.prisma.parameters.findUnique({
select: this.selects['id.title'],
where: { id: payload.activityType }
}),
// Get a case by id
this.prisma.expedients.findUnique({
where: { id: payload.case },
select: this.selects['id.code.description.attributes']
}),
// Get a person by id
this.prisma.people.findFirst({
select: this.selects['id.fullname'],
where: { isClient: true, id: payload.client }
}),
// Get attendees by id
this.prisma.people.findMany({
select: this.selects['id.fullname'],
where: {
isEmployee: true,
id: { in: payload.assistants }
}
}),
// Get reminders by id
this.prisma.parameters.findMany({
select: this.selects['id.title.attributes'],
where: {
id: { in: payload.reminders }
}
})
])
return {
reminders: reminders,
assistants: assistants,
client: client === null ? {} : client,
case: eventCase === null ? {} : eventCase,
activityType: activityType === null ? {} : activityType
}
/**
* Create filters to filter by customer or event attendees
* #param {EventsArgs} args Arguments to filter
* #returns {Promise<Prisma.EventsFindManyArgs['where']>} Returns an object with filters
*/
async createEventFilters(
args: EventsArgs
): Promise<Prisma.EventsFindManyArgs['where']> {
const filters: Prism.EventsFindManyArgs['where'] = {
userId: args.user
}
// Filter by customer
if (typeof args.client === 'string') {
const clients = await this.prisma.people.findMany({
where: {
isClient: true,
fullname: {
mode: 'insensitive',
contains: args.client
}
},
select: {
id: true,
fullname: true
}
})
filters.OR = []
for (const client of clients) {
filters.OR.push({
client: { equals: client }
})
}
}
// Filter by attendees
if (Array.isArray(args.assistants)) {
const assistants = await this.prisma.people.findMany({
where: {
isEmployee: true,
id: {
in: args.assistants
}
},
select: {
id: true,
fullname: true
}
})
if (!Array.isArray(filters.OR)) {
filters.OR = []
}
filters.OR.push({
assistants: { hasSome: assistants }
})
}
return filters
}
}

Mongoose Schema properties validation with Typescript NextJS

i am trying to save new document to mongo db, the Schema validation is not working for me, i am trying ti make required true, but i still can add new document without the required field.
this is my schema:
// lib/models/test.model.ts
import { Model, Schema } from 'mongoose';
import createModel from '../createModel';
interface ITest {
first_name: string;
last_name: string;
}
type TestModel = Model<ITest, {}>;
const testSchema = new Schema<ITest, TestModel>({
first_name: {
type: String,
required: [true, 'Required first name'],
},
last_name: {
type: String,
required: true,
},
});
const Test = createModel<ITest, TestModel>('tests', testSchema);
module.exports = Test;
this is createModel:
// lib/createModel.ts
import { Model, model, Schema } from 'mongoose';
// Simple Generic Function for reusability
// Feel free to modify however you like
export default function createModel<T, TModel = Model<T>>(
modelName: string,
schema: Schema<T>
): TModel {
let createdModel: TModel;
if (process.env.NODE_ENV === 'development') {
// In development mode, use a global variable so that the value
// is preserved across module reloads caused by HMR (Hot Module Replacement).
// #ts-ignore
if (!global[modelName]) {
createdModel = model<T, TModel>(modelName, schema);
// #ts-ignore
global[modelName] = createdModel;
}
// #ts-ignore
createdModel = global[modelName];
} else {
// In production mode, it's best to not use a global variable.
createdModel = model<T, TModel>(modelName, schema);
}
return createdModel;
}
and this is my tests file:
import { connection } from 'mongoose';
import type { NextApiRequest, NextApiResponse } from 'next';
const Test = require('../../../lib/models/test.model');
import { connect } from '../../../lib/dbConnect';
const ObjectId = require('mongodb').ObjectId;
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
switch (req.method) {
case 'POST': {
return addPost(req, res);
}
}
}
async function addPost(req: NextApiRequest, res: NextApiResponse) {
try {
connect();
// const { first_name, last_name } = req.body;
const test = new Test({
first_name: req.body.first_name,
last_name: req.body.last_name,
});
let post = await test.save();
// return the posts
return res.json({
message: JSON.parse(JSON.stringify(post)),
success: true,
});
// Erase test data after use
//connection.db.dropCollection(testModel.collection.collectionName);
} catch (err) {
//res.status(400).json(err);
res.status(400).json({
message: err,
success: false,
});
}
}
in the Postman, i send a request body without the required field (first_name) and i still can add it.
any help?

How Flutter Graphql not mapping to DTO

Hello I'm not new to Flutter but super new to Graphql. I have this Resolver in NestJs
#Mutation(() => Recipe)
createRecipe(
#Args('createRecipeInput') createRecipeInput: CreateRecipeInput,
) {
return this.recipesService.create(createRecipeInput);
}
The DTO looks like this.
#InputType()
export class CreateRecipeInput {
#Field(() => [String], { nullable: true })
ingredientNames?: string[];
#Field(() => [String], { nullable: true })
instructionNames?: string[];
}
My mutation
const String createRecipe = r'''
mutation CreateRecipe(
$ingredientNames: [String!]!,
$instructionNames: [String!]!,
) {
action: createRecipe(createRecipeInput: {
instructionNames: $instructionNames,
}) {
ingredientNames
instructionNames
}
}
''';
final MutationOptions options = MutationOptions(
document: gql(createRecipe),
operationName: 'CreateRecipe',
variables: <String, dynamic>{
'ingredientNames': ingredientNames,
'instructionNames': instructionNames,
},
);
await client.mutate(options);
I get this error
{"errors":[{"message":"Cannot query field \"ingredientNames\" on type \"Recipe\". Did you mean \"ingredients\"?","locations":[{"line":8,"column":5}],"extensions":
Is like the request is not mapping to the DTO but the actual entity. I tried this example using the playground and postman and in both this code works. Not sure if the library is busted or I'm just missing something.
Thanks.

Angular 2 Reactive Form nested group passed null on reset throw error

I have this form group:
Id: [null],
Nominativo: [null, [Validators.required, Validators.maxLength(100)]],
Area: fb.group({
Id: [null]
})
When i try to reset the form with this model:
Id: 1,
Nominativo: 'Test'
Area: null
the area null value throw exception "TypeError: Cannot read property 'Id' of null". My expected result is all Area value become null. I can avoid this problem? My server response with all nested model null when they are all null
I don't think there is a simple clean solution for this. You need to iterate the object properties and set the values manually. So something like this:
resetVal = {Id:1, Nominativo: 'Test', Area: null }
and when you receive the data, you first check if Area is null, if so, set the formcontrols to null in that formgroup. If not, then set the values you get from backend to your formgroup:
this.areaCtrl = this.myForm.controls.Area as FormGroup;
this.myForm.patchValue({
Id: this.resetVal.Id,
Nominativo: this.resetVal.Nominativo,
})
if(this.resetVal.Area === null) {
this.areaCtrl.patchValue({
Id: null
})
}
else {
for(let a in this.resetVal.Area) {
this.areaCtrl.patchValue({
[a]:this.resetVal.Area[a]
})
}
}
DEMO
I suggest to do something like this:
area.model.ts
export class Area {
id: string;
}
data.model.ts
export class DataModel {
id: string;
nominativo: string;
area: Area;
}
form.model.ts
export class FormModel {
id: string;
nominativo: string;
area?: Area;
}
data-model.service.ts
#Injectable()
export class DataModelService {
toFormModel(dataModel: DataModel): FormModel {
const form: FormModel = new FormModel();
form.id = dataModel.id;
form.nominativo = dataModel.nominativo;
if (dataModel.area) {
form.area = dataModel.area;
}
return form;
}
}
form.component.ts
constructor(dataModelService: DataModelService,
dataApi: dataApiService,
fb: FormBuilder) {
this.form = this.fb.group({
id: '',
nominativo: '',
area: this.fb.group({
id: ''
})
});
this.dataApi.getData()
.map((data: DataModel) => this.dataModelService.toFormModel(data))
.subscribe((formData) => {
this.form.patchValue(formData);
});
}

sails js model validation against database

I am writing a custom validation rule to check if the "category_id" passed to my create function is valid or not.
types: {
isValidCategoryId: function(id){
return Category.findOne({id: id}).exec(function(err, user){
if(err || !user)
return false;
else{
return true;
}
});
}
},
attributes: {
category_id : { isValidCategoryId: true, required: true, type: 'string' },
}
I understand that my custom validation function should return true, but in an asynchronous context, this may not work, like checking the value in DB.
How should I write my custom validation function to make it behave correctly?
I tried particlebanana's solution. It didn't work but at least it pointed me in the right direction.
According to the docs:
Validation rules may be defined as simple values or functions (both sync and async) that return the value to test against.
So, one easy way to do this would be:
attributes: {
category_id : {
required: true,
type: 'string',
'true': function(cb) {
Category.findOne({id: this.category_id}).exec(function(err, category){
return cb(!err && category);
});
}
}
}
As you can see, I'm just using the "true" validator here, but you could of course write your own validator to work out some more advanced logic. The key here is that your custom validators aren't async, but your validation rules can be. Yes, the terminology is very confusing.
Here's another example with a custom validator:
attributes: {
author: {
required: true,
type: 'string',
canPost: function(cb) {
Author.findOne(this.author).exec(function(err, author) {
return cb(author);
});
}
}
},
types: {
canPost: function(id, author) {
return author && author.canPost();
}
}
Hopefully that makes sense. If not, See the docs.
You can pass in a callback and return the result. It's a bit weird because it doesn't look like it follows the (err, result) standard but instead just uses (result). Give this a try:
types: {
isValidCategoryId: function(id, cb){
return Category.findOne({id: id}).exec(function(err, user){
if(err || !user)
return cb(false);
else{
return cb(true);
}
});
}
},