Data not retrieving while querying from reference mongoose data base schema? - node.js

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.

Related

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()

Mongoose Populate in Node JS/express js

I'm trying to link between two mongoose collections. So that user's information are stored in a collection named user, and his content is stored in another collection.
So far I copied some code from here and edited it;
then with Postman, I created a username named "example";
then I created a post with a random content, and as author I set it "example", but it seems that it doesn't work, when I get '/test' it logs :
Populated User { posts: [], _id: 5c530cd4ede117109cf1a5e9,
username: 'example', __v: 0 }
posts is empty as you see, what should I change in order to fix that?
const UserSchema = new mongoose.Schema({
username: String,
posts: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Post'
}]
})
const PostSchema = new mongoose.Schema({
content: String,
author: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User'
}
})
function getUserWithPosts(username){
return User.findOne({ username: username })
.populate('posts').exec((err, posts) => {
console.log("Populated User " + posts);
})
}
const Post = mongoose.model('Post', PostSchema, 'posts');
const User = mongoose.model('User', UserSchema, 'users');
app.post('/testUser', (req, res) => {
var username = req.body.username;
var clientModel = new User({
username: username,
});
clientModel.save(function (error) {
if (error) {
console.log(error);
}
res.send({
success: true,
username: username,
message: 'account saved successfully!'
});
});
});
app.post('/testPost', (req, res) => {
var content = req.body.content;
var author = req.body.author;
var clientModel = new Post({
content: content,
author: author
});
clientModel.save(function (error) {
if (error) {
console.log(error);
}
res.send({
success: true,
content: content,
author: author,
message: 'account saved successfully!'
});
});
});
app.get('/test', (req, res) => {
res.send({
posts: getUserWithPosts("example")
})
});
EDIT
I see whats going on here now. It appears you have an issue of circular references. Your user.posts references the posts that the user created. The posts.author references the author who created the post. If you want to make this work, you will have to go in and update the user object, after you create the post object, and pass in the post._id.
You can do one other, better thing. You can use virtuals. Mongoose can calculate values run time that reference other collections. This would be my suggestion. The downside is, virtuals are not stored in the database, and you have to explicitly populate this field every time you want it. Heres an example:
User Model
const UserSchema = new mongoose.Schema({
username: String
})
UserSchema.virtual('posts', {
ref: 'Post',
localField: 'username',
foreignField: 'author',
justOne: false
});
This will create the virtual, and whenever you find a user and call .populate('posts'), you will get a array of posts back which the user has authored.
Read more about mongoose virtuals here: http://thecodebarbarian.com/mongoose-virtual-populate

Implement follow/unfollow like action using Node + Express.js and MongoDB

I'm trying to design a Twitter style API. I've come up with the following Schema:
User collection
username
password
Followers collection
follower_id
followee_id
start_date
end_date
Eg:
{
follower_id: 1235
followee_id: 1024
start_date: November 3, 2018
}
which means user 1024 follows user 1235.
Absence of end_date indicates that the user is still following.
Tweets collection
text
date_created
user_id
user_id will be a reference to User collection.
How can I achieve follow/unfollow action with above schema?
For the user collection, you could have a following[] array. As in, you put each user id that they are following in the array and then remove it when they unfollow a person. Then with express, you can handle a post request sent by the front end with express. Of course you would need a boolean to say like:
// To show the button saying "follow" or "unfollow"
var followingArray = [123414, 312456, 656465];
var following = false;
if (followingArray.indexOf(userid)) {
following = true;
} else {
following = false;
}
Obviously, this is just a concept and you would need other code from mongoose or the regular mongodb module to get the user id. This would also decide whether or not the post request link is going to be http://myapp.com/follow or http://myapp.com/unfollow. Based on these two post requests, you would handle the data like the user id with body-parser and express:
var bodyParser = require('body-parser');
const express = require('express');
const app = express();
const port = 8080
app.use( bodyParser.json() ); // to support JSON-encoded bodies
app.use(bodyParser.urlencoded({ // to support URL-encoded bodies
extended: true
}));
app.post('/follow', (req, res) => {
//handle data here for a request to follow someone
var userid = req.body.userid;
})
app.post('/unfollow', (req, res) => {
//handle data here for a request to unfollow someone
var userid = req.body.userid
})
app.listen(port, () => {
console.log(`Server running on ${port}`);
})
Then you would use this data with mongoose or the mongodb module to remove or add that user id from the array and change the following boolean to true or false. Then if you wanted to add other features like a followers list or a following list, you could use the user id to do so. I am not giving exact code on how to do this as there are many concepts involved. I am just trying to give you the concepts that you would use in doing so.
I recommend this:
Keep the database as "flat" as possible. That means fewer collections.
You'd probably need only 2 collections: users and tweets
In your users schema should look something like this:
// Asuming you use mongoose, if you haven't heard about it, you should look it up.
const userSchema = new Schema({
username: {type: String, required: true, trim: true},
password: {type: String, required: true},
following: {type: Array}, // Store all user id of the people that this user is following in an array.
followers: {type: Array}, // Store all users id of this user's followers in an array.
tweets: {
type: Schema.Types.ObjectId, // Store all tweets by this user in an object.
ref: "Tweet" // Link to Tweet Schema
}
}, {_id: true});
Your tweets schema:
const tweetSchema = new Schema({
text: {type: String, required: true}
// You can also have a userId to store the id of the user whose this tweet belongs to
}, {_id: true});
You then can use create to create users or tweets, update to edit following or followers.
Obviously there are several ways to achieve what you want. This is just one of them.
Following Solution Works for me:
const mongoose = require("mongoose");
const userSchema = new mongoose.Schema(
{
name: {
type: String,
trim: true,
},
following: {
type: Array
},
followers: {
type: Array
},
},
{
timestamps: true,
}
);
const User = mongoose.model("User", userSchema);
module.exports = User;
router.patch("/api/v1/follow/", auth, async (req, res) => {
try {
let whomFollowed = await User.findByIdAndUpdate({ _id: req.body.followingId},
{ $push: { following: req.body.followerId } }
);
let whoFollowedMe = await User.findByIdAndUpdate({ _id: req.body.followerId },
{ $push: { followers: req.body.followingId } }
)
return res.status(200).send({ message: "User Follow Success"});
} catch (e) {
return res.status(500).send({ message: "User Follow Failed", data: e.message });
}
});
router.patch("/api/v1/unfollow/", auth, async (req, res) => {
try {
let whomUnFollowed = await User.findByIdAndUpdate({ _id: req.body.followingId },
{ $pull: { following: req.body.followerId } }
);
let whoUnFollowedMe = await User.findByIdAndUpdate({ _id: req.body.followerId },
{ $pull: { followers: req.body.followingId } }
)
return res.status(200).send({ message: "User UnFollow Success"});
} catch (e) {
return res.status(500).send({ message: "User UnFollow Failed", data: e.message });
}
});

