Using Multiple FindOne in Mongodb - javascript

I am trying to extend the amount of fields that our API is returning. Right now the API is returning the student info by using find, as well as adding some information of the projects by getting the student info and using findOne to get the info about the project that the student is currently registered to.
I am trying to add some information about the course by using the same logic that I used to get the project information.
So I used the same findOne function that I was using for Projects and my logic is the following.
I created a variable where I can save the courseID and then I will put the contents of that variable in the temp object that sending in a json file.
If I comment out the what I added, the code works perfectly and it returns all the students that I require. However, when I make the additional findOne to get information about the course, it stops returning anything but "{}"
I am going to put a comment on the lines of code that I added, to make it easier to find.
Any sort of help will be highly appreciated!
User.find({
isEnrolled: true,
course: {
$ne: null
}
},
'email pantherID firstName lastName project course',
function(err, users) {
console.log("err, users", err, users);
if (err) {
return res.send(err);
} else if (users) {
var userPromises = [];
users.map(function(user) {
userPromises.push(new Promise(function(resolve, reject) {
///////// Added Code START///////
var courseID;
Course.findOne({
fullName: user.course
}, function(err, course) {
console.log("err, course", err, course);
if (err) {
reject('')
}
courseID = course ? course._id : null
//console.log(tempObj)
resolve(tempObj)
}),
///// ADDED CODE END //////
Project.findOne({
title: user.project
}, function(err, proj) {
console.log("err, proj", err, proj);
if (err) {
reject('')
}
//Course ID, Semester, Semester ID
//map to custom object for MJ
var tempObj = {
email: user.email,
id: user.pantherID,
firstName: user.firstName,
lastName: user.lastName,
middle: null,
valid: true,
projectTitle: user.project,
projectId: proj ? proj._id : null,
course: user.course,
courseId: courseID
}
//console.log(tempObj)
resolve(tempObj)
})
}))
})
//async wait and set
Promise.all(userPromises).then(function(results) {
res.json(results)
}).catch(function(err) {
res.send(err)
})
}
})

