How to do query in mongoose - node.js

let UserSchema = new Schema({
created: {
type: Date,
default: Date.now,
required: true
},
username: {
type: String,
unique: true,
required: true
},
password: {
type: String,
required: true
},
role: {
default: 'user',
type: String, //we can consider using array of strings as in case user has several roles at the same time
requierd: true
},
devices: [
{
id: '1',
permissions:['start','stop']
},
{
id: '2',
permissions:['start','restart']
}
]
});
How can i push new {id:'2', permissions:['stop']} without duplications, when i receive it in req.params.headers. I need to check is id:'2' already exist or not, then I'm shuld check is permission['stop'] is exist, and then if it isn't exist i should push it in collection.

If you want to find any object that matchs either the id or the permission, you can do it with $or operand, something like this:
return User.findOne({ $or:[ {'devices.id': idValue}, {'devices.permissions': permission} ]}).exec()
.then(user => {
if(user) {
console.log("We don't have to push it");
} else {
console.log("We have to push it");
}
});

You can use update with elemMatch in condition to find the inner object. To avoid adding duplicate keys in array, you can use $addToSet
<>.update({"devices": { "$elemMatch": { "id": "2" }}},{ "$addToSet": { "devices.$.permissions": "stop" }})

Related

Mongoose can't find capital letter fields

