Virtual field not setting field in mongoose model - node.js

I am new to nodeJS and mongoose. I am trying to make a user model that does not save a password as plain text. In other backend frameworks you can accomplish this with an ORM by utilizing a virtual field. I looked up the docs for Mongoose and found that this can be accomplished. Following the dics I created the following Mongoose model. Mind you this is not the final implementation and is for merely testing my understanding of how Mongoose handle virtual fields.
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: {type: String, required: true},
email: {type: String, required: true},
passwordHash: {type: String, required: true}
});
userSchema.virtual("password")
.get(() => this._password)
.set(val => {
this._password = val;
console.log("setting: ", val);
this.passwordHash = "test";
})
module.exports = mongoose.model("Users", userSchema);
I also have the following test for this model
it("should not save passwords as plain test", done => {
const user = new User({name: "john", email: "john#example.com", password: "password1234"});
console.log(user);
user.validate(({errors}) => {
expect(errors).to.not.exist
});
done();
});
The test fails because I have an error. The error states that the passwordHash field is missing. I know I have that field as required, but I assign the value "test" to this.passwordHash in the set function just like the docs say to do. This is where I get stuck. Any guidance is much appreciated.

I think problem is with this context in userSchema.virtual("password") function
userSchema.virtual("password")
.get(() => this._password) // this points to global object
.set(val => {
this._password = val; // this points to global object
console.log("setting: ", val);
this.passwordHash = "test";
});
This is one of exceptions when you cant use Arrow function.
userSchema.virtual("password")
.get(function() {
return this._password;
})
.set(function(val) {
this._password = val;
console.log("setting: ", val);
this.passwordHash = "test";
});
Let me know is it working now properly.
My general advice: for hash/check passwords use Schema.pre('save') hook. Eg.:
// before save user
userSchema.pre('save', function(next) {
if (this.isModified('password')) { //only if password is modified then hash
return bcrypt.hash(this.password, 8, (err, hash) => {
if (err) {
return next(err);
}
this.password = hash; //save hash in UserSchema.password in database
next();
});
}
next();
});
Schema.pre is part of middleware. More about middleware in mongoose: http://mongoosejs.com/docs/middleware.html

Related

Mongoose user.save() doesn't return updated _id

I have a simple code that uses mongoose with nodejs to save a user object but when I log the result object that is return from save() method it is just the object I sent and the _id and id values are undefined.
Still, the object is saved correctly in the database with the new _id.
this is my code
`
import { mongoose } from "mongoose";
const Schema = mongoose.Schema;
const userSchema = new Schema({
name: {
type: String,
required: true,
},
age: {
type: Number,
required: true,
},
email: {
type: String,
required: true,
},
});
export const addUser = async (_user) => {
let user = new User({ ..._user });
let result;
try {
result = await user.save();
console.log(result._id);
} catch (err) {
console.log(err);
}
return result;
};
`
I expect console.log(result._id) to have the new generated Id but it is undefined even though it is saved correctly to the database
The solution that worked for me is to assign the _id before calling save()
export const addUser = async (_user) => {
let user = new User({ ..._user });
user._id = mongoose.Types.ObjectId();
let result;
try {
result = await user.save();
console.log(result._id);
} catch (err) {
console.log(err);
}
return result;
};

Error while updating mongo db object by id

