How to pass one Schema into another using MongoDB & Node.js - node.js

I am attempting to post comments to a blog post and have these comments as an array for each post.
I have managed to get the comments to post, but I cannot call the information from the post. The comment model has text and author in it but the comment only takes the author part of the model. When the comments show up on the post, they only show the name of the user that has written the comment. I have checked the database and comment.text isn't getting passed through at all.
Comments Create Route
router.post("/", ensureAuthenticated, (req, res) => {
Blog.findById(req.params.id, (err, blog) => {
if(err){
console.log(err);
res.redirect("/blog");
} else {
Comment.create(req.body.comment, (err, comment) => {
if(err){
req.flash("error", "Something went wrong");
console.log(err);
} else {
//add username and id to comment
comment.author.id = req.user._id;
comment.author.name = req.user.name;
comment.author.email = req.user.email;
comment.save();
//save comment
blog.comments.push(comment);
blog.save();
req.flash("success", "Successfully added comment");
res.redirect("/blog/" + blog._id);
}
});
}
});
});
Comment Schema
const mongoose = require("mongoose");
var commentSchema = new mongoose.Schema({
text: String,
author: {
id: {
type: mongoose.Schema.Types.ObjectId,
ref: "User"
},
name: String,
email: String
}
});
module.exports = mongoose.model("Comment", commentSchema);
Part of the Show Page Which Shows The Comment
<div class="card-body">
<% blog.comments.forEach(function(comment){ %>
<div class="row">
<div class="col-md-12">
<strong><%= comment.author.name %></strong>
<span class="float-right">10 days ago</span>
<p class="card-text">
<%= comment.text %>
</p>
<% if(user && comment.author.id.equals(user._id)) { %>
<a class="btn btn-warning btn-sm d-inline-block" href="/blog/<%= blog._id %>/comments/<%= comment._id%>/edit">Edit</a>
<form class="d-inline-block" action="/blog/<%= blog._id %>/comments/<%= comment._id%>?_method=DELETE" method="POST">
<button class="btn btn-danger btn-sm">Delete</button>
</form>
<% } %>
<hr>
</div>
</div>
<% }) %>
</div>
I am expecting the show page to show the text of each comment.

I was missing bodyParser. In case anyone else has this problem, https://stackoverflow.com/a/55793723/8145657 fixed it for me.

Related

how to submit a form on the same post in the same page

