Update array inside of Mongo document doesn't work - javascript

I have a user's collection, each user has a subscription array of objects. I want to add another subscription to that array. I'm getting the new subscription, updating it with findByIdAndUpdate, but it doesn't add the new subscription, however it shows that the document was updated. I tried several approaches but nothing worked well.
Here is the last approach:
...
const { user_id } = req.params;
const subscription = req.body; // Getting subscription
const user = await UserModel.findById(user_id).lean().exec(); // Getting user by id
const { push_subscriptions } = user; // Getting subscribtions with destructuring
const updated_subs = [...push_subscriptions, subscription];
// Getting user another time and updating the push_subscriptions array
const updated_user = await UserModel.findByIdAndUpdate(user_id,
{push_subscriptions: updated_subs},
{ new: true }
).exec();
...
Here are the logs of request body and params
// body
{
endpoint: 'https://fcm.googleapis.com/fcm/send/cQ6wlRJ8t-s:APA91bEjqdLMzQLsroJ7zHzdjrzoshdPD8IJy_iIeRa8qV_Yjt6N1jeMUtyMq73wSn9JJT-4WXr_8uwHXttj-XFxHPCPAOqgN7zALsmf_BeIRZowRBTRHf9YH8v3AlcaZXWAIQ0qJNdn',
expirationTime: null,
keys: {
p256dh: 'BBPC5h1QnBMPKMfPacgJu_2RFT7LAejyINh3CvP4pamkrlERr06YpRlSb7RbTUOn6MYW4adG93KfdEWXz68F9iQ',
auth: 'Zl3iaOdBvihXG2QVOb26IQ'
}
}
//params
{ user_id: '5fedc679f414663c693cf549' }
User schema, push_subscriptions part:
push_subscriptions: {
type: Array,
},

