How to update deeply nested array document in MongoDB? - javascript

I'm trying to update this attached
Mongo collection using the following controller, but getting bad value mongoError. Should I need to change the Model or are any changes needed in the current controller?
updateMarkCard = (req, res) => {
const reg = "66";
const sem = "sem-1";
const Ia = "IA-1";
MarksCardList.find({ student_id: reg }).exec((err, data) => {
if (err) res.status(400).json({ message: "Student Not Found" });
if (data) {
const findSem = data[0].marksCard_list.find((el) => {
return el.semister === sem;
});
const findIA =
findSem &&
findSem.IA.find((el) => {
return el.IA_type === Ia;
});
MarksCardList.findOneAndUpdate(
{
student_id: reg,
"marksCard_list._id": findSem._id,
},
{
$set: {
"marksCard_list.$[marksCard_list].IA.$[IA].marks": req.body.marks,
},
},
{
arrayFilters: [
{ "marksCard_list._id": findSem._id },
{ "IA._id": findIA._id },
],
}
).exec((er, data) => {
if (er) res.status(400).json({ ...er });
if (data) res.status(400).json({ data });
});
}
});
};

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]" }})

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

How do I $cond a $push/$pull in a MongoDB update with upsert:true

I'm trying to do a push or pull based on a condition, along with an upsert
myCollection.update(
{'id': location},
{
$set: { count },
$setOnInsert: {
id: location,
users: []
},
},
{
$cond: {
if: (increment==1),
then: {$push: { users: userToken }},
else: {$pull: { users: userToken }}
}
},
{'upsert':true},
(err, data) => {
...
I'm trying to DRY this up (which works):
mongo.connect(dbUrl, (err, db) => {
if (err) throw err
let myCollection = db.collection('myCollection')
if(increment==1){
myCollection.update(
{'id': location},
{
$set: { count },
$push: { users: userToken },
$setOnInsert: {
id: location
}
},
{'upsert':true},
(err, data) => {
if (err) throw err
console.log(data);
callback()
db.close()
}
)
}
else{
myCollection.update(
...
$pull: { users: userToken },
...
)
}
})
It's not adding anything to the DB when I have $cond. Where should the $cond be?
$cond is not applicable here but in the aggregation framework. What you need is a pure old native JS conditional statement where you create the update document prior to using it in the update operation, and this of course should be set in a condition block. Consider the following example:
let queryObj = { 'id': location },
usersObj = { 'users': userToken },
updateObj = {
'$set': { count },
'$setOnInsert': queryObj
},
options = { 'upsert': true },
updateOperator = '$pull';
if (increment == 1) updateOperator = '$push';
updateObj[updateOperator] = usersObj;
myCollection.update(queryObj, updateObj, options,
(err, data) => {
if (err) throw err
console.log(data);
callback();
db.close();
}
)

How to increment count in mongodb?

I want to increment the quantity count in my database everytime I click my button.
This is my angular directive:
var count = 0
$scope.postNote = function () {
var deferred = $q.defer()
var token = $scope.userInfo.$$state.value.accessToken
$scope.userInfo.$$state.value.cart.quantity.push(count += 1)
$http.put('/api/me/cart?access_token=' + token, $scope.userInfo.$$state.value)
.then(function (result) {
deferred.resolve(result)
}, function (err) {
deferred.reject(err)
})
return deferred.promise
}
This is my api:
router.put('/me/cart', wagner.invoke((User) => {
return (req, res) => {
try {
var cart = req.body.cart
} catch (e) {
return res.status(status.BAD_REQUEST)
.json({ error: 'Sorry, you have failed in life' })
}
req.user.data.cart = cart
req.user.save((err, user) => {
if (err) {
return res.status(status.INTERNAL_SERVER_ERROR)
.json({ error: err.toString() })
}
return res.json(user)
})
}
}))
Finally, this is how my document look:
"_id" : ObjectId("56ca4dc77aaab42f074250ba"),
"password" : "$2a$10$KnIVYcIjE/AYfUnMgkWhc.bQ1Luxo8XRUq/lnuPXKsOKR8YEz2m7O",
"username" : "ken",
"data" : {
"cart" : {
"quantity" : [
1,
2,
3,
4,
5
]
}
},
"__v" : 0
I just want the quantity element to increment everytime I click it.
I figured it out. I made the change in my directive.
var count = 0
$scope.postNote = function () {
var deferred = $q.defer()
var token = $scope.userInfo.$$state.value.accessToken
$scope.userInfo.$$state.value.cart.quantity = count++
$http.put('/api/me/cart?access_token=' + token, $scope.userInfo.$$state.value)
.then(function (result) {
deferred.resolve(result)
}, function (err) {
deferred.reject(err)
})
return deferred.promise
}

Categories

Resources