So i am trying to submit a form on the same post (kinda like the comments on facebook or youtube where on a post you have a field you populate the field and submit then you are redirected to the same page but the post has a comment or in my case a tag added).
Schema
Tag schema
var mongoose = require("mongoose");
var tagSchema = new mongoose.Schema({
tagDescription: String
});
module.exports = mongoose.model("Tag", tagSchema);
Note Schema
var mongoose = require("mongoose");
var noteSchema = new mongoose.Schema({
title: String,
body: String,
category: String,
created: { type: Date, default: Date.now },
tags: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Tag"
}
]
});
module.exports = mongoose.model("note", noteSchema);
So I tried the following code but whenever i submit a tag the tags are added only to the first post, and if I remove findOne and replace it with find a cannot read property of push of undefined occurs.
this is the index.ejs page
<div class="card-body">
<h2 class="card-title"><%= note.title %></h2>
<div class="card-text-center">
<p><%= note.category %></p>
<p><%= note.body.substring(0,20)%>...</p>
<% note.tags.forEach(function(tag){ %>
<p><%= tag.tagDescription %></p>
<% }) %>
<div class="float-right card-footer">
<small><%= note.created.toDateString() %></small>
</div>
<p>Read More</p>
<form action="/" method="post">
<input class="col-md-2 form-control" type="text" name="tag[tagDescription]" placeholder="Tag" />
<button class="btn btn-primary">Submit</button>
</form>
Routes
app.post("/", function (req, res) {
Note.findOne({}, function (err, note) {
if (err) {
console.log(err);
res.redirect("/notes");
} else {
Tag.create(req.body.tag, function (err, tag) {
if (err) {
console.log(err);
} else {
note.tags.push(tag);
note.save();
res.redirect("/notes");
}
});
}
});
});
app.get("/notes", function (req, res) {
Note.find({}).populate("tags").exec(function (err, notes) {
if (err) {
console.log(err);
} else {
res.render("index", { notes: notes/*, tags: i */});
//console.log(i);
}
});
});
app.get("/notes/new", function (req, res) {
res.render("new");
})
app.post("/notes", function (req, res) {
Note.create(req.body.note, function (err, newNote) {
if (err) {
console.log(err);
} else {
res.redirect("/notes");
}
});
});
form submit for new note/post
<form action="/notes" method="post">
<div class="form-group">
<label>Title</label>
<input class="form-control" type="text" name="note[title]" placeholder="Title" />
</div>
<div class="form-group">
<label>Category</label>
<input class="form-control" type="text" name="note[category]" placeholder="Category" />
</div>
<div class="form-group">
<label>Note content</label>
<textarea class="form-control" name="note[body]" placeholder="Add a new Note..."></textarea>
</div>
<div class="form=group">
<button class="btn btn-primary btn-large btn-block">Submit</button>
</div>
</form>
When posting a tag, the route needs to know which note the tag belongs to. Instead of using findOne(), I would prefer the original solution that you were using by routing to notes/:id/tag and call
Note.findById(req.params.id, ...);
If you insist on posting to '/' as your route, you could pass the noteId as a parameter
<form action="/?noteId=<%= note.id %>" method="post">
and then catch it on your route
Note.findById(req.body.noteId, ...);
The trade offs for using nested resources in REST are discussed well here.
index
<% note.tags.forEach(function(tag){ %>
<div class="badge">
<div class="badge badge-pill badge-info">
<form action="/notes/<%= note._id %>/tags?_method=DELETE" method="post" style="display: inline">
<button class="btn btn-sm">x</button>
</form>
<%= tag.tagDescription %>
</div>
</div>
<% }) %>
<p>Read More</p>
<div class="float-right card-footer">
<small><%= note.created.toDateString() %></small>
</div>
<form action="/notes/<%= note.id %>/tags" method="post">
<input class="col-md-2 form-control" type="text" name="tag[tagDescription]" placeholder="Tag" />
<button class="btn btn-primary">Add Tag</button>
</form>
routes
app.post("/notes/:id/tags", function (req, res) {
Note.findById(req.params.id, function (err, note) {
if (err) {
res.redirect("/notes");
}
else {
Tag.create(req.body.tag, function (err, tag) {
if (err) {
console.log(err);
}
else {
note.tags.push(tag);
note.save();
res.redirect("/notes");
}
});
}
});
});
Note.find({}).populate("tags").exec(function (err, notes) {
if (err) {
console.log(err);
} else {
res.render("index", { notes: notes });
}
});

How to show follow/buttons in ejs

