mongoose middleware pre update - node.js

I am using
schema.pre('save', function (next) {
if (this.isModified('field')) {
//do something
}
});
but I now need to use this same function isModified in a schema.pre('update' hook, but it does not exists. Does anyone know how I can use this same functionality in the update hook?

Not possible according to this:
Query middleware differs from document middleware in a subtle but
important way: in document middleware, this refers to the document
being updated. In query middleware, mongoose doesn't necessarily have
a reference to the document being updated, so this refers to the query
object rather than the document being updated.
update is query middleware and this refers to a query object which has no isModified method.

#Jeremy I've arrived to the same issue and finally got a workaround:
schema.pre('update', function(next) {
const modifiedField = this.getUpdate().$set.field;
if (!modifiedField) {
return next();
}
try {
const newFiedValue = // do whatever...
this.getUpdate().$set.field = newFieldValue;
next();
} catch (error) {
return next(error);
}
});
Taken from here: https://github.com/Automattic/mongoose/issues/4575
With this, you can check if it comes any update on a field but you can't check if the incoming value is different than stored.
It works perfect for my use case (encrypt password after reset)
I hope it helps.

Schema.pre('updateOne', function (next) {
const data = this.getUpdate()
data.password = 'Teste Middleware'
this.update({}, data).exec()
next()
})
const user = await User.updateOne({ _id: req.params.id }, req.body)
this worked to me

Not quite a solution for OP, but this is what worked for me
Best solution I tried, taken from here
schema.pre("update", function(next) {
const password = this.getUpdate().$set.password;
if (!password) {
return next();
}
try {
const salt = Bcrypt.genSaltSync();
const hash = Bcrypt.hashSync(password, salt);
this.getUpdate().$set.password = hash;
next();
} catch (error) {
return next(error);
}
});

Actually André Rodrigues Answer was almost perfect, but in
Mongoose v5.13.0 you can easily mutate the body itself without executing it by
schema.pre('updateOne', async function () {
let data = this.getUpdate();
const salt = await bcrypt.genSalt();
data.password = await bcrypt.hash(data.password, salt);
});
Welcome as always :)

Well, what i have done in case of password hashing in update method is:
userRouter.patch("/api/users/:id", async (req, res) => {
const updatedAttributes = Object.keys(req.body);
const availableUpates = ["firstName", "lastName", "email", "password"];
const check = updatedAttributes.every((udate) =>
availableUpates.includes(udate)
);
if (!check) {
return res.status(400).send("Requested Fields Can't Be Updated");
}
const password = req.body.password;
if (password) {
const hashedPass = await bcrypt.hash(password, 8);
req.body.password = hashedPass;
}
try {
const user = await User.findByIdAndUpdate(req.params.id, req.body, {
runValidators: true,
new: true,
});
res.status(202).send(user);
} catch (error) {
res.status(400).send(error);
}
});
And don't forget to import needed npm modules.

//Wouldn't work for $inc updates etc...
Schema.pre("updateOne", function(next){
const data = this.getUpdate()
let updateKeys = Object.keys(data)
let queryArr = []
const checkQuery = (arr, innerData = data) => {
arr.map((elem) => {
if (elem === "$set" || elem === "$push") {
checkQuery(Object.keys(innerData[elem]), innerData[elem]);
} else {
queryArr.push(elem);
}
})
};
checkQuery(updateKeys);
const isModified = (value) => queryArr.includes(value)
if(isModified("field")) {
data.field = whatever...
}
next()
})
I recursively checked for keys here

This was my solution, using bcrypt and an async function. One of the big points of confusion for me is that changes are passed under this._update which differs from handling on .pre('save') where they're passed directly. So you see I had to call this._update.password as opposed to simply this.password.
UserSchema.pre('findOneAndUpdate', async function() {
const userToUpdate = await this.model.findOne(this.getQuery())
if (userToUpdate.password !== this._update.password) {
this._update.password = await bcrypt.hash(this._update.password, 12)
}
})

This worked for me to change the user password
userSchema.pre("updateOne", function (next) {
const user = this;
if (user.getUpdate().password !== undefined) {
bcrypt.hash(user.getUpdate().password, 10, (err, hash) => {
if (err) return next(err);
user.getUpdate().password = hash;
return next();
});
} else {
return next();
}
});

Thank you for your help. I wanted to hash users updated password and this is how I got it to work with the help of the previous posts.
UserSchema.pre("findOneAndUpdate", async function (next) {
const data = this.getUpdate();
const salt = await bcrypt.genSalt(10);
data.password = await bcrypt.hash(data.password, salt);
next();
});

But you can use query hooks; although you might need to use POST, rather than PRE hooks.
schema.pre('findOneAndUpdate', async function() {
const docToUpdate = await this.model.findOne(this.getQuery());
console.log(docToUpdate); // The document that `findOneAndUpdate()` will modify
});
So,
schema.post(/update/i, async (query, next) {
if (query instanceof mongoose.Query) {
await Model.find(query.getQuery()).each((el) => {
if (isModified('field')) {
//do something
}
})
}
next()
});

Related

req.session is not saving data