I am trying to update my mongodb database by Id but I am getting error userId.save is not a function. What I did was get all the databases data by Object.findById then used Object.assign to assign an updated value to the specified key then saved the updated Object back to the database. Where did I go wrong. How can I update a mongodb object by Id. Thanks in advance.
const Users = require('pathToSchema')
const userId = Users.findById('ObjectId')
Object.assign(userId, '{"email": "test#gmail.com"}')
//error arrises here. "userId.save is not a function"
userId.save()
.then((result) => {
console.log(result)
})
.catch((err) => {
console.log(err)
})
const mongoose = require('mongoose')
const Schema = mongoose.Schema
const users_Schema = new Schema({
name: {
type: String,
required: true
},
email: {
type: String,
required: true
},
password: {
type: String,
required: true
}
}, {timestamps: true})
const Users = mongoose.model('users', users_Schema)
module.exports = Users;
The findById is not execute yet. You have to use it with a callback or an exec(). You can learn more at mogoose doc.
Try change line const userId = Users.findById('ObjectId') to const userId = await Users.findById('ObjectId').exec(). exec() will return a promise, so you could use await to get result.
Furthermore, the Object.assign statement is not correct, there is no need for the string character (which is '). It's just Object.assign(userId, {"email": "test#gmail.com"})
Try assigning the email prop instead of using Object.assign. Also bear in mind that you need to assign 2 objects but you assign a string instead.
Try this:
const userId = await Users.findById('ObjectId')
userId.email = 'test#gmail.com';
userId.save()
.then((result) => {
console.log(result)
})
.catch((err) => {
console.log(err)
})
Also, make sure you create a model from the schema and use it to findById. For instance:
const UserSchema = new Schema({
name:String,
username:{type:String, required:true, index:{unique:true}},
password:{type:String, required:true, select:false}
});
const UserModel = mongoose.model('User', UserSchema);
const user = await UserModel.findById(...);
user.save();
This worked for me.
Users.findById('ObjectId')
.then((result) => {
Object.assign(result, {
"email": "test#gmail.com"
})
result.save()
.then((result) => {
console.log(result)
})
.catch((err) => {
console.log(err)
})
})
.catch((err) => {
console.log(err)
})

Why is the reference not being saved along with the rest of the data?

I am new with MongoDB "relations" and I am trying to save data to a MongoDB database. There are two models, one model is the user and the other model is the authentication data. The data is saved correctly.
import { Schema, model } from 'mongoose'
const stringRequired = {
type: String,
trim: true,
required: true
}
const stringUnique = {
...stringRequired,
unique: true
}
const UserSchema = new Schema({
name: stringRequired,
username: stringUnique,
email: stringUnique,
}, { timestamps: true });
const AuthSchema = new Schema({
email: { type: Schema.Types.ObjectId, ref: 'User' },
salt: stringRequired,
hash: stringRequired,
}, { timestamps: true })
export const userModel = model('User', UserSchema)
export const authModel = model('Auth', AuthSchema)
As you can see, one of the models is referenced by another. The email field has a reference to the user, email being the id that I want to use for authentication. But for some reason, when I save the documents, all the data is sent except the reference.
This is my controller, which as you can see, abstracts the user and the authentication to carry out the business logic and then save it in the database separately.
function add(body: any) {
return new Promise((resolve, reject) => {
if (!body) {
const error = new Error('No body on the request')
reject(error)
} else {
const user = {
username: body.username,
email: body.email,
name: body.name
}
const saltRouds = 10
const salt = bcrypt.genSaltSync(saltRouds)
const hash = bcrypt.hashSync(body.password, salt)
const auth = { salt, hash }
store.add(userModel, user)
store.add(authModel, auth)
resolve('User created')
}
})
}
This is the store.add function.
async function add (collection: any, data: any) {
return await collection.create(data)
}
Note this code is writhed with Typescript.
You're missing the reference key when creating the Auth instance. The "foreign key" in MongoDB is the id of a document that has type Schema.Types.ObjectId and can be accessed with document._id.
So your code should look like:
const auth = { salt, hash };
const user = store.add(userModel, user);
auth.email = user._id;
store.add(authModel, auth);
Be aware that your store.add() function is an async function and you should wait for it's result like #jfriend00 said in the comments.
You can achieve that by making add() also an async funtion and doing:
const auth = { salt, hash };
const user = await store.add(userModel, user);
auth.email = user._id;
await store.add(authModel, auth);
or using the Promise approach by chaining .then(). You can read more about it here.

Data not retrieving while querying from reference mongoose data base schema?

I am not able to find the issue. I am using two schemas user and campaign . I am using mongoose populate method to show campaigns by finding unique userid because i need to show the data of concerned user only but no data come to ejs template and my route also not populating user schema inside campaign schema. I cant find the issue why data is not showing. is population route correct? if yes my ejs template correct format? what is the issue ? lot of confusion and struggling with more than 10 days
My campatb route like this
router.get("/camptab", function(req, res) {
let user = req.user.id;
Campaign.find({ user })
// User.findById(req.user.id)
.populate("userId")
.exec((err, campaign) => {
if (err) {
console.log(err);
return res.status(500).send("Something went wrong");
}
res.render("camptab", { camplist: campaign });
});
});
data storing like this
Campaign data is not visible
I tried giving reference in both schema but it dint work and then tried using ref in single schema still i am facing same. I cant figure out the issue. I want to user can see his campaign data after login in his "/camptab " page
My ejs template
<tbody class="text-center">
<%camplist.forEach(function(camp){%>
<tr>
<td><%=camp.Title%> </td>
<td><%=camp.Description%></td>
<td> <img src="campaign/<%=camp.Banner%>" style="width:100px; height:50px;" alt=""></td>
</tr>
<%})%>
</tbody>
Campaign schema
var mongoose = require('mongoose');
var user= require ("./User")
var Schema = mongoose.Schema;
var campaignSchema = new Schema({
Title: {type: String},
Description: { type: String },
Rules: {} ,
Banner: { type: String },
userId: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
});
module.exports = mongoose.model('Campaigns', campaignSchema);
User schema
const bcrypt = require('bcryptjs');
const crypto = require('crypto');
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: String,
email: { type: String, unique: true },
password: String,
phonenumber: Number,
passwordResetToken: String,
passwordResetExpires: Date,
emailVerificationToken: String,
emailVerified: Boolean,
snapchat: String,
facebook: String,
twitter: String,
google: String,
github: String,
instagram: String,
linkedin: String,
steam: String,
quickbooks: String,
tokens: Array,
profile: {
name: String,
gender: String,
location: String,
website: String,
picture: String
}
});
/**
* Password hash middleware.
*/
userSchema.pre('save', function save(next) {
const user = this;
if (!user.isModified('password')) { return next(); }
bcrypt.genSalt(10, (err, salt) => {
if (err) { return next(err); }
bcrypt.hash(user.password, salt, (err, hash) => {
if (err) { return next(err); }
user.password = hash;
next();
});
});
});
/**
* Helper method for validating user's password.
*/
userSchema.methods.comparePassword = function comparePassword(candidatePassword, cb) {
bcrypt.compare(candidatePassword, this.password, (err, isMatch) => {
cb(err, isMatch);
});
};
/**
* Helper method for getting user's gravatar.
*/
userSchema.methods.gravatar = function gravatar(size) {
if (!size) {
size = 100;
}
if (!this.email) {
return `https://gravatar.com/avatar/?s=${size}&d=blank`;
}
const md5 = crypto.createHash('md5').update(this.email).digest('hex');
return `https://gravatar.com/avatar/${md5}?s=${size}&d=blank`;
};
const User = mongoose.model('User', userSchema);
module.exports = User;
I tried giving reference in both schema but it dint work and then tried using ref in single schema still i am facing same. I cant figure out the issue. I want to user can see his campaign data after login in his "/camptab " page
I tried giving reference in both schema but it dint work and then tried using ref in single schema still i am facing same. I cant figure out the issue. I want to user can see his campaign data after login in his "/camptab " page
In your camptab route
You are not using proper format for fetching the user info from database.
Try,
Campaign.findById(user)
or
Campaign.find({_id : user})
Both of them works. I usually use first one when fetching data using id.

