request body validation with fastest validator - node.js

i want to change the error style of fastest validator.
copy from fastest validator document
const Validator = require("fastest-validator");
const v = new Validator();
const schema = {
id: { type: "number", positive: true, integer: true },
name: { type: "string", min: 3, max: 255 },
status: "boolean" // short-hand def
};
enter code here
const check = v.compile(schema);
console.log("Second:", check({ id: 2, name: "Adam" }));
/* Returns an array with errors:
[
{
type: 'required',
field: 'status',
message: 'The \'status\' field is required!'
}
]
*/
in the top example the error have some key value but i want this syntax :
[
{
status: 'The \'status\`enter code here`' field is required!',
}
]

For this, we need to prepare a response likewise
Use try and catch then so we can grab that error in catch block and we can change the syntax of error accordingly.
try {
// Your code
} catch (error) {
console.log(error) // Look into where this message is located
return res.json([
{
status: error.message
}
])
}

Related

Serverless Event object failed validation at createError

I am working on a serverless/nodejs12 project. The project deploys fine, but when I try to invoke the endpoint with postman I get the following error. I have browsed through many postings of similar errors but still failed to understand what's going on. Will appreciate any pointers.
Thank you
at createError (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/util/index.js:259:1)
at validatorMiddlewareBefore (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/validator/index.js:55:1)
at runMiddlewares (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/core/index.js:120:1)
at runRequest (/var/task/src/handlers/webpack:/home/serverless-workspace/notification/node_modules/#middy/core/index.js:80:1) {
details: [
{
instancePath: '/body',
schemaPath: '#/properties/body/type',
keyword: 'type',
params: [Object],
message: 'must be object'
}
]
createNotification.js
--------------------------
async function createNotification(event, context) {
const { title } = event.body;
const { destination } = event.body;
const { destinationType } = event.body
const notification = {
id: uuid(),
title,
destination,
destinationType,
status: 'OPEN',
createdAt: new Date().toISOString(),
}
try {
await dynamodb.put({
TableName: process.env.NOTIFY_TABLE_NAME,
Item: notification
}).promise();
} catch (error) {
console.error(error);
throw new createError.InternalServerError(error);
}
return {
statusCode: 200,
body: JSON.stringify(notification),
};
}
export const handler = commonMiddleware(createNotification)
.use(validator({ inputSchema: createNotificationSchema }));
and the schema
----------------------
const schema = {
type: 'object',
properties: {
body: {
type: 'object',
required: ['status'],
default: { status: 'OPEN' },
properties: {
status: {
default: 'OPEN',
enum: ['OPEN', 'CLOSED'],
},
},
},
},
required: ['body'],
};
export default schema;
You're getting the error must be object. I'm guessing your input looks like { event: { body: 'JSON string' } }. You'll need to use another middy middleware to parse the body prior to validating the input. Which middleware will depend on what AWS event it's expecting. Middy >3.0.0 supports all AWS events.

How to grab field value during a MongooseModel.bulkWrite operation?

Context:
I am trying to upsert in bulk an array of data, with an additional computed field: 'status'.
Status should be either :
- 'New' for newly inserted docs;
- 'Removed' for docs present in DB, but inexistent in incoming dataset;
- a percentage explaining the evolution for the field price, comparing the value in DB to the one in incoming dataset.
Implementations:
data.model.ts
import { Document, model, Model, models, Schema } from 'mongoose';
import { IPertinentData } from './site.model';
const dataSchema: Schema = new Schema({
sourceId: { type: String, required: true },
name: { type: String, required: true },
price: { type: Number, required: true },
reference: { type: String, required: true },
lastModified: { type: Date, required: true },
status: { type: Schema.Types.Mixed, required: true }
});
export interface IData extends IPertinentData, Document {}
export const Data: Model<IData> = models.Data || model<IData>('Data', dataSchema);
data.service.ts
import { Data, IPertinentData } from '../models';
export class DataService {
static async test() {
// await Data.deleteMany({});
const data = [
{
sourceId: 'Y',
reference: `y0`,
name: 'y0',
price: 30
},
{
sourceId: 'Y',
reference: 'y1',
name: 'y1',
price: 30
}
];
return Data.bulkWrite(
data.map(function(d) {
let status = '';
// #ts-ignore
console.log('price', this);
// #ts-ignore
if (!this.price) status = 'New';
// #ts-ignore
else if (this.price !== d.price) {
// #ts-ignore
status = (d.price - this.price) / this.price;
}
return {
updateOne: {
filter: { sourceId: d.sourceId, reference: d.reference },
update: {
$set: {
// Set percentage value when current price is greater/lower than new price
// Set status to nothing when new and current prices match
status,
name: d.name,
price: d.price
},
$currentDate: {
lastModified: true
}
},
upsert: true
}
};
}
)
);
}
}
... then in my backend controller, i just call it with some route :
try {
const results = await DataService.test();
return new HttpResponseOK(results);
} catch (error) {
return new HttpResponseInternalServerError(error);
}
Problem:
I've tried lot of implementation syntaxes, but all failed either because of type casting, and unsupported syntax like the $ symbol, and restrictions due to the aggregation...
I feel like the above solution might be closest to a working scenario but i'm missing a way to grab the value of the price field BEFORE the actual computation of status and the replacement with updated value.
Here the value of this is undefined while it is supposed to point to current document.
Questions:
Am i using correct Mongoose way for a bulk update ?
if yes, how to get the field value ?
Environment:
NodeJS 13.x
Mongoose 5.8.1
MongoDB 4.2.1
EUREKA !
Finally found a working syntax, pfeeeew...
...
return Data.bulkWrite(
data.map(d => ({
updateOne: {
filter: { sourceId: d.sourceId, reference: d.reference },
update: [
{
$set: {
lastModified: Date.now(),
name: d.name,
status: {
$switch: {
branches: [
// Set status to 'New' for newly inserted docs
{
case: { $eq: [{ $type: '$price' }, 'missing'] },
then: 'New'
},
// Set percentage value when current price is greater/lower than new price
{
case: { $ne: ['$price', d.price] },
then: {
$divide: [{ $subtract: [d.price, '$price'] }, '$price']
}
}
],
// Set status to nothing when new and current prices match
default: ''
}
}
}
},
{
$set: { price: d.price }
}
],
upsert: true
}
}))
);
...
Explanations:
Several problems were blocking me :
the '$field_value_to_check' instead of this.field with undefined 'this' ...
the syntax with $ symbol seems to work only within an aggregation update, using update: [] even if there is only one single $set inside ...
the first condition used for the inserted docs in the upsert process needs to check for the existence of the field price. Only the syntax with BSON $type worked...
Hope it helps other devs in same scenario.

updating values in nested object arrays Mongoose Schema

I have my schema designed like this
const templateSchema = new Schema({
Main: {
text: String,
textKey: String,
index: String,
part: String,
overallStatus: String,
subjects: [
{
id: String,
text: String,
textKey: String,
index: String,
type: String,
comment: String,
image: String,
answer: String,
}
and I have to update subjects text and subject id and I am doing it like this
router.post("/edit", (req, res, next) => {
Template.findOneAndUpdate({
id: req.body.id,
text: req.body.text
}).then(updatedTemp => {
console.log(updatedTemp);
if (updatedTemp) {
res.status(200).json({
message: "Template updated.."
});
} else {
res.status(404).json({
message: "Checklist not found"
});
}
});
});
it returns template updated and status 200 but it doesn't update the new values. How can i access subject ID and subject text in this schema
so, According to the provided schema,
you should find inside the array, and update there,
you could do something like this:
router.post("/edit", (req, res, next) => {
Template.update(
{
"Main.subjects.id": req.body.oldId,
"Main.subjects.text": req.body.oldText,
//you can pass some more conditions here
},
{
$set: {
"Main.subjects.$.id": req.body.id,
"Main.subjects.$.text": req.body.text
}
}
)
.then(updated => {
if (updated.nModified) {
res.status(200).json({
message: "Template updated.."
});
} else {
//not updated i guess
}
})
.catch(error => {
//on error
});
});
so payload in body you need to pass :
oldId : <which is currently there>,
oldText: <which is currently there>,
id: <by which we will replace the id field>
text:<by which we will replace the txt>
NOTE:
assuming , id or text will be unique among all the docs.
sample data:
{
"_id" : ObjectId("5be1728339b7984c8cd0e511"),
"phases" : [
{
"phase" : "insp-of-install",
"text" : "Inspection of installations",
"textKey" : "",
"index" : "1.0",
"subjects" : [...]
},...]
}
we can update text here like this [in the top most level]:
Template.update({
"_id":"5be1728339b7984c8cd0e511",
"phases.phase":"insp-of-install",
"phases.text":"Inspection of installations"
},{
"$set":{
"phases.$.text":"Some new text you want to set"
}
}).exec(...)
but, incase you want to do deep level nested update,
you can have a look at this answer : here by #nem035

How to add a validator to an existing collection via node.js mongodb driver?

Here is a code where I'm trying to add a validator to an existing collection.
const { MongoClient } = require("mongodb")
const schema = {
$jsonSchema: {
bsonType: "object",
additionalProperties: false,
required: ["name"],
properties: {
_id: {
bsonType: "objectId"
},
name: {
bsonType: "string"
}
}
}
}
const main = async () => {
const client = await MongoClient.connect(
"mongodb://localhost",
{ useNewUrlParser: true }
)
const db = client.db("t12")
// await db.createCollection("test", { validator: schema })
await db.createCollection("test")
await db.admin().command({ collMod: "test", validator: schema })
await db.collection("test").createIndex({ name: 1 }, { unique: true })
await db.collection("test").insertOne({ name: "t1" })
await db.collection("test").insertOne({ value: "t2" }) // should fail
const all = await db
.collection("test")
.find({})
.toArray()
console.log(all)
await client.close()
}
main().catch(err => console.error(err))
It fails:
max7z#mbp t12__npm__mongodb (master)*$ node test/1.js
{ MongoError: ns does not exist
at /Users/max7z/projects/t/t12__npm__mongodb/node_modules/mongodb-core/lib/connection/pool.js:581:63
at authenticateStragglers (/Users/max7z/projects/t/t12__npm__mongodb/node_modules/mongodb-core/lib/connection/pool.js:504:16)
at Connection.messageHandler (/Users/max7z/projects/t/t12__npm__mongodb/node_modules/mongodb-
ok: 0,
errmsg: 'ns does not exist',
code: 26,
codeName: 'NamespaceNotFound',
name: 'MongoError',
[Symbol(mongoErrorContextSymbol)]: {} }
^C
If I create the collection with that schema it works, but when I'm trying to add a vatidator via collMod, it fails.
How to add a validator to an existing collection via collMod command?
I created a function like
const updateValidator = async (collectionName, newValidator) => {
return db.command({
collMod: collectionName,
validator: newValidator,
validationLevel: "moderate",
validationAction: "warn"
});
}
The problem with db.command is that is replaces the whole validation schema. Therefore you need access to the current schema of the collection. As I did not found the function db.getCollectionInfos in the nodejs library I added the posibility of passing it as a parameter.
In my case I get it from other migration module with a require. E.g.
const currentValidator = require("migration-file-where-I-defined-the-previous-schema").schema.validator;
In the required file I have some initial schema like:
module.exports.schema = {
validator: {
$jsonSchema: {
bsonType: "object",
required: ["name"],
properties: {
name: {
bsonType: "string",
maxLength: 300,
minLength: 3,
description: "Must be a string and is required"
},
created: {
bsonType: "date",
description: "Date when it was created"
},
deleted: {
bsonType: "date",
description: "Date when it was deleted"
}
}
},
}
};
Then I create a merge of the new schema and that will be enough. E.g.
const updatedValidator = Object.assign({}, currentValidator);
updatedValidator.$jsonSchema.properties.new_attribX = {
enum: ["type1", "type2"],
description: "State of the tenant related to its life cycle"
};
updatedValidator.$jsonSchema.required.push('new_attribX');
updateValidator("mycollection", updatedValidator)
.then(next)
.catch(console.error);
This will replace the previous schema with the new one which has the changes applied. For me, this was enough, but bear in mind that when you have existing data that needs to be updated with new data, then you need to update them with something like
collection.updateMany({'new_attribX': {$exists : false}}, {$set: {'new_attribX': 'type1'}});
Which for that data which has not that attribute (new_attribX) it should add them kind of this initial default value: type1.
I hope it helps.
The problem was in that line:
await db.admin().command({ collMod: "test", validator: schema })
The right way to do it:
await db.command({ collMod: "test", validator: schema })

mongoDb database form-data empty response

I am sending Rest POST Request(form-data) using postman to mongoDb. Even after providing all the key-value pairs in the Model, only the product _id gets stored into the database not other array of objects. Here's my model schema:
const mongoose = require('mongoose');
const productSchema = mongoose.Schema({
name: String,
price: Number,
editor1: String,
year: String,
quantity: Number,
subject: String,
newProduct: String,
relatedProduct: String,
//coverImage: { type: String, required: false }
});
module.exports = mongoose.model('Product', productSchema);
And here's my POST request for the products:
exports.products_create_product = (req, res, next) => {
const product = new Product(req.body);
product
.save()
.then(result => {
console.log(result);
res.status(201).json({
message: "Created product successfully",
createdProduct: {
name: result.name,
price: result.price,
_id: result._id,
request: {
type: "GET",
url: "http://localhost:3000/products/" + result._id
}
}
});
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
};
And this is my result output:
{
"message": "Created product successfully",
"createdProduct": {
"_id": "5b2df3420e8b7d1150f6f7f6",
"request": {
"type": "GET",
"url": "http://localhost:3000/products/5b2df3420e8b7d1150f6f7f6"
}
}
}
Tried every possible way to solve this but in vain.
try the callback
product.save((err,result) =>{
if(err){
res.status(500).json({
error: err
});
return false
}
res.status(201).json({
message: "Created product successfully",
createdProduct: {
name: result.name,
price: result.price,
_id: result._id,
request: {
type: "GET",
url: "http://localhost:3000/products/" + result._id
}
}
})
First let's try to understand why this happens ? ,
When you use .json() method inside this method it uses JSON.stringify() function what this function does is that take any JavaScript value and convert it to JSON string for example
let product = {
name : 'p1' ,
price : 200
}
console.log(JSON.stringify(product)); // {"name":"p1","price":200}
that's good , but you should know that if this function during the conversion see any undefined values it will be omitted ,
for example
let product = {
id:"23213214214214"
} // product doesn't have name property and its undefind
console.log(JSON.stringify({
id:product.id ,
name : product.name
})); // {"id":"23213214214214"}
if you look at the example above you'll see that because the value of name is **undefined ** you get a JSON output but only with the id value , and any value that is undefined will not be in the result .
that's the main reason why you get the result with only the product._id because other values like price and name are undefined .
so what to do to solve this problem ?
log the req.body and make sure that it has properties like name and price and I think this is the main reason of the problem because when you create the product variable it will only has the _id prop because other props are not exist .
after you create the product log it to the console and make sure that it has other properties like name and price
#mostafa yes I did tried.
{
"count": 3,
"products": [
{
"_id": "5b2e2d0cb70f5f0020e72cb6",
"request": {
"type": "GET",
"url": "https://aa-backend.herokuapp.com/products/5b2e2d0cb70f5f0020e72cb6"
}
},
{
"_id": "5b2e2d37b70f5f0020e72cb7",
"request": {
"type": "GET",
"url": "https://aa-backend.herokuapp.com/products/5b2e2d37b70f5f0020e72cb7"
}
},
{
this is the only output I am getting.

Resources