For now, I'm using the default store for sessions (i.e. not yet saving session data to a DB). I have a POST route that uses a middleware that checks a DB for a user, and if the user is present it saves the user object to the session, this is working fine, then a little further down in that same middleware I add to that same session based on a condition, this second write to the session is not occurring, not even at the completion of the route.
app.post('/', searchDb(db), (req, res) => {
res.redirect(`/someplace`);
});
In a middleware folder ...
searchDB = (db) => {
return async (req, res, next) => {
{ email } = req.body;
const user = await db.findOne({ emailAddress: `${email}` })
if (user) {
req.session.validUser = user;
if (condition) {
req.session.validUser.storeMoreStuff = "something"
} else {
req.session.validUser.storeMoreStuff = "somethingelse"
}
return next();
}
}
}
module.exports = { searchDB };
What am I not understanding?
Update#1: 1/14/22: Although it felt a little hacky, I tried modifying the user object prior to saving it to the session.
searchDB = (db) => {
return async (req, res, next) => {
{ email } = req.body;
let user = await db.findOne({ emailAddress: `${email}` })
if (user) {
user.storeMoreStuff = "something"
req.session.validUser = user;
return next();
}
}
}
module.exports = { searchDB };
However, only the original version of the user object that was pulled from the DB got written to the session.
Update#2: 1/14/22: Tried also to modify a copy of the user object, before saving to the session.
searchDB = (db) => {
return async (req, res, next) => {
{ email } = req.body;
let user = await db.findOne({ emailAddress: `${email}` })
let test = user;
test.storeMoreStuff = "something"
req.session.validUser = test;
}
}
module.exports = { searchDB };
Same result. I'm wondering now if "user", which comes from the DB find operation, is even an object (even though it seems to look like one). Perhaps this is why seemingly normal object operations are not working as expected. Going to add a "mongodb" tag, in case this is a Mongo idiosyncrasy / misunderstanding.
I'm reluctant to call this an answer, because I don't understand why, however, by copying each key-value pair of the user object into a temporary object before making the additional key-value pair assignment I was able to update the object and then write it to the session:
searchDB = (db) => {
return async (req, res, next) => {
{ email } = req.body;
const user = await db.findOne({ emailAddress: `${email}` })
if (user) {
const copyUser = {
key1: user.value1,
key2: user.value2,
key3: user.value3
}
req.session.validUser = copyUser;
if (condition) {
req.session.validUser.storeMoreStuff = "something"
} else {
req.session.validUser.storeMoreStuff = "somethingelse"
}
return next();
}
}
}
module.exports = { searchDB };

How to dynamically delete MongoDB entry using API route

I would just like to simply delete a record from a dynamically displayed list. I've tried every permutation of the backend code and ai just can't get it to work.
The backend is called like this:
async function deletePost() {
setLoading(true)
try {
await axios.delete(`/api/delete/${id}`)
alert("Post deleted")
}
catch (err) {
// notify user that something went wrong
console.log(err)
}
finally {
setLoading(false)
}
setLoading(false)
}
And /api/delete/${id} looks like this:
import { connectToDatabase } from "util/mongodb"
export default async (req, res) => {
const { id } = req.query;
console.log(id)
try {
const { db } = await connectToDatabase()
await db.collection("users").deleteOne({'_id': `ObjectId("${id}")`})
res.sendStatus(200).send({ done: true })
}
catch (error) {
return res.json({ error })
}
}
The console log shows the correct post id, the alert in the 'try' frontend code displays, but the dam post just wont delete. Can anyone offer any advice please? I have tried ".deleteOne({'_id': id})" but that does nothing either.
I believe you are searching for a string, instead of the ObjectId, so no documents match.
You can fix it by converting to ObjectId and then using the value converted, .deleteOne.
var ObjectId = require('mongodb').ObjectId;
const { id } = req.query;
const convertedObjectId = new ObjectId(id);
db.collection("users").deleteOne({_id: convertedObjectId })
Actual example from documentation:
try {
db.orders.deleteOne( { "_id" : ObjectId("563237a41a4d68582c2509da") } );
} catch (e) {
print(e);
}
Reference: MongoDB Documentation - Delete One.
Fixed problem:
app.delete("/cars/:id", async (req, res) => {
const carsId = req.params.id;
const query = { _id: ObjectId(carsId) };
const result = await carCollection.deleteOne(query);
res.send(result);
});

Mongo/Express: How to return all documents in collection if no query params are passed?

