How do I check if the objectids are similar? - node.js

Below is my function to check if the username in session matches to the person who commented originally:
async sameUser(id,uname){
const blogsCollection = await blogs();
id = ObjectId(id)
let a;
const finder = await blogsCollection.findOne({'comments._id':id});
for(i=0;i<finder.comments.length;i++){
if (finder.comments[i]._id == id) {
a = finder.comments[i].commentuser;
}
}
if (a == uname) return true
else return false
}
This is what the collection looks like in MongoDB:
{
_id: new ObjectId("61f4c62818c9c4633a117beb"),
title: 'my first blog',
body: 'wazzzzzzzzzzzzzzzzzzup',
bloguser: {
_id: new ObjectId("61f22f8bb5c180195ca925df"),
username: 'tom'
},
comments: [{
_id: new ObjectId("61f4ec0a0d4c9731bdbe5f6b"),
comment: 'wow',
commentuser: 'tom'
}
]
}
I am calling the sameUser function this way:
const user = await blogs.sameUser('61f4ec0a0d4c9731bdbe5f6b','tom');
console.log(user);
I don't know why variable "a" always returns undefined, I consoled.log both the ids in the for loop and they display the same thing:
new ObjectId("61f4ec0a0d4c9731bdbe5f6b")
new ObjectId("61f4ec0a0d4c9731bdbe5f6b")
I debugged and checked that it never reaches the statement after the if condition, not sure what's wrong.

Found solution here:
https://whitehorsesblogarchive.wordpress.com/2017/10/15/how-to-compare-mongo-_ids-in-javascript/
Apparently comparing objectIds should be done using equals() instead of "=="

Related

Not able to get the value of a custom attribute in hyperledger-fabric using cid.getAttributeValue function

Other than the 3 attributes hf.EnrollmentId, hf.type and hf.Affiliation, I've created a custom attribute named email and added it as attrs:[{name: 'email', value: rahul18#gmail.com, ecert: true}] and it was successfully added to the attribute list.
In my chaincode, i'm able to get the enrollmentId by using the following command : cid.GetAttributeValue(ctx.GetStub(), "hf.EnrollmentID") but i'm not able to get the email using the same method cid.GetAttributeValue(ctx.GetStub(), "email")
Any help would be appreciated regarding why the first one is working and the second isn't
Does getAttributeValue not support custom made attributes?
Here is an example that may be helpful. A previous stackoverflow contribution helped me with a similar situation. I don't have the link for it right now, but thanks anyway.
First of all, you state that you have added attributes successfully. Here is some code as an example which I had placed in the code file for registering users.
//create user attr array
let registerAttrs = [];
let registerAttribute = {
name: "recycler",
value: config.recycler,
ecert: true,
};
registerAttrs.push(registerAttribute);
const secret = await ca.register({
affiliation: config.affiliation,
enrollmentID: config.recycler,
role: "client",
attrs: registerAttrs,
},
adminUser
);
The contract code is able to find the value of "recycler" using the following code. Of particular importance is the getCurrentUserId() function.
async getCurrentUserId(ctx) {
let id = [];
id.push(ctx.clientIdentity.getID());
var begin = id[0].indexOf("/CN=");
var end = id[0].lastIndexOf("::/C=");
let userid = id[0].substring(begin + 4, end);
return userid;}
async getCurrentUserType(ctx) {
let userid = await this.getCurrentUserId(ctx);
// check user id; if admin, return type = admin;
// else return value set for attribute "type" in certificate;
if (userid == "admin") {
return userid;
}
return ctx.clientIdentity.getAttributeValue(userid);}
The user type returned from the getCurrentUserType function is subsequently examined further up in the contract code, as shown in the following example.
async readTheAsset(ctx, id) {
let userType = await this.getCurrentUserType(ctx);
const buffer = await ctx.stub.getState(id);
const asset = JSON.parse(buffer.toString());
asset.userType = userType;
asset.userID = ctx.clientIdentity.getID();
if (asset.userType === "recycler") {
throw new Error(`The record cannot be read by ${asset.userType} `);
}
return asset;}
I feel sure that this code should solve your issue, as there is a lot of similarity.
const updateObj = {
enrollmentID : userName,
type:'client',
affiliation:'' ,
attrs: [{name: 'email', value: email, ecert: true}, {name: 'orgId', value: orgId, ecert: true}, {name: 'userId', value: userName, ecert: true}] ,
}
const response = await identityService.update(userName, updateObj ,adminUser)
const clientUser = await provider.getUserContext(userIdentity, userName);
const reenrollment = await caClient.reenroll(clientUser,
[{
name: 'email',
optional: false
},
{
name: 'orgId',
optional: false
},
{
name: 'userId',
optional: false
}
]);

