Why is User.follow only available in two view? - node.js

I'm using Passport to authenticate my users and pass their information to every view, beside the information stored in the user database. I'm trying to pass the friendship information related to the user (logged in user). The problem is that this only works in two of my views:
Single User page
//info is the profile information of a registered user
<%= info.profile.fullname %>
<br>
<%= info.email %>
<br>
<%= info.profile.country %>
<br>
<%= user.follow %>// just for testing
<br>
<% if(user && (user._id != info.id)){ %>
<br>
<form action="/follow/<%= info._id %>" method="post">
<button type="submit">Follow</button></form>
<%}%>
<br>
atras
List of Registered User
<%if (user) {%>
<h1>Hola <%= user.profile.fullname %> </h1>
<%}%>
<br>
<% users.forEach(function (user) {%>
<p><%= user.profile.fullname %></p>
<%});%>
<%= user.follow %>// just for testing
Home
This is my index and in this view the user.follow doesn't work
<%if (!user) {%>
sign up
<br>
login
<%} else { %>
<h1>Hola <%= user.profile.fullname %> Eres lo Maximo</h1>
perfil
<br>
Logout
<br>
<%}%>
<br>
Lista de Usuarios
<%= user.follow %>// just for testing
//in this view i got undefined
deserializeUser config.
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
FF.find({ follower:id }, function(err, follow) {
user.follow = follow;
});
done(err, user);
});
});
This is my route code
router.get('/', function(req, res) {
res.render('index');
});
router.get('/login', userController.getLogin);
router.post('/login', userController.postLogin);
router.get('/logout', userController.logout);
router.get('/signup', userController.getSignup);
router.post('/signup', userController.postSignup);
router.get('/profile', passportConf.isAuthenticated, userController.getAccount);
router.post('/profile', passportConf.isAuthenticated, userController.postUpdateProfile);
//Test Usuarios
router.get('/users', friendController.getListUsers);
router.get('/users/:id',friendController.getShowUser);
// Test Follow
router.post('/follow/:id', passportConf.isAuthenticated,friendController.postFollowUser);

Related

How to pass one Schema into another using MongoDB & 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.

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 display all users database (mongodb) details on .ejs file

I have a node.js website that i build with this tutorial:
https://scotch.io/tutorials/easy-node-authentication-setup-and-local
now i want in one of my views to print/display all users data.
in routes.js:
var User = require('../app/models/user'); //the connection to users database
app.get('/profile', isLoggedIn, function (req, res) {
res.render('profile.ejs', {
Users: User // get the user out of session and pass to template
});
});
and this is in my profile.ejs file:
<ul>
<% Users.forEach( function(err, user) { %>
<li><%= user %> </li>
<% }); %>
i get an error: "undefined is not a function" because of the <% Users.forEach( function(err, user) { %> line
what am i doing wrong?
You need to actually query the user collection via the model's find() query function which will return an array of user documents when executed, i.e.
var User = require('../app/models/user'); //the connection to users database
app.get('/profile', isLoggedIn, function (req, res) {
User.find({}).exec(function(err, users) {
if (err) throw err;
res.render('profile.ejs', { "users": users });
}
});
then render the list of user documents in your profile.ejs as:
<% users.forEach(function (user) { %>
<li>
<h1><%= user.name %></h1>
</li>
<% }) %>

Error: can't set header after they are sent

I have two schemas, one for user and one for friendship system (follow system)
module.exports = mongoose.model('User', {
email: {
type: String,
unique: true,
lowercase: true
},
password:String,
profile: {
fullname: String,
gender: String,
role: {type: String, default: 'Autorizado'},
country: String },
});
and this for friend system
module.exports = mongoose.model('Friendship', {
follower: String,
followed: String
});
I'm using passport to auth my users... the problem is that I want lo load the user information and the friend information related to the user globally using res.locals I make this happen using 2 functions in login post route....
router.post('/login', userController.postLogin,friendController.getFollow);
this is my postlogin function
exports.postLogin = function(req, res, next) {
passport.authenticate('local', function(err, user, info) {
if (err) return next(err);
if (!user) {
req.flash('errors', { msg: info.message });
return res.redirect('/login');
}
req.logIn(user, function(err) {
if (err) return next(err);
req.flash('success', { msg: 'Success! You are logged in.' });
res.redirect('/');
});
})
(req, res, next);
};
When I try to execute this code everything goes fine but when I log in I get the header error please how can I solved this or other way to get the user and follow information and use it in every view?.
EDIT 3: only works in the next two views
Single User page
<%= info.profile.fullname %>
<br>
<%= info.email %>
<br>
<%= info.profile.country %>
<br>
<%= user.follow %>
<br>
<% if(user && (user._id != info.id)){ %>
<br>
<form action="/follow/<%= info._id %>" method="post">
<button type="submit">Follow</button></form>
<%}%>
<br>
atras
List of Registered User
<%if (user) {%>
<h1>Hola <%= user.profile.fullname %> </h1>
<%}%>
<br>
<% users.forEach(function (user) {%>
<p><%= user.profile.fullname %></p>
<%});%>
Home
This is my index and in this view the user.follow doesn't work
<%if (!user) {%>
sign up
<br>
login
<%} else { %>
<h1>Hola <%= user.profile.fullname %> Eres lo Maximo</h1>
perfil
<br>
Logout
<br>
<%}%>
<br>
Lista de Usuarios
EDIT 4: new deserializeUser config.
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
FF.find({ follower:id }, function(err, follow) {
user.follow = follow;
});
done(err, user);
});
});
EDIT 5: This is my route code
router.get('/', function(req, res) {
res.render('index');
});
router.get('/login', userController.getLogin);
router.post('/login', userController.postLogin);
router.get('/logout', userController.logout);
router.get('/signup', userController.getSignup);
router.post('/signup', userController.postSignup);
router.get('/profile', passportConf.isAuthenticated, userController.getAccount);
router.post('/profile', passportConf.isAuthenticated, userController.postUpdateProfile);
You had friendController.getFollow which would've worked only for the request of '/login' page, so the follow object wouldn't have been persistently available on other routes. To fix that you had to put all that code inside the passport.deserializeUser function.
Inside the passport.deserializeUser function you could only call done once so you had to put the logic to find follow inside the callback of User.find so that you could attach follow to user.
This allowed you to remove the friendController.getFollow from your '/login' route which I think was the cause of your error.

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