Schema validate causes Save to fail in Mongoose - node.js

Why does the following code causes the Mongoose save to fail? If I remove validate: utils.uuid.isValid (returns a boolean) the process will complete:
var accountSchema = new mongoose.Schema({
_id : { type: String, default: utils.uuid.init, validate: utils.uuid.isValid },
n : { type: String }, // Display Name
ac : { type: Date },
au : { type: Date },
ad : { type: Date },
am : { type: String }
});
Also, if I remove the validate property of the field, and try setting values in the pre('validate', cb) or pre('save', cb) call, this will cause the same result:
accountSchema.pre('validate', function(next) {
var now = new Date(),
memberId = this.am;
console.log('In Account validate (member : ' + memberId + ')');
if (this.isNew) { this.ac = now; console.log('Item is new.'); }
else if (this.isModified()) { this.au = now; console.log('Item is modified.'); }
else { return next();
console.log('Canceling Account validate.');
}
console.log(JSON.stringify(this));
console.log('Completed Account validate.');
next();
});
Nothing crashes. In Webstorm, processing just stops with a message "Process finished with exit code 139".

Related

Mongoose $gte and $lte queries not working

I have the following method:
const getArbolMarkers = async ({ latitudes, longitudes }) => {
// console.log(latitudes, longitudes);
try {
const markersLatLon = await Arbol.find({}, {
_id: 0,
lon: true,
})
for (let i = 0; i < markersLatLon.length; i++) {
if (markersLatLon[i].lon >= longitudes.min && markersLatLon[i].lon <= longitudes.max) console.log('DBLon: ' + markersLatLon[i].lon + '\nMaxLon: ' + longitudes.max + '\nMinLon: ' + longitudes.min + '\n');
}
const markers = await Arbol.find({
lon: {
$gte: longitudes.min,
$lte: longitudes.max
}
})
return markers;
} catch (err) {
console.log(err);
return res.status(500).send("Error");
}
};
And this is my model:
var mongoose = require('mongoose');
const ArbolSchema = mongoose.Schema({
name: {
type: String,
require: true
},
lat: {
type: Number,
require: true
},
lon: {
type: Number,
require: true
},
});
module.exports = mongoose.model('ArbolSchema', ArbolSchema, 'arboles');
When I try to filter my collection through $gte and $lte, i'm not receiving any value, just an empty array. But when doing it "manually" in my for cycle i'm obtaining the following input:
DBLon: -106.07637092471123
MaxLon: -106.07548742070055
MinLon: -106.07696542070055
DBLon: -106.07608459889889
MaxLon: -106.07548742070055
MinLon: -106.07696542070055
DBLon: -106.0762995108962
MaxLon: -106.07548742070055
MinLon: -106.07696542070055
...
Meaning that there are multiple records that really exists on my collection and meet the conditions for max and min lon. Is there any reason in particular for this behaviour?

Casting error while saving in the database

I am using Angular as frontend and NodeJS for the backend.
I have a route that saves the data received from the frontend to the database. When I execute the save() method, I get prompted the following error:
err : ValidationError: conf.0: Cast to [Boolean] failed for value "[ {
name: 'v', percentage: 2, type: false, status: true } ]" (type string)
at path "conf.0"
Below is the route that stores the data:
app.post("/api/submitTaxCollection", (req, res) => {
console.log(req.body);
const submitTaxSchema = new addTaxesSchema(req.body);
try {
submitTaxSchema.save(function (err) {
if (err) return console.log("err : " + err);
});
} catch (error) {
console.log("ERROR : " + error);
return res.send(error);
}
});
and this is the schema.ts file:
var mongoose = require("mongoose");
//Define a schema
var taxSchema = mongoose.Schema;
var AddTaxSchema = new taxSchema({
parentId: String,
conf: [
{
name: String,
percentage: Number,
type: Boolean,
status: Boolean,
},
],
});
var newTaxesSchema = mongoose.model("addTaxSchema", AddTaxSchema);
module.exports = newTaxesSchema;
In Angular, model is setup as below:
export class TaxRatesConfigurationsModel {
name: string = "";
percentage: number = 0;
type: boolean = false;
status: boolean = true;
}
export class TaxRatesModel {
parentId: string = "";
conf: TaxRatesConfigurationsModel[] = [];
}
and I am calling the API as below:
this._httpService
.post(environment.url + "/api/submitTaxCollection", request)
.subscribe((data) => {
console.log(data);
});
when I console.log(req.body);, I get the following printed to the console (Nodejs):
{
parentId: '23948923nur8cw9yicnyu',
conf: [ { name: 'v', percentage: 2, type: false, status: true } ]
}
and the error occurs in Nodejs
What is causing this weird issue?