Im trying to make a user profile page where a user can follow another user.
my routes are fine
but then i wanted to ask is if a user clicks on follow. Only the unfollow button will be shown. if i unfollow the follow button will be shown.
these are my codes
router.get('/follow/:id', isLoggedIn, async function(req, res, next) {
try {
const user = await User.findById(req.params.id);
if (!user) {
req.flash("error", "User does not exist");
res.redirect('back')
return res.status(404).json({ error: 'User does not exist' });
}
if (user.followers.indexOf(req.user.id) !== -1) {
req.flash("error",`You're already following ${user.local.username}` )
res.redirect('back')
return res.status(400).json({ error: `You're already following
${user.local.username}` });
}
user.followers.addToSet(req.user.id);
await user.save();
const users = await User.findById(req.user.id);
users.following.addToSet(user.id);
await users.save();
req.flash("success", "User added")
return res.redirect('back')
} catch (err) {
return next(err);
}
});
router.delete('/follow/:id', isLoggedIn, async function(req, res) {
try {
const user = await User.findById(req.params.id);
if (!user) {
return res.status(404).json({ error: 'User not found' });
}
const following = user.followers.indexOf(req.user.id);
if (following === -1) {
return res.status(400).json({ error: `You're not following
${user.username}` });
}
user.followers.splice(following, 1);
await user.save();
const userLogged = await User.findById(req.user.id);
const positionUnfollow = userLogged.following.indexOf(user.id);
userLogged.following.splice(positionUnfollow, 1);
await userLogged.save();
req.flash('success', `You have sucessfully unfollowed
${user.local.username || user.facebook.name}`)
return res.redirect("back");
} catch (err) {
return next(err);
}
});
my ejs page
<div class="follow justify-content-center">
<% user.followers.forEach(function(follower) { %>
<% if(currentUser && currentUser._id.equals(follower.id)) { %>
<form id="delete-btn" action="/users/follow/<%= user.id%>?
_method=DELETE" method="POST">
<button class="btn btn-danger"><i class="fas fa-user-slash">
</i></button>
</form>
<% } %>
<% }) %>
</div>
<% user.following.forEach(function(follow) { %>
<% if(currentUser && follow._id !== user._id) {%>
<a href="/users/follow/<%= user.id %>"><button class="btn btn-
primary">
<i class="fas fa-user-plus"></i></button></a>
<% } %>
<% }) %>
<div class="card-footer text-center">
<div class="social-media">
<i class="fab fa-facebook"></i>
<i class="fab fa-twitter-square"></i>
<i class="fab fa-instagram"></i>
</div>
</div>
</div>
</div>
my user schema
followers: [
{
type: mongoose.Schema.ObjectId, ref: 'User'
}
],
following: [
{ type: mongoose.Schema.ObjectId, ref: 'User'
}
],
the problem now is if i follow a user the unfollow button will appear but the follow button won't go away.
also if i did a <%=user._id%> and <%=follow.id%> the values are the same but not sure why the environment variables didn't work.

Node.js blog post add comment not working

When I add a comment to any blog post it doesn't work. This is a normal Node.js MVC controller which is linked to route:
commentBlog(req, res) {
const comment = {
comment: req.body.comment,
author: req.params.id
}
Comment.create(comment, (error, comment) => {
if (error) {
console.log(error);
} else {
Blog.findById(req.params.id, (error, blog) => {
blog.comments.push(comment);
console.log(comment);
blog.save((error, savedBlog) => {
if (error) {
console.log(error);
} else{
res.redirect('/blogs/' + blog._id);
}
})
})
}
})
};
( this is the model )
--------------------
const mongoose = require('mongoose'),
Schema = mongoose.Schema;
var commentSchema = new Schema({
author: { type: Schema.Types.ObjectId, ref: 'user' },
comment: {type: String},
created: {type: Date, default: Date.now},
blog: { type: Schema.Types.ObjectId, ref: 'blog' }
});
var Comment = mongoose.model('comment', commentSchema);
module.exports = Comment;
this is the ejs file
---------------------
<body>
<% if(blog) { %>
<form action="/blogs/<%= blog._id %>/comment" method="POST">
<textarea name="comment[text]"
rows="10" cols="50">Write something here
</textarea>
<input type="submit" value="Post comment">
</form>
<% } %>
**i don't know why it doesn't display when i add like to any post it just
don't work**
Well, you were not displaying the blog content in your ejs file. Only rendering the form. You should render the blog fields for them to display. Below is an example to show the blog title( provided there is a title field in the blog object).
<body>
<% if(blog) { %>
<h2><%= blog.title %> </h2>
<form action="/blogs/<%= blog._id %>/comment" method="POST">
<textarea name="comment[text]"
rows="10" cols="50">Write something here
</textarea>
<input type="submit" value="Post comment">
</form>
<% } %>
You can then display the other fields in your blog object using the example above.

bcrypt.compareSync is always returning false

