req.session is not saving data - node.js

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 };

Related

Save in mongodb and pass created object to a function

I'm beginner at programing and I don't know how can I do something with the mongoose save result.
In my post endpoint I would like to not save and directly return but instead of it I would like to do something with the result of save method like take the _id value of the new object created and pass to a function.
Here's what my post endpoint is doing and I would like to after saving not return but instead call a function passing the checkout object created:
router.post('/', async function(req, res) {
const { checkinId, eventId, email } = req.body;
let CheckoutTest = {
checkinId: checkinId,
eventId: eventId,
email: email,
}
const newCheckout = new Checkout(CheckoutTest);
await newCheckout.save((err, checkout) => {
if(err) {
return res.status(400)
.send(err);
}else {
return res.status(200)
.json({message: "Checkout successfully added!", checkout});
}
})
});
An elegant way to do this would be to add a try...catch block
router.post('/', async function(req, res) {
const { checkinId, eventId, email } = req.body;
let CheckoutTest = {
checkinId: checkinId,
eventId: eventId,
email: email,
}
const newCheckout = new Checkout(CheckoutTest);
try {
const newCheckoutObject = await newCheckout.save()
// Call the function that you wanted to after the save.
// You can pass in the "_id" of the object as shown here
const newData = await functionToBeCalled(newCheckoutObject._id)
return res.status(200).json({message: "Checkout successfully added!", newData});
} catch (err) {
return res.status(400).send(err);
}
}

Variable cannot be change inside findOne

I want to make a quick Util file which would contain multiple functions such as an user_id to his name. As the image under shows, I tried to save the value throughout the code but the variable "name" doesn't get affected inside the .then(user => {}) for some reasons
I tried returning directly the value without using variable to save it.
I debugged and code runs fine and it gets into the if(!user){}else{}perfectly.
The user returned by Mongoose works and I can console log the user.username (Cannot return it, either can I save it to a variable, which is what i'm doing on the code under)
const User = require('../models/users')
exports.id2name = (id) => {
let name = 'none'
User.findOne({_id: id}).then(user => {
if (!user) {
name = 'Unknown'
} else {
name = user.username
}
}).catch(err => console.log(err))
return name
}
I don't get any errors on my console. It is returning 'none' as a result even if it gets into the else statement inside the mongoose request.
You are getting this result for asynchronous behaviours of JavaScript.
You can try this code for getting the desired result.
exports.id2name = async (id) => {
try{
let name = 'none'
const user = await User.findOne({_id: id});
if(user) {
name = user.username;
} else {
name = 'Unknown';
}
return name;
} catch(e) {
console.log(e);
}
}
It's asynchronous return. I mean you may need to use callback, Promise or other asynchronous way to deal with it, for example:
const User = require('../models/users')
exports.id2name = (id, callback) => {
User.findOne({_id: id}).then(user => {
if (!user) {
callback(null, 'Unknown')
} else {
callback(null, user.username)
}
}).catch(callback)
}
or promise:
const User = require('../models/users')
exports.id2name = (id) => {
return new Promise((resolve, reject) => {
User.findOne({_id: id}).then(user => {
if (!user) {
resolve('Unknown')
} else {
resolve(user.username)
}
}).catch(reject);
});
}

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.

Save multiple model documents in one POST route with Mongoose/Express/Node

I have a one-to-many relationship with my Search model and Result model. My user will do a search, select the results that were helpful, and hit a save button. That save button will hit an app.post() request. This should save an instance of the Search and one (or more) instance(s) of the selected Results. I can successfully save the Search instance with the following code:
controllers/searchController.js
const Search = require('../models/search');
exports.search_create_post = (req, res) => {
let newSearch = new Search({ search_text: req.body.search_text });
newSearch.save((err, savedSearch) => {
if (err) {
console.log(err);
} else {
res.send(savedSearch);
}
})
routes/search.js
const express = require('express');
const router = express.Router();
const search_controller = require('../controllers/searchController');
//Search Routes
router.get('/', search_controller.search_home);
router.get('/results', search_controller.search_results_get);
router.post('/', search_controller.search_create_post);
module.exports = router;
How can I make it so that my user hitting the save button once will save the Search instance above and also the Results?
I ended up doing what I needed by passing two callbacks into my post() route and calling next() inside the first one along with passing the data the second one needed through the req object. My code is as follows:
routes/search.js
router.post('/', search_controller.search_create_post, result_controller.result_create_post);
controllers/searchController.js
exports.search_create_post = (req, res, next) => {
let newSearch = new Search({ search_text: req.body.search_text });
newSearch.save((err, savedSearch) => {
if (err) {
console.log(err);
} else {
req.searchData = savedSearch;
}
next();
})
};
controllers/resultController.js
exports.result_create_post = (req,
let newResult = new Result({ url: 'req.body.url', search: req.searchData });
newResult.save((err, savedResult) => {
if (err) {
console.log(err);
} else {
res.send(savedResult);
}
})
};

mongoose middleware pre update

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()
});

Resources