Mongoose $pull operator not removing ObjectID from array - javascript

In my application, I have a post schema (shown below):
const postSchema = new mongoose.Schema({
file: {
type: String,
required: true
},
caption: {
type: String,
maxLength: 2000
},
user: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
likeNum: {
type: Number,
default: 0,
min: 0
},
likes: [{
type: mongoose.Schema.Types.ObjectId
}]
})
I want to remove an objectid from the likes array when a user request is sent.
Route:
const post = await Post.findOne({_id: req.params.postid})
const user = req.user._id
post.update({}, {$pull: {likes: user}})
post.likeNum--
await post.save()
res.send('Unliked')
However the objectid is not removed from the array when the route is called. Can anyone spot why? Thanks.
UPDATE:
const user = mongoose.Types.ObjectId(req.user._id)
UPDATE 2:
Post.updateOne({_id: req.params.postid}, { $pull: { likes: mongoose.Types.ObjectId(req.user._id) } })
post.likeNum--
await post.save()
res.send('Unliked')

You can do both operations in a single query no need to findOne,
convert req.user._id to object id using mongoose.Types.ObjectId
$inc to decrees the counts of likeNum
await Post.updateOne(
{ _id: req.params.postid },
{
$pull: { likes: mongoose.Types.ObjectId(req.user._id) },
$inc: { likeNum: -1 }
}
);
res.send('Unliked');
Playground

Related

problem getting data in mongodb, get by category id

I'm trying to filter my pets by category, I have the following model of pets:
const Pet = mongoose.model(
'Pet',
new Schema({
name: {
type: String,
required: true,
},
age: {
type: Number,
required: true,
},
description: {
type: String,
},
weight: {
type: Number,
required: true,
},
color: {
type: String,
required: true,
},
images: {
type: Array,
required: true,
},
available: {
type: Boolean,
},
category: Object,
user: Object,
adopter: Object,
}, { timestamps: true }),
);
module.exports = Pet;
when I try to get the data through postman it returns an empty array as a response.
my code to filter by category:
static async getByCategory(req, res) {
const id = req.params.id;
// check if id is valid
if (!ObjectId.isValid(id)) {
res.status(422).json({ msg: 'Invalid ID' });
return;
}
const pets = await Pet.find({ 'category._id': id }).sort('-createdAt');
if (!pets) {
res.status(404).json({ msg: 'Pets not found!' });
return;
}
res.status(200).json({ pets });
}
it's my first time using mongodb so i'm not sure what's wrong.
id being passed from the client side is string and the one which is saved in the db is ObjectId. Convert the string to Mongoose ObjectId before Pet.find().
const id = mongoose.Types.ObjectId(req.params.id);
const pets = await Pet.find({ 'category._id': id }).sort('-createdAt');
Don't forget to import 'mongoose'.
Could you check that your MongoDB indeed has a field category._id?

Mongoose How to get a nested object within a document via findOne

