Issue in saving registered user to MongoDB in Node JS - Express application - node.js

I am new to Node.js and trying to create a chat application program. For that I have created a Signup registration form with express framework. The data will be saved in MongoDB. Application uses passport middleware signup functionality. Issue is when submitting a new user for second time I am not able to see the data in mongoDB, instead i can see only the first data. I set the mongodb debug option to true, after submitting the form, user submitted data will be seen through console.
Please see the github code which i created: https://github.com/Deepesh316/jabarchat
Please see the mongodb user details getting saved data code: passport-local.js
passport.use('local.signup', new LocalStrategy({
usernameField: 'email',
passwordField: 'password',
passReqToCallback: true
}, (req, email, password, done) => {
User.findOne({'email': email}, (err, user) => {
// Network or Internet connection error
if(err) {
return done(err);
}
if(user) {
return done(null, false, req.flash('error', 'User with email already exist'));
}
// Creating new instance of user and save it to database
const newUser = new User();
newUser.username = req.body.username;
newUser.email = req.body.email;
newUser.password = newUser.encryptPassword(req.body.password);
newUser.save((err) => {
done(null, newUser);
});
});
}));
Below is the code snippet for Model:
const mongoose = require('mongoose');
const bcrypt = require('bcrypt-nodejs');
const userSchema = mongoose.Schema({
username: { type: String, unique: true },
fullname: { type: String, unique: true, default: ''},
email: { type: String, unique: true },
password: { type: String, default: ''},
userImage: { type: String, default: 'default.png'},
facebook: { type: String, default: ''},
fbTokens: Array,
google: { type: String, default: ''},
googleTokens: Array
});
userSchema.methods.encryptPassword = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(10), null);
};
userSchema.methods.validUserPassword = function(password) {
return bcrypt.compareSync(password, this.password);
};
module.exports = mongoose.model('User', userSchema);

The error message is saying that there's already a record with null as the fullname. In other words, you already have a user without an fullname.
The relevant documentation for this:
If a document does not have a value for the indexed field in a unique index, the index will store a null value for this document. Because of the unique constraint, MongoDB will only permit one document that lacks the indexed field. If there is more than one document without a value for the indexed field or is missing the indexed field, the index build will fail with a duplicate key error.
You can combine the unique constraint with the sparse index to filter these null values from the unique index and avoid the error.
Sparse indexes only contain entries for documents that have the indexed field, even if the index field contains a null value.
In other words, a sparse index is ok with multiple documents all having null values.
Check with mydb.users.getIndexes() if this is the case and manually remove the unwanted index with mydb.users.dropIndex()

Related

Custom Validation on a field that checks if field already exists and is active

I have a mongodb Collection "Users" having "Name", "Email", "Active" fields.
I want to add a validation that for every document email should be unique. However if a document is invalid i.e Active is false then the email can be accepted.
Here is the model
email: { type: String, validate: {
validator: function(v) {
return new Promise((resolve, reject)=> {
console.log("validating email")
const UserModel = mongoose.model('User');
UserModel.find({email : v, active: true}, function (err, docs)
{
if (!docs.length){
resolve();
}else{
console.log('user exists: ',v);
reject(new Error("User exists!"));
}
});
})
},
message: '{VALUE} already exists!'
}
},
name: {
type: String,
required: true
},
active: {
type: Boolean,
default: true
}
Problem is whenever i do any updation on this model then this validation is called.
So if i update the name then also this validation is called and it gives the error that email already exists.
How do I add a validation on email field so if someone adds a new entry to database or updates email it checks in database if existing user has same email id and is active?
I would first call Mongoose findOne function if the User is already registered the Mongo DB, for example;
let foundUser = await User.findOne({email});
if (!foundUser) {
// update user, create user etc.
...
}
I think it is better to not use logic inside the Mongoose document object. Maybe there is a way to achieve it but I prefer to do these validations in the code, not in the document, it is just my preference.
Also you can try making email unique as follows:
email: {
type: String,
unique: true
}
I'd use unique compound index, instead of having one more additional query to your db. Your code would look like this:
const schema = = new Schema(...);
schema.index({email: 1, active: 1}, {unique: true});
Mongo itself will reject your documents and you can catch it in your code like this:
const {MongoError} = require('mongodb'); // native driver
try {
await model.updateOne(...).exec(); // or .save()
} catch (err) {
//11000 is error code for unique constraints
if (err instanceof MongoError && err.code === 11000)
console.error('Duplicate email/active pair');
}

Initialize collection with data in mongoDB using mongoosejs

I am trying to implement a learning platform where users answer questions and get points. I want in the user profile/collection to hold the questions and the answers as well as the number of tries the users took to get the correct answer.
My mongoose schema is the following:
username: { type: String, lowercase: true, unique: true },
hash: String,
salt: String,
realname : String,
score: Number,
CourseFinished: Boolean,
questions: [{
question : String,
answer : String,
noOfTries: Number
}]
The route that creates the user is this one:
router.post('/register', function(req, res, next){
if(!req.body.username || !req.body.password){
return res.status(400).json({message: 'Please fill out all fields'});
}
var user = new User();
user.username = req.body.username;
user.setPassword(req.body.password);
user.questions.question = "What is the language used to write websites?"; //this one doesn't work
user.questions.answer.push("HTML"); //this one doesn't work either
user.save(function (err){
if(err){ return next(err); }
return res.json({token: user.generateJWT()})
});
});
How can I initialize an array of questions and answers with the creation of the user?

