I'm currently working on a validation system using Node, Express and Mongoose and have stumbled into a bit of problem. In my Schema I have a verificationId associated with the user so that when they click the link emailed to them it can check to see if that verificationId is the same as the one in the database. All of that works fine, but I can't figure out how to delete the verificationId now that it's no longer needed.
Currently it's validating the user but not deleting verificationId. I've tried messing with the $pull method but I haven't had any success with it. Any help would be appreciated!
//User verification page
app.get("/verify/users/:verifiedId", (req, res) => {
const verifiedId = req.params.verifiedId;
//Check to see if the verificationHash the user was sent is the same as the one stored for them in the database
User.findOne({ verificationHash: verifiedId }, (err, result) => {
if (!err) {
console.log(verifiedId);
console.log(result);
const originalValue = { isVerified: false };
const newValue = { isVerified: true };
//Verify the user in the database
User.findOneAndUpdate(originalValue, newValue, (err) => {
if (!err) {
if (newValue) {
res.redirect("/success");
} else {
res.send(
"There was an error verifying your account. Please try again."
);
}
} else {
res.send(500, { error: err });
}
});
} else {
res.send(err);
console.log(err);
console.log(verifiedId);
}
//Delete the verificationHash from the user in the database
User.findOneAndUpdate(
{ verificationHash: verifiedId },
{ $pull: { verificationHash: { verificationHash: verifiedId } } },
{ new: true }
) });
});
I'm not very sure about this answer but try using the unset operator:
User.findOneAndUpdate(
{ verificationHash: verifiedId },
{ { $unset: {"verificationHash": ""} },
{ new: true }
)
or this may work ( setting the value to null )
User.findOneAndUpdate(
{ verificationHash: verifiedId },
{ verificationHash: null },
{ new: true }
)
Related
I am trying to create a login functionality for my Reactjs Webiste using Nodejs express backend.
I want to set a JWT token when the user tries to log in and update that token in my mongoDB database and then verify the token on the frontend and save it to localStorage.
However, when the user tries to log in after registration, it returns back the result without the token, and thus not allowing the user to log in, unless he clicks the login button again, then my code would generate and update the user with the JWT token.
Why is this behavior happening? Why is the first response only returning the found user from the findOne() operation when i am resolving the result from the findOneAndUpdate operation?
Here is my code:
Auth Controller:
login(params) {
params.email = params.email.toLowerCase();
return new Promise((resolve, reject) => {
db.collection("Users").findOne({ email: params.email }).then((response) => {
console.log(response)
if(response) {
bcrypt.compare(params.password, response.password, (err, success) => {
if(success) {
let token = jwt.sign({
name: response.name,
id: response._id
}, proccess.env.JWT_SECRET);
db.collection("Users").findOneAndUpdate({
email: params.email
}, {
$set: { token: token, lastLogin: new Date() },
}, function (e, s) {
if(e) {
console.log(e)
reject(e)
} else {
console.log("updated")
resolve(s)
}
})
} else {
reject({msg: 'Incorrect email or password.'})
}
})
} else {
reject({msg: 'cannot log in user'});
}
})
})
}
Auth Router:
router.post('/login', (req, res) => {
let User = new models.User()
let processes = [];
processes.push(function (callback) {
User.login(req.body).then(function (response) {
callback(null, response);
}, function (error) {
console.log(error)
callback(error);
});
});
async.waterfall(processes, function (error, data) {
if (!error) {
return res.json({
statusCode: 200,
msg: 'User logged in successfully.',
result: data
});
} else {
return res.json({
statusCode: 401,
msg: 'Cannot login user.',
error: error
});
}
});
})
React Login.js:
const login = () => {
axios.post('/login', data).then(async (response) => {
console.log(response)
if(response && response.data.result.value.token ) {
localStorage.setItem("authUser", JSON.stringify(response.data.result.value.token))
history.push("/")
console.log(response.data.result)
} else {
console.log("ERROR")
}
})
}
MongoDBs method findOneAndUpdate does return the old document by default.
In order to return the updated document pass returnNewDocument: true as option:
https://www.mongodb.com/docs/manual/reference/method/db.collection.findOneAndUpdate/
In your case:
db.collection("Users").findOneAndUpdate({
email: params.email
}, {
$set: { token: token, lastLogin: new Date() },
}, {
returnNewDocument: true
}, function (e, s) {
if(e) {
console.log(e)
reject(e)
} else {
console.log("updated")
resolve(s)
}
})
PS: You might should use async functions with await. This could make your code way more readable (at least within the User Model) :)
This can help you.
In your model
async login(params) {
params.email = params.email.toLowerCase();
try {
const user = await db.collection("Users").findOne({ email: params.email });
if(!user) {
throw {message: "Incorrect email"}
}
const vaild = await bcrypt.compare(params.password, user.password);
if(!valid) {
throw {msg: 'Incorrect email or password.'}
}
let token = jwt.sign({
name: user.name,
id: user._id
}, proccess.env.JWT_SECRET);
return db.collection("Users").findOneAndUpdate({
email: params.email
}, {
$set: { token: token, lastLogin: new Date() },
}, {new: true}); //FOR THE RETRIEVE NEW UPDATEs FROM MONGODB
} catch(e) {
throw e
}
}
I have this Schema here
Consider the likedTours which is an Array of Objects (Tours) (ignore position 0).
I want to pull any Objects where the _id of a Tour matches the critiria.
Adding a new Tour upon liking a tour is okay, but on unlike I don't know how to pull that item out.
Here is my function in the Controller in the Node.JS backend
const unlikeTour = async (req, res) => {
try {
TourDB.Tour.findOneAndUpdate(
{ _id: req.params.tourid },
{
$pull: { likedUsers: req.userID },
$inc: { likes: -1 },
}
).exec(async (err, docs) => {
if (!err) {
try {
await UserDB.User.findOneAndUpdate(
{ _id: req.userID },
{ $pull: { 'likedTours._id': docs._id } } //Here I need help
).exec()
return res.status(200).send({ successMessage: 'Tour successfully unliked' })
} catch (err) {
return res.status(500).send({ errorMessage: 'User not found' })
}
} else {
return res.status(500).send({ errorMessage: 'Tour not found' })
}
})
} catch (err) {
return res.status(500).send({ errorMessage: err })
}
}
This method looks for a tour and update it by pulling out the userID and decrement the likes count by -1.
And then I try to find in the UserDB that tour in the likedTours and tried to pull but it doesn't not work like that.
Thanks in advance
you can update as
await UserDB.User.findOneAndUpdate(
{ _id: req.userID },
{ $pull: { likedTours: { _id: docs._id } } } //Here I need help
).exec();
reference: https://docs.mongodb.com/manual/reference/operator/update/pull/
Well, I am trying to do autoincrement in node.
So I have a simple function that I found here How to add auto increment to existing collection in mongodb/node.js?
// Increment DB Value
exports.getNextSequenceValue = async function (sequenceName) {
try {
const seqNum = await db.collection('counters').findOneAndUpdate({
_id: sequenceName
}, {
$inc: {
seq: 1
}
}, {
returnNewDocument: true
});
// Check if response has seqNum.seq exists
console.log(seqNum.value.seq);
if (seqNum && seqNum.value.seq) {
return seqNum.value.seq
} else {
return 'what ever you want '
}
} catch (err) {
console.error(err)
}
}
and my controller where i used it.
const user = new User({
profile: {
firstname: req.body.firstname,
lastname: req.body.lastname,
location: req.body.location,
status: 1,
avatar: "/uploads/avatars/defaultProfilePic.jpg"
},
email: req.body.email,
password: req.body.password,
xid: core.getNextSequenceValue('userid') // ----HERE ITS FUNCTION-----
});
console.log(core.getNextSequenceValue('userid')) //---- [object Promise]X ---
User.findOne({
email: req.body.email
}, (err, existingUser) => {
if (err) {
return next(err);
}
if (existingUser) {
req.flash('errors', {
msg: 'Account with that email address already exists.'
});
return res.redirect('/signup');
}
user.save((err) => {
if (err) {
return next(err);
}
req.logIn(user, (err) => {
if (err) {
return next(err);
}
res.redirect('/');
});
});
});
};
But now i get error.
ValidationError: User validation failed: xid: Cast to Number failed for value "Promise { <pending> }" at path "xid"
Have no idea how to fix it.
Thanks for any help.
Ok your issue seems to be with new : true when you're using findOneAndUpdate(), it has to be returnNewDocument: true more over you need to do await on function which takes sometime to finish but not on the response of it. Remember to always wrap your async & await with try-catch block.
exports.getNextSequenceValue = async function (sequenceName) {
try {
const seqNum = await db.collection('counters').findOneAndUpdate({
_id: sequenceName
}, {
$inc: {
seq: 1
}
},
{ returnNewDocument: true }
);
// Check if response has seqNum.seq exists
console.log(seqNum.seq);
if (seqNum && seqNum.seq) { return seqNum.seq } else {
return 'what ever you want '
}
} catch (err) {
console.error(err)
}
}
I'm trying to figure out how to update the field by incrementing +1 each time the page is visited and if it has never been visited then add it to the DB.
Currently, this is what I have got but it does not seem to do much. I must have gone wrong somewhere and I have not yet implemented the part where if the page has never been viewed then create a new object in the array which is stored in the database.
Little note: Where I created the map they do match with the same ID if I view the page with the same ID as the one stored in the database but no increment happens.
exports.pageVisitCount = (req, res, next) => {
User.findById({
_id: req.userData.userId
}, 'visits', function (err, pageVists) {
if (err) {
res.status(401).json({
message: "Error Occured!"
})
} else {
const pageCounts = pageVists.visits;
pageCounts.map(page => {
const postViewed = req.body.postId;
if (page.postId.toString() === postViewed) {
User.findByIdAndUpdate({
_id: req.userData.userId
}, {
$set: {
visits: [{
"postId": postViewed,
$inc: { visitCount: 1 }
}]
}
}, {
upsert: false
},
(err) => {
if (err) {
res.status(401).json({
message: "Error Occured!"
})
} else {
res.status(200).json({
message: "Update successful!"
})
}
});
}
});
}
});
}
This is the schema I am using:
const visitsSchema = new Schema ({
postId: {
type: String
},
visitCount: {
type: Number
}
})
const userSchema = mongoose.Schema({
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
role: {
type: String,
required: true
},
answers: {
type: String
},
visits: [visitsSchema]
});
Any feedback would be highly appreciated, I would like to mention that I am new to backend, thanks!
To avoid using the map to filter the visits after querying the visits of the user under consideration, I suggest you let mongodb do that for you. In this case you first do a find based on both the user id and the postId. If you get a record matching both criteria you are sure you can easily update the user visits by incrementing the particular visits visitCount by 1.
Otherwise i.e. if they don't match any records then since u might be using a valid user id then such user has not visited such post. So you now create a new visit with the postId and initialize its visitCount to 1 (Although we intend to create, but since its a subdocument you'll need use $push). Enough of the talking try the code below.
exports.pageVisitCount = (req, res, next) => {
User.findOne({
_id: req.userData.userId, "visits.postId": req.body.postId
}, 'visits.$', function (err, user) {
if (err) {
res.status(401).json({
message: "Error Occured!"
});
} else {
if(user == null){
User.findByIdAndUpdate({
_id: req.userData.userId
}, {
$push: {
visits: {
"postId": req.body.postId,
visitCount: 1
}
}
}, function (err) {
if(err)
return res.status(401).json({
message: "Error Occured when creating new visit!"
})
return res.status(200).json({
message: "Success"
})
})
}
User.update({
_id: req.userData.userId, "visits.postId": req.body.postId
}, {
$inc: { "visits.$.visitCount": 1 }
},(err) => {
if (err) {
res.status(401).json({
message: "Error Occured!"
})
} else {
res.status(200).json({
message: "Update successful!"
})
}
});
}
});
};
I have created a function that is looking for any reservation document with matching _id, if exists is deleted, and next the Book(findById) function is performed which is looking for any books using function(findOneAndUpdate), if exists is added value to quantity +1.But i don't know why but the function does not want to be done.
router.post('/cancel-reservation', (req, res) => {
var reservation = req.body.reservation;
Reservation.findByIdAndRemove(reservation._id, function (err) {
if (err) throw err;
else {
Book.findById(reservation.idBook, (err, book) => {
if (err) throw err;
if (book == null) {
//////////// //*WORKING *////////////////
let bookObj = {
name: reservation.nameBook,
description: reservation.description,
publishingHouse: reservation.publishingHouse,
quantity: 1,
year: reservation.year,
sites: reservation.sites
};
var book = new Book(bookObj);
book.save()
.then(function (book) {
res.json(book)
})
.catch((err) => {
res.json('not saved')
})
//////////// //*WORKING *////////////////
} else if (book) {
// findOneAndUpdate
//////////// //*HERE IS PROBLEM *////////////////
book.quantity = book.quantity+1;
console.log(book._id)
Book.findOneAndUpdate({ _id: book._id },
{ $set: { quantity : book.quantity } }, { upsert: true }),
((err, complete) => {
if(err) console.log('errrrrr');
else {
console.log(complete)
console.log('complete')
res.json(complete)
}
})
}
});
}
});
});
this problem may be related to the fact that the findOneAndUpdate function is nested in findById?
I think that you have an extra parentheses on the findOneAndUpdate
Book.findOneAndUpdate({ _id: book._id },
{ $set: { quantity : book.quantity } }, { upsert: true },
(err, complete) => {
if(err) console.log('errrrrr');
else {
console.log(complete)
console.log('complete')
res.json(complete)
}
})