Getting an {"message":"Invalid update pipeline operator: \"_id\""} error - node.js

I am trying to update two rows in my players table based on the id. I am trying to use the updateMany method where id can be found in an array of id's but I am getting the {"message":"Invalid update pipeline operator: \"_id\""} error. I checked the array to make sure it is valid id's. Here is my code
const winningTeam = asyncHandler(async (req, res) => {
req.body.forEach((element) => {
element.wins += 1;
element.lastPlayed = Date.now();
element.percentage = (element.wins / (element.wins + element.wins)) * 1000;
});
let usersId = [];
usersId.push(req.body[0]._id);
if (req.body.length === 2) {
usersId.push(req.body[1]._id);
}
const player = await Player.updateMany({ _id: { $in: usersId } }, req.body);
if (player) {
res.status(200).json(player);
} else {
res.status(400);
throw new Error("Invalid Data");
}
});

You should use $set property for the update parameter. I'm not sure about the structure of your req.body but it should be something like this:
Player.updateMany({ _id: { $in: usersId } }, {$set: req.body});
instead of this:
Player.updateMany({ _id: { $in: usersId } }, req.body);
Take a look at docs for updateMany

Related

Remove object array items by comparing object array from mongodb

There is document which is having array of object inside.
Like
Objectid('')
fruits : [{_id:'2'},{_id:'3'},{_id:'4'}]
I want to delete these items fruits_id = [{_id:'3'},{_id:'4'}].
fruit_group.updateOne(
{collection_id: collection_id},
{$pullAll: {"fruits": fruits_id}}
)
so far i found below logic which i think is not efficient.
routes.post('/removeFruits', async (request, response, next) => {
var post_data = request.body;
var collection_id = post_data.collection_id;
var fruits_ids = JSON.parse(post_data.fruits_ids);
var prev_fruits;
await fruit_group.findOne({'collection_id': collection_id}, function (err, result) {
if (err) {
console("Some error occurred");
response.json({'message': "Some error occurred", 'result': 'false'});
}
prev_fruits = result.fruits;
});
for (var i = 0; i < fruits_ids.length; i++) { // this will delete all occurring items from array
var key = fruits_ids[i].user_id;
prev_fruits.filter(x => x.user_id === key).forEach(x => prev_fruits.splice(prev_fruits.indexOf(x), 1));
}
await fruit_group.updateOne({'collection_id': collection_id}, {$set: {'fruits': prev_fruits}}, function (err, result) {
if (err) {
response.json({'message': "Some error occurred", 'result': 'false'});
}
response.json({'message': 'Deletion successfully', 'result': 'true'});
});
});
is there anyway to achieve the same result?
Assuming fruits_id = [{ _id: '3' }, { _id: '4' }], you could do something like this using $pull and $in:
await fruit_group.updateOne({'collection_id': collection_id}, { $pull: { fruits: { $in: fruits_id }}})
This follows the example of removing all items that equal a specified value.

Delay in return value - nodejs & mongoose