I'm trying to return all documents from my Mongo collection if no query parameters are passed. Currently I have 3 optional query parameters that could be passed by the user.
localhost:3000/api/projects
//should return all projects. Currently this is returning []
localhost:3000/api/projects?id=1
//should return projects with id of "1". Working properly.
localhost:3000/api/projects?name=myproject
//should return projects with name of "myproject". Working properly.
localhost:3000/api/projects?created_by=John
//should return projects created by "John". Working properly.
Within my route, I'm trying to determine my request has any query values. If it does not, then I want to return all documents in the collection. As stated above, this is not returning anything.
router.get('/', async (req, res) => {
if (req.query !== '') {
const project = await Projects.find({
$or: [
{ _id: req.query.id },
{ name: req.query.name },
{ created_by: req.query.created_by }]
});
res.json(project);
}
else {
const project = await Projects.find();
res.json(project);
}
});
Try as below:
router.get('/', async (req, res) => {
let searchQuery = {}
if(req.query.id){
searchQuery._id = req.query.id
}
if(req.query.name){
searchQuery.name = req.query.name
}
if(req.query.created_by){
searchQuery.created_by = req.query.created_by
}
const project = await Projects.find(searchQuery);
res.json(project);
});
You can write your api handler like this:
router.get('/', async (req, res)=>{
let options = {...req.query};
try{
const project = await Projects.find(options);
res.json(project);
}catch(e){
console.log(e);
}
});
This will fetch the documents on the basis of your query. If there is no query params req.query will be empty object and hence it will find all documents.
Hope this helps!!
router.get('/', async (req, res) => {
const id = req.query.id || null;
const name = req.query.name || null;
const created_by = req.query.created_by || null;
const query = { id, name, created_by };
const project = await Projects.find(query);
res.json(project);
});
I didn't test it, but I would solve your problem this way.

Node js, Wait, until get all data from MongoDB

I have a problem with async function and callbacks in Node js. I need to get my friends' all posts and display it. But if i do that without setTimeout(), it returns only some part of data. How can i solve this problem without putting setTimeout? Of course it's absurd to wait for 5-6 or 10 seconds to get all data. I also tried with Promises, but again response is incomplete. Please someone can help me?
//Sending request with axios to Controller
axios.post(packages.proxy+'users/getFriendsPosts',{id: user_id},config)
.then(res => {
// Code for displaying result
})
//User Controller
router.post("/getFriendsPosts", getFriendsPosts);
//Send request body to userService.js
function getFriendsPosts(req, res, next) {
userService.getFriendsPosts(req.body, function(posts, user){
res.json({posts,user});
})
.catch(err => next(err));
}
//userService.js
module.exports = {
getFriendsPosts,
};
async function getFriendsPosts(user,callback){
var arr = [];
var array = [];
MongoClient.connect(url, async function(errr, db) {
if (errr) throw errr;
var dbo = db.db("drone-x");
//Find user
dbo.collection("users").find({_id: ObjectId(user.id)}).toArray(async function(err, result) {
if (err) throw err;
result.forEach(async function(element, index) {
if(element.friends.length != 0){
element.friends.forEach(async function(elem) {
//Find user's friends
dbo.collection("users").find({_id: ObjectId(elem.id)}).toArray(async function(error, res) {
if (error) throw error;
//push user's friends to arr
arr.push(res);
res.forEach(async function(elements) {
//Find user's friends posts
dbo.collection("posts").find({userId: elements._id.toString()}).toArray(async function(errors, results) {
if (errors) throw errors;
//push user's friends posts to array
array.push(results);
//callback results through setTimeout
setTimeout(async function(){ await callback(array, arr); db.close(); }, 2000);
});
});
});
});
}
else
{
await callback("0");
}
});
});
});
}
If i don't use setTimeout function, it just returns 2-3 data, but with setTimeout, it returns all data. And if data will be raise, then i need to increase the setTimeout time. But of course it's not good idea. Someone can help me?
You should use try catch in this code
getFriendsPosts = async (user,callback) => {
const arr = [];
const array = [];
const db = await MongoClient.connect(url);
const dbo = db.db("drone-x");
const results = await dbo.collection("users").find({_id: ObjectId(user.id)})
const resultPromise = _.map(results, async element => {
const friends = _.get(element, 'friends', [])
if(friends.length != 0) {
const friendPromise = _.map(friends, async friend => {
const ress = await dbo.collection("users").find({_id: ObjectId(friend.id)})
arr.push(ress);
const resPromise = _.map(ress, async res => {
const posts = await dbo.collection("posts").find({userId: res._id.toString()})
const postPromise = _.map(posts, post => {
array.push(post);
})
await Promise.all(postPromise)
})
await Promise.all(resPromise)
})
await Promise.all(friendPromise)
}
})
await Promise.all(resultPromise)
return { arr , array }
}
I am not recommand this way it take too much time. You should use mongoose and use aggregation for long Query.

passing json formatted with information from multiple json documents nodeJs

I have a function where I would like to return an arrays of JSON objects with the necessary information. to send directly to the front everything ready.
async listProcessByOffice(req, res, next) {
try {
const actualPlayer = await PlayerOffice.findById(req.userId);
const actualOffice = await Office.findById(actualPlayer.Office);
const listProcesses = await Processes.find({ '_id': { $in: actualOffice.processes } });
const infosTable = {
protocol: ''
};
for (let i in listProcesses) {
this.protocol = listProcesses[i].prc_protocol;
console.log(this.protocol)
}
return res.status(200).json({ infosTable });
} catch (err) {
return next(err);
}
Not sure what you are looking for but i am assuming that you want to response back with array list of objects. So simple answer will be,
const infosTable = [];
for (let i in listProcesses) {
this.protocol = listProcesses[i].prc_protocol;
infosTable.push({protocol:listProcesses[i].prc_protocol})
console.log(this.protocol)
}

Resources