Virtual field not setting field in mongoose model

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

node js passport js change user's password

I'm creating an application using node js. in this application i already completed user login and registration via passport js. So now i need to provide access to the logged user to change there password. So i'm trying to do this in my own way but when i run this process the changed password doesn't updated and save it to the logged user's mongoose document. I'll provide the code that i used to that process. So i'm requesting you guys please let me know how can i do this in with my program.
This is my POST route for the change password.
app.post('/changePass/:hash', isLoggedIn, function(req, res){
cph.findOne({hash: req.params.hash}).populate('userId', "local.password -_id").exec(function(err, hash){
if(err) throw err;
if(validator.isEmpty(req.body.currentPassword) || validator.isEmpty(req.body.newPassword) || validator.isEmpty(req.body.confirmPassword)){
res.render('admin/settings/pages/cup/cpf', {
user: req.user,
message: 'Fields must be required',
data: hash
});
}
else {
if(!bcrypt.compareSync(req.body.currentPassword, hash.userId.local.password)){
res.render('admin/settings/pages/cup/cpf', {
user: req.user,
message: 'Current password is incurrect',
data: hash
});
}
else {
if(req.body.newPassword != req.body.confirmPassword){
res.render('admin/settings/pages/cup/cpf', {
user: req.user,
message: 'New password and confirm password do not match',
data: hash
});
}
else {
cph.update({$set:{'userId.local.password': bcrypt.hashSync(req.body.confirmPassword, bcrypt.genSaltSync(8), null)}}, function(){
console.log('Success')
});
}
}
}
});
});
This is the mongoose collection that creating a hash to change the password sending as a combined link to the logged user's email.
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');
var cpHashSchema = Schema({
userId: {
type: Schema.ObjectId,
ref: 'users'
},
hash: {
type: String
}
});
module.exports = mongoose.model('changepasswordHash', cpHashSchema);
This is the user's collection
var mongoose = require('mongoose');
var Schema = mongoose.Schema;
var bcrypt = require('bcrypt-nodejs');
var userSchema = Schema({
active: {
type: Boolean,
default: false
},
first: {
type: String
},
last: {
type: String
},
email: {
type: String
},
local: {
username: {
type: String
},
password: {
type: String
}
},
joined: {
type: Date,
default: Date.now
},
usertype: {
type: String,
default: 'user'
}
});
userSchema.methods.generateHash = function(password) {
return bcrypt.hashSync(password, bcrypt.genSaltSync(8), null);
};
userSchema.methods.validPassword = function(password) {
return bcrypt.compareSync(password, this.local.password);
};
module.exports = mongoose.model('users', userSchema);
These are the source code that i'm using to build this application. So guys please help me to complete this application.
thank you
First of all - you trying to update changepasswordHash collection with fields from another table. MongoDB couldn't update related records.
You have to update users collection using userId something like:
users.update({_id: hash.userId._id}, {$set: {'local.password': newPass}}, callbackHere)

Resources