Mongoose post save hook, modifiedPaths() is always empty

Our aim is to have a post hook in place where we can track changed fields.
Model file:
const VariationSchema = new Schema({
_id: {
type: String,
},
title: {
type: String,
},
desc: {
type: String,
},
});
VariationSchema.post('save', async function (doc) {
console.log(doc.modifiedPaths());
});
const VariationModel = mongoose.model('variation', VariationSchema);
module.exports = {
VariationModel,
VariationSchema,
};
Service file:
const variationDocument = await VariationModel.findById(variationId).select({});
variationDocument.desc = (Math.random() + 1).toString(36).substring(7);
await variationDocument.save();
return variationDocument.toJSON();
No matter what we do, doc.modifiedPaths() is always empty. Please help

Routing to sub docs with express 4 and mongoose

EDIT: It's possible the problem is an issue with pathing. my current query looks like this:
router.route('/projects/:project_id/techDetails')
.get(function(req, res) {
Project.findById(req.params.project_Id, function(err, project) {
if (err)
return res.send(err);
res.json(project);
console.log('get success (project techDetails)');
});
});
this returns null. even though it's identical to a working line of code in every way except for the addition of `/techDetails' to the route.
original question:
I'm building a MEAN stack app with express and mongo. I can't figure out how to route to nested documents properly.
here is my Project schema:
const ProjectSchema = new Schema({
idnumber: { type: Number, required: true },
customername: String,
projectdetails: String,
jobaddress: String,
techDetails: [{
scope: String,
edgedetail: String,
lamination: String,
stonecolour: String,
slabnumber: String,
slabsupplier: String,
purchaseordernum: String,
splashbacks: String,
apron: String,
hotplate: String,
sink: String,
sinkdetails: String,
tappos: String
}],
sitecontactname: String,
sitecontactnum: String,
specialreq: String,
install_date: String,
created_on: { type: Date, default: Date.now },
created_by: { type: String, default: 'SYSTEM' },
active: { type: Boolean, default: true },
flagged: { type: Boolean, default: false },
});
I can successfully route to /projects with GET and POST, and /projects/:project_id with GET, PUT and DEL.
using the PUT route and a project's _ID i can push new entries to a project's techDetails subdoc array. the resulting JSON data looks like this:
{
"_id": "59e577e011a3f512b482ef13",
"idnumber": 52,
"install_date": "10/20/2017",
"specialreq": "some...",
"sitecontactnum": "987654321",
"sitecontactname": "bill",
"jobaddress": "123 st",
"projectdetails": "some stuff",
"customername": "B Builders",
"__v": 16,
"flagged": false,
"active": true,
"created_by": "SYSTEM",
"created_on": "2017-10-17T03:24:16.423Z",
"techDetails": [
{
"scope": "Howitzer",
"edgedetail": "12mm",
"lamination": "No",
"stonecolour": "Urban™",
"slabnumber": "1",
"slabsupplier": "Caesarstone",
"purchaseordernum": "no",
"splashbacks": "No",
"apron": "No",
"hotplate": "N/A",
"sink": "N/A",
"sinkdetails": "no",
"tappos": "no",
"_id": "59e577e011a3f512b482ef14"
},
{
"scope": "kitchen",
"edgedetail": "12mm",
"lamination": "etc",
"_id": "59e7da445d9d7e109c18f38b"
},
{
"scope": "Vanity",
"edgedetail": "12mm",
"lamination": "No",
"stonecolour": "Linen™",
"slabnumber": "1",
"slabsupplier": "Caesarstone",
"purchaseordernum": "1",
"splashbacks": "No",
"apron": "No",
"hotplate": "N/A",
"sink": "N/A",
"sinkdetails": "no",
"tappos": "woo",
"_id": "59e81e3324fb750fb46f8248"
}//, more entries omitted for brevity
]
}
as you can see everything so far is working as expected. However now i need to edit and delete individual entries in this techDetails array. i'd also like to route to them directly using projects/:project_id/techDetails and projects/:project_id/techDetails/:techdetails_id.
From what i can see there are two approaches to this. either i can:
A) use a new routing file for the techDetails that uses mergeParams. this is the approach i'm trying currently, however I can't figure out how to complete the .find to return all techDetails, since i can only use the Project model schema and i'm unsure how to access the sub docs.
an excerpt from my routes.js:
const techDetails = require('./techDetails');
//other routes here
//see techdetails file
router.use('/projects/:project_id/techdetails', techDetails);
//here lies an earlier, failed attempt
/* router.route('/projects/:project_id/techdetails/:techDetails_id')
.get(function(req, res) {
Project.findById(req.params.project_id.techDetails_id, function(err,
project) {
if (err)
return res.send(err);
res.json(project.techDetails);
console.log('get success (techDetails)');
});
})
; */
and my techdetails.js:
const express = require('express');
const Project = require('./models/project');
const router = express.Router({mergeParams: true});
router.get('/', function (req, res, next) {
/* Project.find(function(err, techDetails) {
if (err)
return res.send(err);
res.json(techDetails);
console.log('get success (all items)');
}); */
res.send('itemroutes ' + req.params);
})
router.get('/:techDetails_id', function (req, res, next) {
res.send('itemroutes ' + req.params._id)
})
module.exports = router
I can successfully check that the routes work with Postman, both will receive the response. now the problem is, instead of res.send i want to use res.json with Project.find (or similar) to get the techDetails.
however there is also another option:
B) put the techDetails document into it's own schema and then populate an array of IDs inside projects.
however this seems more complex so i'd rather avoid having to do so if i can.
any thoughts and suggestions welcome. let me know if more of my code is needed.
In this particular case I would put techDetails in a separate schema:
const ProjectSchema = new Schema({
idnumber: { type: Number, required: true },
customername: String,
projectdetails: String,
jobaddress: String,
techDetails: [techDetailsSchema],
sitecontactname: String,
sitecontactnum: String,
specialreq: String,
install_date: String,
created_on: { type: Date, default: Date.now },
created_by: { type: String, default: 'SYSTEM' },
active: { type: Boolean, default: true },
flagged: { type: Boolean, default: false },
});
Don't register the techDetails schema with mongoose.model as it is a subdocument. Put it in a separate file and require it in the project model file (const techDetailsSchema = require('./techDetails.model');).
I would create the controller functions like this:
Getting with GET (all):
module.exports.techDetailsGetAll = function (req, res) {
const projectId = req.params.projectId;
Project
.findById(projectId)
.select('techDetails')
.exec(function (err, project) {
let response = { };
if (err) {
response = responseDueToError(err);
} else if (!project) {
response = responseDueToNotFound();
} else {
response.status = HttpStatus.OK;
response.message = project.techDetails;
}
res.status(response.status).json(response.message);
});
}
Getting with GET (one):
module.exports.techDetailsGetOne = function (req, res) {
const projectId = req.params.projectId;
const techDetailId = req.params.techDetailId;
Project
.findById(projectId)
.select('techDetails')
.exec(function (err, project) {
let response = { };
if (err) {
response = responseDueToError(err);
} else if (!project) {
response = responseDueToNotFound();
} else {
let techDetails = project.techDetails.id(techDetailId);
if (techDetails === null) {
response = responseDueToNotFound();
} else {
response.status = HttpStatus.OK;
response.message = techDetails;
}
}
res.status(response.status).json(response.message);
});
}
For adding with POST:
module.exports.techDetailsAddOne = function (req, res) {
const projectId = req.params.projectId;
let newTechDetails = getTechDetailsFromBody(req.body);
Project
.findByIdAndUpdate(projectId,
{ '$push': { 'techDetails': newTechDetails } },
{
'new': true,
'runValidators': true
},
function (err, project) {
let response = { };
if (err) {
response = responseDueToError(err);
} else if (!project) {
response = responseDueToNotFound();
} else {
response.status = HttpStatus.CREATED;
response.message = project.techDetails; // for example
}
res.status(response.status).json(response.message);
});
}
For updating with PUT
module.exports.techDetailsUpdateOne = function (req, res) {
const projectId = req.params.projectId;
const techDetailId = req.params.techDetailId;
let theseTechDetails = getTechDetailsFromBody(req.body);
theseTechDetails._id = techDetailId; // can be skipped if body contains id
Project.findOneAndUpdate(
{ '_id': projectId, 'techDetails._id': techDetailId },
{ '$set': { 'techDetails.$': theseTechDetails } },
{
'new': true,
'runValidators': true
},
function (err, project) {
let response = { };
if (err) {
response = responseDueToError(err);
res.status(response.status).json(response.message);
} else if (!project) {
response = responseDueToNotFound();
res.status(response.status).json(response.message);
} else {
project.save(function (err) {
if (err) {
response = responseDueToError(err);
} else {
response.status = HttpStatus.NO_CONTENT;
}
res.status(response.status).json(response.message);
})
}
});
}
And deleting with DELETE:
module.exports.techDetailsDeleteOne = function (req, res) {
const projectId = req.params.projectId;
const techDetailId = req.params.techDetailId;
Project
.findById(projectId)
.select('techDetails')
.exec(function (err, project) {
let response = { }
if (err) {
response = responseDueToError(err);
res.status(response.status).json(response.message);
} else if (!project) {
response = responseDueToNotFound();
res.status(response.status).json(response.message);
} else {
let techDetail = project.techDetails.id(techDetailId);
if (techDetail !== null) {
project.techDetails.pull({ '_id': techDetailId });
project.save(function (err) {
if (err) {
response = responseDueToError(err);
} else {
response.status = HttpStatus.NO_CONTENT;
}
res.status(response.status).json(response.message);
})
} else {
response = responseDueToNotFound();
res.status(response.status).json(response.message);
}
}
});
}
And finally routing like this:
router
.route('/projects')
.get(ctrlProjects.projectsGetAll)
.post(ctrlProjects.projectsAddOne);
router
.route('/projects/:projectId')
.get(ctrlProjects.projectsGetOne)
.put(ctrlProjects.projectsUpdateOne)
.delete(ctrlProjects.projectsDeleteOne);
router
.route('/projects/:projectId/techDetails')
.get(ctrlTechDetails.techDetailsGetAll)
.post(ctrlTechDetails.techDetailsAddOne);
router
.route('/projects/:projectId/techDetails/:techDetailId')
.get(ctrlTechDetails.techDetailsGetOne)
.put(ctrlTechDetails.techDetailsUpdateOne)
.delete(ctrlTechDetails.techDetailsDeleteOne);
This is what I prefer when I'm constantly updating the subdocument independently of the rest of the document. It doesn't create a separate collection, so no need for populate.
EDIT:
This answer goes more into detail on whether you should use embedding or referencing. My answer uses embedding.
So, the solution i came to was a combo of A) and B). I used a separate routing file and put ({mergeParams: true}) in the router declaration, and i created a separate file for the techDetails nested model, without declaring it. However I don't believe either of these actually made any significance... but anyway.
the working code i ended up with was, in my routes:
router.use('/projects/:project_id/techDetails', TechDetails);
and in techDetails.js:
const router = express.Router({mergeParams: true});
router.route('/')
.get(function(req, res) {
Project.findById(req.params.project_id,
'techDetails', function(err, project) {
if (err)
return res.send(err);
res.json(project);
console.log('get success (project techDetails)');
});
});
What's different about it? namely, the 'techDetails', parameter in the Project.findById line. According to the mongoose API this acts as a select statement. The only other major difference is I fixed a typo in my original code ( project_id was written project_Id. dubious... ). I probably would have noticed this if i was using VS or something instead of notepad++, but it is my preferred coding arena.
It may be possible to return res.json(project.techDetails) and remove the 'techDetails', select parameter, but I likely won't test this.
Edit: Turns out migrating techDetails to a separate file meant they no longer generated with objectIds, which is crucial for PUT and DEL. I might've been able to work around them with a simple pair of curly braces inside the array declaration, but I didn't think of that until after i re-migrated it back to the project schema...

