Mongoose create on session fails on validation - mongodb

I am trying to create a document, on an existing collection, using MongoDB + async..await programming + session/transaction control (in order to be able to rollback to a previous state if anything goes wrong). The relevant code is shown below:
exports.campaign_totalize = async (campaign, start_date, end_date, now) => {
var session = await mongoose.startSession();
session.startTransaction();
try {
// ...
let commission = await commissionModel.create({
datetime: Date.now(),
value: 10,
status: 'issued',
affiliate: affiliate_id,
campaign: campaign_id
}, { session: session });
// ...
}
// ...
} catch (err) {
console.log("Error totalizing commissions:", err);
await session.abortTransaction();
session.endSession();
throw err;
}
}
Executing code above I get ValidationError on ALL fields (even being sure they are all OK). If I remove the session parameter, however, everything works fine! The collections exists for sure.
Below information about my environment:
"dependencies": {
"express": "^4.16.4",
"mongoose": "^5.3.9",
"mongoose-bcrypt": "^1.6.0",
"validator": "^10.9.0"
}
$ node --version
v10.14.0
$ mongod --version
db version (v3.6.3) -> v4.0.4
Thank you very much.
Update:
As Shivam Pandey pointed out in the comments, mongoDB version starts supporting transactions on version 4.0. I just updated it to 4.0.4 and it is returning the same errors, though.
Update 2:
Stack trace:
Trace: error
at Object.exports.campaign_totalize (/home/luiz/magalabs/magafilio/magafilio-server/logic/commission_calc.js:147:17)
at process._tickCallback (internal/process/next_tick.js:68:7)
Error totalizing commissions: { ValidationError: Commission validation failed: campaign: All commissions must be associated to a campaign, affiliate: All commissions must be associated to an affiliate, status: Commission needs a status associated, value: Commission needs a value - use 0 for no valued commissions, datetime: Commission needs date/time info
at ValidationError.inspect (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/error/validation.js:59:24)
at formatValue (internal/util/inspect.js:453:31)
at inspect (internal/util/inspect.js:193:10)
at Object.formatWithOptions (util.js:165:18)
at Console.(anonymous function) (console.js:188:15)
at Console.log (console.js:199:31)
at Object.exports.campaign_totalize (/home/luiz/magalabs/magafilio/magafilio-server/logic/commission_calc.js:148:17)
at process._tickCallback (internal/process/next_tick.js:68:7)
errors:
{ campaign:
{ ValidatorError: All commissions must be associated to a campaign
at new ValidatorError (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/error/validator.js:29:11)
at validate (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:844:13)
at /home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:897:11
at Array.forEach (<anonymous>)
at ObjectId.SchemaType.doValidate (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:853:19)
at /home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/document.js:1893:9
at process._tickCallback (internal/process/next_tick.js:61:11)
message: 'All commissions must be associated to a campaign',
name: 'ValidatorError',
properties: [Object],
kind: 'required',
path: 'campaign',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true },
affiliate:
{ ValidatorError: All commissions must be associated to an affiliate
at new ValidatorError (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/error/validator.js:29:11)
at validate (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:844:13)
at /home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:897:11
at Array.forEach (<anonymous>)
at ObjectId.SchemaType.doValidate (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:853:19)
at /home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/document.js:1893:9
at process._tickCallback (internal/process/next_tick.js:61:11)
message: 'All commissions must be associated to an affiliate',
name: 'ValidatorError',
properties: [Object],
kind: 'required',
path: 'affiliate',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true },
status:
{ ValidatorError: Commission needs a status associated
at new ValidatorError (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/error/validator.js:29:11)
at validate (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:844:13)
at /home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:897:11
at Array.forEach (<anonymous>)
at SchemaString.SchemaType.doValidate (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:853:19)
at /home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/document.js:1893:9
at process._tickCallback (internal/process/next_tick.js:61:11)
message: 'Commission needs a status associated',
name: 'ValidatorError',
properties: [Object],
kind: 'required',
path: 'status',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true },
value:
{ ValidatorError: Commission needs a value - use 0 for no valued commissions
at new ValidatorError (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/error/validator.js:29:11)
at validate (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:844:13)
at /home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:897:11
at Array.forEach (<anonymous>)
at SchemaNumber.SchemaType.doValidate (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:853:19)
at /home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/document.js:1893:9
at process._tickCallback (internal/process/next_tick.js:61:11)
message: 'Commission needs a value - use 0 for no valued commissions',
name: 'ValidatorError',
properties: [Object],
kind: 'required',
path: 'value',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true },
datetime:
{ ValidatorError: Commission needs date/time info
at new ValidatorError (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/error/validator.js:29:11)
at validate (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:844:13)
at /home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:897:11
at Array.forEach (<anonymous>)
at SchemaDate.SchemaType.doValidate (/home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/schematype.js:853:19)
at /home/luiz/magalabs/magafilio/magafilio-server/node_modules/mongoose/lib/document.js:1893:9
at process._tickCallback (internal/process/next_tick.js:61:11)
message: 'Commission needs date/time info',
name: 'ValidatorError',
properties: [Object],
kind: 'required',
path: 'datetime',
value: undefined,
reason: undefined,
[Symbol(mongoose:validatorError)]: true } },
_message: 'Commission validation failed',
name: 'ValidationError' }