Mongoose - caused by :: 11000 E11000 duplicate key error index?

Why do I get this duplicate error - Error creating new user: WriteError({"code":11000,"index":0,"errmsg":"insertDocument :: caused by :: 11000 E11000 duplicate key error index?
All the provided fields are not empty at all.
Schema:
// Declare schema
var userSchema = new mongoose.Schema({
username: {type: String, required: true, index: {unique: true}},
password: {type: String, required: true},
created_on: {type: Date, default: Date.now}
});
Post:
// Create - POST
// Create the first method of the API : POST used to create a new user.
router.post("/", function(req, res, next) {
// Get values from POST request
var username = req.body.username;
var password = req.body.password;
console.log(req.body); // { username: 'tealou', password: 'test123' }
// Create new user document
User.create({
username: username,
password: password
}, function(err, user) {
console.log(user); // undefined
if (err) {
console.log("Error creating new user: " + err);
res.send("Error creating new user.");
} else {
console.log("POST creating new user: " + username);
res.json(user);
}
})
});
Error:
Error creating new user:
WriteError({"code":11000,"index":0,"errmsg":"insertDocument :: caused
by :: 11000 E11000 duplicate key error index: iotdb.users.$name_1 dup
key: { : null
}","op":{"username":"tealou","password":"$2a$10$7mPGND2FRuJDGnXaVTnkru2.xsGn2Ksf8veBKur4ouD9VUNj60RaC","_id":"5786020088245d33140d6f94","created_on":"2016-07-13T08:55:28.279Z","__v":0}})
any ideas?
You initially had a field called name in your schema, that was set to unique.
How do I know? Because of the error telling me so:
duplicate key error index: **iotdb.users.$name_1**
You renamed the field to username, but didn't remove the old index. By default, MongoDB will set the value of a non-existent field to null in that case.
Relevant documentation here:
If a document does not have a value for the indexed field in a unique index, the index will store a null value for this document. Because of the unique constraint, MongoDB will only permit one document that lacks the indexed field.
To solve this, you need to remove the index for the renamed name field.
Dropping the collection and allowing my code to recreate it, is what worked for me.

Validation on user inputs with MongoDB and mongoose?

I would an unified method to validate my schemas assuming a user input, so not only apply the built-in validation on save/update, but also on find(), etc..
var User = mongoose.model("User", new Schema({
name: {type: String, minlength: 5, maxlength: 128, required: true, unique: true});
}));
What I want is to run validators every time before I run the queries with mongoose, to assure that the user inputs comply with the global schema rules.
Something like that in my route:
var username = $.get["username"], //An input from GET querystring
User = mongoose.model("User");
User.validate({name: username}, function(err) {
if (err) return console.log("not valid input"); //i.e. too short
//run query if valid
});
Is there a plugin (assumed that I'm not using Express) or maybe other already included in mongoose for that?
Documentation: http://mongoosejs.com/docs/validation.html
It is supported in mongoose by default. If you are looking for generic validation before each save operation you can specify the field to be validated path and the validation validate(function(valueEntered, howToRespond). If the validation is not passed the error will be thrown as shown in the example below.
Example: Using bluebird for sake of convenience. The following snippet validates the email, before every save operation.
var mongoose = require('bluebird').promisifyAll(require('mongoose'));
var Schema = mongoose.Schema;
var UserSchema = new Schema({
name: String,
email: {
type: String,
lowercase: true
},
password: String,
});
UserSchema
.path('email')
.validate(function(value, respond) {
var self = this;
return this.constructor.findOneAsync({ email: value })
.then(function(user) {
if (user) {
if (self.id === user.id) {
return respond(true);
}
return respond(false);
}
return respond(true);
})
.catch(function(err) {
throw err;
});
}, 'The specified email address is already in use.');

Can a Schema use it's own model to validate?

For example, say I have a user schema, and I want to validate that the username is unique before even attempting to save the user to the database.
...
UserSchema.path('username')
.validate(function (value, respond) {
User.findOne({ username: this.username }) // This isn't valid.
.lean()
.select('_id')
.exec(function (err, user) {
if (err) {
winston.warn('User_username: Error looking for duplicate users');
respond(false);
}
// If a user was returned, then the user is non-unique!
if (user) {
respond(false);
}
respond(true);
});
});
...
var User = mongoose.model('User', UserSchema);
I know I could use mongoose.model('User').findOne(...) but that just seems a bit silly, is there no better way to do it?
You can create an unique index in your schema by setting unique: true. This will make use of the unique index option that is available in mongodb. Here is an example snippet from one of my models using this option:
// The (generated) uniform resource locator
url: {
// ... which is required ...
required: true,
// ... which is an unique index ...
unique: true,
// ... and is a string.
type: String
}
Compound key from comments:
Schema.index({ username: 1, accountCode: 1 }, { unique: true })

Resources