MongoDB: stored base64 buffer data and retrieved base64 does not match

I am posting small sized base64 encoded images (around 100-200K each) directly into mongodb using Mongoose. Before storing I could see the content is same as what sent from the client. But the query base64 string is not same as what went in? Any idea what I am missing ?
// process the add image request
app.post('/addimage', function(req, res) {
console.log("Addimage post....");
var qry = {'_email' : req.user.local.email, 'date' : req.body.date, 'subject' : req.body.subject };
Item.findOne(qry, function(err, item) {
if (err) {
console.log('Find images failed: ' + err);
var resp = '{"result" : "failed", "msg" : ' + err + ', "_req" : "addimage"}';
res.send(resp);
} else {
if (item == null) { // item doesn't exist, create now
var item = new Item();
item._email = req.body._email;
item.date = req.body.date;
item.subject = req.body.subject;
}
// add image
var image = new Image();
image.timestamp = req.body.timestamp;
image.contentType = req.body.contentType;
var imgBuf = new Buffer(req.body.image, 'base64');
image.image = imgBuf;
item.images.push(image);
item.save(function(err, result) {
if (err) {
console.log('Find images failed: ' + err);
var resp = '{"result" : "failed", "msg" : ' + err + ', "_req" : "addimage"}';
res.send(resp);
} else {
res.send('{"result" : "ok", _req: "addimage"}');
console.log("Image saved and responded to ...");
}
});
}
});
});
The data string stored in req.body.image started with something like "/9j/4AAQSkZJ ..." but from mongo shell I see it different. Is it the subtype 0 for the Bindata that's causing this ?
> db.items.find({subject:"Science"},{'images.image':1})
{ "_id" : ObjectId("547382943fc884447a767d58"), "images" : [ { "image" : BinData(0,"LwA5AGoALwA0AEEAQQBRAFMAawBaAEoAUgBnAEEAQgBBAFEAQQBBAEEAUQBBAEIAQQBBAEQALwA0AFEAQgBZAFIAWABoAHAAWgBnAEEAQQBUAFUAMABBAEsAZwBBAEEAQQBBAGcAQQBBAGcARQBTAEEAQQBN
Schema:
var imageSchema = new mongoose.Schema({
timestamp : String,
contentType : String,
image : Buffer,
}, { strict: true });
imageSchema.index({ timestamp: 1}, { unique: true });
var itemSchema = new mongoose.Schema({
_email : {
type: String,
required: true,
},
date : {
type: String,
required: true,
},
subject : {
type: String,
required: true,
},
images : [imageSchema],
}, { strict: false });
itemSchema.index({ _email: 1, date: 1, subject: 1}, { unique: true });
Mongodb version 2.6.5, Mongoose 3.8.1, Node v0.11.6-pre
Also, tried 'utf8' / 'ascii' / 'ucs2' instead of 'base64' while creating Buffer object.
Thanks!
It was node version ;( Restoring to 0.10.33 solved the issue !

Resources