I think there's an issue with how you're using the create, or at least that's my first guess.
To specify options, docs must be an array, not a spread.
https://mongoosejs.com/docs/api.html#model_Model.create
Has to be in this form
Model.create([doc], options)
you have
Model.create(doc, options)
This might be what you want
let [commission] = await commissionModel.create([{datetime: Date.now(),
...}], {session})
let [commission] = await commissionModel.create([doc], options})
When you pass an array into Model.create(), Model.create() will return an array with the documents you created/saved so that's why I used "let [commission]" which will set variable commission to the first item in the array that gets returned.

Related

Error on sequelize full text search raw sql query

Attempting to use raw sql in sequelize db, not sure why it is not working.
Here is the controller with the query:
const db = require("../../models");
const Book = db.book;
const User = db.user;
const Sequelize = require('sequelize');
const { sequelize } = require('../../models/index.js');
exports.getAllBooks = async (req, res) => {
console.log('query: ', req.query);
let books;
if (Object.keys(req.query).length === 0) {
books = await Book.findAll();
res.json(books);
} else {
[books, metadata] = await sequelize.query(`
SELECT ('title')
FROM Book
WHERE searchable ## to_tsquery(${req.query});
`);
res.json(books);
}
};
get request to
http://localhost:8080/api/books?searchable=humor
the tsvector column in the db is named "searchable"
the log response:
Server is running on port 8080.
query: { searchable: 'humor' }
Executing (default): SELECT ('title')
FROM Book
WHERE searchable ## to_tsquery([object Object]);
node:internal/process/promises:279
triggerUncaughtException(err, true /* fromPromise */);
^
Error
at Query.run (/Users/darius/IdeaProjects/node-jwt-auth-postgresql/node_modules/sequelize/lib/dialects/postgres/query.js:50:25)
at /Users/darius/IdeaProjects/node-jwt-auth-postgresql/node_modules/sequelize/lib/sequelize.js:314:28
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async exports.getAllBooks (/Users/darius/IdeaProjects/node-jwt-auth-postgresql/app/controllers/book.controller.js:16:29) {
name: 'SequelizeDatabaseError',
parent: error: syntax error at or near "["
at Parser.parseErrorMessage (/Users/darius/IdeaProjects/node-jwt-auth-postgresql/node_modules/pg-protocol/dist/parser.js:287:98)
at Parser.handlePacket (/Users/darius/IdeaProjects/node-jwt-auth-postgresql/node_modules/pg-protocol/dist/parser.js:126:29)
at Parser.parse (/Users/darius/IdeaProjects/node-jwt-auth-postgresql/node_modules/pg-protocol/dist/parser.js:39:38)
at Socket.<anonymous> (/Users/darius/IdeaProjects/node-jwt-auth-postgresql/node_modules/pg-protocol/dist/index.js:11:42)
at Socket.emit (node:events:513:28)
at addChunk (node:internal/streams/readable:315:12)
at readableAddChunk (node:internal/streams/readable:289:9)
at Socket.Readable.push (node:internal/streams/readable:228:10)
at TCP.onStreamRead (node:internal/stream_base_commons:190:23) {
length: 90,
severity: 'ERROR',
code: '42601',
detail: undefined,
hint: undefined,
position: '83',
internalPosition: undefined,
internalQuery: undefined,
where: undefined,
schema: undefined,
table: undefined,
column: undefined,
dataType: undefined,
constraint: undefined,
file: 'scan.l',
line: '1180',
routine: 'scanner_yyerror',
sql: "SELECT ('title')\n" +
' FROM Book\n' +
' WHERE searchable ## to_tsquery([object Object]);',
parameters: undefined
},
original: error: syntax error at or near "["
at Parser.parseErrorMessage (/Users/darius/IdeaProjects/node-jwt-auth-postgresql/node_modules/pg-protocol/dist/parser.js:287:98)
at Parser.handlePacket (/Users/darius/IdeaProjects/node-jwt-auth-postgresql/node_modules/pg-protocol/dist/parser.js:126:29)
at Parser.parse (/Users/darius/IdeaProjects/node-jwt-auth-postgresql/node_modules/pg-protocol/dist/parser.js:39:38)
at Socket.<anonymous> (/Users/darius/IdeaProjects/node-jwt-auth-postgresql/node_modules/pg-protocol/dist/index.js:11:42)
at Socket.emit (node:events:513:28)
at addChunk (node:internal/streams/readable:315:12)
at readableAddChunk (node:internal/streams/readable:289:9)
at Socket.Readable.push (node:internal/streams/readable:228:10)
at TCP.onStreamRead (node:internal/stream_base_commons:190:23) {
length: 90,
severity: 'ERROR',
code: '42601',
detail: undefined,
hint: undefined,
position: '83',
internalPosition: undefined,
internalQuery: undefined,
where: undefined,
schema: undefined,
table: undefined,
column: undefined,
dataType: undefined,
constraint: undefined,
file: 'scan.l',
line: '1180',
routine: 'scanner_yyerror',
sql: "SELECT ('title')\n" +
' FROM Book\n' +
' WHERE searchable ## to_tsquery([object Object]);',
parameters: undefined
},
sql: "SELECT ('title')\n" +
' FROM Book\n' +
' WHERE searchable ## to_tsquery([object Object]);',
parameters: {}
}
[nodemon] app crashed - waiting for file changes before starting...
First, you passed the whole query object instead of the searchable prop only.
Second, it's better to use bind option to pass such values safely (remember about SQL injection!).
await sequelize.query(`
SELECT ('title')
FROM Book
WHERE searchable ## to_tsquery($searchable);
`, {
type: QueryTypes.SELECT,
bind: {
searchable: req.query.searchable
}
})

