Load and save document in mongodb with mongoose - javascript

I have node running with express as the server side framework.
I have created the following endpoint:
app.post('/post/save', auth.auth, function (req, res) {
Post.findById(req.body._id, function (err, post) {
post = post || new Post();
post.author.name = req.user.getName();
post.author.id = req.user._id;
post.title = req.body.title;
post.body = req.body.body;
post.save(function (err, object) {
err && res.send(500);
res.status(200).send({ /*id: object._id*/ });
});
});
});
When I call this the first time, it works.
When I call this the second time, it fails. The request just keeps pending, and the object returned from the save function call is undefined.
req.body._id is undefined in both the requests. I try to create 2 new posts in a row.
What I want to do is to check if a document exist, if it does, update it and then save it, or create a new document.
I know stuff like upsert exist, but I cant use it because I need the pre-save middleware to trigger, and it only triggers before .save.
Can anyone see the error?

What if you put your logic to a callback, and then - either create or find a Post based on the request query value, passing your callback function? Just dont forget to remove this assignment: post.author.id = req.user._id;
app.post('/post/save', auth.auth, function (req, res) {
var callback = function(post) {
post.author.name = req.user.getName();
post.title = req.body.title;
post.body = req.body.body;
post.save(function (err, object) {
err && res.send(500);
res.status(200).send({ /*id: object._id*/ });
});
};
if (req.body._id) {
Post.findById(req.body._id, function (err, post) {
callback(post);
});
} else {
var post = new Post();
callback(post);
}
});

My original post worked, once I removed the unique field from the model, and dropped the collections in the database.
It might have been enough to drop the indexes; see Leonid Beschastnys comment;
when you're setting a field to be unique, Mongoose creates an unique
index on this field. This index persist in MongoDB even after removing
unique: true flag. Dropping collection indexes should resolve your
problem

Related

Is there a way to request more than one url in a get method in javascript?