NodeJS Mongoose updateOne giving no match every time

I am trying to update a document in mongo with mongoose using updateOne method:
const updateResult = await UserModel.updateOne({
_id: mongoose.Types.ObjectId(userId)
}, {
$set: {
a: 'B'
}
})
userId contains a string of the ID of the user.
I have tried using the following
1. { _id: userId }
2. { email: theEmailOfTheUser }
But still, the updateResult is
n:0, nModified:0, ok:0
So I think it's must be something with the method itself and not in my query.
Also, when I'm trying to find the user using the query below, it can find it:
const user = await UserModel.find({
_id: userId
});
//user is found
Actually mongoose takes care of the $set and you do not have to add it. just:
const updateResult = await UserModel.updateOne({
_id: userId
}, {
a: 'B'
})
but the better solution would to just use findByIdAndUpdate():
const updateResult = await UserModel.findByIdAndUpdate(userId, {
a: 'B'
})

Ignore null or empty values when saving a mongoose array schema type

I have the following schema:
const userSchema = new mongoose.Schema({
email: [{
type: String,
trim: true,
}]
})
When saving a new user,
const user = new User({
email: ["example#example.com", ""]
//or email: ["example#example.com", null]
})
try{
await user.save()
} catch (e) {
console.log(e)
}
This will save both those values (including empty string and null respectively).
Is there a way to save only the proper email value while discarding the empty or null value.
Instead of this:
"email" : [
"example#example.com",
""
],
store only the proper email:
"email" : [
"example#example.com",
],
Currently for other schema fields I am using set. For example, in the user schema above
url: {
type: String,
set: deleteEmpty
}
const deleteEmpty = (v) => {
if(!v) {
return undefined
}
return v
}
This will of course not save the url field at all if the value is empty or null.
Using this method on the email field above however will generate a null value.
Is there a way to store only the proper email value (i.e. "example#example.com" in this case while ignoring the null or empty value.)?
👨‍🏫 I think you can make it something like this code below 👇 with your userSchema:
userSchema.pre('save', function(next) {
this.email = this.email.filter(email => email);
next();
})
The code above ☝️ will igrone all empty or null value in an array. You can try it.
Or the Second Options, you can add required on your email field in your userSchema. It's well looks like this code below: 👇
const userSchema = new mongoose.Schema({
email: [{
type: String,
trim: true,
required: true // add some required
}],
});
💡 The code above ☝️, will give you an error if you passing an empty string on your array.
I hope it's can help you 🙏.
You can do the following to achieve what you want.
var arr = ['example#example.com', '']; // variable to keep the an array containing values
var i;
for (i = 0; i < arr.length; i++) {
if (arr[i] == null || arr[i] == '') {
arr.slice(i); // remove null or '' value
}
}
console.log('normalized array: ', arr);
// schema code
const user = new User({
email: arr
})
try{
await user.save()
} catch (e) {
console.log(e)
}
Good luck, I hope I answered your question.
If anyone has one or more fields that takes array value and wants to check for each field, I recommend using a middleware on the pre save hook.
supplierSchema.pre('save', normalizeArray)
const normalizeArrray = function(next) {
//take the list of object feilds and for each field check if it is array
Object.keys(this.toObject()).forEach((field) => {
if(Array.isArray(this[field])) {
//removes null or empty values
this[field] = this[field].filter(field => field)
}
})
next()
}
This just builds on the answer already approved above.
Simply set the default value of the field you wish to ignore, if empty, to undefined. Also, set required to false.
Use the code below:
const userSchema = new mongoose.Schema({
email: [{
type: String,
trim: true,
required: false,
default: undefined
}]
})