Mongoose 6 - How to get only the custom validation error message without the error?

const userSchema = new mongoose.Schema({
email: {
type: String,
unique: true,
required: [true, 'Email address is required'],
minlength: [4, 'Must be at least 4 characters long']
}
});
When I log the error I get:
User validation failed: email: Must be at least 4 characters long
How can I get only my custom message:
Must be at least 4 characters long
The only thing I do as of now is
const messageString = message.split(':');
messageString[3]
But is there a better way? I saw some solutions for this but its all for mongoose version 5 or less and it doesnt apply to version 6 as the error object has different properties.
On a side note (maybe this should be a seperate question) but as the error object is different now, how can I check if its a mongoose validation error? The err object doesnt come with it anymore.
Error: User validation failed: email: Must be at least 4 characters long
at ValidationError.inspect (/Users/... {
errors: {
email: ValidatorError: Must be at least 4 characters long
at validate (/Users/user/....
at processTicksAndRejections (node:internal/process/task_queues:78:11) {
properties: [Object],
kind: 'minlength',
path: 'email',
value: 'hgg',
reason: undefined,
[Symbol(mongoose:validatorError)]: true
}
},
_message: 'User validation failed'
}
Also, I dont want to have to access the modal property 'email' when accessing any other property as in my errorhandler, I want to keep it reusable for other errors too

Serverless Framework and DynamoDB: Unexpected token in JSON at JSON.parse

Hi I followed this Serverless + AWS REST API tutorial and it went great, I got it to work.
Now, I'm trying to modify it but have hit a wall while trying to submit data into the DynamoDB table.
Using Postman to submit a valid JSON object I get a 502 response. If I test the function in Lambda, I get the following error:
{
"errorType": "SyntaxError",
"errorMessage": "Unexpected token o in JSON at position 1",
"trace": [
"SyntaxError: Unexpected token o in JSON at position 1",
" at JSON.parse (<anonymous>)",
" at Runtime.module.exports.submit [as handler] (/var/task/api/interview.js:11:28)",
" at Runtime.handleOnce (/var/runtime/Runtime.js:66:25)",
" at process._tickCallback (internal/process/next_tick.js:68:7)"
]
}
After searching for solutions, what I found out is that it seem like the event that is being passed as JSON.parse(event)is undefined.
Here's the serverless.yml:
service: interview
frameworkVersion: ">=1.1.0 <2.0.0"
provider:
name: aws
runtime: nodejs10.x
stage: dev
region: us-east-1
environment:
INTERVIEW_TABLE: ${self:service}-${opt:stage, self:provider.stage}
INTERVIEW_EMAIL_TABLE: "interview-email-${opt:stage, self:provider.stage}"
iamRoleStatements:
- Effect: Allow
Action:
- dynamodb:Query
- dynamodb:Scan
- dynamodb:GetItem
- dynamodb:PutItem
Resource: "*"
resources:
Resources:
CandidatesDynamoDbTable:
Type: 'AWS::DynamoDB::Table'
DeletionPolicy: Retain
Properties:
AttributeDefinitions:
-
AttributeName: "id"
AttributeType: "S"
KeySchema:
-
AttributeName: "id"
KeyType: "HASH"
ProvisionedThroughput:
ReadCapacityUnits: 1
WriteCapacityUnits: 1
StreamSpecification:
StreamViewType: "NEW_AND_OLD_IMAGES"
TableName: ${self:provider.environment.INTERVIEW_TABLE}
functions:
interviewSubmission:
handler: api/interview.submit
memorySize: 128
description: Submit interview information and starts interview process.
events:
- http:
path: interviews
method: post
and the interview.js
'use strict';
const uuid = require('uuid');
const AWS = require('aws-sdk');
AWS.config.setPromisesDependency(require('bluebird'));
const dynamoDb = new AWS.DynamoDB.DocumentClient();
module.exports.submit = (event, context, callback) => {
const requestBody = JSON.parse(event);
const fullname = requestBody.fullname;
const email = requestBody.email;
const test = requestBody.test;
const experience = requestBody.experience;
if (typeof fullname !== 'string' || typeof email !== 'string' || typeof experience !== 'number') {
console.error('Validation Failed');
callback(new Error('Couldn\'t submit interview because of validation errors.'));
return;
}
submitInterviewP(interviewInfo(fullname, email, experience, test))
.then(res => {
callback(null, {
statusCode: 200,
body: JSON.stringify({
message: `Sucessfully submitted interview with email ${email}`,
interviewId: res.id
})
});
})
.catch(err => {
console.log(err);
callback(null, {
statusCode: 500,
body: JSON.stringify({
message: `Unable to submit interview with email ${email}`
})
})
});
};
const submitInterviewP = interview => {
console.log('Submitting interview');
const interviewInfo = {
TableName: process.env.INTERVIEW_TABLE,
Item: interview,
};
return dynamoDb.put(interviewInfo).promise()
.then(res => interview);
};
const interviewInfo = (fullname, email, experience,test) => {
const timestamp = new Date().getTime();
return {
id: uuid.v1(),
fullname: fullname,
email: email,
experience: experience,
test: test,
submittedAt: timestamp,
updatedAt: timestamp,
};
};
If I replace the event param for a valid JSON object and then deploy again. I'm able to successfully insert the object into dynamoDB.
Any clues? Please let me know if there's anything I missing that could help.
Thanks!
API Gateway stringify the request body in event's body property.
Currently you are trying to parse event object const requestBody = JSON.parse(event); which is wrong. You need to parse event.body property:
const requestBody = JSON.parse(event.body);

"email" validation rule crash sails server - Mongo with Sails.js

while the email validation rule fails on module of the sails.js, the server is crashing.
Here the snippet of my module:
// The user's email address
email: {
type: 'string',
email: true,
required: true,
unique: true
},
And the error as below :
err: Error (E_VALIDATION) :: 1 attribute is invalid
at WLValidationError.WLError (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\error\WLError.js:26:15)
at new WLValidationError (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\error\WLValidationError.js:20:28)
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\query\validate.js:45:43
at allValidationsChecked (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\core\validations.js:203:5)
at done (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:135:19)
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:32:16
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\core\validations.js:184:23
at done (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:135:19)
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:32:16
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\core\validations.js:157:64
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:125:13
at Array.forEach (native)
at _each (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:46:24)
at Object.async.each (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:124:9)
at validate (C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\waterline\lib\waterline\core\validations.js:156:11)
at C:\Users\yuri\AppData\Roaming\npm\node_modules\sails\node_modules\async\lib\async.js:125:13
Invalid attributes sent to User:
• email
• undefined should be a email (instead of "admin#gmailasd", which is a string)
The correct way to declare an email field is like this :
email: {
type: 'email',
required: true,//Email field will be required for insert or update
unique: true //Insert or update will crash if you try to insert duplicate email
},
You can see all different attribut types here http://sailsjs.org/documentation/concepts/models-and-orm/attributes
If you want to catch insert/update errors you can do this on your controller :
MyModel.create({email:email}).exec(function(err, model)
{
if(err)
{
//Check if it's a validation error or a crash
if(err.code == "E_VALIDATION")
sails.log.debug("valid fail, check form");
else
sails.log.debug("crash");
}
else
{
//do what you want to do with the data
}
});
Her the answer.
Thanks to jaumard, i found the problem.
I used undefined field in error, without checking if exists before
err.originalError.code but it was undefined.
So the correct way is :
err.originalError && err.originalError.code && err.originalError.code === 11000
and not
err.originalError.code === 11000.
Previous versions of Sails recommended that email validation was achieved like this
email: {
type: 'string',
email: true,
required: true
},
The current version should be like this
email: {
type: 'email',
required: true
},

Typeahead: Uncaught Error Missing source

I am getting this error in console everytime I try to run the following code
$('#autcomplete_search').typeahead({
highlight: true
},
{
name: 'apple_game',
remote: "/search/autocomplete?keyword=make"
});
Typeahead: Uncaught Error Missing source
If fetching remote, you need to specify the engine. Here is an example from the docs
var bestPictures = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace('value'),
queryTokenizer: Bloodhound.tokenizers.whitespace,
prefetch: '../data/films/post_1960.json',
remote: '../data/films/queries/%QUERY.json'
});
bestPictures.initialize();
$('#remote .typeahead').typeahead(null, {
name: 'best-pictures',
displayKey: 'value',
source: bestPictures.ttAdapter()
});
in this example, var bestPictures is your engine
bloodhound documentation