Mongoose virtual field not updated - node.js

i created a schema for user like this:
var schema = new Schema({
username: {
type: String,
unique: true,
required: true
},
hashedPassword: {
type: String,
required: true
},
salt: {
type: String,
required: true
}
});
schema.virtual('password')
.set(function(password) {
this._plainPassword = password;
this.salt = Math.random() + '';
this.hashedPassword = this.encryptPassword(password);
})
.get(function() { return this._plainPassword; });
schema.methods.encryptPassword = function(password) {
return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
};
then I'm trying to change the password using the two approaches:
work nice
User.findById('userId..', function(err, user) {
user.password = '456';
user.save(cb);
})
why is this approach not working ?
User.findByIdAndUpdate('userId', {$set: {password: '456'}}, cb)

This happens because Mongoose doesn't apply any of the following on a findByIdAndUpdate() operation:
defaults
setters
validators
middleware
From the docs:
If you need those features, use the traditional approach of first
retrieving the document.
Model.findById(id, function (err, doc) {
if (err) ..
doc.name = 'jason borne';
doc.save(callback);
})

with version 4.0.9+ middleware supports findByIdAndUpdate().
CustomerSchema.pre('findOneAndUpdate', function(next, done) {
console.log("findOneAndUpdate pre middleware!!!");
next();
});

Related

Generate hashed password in findOneAndUpdate

Here is my query for findOneAndUpdate
const { email, password, id } = req.body
Artist.findOneAndUpdate({ _id: id }, { $set: req.body }).then((artist) => {
return res.json({
success: true,
message: "Invitation sent."
});
})
And here is my schema
var artistSchema = new mongoose.Schema({
name: { type: String, default: '' },
password: { type: String, default: '' }
})
artistSchema.pre('findOneAndUpdate', function (next) {
console.log('------------->>>>>> findOneAndUpdate: ');
console.log(this.password) // why undefined?
next();
});
I want to create a hashed password when user update details
const { email, password, id } = req.body;
Artist.findByIdAndUpdate(id, { $set: req.body }).then(artist => {
return res.json({
success: true,
message: "Invitation sent."
});
});
Example with bcrypt
var artistSchema = new mongoose.Schema({
name: { type: String, default: "" },
password: { type: String, default: "" }
});
artistSchema.pre("update", function(next) {
bcrypt.hash(this.password, 10, function(err, hash) {
if (err) return next(err);
this.password = hash;
next();
});
});
let crypto = require('crypto');
let mongoose = require('../mongoose'),
Schema = mongoose.Schema;
Then Schema
let schema = new Schema({
name: {
type: String,
default: ''
},
hashedPassword: {
type: String,
required: true
},
salt: {
type: String,
required: true
}
});
Then methods and virtuals
schema.methods.encryptPassword = function(password){
return crypto.createHmac('sha1', this.salt).update(password).digest('hex');
};
schema.virtual('password').set(function(password){
this._plainPassword = password;
this.salt = Math.random() + '';
this.hashedPassword = this.encryptPassword(password);
}).get(function(){ return this._plainPassword; });
You can check password like that
schema.methods.checkPassword = function(password){
return this.encryptPassword(password) === this.hashedPassword;
};
Export module
module.exports.Artist = mongoose.model('Artist', schema);
Then just save like before
const { email, password, id } = req.body;
Artist.findOneAndUpdate({ _id: id }, { $set: req.body }).then((artist) => {
return res.json({
success: true,
message: "Invitation sent."
});
});
But I sugest you also to use statics. For example:
schema.statics.updateUser = function (data){
// your code
}
And then you can use
Artist.updateUser(req.body).then((res) => {
// response
})
The answer: Writeconsole.log(JSON.stringify(this._update));
My solution for check blank password.
userSchema.pre('findOneAndUpdate', function() {
console.log(JSON.stringify(this._update));
if (this._update.password.length == 0) {
this._update = {
"fullname": this._update.fullname
};
}
else {
this._update = {
"fullname": this._update.fullname,
"password": bcrypt.hashSync(this._update.password, bcrypt.genSaltSync(8), null)
};
}
});

MongoDB + ExpressJS - Observe insertions

