Validation with MongooseJS causing NodeJS app to crash - node.js

I'm trying to find a better way of performing this validation. I have the user schmea setup and I'm trying to get the age validation working properly as to not cause the app to crash. You'll have to forgive me as I'm still relatively new to the language, so I may not be explaining it 100%. However, here is the User schema I created.
const mongoose = require('mongoose')
const validator = require('validator')
const bcrypt = require('bcryptjs')
const jwt = require('jsonwebtoken')
const userSchema = new mongoose.Schema({
name: {
type: String,
required: true,
trim: true
},
age: {
type: Number,
default: 0,
validate(value) {
if(value < 13){
throw new Error('You must be over the age of 13 to register for this site!')
}
}
},
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,
trim: true,
minlength: 7,
validate(value){
if (value.toLowerCase().includes('password')) {
throw new Error('Password cannot contain "password"')
}
}
},
tokens: [{
token: {
type: String,
required: true
}
}]
})
userSchema.virtual('tasks', {
ref: 'Task',
localField: '_id',
foreignField: 'owner'
})
userSchema.methods.generateAuthToken = async function () {
const user = this
const token = jwt.sign({ _id: user._id.toString() }, 'thisismynewcourse')
user.tokens = user.tokens.concat({ token })
await user.save()
return token
}
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({ email })
if (!user) {
throw new Error('Unable to login')
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw new Error('Unable to login')
}
return user
}
//Hash the plain text password before saving
userSchema.pre('save', async function(next) {
const user = this
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8)
}
next()
})
userSchema.methods.toJSON = function () {
const user = this
const userObject = user.toObject()
delete userObject.password
delete userObject.tokens
return userObject
}
const User = mongoose.model('User', userSchema)
module.exports = User
The exact area that I'm trying to hone in on is in the age section, I'm trying to validate ages 13 or older, and when I run a test user creation through post man it performs the validation correctly, but it stops the application with the following:
UnhandledPromiseRejectionWarning: ValidationError: User validation failed: age: You must be over the age of 13 to register
Is there a way that I can prevent the application from crashing or should I perform the validation else where? Thanks in advance.

Normally the validation is performed in another file. This can be considered to be a service. But it should pass through a controller first if you want to do it properly. Here is an example of a simple blog post schema I made. You can see the function at the bottom runs every time before I send it to the database.
This is how it looks like in my schema file looks like which is located in folder called models.
// Requiring modules
const mongoose = require('mongoose');
// Initializing Schema
var Schema = mongoose.Schema;
// Creating a data model
const schema = new Schema({
shopname : {type: String, required:true},
address : {type: String, required:true},
review : {type: String, required:false},
image : {type: String, required:false},
originalname: {type: String, required:false},
filename: {type: String, required:false},
mimetype: {type: String, required:false},
size : {type: String, required:false},
updatedAt: {type: Date, required:false},
createdAt: {type: Date, required:false}
})
// Settings up the process before the data is sent to mongoDB.
// This process is call everytime 'save' is called.
// it sets the data for createdAt and updatedAt.
schema.pre('save', function(next){
if (!this.createdAt){
this.createdAt = new Date();
}else{
this.updatedAt = new Date();
}
next();
})
// Exports module
module.exports = mongoose.model("Blog", schema);

Related

Circular Dependency Error for deleteMany MongoDB

