I'm building an Express/Node application and I'm trying build a login controller. When the request comes through to /login, I am able to find the user details in my Mongo collection with Mongoose' model.findOne, but the problem is that what the function returns is not all that is in the mongo document.
Querying the data through Robo 3T returns all the information
Here is my model
{
firstname: String,
surname: String,
fullname: String,
firthDate: Date,
identityType: String,
identityNumber: String,
address: {
residential: Object,
business: Object
},
contact: {
email: String,
cellphone: String,
home: String,
business: String,
fax: String
},
compliance: {
type: String,
value: String
},
account: {
type: String,
username: String,
password: String,
masala: String
},
created: Date,
updated: Date
}
Here is my controller:
user.findOne({"account.username": username}, (err, doc) => {
if (!err) {
// found user. Compare passwords and return JWT;
console.log(doc);
bcrypt.compare(password, doc.account.password, (err, isValid) => {
// I get an error here : Cannot read property password of undefined.
if (!err) {
if (isValid) {
// generate jwt and send back to user;
}
} else {
// invalid password provided;
}
});
} else {
}
});
I get an error: Cannot read property "password of undefined.
And this is the response that is in "doc" :
{
firstname: "hello",
surname: "world",
fullname: "hello world",
firthDate: "01 January 1970",
identityType: "idnumber",
identityNumber: "12345",
address: {
residential: {},
business: {}
},
contact: {
email: "",
cellphone: "",
home: "",
business: "",
fax: ""
},
compliance: {
type: "",
value: ""
}
}
It looks like the "doc.account" data is not returned as part of the response from the findOne function. I don't know why because I am using the data in the "doc.account" to search in the first place.
Your account schema is wrong.
For nested objects, it should be like this.
acount:{
username: {type:String},
password: {type:String},
}
Now this will be considered an object with properties username and password
Related
I am using the mongoose pre hook for findOneAndUpdate. I went through the documentation to understand better it's usage. I would like to update the password field before it saves to DB. However, I am not getting the disired result - nothing gets changed. What would be the right approach for using the findOneAndUpdate pre hook to modify a certain field in the doc?
Actual Document
{
_id: new ObjectId("622457f5555562da89b7a1dd"),
id: '5982ca552aeb2b12344eb6cd',
name: 'Test User',
configuration: [
{
email: 'test2#gmail.com',
password: 'p#ssw0rd',
_id: new ObjectId("9473l58f2ad34efb816963dd"),
},
{
email: 'test3#gmail.com',
password: 'trUstN0oNe',
_id: new ObjectId("8674884cec1877c59c8838e0")
}
],
__v: 0
}
Desired Document
{
_id: new ObjectId("622457f5555562da89b7a1dd"),
id: '5982ca552aeb2b12344eb6cd',
name: 'Test User',
configuration: [
{
email: 'test2#gmail.com',
password: '0f359740bd1cda994f8b55330c86d845',
_id: new ObjectId("9473l58f2ad34efb816963dd"),
},
{
email: 'test3#gmail.com',
password: '3dba7872281dfe3900672545356943ce',
_id: new ObjectId("8674884cec1877c59c8838e0")
}
],
__v: 0
}
Code:
const UserSchema = new Schema({
id: {
type: String,
required: [true, "'id' value is required"]
},
name: {
type: String,
required: [true, "'name' value is required"]
},
configuration: [ConfigModel.schema]
});
const ConfigSchema = new Schema({
email: {
type: String,
required: [true, "Email is required"]
},
password: {
type: String,
required: [true, "Password is required"]
}
});
UserSchema.pre('findOneAndUpdate', async function(next) {
const docToUpdate = await this.model.findOne(this.getQuery());
docToUpdate.configuration.forEach((item,i) => {
docToUpdate.configuration[i].password = md5(item.password);
});
return next();
});
You are missing the .save() document command after changing the information inside the document, because you are only using findOne
const docToUpdate = await this.model.findOne(this.getQuery());
docToUpdate.botconfiguration.forEach((item,i) => {
docToUpdate.configuration[i].password = md5(item.password);
});
await docToUpdate.save() // <---- this line
You dont need the updateMany() here because the ConfigSchema is nested inside the user collection
in userModel you read configuration from ConfigModel so you have to modify the config model not user model it just read and populate the data from config model.
I changed two properties in my User.model:
address: [
{
street: String,
number: Number,
city: String,
country: String
}],
phone: [
{
landLine: String,
mobilePhone: String
}]
to only a object:
address:{
street: String,
number: Number,
city: String,
country: String
},
phone:{
landLine: String,
mobilePhone: String
}
However, there are still some users which holds an array on it. So I created an script to update those users, but when using User.find({}), the phone and address property are undefined. I guess that is because that there are not valid with the current model.
Since user.address and user.phone are both undefined, the data object will only contain 2 empty objects for phone and address which results in users without address and phone.
This is the code which I use for getting and updating the users:
function updateUser(id, data){
return new Promise( (resolve, reject) =>{
User.findByIdAndUpdate(
{_id: id},
{
$set: data
}
).exec( (err, res) => {
if(err) return reject(err);
return resolve();
})
})
}
module.exports.updateUsers = (req, res) => {
Users.find({}).exec( (err, users) => {
//For testing, update only 1 user
//User.find({username: 'some#emailaddress'}).exec( (err, users) => {
if(err) return res.status(500).json({message: "Error getting users"})
return blueBird.mapSeries(users, (user) => {
let data = {
address: {},
phone: {}
}
if(user.address && user.address.length > 0){
data.address = user.address[0];
}
if(user.phone && user.phone.length > 0){
data.phone = user.phone[0];
}
return updateUser(user._id, data);
})
.then(() => {
return res.status(200).json({ status: true, message: "Users are updated" });
})
.catch((e) => {
return res.status(500).json({ status: false, message: "Some error occured while code execute.", error: e });
})
})
}
How can I get the current data from the model without validation from the model?
I think you should revert your address and phone Schema, and create temporary fields (like tmpAddress and tmpPhone) with you new Object schema like this :
address: [{
street: String,
number: Number,
city: String,
country: String
}],
phone: [{
landLine: String,
mobilePhone: String
}],
tmpAddress: {
street: String,
number: Number,
city: String,
country: String
},
tmpPhone: {
landLine: String,
mobilePhone: String
}
And then you save your new fields like this :
data.tmpAddress = user.address[0];
data.tmpPhone = user.phone[0];
Once this is done, you can rewrite your address and phone Fields with Object Schema, copy tmpAddress -> address and tmpPhone -> phone, and delete tmpAddress and tmpPhone fields.
Hope it helps.
I am attempting to findOneAndUpdatea string-based token on a User model. And
I receive the error:
Cast to ObjectId failed for value "{ passwordResetToken: '4946d72f19b9649d3f306a0f5be59005c884ae453fc049c7',
passwordResetExpires: { '$gt': 1543196590882 } }" at path "_id" for model "User"
the document is stored like so:
{
"_id": {
"$oid": "5bfb424da0cc0923f05b67f1"
},
"local": {
"email": "XXXXXXXXXXXXXXXXX",
"password": "XXXXXXXXXXXXXXXXX"
},
"isVerified": false,
"method": "local",
"__v": 0,
"passwordResetExpires": {
"$date": "2018-11-26T02:41:17.851Z"
},
"passwordResetToken": "4946d72f19b9649d3f306a0f5be59005c884ae453fc049c7"
}
and I query the document like so:
req.params.token = "4946d72f19b9649d3f306a0f5be59005c884ae453fc049c7"
User.findByIdAndUpdate({
'passwordResetToken': req.params.token,
'passwordResetExpires': { $gt: Date.now() }
},
{
'local.password' : req.body.password,
'passwordResetExpires' : null,
'passwordResetToken' : null
}, {new: true})
.then(user => {
res.send(user);
})
.catch(err => next(err))
This is my current Schema:
var userSchema = mongoose.Schema({
method: {
type: String,
enum: ['local', 'google', 'facebook']
},
local: {
email: {
type: String,
lowercase: true
},
password: String,
},
google: {
id: String,
email: {
type: String,
lowercase: true
},
name: String,
token: String
},
facebook: {
id: String,
name: String,
token: String
},
isVerified: {
type: Boolean,
default: false,
required: true
},
passwordResetToken: String,
passwordResetExpires: Date
});
I guess mongoose Is attempting to cast this hex string into a _id value? Is there some way to prevent mongoose from casting the string into an ObjectId Type?
In mongoose, if you use findByIdAndUpdate(), you have to provide a value that is the objectID. So, in your case, it tries to find an Object ID but cannot and hence you get an error. Something more appropriate to your use case would be findOneAndUpdate(). Here, you are free to use other parameters.
Using mongoose against mongodb 2.6 - another issue raised that sounds similar to mine;
https://github.com/LearnBoost/mongoose/issues/1677
I have this piece of code:
$addToSet: {
invite_list: {
$each : [
{ email: 'test#test.com' },
{ email: 'test#test.com' },
{ email: 'test#test.com' },
{ email: 'test#test.com' }]
}
}
which should only store one item but instead its storing 4!
However, changing the query to this
$addToSet: {
invite_list: {
$each : [
'test#test.com',
'test#test.com',
'test#test.com',
'test#test.com' ]
}
}
returns one item, as expected.
Model schema field:
invite_list: [{email: {type: String, trim: true}}],
The query looks like this;
UserModel.findOneAndUpdate(
{
_id: req.params.id,
},
{
$addToSet: {invite_list: { $each : [{ email: 'test#test.com' },
{ email: 'test#test.com' },
{ email: 'test#test.com' },
{ email: 'test#test.com' }] }}
}, function (err, user) {
// morel logic here...
return res.status(200).json(user);
});
http://docs.mongodb.org/manual/reference/operator/update/addToSet/
Is there something Im missing.
Thanks.
J
After reading this, it got me thinking;
Stop Mongoose from creating _id property for sub-document array items
Found a fix;
Model now looks like this;
var subSchema = new mongoose.Schema({email: {type: String, trim: true, _id: false}},{ _id : false })
invite_list: [subSchema],
Works as expected...
J
I've created a Mongoose Schema that looks like this:
var UserSchema = new Schema({
user: [{
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
index: { unique: true }
},
password: {
type: String,
required: true
},
mobile: Number
}],
account: [{
locked: {
type: Boolean,
default: false
},
accountType: {
type: String,
default: "guest"
},
failedLogins: {
type: Number,
default: 0
}
}],
reset: [{
resetToken: String,
resetExpirey: Date
}],
details: [{
accountCreated: Date,
lastLogin: Date
}]
});
As you can see I've tried to group certain fields. Is this the correct way to do it? I'm now having trouble referencing the fields. I get an error when I try this:
User.create({
user.name : req.body.name,
user.email : req.body.email,
user.password: req.body.password
}, function(err) {
if (err) res.send(err);
});
Error is unexpected token '.' in user.name
Your create statement still needs to be proper javascript, so your left-side object literals will need to be strings.
User.create({
'user.name' : req.body.name,
'user.email' : req.body.email,
'user.password': req.body.password
}, function(err) {
if (err) res.send(err);
});
Furthermore, because Mongoose requires you to have an array instead of allowing proper sub-objects, you'll need to actually insert these as an array.
var user = {user: [{name:req.body.name, email: req.body.email, password: req.body.password}]};
User.create(user) ...
As to the "is it worth it" to do it like this, my opinion is: no. Just put all these things in the root object unless you plan on having more than one user or more than one account in this one document.