I'm working on a simple login system for my NodeJS application. For this I have created a structure where one object, a "corporation", holds an array of users. I've done because I plan to use the corporation object to store application session data.
{
"name": "My Corporation",
"prefix": "MYCORP",
"users": [
{
"username": "some#user.com",
"password": "974dae09cd5869958c19e1742117c2f8",
"name": "Freddly the User"
},
{
"username": "other#user.com",
"password": "974dae09cd5869958c19e1742117c2f8",
"name": "Max the Admin"
}
]
}
The problem is when querying after a user (in a log-in scenario) the query, as expected, returns the entire corporation object. Thus I'm exposing all users even though I only want one. As far as security is concerned I guess it isn't a big deal, but I'm more worried about performance. Below is the current query and a very ugly way to delete all users but the one requested.
Ignore the different asserts. Code is very much work-in-progress .. :)
db.collection('kat_corp', function (err, collection) {
try {
assert.equal(null, err);
collection.findOne({
users: {
$elemMatch: {
username: user.username
}
}
}, function (err, result) {
if (err) callback(err, false);
// Delete all other users from the to-be session object
for (var i = 0; i < result.users.length; i++) {
if (result.users[i].username != user.username) {
delete result.users[i];
}
}
// Will be replaced with success callback
console.log(result);
});
} catch (err) {
callback(err, false);
}
});
If you're using MongoDB 2.2 or greater you can just use the "$" positional operator.
The following query worked for me :
db.collection('kat_corp', function (err, collection){
collection.findOne({"users.username":user.username}, {'name':1,'users.$': 1}, console.log)
});
Although I would agree with the other comments that you should probably reconsider your schema...
Related
I have a Documents in a Collection that have a field that is an Array (foo). This is an Array of other subdocuments. I want to set the same field (bar) for each subdocument in each document to the same value. This value comes from a checkbox.
So..my client-side code is something like
'click #checkAll'(e, template) {
const target = e.target;
const checked = $(target).prop('checked');
//Call Server Method to update list of Docs
const docIds = getIds();
Meteor.call('updateAllSubDocs', docIds, checked);
}
I tried using https://docs.mongodb.com/manual/reference/operator/update/positional-all/#positional-update-all
And came up with the following for my Server helper method.
'updateAllSubDocs'(ids, checked) {
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
}
But that throws an error 'foo.$[].bar is not allowed by the Schema'. Any ideas?
I'm using SimpleSchema for both the parent and subdocument
Thanks!
Try passing an option to bypass Simple Schema. It might be lacking support for this (somewhat) newer Mongo feature.
bypassCollection2
Example:
Items.update({ _id: { $in: ids } }, { $set: { "foo.$[].bar": bar } },
{ multi: true, bypassCollection2: true }, function (err, result) {
if (err) {
throw new Meteor.Error('error updating');
}
});
Old answer:
Since you say you need to make a unique update for each document it sounds like bulk updating is the way to go in this case. Here's an example of how to do this in Meteor.
if (docsToUpdate.length < 1) return
const bulk = MyCollection.rawCollection().initializeUnorderedBulkOp()
for (const myDoc of docsToUpdate) {
bulk.find({ _id: myDoc._id }).updateOne({ $set: update })
}
Promise.await(bulk.execute()) // or use regular await if you want...
Note we exit the function early if there's no docs because bulk.execute() throws an exception if there's no operations to process.
If your data have different data in the $set for each entry on array, I think you need a loop in server side.
Mongo has Bulk operations, but I don't know if you can call them using Collection.rawCollection().XXXXX
I've used rawCollection() to access aggregate and it works fine to me. Maybe work with bulk operations.
I'm creating a Loopback application and have created a custom user model, based on built-in User model.
{
"name": "user",
"base": "User",
"idInjection": true,
"properties": {
"test": {
"type": "string",
"required": false
}
},
"validations": [],
"acls": [],
"methods": []
}
Then in boot script I'm creating (if not exists) new user, new role and a roleMapping.
User.create(
{ username: 'admin', email: 'admin#mail.com', password: 'pass' }
, function (err, users) {
if (err) throw err;
console.log('Created user:', users);
//create the admin role
Role.create({
name: 'admin'
}, function (err, role) {
if (err) throw err;
//make user an admin
role.principals.create({
principalType: RoleMapping.USER,
principalId: users.id
}, function (err, principal) {
if (err) throw err;
console.log(principal);
});
});
});
Then in custom remote method I'm trying to get all roles for User, using user's id. Loopbacks' documentation on this topic says that
Once you define a “hasMany” relation, LoopBack adds a method with the relation name to the declaring model class’s prototype automatically. For example: Customer.prototype.orders(...).
And gives this example:
customer.orders([filter],
function(err, orders) {
...
});
But when I am trying to use User.roles() method, (const User = app.models.user;) I get the next error:
TypeError: User.roles is not a function
But when I'm making a remote request http://localhost:9000/api/users/5aab95a03e96b62718940bc4/roles, I get the desired roleMappings array.
So, i would appreciate if someone could help get this data using js. I know I can probably just query the RoleMappings model, but I've wanted to do it the documentation-way.
Loopback documentation suggests to extend the built-in user model
to add more properties and functionalities.
A good practice is creating a model Member that extends the built-in model User. In the new model declare the following relationship:
"relations": {
"roles": {
"type": "hasMany",
"model": "RoleMapping",
"foreignKey": "principalId"
}
}
Now, you can get all the user roles:
user.roles(function (err, roles) {
// roles is an array of RoleMapping objects
})
where user is an instance of Member.
This is an old question, but I faced the same issue and was able to solve it by having the relation Antonio Trapani suggested and accessing the roles like this:
const userInstance = await User.findById(userId);
const roles = await userInstance.roles.find();
Roles is not a function, it is an object. By the way this is using loopback 3.
I am using REST API(express.js and mongodb) and trying to update my document but it's not working. I don't know what is the error but can someone help me out to move forward? I have added my route and controller
Routes:
app.route('/articleupdation')
.post(article.updatearticle);
Controller:
exports.updatearticle = function(req, res) {
Article.findOne({
Username: 'xx',
Email: 'xx#gmail.com',
Info: 'Deactivate',
}, function(err, article) {
if (!err && article) {
article.Info = 'Active';
article.save(function(err) {
if (err) {
console.log('not working');
} else {
console.log('working');
}
});
} else {
console.log('Condtion not matched ');
console.log(err);
}
});
};
Data stored like this
{
"_id": {
"$oid": "5799995943d643600fabd6b7"
},
"Username": "xx",
"Email": "xx#gmail.com",
"Info": "Deactivate",
"Description": "aajdjdjddjdkjddjdjdhdj",
}
Here is what I am trying to achieve; if Username, Email, Info are matched I need to update article.Info = 'Active'; but this is not working, can someone help me out please?
From the looks of it, your query is not matching any documents in the collection hence the statement branch which does the update is not being reached, just the else statement as the returned article is null. You can test this by running the raw query in mongo shell on the underlying collection i.e.
db.articles.findOne({
"Username": "xx",
"Email": "xx#gmail.com",
"Info": "Deactivate"
})
and see if that returns any matching document. If not, check the Info field from the document returned in this query
db.articles.findOne({
"Username": "xx",
"Email": "xx#gmail.com"
})
The best way to go about this within an atomic update that does not require two requests to the server (i.e. one to query the document and the other to write the changes to the server) is to use the findOneAndUpdate api. This will issue a mongodb findAndModify update command which modifies and returns a single document. By default, the returned document does not include the modifications made on the update. To return the document with the modifications made on the update, use the new option.
Thus your refactored code could follow this pattern:
exports.updatearticle = function(req, res) {
Article.findOneAndUpdate(
{ "Username": req.body.username, "Email": req.body.email, "Info": "Deactivate" },
{ "$set": { "Info": "Active" } },
{ "new": true },
function (err, doc) {
if (err) { // err: any errors that occurred
console.log(err);
} else { // doc: the document before updates are applied if `new: false`
console.log(doc); // , the document returned after updates if `new true`
console.log(doc.Info);
}
}
);
};
When a user is created on my app their details are saved on the MongoDB using mongoose. The user schema contains sub-documents and I am trying to access the _id if the sub-document after using the user.save function.
The schema is below:
{
name: String,
email: String,
address: String,
phone:[
{landLine: Number,
mobile: Number}
]
}
I can access the name, email and address easily like so:
console.log(user.name + user.email + user.address)
I tried user.phone._id but it returns undefined. I think because phone is an array of objects.
user.save(function(err) {
if (err)
throw err;
else {
console.log("user ID " + user._id); // SUCCESS!!
console.log("user sub-document ID " + user.phone._id); // UNDEFINED!!
return (null, user);
}
});
How can I access the _id of the sub-document inside the save function right after the user is created and saved into mongoDB?
There are a couple of approaches to getting this information, but personally I prefer the "atomic" modification method using $push.
The actual implementation here is helped by mongoose automatically including an ObjectId value which is "monotonic" and therefore always increasing in value. So this means that my method for handling this even works with a $sort modifier applied to the $push.
For example:
// Array of objects to add
var newNumbers = [
{ "landline": 55555555, "mobile": 999999999 },
{ "landline": 44455555, "mobile": 888888888 }
];
User.findOneAndUpdate(
{ "email": email },
{ "$push": { "phone": { "$each": newNumbers } } },
{ "new": true },
function(err,user) {
// The trick is to sort() on `_id` and just get the
// last added equal to the length of the input
var lastIds = user.phone.concat().sort(function(a,b) {
return a._id > b._id
}).slice(-newnumbers.length);
}
)
And even if you used a $sort modifier:
User.findOneAndUpdate(
{ "email": email },
{ "$push": { "phone": { "$each": newNumbers, "$sort": { "landline": 1 } } } },
{ "new": true },
function(err,user) {
var lastIds = user.phone.concat().sort(function(a,b) {
return a._id > b._id
}).slice(-newnumbers.length);
}
)
That little trick of "sorting" a temporary copy on the _id value means that the "newest" items are always at the end. And you just need to take as many off the end as you added in the update.
The arguable point here is that it's actually mongoose that is inserting the _id values in the first place. So in fact those are being submitted in the request made to the server for each array item.
You "could" get fancy and use "hooks" to record those ObjectId values that were actually added to the new array members in the update statement. But it's really just a simple process of returning the last n "greatest" _id values from the array items anyway, so the more complex approach is not needed.
I have a MongoDb schema like this
var User = new Schema({
"UserName": { type: String, required: true },
"Email": { type: String, required: true, unique: true },
"UserType": { type: String },
"Password": { type: String }
});
I am trying to create a new user
This is done in NodeJs using mongoose ODM
And this is the code for creating:
controller.createUser = function (req, res) {
var user = new models.User({
"UserName": req.body.UserName.toLowerCase(),
"Email": req.body.Email.toLowerCase(),
"UserType": req.body.UserType.toLowerCase()
});
models.User.findOne({ 'Email': user.Email }, function (err, olduser) {
if (!err) {
if (olduser) {
res.send({ 'statusCode': 409, 'statusText': 'Email Already Exists' });
}
else if (!olduser) {
user.setPassword(req.body.Password);
user.save(function (err, done) {
if (!err) {
console.log(user);
res.send({ 'statusCode': 201, 'statusText': 'CREATED' });
}
else {
res.send({ 'Status code': 500, 'statusText': 'Internal Server Error' });
}
});
}
}
else {
res.send({ 'statusCode': 500, 'statusText': 'ERROR' });
}
});
};
The for creating new user,I am giving attributes and values as follows:
{
"UserName": "ann",
"Email": "ann#ann.com",
"UserType": "normaluser",
"Password":"123456"
}
And I am getting error like this:
{"Status code":500,"statusText":"Internal Server Error","Error":{"name":"MongoError","err":"E11000 duplicate key error index: medinfo.users.$UserName_1 dup key: { : \"ann\" }","code":11000,"n":0,"connectionId":54,"ok":1}}
I understand that this error is because UserName is duplicated ,but I haven't set UserName with unique constraint.Whenever I add a new row,I need only email to be unique,UserName can be repeated.How to achieve this??
#ManseUK Is probably right, that looks like UserName is a 'key' - in this case an index. The _id attribute is the "primary" index that is created by default, but mongodb allows you to have multiple of these.
Start a mongo console and run medinfo.users.getIndexes()? Something must have added an index on 'UserName'.
required: true wouldn't do that, but you might have played with other settings previously and the index hasn't been removed?
There should be an index that is blocking.
You can try the db.collection.dropIndex() method
medinfo.users.dropIndexes()
I got the similar issue on my project. I tried to clear out all the documents and the dup issue still keep popping up. Until I dropped this collection and re-start my node service, it just worked.
What I had realized is that my data-structures were changing -- this is where versioning comes in handy.
You may need to get a mongoose-version module, do a thing.remove({}, ...) or even drop the collection: drop database with mongoose
I use RoboMongo for an admin tool (and I highly recommend it!) so I just went in and right-clicked/dropped collection from the console.
If anyone knows how to easily version and/or drop a collection from within the code, feel free to post a comment below as it surely helps this thread ( and I :) ).