I have a rapidly growing collection in my mongodb. I want to take certain actions when new documents are inserted into those collection. How can I observe and then trigger actions, when such a new model is inserted?
I did discover old solutions such as mongo-observer, but those seem to be pretty old and did not work for me.
Can anybody recommend a relatively new and maintained solution?
schema.pre() hooks would do it. Example:
export const schema = new mongoose.Schema({
name: String,
username: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
}
}, { timestamps: { createdAt: "created_at", updatedAt: "updated_at" }
});
schema.pre("save", function (next) {
bcrypt.hash(this.password, 10, (err, hash) => {
this.password = hash;
next();
});
});
schema.pre("update", function (next) {
bcrypt.hash(this.password, 10, (err, hash) => {
this.password = hash;
next();
});
});
You can refer to npm module - mongohooks.
Update:
Adding sample code:
const db = require('mongojs')('mydb', ['members']); // load mongojs as normal
const mongohooks = require('mongohooks');
// Add a `createdAt` timestamp to all new documents
mongohooks(db.members).save(function (document, next) {
document.createdAt = new Date();
next();
});
// Now just use the reqular mongojs API
db.members.save({ name: "Thomas" }, function (error, result) {
console.log("Created %s at %s", result.name, result.createdAt);
});

How to save userId in mongoose hook?

Given yon schema, how do I save userId to createdBy and updatedBy?
This seems like it should be an easy use case. How do I do it?
I'm not sure how to get userId from req.user.id to the model before being written.
// graph.model.js
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var schema = new Schema({
title: String,
createdAt: Date,
createdBy: String,
updatedAt: Date,
updatedBy: String,
});
// This could be anything
schema.pre('save', function (next) {
- if (!this.createdAt) {
this.createdAt = this.updatedAt = new Date;
this.createdBy = this.updatedBy = userId;
} else if (this.isModified()) {
this.updatedAt = new Date;
this.updatedBy = userId;
}
next();
});
Here's the controller code if you're interested:
var Graph = require('./graph.model');
// Creates a new Graph in the DB.
exports.create = function(req, res) {
Graph.create(req.body, function(err, thing) {
if(err) { return handleError(res, err); }
return res.status(201).json(thing);
});
};
// Updates an existing thing in the DB.
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Graph.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.send(404); }
var updated = _.merge(thing, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.json(thing);
});
});
};
The following is just another way of saving userId.
Sample model with createdBy, updatedBy, createdAt, updatedAt fields:
import mongoose from 'mongoose';
const SupplierSchema = new mongoose.Schema(
{
name: {
type: String,
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
updatedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
},
},
{
timestamps: {
createdAt: true,
updatedAt: true,
},
},
);
export default mongoose.model('Supplier', SupplierSchema);
Note that in mongoose starting from version ^4.13.17 you can simply specify timestamps createdAt, updatedAt directly in the schema.
https://mongoosejs.com/docs/4.x/docs/guide.html#timestamps
Then in supplier controller assign req.user._id to the fields createdBy, updatedBy:
import mongoose from 'mongoose';
import { Supplier } from '../models';
exports.create = async (req, res) => {
const supplierToCreate = new Supplier({
_id: new mongoose.Types.ObjectId(),
name: req.body.name,
createdBy: req.user._id,
updatedBy: req.user._id,
});
return supplierToCreate
.save()
.then(() =>
res.status(201).json({
message: 'New supplier is created successfully.',
}),
)
.catch(errSaving => res.status(500).json({ error: errSaving }));
};
You can't access req object inside of mongoose hook.
I think, you should define virtual field with a smart setter instead:
schema.virtual('modifiedBy').set(function (userId) {
if (this.isNew()) {
this.createdAt = this.updatedAt = new Date;
this.createdBy = this.updatedBy = userId;
} else {
this.updatedAt = new Date;
this.updatedBy = userId;
}
});
Now all you have to do is to set modifiedBy field with correct userId value in your controller:
var updated = _.merge(thing, req.body, {
modifiedBy: req.user.id
});

Mongoose .pre('save') does not trigger