Your query looks good, you can do single query using $push instead of doing manual process,
const { user_id } = req.params;
const subscription = req.body;
const updated_user = await UserModel.findByIdAndUpdate(user_id,
{
$push: {
push_subscriptions: subscription
},
{ new: true }
).exec();

Related

MongoDB updateOne not able to update document when referring an ID as filter

I'm trying to update a property value of a single document by sending a request to my NextJs API via fetch.
// Update items in state when the pending time in queue has passed, set allowed: true
items.map((item) => {
const itemDate = new Date(item.added)
const timeDiff = currentDate.getTime() - itemDate.getTime()
const dateDiff = timeDiff / (1000 * 3600 * 24)
if (dateDiff >= 7) {
const updateItem = async () => {
try {
// Data to update
const updatedItem = {
_id: item._id,
name: item.name,
price: item.price,
allowed: true
}
console.log(updatedItem)
await fetch ('/api/impulses', {
method: 'PATCH',
body: JSON.stringify(updatedItem)
})
} catch (error) {
alert('Failed to update items status')
}
}
updateItem()
}
})
API receives the data object as a whole and I am able to parse every piece of data I need for updating the MongoDb document from the req.body. However, when trying to use the item's _id (which is generated by MongoDb and values as _id: ObjectId('xx1234567890xx')) to filter the document I need to update, it seems to treat the ID differently.
I've tried to mess around with the format, forcing the object that gets sent to the API to include just the things I want to be updating (and the _id, of course) but still...
const jsonData = JSON.parse(req.body)
const { _id } = jsonData
// Fields to update
const { name, price, allowed } = jsonData
const data = {
name,
price,
allowed
}
const filter = {
_id: _id
}
const update = {
$set: data
}
const options = {
upsert: false
}
console.log("_id: ", filter) // returns { _id: 'the-correct-id-of-this-document' }
And finally, updating thedb.collection and returning responses:
await db.collection('impulses').updateOne(filter, update, options)
return res.json({
message: 'Impulse updated successfully',
success: true
})
So, as the _id clearly matches the document's id, I cannot figure out why it doesn't get updated? To confirm that this isn't an issue with database user privileges, if I set upsert: true in options, this creates a new document with the same _id and the updated data the request included...
Only difference to the other documents created through a submit form is that the id is in the following format: _id: 'xx1234567890xx' - so, comparing that to an ID with the ObjectId on front, it doesn't cause a conflict but I really don't get this behavior... After noticing this, I've also tried to include the 'ObjectId' in the ID reference in various ways, but it seemed like initiating a new ObjectId did exactly that - generate a new object ID which no longer referenced the original document.
Any ideas?
You compare an ObjectId object from _id with a string, this does not work.
Create proper filter object, e.g.
const filter = { _id: ObjectId(_id) }
or the other way around:
const filter = { $expr: {$eq: [{$toString: "$_id"}, _id] } }
but this will prevent to use the index on _id, so the first solution should be preferred.

Firebase: Add a Additional Collection to a Document Unhandled Promise Rejection

I currently have a Collection | Document | Collection Firestore.
I want to search for a specific collection within the document and if the user ID matches, then update. If the user ID is not in the collection, then I want to add a new collection.
The collection update aspect works, but getting a Promise Rejection when adding to the collection via .set().
if (!query.empty) {
console.log('event leaderboard exists in db');
for (let i in leaderboard) {
const result = leaderboard[i].data();
for (let y in result) {
const userLeaderboard = result[y];
if (userLeaderboard.uid === currentUser.uid) {
// User exists in record and update score
return query.docs[i].ref.update({
score: increaseBy,
});
} else {
// User does not exist and need to add to record
addUserToLeaderboard();
}
}
}
...
const addUserToLeaderboard = async() => {
let userLeaderboard = {
uid: currentUser.uid,
score: currentTrivia.points,
banned: false,
};
const leaderboardRef = db.collection('leaderboard').where(firebase.firestore.FieldPath.documentId(), '==', eventId);
await leaderboardRef.set(userLeaderboard, { merge: true });
}
You can't call set on a query.
If you want to update the one document with the given ID, use:
const leaderboardRef = db.collection('leaderboard').doc(eventId);
await leaderboardRef.set(userLeaderboard, { merge: true });
If you want to update (potentially multiple) items matching a query, you'll need to execute the query, loop over the results, and update each document in turn. But that' doesn't seem to be needed here.

React Native: Convert the value stored in AsyncStorage into an object

I am making a simple CRUD app using React Native with MongoDB, NodeJS and Express. When the user registers, the _id is saved inside AsyncStorage to check if the user is already authenticated or not every time he/she opens the app.
const response = await axios.post('API_LINK', {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
})
const data = await response.data
console.log(data)
await AsyncStorage.setItem('userId', data.user._id)
console.log(data) returns
Object {
"user": Object {
"__v": 0,
"_id": "62e42244b7bc9e7f9de5131b",
"email": "testing5#gmail.com",
"name": "Test 5",
"password": "$2a$10$SE7Eq46mBXGw7FYBtOtqqO49wvIMYDl0LfHknrrp.rWdrYNr6Dk8.",
"projects": Array [],
},
}
The projects [] contains all the projects created by that particular user. So, in the Project Model and Controller, the userId is also passed, so that the specific project is stored in the projects array of that specific user whose userId is passed.
ProjectModel
const projectSchema = new Schema({
title: {
~~~~~~~~~~~~~~~~~~~~~~~~~~~
},
description: {
~~~~~~~~~~~~~~~~~~~~~~~~~~~
},
user: {
type: mongoose.Types.ObjectId,
ref: 'User',
required: true
}
})
Project Controller
export const createProject = async (req, res, next) => {
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
let userExists
try {
userExists = await User.find(user)
} catch (err) {
return console.log(err)
}
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
const project = new Project({
title,
description,
user
})
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
}
CreateProject
const userId = await AsyncStorage.getItem('userId')
const response = await axios.post('http://IP_ADDRESS:8000/api/project/create', {
title: title.trim(),
description: description.trim(),
image: image.trim(),
user: userId
})
But on clicking Create button, this error is shown
ObjectParameterError: Parameter "filter" to find() must be an object, got 62e42244b7bc9e7f9de5131b, linking to the createProject inside Project Controller.
Then, I saw that _id stored as userId is string, so I tried to convert it to object using JSON.parse().
const userId = await AsyncStorage.getItem('userId')
const userId2 = JSON.parse(userId)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
user: userId2
But, now I get this error JSON Parse error: Unable to parse JSON string.
So, how to convert the userId stored in AsyncStorage into object? And, if there are any other better methods to perform this task, please suggest.

Mongoose Update Field With the Item of the Another Field That is Array

I am building an Expense Tracker Application, There is a User Collection that has fields like Name, Amount, Expenses Array, Incomes Array, and So On.
My Database Is Mongo Db with Mongoose and the Server Is Written In Express.
Here Is A Screenshot Of the Database after The Values are Filled
I am trying to Implement a Route in which The User Can Delete an Expense and After Deleting the Expense I want to update the Balance and make the Balance = Balance + Expense. I am able to Delete and Expense But Not able to Update the Balance as I do not know how to retrieve the Balance from the Deleted Expense
Here is the Delete Route :
router.delete("/", (req, res) => {
const { UserID, ExpenseID } = req.query;
const query = {
$pull: {
Expenses: { _id: ExpenseID },
},
};
User.findByIdAndUpdate(UserID, query)
});
I want to add a Mongoose Method which Will Fetch the Expense Amount from the Received Expense Id and Store it in a variable and After the Expense Gets Deleted, I want to call a method to update the balance of the User in the Promise.
Here is an example of what I intend to do
// Deletes An Expense
router.delete("/", (req, res) => {
const { UserID, ExpenseID } = req.query;
const query = {
$pull: {
Expenses: { _id: ExpenseID },
},
};
User.find({/*Some Query Which Will Return me The Provided Expense ID's Amount*/})
User.findByIdAndUpdate(UserID, query)
.then(() => {
// Once I find and Remove the Expense, Now I would Like to Update the Users Balance
// NewBalance = OldBalance + Expense ID's Amount Returned by the Find Method
// Here I would Call another User.find method to update the Users Balance
})
});
Let's Say From the Above Database Snap, I want to delete the Expenses (Starting From 0) 1st Object Element, Name: Uber Fare, I will send the Object Id which is 6091f725403c2e1b8c18dda3 to the Server and should Expect my Balance to Increase from 48495 to 49695
What you can do is:
Fetch user document with UserID
Find the expense with ExpenseID
Update the Balance with the expense amount
Remove expense from Expenses array
Save user document
router.put("/", async (req, res) => {
try {
const { UserID, ExpenseID } = req.query;
let user = await User.find({ _id: UserID });
index = user.Expenses.findIndex((expense) => expense._id === ExpenseID);
if (index != -1) {
user.Balance += user.Expenses[index].Amount;
user.Expenses.splice(index, 1);
}
await user.save();
return res.status(200).json({ success: true, user: user });
} catch (error) {
return res.status(400).json({ success: false, error: error })
}
});
NOTE: Since this is updating the document, I configured put method instead of delete on the router.
Create a static method for the schema like so
userSchema.statics.deleteExpenseUpdateBalance = async function (userID, expenseID) {
const user = await this.findOne({ _id: userID });
let userExpenses = user.Expenses;
userExpenses.forEach((expense) => {
if (expense._id === expenseID) {
let amount = expense.Amount;
let index = userExpenses.indexOf(expense);
userExpenses.splice(index, 1);
user.Balance += amount;
return;
}
});
user.save((err) => {
if (err) {
console.log(err)
}
});
};
Access this in your route handler
router.delete("/", async (req, res) => {
const { UserID, ExpenseID } = req.query;
let result = await User.deleteExpenseUpdateBalance(UserID, ExpenseID)
});
This static method will remove the expense and update the balance.
Explanation: The static method just finds the user and iterates through their Expenses and removes the required expense and the amount is just added to the balance. Finally the user.save() method is called to save all documents.
NOTE: Use this only if you are sure that the expenseID exists in the Expense array, otherwise you'll just have to add an extra check for that in the static method
Also put the static method in the file where you create your schema and model in between both of their creations i.e after you've created the userSchema and before you finalise it into a model.

Populating the "Ref" in mongoose schema while working with Graphql

I am working with Graphql and then I come to a situation where I need to populate, but I am not getting how to excute that.
Here is my Booking schema
const mongoose=require('mongoose')
const Schema=mongoose.Schema
const bookingschema=new Schema({
event:{
type:Schema.Types.ObjectId,
ref:'Event'
},
user:{
type:Schema.Types.ObjectId,
ref:'User'
}
}
,{timestamps:true})
module.exports=mongoose.model('Booking',bookingschema)
Here is my resolver to create a booking event
bookevent: async args => {
const fetchevent = await Event.findOne({ _id: args.eventid });
const booking = new Booking({
user: "5d64354bfd7bb826a9331948",
event: fetchevent
});
const result = await booking.save();
return {
...result._doc,
_id: result._id,
createdAt: new Date(result._doc.createdAt).toISOString(),
updatedAt: new Date(result._doc.updatedAt).toISOString()
};
}
};
When i try to run the graphql query i easily get what i require
mutation{
bookevent(eventid:"5d6465b4ef2a79384654a5f9"){
_id
}
}
gives me
{
"data": {
"bookevent": {
"_id": "5d64672440b5f9387e8f7b8f"
}
}
but now how do I populate user here ???
cause at the end I want this query to be executed successfully
mutation{
bookevent(eventid:"5d6465b4ef2a79384654a5f9"){
_id
user{
email
}
}
Schema of eventtype is
type Event{
_id:ID!
title:String!
description:String!
price:Float!
date:String!
creator:User!
}
cause my user schema has email inside it and I am trying to reach that out
So where in my Booking resolver should I populate "user" ??
To resolve the user I did
const result = await booking.save();
const res=await result.populate("user");
console.log(res) //doesnt gives the populated user only gives me id
If I am not wrong for these cases populate is the way right?
I hope it may help you.
const result = await booking.save();
const res=await booking.findById(result._id).populate("user");
console.log(res)
I have never made anything like this before but after saving new Booking here:
const result = await booking.save();
You can use .populate() on result. Example:
await result.populate("user");
Or if the above not work:
await result.populate("user").execPopulate();

Categories