How to mimic mongoose 'save' and 'validate' methods - node.js

I have this code
var user = new User({ /** stuff **/ });
user.validate((err) => {
if (err) {
res.json({ success: false, message: 'Invalid input' });
}
else {
// save the sample user
user.save((err) => {
if (err) { throw err; }
res.json({ success: true });
});
}
});
I want to change user to my mock user so code above won't talk to the actual db. How can write custom validate, 'save' methods in such case?
My custom user mock save and validate:
public save(err:any) {
return this;
}
public validate(err:any) {
return this;
}
But the execution just freezes then when I using my custom user. With console.log() I find out that my custom method validate() fired but execution is not continued after probably because missing next() but I have no idea how I can plug it in. Need help.

Related

Cannot set headers after they are sent to the client erro when i ask the same route for 2nd time? (Always)

I am now trying to write a update function in mongoose.
My code is like this
app.patch("/users/:id", async (req, res) => {
let _id = req.params.id;
if (_id.match(/^[0-9a-fA-F]{24}$/)) {
try {
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
new: true,
runValidators: true,
});
if (!user) {
return res.status(404).send("User not found");
}
res.send(user);
} catch (err) {
res.status(400).send(err);
}
}
res.send("error format");
});
As you can see i am using path method to update the obj.
The problem is on this line
if (_id.match(/^[0-9a-fA-F]{24}$/)) {
When ever i try to match the object id, On the 2nd calls I always get this error. Can Someone explain why?
Cannot set headers after they are sent to the client
If I remove this if statement the error goes away.
You forgot to exit from a handler after res.send(user).
if (!user) {
return res.status(404).send("User not found");
}
res.send(user);
return;
Without return your code continues to execute and next line will be res.send("error format");

Mongoose Schema Instance Methods is Not a Function

I've have had a working version of mongoose instance methods working before. I'm not sure what is different about it this time around. The only thing I have done differently this time is that I separated the mongoose connection function outside of the server.js file into a config file that will be imported and call the connect() function.
I will mostly use this instance method in passport with the local strategy to log in the user. When I go to call my instance method on the user instance that was found by the previous UserModel.findOne({ email }) the verify(password) instance method is not called and does not throw any errors.
For testing purposes, I've tried to hard code a UserModel.findOne() right into connection field and I do get a user back. I then decided to call my instance method right off of the returned user instance named verify().
I have also attempted changing the name of the method to comparePassword, I've tried testing with statics to check if it is even called at all (which it wasn't), I've also tried to research other ways to import my schemas and models, and that does not seem to work. I have experimented with Async/Await hasn't changed the output
File: mongo.db.js
const connect = () => {
return new Promise((resolve, reject) => {
mongoose.connect(
config.get('DB.STRING'),
{ useCreateIndex: true, useNewUrlParser: true },
async (err) => {
if (err) reject(err)
resolve()
// TESTING INSTANCE METHODS
await mongoose.connection
.collection('users')
// HARD CODED TEST EMAIL
.findOne({ email: 'braden_feeney#hotmail.com' }, (err, result) => {
if (err) reject(err)
console.log(result)
console.log(result.verify('test1234'))
})
},
)
})
}
const close = () => {
return mongoose.disconnect()
}
export default { connect, close }
File: passport.config.js
passport.use(
new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password',
},
async (email, password, done) => {
try {
// Find the user given the email
const user = await User.findOne({ email })
// If not found
if (!user) return done(null, false)
// Check if the password is correct
const isValididated = await user.verify(password)
// If not matched
if (!isValididated) return done(null, false)
// Return the user
done(null, user)
} catch (error) {
done(error, false)
}
},
),
)
File: users.model.js
const UserSchema = new Schema(
// HIDDEN FOR SECURITY
{ ... },
{ versionKey: false, timestamps: true },
)
// HIDDEN FOR SECURITY - PRE SAVE WORKS AS EXPECTED
UserSchema.pre('save', async function(next) { ... })
// THIS IS THE METHOD THAT SHOWS AS 'Not a Function'
UserSchema.methods.verify = function(password) {
bcrypt.compare(password, this.password, (err, res) => {
if (err) return new Error(err)
return res
})
}
export default model('User', UserSchema)
When I call user.verify(password) I expect to see a Boolean value either get returned from the function.
The actual result is an Error thrown stating
TypeError: user.verify is not a function
This section does not make sense:
async (email, password, done) => {
try {
const user = await User.findOne({ email })
if (user) // <-- if you have a user record
return done(null, false) // <-- you return!
// There was no valid user ... how would there be user.verify?
const isValididated = await user.verify(password)
if (!isValididated) return done(null, false)
done(null, user)
} catch (error) {
done(error, false)
}
}
You seem to return on valid user but calling user.verify when you do not have a user. So it seems your flow is somewhat problematic.
After tinkering around with how mongoose creates instance methods, I tried using another way to make the method work. I had to wrap the bcrypt.compare() in a promise because my code wasn't waiting for a response.
File: users.model.js
UserSchema.method('verify', function(password) {
return new Promise((resolve, reject) => {
bcrypt.compare(password, this.password, (err, res) => {
if (err) reject(err)
resolve(res)
})
})
})
I still prefer the way that is mentioned in my question because I believe it looks cleaner and is not relying on a string to be the method name.
All you have to do is just mark this function with "async".
// THIS IS THE METHOD THAT SHOWS AS 'Not a Function'
UserSchema.methods.verify = async function (password) {
bcrypt.compare(password, this.password, (err, res) => {
if (err) return new Error(err)
return res
})
}
& Another thing that you've to keep in mind is that when you're retrieving user from your DB make sure to use the "await" keyword there as well.
I was getting the same error and then I noticed that I forgot to use the "await" keyword and my code goes to the next line without even waiting for the "user" to arrive from DB and we don't have a user or instance and we're calling "compare" method on that instance which hasn't arrived from DB.
That's why you were getting error that comparePassword is not a function.