I need to get a nested object within a certain document (searched by user ID) that also has an object inside of it (there's no guarantee that this object will be the same object).
I have the User model to be:
const mongoose = require('mongoose');
const { bool } = require('#hapi/joi');
const monitoringSchema = new mongoose.Schema({
type: Object,
default: {}
})
const hubSchema = new mongoose.Schema({
hubID: {
type: String,
default: ""
},
isSetup: {
type: Boolean,
default: false
},
monitoring: {
type: monitoringSchema
}
}, {strict:false})
const finalUserSchema = new mongoose.Schema({
username: {
type: String,
required: true,
max: 255
},
email: {
type: String,
required: true,
max: 255,
},
password: {
type: String,
required: true,
min: 10,
max: 1024,
},
date: {
type: Date,
default: Date.now
},
isVerified: {
type: Boolean,
default: false
},
hub: {
type: hubSchema
}
}, {strict:false});
module.exports = mongoose.model('User', finalUserSchema);
OR It has the layout:
_id: "id"
isVerified: true
username: "nathan"
email: "email#email.com"
hub:
hubID: "id"
monitoring: // WHOLE OBJECT I NEED TO RETREIVE
exampleObject:
exampleValue: exampleKey
I have an array of user IDs I need to update and I tried the query:
for(i in usersToUpdate){
User.findOne({_id: usersToUpdate[i], "hub.monitoring": {}}, {}, callbackResponse);
function callbackResponse(err, data){
if(err) return console.log(err)
console.log(data)
}
}
But it returns null as the data so obviously the query is wrong. I know the error is:
{_id: usersToUpdate[i], "hub.monitoring": {}}
more specifically:
"hub.monitoring": {}
I'm using {} to reference an object within monitoring, what's the correct reference to reference an unknown object and get it's values back, like a wildcard? I've tried:
{_id: usersToUpdate[i], "hub.monitoring": Object}
and it still doesn't work. I've seen this answer, however they reference a value that they already know, like a name?
To retrieve only the monitoring object, aggregation pipeline can be used.
Using $match to filter and $project to output/ supress fields.
User.aggregate([
{
$match: {
_id: mongoose.Types.ObjectId(usersToUpdate[i]),
},
},
{
$project: {
monitoring: "$hub.monitoring",
_id: 0,
},
},
]).exec(callbackResponse);
Playground example
You can try using the 2 object form of findOne where the first object is the query and the second object is the projection of what you want to return.
User.findOne({_id: usersToUpdate[i]}, {"hub.monitoring": {$exists: true}}, callbackResponse);
function callbackResponse(err, data){
if(err) return console.log(err)
console.log(data)
}
This way, the object will be returned if the monitoring object exist.

How to save Data In MongoDB ? Not all the Fields at one single time

I Have created a Schema to store user Details and Some information regarding it's test.
const mongoose = require("mongoose");
const schema = mongoose.Schema;
const userSchema = new schema({
name: { type: String },
gender: { type: String },
stream: { type: String },
email: {
type: String,
required: true,
unique: true,
match: /[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*#(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?/,
},
verified: { type: Boolean, default: false },
password: { type: String, required: true },
// For premium Calculations
test: {
type1Count: { type: Number, default: 0 },
type2Count: { type: Number, default: 0 },
type3Count: { type: Number, default: 0 },
},
testCompleted: [
{
testId: { type: String },
testDate: { type: Number },
testTypeNumber: { type: Number },
testTypeName: { type: Number },
testDurationInMinutes: {type: Number},
listOfQuestionId: [{ type: String }],
listOfCorrectAnswer: [{ type: Number }],
listOfAnswerByUser: [{ type: Number }],
totalNumberOfQuestion: { type: Number },
numberOfAttemptedQuestion: { type: Number },
numberOfUnattemptedQuestion: { type: Number },
numberOfCorrectAnswer: { type: Number },
numberOfWrongAnswer: { type: Number },
marksObtained: {type: Number},
isTestCompleted : {type: Boolean, default: false}
},
]
});
module.exports = mongoose.model("User", userSchema);
Now, I am not save all the data at a single time,
first, I will save only it's registration part like Name, Email, password.
code to register user...
const newUser = new User({
name,
email,
password,
});
const salt = await bcrypt.genSalt(10);
newUser.password = await bcrypt.hash(password, salt);
await newUser.save();
Now there is a user created in my database.
now I want to save this data ...
// some logics are there, but the data is fine ..
var paper = {
testId: Object.keys(user.testCompleted).length +1, // I choose Normal serialise Number starting from 1
testDate: Date.now(),
testTypeNumber: type,
testTypeName: testList[type - 1]["testName"],
testDurationInMinutes: testList[type - 1]["testDurationInMinutes"],
weightageOfQuestion: list[type - 1],
totalNumberOfQuestion: list[type - 1]["physics"] + list[type - 1]["chemistry"] + list[type - 1]["biology"],
listOfQuestionId: arrayOfQuestion,
listOfCorrectAnswer: arrayOfAnswer,
};
Now I have created a user earlier
and In the later stage, I have to save paper data in the testCompleted [] setion, which is a array of objects
and In the later stage i have to store this data .. in the same testCompleted [] setion,
const data = {
totalNumberOfQuestion: totalNumberOfQuestion,
numberOfAttemptedQuestion: attempt,
numberOfUnattemptedQuestion: totalNumberOfQuestion - attempt,
numberOfCorrectAnswer: attempt - wrong,
numberOfWrongAnswer: wrong,
marksObtained: marks,
isTestCompleted: true
}
Please help me, how to save data partially in mongodb at any points ?
2 ways for updating array inside documents:
Find the document then use push() for adding into array then call .save().
const userToUpdate = await User.find({_id:userId})
userToUpdate.testCompleted.push(data)
userToUpdate.save()
Using findOneAndUpdate() or update() and $push
User.update(
{ _id: userId },
{ $push: { testCompleted: data } },
done
);
Reference: using_nodejs_and_mongoose_to_update_array

How can i iterate mongoose returned documents array in loop using mongoose?

I have a node.js(express based) server in which i have a function which returns all users. Here is the function.
export async function findAllUser() {
let users = await User.find({}).exec()
return users
}
In my node.js applicaiton i have two models(schema) of Users and Referrals like this .
var User = mongoose.model(
"users",
new Schema({
first_name: String,
last_name: String,
name: String,
email: String,
password: String,
roleId: { type: Number, default: 0 },
country: String,
token: String,
createdAt: String,
updatedAt: String,
tempToken: String,
verificationCode: String,
fbUserId: String,
isFbUser: { type: Boolean, default: false },
isActive: { type: Boolean, default: true },
isEmailVerified: { type: Boolean, default: false },
rememberme: Boolean,
}, {
toJSON: { virtuals: true },
toObject: { virtuals: true }
})
);
User.virtual("referrals", {
ref: "referralLinks",
foreignField: "userId",
localField: "_id"
});
export var ReferralLink = mongoose.model(
"referralLinks",
new Schema({
referral_link: String,
referral_code: String,
isLink: Number,
offer_name: String,
offer_desc: String,
user_email: String,
companyId: { type: Schema.Types.ObjectId, ref: 'companies' },
addedByAdmin: { type: Boolean, default: true },
number_of_clicks: Number,
referral_country: String,
link_status: String,
categoryId: { type: Schema.Types.ObjectId, ref: 'categories' },
number_of_clicks: { type: Number, default: 0 },
createdAt: String,
updatedAt: String,
userId: { type: Schema.Types.ObjectId, ref: 'users' }
})
);
I have my separate api.route.js file in which i have get all users route like this
router.get("/", log, getAllUsers);
And i my api.controller.js file i have getAllUsers like this
export async function getAllUsers(req, res) {
try {
let Users = await findAllUser()
if (Users) {
generateResponse(true, "All Users fetched", Users, res)
} else {
generateResponse(false, "No Users found", null, res)
}
} catch (err) {
generateResponse(false, 'Error occured, 404 not found!', err, res)
}
}
And in my api.handler.js file i have findAllUser function like this
export async function findAllUser() {
let users = await User.find({}).populate("referrals").exec()
return users
}
Single user can have more than one Referrals. But unfortunately i don't have 'Referrals' reference _id in Users document. Now, i want to get all users with their respective Referrals
I am getting all users correctly but for each user i also want to fetch all their respective referrals. So for that i definitely can't use for or forEach loop because of async nature of mongoose find. So what should i use instead of for or forEach loop?
My desired results
results = [
{
first_name : "Fahad",
last_name : "subzwari",
email : "fahadsubzwari#gmail.com",
password : "***",
referrals : [
{
//referral object 1
},
{
//referral object 2 ...
}
]
},
{
first_name : "Alex",
last_name : "Hales",
email : "alex#gmail.com",
password : "***",
referrals : [
{
//referral object 1
},
{
//referral object 2 ...
},
{
//referral object 3 ...
}
]
},
]
To be able to access referrals from user you need to use virtual populate.
So your userSchema must be like this:
const userSchema = new Schema(
{
first_name: String,
last_name: String,
name: String,
email: String,
password: String,
roleId: { type: Number, default: 0 },
country: String,
token: String,
createdAt: String,
updatedAt: String,
tempToken: String,
verificationCode: String,
fbUserId: String,
isFbUser: { type: Boolean, default: false },
isActive: { type: Boolean, default: true },
isEmailVerified: { type: Boolean, default: false },
rememberme: Boolean
},
{
toJSON: { virtuals: true },
toObject: { virtuals: true }
}
);
// Virtual populate
userSchema.virtual("referrals", {
ref: "referralLinks",
foreignField: "userId",
localField: "_id"
});
var User = mongoose.model("users", userSchema);
And now you can use this route to access referrals from users:
router.get("/", async (req, res) => {
const result = await User.find({}).populate("referrals");
res.send(result);
});
The result will be like this: ( I excluded some fields for simplicity)
[
{
"_id": "5dd6819201419f5930d02334",
"name": "User 1",
"email": "user1#gmail.com",
"password": "123123",
"__v": 0,
"referrals": [
{
"_id": "5dd6829831b95a6b2cd58fca",
"referral_link": "referral_link 1",
"userId": "5dd6819201419f5930d02334",
"__v": 0
},
{
"_id": "5dd682a031b95a6b2cd58fcb",
"referral_link": "referral_link 2",
"userId": "5dd6819201419f5930d02334",
"__v": 0
}
],
"id": "5dd6819201419f5930d02334"
},
{
"_id": "5dd681a101419f5930d02335",
"name": "User 2",
"email": "user2#gmail.com",
"password": "123123",
"__v": 0,
"referrals": [
{
"_id": "5dd682a731b95a6b2cd58fcc",
"referral_link": "referral_link 3",
"userId": "5dd681a101419f5930d02335",
"__v": 0
}
],
"id": "5dd681a101419f5930d02335"
}
]
UPDATE:
Here is the steps for your project setup:
api.handler.js:
exports.findAllUser = async function() {
console.log("api handler inside");
let users = await User.find({})
.populate("referrals")
.exec();
console.log("in handler: ", users);
return users;
};
api.controller.js:
const handler = require("./api.handler");
exports.getAllUsers = async function(req, res) {
console.log("userController.getAllUsers");
try {
let Users = await handler.findAllUser();
if (Users) {
return res.send(Users);
generateResponse(true, "All Users fetched", Users, res);
} else {
generateResponse(false, "No Users found", null, res);
}
} catch (err) {
generateResponse(false, "Error occured, 404 not found!", err, res);
}
};
api.route.js
const apiController = require("../controllers/api.controller");
router.get("/", log, apiController.getAllUsers);
You say "i don't have 'Referrals' reference _id in Users" so I assume you have a reference to the user in the Referrals schema?
Otherwise, with no way to link them you are lost at sea I'm afraid... :-(
If you do then you would do it in a separate query:
const userIds = users.map(user => user._id);
const referrals = await Referrals.find({ userId: { $in: userIds } })
The $in operator will grab any field where the user id is included in the array.
EDIT: In response to your update - yes the above should work fine. Then you can do what you want with them e.g. map the referrals to the user objects, or use them individually etc. etc.
EDIT2: Yep this is the way. At this point you have an array of users and an array of referrals so you just need to put them together.
users.map(user => ({
// add props from user obj
...user,
// add all referrals that with matching userId
referrals: referrals.filter(referral => referral.userId === user._id)
}))
Remember that as you are dealing with asynchronous calls and promises so you will either need to use the async/await keywords, or parse the results in the promise callback.

Dynamically push, pull, and set on mongoose schema update

I am trying to setup my patch api so that I can create a dynamic query to push, pull, and set data in my mongoose schema. I have plenty of values that I would change using set, but I also have an array of objects which would require me to call push when I need to insert and pull when I need to remove an item. I'm trying to find the best way to combine this into a dynamic structure.
Schema:
const StepSchema = new Schema({
position: {
type: Number,
required: true
},
name: {
type: String,
required: true
},
due_date: {
type: Date
},
status: [{
label: {
type: String,
enum: ['Inactive', 'In Progress', 'Flagged', 'Complete'],
default: 'Inactive'
},
user: {
type: Schema.Types.ObjectId,
ref: 'users',
},
date: {
type: Date
}
}],
comments: [{
user: {
type: Schema.Types.ObjectId,
ref: 'users',
required: true
},
body: {
type: String,
required: true
},
date: {
type: Date,
required: true
},
}],
});
Api:
router.patch('/',
async (req, res) => {
let setQuery = req.body;
let pushQuery = {};
let pullQuery = {};
//remove id from set query
delete setQuery.id;
//if there is a comment
if(req.body.comment){
pushQuery.comments = req.body.comment
}
//if I need to remove a comment
if(req.body.remove_comment){
pullQuery.comments = {_id: req.body.remove_comment.id}
}
//Push new status into array
if(req.body.status) {
pushQuery.status = {
label: req.body.status,
user: req.user._id,
date: new Date()
};
delete setQuery.status;
}
//update step
await Step.findByIdAndUpdate(req.body.id, {$set: setQuery, $push: pushQuery, $pull: pushQuery})
.then(step => {
if(!step){
errors.noflow = "There was a problem updating the step";
return res.status(400).json(errors);
}
res.json(step)
})
.catch(err => {
console.log(err);
res.status(404).json(err);
});
});
I've been getting the following error when trying to push a new status into my document:
operationTime: Timestamp { bsontype: 'Timestamp', low: 1, high_:
1560978288 }, ok: 0, errmsg: 'Updating the path \'status\' would
create a conflict at \'status\'', code: 40, codeName:
'ConflictingUpdateOperators', '$clusterTime': { clusterTime:
Timestamp { bsontype: 'Timestamp', low: 1, high_: 1560978288 },
signature: { hash: [Object], keyId: [Object] } },
Oh, you're doing that $set and $push on a status. Your pushQuery is trying to have status be an array on the document, and your setQuery wants to set it to whatever it was on the actual body (I'm guessing the same object.
A quickfix would be to remove it from the set object:
delete setQuery.status
A reasonable and stable way to do this would be to actually only take the things from req.body which you really want for each of the stages. Example:
const { position, name, dueDate, status, comment, remove_comment } = req.body;
const setQuery = { position, name, dueDate };
const pushQuery = { status, comments: comment };
// ...
That way your queries are not conflicting in any way.

Categories