mongoose Model.create function returns undefined

The above query returns a 200 when I try to create a User, but whenever I log into MongoDB there is no collections created. Can anyone help ?
//user model
const userSchema = mongoose.Schema({
name: {
type : String,
required : true,
trim : true
},
email: {
type: String,
required: true,
unique: true,
lowercase: true,
validate: value => {
if(!validator.isEmail(value)){
throw new Error({error : 'Invalid email address'})
}
}
},
password: {
type: String,
required: true,
minLength: 5
},
// a user can have multiple jobs
jobs : [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Job'
}],
tokens: [{
token: {
type: String,
required: true
}
}]
})
const User = mongoose.model('User', userSchema)
module.exports = User
// user functions written
createUser(name, email, password){
return User.create({name: name, email: email, password : password}, (err, docs) => {
if(err){
throw err.message;
}
});
}
//routes.js
// user create
router.post('/users', async(req, res) => {
try{
const {name, email, password } = req.body
const user = userManager.createUser(name, email, password); [1]
res.status(200).json(user)
}
catch(error) {
res.status(400).send({error : error.message})
}
})
The line[1] returns undefined. Why ?
note : all module requirements are fulfilled
After you create the schema you need to create a Model FROM that schema.
Example from MDN:
// Define schema
var Schema = mongoose.Schema;
var SomeModelSchema = new Schema({
a_string: String,
a_date: Date
});
// Compile model from schema
var SomeModel = mongoose.model('SomeModel', SomeModelSchema );
Now after you create the model you can use SomeModel.create
EDIT:
line[1] will always return undefined because you are using callbacks and only way to get value out of callback is either push another callback(I would really discourage that). But best way is to use Promises now mongoose by default supports `Promises. So, basically for promises it will be,
// user functions written
async function createUser(name, email, password){
try {
return await User.create({ name: name, email: email, password: password });
} catch (err) {
throw err.message;
}
}
In the router adda await:
const user = await userManager.createUser(name, email, password);
The problem is you call an asynchronous function synchronously. It returned undefined because the function hasn't been resolved yet.
A solution could be to use promises or async/await.
Example:
async createUser(name, email, password) {
const createdUser = await User.create({name,email,password});
return creaatedUser;
}
Something I ran into was you need to pass in an empty object if your not setting any fields - i.e.
Good: Model.create({})
Bad: Model.create()

Resources