I'm fairly new to nodejs and I'm doing a full stack developer challenge from devchallenges.io (Shoppingify). Below, I'm trying to increase the quantity value based on whether the user clicks to increase or decrease the item quantity. However, there's a slight delay between the return value from the request and the actual value in the database. It seems that the value updates immediately which is great however, the return value in the request is the previous value rather than being the current quantity value in the database.
mongoDB Database
// #route PUT api/list/item/quantity/:id
// #desc update item quantity
// #access Private
router.put('/item/quantity/:id', auth, async (req, res) => {
const { action } = req.body;
try {
let list = await List.findOne({ user: req.user.id });
// find current quantity
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
// increase quantity
if (action === 'increase') {
list = await List.findOneAndUpdate(
{ 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity + 1 } },
{ new: true }
);
} else {
// decrease quantity
list = await List.findOneAndUpdate(
{ 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity - 1 } },
{ new: true }
);
}
res.json(item.quantity);
} catch (error) {
console.error(error.message);
res.status(500).send('Server Error');
}
});
You are defining item in here:
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
At this point list is the "old" version of the object, you want to be doing the same after the update when the list object is updated and only then to return it.
// this is the original "list" item
let item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
...
update list
...
// now "list" is updated
item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
I will just add two additional tips to improve performance, they are mutually exclusive so you'll have to choose one of the two.
in the update query add the list._id to it, If I were to guess the collection does not have an index on the items field ( and if it does it's a bad idea usually ). this means when you updated just using the item._id field it takes longer for mongo to find the object. it's quick change to both updates:
list = await List.findOneAndUpdate(
{ _id: list._id, 'items._id': req.params.id },
{ $set: { 'items.$.quantity': item.quantity - 1 } },
{ new: true }
);
(my preferred option) do it in a single call, using the update arrayFilters option, like so:
const list = await List.findOneAndUpdate(
{
user: req.user.id,
},
{
$inc: {
'items.$[elem].quantity': action === 'increase' ? 1 : -1,
},
},
{
arrayFilters: [
{
'elem._id': new ObjectId(req.params.id),
},
],
new: true,
});
const item = list.items.find((item) => {
return item._id.toString() === req.params.id;
});
Mongo Playground
In my opinion now your route looks much better, you're also cutting down from 2 db calls to 1.

I want to delete all the item but except of the current user

I am creating a call that deletes all the users except the current user logged in.
Here is my code;
exports.deletealluser = async (req, res) => {
try {
const { sub } = req.user;
const usersExceptCurrent = await User.find({ _id: !sub });
const deletedUsers = await User.deleteMany(usersExceptCurrent);
res.status(201).json({
message: 'A all user is successfully deleted!',
deletedUsers,
});
} catch (err) {
return res.status(400).json({
message: 'Something went wrong.',
});
}
};
sub is the id of the current user. As you can see, I call find query first to filter the data which is not equal to sub. Then I use the usersExceptCurrent as filter to my deleteMany query.
But it returns status 400
And here is my axios call;
const onDelete = async () => {
try {
const { data } = await fetchContext.authAxios.delete(
'admin/delete-all-users'
);
fetchContext.setUserList(
fetchContext.userList.filter((row) => row === data.deletedUsers)
);
setSignupSuccess(data.message);
setSignupError('');
setOpen(false);
setOpenAlert(false);
} catch (err) {
setIsLoaded(true);
setError(err);
const { data } = err.response;
setSignupError(data.message);
setSignupSuccess('');
}
};
Use $ne
$ne selects the documents where the value of the field is not equal to the specified value. This includes documents that do not contain the field.
db.collection.find({ _id: { $ne: sub } })
Demo - https://mongoplayground.net/p/ecMNn4ueZrn
If you still face for _id should be ObjectId you can do
const ObjectId = require("mongodb").ObjectId;
db.collection.find({ _id: { $ne: ObjectId(sub) } })
See what ! does, converts to bool value in JS
console.log(!"a");
console.log(!2);

MongoDB Result can console.log() but not pushed to array NodeJS

I have a list of users I would like to look up in my MongoDB database and once I look them up, I want to store them in an array so I can do things with the data. I'm using the official mongodb package in NodeJS. Here's the code
var chatsData = []
for (let userID of chatIDs) {
db.collection('users').find({ '_id': ObjectId(userID) }).toArray((err, result) => {
if (err) throw err
chatsData.push(result)
console.log(result)
})
}
}
console.log('vvv Final data in array: vvv')
console.log(chatsData)
When I run the code this, I get this vvv
vvv Final data in array: vvv
[]
[
{
_id: 5eae4c90ad1dd6304c69a75a,
usnm: 'gohjunhao',
eml: 'junhao#gmail.com',
phnm: '00000000',
pswd: '$2a$10$IUaxiweNrUUwxZP6XEQfFeTTnbta13/kv6DdebwJ0WT/bM.3fc5ay',
register_date: 2020-05-03T04:46:08.054Z,
__v: 0
}
]
[
{
_id: 5ead401f8059852114bf9867,
usnm: 'gfox.2020',
eml: 'carrie#gmail.com',
phnm: '11111111',
pswd: '$2a$10$UYaEraoI4Kj0dI.nt5Hbr.LgDL1TNtDOsz7tcxETJW7HRtmgWo.UK',
register_date: 2020-05-02T09:40:47.684Z,
__v: 0
}
]
How do I get a proper array of data in my array so it can be used later? Is what I'm doing wrong? Do I need to use a .then() statement or an async await?
Here's the full code
MongoClient.connect(url, { useUnifiedTopology: true }).then(async chooseDB => {
db = chooseDB.db('nodejs')
// Get a list of all tables
db.listCollections().toArray((err, result) => {
/***** YOU DON'T NEED TO UNDERSTAND THIS PART OF THE CODE ******/
if (err) throw err
var chatList = []
var chatIDs = []
for (let i = 0; i < result.length; i++) {
const table = result[i]
if (table.name.indexOf(data) > 1) {
// add tables with personal id to chatList
chatList.push(table.name)
// add id's of other chats to out table
chatIDs.push(table.name.replace('dm', '').replace('~', '').replace(data, ''))
}
}
/***** IT'S JUST HOW I GET MY CHAT ID'S *****/
// Get data on users
var chatsData = []
for (let userID of chatIDs) {
try{
let temp = await db.collection('users').find({ '_id': toMongoObjectId(userID) }).toArray()
chatsData.push(temp)
}
catch(error) {
console.log(error)
}
}
console.log('vvv Final data in array: vvv')
console.log(chatsData)
toClient.userData = chatsData
toClient.users = chatList
socket.emit('res_chatList', toClient)
})
})
This can be solved using async-await, write async to the function of then block-like,
mongoclient.connect().then( async (cli) => {
db = cli.db(dbName);
...
})
your logic to fetch data will be
var chatsData = []
for (let userID of chatIDs) {
try{
let temp = await db.collection('users').find({ '_id': ObjectId(userID) }).toArray();
chatsData.push(temp);
}
catch(error) {
console.log(error);
}
}
console.log(chatsData);

How to get data from a mongoose aggregate query in nodejs

The mongoose query is fetching the correct results but i am unable to access the data.
I want to get the current max id in the database, increment the value by 1 and save the data to the database
// add a new book
router.route('/books/add').post(jsonParser, (req, res) => {
Book.aggregate([{ $group: { _id: "id", id: { $max: "$id" } } }], (err, books) => {
if (!books)
return next(new Error("Could not load book!"))
else {
let book = new Book(req.body);
// log the max id returned, currently returns undefined
console.log("Id found ", books.id);
book.id = books.id + 1;
console.log("BookId ", book.id);
res.status(200).json({ 'book': 'Added successfully!' });
// book.save().then(book => {
// res.status(200).json({ 'book': 'Added successfully!' });
// }).catch(err => {
// res.status(400).send('Failed to create new record.');
// });
}
});
});
console.log(books.id) returns undefined whereas console.log(books) does show me the result which is [ { _id: 'id', id: 5 } ]. What i want to do is get the id i.e. 5, how do i do that ? Thank you.
The aggregate query result is an array,, and you have to use the zero index to get the first element which is an object contains your result. Try this:
console.log(books[0].id)

Resources