I am writing the Model for my Web App API, and am getting the following circular dependency error:
Warning: Accessing non-existent property 'deleteMany' of module exports inside circular dependency
(Use node --trace-warnings ... to show where the warning was created)
.
Here is my code:
const validator = require('validator')
const bcrypt = require('bcrypt')
const jwt = require('jsonwebtoken')
const Task = require('./task')
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const userSchema = new Schema({
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,
trim: true,
minLength: 8
},
name: {
type: String,
unique: true,
required: true,
trim: true
},
tokens: [{
token: {
type: String,
required: true
}
}]
})
userSchema.pre('save', async function(next) {
const user = this
if (user.isModified('password')) {
user.password = await bcrypt.hash(user.password, 8)
}
next() // run the save() method
})
userSchema.pre('deleteOne', {document: true, query: false}, async function(next) {
const user = this
await Task.deleteMany({owner: user._id})
next()
})
userSchema.methods.toJSON = function() {
const user = this
const userObject = user.toObject()
delete userObject.password
delete userObject.__v
delete userObject.tokens
return userObject
}
userSchema.methods.generateAuthToken = async function () {
const user = this
const token = jwt.sign({ _id: user._id.toString() }, process.env.JSON_WEB_TOKEN_SECRET)
user.tokens = user.tokens.concat({ token })
await user.save()
return token
}
userSchema.statics.findByCredentials = async (email, password) => {
const user = await User.findOne({email})
if (!user) {
throw new Error('Unable to login')
}
const isMatch = await bcrypt.compare(password, user.password)
if (!isMatch) {
throw new Error('Unable to login')
}
return user
}
userSchema.virtual('tasks', {
localField: '_id',
foreignField: 'owner',
ref: 'Task'
})
const User = mongoose.model('User', userSchema);
module.exports = User
Any idea what could be going wrong? I have checked my Node.js and MongoDB versions and updated them, but continue to get this same error when I try to delete. I can provide further details of my code if necessary. The problem area in question is the one leading with userScheme.pre('deleteOne'....

When making a POST request for a subdocument it comes back as undefined? mongoose, express

I'm trying a to make a post request to save new data to one of my subdocuments, but I'm getting an error when trying to access the subdocument in the function. It keeps coming back as undefined. How can I get a specific user by id and create and add new data the one it's subdocuments?
model
const mongoose = require("mongoose");
const Schema = mongoose.Schema;
const ClassworkSchema = new Schema({
name: String,
time: Date,
todo: String,
isDone: false
});
const OutcomesSchema = new Schema({
name: String,
time: Date,
todo: String,
isDone: false,
isApproved: false
})
const MeetupSchema = new Schema({
name: String,
time: Date,
location: String,
attended: false
})
const UserSchema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
},
classwork:{type: [ClassworkSchema], default: []},
outcomes: [OutcomesSchema],
meetups: [MeetupSchema],
});
module.exports = User = mongoose.model('users', UserSchema);
controller
classworkRouter.post("/:userId/", (req, res) => {
User.findById(req.params.user_id, (err, user) => {
if (err) return err;
new_classwork = new classwork();
(new_classwork.name = req.body.name),
(new_classwork.date = req.body.date),
(new_classwork.todo = req.body.todo),
(new_classwork.isDone = req.body.isDone);
console.log(new_classwork);
user.classwork = {};
user.classwork.name = req.body.classwork.name;
user.classwork.todo = user.classwork.todo;
if (user.classwork === undefined) {
user.classwork.push(new_classwork);
} else {
user.classwork = [new_classwork];
}
user.save(function (err, data) {
if (err) res.send(err);
res.json({ message: "work added", data: data });
});
});
});
you can see the error in the terminal in the following phto:
in this part of code
new_classwork = new classwork()
you shoud defined the new_classwrok like this :
let new_classwork = new classwork()
and new classwork() is not defined, you must to require Model of classwork in controller..
in schema file export schemas like this :
const User = mongoose.model('users', UserSchema);
const Classwork = mongoose.model('Classwork', ClassworkSchema );
module.exports = {
User : User ,
Classwork : Classwork
}
in controller.js
const {User} = require('../models/certification');
const {Classwork } = require('../models/certification');
after require models you can use new Crosswork like this :
note: Classwork with uppercase character
let new_classwork = new Classwork()

MongoDB/Mongoose some of the model properties undefined in app.js

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

how to return the user email