Check if ID exists in a collection with mongoose

For instance, I have a collection User:
var mongoose = require('mongoose');
var UserSchema = new mongoose.Schema({
email: String,
googleId: String,
facebookId: String,
displayName: String,
active: Boolean
});
module.exports = mongoose.model('User', UserSchema);
And then I have an ID:
var userID = "some-user-id"
What is the right way to just check if this id exists in the User collection. I don't need it to read the file or return it, I just need the true or false value.
Here is one way to achieve it:
User.findOne({
_id: userID
}, function (err, existingUser) {
But is there faster and more efficient way?
Use count rather than findOne.
This will (under the hood) cause mongoose to use find : http://docs.mongodb.org/manual/reference/method/db.collection.count
findOne() will read + return the document if it exists
On the other hand, find() just returns a cursor (or not) and only reads the data if you iterate over the cursor.
So in our case, we're not iterating over the cursor, merely counting the results returned.
User.countDocuments({_id: userID}, function (err, count){
if(count>0){
//document exists });
}
});
You can now use User.exists() as of September 2019 like so:
const doesUserExit = await User.exists({ _id: userID });
From the docs:
Under the hood, MyModel.exists({ answer: 42 }) is equivalent to
MyModel.findOne({ answer: 42 }).select({ _id: 1 }).lean().then(doc =>
!!doc)
The accepted answer is fine for small collections.
A faster way on larger collections is to simply use this:
const result = await User.findOne({ _id: userID }).select("_id").lean();
if (result) {
// user exists...
}
// or without "async/await":
User.findOne({ _id: userID }).select("_id").lean().then(result => {
if (result) {
// user exists...
}
});
It won't return all fields. I believe they are currently working on a new feature to support what you (and I) want.
In the meantime you could create a plugin, very simple and reusable.
Create an any.js file with this code:
module.exports = function any(schema, options) {
schema.statics.any = async function (query) {
const result = await this.findOne(query).select("_id").lean();
return result ? true : false;
};
}
Then in your model you do this:
var mongoose = require('mongoose');
const any = require('./plugins/any'); // I'm assuming you created a "plugins" folder for it
var UserSchema = new mongoose.Schema({
email: String,
googleId: String,
facebookId: String,
displayName: String,
active: Boolean
});
UserSchema.plugin(any);
module.exports = mongoose.model('User', UserSchema);
...and use it like this:
const result = await User.any({ _id: userID });
if (result) {
// user exists...
}
// or without using "async/await":
User.any({ _id: userID }).then(result => {
if (result) {
// user exists...
}
});
OR you can simply use exists function, without making any async/await:
myData = {_id: userID};
User.exists(myData,(error, result)=>{
if (error){
console.log(error)
} else {
console.log("result:", result) //result is true if myData already exists
}
});
You can play with the result now!
User.exists({ _id: userID }).then(exists => {
if (exists) {
res.redirect('/dashboard')
} else {
res.redirect('/login')
}
})
More info can be found at Mongoose docs.
The accepted answer is excellent, but I would really recommend using estimatedDocumentCount() if you are searching existing document by an indexed property (like _id of X).
On the other hand, this should actually work better and is cleaner.

mongoose recursive populate

I have been searching for a while and I didn't find any good answer. I have n-deep tree that I am storing in DB and I would like to populate all parents so in the end I get the full tree
node
-parent
-parent
.
.
-parent
So far I populate to level 2, and as I mentioned I need to get to level n.
Node.find().populate('parent').exec(function (err, items) {
if (!err) {
Node.populate(items, {path: 'parent.parent'}, function (err, data) {
return res.send(data);
});
} else {
res.statusCode = code;
return res.send(err.message);
}
});
you can do this now (with https://www.mongodb.com/blog/post/introducing-version-40-mongoose-nodejs-odm)
var mongoose = require('mongoose');
// mongoose.Promise = require('bluebird'); // it should work with native Promise
mongoose.connect('mongodb://......');
var NodeSchema = new mongoose.Schema({
children: [{type: mongoose.Schema.Types.ObjectId, ref: 'Node'}],
name: String
});
var autoPopulateChildren = function(next) {
this.populate('children');
next();
};
NodeSchema
.pre('findOne', autoPopulateChildren)
.pre('find', autoPopulateChildren)
var Node = mongoose.model('Node', NodeSchema)
var root=new Node({name:'1'})
var header=new Node({name:'2'})
var main=new Node({name:'3'})
var foo=new Node({name:'foo'})
var bar=new Node({name:'bar'})
root.children=[header, main]
main.children=[foo, bar]
Node.remove({})
.then(Promise.all([foo, bar, header, main, root].map(p=>p.save())))
.then(_=>Node.findOne({name:'1'}))
.then(r=>console.log(r.children[1].children[0].name)) // foo
simple alternative, without Mongoose:
function upsert(coll, o){ // takes object returns ids inserted
if (o.children){
return Promise.all(o.children.map(i=>upsert(coll,i)))
.then(children=>Object.assign(o, {children})) // replace the objects children by their mongo ids
.then(o=>coll.insertOne(o))
.then(r=>r.insertedId);
} else {
return coll.insertOne(o)
.then(r=>r.insertedId);
}
}
var root = {
name: '1',
children: [
{
name: '2'
},
{
name: '3',
children: [
{
name: 'foo'
},
{
name: 'bar'
}
]
}
]
}
upsert(mycoll, root)
const populateChildren = (coll, _id) => // takes a collection and a document id and returns this document fully nested with its children
coll.findOne({_id})
.then(function(o){
if (!o.children) return o;
return Promise.all(o.children.map(i=>populateChildren(coll,i)))
.then(children=>Object.assign(o, {children}))
});
const populateParents = (coll, _id) => // takes a collection and a document id and returns this document fully nested with its parents, that's more what OP wanted
coll.findOne({_id})
.then(function(o){
if (!o.parent) return o;
return populateParents(coll, o.parent))) // o.parent should be an id
.then(parent => Object.assign(o, {parent})) // replace that id with the document
});
Another approach is to take advantage of the fact that Model.populate() returns a promise, and that you can fulfill a promise with another promise.
You can recursively populate the node in question via:
Node.findOne({ "_id": req.params.id }, function(err, node) {
populateParents(node).then(function(){
// Do something with node
});
});
populateParents could look like the following:
var Promise = require('bluebird');
function populateParents(node) {
return Node.populate(node, { path: "parent" }).then(function(node) {
return node.parent ? populateParents(node.parent) : Promise.fulfill(node);
});
}
It's not the most performant approach, but if your N is small this would work.
Now with Mongoose 4 this can be done. Now you can recurse deeper than a single level.
Example
User.findOne({ userId: userId })
.populate({
path: 'enrollments.course',
populate: {
path: 'playlists',
model: 'Playlist',
populate: {
path: 'videos',
model: 'Video'
}
}
})
.populate('degrees')
.exec()
You can find the official documentation for Mongoose Deep Populate from here.
Just don't :)
There is no good way to do that. Even if you do some map-reduce, it will have terrible performance and problems with sharding if you have it or will ever need it.
Mongo as NoSQL database is really great for storing tree documents. You can store whole tree and then use map-reduce to get some particular leafs from it if you don't have a lot of "find particular leaf" queries. If this doesn't work for you, go with two collections:
Simplified tree structure: {_id: "tree1", tree: {1: [2, {3: [4, {5: 6}, 7]}]}}. Numbers are just IDs of nodes. This way you'll get whole document in one query. Then you just extract all ids and run second query.
Nodes: {_id: 1, data: "something"}, {_id: 2, data: "something else"}.
Then you can write simple recurring function which will replace node ids from first collection with data from second. 2 queries and simple client-side processing.
Small update:
You can extend second collection to be a little more flexible:
{_id: 2, data: "something", children:[3, 7], parents: [1, 12, 13]}
This way you'll be able to start your search from any leaf. And then, use map-reduce to get to the top or to the bottom of this part of tree.
This is a more straight forward approach to caub's answer and great solution. I found it a bit hard to make sense of at first so I put this version together.
Important, you need both 'findOne' and 'find' middleware hooks in place for this solution to work. *
* Also, the model definition must come after the middleware definition *
const mongoose = require('mongoose');
const NodeSchema = new mongoose.Schema({
children: [mongoose.Schema.Types.ObjectId],
name: String
});
const autoPopulateChildren = function (next) {
this.populate('children');
next();
};
NodeSchema
.pre('findOne', autoPopulateChildren)
.pre('find', autoPopulateChildren)
const Node = mongoose.model('Node', NodeSchema)
const root = new Node({ name: '1' })
const main = new Node({ name: '3' })
const foo = new Node({ name: 'foo' })
root.children = [main]
main.children = [foo]
mongoose.connect('mongodb://localhost:27017/try', { useNewUrlParser: true }, async () => {
await Node.remove({});
await foo.save();
await main.save();
await root.save();
const result = await Node.findOne({ name: '1' });
console.log(result.children[0].children[0].name);
});
I tried #fzembow's solution but it seemed to return the object from the deepest populated path. In my case I needed to recursively populate an object, but then return the very same object. I did it like that:
// Schema definition
const NodeSchema = new Schema({
name: { type: String, unique: true, required: true },
parent: { type: Schema.Types.ObjectId, ref: 'Node' },
});
const Node = mongoose.model('Node', NodeSchema);
// method
const Promise = require('bluebird');
const recursivelyPopulatePath = (entry, path) => {
if (entry[path]) {
return Node.findById(entry[path])
.then((foundPath) => {
return recursivelyPopulatePath(foundPath, path)
.then((populatedFoundPath) => {
entry[path] = populatedFoundPath;
return Promise.resolve(entry);
});
});
}
return Promise.resolve(entry);
};
//sample usage
Node.findOne({ name: 'someName' })
.then((category) => {
if (category) {
recursivelyPopulatePath(category, 'parent')
.then((populatedNode) => {
// ^^^^^^^^^^^^^^^^^ here is your object but populated recursively
});
} else {
...
}
})
Beware it's not very efficient. If you need to run such query often or at deep levels, then you should rethink your design
Maybe a lot late for that but mongoose has some documentation on this :
Ancestors Tree Array
Materialized Path Tree Array
I think the first one is more appropriate to you as you are looking to populate parents.
With that solution, you can with one regex query, search all the documents matching your designered output tree.
You would setup documents with this Schema :
Tree: {
name: String,
path: String
}
Paths field would be the absolute path in your tree :
/mens
/mens/shoes
/mens/shoes/boots
/womens
/womens/shoes
/womens/shoes/boots
For example you could search all the childrens of your node '/mens/shoes' with one query :
await Tree.find({ path: /^\/mens/shoes })
It would return all the documents where the path starts with /mens/shoes :
/mens/shoes
/mens/shoes/boots
Then you'd only need some client-side logic to arrange it in a tree structure (a map-reduce)

Resources