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'....
Related
I tryed myself on a little NextJS App with Mongoose and JWT (json web token).
Everything works and I can put user into my database and other stuff. Just when I create a JWT I can't put it into my database. Here is my code, grateful for every help :D
First my schema for mongoose:
const mongoose = require('mongoose');
const UserSchema = new mongoose.Schema({
username: {
type: String,
required: [true, 'Please add a title'],
unique: true,
maxlength: [15, 'Username cannot be more than 15 characters']
},
password: {
type: String,
required: true,
maxlength: [100, 'Password cannot be more than 100 characters']
},
jwt: {
type: String,
required: true,
maxlength: [1000, 'JWT cannot be more than 200 characters']
}
})
module.exports = mongoose.models.User || mongoose.model('User', UserSchema);
I don't know if type: String for jwt is correct, but JSON looks weird too and doesn't work.
Now my backend API Code:
import dbConnect from '../../../utils/dbConnect';
import User from '../../../models/User';
import sjcl from 'sjcl';
var jwt = require('jsonwebtoken');
const hashword = "censored"
dbConnect();
export default async (req, res) => {
var passwordtohash = req.body.password + hashword
const BitArrayHash = sjcl.hash.sha256.hash(passwordtohash);
const passwordhashed = sjcl.codec.hex.fromBits(BitArrayHash)
var bodywithhash = req.body
bodywithhash.password = passwordhashed
const { method } = req;
switch(method) {
case 'POST':
try {
const user = await User.find({"username": bodywithhash.username, "password": bodywithhash.password});
if (user.length > 0) {
createJWT(user);
res.status(200).json({ success: true })
} else (res.status(200).json({ success: false}))
} catch (error) {
res.status(400).json({ success: false });
}
break;
default:
res.status(400).json({ success: false });
}
}
async function createJWT(user) {
jwt.sign({
exp: Math.floor(Date.now() / 1000) + (60 * 60),
data: 'foobar'
}, 'secret')
const iduser = { "_id" : user[0]._id.toString()}
const updateuser = await User.findByIdAndUpdate(iduser, jwt);
}
All my users in the database have a default value for JWT called "default"
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);
I am trying to save a user to MongoDB database using post request as follow, but I got the error TypeError: User is not a function. I can't figure out anything wrong with it. The output says that error is present on the line " const user = new User(req.body);"
postman output nodejs output
is my userschema wrong or the export method is wrong.
const User = require("../models/user");
exports.signup = (req, res) => {
const user = new User(req.body);
user.save((err, user) => {
if (err) {
return res.status(400).json({
err: "NOT able to save user in DB"
});
}
res.json({
name: user.name,
email: user.email,
id: user._id
});
});
};
exports.signout = (req, res) => {
res.json({
message: "User signout"
});
};
//user schema
var mongoose = require("mongoose");
const crypto = require('crypto');
const uuidv1 = require('uuid/v1');
var userSchema = new mongoose.Schema({
name:{
type:String,
required:true,
maxlenght:32,
trim: true
},
lastname:{
type: String,
maxlenght:32,
trim: true
},
email:{
type: String,
trim: true,
required: true,
unique:true
},
userinfo:{
type:String,
trim:true
},
encry_password:{
type:String,
required: true,
},
salt:String,
role:{
type:Number,
default:0,
},
purchases :{
type:Array,
default:[]
}
} ,{ timestamps: true } );
userSchema.virtual("password")
.set(function(password){
this._password = password;
this.salt = uuidv1();
this.encry_password = this.securePassword(password);
})
.get(function(){
return this._password;
})
userSchema.methods = {
authenticate: function(plainpassword){
return this.securePassword(plainpassword) === this.encry_password;
},
securePassword: function (plainpassword){
if(!plainpassword) return "";
try {
return crypto.createHmac('sha256',this.salt)
.update(plainpassword)
.digest('hex');
} catch (err) {
return "";
}
}
};
module.export = mongoose.model("User",userSchema)
Here is the issue with the code.
Line 1: const user = new User(req.body);
Line 2: user.save((err, user) => {
JS is now confused about user which is a constant variable to that of the user which is the return value of the save action.
So, to get rid of this, rename the return value of save action to something else like responseUserObj. Thus your above two lines of code should now be
const user = new User(req.body);
user.save((err, responseUserObj) => {
Happy coding.
I am using mongoose v5.2.17.
I was wondering is it possible to have multiple models map to the 1 schema.
For example - I have the following model
const mongoose = require('mongoose');
const validator = require('validator');
const jwt = require('jsonwebtoken');
const _ = require('lodash');
const bcrypt = require('bcryptjs');
const UserSchema = new mongoose.Schema({
email: {
type: String,
required: true,
trim: true,
minlength: 1,
unique: true,
validate: {
validator: validator.isEmail,
message: '{VALUE} is not a valid email',
},
},
password: {
type: String,
required: true,
minlength: 6,
},
isTrialUser: {
type: Boolean,
default: true,
},
isAdminUser: {
type: Boolean,
default: false,
}
});
UserSchema.methods.toJSON = function () {
const user = this;
const userObject = user.toObject();
return _.pick(userObject, ['_id', 'email', 'isTrialUser']);
};
UserSchema.pre('save', function (next) {
const user = this;
if (user.isModified('password')) {
bcrypt.genSalt(10, (err, salt) => {
bcrypt.hash(user.password, salt, (hashErr, hash) => {
user.password = hash;
next();
});
});
} else {
next();
}
});
const User = mongoose.model('User', UserSchema);
module.exports = { User, UserSchema };
Is it possible for me to create another AdminModel where admin specific methods can live?
I also want to return all data from the toJSON method from the AdminModel.
Please let me know if this is possible or if there is a better way to perform such a task
Thanks
Damien
If I am understanding you correctly you want to inherit the UserModel in an AdminModel and decorate that one with extra methods etc. For that you can use util.inherits (or the so called Mongoose discriminators) like so:
function BaseSchema() {
Schema.apply(this, arguments);
this.add({
name: String,
createdAt: Date
});
}
util.inherits(BaseSchema, Schema);
var UserSchema = new BaseSchema();
var AdminSchema = new BaseSchema({ department: String });
You can read more about it in Mongoose docs.
There is also a good article on the mongoose discriminators here
I'm pretty new with nodejs and mongoDB. I have created a registration and user schema but it doesn't recognize this and send the following error:
ReferenceError: userModel is not defined
When I trace the error, I found that it doesn't recognize this keyword.
Here is user.js code:
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt');
var userSchema = new Schema({
teamName: {
type: String,
unique: true,
trim: true,
required: true
},
faculty: {
type: String,
required: true
},
email: {
required: true,
unique: true,
trim: true,
type: String
},
password: {
required: true,
type: String
},
score: {
type: Number,
default: 0
}
});
userSchema.pre('save', function(next) {
var user = this;
bcrypt.hash(user.password, 10, (err, hash) => {
if (err) return next(err)
user.password = hash;
next();
});
})
var userModel = mongoose.model('User', userSchema);
module.exports = userModel;
server.js
router.post('/register', (req, res) => {
var newUser = {
teamName: req.body.teamName,
faculty: req.body.faculty,
email: req.body.email,
password: req.body.password
}
userModel.create(newUser, (err, user) => {
if (err) {
console.log('[Registratoin]: ' + err);
} else {
console.log(user)
console.log('[Registration]: Done');
// req.session.userID = user._id;
res.redirect('/scoreboard')
}
});
});
The this keyword in the pre-save hook in your model is not the issue.
ReferenceError: userModel is not defined
ReferenceError: "x" is not defined means that the userModel is undefined from the scope you're calling it from. (Thus you're referencing a non-existent value)
I have include it like var userModel = require('the_path')
As you're requiring and exporting the userModel correctly. I would double check the path you're importing the module from.