Mongoose validate and save code

That is how I validating and saving my user:
var user = new User({ /** from body **/ });
user.validate((err) => {
if (err) {
console.log(err);
res.json({ success: false, message: 'Invalid input' });
}
else {
user.save((err) => {
if (err) { throw err; }
console.log(err);
res.json({ success: true });
});
}
});
Is there a better way of validate and save with mongoose with the less code lines or without if/else?
You can also add your validations inside your schema. I will recommend this way because you'll be able to make custom validations depending of the field.
http://mongoosejs.com/docs/validation.html
Then you'll only need to save your user and check for an error inside the save callback method.

Callback code not executed

Here is my code:
socket.on('add user', function (data) {
d('"add user" event');
d(data, true);
debugger;
UserEngine.login({
username: data.username,
password: data.password
}, function(err, user) {
if (err) {
d('Bad Login. Username: ' + data.username);
return;
}
/*
** Code after this comment is never executed
*/
debugger;
d('Login OK: ' + data.username);
socket.username = data.username;
usernames[data.username] = data.username;
++numUsers;
addedUser = true;
socket.emit('login', {
numUsers: numUsers
});
socket.broadcast.emit('user joined', {
username: socket.username,
numUsers: numUsers
});
return;
});
});
If there is an error in UserEngine.login(...), the If statement before the comment works correctly and the callback returns. But if the method works correctly, the code after the If statement isn't executed.
Why?
Edit:
Here is the code of the UserEngine module:
http://pastebin.com/u2DQJrV3
My guess is that your code is throwing an exception somewhere in your login() method and because much of that code is executed in async callbacks, you may not get any exception info in the console.
Here are several suggestions for debugging this problem:
You need to follow the exact flow through your login() method. If what you say in your question is true, then it isn't calling the callback sometimes and you need to find out why. The simplest way to follow the flow is to insert a uniquely tagged console.log() statement in every branch of the login() method so you can see exactly which way the control flow goes and what is executed and what is not. Once you find that, you can then output various values to see why it goes that way or set an appropriate breakpoint and trace through it.
There also could be an exception of some kind getting thrown in the login() method or in something that it calls. One you get into an async callback, exceptions may not get logged in the console so things could fail silently and the callback could just be skipped without anything showing in the console. If you suspect an exception in a particular place, you can put your own exception handler there and log what the exception is. Remember, each async scope needs its own exception handler - you can't just use one exception handler at the top level. This is a complication of async coding and one reason why using promises instead of plain callbacks is massively more useful because the promise system will catch all async exceptions for you and turn it into a rejected promise with the exception as the reason (so async exceptions don't fail silently).
There is a logic problem in your login() method if it goes does the user.save() branch of code. I've documented that issue below, though I don't think that is your main issue - but it is something to fix.
I'd also suggest you put logging where you call your UserEngine.login() so you are logging ALL possible callback values.
Suggested logging:
UserEngine.login({
username: data.username,
password: data.password
}, function(err, user) {
// =========== Add this ============
console.log("UserEngine.login() callback");
console.log(err, user);
if (err) {
d('Bad Login. Username: ' + data.username);
return;
}
Here's the issue with the user.save() path in your login() method:
In your login() code, as part of the self._verifyToken() function call, you need to change this:
login: function(args, callback) {
/**
* username [String]
* password [String]
*/
var self = this;
if (!args.username || !args.password) {
return callback(Error.genObj(Error.code.MISSING_PARAMS));
}
User.findOne({
username: args.username
}, function(err, user) {
console.log('[Debug] In User.findOne(...) Callback;');
if (err) {
return callback(Error.genObj(Error.code.INTERNAL));
}
if (!user) {
return callback(Error.genObj(Error.code.TOKEN_AUTH_FAILED));
}
if (self._generateHash({ password: args.password, salt: user.salt }) != user.password) {
return callback(Error.genObj(Error.code.TOKEN_AUTH_FAILED));
}
self._verifyToken({
token: user.token,
username: args.username,
key: Config.tokenKey
}, function(err) {
if (err) {
var token = self._generateToken({ username: args.username, key: Config.key });
user.token = token;
user.save(function(err) {
if (err) {
return callback(Error.genObj(Error.code.INTERNAL));
}
return callback(null, user);
});
}
return callback(null, user);
});
});
},
To this:
login: function(args, callback) {
/**
* username [String]
* password [String]
*/
var self = this;
if (!args.username || !args.password) {
return callback(Error.genObj(Error.code.MISSING_PARAMS));
}
User.findOne({
username: args.username
}, function(err, user) {
console.log('[Debug] In User.findOne(...) Callback;');
if (err) {
return callback(Error.genObj(Error.code.INTERNAL));
}
if (!user) {
return callback(Error.genObj(Error.code.TOKEN_AUTH_FAILED));
}
if (self._generateHash({ password: args.password, salt: user.salt }) != user.password) {
return callback(Error.genObj(Error.code.TOKEN_AUTH_FAILED));
}
self._verifyToken({
token: user.token,
username: args.username,
key: Config.tokenKey
}, function(err) {
if (err) {
var token = self._generateToken({ username: args.username, key: Config.key });
user.token = token;
user.save(function(err) {
if (err) {
return callback(Error.genObj(Error.code.INTERNAL));
}
return callback(null, user);
});
} else {
// ======== put this in an else clause ==========
return callback(null, user);
}
});
});
},
The issue is that if you get an error from verifyToken(), then it will start user.save(), but since that's async, it will continue on and execute return callback(null, user) before the user.save() operation is done and then it will call the callback again when user.save() is done.

Mongoose's static method throwing: TypeError: Model.call has no method 'save'

When I send a completely valid JSON body across a route in Express, I get the following error related to a static method of a model in Mongoose.
Product.save({
^
TypeError: Object function model(doc, fields, skipId){
bla bla bla ...
} has no method 'save'
I have the following routing function in Express:
function createNewProduct(req, res){
// Check if the Merchant is authorized
User.checkIfExists(req.body.key, function(isThere, user){
// Check if username is valid
if(isThere === true){
// Check if he has merchant priveleges
if(user.usertype == 'merchant'){
// Check if all the product field are valid
// TODO add a product parameter validator here
// Generate a OriginID
var originId = user.username + Date.now();
// Save the product to the db
Product.createNewProduct(originId, req.body, function(isItCreated, product){
if(isItCreated === true){
res.json(product);
} else {
res.json({
"error":"Problem in database!"
});
}
});
} else {
res.json({
"error": "Not A Merchant"
});
}
} else {
res.json({
"error": "Invalid Username"
});
}
});
}
Following is the implementation of createNewProduct static in Mongoose:
// Static method for creating a product
productSchema.static('createNewProduct', function(originId, reqBody, callback){
Product.save({
"originId": originId,
"name": reqBody.name,
//image: { type: String },
"merchant": reqBody.key,
//shop: String,
"description": reqBody.description,
"cost": reqBody.cost,
"availableNow": true
}, function(err, product){
if(err){
console.log('Error creating new product');
console.log(err);
callback(false);
} else {
callback(true, product);
}
});
});
save is an instance method, so you can only call it on a Product model instance, not the model itself.
To create a new document based on the Product model and save it, call create on the model instead:
Product.create({...}, function(err, product) {...});
Alternatively, you could separately create a new Product model instance and then call save on that:
var product = new Product({...});
product.save(function(err, product) {...});

Resources