I have a schema in Mongo like this:
const userSchema = new mongoose.Schema({
_id: {
type: mongoose.Types.ObjectId,
required: true,
},
organizationId: {
type: mongoose.Types.ObjectId,
required: true,
},
projectsId: {
type: [mongoose.Types.ObjectId],
default: []
},
roleId: {
type: mongoose.Types.ObjectId,
required: true,
},
email: {
type: String,
required: true,
},
password: {
type: String,
required: true,
},
pic: {
type: String,
default: ''
},
createdOn: {
type: Date,
default: Date.now
}
I want to find users with a specific organizationId, but mongoose returns empty array. When i try to find with an _id, email it works but when i try it with organizationId and projectsId it doesn't find it. This led me to believe there is a problem with field names which have capital letters in them.
Here is my node/express code:
const getMemberListController = async (req, res, next) => {
try {
const memberList = await User.find({organizationId: '6239b5fc3c16a8041341a3e8'});
res.status(200).json(memberList);
} catch (error) {
res.status(500).json(error);
}
}
The above returns an empty array, eventhough
User.find({_id: '623ad5c75d17751ff664a9ae'})
returns:
[
{
"pic": "",
"_id": "623ad5c75d17751ff664a9ae",
"organizationId": "6239b5fc3c16a8041341a3e8",
"roleId": "ba3e35fc1341a80462c16398",
"projectsId": [
"6239b61f8f8e67d675297178",
"6239b637a4ec3ff5ef2df0cb"
],
"email": "yosephten#gmail.com",
"password": "$2a$10$hIMWjl2wz9g1C3NN4sqwHOMgx0epaJtdOmGVWckkRcCqltIn9JlTe",
"createdOn": "2022-03-23T10:42:11.170Z"
}
]
I tried it in mongoDB locally by running the same code: db.users.find({organizationId: '6239b5fc3c16a8041341a3e8'}) but this time, it returns the specified user.
I don't know if it is the capitalization or mongoose the issue. Please help.
Thanks.
Found it.
The issue was when i was creating users in db, i was using this script:
db.users.insertMany([
{
'organizationId': '3c16a8046239b5fc1341a3e8',
'roleId': 'ba3e35fc1341a80462c16398',
'email': 'yosephten#gmail.com',
'password': '$2a$10$hIMWjl2wz9g1C3NN4sqwHOMgx0epaJtdOmGVWckkRcCqltIn9JlTe'
},
{
'organizationId': 'b5fc3c16a80462391341a3e8',
'roleId': 'ba3e35fc1341a80462c16398',
'email': 'yoseph2#gmail.com',
'password': '$2a$10$hIMWjl2wz9g1C3NN4sqwHOMgx0epaJtdOmGVWckkRcCqltIn9JlTe'
},
])
But this will make the foreign key fields(organizationId roleId and projectsId) strings. Thus failing the check.
The script should be:
db.users.insertMany([
{
'organizationId': new ObjectId('3c16a8046239b5fc1341a3e8'),
'roleId': new ObjectId('ba3e35fc1341a80462c16398'),
'email': 'yosephten#gmail.com',
'password': '$2a$10$hIMWjl2wz9g1C3NN4sqwHOMgx0epaJtdOmGVWckkRcCqltIn9JlTe'
},
{
'organizationId': new ObjectId('b5fc3c16a80462391341a3e8'),
'roleId': new ObjectId('ba3e35fc1341a80462c16398'),
'email': 'yoseph2#gmail.com',
'password': '$2a$10$hIMWjl2wz9g1C3NN4sqwHOMgx0epaJtdOmGVWckkRcCqltIn9JlTe'
},
])
Now mongoose will check ObjectId to ObjectId instead of ObjectId to String.

how to insert into deeply nested element API nodejs mongoose

I have 4 nested documents as follow:
//Nested sub document subControl
const SubControlSchema = new Schema({
subControlNo: {
type: String
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
//Nested sub document control
const ControlSubSchema = new Schema({
mainControl: {
type: String
},
subControls: [SubControlSchema],
controlDescription: {
type: String,
trim: true
},
updated: Date,
created: {
type: Date,
default: Date.now
}
});
//Nested sub document domain
const DomainSubSchema = new Schema({
_id: {
type: Schema.ObjectId,
auto: true
},
domainNo: {
type: String,
trim: true
},
domainName: {
type: String,
trim: true
},
domainDescription: {
type: String,
trim: true
},
controls: [ControlSubSchema],
updated: Date,
created: {
type: Date,
default: Date.now
}
});
// framework Schema
const FrameworkSchema = new Schema({
name: {
type: String,
trim: true
},
description: {
type: String,
trim: true
},
regulator: {
type: Schema.Types.ObjectId,
ref: 'Regulator',
default: null
},
client: {
type: Schema.Types.ObjectId,
ref: 'Client',
default: null
},
domains: [DomainSubSchema],
updated: Date,
created: {
type: Date,
default: Date.now
}
});
module.exports = Mongoose.model('Framework', FrameworkSchema);
I'm trying to post a control under the domain which is inside the framework, here's what I have been trying to do:
//Add new control under a specific domain and framework
router.post('/add/:frameworkId/:domainId', auth, async (req, res) => {
try {
const control = req.body.controls; //take the request from the body
const query = { _id: req.params.frameworkId, _id: req.params.domainId };//pushing into the framework model by taking the ID from URL
await Framework.updateOne(query, { $push: { domains: control } }).exec(); //push the query into the framework model
res.status(200).json({
success: true,
controls: control
});
} catch (error) {
res.status(400).json({
// error: 'Your request could not be processed. Please try again.'
error
});
}
});
Data posted in postman:
Link: http://localhost:3000/api/framework/add/6233277f411377367f8ad1c0/6233277f411377367f8ad1c1
{
"controls":
{
"mainControl": "1-5",
"subControls": [{
"subControlNo": "1-4-1"
},
{
"subControlNo": "1-4-2"
}],
"controlDescription": "controlDescriptionTest"
}
}
Response:
{
"success": true,
"controls": {
"mainControl": "1-5",
"subControls": [
{
"subControlNo": "1-4-1"
},
{
"subControlNo": "1-4-2"
}
],
"controlDescription": "controlDescriptionTest"
}
}
Problem: I'm not getting any new data in mongodb , any idea if I'm approaching this the correct way? I'm guessing the data is posted correctly and It's a problem with saving it to the database
Picture of my schema: I want to be able to add elements under the controls:
First if you want your code to insert and not update you should use insertOne and not updateOne, regarding an "update" operation I can see 2 potential "issues" here:
req.params.frameworkId and req.params.domainId come as string type. And I assume the _id field is type ObjectId and not string.
To fix this you just need to cast it to the proper type, like so:
import { ObjectId } from 'mongodb';
...
{ _id: new ObjectId(req.params.frameworkId) }
Both parameters are "querying" the same field (_id), unless this is intentional somehow if these values are different it will never find a document to match, this should be changed.
Lastly if you want to update an existing object if exists, and if not insert then you should use updateOne with the upsert option:
await Framework.updateOne(query, { $push: { domains: control } }, { upsert: true }).exec();

Mongoose query not returning updated information with { new: true } param

Why is my query not returning updated information?
UserSchema.findByIdAndUpdate(
{ _id: userId },
{ $set: { couponList: couponList } }, { new: true }).populate('couponList').exec().then(user => {
// user returning with the old information
}).catch(err => console.log(err));
I have 3 params:
first one is the id of the user i want to update (objectId)
second one is the information I want to update (objectId Array)
third is the flag that says I want to receive the updated information (Boolean)
My coupon schema goes like this:
import mongoose from 'mongoose';
const CouponSchema = new mongoose.Schema({
name: {
type: String,
default: 'Unknown'
},
description: {
type: String,
default: undefined
},
validity: {
type: Date,
default: null
},
code: {
type: String,
default: undefined
},
blackList: {
type: Array,
ref: 'user',
default: []
},
blackListFlag: {
type: Boolean,
default: false,
},
whiteList: {
type: Array,
ref: 'user',
default: []
},
limit: {
type: Number,
default: 0,
},
counter: {
type: Number,
default: 0,
},
amount: {
type: Number,
default: 0,
},
discountType: {
type: String,
default: undefined,
}
}, { collection: 'coupon' });
export default mongoose.model('coupon', CouponSchema);
And in my user schema I have a ref to the coupon schema:
couponList : {
type: Array,
ref: 'coupon',
default: []
},
I think you need to define the field couponList in your schema.
Edit: Just noticed the UserSchema, theoretically, you should be fine, if you are pushing correct objectIds.
findByIdAndUpdate with {new: true} must work as intended.
But I'm not aware of Your code totally and what kind of data You're sending as couponList.
So try to separate update and select operations and see what happens. In fact mongoose does the same when You call findByIdAndUpdate.
For example using express framework:
const User = mongoose.model('user');
router.put('/user/:userId/coupons', async (req, res) => {
try {
const {userId} = req.params;
const {couponList} = req.body;
await User.updateOne(
{_id: userId},
{$set: {couponList: couponList}},
{upsert: false}
);
const user = await User
.findById(userId)
.populate('couponList').lean();
res.status(200).send(user);
}
catch (error) {
console.log(error);
res.status(500).send({})
}
});
P.S. Only reason for that unexpected behavior may be that somehow (but it's not possible) it uses native driver for which {new: true} must be written as: {returnNewDocument: true}
Check this link
I found out that the problem was not with returning updated information but it was on populating the collection.
The correct reference to the coupon collection in user schema:
couponList: [ { type: mongoose.Schema.ObjectId, ref: 'coupon' } ],

Mongoose findOneAndUpdate when value is not in array

I have the below Mongoose function where I am trying to do a select WHERE email = req.body.email and referenceId is not in an array.
referenceId is a string array [String] which has an array of object ID's.
const refereeSchema = new Schema({
firstName: {
type: String,
required: true
},
lastName: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
referenceId: [
{
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
}
],
token: {
type: String,
required: true
},
password: {
type: String
},
company: {
type: String,
required: true
},
role: {
type: String,
required: false
},
startedOn: Date,
endedOn: Date,
createdAt: Date,
updatedAt: Date
});
Referee.findOneAndUpdate({
email: req.body.email,
referenceId: {
'$ne': [new mongo.ObjectID(req.params.referenceId)]
}
}, {
$push: {
referenceId: new mongo.ObjectID(req.body.referenceId)
}
}, {
new: true
}, (err, doc) => {
if (err) {
res.status(401).send(err);
}
res.send(doc);
});
Each time I run the above it always matches and pushes the ObjectId even though it already existed before.
{ referenceId:
[ 5ba94232b492890982e7a417,
5ba94232b492890982e7a417,
5c20d8f907a55accd720c27f,
5c20d8f907a55accd720c27f,
5c20d8f907a55accd720c27f,
5c20d8f907a55accd720c27f,
5c20d8f907a55accd720c27f,
5c20d8f907a55accd720c27f ],
_id: 5c20e86d72d5c1ce20b961fa,
firstName: 'Richard',
lastName: 'Hendricks',
email: 'hello#domain.com',
token: '319d9f5a-68e1-6f47-a9f1-7fbbf617a45c',
company: 'Pied Piper',
role: '',
__v: 0 }
Could someone please help me?
I'd like to do a check to see if the ObjectId is already in the array then DO NOTHING else push it.
$push will just push data in array, you should use $addToSet
$addToSet only ensures that there are no duplicate items added to the set and does not affect existing duplicate elements. $addToSet does not guarantee a particular ordering of elements in the modified set
more details
Referee.findOneAndUpdate({
email: req.body.email,
referenceId: {
'$ne': [new mongo.ObjectID(req.params.referenceId)]
}
}, {
$addToSet: {
referenceId: new mongo.ObjectID(req.body.referenceId)
}
}, {
new: true
}, (err, doc) => {
if (err) {
res.status(401).send(err);
}
res.send(doc);
});
more ever, you can use $elemMatch or $nin as find query, so that document itself doesnt return if id is in referenceId
$elemMatch :
referenceId: {
'$elemMatch': { '$ne': new mongo.ObjectID(req.params.referenceId) }
}
$nin:
referenceId: {
'$nin': [new mongo.ObjectID(req.params.referenceId)]
}
Consider using the $elemMatch operator in complement of $ne. $ne against the array itself will always return true in this case, because the array you're passing in the parameter is never going to equal the array in the document.
I haven't tested this, but something along these lines should work:
Referee.findOneAndUpdate({
email: req.body.email,
referenceId: {
'$elemMatch': { '$ne': new mongo.ObjectID(req.params.referenceId) }
}
}, {
$push: {
referenceId: new mongo.ObjectID(req.body.referenceId)
}
}, {
new: true
}, (err, doc) => {
if (err) {
res.status(401).send(err);
}
res.send(doc);
});
Also, you probably already know this but there are actually two ObjectId classes you might be importing, the one you want is mongoose.Types.ObjectId

Push values into array with mongoose

I have a problem pushing values into an array with mongoose (yes I have read many topics about it and tried many ways to do it).
So I have this schema
const Postit = new Schema({
text: {
type: String,
required: true
},
status: {
type: String,
default: 'TODO'
},
modified: {
type: Date,
default: Date.now
},
user: {
type: ObjectId,
ref: 'User',
required: true
},
collaborators: [String]
})
And I'm trying to push a string in the collaborators property where the queries match.
So this is the method I use to update it
addCollaborator(uid, pid) {
return Postit.updateOne({
_id: pid,
user: uid
},
{ $push: { collaborators: 'pepe' } },
(err, raw) => {
//TO DO
})
}
But nothing happens. The query match because if I change $push for $set and put a new value to status property for example it updates.
The funny thing is that if I run it in mongodb client terminal it works.
db.postits.updateOne({
_id: ObjectId("5beb1492cf484233f8e21ac1"),
user: ObjectId("5beb1492cf484233f8e21abf")
},
{ $push: {collaborators: 'pepe' }
})
What i'm doing wrong?
Pick promises or callbacks but do not mix them together. Either you do:
addCollaborator(uid, pid) {
Postit.updateOne({
_id: mongoose.Types.ObjectId(pid),
user: uid
},
{ $push: { collaborators: 'pepe' } },
(err, raw) => {
// TO DO
})
}
Or you do:
addCollaborator(uid, pid) {
return Postit.updateOne({
_id: mongoose.Types.ObjectId(pid),
user: uid
},
{ $push: { collaborators: 'pepe' } }).exec()
.then(result => {
// TO DO
})
}
Also make sure your objectId is an actual mongoose.Types.ObjectId
Ok, the problem is I was using an old schema.
The code works perfectly. Enough code for today...

Resources