I have created post route to store posts in the database. It's a protected route so user can store post only after entering the login details. When I post in postman, I've seen that the user email is not returned in the object. Even in the mongodb collection, I don't see the email associated with the post. How do I include the email as well with the post object. I don't want the user to enter the email again and again when posting because they have already logged in. So I kinda want to store the email automatically with the post. Hope I make sense. Can someone help me with this?
Right now the object is kinda stored like this in the posts collection in mongodb
_id: ObjectId("5f1a99d3ea3ac2afe5"),
text: "It is a long established fact that a reader will be distracted by the readable content of a page when looking at its layout. ",
user:ObjectId("5f1a99d3eac2c82afe5"),
age:20,
country:"India",
gender:"male",
date:2020-07-24T08:23:35.349+00:00,
__v:0
I want the email too in the above object.
Post model
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const PostSchema = new Schema ({
user: {
type: Schema.Types.ObjectId,
ref: 'users'
},
text: {
type: String,
required: true
},
name: {
type: String
},
email: {
type: String
}
,
age: {
type: Number,
required: true
},
gender: {
type: String,
required: true
},
country: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = Post = mongoose.model('post', PostSchema)
post route
const express = require('express');
const router = express.Router();
const auth = require('../../middleware/auth')
const { check, validationResult} = require('express-validator');
const User = require('../../models/User')
const Post = require('../../models/Post')
router.post('/', [auth, [
check('text', 'Text is required').not().isEmpty()
]], async (req,res)=>{
const errors = validationResult(req);
if(!errors.isEmpty()){
return res.status(400).json({errors: errors.array()})
}
try {
const user = await (await User.findById(req.user.id)).isSelected('-password')
const newPost = new Post({
text: req.body.text,
name: user.name,
user: req.user.id,
age: req.body.age,
country: req.body.country,
gender: req.body.gender,
email: req.user.email // this email is not stored with the post and I want this to be automatically posted in the collection without the user having to type it again to save the post
})
const post = await newPost.save();
res.json(post);
} catch (err) {
console.error(err.message);
res.status(500).send('Server Error')
}
})
module.exports = router;
User model
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true,
unique: true
},
password: {
type: String,
required: true
},
date: {
type: Date,
default: Date.now
}
})
module.exports = User = mongoose.model('user', UserSchema);
Change isSelected to select
const user = await (await User.findById(req.user.id)).isSelected(password')
What I potentially see the problem here is, once you have grabed the object of user, you're still referring to req.user.email instead of user.email.
If that does not solve your problem, try to console.log the user returned from after User.findById
Update:
You can see here that isSelected returns boolean. So you're essentialy getting true for having password field in user. Also instead of req.user.email use user.email

mongoose-unique-validator ReferenceError: User is not defined

I'm new to Node.js so I can't understand what I need to add, to make the default example from mongoose-unique-validator work. Here it is:
const mongoose = require('mongoose'),
uniqueValidator = require('mongoose-unique-validator'),
userSchema = mongoose.Schema({
username: { type: String, required: true, unique: true },
room: { type: String, required: true },
});
userSchema.plugin(uniqueValidator);
const user = new User({ username: 'JohnSmith', room: 'asd');
user.save(function (err) {
console.log(err);
});
The part with the user is not working, because of ReferenceError: User is not defined.
As far as I understand, the user part is the part that the library user should define, but I don't know what should be in there to make it work.
TL; DR:
I just want to make an example work.
Thanks.
Update:
Ok, so I've added this line of code:
const User = mongoose.model('Model', userSchema);
and it does not trows an error anymore. But it does not notify that username is not unique. It does not work yet. I want to check a valid username for the room. And that's all.
you didn't define your model collection ("USER") try this:
const mongoose = require('mongoose'),
uniqueValidator = require('mongoose-unique-validator'),
userSchema = mongoose.Schema({
username: { type: String, required: true, unique: true },
room: { type: String, required: true },
});
userSchema.plugin(uniqueValidator);
module.exports.User = mongoose.model('User', UserSchema);
then :
const user = new User({ username: 'JohnSmith', room: 'asd');
user.save(function (err) {
console.log(err);
})

Resources