MongoDB nodejs updateOne always returning modifiedCount: 0 - node.js

I’m having an issue where I’m attempting to update my document and the change is not being reflected. I suspect MongoDB is finding that my value is somehow the same even though I’ve changed it
const User = require('./assets/models/User.js');
var message = user.messages;
//should be an empty array for right now, can be something like
//['erin': [{ from: ‘erin’, to: ‘erin’, content: ‘test’ }]]
//in the future
if (!message[otherPerson]) message[otherPerson] = [];
await message[otherPerson].push(msg);
//where msg is a msg object
//pushes message into new index
//updates messages with new data
const test = await User.updateOne({ usertag: person }, {
$set: { messages: message }
});
console.log(await test);
I’ve tried multiple formats of updating such as
User.updateOne({ usertag: person }, {
messages
});
where the messages variable is called message in the earlier example
or
User.updateOne({ usertag: person }, {
$set: { messages }
});
and nothing seems to work
I will also mention that this is some rather old code that used to work pretty well. Has something changed in how MongoDB handles updates or am I doing something wrong?

If you want to add a new value to the messages array you should use $push:
const test = await User.updateOne({ usertag: person }, {
$push: { messages: msg }
});
If you want to edit a specific message you should filter by its id and reference the specific array element (I'm assuming that _id is your identifier for message elements):
const test = await User.updateOne({ usertag: person, messages._id: msg._id }, {
$set: {
messages.$.from: msg.from,
messages.$.to: msg.to,
messages.$.content: msg.content,
}
});
Also, you should not await the test result since you already resolved the Promise awaiting the updateOne:
console.log(test);

Related

Firebase Cloud Function batch write update document overwrote the entire document?

The following Cloud Function has a batch write operation that, in part, updates a single field in a document. This overwrote the entire document and now the document has a single field joinedCount: -1. Is this not the way to update individual fields in documents without overwriting them?
exports.deleteUserTEST = functions.https.onCall(async (data, _context) => {
const uId = data.userId;
const db = admin.firestore();
try {
const batch = db.batch();
const settingsDoc = await db.collection("userSettings").doc(uId).get();
const joinedIds = settingsDoc.get("private.joinedIds");
Object.keys(joinedIds).forEach(function(jId, _index) {
batch.update(
db.collection("profiles").doc(jId),
{
private: {
joinedCount: admin.firestore.FieldValue.increment(-1), // <-- the culprit
},
},
);
});
await batch.commit();
} catch (error) {
throw new functions.https.HttpsError("unknown", "Failed the delete the user's content.", error);
}
return Promise.resolve(uId);
});
Moving the solution found in the comments by #Dharmaraj into a community answer, this problem was caused by the structure of the document.
Since all the data in the document was inside the private map field, passing a new map through the update method would make it appear that the entire document was being overwritten instead of updated.
In this case, you would need to access the fields through dot notation. This allows those inner fields within the map to be updated, without replacing the entire private map:
Object.keys(joinedIds).forEach(function(jId, _index) {
batch.update(db.collection("profiles").doc(jId), {
"private.joinedCount": admin.firestore.FieldValue.increment(-1)
});
});
Another example from the documentation:
import { doc, setDoc, updateDoc } from "firebase/firestore";
// Create an initial document to update.
const frankDocRef = doc(db, "users", "frank");
await setDoc(frankDocRef, {
name: "Frank",
favorites: { food: "Pizza", color: "Blue", subject: "recess" },
age: 12
});
// To update age and favorite color:
await updateDoc(frankDocRef, {
"age": 13,
"favorites.color": "Red"
});

how to loop over an array and get data for each element? - with mongoose & node.js

I am trying to build a social network. I have a collection of "connections"/"friend requests".
the schema is as follows:
{
senderId: { type: String },
receiverId: { type: String },
approved: { type: Boolean },
},
when the person didn't approve the connection yet, the connection is marked with "pending".
when the user enter to the website, i get all the pending connections people sent him, with this command:
const requestsPending = await Connection.find({
receiverId: userId,
approved: false,
});
the thing is, in the backend, I want to get the details of the people who send the friend requests (so I can show their name+picture).
I want to loop over the pending requests array, and get the extra data of the user who sent the request.
await User.findById(requestsPending[0][senderId]);
any idea how to do it for each element? what's the best approach?
or any idea how to do it more efficiently?
thank you.
this seems to work:
var requestsPendingWithUsersData = await Promise.all(
requestsPending.map(async (item) => {
const userData = await User.findById(item.senderId);
return {
item,
senderData: { picture: userData.picture, name: userData.username },
};
})
);
await User.find({
'senderId': { $in: [
101,102,103,...
]}
})
You can try something like the above, where you pass an array of senderID to $in. The $in clause is similar to like operator in SQL.
If the senderId is an ObjectId, pass them as ObjectID types
await User.find({
'senderId': { $in: [
mongoose.Types.ObjectId(<senderID>),
]}
})
If the mongo Document is heavier/larger, use lean() at the end of the query. Enabling the lean option tells Mongoose to skip instantiating a full Mongoose document.
await User.find({
'senderId': { $in: [
101,102,103,...
]}
}).lean()

Can't push object into the array after running mongodb query

I was trying to set up the logic for adding some items into an array, which id's express server receives from a client. My program receives the id of the product and then I was fetching the product details from MongoDB query findOne, and then with some customized details I used to push that item into an array but it's not working, whenever I try to push any element after MongoDB query it's not working, I don't know why, but please help me, Sorry for my bad English !
It's an ExpressJS server using MongoDB
Items received: (But it is actually received from the client in the form of JSON) :
const items= [
{
productId:"61e01e7e24612b56c33b06c3",
quantity:"4"
},
{
productId:"61e01e9024612b56c33b06c6",
quantity:"10"
}
]
The actual code here is the problem
let itemsData = [];
items.forEach(async (item) => {
const itemData = await findProduct({ _id: item.productId });
// Check if product found
if (!itemData) return res.status(400).json({ message: "Product is Invalid" });
// If found add that in object
itemsData.push({
productId: itemData._id,
name: itemData.name,
price: itemData.price,
quantity: item.quantity,
unit: "Nos",
totalPrice: parseInt(itemData.price) * parseInt(item.quantity)
});
});
The code above doesn't push that object into the itemsData
array
findProduct Function
// Service to find Product
async findProduct(filter) {
return await ProductModel.findOne(filter);
}
If I used that push method and tried only to itemsData.push("hello"); before the MongoDB query it works, but if I put it after the findProduct Query it doesn't work! I don't know what is wrong with it! Somebody help me!
I just want to push those items with detail into itemData object happily which is not happening I tried to console.log(itemsData) it just return [], what should I do?
Try using For Of instead of forEach (don't forget to add async)
let itemsData = [];
for (const item of items) {
const itemData = await findProduct({ _id: item.productId });
// Check if product found
if (!itemData) return res.status(400).json({ message: "Product is Invalid" });
// If found add that in object
itemsData.push({
productId: itemData._id,
name: itemData.name,
price: itemData.price,
quantity: item.quantity,
unit: "Nos",
totalPrice: parseInt(itemData.price) * parseInt(item.quantity)
});
}
It's because forEach function is not designed to work well with async calls.
You could use map instead.
This should work:
let itemsData = [];
const promises = items.map(async (item) => {
const itemData = await findProduct({ _id: item.productId });
// Check if product found
if (!itemData) return res.status(400).json({ message: "Product is Invalid" });
return {
productId: itemData._id,
name: itemData.name,
price: itemData.price,
quantity: item.quantity,
unit: "Nos",
totalPrice: parseInt(itemData.price) * parseInt(item.quantity)
};
});
itemsData = await Promise.all(promises);
When you use map with async, you will have an array of promises, so you can use Promise.all to wait for the values to get resolved.
Check this out for more details.

Mongoose not saving record after array is updated

I've seen a couple similar posts, but I can't get anything to work. The following is for a podcast episode topic suggestion app. It's meant to upvote a topic by adding a user ID to an array of user IDs saved to the topic object. Everything seems like it works, but topic.save() isn't actually saving.
router.post('/upvote/:id', auth, async (req, res) => {
try{
var topic = await Topic.findById(req.params.id);
const reqId = req.body._id;
if(topic.upvotes.includes(reqId)){
res.status(409).send('Topic already upvoted.');
}
console.log(`pre-update: ${topic}`);
topic.set({
upvotes: topic.upvotes.push(reqId)
});
console.log(`post-update: ${topic}`);
try{
//topic.markModified('topic.upvotes');
topic = await topic.save();
res.status(201).send(topic);
} catch{
next();
};
} catch{
res.status(404).send('Topic with given ID not found.');
};
});
I tried a few different variations on topic.markModified() because I saw that suggested on other posts, but nothing worked.
Here's what those two console.log()s show:
pre-update: {
upvotes: [],
_id: 612d701dd6bbfd3c5c36c906,
name: 'a topic',
description: 'is described',
category: 61217a75f30c6c826af9076b,
__v: 0
}
post-update: {
upvotes: [ 612996b46f21d2086c9d4d52 ],
_id: 612d701dd6bbfd3c5c36c906,
name: 'a topic',
description: 'is described',
category: 61217a75f30c6c826af9076b,
__v: 0
}
These look like it should work perfectly.
The 404 response at the very end is what's actually getting sent when I try this. I'm using express-async-errors & if the next() in the nested catch block was getting called, it would send 500.
Any suggestions?
I am actually not sure what exactly your trying to do. If you want to add a new value to a field only at a particular place then put or patch is to be used not post. post will update the whole document. and patch put is for partial updation.
Can you refer the sample code which I have given, hope that would be helpful for you in one or the other way.
router.put("/:id", [auth, validateObjectId], async (req, res) => {
const { error } = validateMovie(req.body);
if (error) {
return res.status(400).send(error.details[0].message);
}
let genre = await Genre.findById(req.body.genreId);
console.log(genre);
if (!genre) {
return res.status(400).send("No genre found with given id");
}
let movieDetails = await Movie.findByIdAndUpdate(
req.params.id,
{
title: req.body.title,
numberInStock: req.body.numberInStock,
dailyRentalRate: req.body.dailyRentalRate,
liked: req.body.liked,
genre: {
_id: genre.id,
name: genre.name,
},
}, //when using patch method, then you need not have to write this whole thing. instead just write req.body
{ new: true }
);
if (!movieDetails) {
return res.status(404).send("No such movie details found.");
}
res.send(movieDetails);
});
I figured it out. I think mongoose doesn't like it if you try to push() a new element like a normal array.
I used addToSet() instead and it worked.
https://mongoosejs.com/docs/api/array.html#mongoosearray_MongooseArray-addToSet

Mongoose overwriting data in MongoDB with default values in Subdocuments

I currently have a problem with updating data in MongoDB via mongoose. I have a nested Document of the following structure
const someSchema:Schema = new mongoose.Schema({
Title: String,
Subdocuments: [{
SomeValue: String
Position: {
X: {type: Number, default: 0},
Y: {type: Number, default: 0},
Z: {type: Number, default: 0}
}
}]
});
Now my problem is that I am updating this with findOneAndUpdateById. I have previously set the position to values other than the default. I want to update leaving the position as is by making my request without the Position as my frontend should never update it (another application does).
However the following call
const updateById = async (Id: string, NewDoc: DocClass) => {
let doc: DocClass | null = await DocumentModel.findOneAndUpdate(
{ _id: Id },
{ $set: NewDoc },
{ new: true, runValidators: true });
if (!doc) {
throw createError.documentNotFound(
{ msg: `The Document you tried to update (Id: ${Id}) does not exist` }
);
}
return doc;
}
Now this works fine if I don't send a Title for the value in the root of the schema (also if i turn on default values for that Title) but if I leave out the Position in the Subdocument it gets reset to the default values X:0, Y:0, Z:0.
Any ideas how I could fix this and don't set the default values on update?
Why don't you find the document by id, update the new values, then save it?
const updateById = async (Id: string, NewDoc: Training) => {
const doc: Training | null = await TrainingModel.findById({ _id: Id });
if (!doc) {
throw createError.documentNotFound(
{ msg: `The Document you tried to update (Id: ${Id}) does not exist` }
);
}
doc.title = NewDoc.title;
doc.subdocument.someValue = NewDoc.subdocument.someValue
await doc.save();
return doc;
}
check out the link on how to update a document with Mongoose
https://mongoosejs.com/docs/documents.html#updating
Ok after I gave this some thought over the weekend I got to the conclusion that the behaviour of mongodb was correct.
Why?
I am passing a document and a query to the database. MongoDb then searches Documents with that query. It will update all Fields for which a value was supplied. If for Title I set a new string, the Title will get replaced with that one, a number with that one and so on. Now for my Subdocument I am passing an array. And as there is no query, the correct behavioud is that that field will get set to the array. So the subdocuments are not updated but indeed initialized. Which will correctly cause the default values to be set. If I just want to update the subdocuments this is not the correct way
How to do it right
For me the ideal way is to seperate the logic and create a seperate endpoint to update the subdocuments with their own query. So to update all given subdocuments the function would look something like this
const updateSubdocumentsById= async ({ Id, Subdocuments}: { Id: string; Subdocuments: Subdocument[]; }): Promise<Subdocument[]> => {
let updatedSubdocuments:Subdocument[] = [];
for (let doc of Subdocuments){
// Create the setter
let set = {};
for (let key of Object.keys(doc)){
set[`Subdocument.$.${key}`] = doc[key];
}
// Update the subdocument
let updatedDocument: Document| null = await DocumentModel.findOneAndUpdate(
{"_id": Id, "Subdocuments._id": doc._id},
{
"$set" : set
},
{ new : true}
);
// Aggregate and return the updated Subdocuments
if(updatedDocument){
let updatedSubdocument:Subdocument = updatedTraining.Subdocuments.filter((a: Subdocument) => a._id.toString() === doc._id)[0];
if(updatedSubdocument) updatedSubdocuments.push(updatedSubdocument);
}
}
return updatedSubdocuments;
}
Been struggling with this myself all evening. Just worked out a really simple solution that as far as I can see works perfectly.
const venue = await Venue.findById(_id)
venue.name = name
venue.venueContact = venueContact
venue.address.line1 = line1 || venue.address.line1
venue.address.line2 = line2 || venue.address.line2
venue.address.city = city || venue.address.city
venue.address.county = county || venue.address.county
venue.address.postCode = postCode || venue.address.postCode
venue.address.country = country || venue.address.country
venue.save()
res.send(venue)
The result of this is any keys that don't receive a new value will just be replaced by the original values.

Resources