I'm working on adding a last login functionality to my Node app and can't seem to get it to work. Here's what I've got for a mongoose user schema:
userSchema = new mongoose.Schema({
username: {
type: String,
unique: true
},
password: String,
email: {
type: String,
unique: true
},
avatar: String,
firstName: String,
lastName: String,
lastLogin: {
type: Date,
default: Date.now
},
resetPasswordToken: String,
resetPasswordExpires: Date,
isAdmin: {
type: Boolean,
default: false
}
});
userSchema.plugin(passportLocalMongoose);
userSchema.statics.newLogin = function login(id, callback) {
return this.findByIdAndUpdate(id,{'$set' : { 'lastLogin' : Date.now()} }, { new : true }, callback);
};
module.exports = mongoose.model("User", userSchema);
Here's my login route:
router.post("/login", passport.authenticate("local",
{
failureRedirect: "/login"
}), function(req, res) {
User.findOneAndUpdate(req.username, {lastLogin: Date.now()}, (err, data) => {
if(err) console.log(err);
else console.log("Successfully updated the lastLogin", data);
res.redirect("/players");
});
});
I've been able to get the initial date to stick when the account is created; however, when I login with that same account the date remains unchanged. What am I missing here?
There are a few other questions with similar topics, but none seem to resolve my issue. Specifically, this question where I've implemented part of the solution to no avail. Thanks in advance for the help!
Here's the code currently being tested, example req.body:
{ username: 'anyUserHere', password: 'anyPasswordHere' }
req.user:
{
isAdmin: true,
_id: 5e9b301a6bb78973c9ec8fae,
username: 'anyUserHere',
salt: 'saltValue',
hash: 'hashValue',
__v: 0,
avatar: '../images/admin.jpg',
email: 'example#example.com',
firstName: 'first',
lastName: 'last',
password: 'anyPasswordHere',
lastLogin: 2020-05-22T18:35:50.941Z
}
So in this case, the 'anyUserHere' example should be the one being updated, but the update occurs to the first user in Mongo. Console output:
Successfully updated the lastLogin {
isAdmin: false,
_id: 5e939f988ced3e0428c8b521,
username: 'test',
__v: 0,
lastLogin: 2020-05-22T18:38:59.836Z
}
Can you update the User.newLogin(); with the below code and try
User.newLogin(id, (err, data) => {
if(err) console.log(err);
else console.log("Successfully updated the lastLogin", data);
});
router.post("/login", passport.authenticate("local",
{
failureRedirect: "/login"
}), function(req, res) {
User.newLogin(id, (err, data) => {
if(err) console.log(err);
else console.log("Successfully updated the lastLogin", data);
res.redirect("/players");
});
});
Edit
According to the current approach with findOneAndUpdate you need to make the following updated to the filter
User.findOneAndUpdate({username: req.username}, {lastLogin: Date.now()}, (err, data) => {
if(err) console.log(err);
else console.log("Successfully updated the lastLogin", data);
res.redirect("/players");
});
Related
I am updating and returning(new Object) a existing object in the database with mongoose findOneAndUpdate but getting an error
Error
response is not defined
at Function.module.exports.updateProfile ........
In router File
router.post('/edit_profile', (req, res) => {
let updateProfile = new Profile({
name: req.body.name,
email: req.body.email,
username: req.body.username,
gender: req.body.gender,
bio: req.body.bio,
user_id: req.body.user_id
});
console.log(updateProfile); //consoling data Place(1)
Profile.updateProfile(updateProfile.user_id, (err, user) => {
if (err) throw err;
else {
console.log("Update User");
console.log(user);
res.json({
user: user
})
}
})
})
consoled data at Place(1)
{ _id: 5c9cd517b3b7db248c6d7981,
name: 'Shivva',
email: 'ritinbhardwaj933#gmail.com',
username: 'zzz',
gender: 'Male',
bio: 'I am HOwdy Member',
user_id: '5c9cd47bf3d9bb1ea8cbfcbe' }
In profile.js
module.exports.updateProfile = (id, callback) => {
let query = { user_id: id };
console.log(query); //consoling data Place(2)
Profile.findOneAndUpdate(query, { $set: response }, { new: true }, (err, user) => {
if (err) throw err;
else {
callback(null, user);
}
});
}
consoled data at Place(2)
{ user_id: '5c9cd47bf3d9bb1ea8cbfcbe' }
Error
The error i am getting is response is not defined a the Function.module.exports.updateProfile
Error
the accepted solution worked but now it is returning the error
collection.findAndModify is deprecated. Use findOneAndUpdate, findOneAndReplace or findOneAndDelete instead
If you look closely, in this line you have used variable response but never have you initialised it.
Profile.findOneAndUpdate(query, { $set: response }, { new: true }, (err, user) => {
That response word should be replaced with an object with whatever changes you want eg.{ name: 'jason bourne' }
And honestly you don't need to create an instance like what you have done below because you aren't using that anywhere.
let updateProfile = new Profile({
name: req.body.name,
email: req.body.email,
username: req.body.username,
gender: req.body.gender,
bio: req.body.bio,
user_id: req.body.user_id
});
I have the following Mongoose schema:
var userSchema = new Schema({
username: {
type: String,
},
password: {
type: String,
},
email: {
type: String,
},
_imageId: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'files'
}]
});
When I retrieve data from the database using the function findOne() in this way
getById: function (req, res) {
User.findOne({ _id: req.params.id }, function (err, user) {
getProfileImage(req, user, function(user) {
return res.status(200).send({
msg: 'User retrieved successfully',
data: user
});
});
});
}
I get an array of image, but only with their id, so I have to manually add the URL using the function getProfileImage.
Is there a way using Mongoose to pragmatically add the URL?
EDIT:
I add the implementation of getProfileImage
var getProfileImage = function(req, user, callback) {
if(user && user._imageId.length > 0) {
user.set('profileImage', req.protocol + '://' + req.get('host') + '/image/get/' + user._imageId[user._imageId.length - 1], { strict: false });
}
callback(user);
};
Yes there is a way, and it doesn't involve storing _imageId in the userSchema. It's the other way around. You need to create a imageSchema that stores _userId and fetch all images from there.
Schemas
var userSchema = new Schema({
username: {
type: String,
},
password: {
type: String,
},
email: {
type: String,
}
});
var imageSchema = new Schema({
url: {
type: String,
},
userId: {
type: mongoose.Schema.Types.ObjectId,
}
});
Controller
getById: function (req, res) {
User.findOne({ _id: req.params.id }, function (err, user) {
// Get images related to the user
Image.find({userId: req.params.id}, function(err, images) {
user.images = images;
return res.status(200).send({
msg: 'User retrieved successfully',
data: user
});
});
});
}
I want to create a DB with Users which also have a reference to another DB called "Library" which has "favourites" and "likes". I will show the idea here:
User Model
const userSchema = Schema({
username: {type: String, minlength: 4, maxlength: 10, required: true, unique: true},
email: {type: String, required: true, unique: true},
password: {type: String, required: true},
isVerified: { type: Boolean, default: false },
library: {type: Schema.Types.ObjectId, ref: 'Library'}
}, { timestamps: true});
Library Model
const librarySchema = new Schema({
likes: [{
likeId: {type: String},
mediaType: {type: String}
}],
favourites: [{
favId: {type: String},
mediaType: {type: String}
}],
user: {type: Schema.Types.ObjectId, ref: 'User'}
});
Can you please tell me if this is the right way to implement these models or if there is a better way?
At the moment if I try to call
User.findOne({email: 'xxx#xxx.com'}).populate('library').exec(function (err, library)
it doesn't find anything...
Library POST request
router.post('/favourites', passport.authenticate('jwt', {session: false}), function (req, res) {
const favouritesFields = {};
if (req.body.favId) favouritesFields.favId = req.body.favId;
if (req.body.mediaType) favouritesFields.mediaType = req.body.mediaType;
Library.findOne({user: req.user._id}).then(library => {
if (library) {
Library.update({user: req.user._id}, {$push: {favourites: favouritesFields}})
.then(library => res.json(library));
} else {
new Library({user: req.user._id, favourites: favouritesFields}).save().then(library => res.json(library));
}
});
});
User POST request
router.post('/signup', function (req, res) {
const {errors, isValid} = validateSignupInput(req.body);
if (!isValid) {
return res.status(400).json(errors);
}
// Check if email already exists
User.findOne({email: req.body.email}, function (user) {
if (user) {
return res.status(400).json({
title: 'Email already exists'
});
}
});
// Create and save the new user
let user = new User({
username: req.body.username.toLowerCase(),
email: req.body.email.toLowerCase(),
password: bcrypt.hashSync(req.body.password, 10)
});
user.save(function (err, result) {
if (err) {
return res.status(500).json({
title: 'An error occurred during the signup',
error: err
});
}
res.status(201).json({
title: 'User created',
obj: result
});
Your problem is not with the query you're making. there is no foundUser.library because one was never added.
You're adding users to libraries, but you're not adding libraries to your users. if you run the following code in your app:
Library.find({}).populate("user").exec(function(err, foundLibraries){
if (err){
console.log(err);
} else {
console.log(foundLibraries);
}
});
You would see that the libraries have their "user" properties, that when populated contain the entire user document as an object. But, the reason that isn't working for foundUser.library when you query for users is that foundUser.library was never assigned. you know how you're assigning the email, username and password when creating users, you have to do the same for the library property. Or, in your case, since a library is only created after the user, you can just set the value of user.library in the callback of creating/saving the library.
I'm running into an issue using Mongoose, Express where I want to save a sub document to my user by pushing it into the sub document array, which I can do. However the issues arise when I want to delete a gamesession that is stored in the users "sessions" attribute and also delete the gamesession globally. I think the issue arises because I'm saving two seperate instances of a gamesession. Here is the code for creating a new sub document called "gamesession" and pushing it onto the users "session" attribute
//POST /posts
// Route for creating gamesessions for specific user
router.post("/gamesessions/:uID/", function(req, res, next) {
var gamesession = new GameSession(req.body);
req.user.sessions.push(gamesession);
gamesession.postedBy = req.user._id;
req.user.save(function(err, user) {
if(err) return next(err);
gamesession.save(function(err, gamesession){
if(err) return next(err);
res.json(gamesession);
res.status(201);
});
});
});
Here is my UserSchema
var UserSchema = new Schema({
posts: [PostSchema],
sessions: [GameSessionSchema],
email: {
type: String,
unique: true,
required: true,
trim: true
},
username: {
type: String,
unique: true,
required: true,
trim: true
},
password: {
type: String,
required: true
}
});
And my GameSessionSchema
var GameSessionSchema = new Schema({
postedBy: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
},
region: {
type: String,
required: true
},
title: {
type: String,
required: true
},
description: {
type: String,
required: true
},
game: {
type: String,
required: true
},
age: String,
createdAt: {type: Date, default: Date.now},
updatedAt: {type: Date, default: Date.now},
platform: {
type: [String],
enum: ["Xbox One", "PS4", "PC"],
required: true
}
});
Edit: Adding my delete route to see if that helps
//DELETE /posts/:id/comments/:id
//Delete a specific comment
router.delete("/gamesessions/:uID/sessions/:gID", function(req, res) {
var gamesession = new GameSession(req.body);
gamesession.remove(function(err) {
req.user.save(function(err, user) {
if(err) return next(err);
res.json(user);
});
});
});
Then, when I want to delete a gamesession with a route, it only deletes the instance saved in user.sessions and when I want to query all gamesessions, it's still there, but deleted in my User document. Any ideas? I think it's because I'm saving the document twice, and if so, what's the best way to save it in user.sessions while also being able to delete from user.sessions and querying a global session.
Possibly not saving the removed gamesession from the GameSession document?
router.delete("/gamesessions/:uID/sessions/:gID", function(req, res) {
var gamesession = new GameSession(req.body);
gamesession.remove(function(err) {
req.user.save(function(err, user) {
if(err) return next(err);
gamesession.save(function(err, gamesession){
if(err) return next(err);
res.json({message: 'Updated GameSession Doc'}, gamesession)
})
res.json(user);
});
});
});
Here I am trying to verify mobile number in user module. I have created token and I sent to user but whenever user is trying to verify using that particular token 'Password' and 'salt' automatically got changed. How to avoid this? Some one help me out .. here I want to update only
user.Mobileverification = 'verfied';
user.Mobileverificationcode = undefined;
user.mobileVerificationExpires = undefined;
Above three variables got changed but I don't know why password and salt has changed?
I have given my routes below:
app.route('/auth/mobilereset/:token').get(users.mobileresetResetToken);
app.route('/auth/mobilereset/:token').post(users.mobilereset);
controller:
exports.mobileresetResetToken = function(req, res) {
User.findOne({
Mobileverificationcode :req.params.token,
mobileVerificationExpires: {
$gt: Date.now()
}
// resetPasswordToken: req.params.token,
// resetPasswordExpires: {
// $gt: Date.now()
// }
}, function(err, user) {
if (!user) {
res.send({
message: 'Invalid token'
});
} else {
console.log('working fine');
}
});
};
exports.mobilereset = function(req, res, next) {
async.waterfall([
function(done) {
User.findOne({
Mobileverificationcode: req.params.token,
mobileVerificationExpires: {
$gt: Date.now()
}
}, function(err, user) {
if (!err && user) {
user.Mobileverification = 'verfied';
user.Mobileverificationcode = undefined;
user.mobileVerificationExpires = undefined;
user.save(function(err) {
if (err) {
return res.status(400).send({
message: errorHandler.getErrorMessage(err)
});
} else {
req.login(user, function(err) {
if (err) {
res.status(400).send(err);
} else {
// Return authenticated user
res.json(user);
done(err, user);
}
});
}
});
} else {
return res.status(400).send({
message: 'reset token is invalid or has expired.'
});
}
});
},
], function(err) {
if (err) return next(err);
});
};
model:
var UserSchema = new Schema({
username: {
type: String,
unique: 'testing error message',
required: 'Please fill in a username',
trim: true
},
password: {
type: String,
default: '',
// validate: [validateLocalStrategyPassword, 'Password should be longer']
},
email: {
type: String,
trim: true,
default: '',
// validate: [validateLocalStrategyProperty, 'Please fill in your email'],
// match: [/.+\#.+\..+/, 'Please fill a valid email address']
},
Mobilenumber: {
type: String,
default: ''
},
roles: {
type: [{
type: String,
enum: ['user', 'admin']
}],
default: ['user']
},
salt: {
type: String
},
provider: {
type: String,
required: 'Provider is required'
},
providerData: {},
additionalProvidersData: {},
updated: {
type: Date
},
created: {
type: Date,
default: Date.now
},
/* For reset password */
Mobileverificationcode: {
type: String,
},
mobileVerificationExpires: {
type: Date
},
Mobileverification: {
type: String,
trim: true,
default: 'Not Verified',
},
resetPasswordToken: {
type: String
},
resetPasswordExpires: {
type: Date
}
});
I don't know if you removed this or not but in MEAN.js user model, you have to be careful with the following code block:
/**
* Hook a pre save method to hash the password
*/
UserSchema.pre('save', function (next) {
if (this.password && this.isModified('password')) {
this.salt = crypto.randomBytes(16).toString('base64');
this.password = this.hashPassword(this.password);
}
next();
});
Which will be called right before you save the user data. That's probably why password and salt keep changing... You are calling user.save in mobile.reset() and that code block above is still present somewhere.
Update:
A possible way of doing it is:
/**
* Hook a pre save method to hash the password
*/
UserSchema.pre('save', function (next) {
if(!this.isModified('Mobileverification') && !this.isModified('Mobileverificationcode') && !this.isModified('mobileVerificationExpires')) {
if (this.password && this.isModified('password')) {
this.salt = crypto.randomBytes(16).toString('base64');
this.password = this.hashPassword(this.password);
}
}
next();
});
However it might need a few adjustments, such as improving this pre-save hook according to your needs and testing password changing and mobile verification to see if nothing is broken.