I have the following model for mongoose.model('quotes'):
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var quotesSchema = new Schema({
created: { type: String, default: moment().format() },
type: { type: Number, default: 0 },
number: { type: Number, required: true },
title: { type: String, required: true, trim: true},
background: { type: String, required: true },
points: { type: Number, default: 1 },
status: { type: Number, default: 0 },
owner: { type: String, default: "anon" }
});
var settingsSchema = new Schema({
nextQuoteNumber: { type: Number, default: 1 }
});
// Save Setting Model earlier to use it below
mongoose.model('settings', settingsSchema);
var Setting = mongoose.model('settings');
quotesSchema.pre('save', true, function(next) {
Setting.findByIdAndUpdate(currentSettingsId, { $inc: { nextQuoteNumber: 1 } }, function (err, settings) {
if (err) { console.log(err) };
this.number = settings.nextQuoteNumber - 1; // substract 1 because I need the 'current' sequence number, not the next
next();
});
});
mongoose.model('quotes', quotesSchema);
There is an additional Schema for mongoose.model('settings') to store an incrementing number for the incrementing unique index Quote.number im trying to establish. Before each save, quotesSchema.pre('save') is called to read, increase and pass the nextQuoteNumber as this.number to the respectively next() function.
However, this entire .pre('save') function does not seem to trigger when saving a Quote elsewhere. Mongoose aborts the save since number is required but not defined and no console.log() i write into the function ever outputs anything.
Use pre('validate') instead of pre('save') to set the value for the required field. Mongoose validates documents before saving, therefore your save middleware won't be called if there are validation errors. Switching the middleware from save to validate will make your function set the number field before it is validated.
quotesSchema.pre('validate', true, function(next) {
Setting.findByIdAndUpdate(currentSettingsId, { $inc: { nextQuoteNumber: 1 } }, function (err, settings) {
if (err) { console.log(err) };
this.number = settings.nextQuoteNumber - 1; // substract 1 because I need the 'current' sequence number, not the next
next();
});
});
For people who are redirected here by Google, make sure you are calling mongoose.model() AFTER methods and hooks declaration.
In some cases we can use
UserSchema.pre<User>(/^(updateOne|save|findOneAndUpdate)/, function (next) {
But i'm using "this", inside the function to get data, and not works with findOneAndUpdate trigger
I needed to use
async update (id: string, doc: Partial<UserProps>): Promise<User | null> {
const result = await this.userModel.findById(id)
Object.assign(result, doc)
await result?.save()
return result
}
Instead of
async update (id: string, doc: Partial<UserProps>): Promise<User | null> {
const result = await this.userModel.findByIdAndUpdate(id, doc, { new: true, useFindAndModify: false })
return result
}
The short solution is use findOne and save
const user = await User.findOne({ email: email });
user.password = "my new passord";
await user.save();
I ran into a situation where pre('validate') was not helping, hence I used pre('save'). I read that some of the operations are executed directly on the database and hence mongoose middleware will not be called. I changed my route endpoint which will trigger .pre('save'). I took Lodash to parse through the body and update only the field that is passed to the server.
router.post("/", async function(req, res, next){
try{
const body = req.body;
const doc = await MyModel.findById(body._id);
_.forEach(body, function(value, key) {
doc[key] = value;
});
doc.save().then( doc => {
res.status(200);
res.send(doc);
res.end();
});
}catch (err) {
res.status(500);
res.send({error: err.message});
res.end();
}
});

Mongoose - findByIdAndUpdate - doesn't work with req.body

I have a problem with update documents in mongodb over mongoose.
My model bellow:
var mongoose = require('mongoose');
var bcrypt = require('bcrypt-nodejs');
var UserSchema = new mongoose.Schema({
first_name:{
type: String
},
last_name:{
type: String
},
email:{
type: String,
unique: true,
required: true
},
password:{
type: String,
required: true
},
is_active:{
type: Boolean,
default: true
},
last_login:{
type: Date
}
});
module.exports = mongoose.model('User', UserSchema);
Controller put function bellow:
exports.updateUser = function (req, res) {
console.log(req.body);
User.findByIdAndUpdate(req.body.user_id, {$set:req.body}, function(err, result){
if(err){
console.log(err);
}
console.log("RESULT: " + result);
});
res.send('Done')
}
Output on console:
Listening on port 3000... { first_name: 'Michal', last_name: 'Test' }
PUT /api/users/54724d0fccf520000073b9e3 200 58.280 ms - 4
The printed params are provided as form-data (key-value). Looks that is not working at least for me any idea what is wrong here?
You have to use req.params.user_id instead req.body.user_id
exports.updateUser = function (req, res) {
console.log(req.body);
User.findByIdAndUpdate(req.params.user_id,{$set:req.body},{new:true}, function(err, result){
if(err){
console.log(err);
}
console.log("RESULT: " + result);
res.send('Done')
});
};
I found the mistake. Note that I'm calling
req.body.user_id
where should be
req.params.user_id
url is (PUT) http://127.0.0.1:3000/api/users/54724d0fccf520000073b9e3
Further, the req.body would have key value as Text and realized as String object, inside the code. Thus, it is useful to parse the string into JSON using JSON.parse(req.body.user) - while the user is the key and { first_name: 'Michal', last_name: 'Test' } is the value.
console.log(req.body);
var update = JSON.parse(req.body.user);
var id = req.params.user_id;
User.findByIdAndUpdate(id, update, function(err, result){
if(err){
console.log(err);
}
console.log("RESULT: " + result);
res.send('Done')
});
Note: the update value is sent to Mongo DB as
{$set: { first_name : 'Michal`, last_name: 'Test' }
Further reference: Mongoose JS documentation - findByIdAndUpdate

Resources