I verified that in my db I am saving the username and hash of the password. I am able to retrieve the name from the db, however when I check the password it always returns false. Not sure what is wrong.
Here is my HTML
<div ng-controller="userController">
<div class=user>
<form name="login_form">
<h2 class>Login</h2>
<h3 class = "login_page">UserName</h3>
<input ng-model="user" type="text" ng-minlength="1" required>
<h3 class = "login_page">Password</h3>
<input ng-model="password" type="password" name="password" ng-minlength="4" required>
<input type="submit" value="Login" ng-click="login()" >
<div ng-if ="login_form.$submitted" ng-messages="login_form.password.$error" style="color:maroon" role="alert">
<div ng-message="minlength">Your field is too short</div>
</div>
<p ng-if="error">Username or login is incorrect</p>
</form>
</div>
<div class=user>
<form name = "register_form">
<h2 class>Register</h2>
<h3 class = "login_page">UserName</h3>
<input ng-model="reg.name" type="text" required>
<h3 class = "login_page">Password</h3>
<input ng-model="reg.password" type="password">
<input type="submit" value="Register" ng-click="register()" required >
<div ng-if ="login_form.$submitted" ng-messages="login_form.password.$error" style="color:maroon" role="alert">
<div ng-message="minlength">Your field is too short</div>
</div>
<p ng-if="duplicate">That user name is taken, please choose another</p>
<p ng-if="correct">Registration Succesfull</p>
</form>
</div>
</div>
Here is my controller on the server side
var mongoose = require('mongoose'),
Todo = mongoose.model('Todo');
Login = mongoose.model('Login');
var bcrypt = require('bcrypt');
var name = ""
module.exports = (function(){
return {
save_name:function(req, res){
req.session.user = req.body.user
Login.findOne({name: req.body.user},
function(err, user) {
if(user){
console.log(user.password);
console.log( bcrypt.compareSync(req.body.password, user.password));
res.json({'error': false});
}else {
res.json({'error': true});
}
})
}, //end of save name method
register:function(req, res){
bcrypt.hashSync(req.body.password, bcrypt.genSaltSync(8));
login = new Login({
name:req.body.user,
password: bcrypt.genSaltSync(8)
})
login.save(function(err){
if(err){
res.json({'error': true});
} else {
res.json({'sucess': true})
}
})
} // end of register user function
}
})();
You're saving a generated salt as the password instead of the actual hash itself. Also, explicitly calling genSalt*() is unnecessary. Lastly, you really should use the async functions instead, to avoid unnecessarily blocking the event loop. So with all of this in mind, you may end up with something like:
module.exports = {
save_name: function(req, res) {
req.session.user = req.body.user;
Login.findOne({ name: req.body.user },
function(err, user) {
if (err)
return res.json({ error: true });
bcrypt.compare(req.body.password,
user.password,
function(err, valid) {
res.json({ error: !!(err || !valid) });
});
});
}, // end of save name method
register: function(req, res) {
bcrypt.hash(req.body.password, 8, function(err, hash) {
if (err)
return res.json({ error: true });
login = new Login({
name: req.body.user,
password: hash
})
login.save(function(err) {
res.json({ error: !!err });
})
});
} // end of register user function
};
Despite other answers, if it is still not resolving your issue. Try by applying the toString() when passing the password upon login like this.
req.body.password.toString();
The immediate cause of your bug is in register you should be using bcrypt.hashSync(myPlaintextPassword, saltRounds) instead of genSaltSync. Fixing that should make things "work".
However, you need to recode all this to use the async bcrypt APIs or your application will respond very poorly under load (like crippled and unusable, not just "slow"). General rule: no sync calls in a node.js server.

sails.js 0.10.0-rc4 cannot flash message to ejs view

