He there,
I just started using express / mongoose and I'm new with the concept. Currently trying to update a counter each time a user logges in. I'm using expres 4.8.8, mongoose, 3.8.15, passport 0.2.1
I have the following route, the function is being called on success:
// Set up the 'signin' routes
app.route('/signin')
.get(users.renderSignin)
.post(passport.authenticate('local', {
failureRedirect: '/',
failureFlash: true
}),function(req,res,next){
users.updateLoginCount(req.user.id);
res.redirect('/');
});
Then in the user controller, I want to update the login counter of the user that is currently logging in:
exports.updateLoginCount = function(user_id){
User.findById(user_id, function(err,user){
if(!err){
user.counters.login += 1;
user.save(function(err){
console.log('start save');
if(err) {
console.log(err);
}else{
console.log(user.username + ' logged in for the ' + user.counters.login + ' time');
}
});
}else{
console.log('Error');
}
});
};
Model looks something like:
// Define a new 'UserSchema'
var UserSchema = new Schema({
firstName: String,
lastName: String,
email: {
type: String,
// Validate the email format
match: [/.+\#.+\..+/, "Please fill a valid email address"]
},
username: {
type: String,
// Set a unique 'username' index
unique: true,
// Validate 'username' value existance
required: 'Username is required',
// Trim the 'username' field
trim: true
},
password: {
type: String,
// Validate the 'password' value length
validate: [
function(password) {
return password && password.length > 6;
}, 'Password should be longer'
]
},
salt: {
type: String
},
provider: {
type: String,
// Validate 'provider' value existance
required: 'Provider is required'
},
providerId: String,
providerData: {},
created: {
type: Date,
// Create a default 'created' value
default: Date.now
},
counters:{
login:{
type:Number,
default: 0,
}
}
});
But somehow it doesn't call the user.save(). It also doesn't show any errors, so I have no idea what I'm doing wrong here. It works fine till the user.save() part.
I hope that someone can point out the mistake I'm making. If there is more information needed, please let me know!
Since the node.js is asynchronus I believe that your res.redirect executed before the login update function finishes executing. So, you need to set a callback for your method and run the res.redirect after your update is completed.
You can use mongoose update statement to increase a value like below by the way. Of course go on with the findById if you need other user info.
exports.updateLoginCount = function(user_id, callback){
User.update('_id' : user_id, {$inc: {"counters.login" : 1}}, function(err){
if(err)
console.log(err);
else{
console.log(user_id + " login count increased by one" );
callback();
}
});
}
Calling the method;
...
}),function(req,res,next){
users.updateLoginCount(req.user.id, function(){
res.redirect('/');
});
});
PS: I don't have a chance to test it now though, probably includes some syntax errors :)
Related
I have one model is user in that model I was added email, username, password and name , when I have insert this data using node JS with the help of rest API, so that condition all 4 records are stored in one table
but I want email and name is stored in registration table and username and password stored in login table ,when I put login request using postman it with username name and password credentials it gives the successful response.
I am new to Node
My controller is
exports.user_signup = (req, res, next) => {
User.find({ username: req.body.username })
.exec()
.then(user => {
if (user.length >= 1) {
return res.status(409).json({
message: "Mail exists"
});
} else {
bcrypt.hash(req.body.password, 10, (err, hash) => {
if (err) {
return res.status(500).json({
error: err
});
} else {
const user = new User({
_id: new mongoose.Types.ObjectId(),
username: req.body.username,
password: hash,
email: req.body.email,
contact: req.body.contact,
});
user
.save()
.then(result => {
// console.log(result);
res.status(201).json({
message: "User created"
});
})
.catch(err => {
console.log(err);
res.status(500).json({
error: err
});
});
}
});
}
});
};
My Postman post method is in JSON form
{
"username":"tene",
"password":"tene",
"email":"tene#gmail.com",
"contact":1234567890
}
You can try this:
import mongoose from 'mongoose'
const { Schema } = mongoose
const userSchema = new Schema(
{
registrationTable : {
email: { type: String, required: true },
mobileNo: { type: String, required: true }
},
loginTable: {
username: { type: String, required: true },
password: { type: String, required: true }
}
},
{ timestamps: true }
)
const UserModel = mongoose.model('User', userSchema)
It will depend on you if you wanna make registration and login table as an object or array, but this will sure help.
required: true will be for, you need that value necessary, if you dont want some value just remove this.
I am working on an Authentication/Session using Express, NodeJS, and MongoDB.
The Mongoose Schema looks like this:
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const userSchema = new Schema({
username: { type: String, required: true, },
email: { type: String, required: true, unique: true, },
password: { type: String, required: true, },
SignUpDate: { type: { type: Date, default: Date.now } },
LastLogin: { type: { type: Date, default: Date.now } },
loggedin: { type: Boolean, required: false, },
attempts: { type: Number },
});
module.exports = mongoose.model("User", userSchema);
The Signup form only takes username, email, password, but I would like to save sign update, last login, failed login attempts, etc.
In the controller.js file, I have the routes, this is the problematic one.
exports.register_post = async (req, res) => {
const { username, email, password } = req.body;
let user = await User.findOne({ email });
if (user) {
req.session.error = "User already exists";
return res.redirect("/register");
}
const hasdPsw = await bcrypt.hash(password, 12);
user = new User({
username,
email,
password: hasdPsw,
SignUpDate,
loggedin: true
});
await user.save();
console.log(user)
res.redirect("/login");
};
And in App.JS I have this
app.post("/register", appController.register_post);
If I only use username, email, and password in the Schema, it all works, saves to the database.
But as above, I get
"UnhandledPromiseRejectionWarning: ReferenceError: SignUpDate is not
defined"
if I submit the signup button on the /register route. Another question, if I want to get a timestamp with Mongoose, do I have to call Date.now() and where?
Or do I have to define and add/push the properties that are not user provided via Signup Post ( SignUpDate,LastLogin:,loggedin:,attempts ) to the Schema after the users sign up? I am new to using Mongoose, going through the docs and cant seem to find how to ad a timestamp.
A little update, if I comment out the SignUpDate,LastLogin variables in the post function, I get "Object, Object" in MongoDBcompass and the object is collapsible, it saved the values in the database but crashed the app. The change that was necessary was simply
SignUpDate: { type: { type: Date, default: Date.now } },
LastLogin: { type: { type: Date, default: Date.now } },
to
SignUpDate: {
type: Date, default: Date.now(),
},
LastLogin: {
type: Date, default: Date.now(),
}
This is how it looks in the database, and it gets saved and the app doesn't crash. but as soon I uncomment "SignUpDate" in the route function, I get the same undefined error again.
I can live with this, but would rather not. Besides, how do I convert "type: Date, default: Date.now()" to a nice out put like ""Sun May 10 2015 19:50:08 GMT-0600 (MDT)"? If I change it in the Schema, it don't work, if I change it in the route function, it will not let me chain the functions and I don't know where to declare the var for the nice formatted output.
Remove "SignUpDate":
const user = new User({
username,
email,
password: hasdPsw,
loggedin: true
});
If you specified a default value, you don't need to specify it when you create new object.
If you want to accumulate the number of unsuccessful attempts, you need to get the users from the base, increase the counter by one and update it in the base. Smth like this:
let userAttempts = await User.findOne({ username });
await User.update({ username }, { $set: { attempts: userAttempts.attempts + 1 } });
I have registration form with username, mail, password and password2. I want to verify passwords that they actually match. I verify practically everything in Mongoose Scheme but I cannot find any useful information in documentation how to grab password2 without actually saving it to database. (I have function to crypt password which runs only before saving)
const userSchema = new mongoose.Schema({
username: {
type: String,
unique: true,
required: true,
trim: true,
validate(value) {
if (!validator.isAlphanumeric(value , 'pl-PL')) {
throw new Error('Name cannot contain special characters.')
}
}
},
email: {
type: String,
unique: true,
required: true,
trim: true,
lowercase: true,
validate(value) {
if (!validator.isEmail(value)) {
throw new Error('Email is invalid')
}
}
},
password: {
type: String,
required: true,
validate(value) {
console.log(value)
if(value !== this.password2) {
throw new Error("Passwords don't match. Try again.")
}
if(value.length < 8) {
throw new Error("Passwords is too short. At least 8 characters.")
}
}
},
tokens: [{
token: {
type: String,
required: true
}
}]
})
You don't need to make password2 a part of userSchema. The better way is to make a compare password function like this:
UserSchema.methods.comparePassword = function(plaintext, callback) {
return callback(null, Bcrypt.compareSync(plaintext, this.password));
};
also you can make a use of Schema.pre:
UserSchema.pre("save", function(next) {
if(!this.isModified("password")) {
return next();
}
this.password = Bcrypt.hashSync(this.password, 10);
next();
});
After this, you need to call the compare function from user controller. Something like this (depending on your logic):
var user = await UserModel.findOne({ username: request.body.username }).exec();
if(!user) {
return response.status(400).send({ message: "The username does not exist" });
}
user.comparePassword(request.body.password, (error, match) => {
if(!match) {
return response.status(400).send({ message: "The password is invalid" });
}
});
For details you can read this excellent article.
You can check password and password2 in your register route, and if they are same you can continue to register.
A sample register route would be like this:
router.post("/register", async (req, res) => {
try {
const { username, email, password, password2 } = req.body;
if (password !== password2) return res.status(400).send("Passwords dont match");
let user = await User.findOne({ email });
//or
//let user = await User.findOne({ username });
if (user) return res.status(400).send("User already registered.");
user = new User({ username, email, password });
user = await user.save();
//todo: at this point you may generate a token, and send to the client in response header or body
res.send(user);
} catch (err) {
console.log(err);
res.status(500).send("Server error");
}
});
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();
}
});
I have a pretty basic user model in mongoose. It looks something like this.
var userSchema = new mongoose.Schema({
name: String,
email: String,
username: String,
message: {
active {type: Boolean, default: false},
text: String
}
});
When a user requests a certain page, I use findOne to get data for the user
UserModel.findOne({username: matchLowerCase(req.session.user)}, function (err, doc)
{
if (doc)
{
res.render('main', {
username: doc.username,
//etc
});
}
});
After the user loads the page, I want to set the "message" key to be inactive, like so.
doc.message = {
active: false,
text: ''
}
doc.markModified('message');
doc.save(function (err)
{
console.log('save err', err);
});
For whatever reason, doc.save() is not updating the message key. If I modify any other field in the script, doc.save() works. What am I missing?