I'm new to java script and I'm having trouble getting my code to work. My get functions need to get from one url, and then get from another and I continuously get header has already been set errors.
app.get('/api/movies/categories/:category',(req,res, next)=>{
// returns an array of all the movies that fit this year
//FUTURE IDEA: Check that is has also won an ocscar!
var inner=0;
var category = req.params.categories;
for (i=0; i < csvarray.length; i++){
if (csvarray[i]['categories'] == category){
var prevlength = list2.length;
next();
if(prevlength != list2.length){
list2[o--] = csvarray[i]['categories'];
}
}
}
if(list2.length == 0){
res.status(404).send(`The Movie category ${req.params} was not found`);
}
res.send(list2); //if you find the year then send it to the user
});
app.get('/api/movies/winners/:winner',(req, next)=>{
var Trues = req.params.winner;
if(csvarray[o]['winners'] == Trues){
list2[o++];
return;
}
});
'''
list2 and o are constants so that both get functions could use them. If there is a way to get a function with a req and takes a url that would be perfect, but so far I cannot find any way to do it.
Get in the habit of using return whenever you send a response. That way it only ever sends one response.
The reason why the error that headers have already been set is showing is because you are calling res.send() twice and both of them are being run in the same call to the API handler.
In order to fix it, just use:
return res.status(404).send(...
const async = require('async');
const request = require('request');
function httpGet(url, callback) {
const options = {
url : url,
json : true
};
request(options,
function(err, res, body) {
callback(err, body);
}
);
}
Use promise.all or manually do it by below code
const urls= [
"http://localhost:3010/alm/build_tool",
"http://localhost:3010/alm/development_tool",
"http://localhost:3010/alm/project_architecture"
];
async.map(urls, httpGet, function (err, res){
if (err) return console.log(err);
console.log(res);
});

How to implement query parameters in Postman with mongoose

I have a driver.js that contains a driver schema. Also the driverController.js, which contains my rest methods. GET, POST, DELETE, and PUT.
What i would like to do is
GET - http://localhost:3000/drivers?available=true
and have it return all of the drivers that are available.
My driver schema simply looks like this:
var mongoose = require('mongoose');
var DriverSchema = new mongoose.Schema({
name: String,
available: Boolean,
latitude: Number,
longitude: Number
});
mongoose.model('Driver', DriverSchema);
module.exports = mongoose.model('Driver');
I looked at some documentation, but I haven't been able to do anything.
Here's my GET method in which I'm attempting to add parameters
// GETS ALL DRIVERS FROM THE DATABASE
router.get('/', function (req, res) {
Driver.find({}, function (err, driver) {
if (err) return res.status(500).send("There was a problem finding the drivers.");
var available = req.query.available;
if (available == driver.available )
res.status(200).send(available );
else
res.status(200).send("Nice! " + driver.available);
});
});
This comparison doesn't ever work. It always goes to the else statement. I'm not quite sure why but the output is "Nice! undefined" Even though I have plenty of drivers in my database, and if I only put inside the else statement
res.status(200).send("Nice! " + driver);
Then it gives me the list of drivers.
Nonetheless, I would like to be able to use query parameters in order to find drivers.
Any hints or tips would be greatly appreciated, as this is a project and I have never worked with restAPI, or javascript before. Thanks!
NOTE: Mongoose, express. node.js, and mongoDB are being used.
if I only put inside the else statement res.status(200).send("Nice! " + driver); Then it gives me the list of drivers.
it's a list of drivers, with if (available == driver.available ) you're comparing a boolean with an array of objects,
instead of fetching all the drivers and checking if they have availabe == true , add the condition to the .find() and return the result :
// GETS ALL DRIVERS FROM THE DATABASE
router.get('/', function (req, res) {
Driver.find({ available : req.query.available }, function (err, drivers) {
if (err) return res.status(500).send("There was a problem finding the drivers.");
res.status(200).send(drivers);
});
});
EDIT :
you can do this to add filter depending on the query string :
// GETS ALL DRIVERS FROM THE DATABASE
router.get('/', function (req, res) {
var params = {};
Object.keys(req.query).forEach((v, k) => params[k] = v);
Driver.find(params, function (err, drivers) {
if (err) return res.status(500).send("There was a problem finding the drivers.");
res.status(200).send(drivers);
});
});
having ?name=Wario&available=true will create an object like { name : 'wario', available : true and pass it to the .find()

Cannot set property 'employeeNum' of undefined

I am writing a function that is meant to add an employee to the end of a list of employees, but I continue to be met with the error in the title. I've tried to alter the code, but I'm not sure what I'm doing wrong. Here's the function:
data-service.js
module.exports.addEmployee = function(employeeData) {
employeeData.employeeNum = ++empCount;
return new Promise(function(resolve,reject) {
employees.push(employeeData);
if(employees.length == 0) {
reject("no results returned");
}
resolve(employeeData);
});
}
server.js
app.get("/employees/add", (req,res) => {
res.render("addEmployee");
});
app.post("/employees/add", (req, res) => {
console.log(req.body);
res.redirect("/employees");
});
The current function is not the root of the problem... However, you are trying to set a property on a param that you expect to be an object. But the caller has either passed a variable that has a value === undefined, or perhaps is passing no params at all ( either way, the param employeeData is undefined and you have no checks against it, thus we see the error).

Writing a function to adapt for two different cases?

Maybe I am not thinking hard enough but lets say I have code that is 99% similar what would be the most minimal way of building a function for it?
// this is just an express route, not the function I am building
// doPay() is the function I am trying to build properly
function(req,res) {
if(req.user) {
// I can use req.user.id in my function
doPay(req,res);
} else {
passport.authenticate('local-signup', function(err, user, info) {
// instead of req.user.id I would just need user.id
doPay();
});
}
}
doPay()
// My doPay() function, above I need to pass either req.user.id or user.id
// based on the boolean, so how do I adjust this to adapt to that?
gateway.customer.find(req.user.id, function(err, customer) {
//do payment stuff for an existing user, if the user is new I need to use
//user.id above
});
Here are two alternate designs.
You could make the function take 3 parameters
function doPay(req, res, user)
and then test in that function to select what is available
var userId;
if (req && req.user && req.user.id) userId = req.user.id;
if (!userId && user && user.id) userId = user.id;
if (!userId) throw "error...";
or you could choose to make the 3rd parameter the userId to use
function doPay(req,res,userId)
and put the userId source logic in the routing code instead of the doPay code. The doPay would just use the userId it was told to use. I think this design is what #Bergi was suggesting in the first comment.

Increment a number field with Mongoose? For sorting results on .find

I have a mongoose model that looks like this:
module.exports = mongoose.model('Item', {
text : String,
position: Number
});
And I'm looking to have a Position field that increments on something like the .length of all the documents, for sorting the results of a .find of All:
// get All Items
app.get('/itemsList', function(req, res) {
// use mongoose to get all items in the database
Item.find({
sort : { position: 1 } // sort by Ascending Position
}. function(err, items) {
// if there is an error retrieving, send the error. nothing after res.send(err) will execute
if (err)
res.send(err)
res.json(items); // return all items in JSON format
});
});
Is there a way to auto-fill a number for the Position field with some javascript in node.js?
// create an item
app.post('/api/item', function(req, res) {
// create an item, information comes from AJAX request from Angular
Item.create({
text : req.body.text,
position:
// something using ++items.length
}, function(err, item) {
if (err)
res.send(err);
});
});
Mongoose lets you hook into the save, validate and remove methods and execute code before and after they're executed.
This code can be asynchronous. For example, in your case you could probably do this:
var schema = mongoose.Schema({
text : String,
position: Number
});
schema.pre("validate", function(next) {
var doc = this;
// If 'position' is not filled in, fill it in.
// Not using !position because 0 might be a valid value.
if(typeof position !== "number") {
// Count the number of Items *
mongoose.model("Item").count(function(err, num) {
// If there was an error, pass it to next().
if(err)
return next(err);
// Update the position, then call next();
doc.position = num;
return next();
});
} else {
// There is no need to count, so call next().
next();
}
});
module.exports = mongoose.model('Item', schema);
More here.
Before validation starts, the number of Items is counted. Afterwards, the position is set.
Validation and other pre-validator ** hooks will not commence until the above code is ready.
* I'm using mongoose.model here to fetch the model because the model is not compiled yet (that happens a bit below).
** The documentation shows you how you can make multiple pre-validator hooks execute in parallel. I've chosen not to do this in this example because the code is easier to read and because you might actually need the validators to run sequentially.
In the pre-validation hook, you could place some logic in the else-case. When inserting an Item with an existing position value, you'll want to move every record down. You can do this by doing the following:
Use this.isModified("position") to check if the value was changed since you last saved. You might also need doc.isNew().
Check if there is an existing document with the same position. Something like Item.where({_id: {$ne: this._id}, position: this.position}).count()
If there is, execute: Item.update({position: {$gte: this.position}}, {position: {$inc: 1}}, {multi: 1})
Then call next() to save your doc.
The above should work. It will leave gaps when you remove documents however.
Also, look into indexes. You'll want to add one on the position field. Perhaps even a unique index.
Per #RikkusRukkus's steps for moving records down, here's logic for the else-case (to be tested)
// load mongoose since we need it to define a schema and model
var mongoose = require('mongoose');
var ItemSchema = mongoose.Schema({
text : String,
position: Number
});
// before validation starts, the number of Items is counted..afterwards, the position is set
ItemSchema.pre("validate", function(next) {
var doc = this;
// if 'position' is not filled in, fill it in..not using !position because 0 might be a valid value
if(typeof position !== "number") {
// count the number of Items *
// use mongoose.model to fetch the model because the model is not compiled yet
mongoose.model("Item").count(function(err, num) {
// if there was an error, pass it to next()
if(err)
return next(err);
// set the position, then call next();
doc.position = num;
return next();
});
} else if(this.isModified("position") || this.isNew()) {
// check if there is an existing document with the same position
// use mongoose.model to fetch the model because the model is not compiled yet
mongoose.model("Item").where({_id: {$ne: this._id}, position: this.position}).count( function (err, count) {
// if there was an error, pass it to next()
if(err)
return next(err);
// if there is a doc with the same position, execute an update to move down all the $gte docs
if(count > 0) {
// use mongoose.model to fetch the model because the model is not compiled yet
mongoose.model("Item").update({position: {$gte: this.position}}, {position: {$inc: 1}}, {multi: 1}, function(err, numAffected) {
// Call next() (with or without an error)
next(err);
});
} else {
// there are no docs that need to move down, so call next()
next();
}
});
} else {
// there is no need to count or update positions, so call next()
next();
}
});
module.exports = mongoose.model('Item', ItemSchema);

Categories