In server js
if (!user) {
if (isEmail) {
req.flash('error', 'Error.Passport.Email.NotFound');
sails.log.warn('User Email not fond.');
} else {
req.flash('error', 'Error.Passport.Username.NotFound');
sails.log.warn('User name not found.');
}
ejs view
<form role="form" action="/auth/local" method="post">
<input type="text" name="identifier" placeholder="Username or Email">
<input type="password" name="password" placeholder="Password">
<button type="submit">Sign in</button>
</form>
<% if (typeof message!== 'undefined') { %>
<%= message %>
<% } else { %>
You E-mail and passport is correct!
<% } %>
if I input one wrong email or passport,the ejs view donot show any error message,why?
How can I flash the error message to the ejs view?am I do something wrong?
Sorry for my poor English.
Thx.
Actually, req gets passed down to your view automatically, so in your view you can just do:
<%- req.flash('message') %>
You don't need to pass the message down to your view manually.
The flash middleware stores the flash message in a session. You still have to pass it to your view and render it by yourself:
app.get('/flash', function(req, res){
// Set a flash message by passing the key, followed by the value, to req.flash().
req.flash('info', 'Flash is back!')
res.redirect('/');
});
app.get('/', function(req, res){
// Get an array of flash messages by passing the key to req.flash()
res.render('index', { messages: req.flash('info') });
});
Checking for message existence using req.session.flash was giving undefined errors In Sailsjs v0.10.0-rc8.
Here is the solution that I ended up using:
<% var errors = req.flash('error'); %>
<% if ( Object.keys(errors).length > 0 ) { %>
<div class="alert alert-danger">
<button type="button" class="close" aria-hidden="true" data-dismiss="alert">×</button>
<ul>
<% Object.keys(errors).forEach(function(error) { %>
<li><%- JSON.stringify(errors[error]) %></li>
<% }) %>
</ul>
</div>
<% } %>
UPDATE
I figured out how to check for flash message existence before reading the message:
<% if (req.session.flash && req.session.flash.error) { %>
<% var errors = req.flash('error') %>
<div class="alert alert-danger">
<button type="button" class="close" aria-hidden="true" data-dismiss="alert">×</button>
<ul>
<% Object.keys(errors).forEach(function(error) { %>
<li><%- JSON.stringify(errors[error]) %></li>
<% }) %>
</ul>
</div>
<% } %>
I found the following custom validation gist way more helpful that using the above. I imagine you'll eventually want custom validation messages at some point anyway, right?
https://gist.github.com/basco-johnkevin/8436644
Note: I created a file named api/services/ValidationService.js and placed the gist code here. This way I didn't have to require it in the Controller.
In the Controller:
create: function(req, res, next) {
//Create a User with the params sent from the signup form
//
User.create( req.params.all(), function userCreated( err, user ) {
// If there's an error
//
if(err) {
if(err.ValidationError) {
errors = ValidationService.transformValidation(User, err.ValidationError);
sails.log.warn(errors);
req.flash('error', errors);
}
// If error redirect back to the sign-up page
return res.redirect('/user/new');
}
// After successfully creating the user
// redirect the to the show action
res.json(user);
});
}
In my User Model:
module.exports = {
schema: true,
attributes: {
name: {
type: 'string',
required: true
},
title: {
type: 'string'
},
email: {
type: 'string',
email: true,
required: true,
unique: true
},
encryptedPassword: {
type: 'string',
minLength: 6,
required: true
}
},
validation_messages: {
name: {
required: 'You must supply a valid name.'
},
email: {
email: 'You must supply a valid email address.',
required: 'You must supply a valid email address.',
unique: 'An account with this email address already exists.'
},
encryptedPassword: {
minLength: 'Your password must be atleast 6 characters long.',
required: 'You must supply a password that is atleast 6 characters long.'
}
}
};
And here's what I ended up on my User sign up form view. Hmm... There must be a better way.
<% if (req.session.flash && req.session.flash.error) { %>
<% var errors = req.flash('error') %>
<div class="alert alert-danger">
<button type="button" class="close" aria-hidden="true" data-dismiss="alert">×</button>
<ul>
<% Object.keys(errors).forEach(function(error) { %>
<% Object.keys(errors[error]).forEach(function(error_message) { %>
<% Object.keys(errors[error][error_message][0]).forEach(function(error_message_res) { %>
<li>
<%- errors[error][error_message][0][error_message_res] %>
</li>
<% }); %>
<% }); %>
<% }); %>
</ul>
</div>

Resources