using promise could be bit tedious, try using async, this is how i would have done it.
// Make sure User, Course & Project models are required.
const async = require('async');
let getUsers = (cb) => {
Users.find({
isEnrolled: true,
course: {
$ne: null
}
}, 'email pantherID firstName lastName project course', (err, users) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
let findCourse = (users, cb) => {
async.each(users, (user, ecb) => {
Project.findOne({title: user.project})
.exec((err, project) => {
if (!err) {
users[users.indexOf(user)].projectId = project._id;
ecb();
} else {
ecb(err);
}
});
}, (err) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
let findProject = (users, cb) => {
async.each(users, (user, ecb) => {
Course.findOne({fullName: user.course})
.exec((err, course) => {
if (!err) {
users[users.indexOf(user)].courseId = course._id;
ecb();
} else {
ecb(err);
}
});
}, (err) => {
if (!err) {
cb(null, users);
} else {
cb(err);
}
});
};
// This part of the code belongs at the route scope
async.waterfall([
getUsers,
findCourse,
findProject
], (err, result) => {
if (!err) {
res.send(result);
} else {
return res.send(err);
}
});
Hope this gives better insight on how you could go about with multiple IO transactions on the same request.

Related

Node Js: Remove string array element from mongoDB

I have a user schema as follows:
const UserSchema = new mongoose.Schema({
skills: [String]
});
module.exports = mongoose.model("User", UserSchema);
And a Fetch request to delete a skill as follows:
const deleteItem = async (id) => {
try {
await fetch(`http://localhost:5000/api/user/deleteskill`, {
method: "DELETE",
headers: { "Content-Type": "application/JSON", token: accessToken },
body: JSON.stringify({ userid: userid , skill:id}),
})
.then((res) => res.json())
.then((data) => {
console.log("USER SKILLS:", data.userskills);
});
} catch (err) {
console.log(err);
}
};
Server
const deleteSkill = async (req, res) => {
try {
const user = await User.findById(req.body.userid)
//user.skills.pull(req.body.skill);
// removeskill = user.skills.filter(function(item) {
// return item !== req.body.skill
// })
if (user.skills.includes(req.body.skill)) {
res.status(400).json("Item Still Exists");
} else {
res.status(200).json("Item Deleted");
}
} catch (error) {
res.status(500).send({ error: error.message });
}
};
the array is in the following structure
[
'skill1', 'java', 'skill5'
]
I have tried to remove the user skill from the array in several ways but I still get res.status(400).json("Item Still Exists");. What I'm doing wrong?
Use the findOneAndUpdate method to find a document with the user id and update it in one atomic operation:
const deleteSkill = async (req, res) => {
try {
let message = "Item Deleted";
let status = 200;
const user = await User.findOneAndUpdate(
{ _id: req.body.userid },
{ $pull: { skills: req.body.skill } },
{ new: true }
)
if (user && user.skills.includes(req.body.skill)) {
message = "Item Still Exists";
status = 400;
} else if (!user) {
message = "User Not Found";
status = 404;
}
res.status(status).send({ message });
} catch (error) {
res.status(500).send({ error: error.message });
}
};
I believe you want to remove skills from the database then the following function could help you out.
var MongoClient = require('mongodb').MongoClient;
var url = "mongodb://localhost:27017/";
MongoClient.connect(url, function(err, db) {
if (err) throw err;
var dbo = db.db("mydb");
var myquery = { userid: userid, skillid: skillid};
dbo.collection("skills").deleteOne(myquery, function(err, obj) {
if (err) throw err;
console.log("1 document deleted");
db.close();
});
});
You have a method of removing elements from arrays, if you want to remove the first one you could use array.shift (more on it here), but if you want to delete it completely from your database you could always, find it and then update it.
User.update({ _id: userid }, { $pull: { "skills": "[skill]" }})

Node JS throwing cannot set headers after they are sent to the client, after using mongoose.removeOne

I have a method that deletes products and before it does it check if the user who is trying to delete the product is the user who created it. When i execute it with Insomnia it successfully removes the product but i get an error on the console saying cannot set headers after they are sent to the client.
My method:
exports.deleteProduct = (req, res) => {
const id = req.params.productId;
Product.deleteOne({ _id: id, userId: req.user._id }, () => {
return res.status(401).json("Not authorized");
})
.then(() => {
return res.status(200).json("Product deleted");
})
.catch((err) => {
return res.status(500).json({
error: err,
});
});
};
I'm pretty sure this is happening because I'm chaining a .then() and .catch() after executing it.
I tried to do this but it didn't work because the err parameter that I'm sending to the callback function is null.:
exports.deleteProduct = (req, res) => {
const id = req.params.productId;
Product.deleteOne({ _id: id, userId: req.user._id }, (err) => {
if (err) {
return res.status(401).json("Not authorized");
}
return res.status(200).json("Product deleted");
});
};
When i tried this second approach I always got the 200 status, meanwhile the product didn't delete.
Any idea how to deal with this?
You can try something like this:
Product.deleteOne({ _id: id, userId: req.user._id }, (err, result) => {
if(err) {
return "something"
}
return "something else"
});
or: in async / await way
try {
await Product.deleteOne({ _id: id, userId: req.user._id });
} catch (err) {
// handle error here
}
By the way, why you are passing userId at the deleteOne method?

Getting a variable to outer scope

I'm confused by this one. First of all this is my code:
router.post('/update', (req, res, next) => {
// Todo legit credit card holding
Account.findOneAndUpdate(
{ _id: req.user._id },
{
$set: {
// username: req.body.username,
creditCardNo: req.body.cardNo,
isPremium: true,
},
},
{ upsert: true },
(err, doc) => {
if (err) {
console.log(err);
}
}
);
var newUser;
Account.findById(req.user._id,(err, doc)=>{
if(err){
console.log(err);
}
else{
newUser = doc;
}
});
console.log(newUser);
res.render('user-pannel/pannel', {
title: 'User pannel',
user: newUser,
});
});
What it does is: It gets the POST call and updates a record in the db. Now I want to basically reload the the page (res.render part) and send the new user object.
I need to send the new one, because the one in req.user is now outdated (was updated before and I'm just printing the old version).
I tried getting around the problem by doing this newUser = doc;, but for some reason the newUservariable is undefined when logged outside of the findById method. Why? If I console log the doc inside of the findById method, it returns the changed object.
I turned it into an async function and awaited its resolution with the desired value.
router.post('/update', async (req, res, next) => {
// Todo legit credit card holding
Account.findOneAndUpdate(
{ _id: req.user._id },
{
$set: {
// username: req.body.username,
creditCardNo: req.body.cardNo,
isPremium: true,
},
},
{ upsert: true },
(err, doc) => {
if (err) {
console.log(err);
}
}
);
const newUser = await new Promise((resolve, reject) => {
Account.findById(req.user._id,(err, doc) => {
if(err) reject(err);
else resolve(doc);
});
});
console.log(newUser);
res.render('user-pannel/pannel', {
title: 'User pannel',
user: newUser,
});
});

Error pushing objects into another object in mongodb

req.body.courses has multiples id's of courses that I want to add to a specific categorie, the problem is that when my code runs it save a course more that one time, sometimes four or five times, depending on the number of loops it does.
The function:
router.post('/categories/:cat_id/', function (req, res) {
Categorie.findById(req.params.cat_id, function(err, categorie){
if(err){
console.log(err);
} else {
var courses = req.body.courses;
courses.forEach(function (course){
Course.findOne({ _id: course }, function(err, foundCourse) {
if(err){
console.log(err);
} else {
categorie.courses.push(foundCourse._id);
categorie.save();
}
});
});
}
});
return res.redirect('/dash');
});
The CategorieSchema:
var categorieSchema = mongoose.Schema({
name: String,
courses: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Course"
}
]
});
Here is an example of trying to add 4 courses to the categorie:
{ "_id" : ObjectId("5a871964a6b4820ecf7abaa7"), "courses" : [ ObjectId("5a870a7374486e0b0d69f710"), ObjectId("5a870a7a74486e0b0d69f711"), ObjectId("5a870a6974486e0b0d69f70f"),
ObjectId("5a870a7374486e0b0d69f710"), ObjectId("5a870a7a74486e0b0d69f711"), ObjectId("5a870a6974486e0b0d69f70f"),
ObjectId("5a870a7374486e0b0d69f710"), ObjectId("5a870a7a74486e0b0d69f711"), ObjectId("5a870a6974486e0b0d69f70f") ], "name" : "test2", "__v" : 3 }
Node.js Is async, It does not wait for the loop to execute completely and each time you are adding _id in existing array because of that adds 2-3 times.
Try this once I have not tested this.
const findOne = (course) => {
return new Promise((resolve, reject) => {
Course.findOne({
_id: course
}, (err, foundCourse) => {
if (err)
return reject(err);
return resolve(foundCourse._id);
});
});
}
router.post('/categories/:cat_id/', function (req, res) {
Categorie.findById(req.params.cat_id, function (err, categorie) {
if (err) {
console.log(err);
res.status(400).json(err);
} else {
var courses = req.body.courses;
Promise.all(courses.map((course) => {
return findOne(course);
})).then((data) => {
// check if course id already there skip
data = data.filter((course) => {
return !categorie.courses.includes(course);
});
categorie.courses = categorie.courses.concat(data);
categorie.save();
return res.redirect('/dash');
}).catch((err) => {
console.log(err);
res.status(400).json(err);
});
}
});
});
An alternative would involve a first query returning the courses using $in operator with Course.find() and then update the courses array in the Categorie model with Categorie.findByIdAndUpdate():
router.post('/categories/:cat_id/', function (req, res) {
Course.find({ '_id': { '$in': req.body.courses }}).exec((err, courses) => {
Categorie.findByIdAndUpdate(
req.params.cat_id,
{ '$addToSet': { 'courses': courses } },
{ 'new': true },
(err, categorie) => {
if (err){
console.log(err);
} else {
return res.redirect('/dash');
}
}
});
});
});

Using async.js for deep populating sails.js

I have a big issue with my function in sails.js (v12). I'm trying to get all userDetail using async (v2.3) for deep populating my user info:
UserController.js:
userDetail: function (req, res) {
var currentUserID = authToken.getUserIDFromToken(req);
async.auto({
//Find the User
user: function (cb) {
User
.findOne({ id: req.params.id })
.populate('userFollowing')
.populate('userFollower')
.populate('trips', { sort: 'createdAt DESC' })
.exec(function (err, foundedUser) {
if (err) {
return res.negotiate(err);
}
if (!foundedUser) {
return res.badRequest();
}
// console.log('foundedUser :', foundedUser);
cb(null, foundedUser);
});
},
//Find me
me: function (cb) {
User
.findOne({ id: currentUserID })
.populate('myLikedTrips')
.populate('userFollowing')
.exec(function (err, user) {
var likedTripIDs = _.pluck(user.myLikedTrips, 'id');
var followingUserIDs = _.pluck(user.userFollowing, 'id');
cb(null, { likedTripIDs, followingUserIDs });
});
},
populatedTrip: ['user', function (results, cb) {
Trip.find({ id: _.pluck(results.user.trips, 'id') })
.populate('comments')
.populate('likes')
.exec(function (err, tripsResults) {
if (err) {
return res.negotiate(err);
}
if (!tripsResults) {
return res.badRequest();
}
cb(null, _.indexBy(tripsResults, 'id'));
});
}],
isLiked: ['populatedTrip', 'me', 'user', function (results, cb) {
var me = results.me;
async.map(results.user.trips, function (trip, callback) {
trip = results.populatedTrip[trip.id];
if (_.contains(me.likedTripIDs, trip.id)) {
trip.hasLiked = true;
} else {
trip.hasLiked = false;
}
callback(null, trip);
}, function (err, isLikedTrip) {
if (err) {
return res.negotiate(err);
}
cb(null, isLikedTrip);
});
}]
},
function finish(err, data) {
if (err) {
console.log('err = ', err);
return res.serverError(err);
}
var userFinal = data.user;
//userFinal.trips = data.isLiked;
userFinal.trips = "test";
return res.json(userFinal);
}
);
},
I tried almost everthing to get this fix but nothing is working...
I am able to get my array of trips(data.isLiked) but I couldn't get my userFInal trips.
I try to set string value on the userFinal.trips:
JSON response
{
"trips": [], // <-- my pb is here !!
"userFollower": [
{
"user": "5777fce1eeef472a1d69bafb",
"follower": "57e44a8997974abc646b29ca",
"id": "57efa5cf605b94666aca0f11"
}
],
"userFollowing": [
{
"user": "57e44a8997974abc646b29ca",
"follower": "5777fce1eeef472a1d69bafb",
"id": "5882099b9c0c9543706d74f6"
}
],
"email": "test2#test.com",
"userName": "dany",
"isPrivate": false,
"bio": "Hello",
"id": "5777fce1eeef472a1d69bafb"
}
Question
How should I do to get my array of trips (isLiked) paste to my user trips array?
Why my results is not what I'm expecting to have?
Thank you for your answers.
Use .toJSON() before overwriting any association in model.
Otherwise default toJSON implementation overrides any changes made to model associated data.
var userFinal = data.user.toJSON(); // Use of toJSON
userFinal.trips = data.isLiked;
return res.json(userFinal);
On another note, use JS .map or _.map in place of async.map as there is not asynchronous operation in inside function. Otherwise you may face RangeError: Maximum call stack size exceeded issue.
Also, it might be better to return any response from final callback only. (Remove res.negotiate, res.badRequest from async.auto's first argument). It allows to make response method terminal

Categories

Resources