Find MongoDB document from id posted as hidden input - javascript

How can I find the document matching the ID being posted in a hidden input?
Here is my schema:
var MessageSchema = Schema({
name: {type: String, required: true},
message: {type: String, required: true},
replies: [{ type: Schema.Types.ObjectId, ref: 'Comment' }]
}, {timestamps: true});
Here is my form:
<% for(var i=0; i<messages.length; i++) { %>
<form action="/comment/create" method="post">
<label>Name: </label>
<input type="name" name="name">
<label>Comment: </label>
<input type="text" name="comment">
<input type="hidden" name="replyTo" value=<%= messages[i]['_id']%> >
<button>Reply</button>
</form>
<% } %>
And my post route is a mess so at this point I just want to know how to find the dang message.
app.post('/comment/create', function(req, res) {
console.log(req.body.replyTo);
var message = Message.find({ _id: req.body.replyTo });
console.log(message);
res.redirect('/');
})
console.log(req.body.replyTo) returns the id 59022ff22951ce73ed9bb773.
console.log(message) returns undefined.

The call to Messages.find is asynchronous, and therefore will not provide a proper value when assigning it's result to the variable message. You need to handle this either with a callback or a Promise:
As a callback
Message.find({ _id: req.body.replyTo }, (err, res) => {
console.log(res); // message
});
As a Promise
Message.find({ _id: req.body.replyTo })
.then((res) => {
console.log(res); // message
})
.catch((err) => {
// ...
});

Related

Express.js with EJS application bug: the view throws a "Cannot read property 'toString'" error

I am working on a blogging application (click the link to see the GitHub repo) with Express, EJS and MongoDB.
I have Posts and Post Categories, each in its own collection.
The Categories Schema:
const mongoose = require('mongoose');
const categorySchema = new mongoose.Schema({
cat_name: {
type: String,
required: true
},
updated_at: {
type: Date,
default: Date.now()
},
created_at: {
type: Date,
default: Date.now()
}
});
module.exports = mongoose.model('Category', categorySchema);
The Posts schema:
const mongoose = require('mongoose');
const postSchema = new mongoose.Schema({
title: {
type: String,
required: true
},
short_description: {
type: String,
required: true
},
full_text: {
type: String,
required: true
},
category: {
type: mongoose.Schema.Types.ObjectId,
ref: 'category'
},
post_image: {
type: String,
required: false
},
updated_at: {
type: Date,
default: Date.now()
},
created_at: {
type: Date,
default: Date.now()
}
});
module.exports = mongoose.model('Post', postSchema);
There is some detail I have missed, because on updating a post, the editpost.ejs view returns a Cannot read property 'toString' of undefined error:
<form action="/dashboard/post/update/<%= post._id %>" method="POST" enctype="multipart/form-data" class="mb-0">
<div class="form-group">
<input type="text" class="form-control" name="title" value="<%= typeof form!='undefined' ? form.titleholder : post.title %>" placeholder="Title" />
</div>
<div class="form-group">
<input type="text" class="form-control" name="excerpt" value="<%= typeof form!='undefined' ? form.excerptholder : post.short_description %>" placeholder="Excerpt" />
</div>
<div class="form-group">
<textarea rows="5" class="form-control" name="body" placeholder="Full text">
<%= typeof form!='undefined' ? form.bodyholder : post.full_text %>
</textarea>
</div>
<% if (categories) { %>
<div class="form-group">
<label for="category">Choose a post category</label>
<select id="category" name="category" class="form-control">
<% categories.forEach(function(category, index) { %>
<option value="<%= category._id %>" <%=category._id.toString()==p ost.category._id.toString() ? 'selected' : ''; %>>
<%= category.cat_name %>
</option>
<% }); %>
</select>
</div>
<% } %>
<label for="postimage">Upload an image</label>
<div class="form-group">
<input type="file" name="postimage" id="postimage" size="20">
</div>
<div class="form-group d-flex mb-0">
<div class="w-50 pr-1">
<input type="submit" value="Update Post" class="btn btn-block btn-md btn-success">
</div>
<div class="w-50 pl-1">
Cancel
</div>
</div>
</form>
In the controller, the updatePost method looks like this:
exports.updatePost = (req, res, next) => {
const query = {
_id: req.params.id
}
const form = {
titleholder: req.body.title,
excerptholder: req.body.excerpt,
bodyholder: req.body.body
};
const errors = validationResult(req);
const post = {};
post._id = req.params.id;
post.title = req.body.title;
post.short_description = req.body.excerpt
post.full_text = req.body.body;
post.category = req.body.category;
if (req.file) {
post.post_image = req.file.filename;
}
if (!errors.isEmpty()) {
req.flash('danger', errors.array());
const categories = Category.find({}, (err, categories) => {
res.render('admin/editpost', {
layout: 'admin/layout',
website_name: 'MEAN Blog',
page_heading: 'Dashboard',
page_subheading: 'Edit Post',
categories: categories,
form: form,
post: post
});
});
} else {
Post.update(query, post, function(err) {
if (err) {
console.log(err);
return;
} else {
req.flash('success', "The post was successfully updated");
req.session.save(() => res.redirect('/dashboard'));
}
});
}
}
What have I missed?
I think your problem here is here:
post.category._id.toString()
When you pass category in req.body you are passing category._id as that parameter, so when you create that post object in your controller, you set post.category as req.body.category, when you return this to the view post.category is not an object with ._id property and the toString() method doesn't exist on it.
Try using post.category instead
Do nothing but handle req.file first in the post route, because you are using enctype="multipart/form-data".
Use req.file somewhere and it'll work fine! I don't know the exact reason but it works!

How to save data when you tried to reference the schema

I am trying to save information from a book that contains in its eschema auhtor and genre that I have it referenced in different files.
The problem is that when I make the reference in the main eschema. The book to have the reference of the author and the genre with the created book only keeps me the information of the book but does not make reference neither to the gender nor to the author
book.js
const mongoose = require('mongoose');
const bookSchema = new mongoose.Schema({
name: {
type: String,
},
author: {
type: mongoose.Schema.ObjectId,
ref: 'Author',
},
numberInStock: {
type: Number,
default: 0,
},
image: {
type: String,
default: '/path/to/default/image.png',
},
genre: {
type: mongoose.Schema.ObjectId,
ref: 'Genre',
},
});
module.exports = mongoose.model('Books', bookSchema);
author.js
const mongoose = require('mongoose');
const authorSchema = new mongoose.Schema({
name: String,
publicatons: [{
type: mongoose.Schema.Types.ObjectId,
ref: 'Book',
}],
});
module.exports = mongoose.model('Author', authorSchema);
createBook.ejs
<div class="container h-100">
<div class="row">
<div class="col-lg-6 col-md-offset-3">
<h1>Add a New Book</h1>
<form action="/books/new/create" method="post">
<div class="panel panel-default">
<div class="panel-body">
<div class="form-group">
<label for="book_name"> Book Name: </label>
<input type="text" class="form-control" name="name">
</div>
<div class="form-group">
<label for="exampleFormControlSelect1">Author</label>
<select class="form-control" name="<%= author._id %>">
<% author.forEach(function(authors){ %>
<option><%= authors._id %></option>
<% }); %>
</select>
</div>
<div class="form-group">
<label for="exampleFormControlSelect1">Genre</label>
<select class="form-control" name="<%= genre._id %>">
<% genre.forEach(function(genres){ %>
<option><%= genres.name %></option>
<% }); %>
</select>
</div>
<div class="form-group">
<label for="numberInStock">Number in stock: </label>
<input type="number" class="form-control" name="numberInStock">
</div>
<div class="form-group">
<label for="image">Image: </label>
<input type="text" class="form-control" name="image">
</div>
<button type="submit" class="btn btn-success">Success</button>
</div>
</div>
</form>
</div>
</div>
</div>
this is the part of the front where I try to send the data to the backent, but only manage to save, the name, image, quantity but not the referenced data.
controller.js
const create = (req, res, next) => {
if (!req.body) {
return res.status(400).json({
error: 'No data',
});
}
const book = new Books(req.body);
book.save((err, result) => {
if (err) {
return res.status(400).json({
error: err.message,
});
}
return res.status(201).json({
message: 'Book created succesfully',
result,
});
});
};
Routes controller
const express = require('express');
const router = express.Router();
const bookController = require('../controllers/book');
const booksave = require('../controllers/create');
const authorController = require('../controllers/author');
const genreController = require('../controllers/genre');
router.get('/books/home/:page', bookController.list);
router.get('/books/new', bookController.createTemplate);
router.post('/books/new/create', booksave.create);
router.get('/books/details/:id', bookController.bookDetail);
router.get('/books/new/create/genre', genreController.createGenreTemplate);
router.post('/books/new/genre', genreController.createGenre);
router.get('/books/new/create/author', authorController.createAuthorTemplate );
router.post('/books/new/author', authorController.createAuthor);
module.exports = router;
create new book render
controller.createTemplate = (req, res) => {
Author.find({}, (err, allAuthors) => {
if (err) {
console.log(err);
} else {
Genre.find({}, (err, allGenres) => {
if (err) {
console.log(err)
} else {
res.render('new', { author: allAuthors, genre: allGenres });
}
})
}
});
};
technically what I hope to obtain is that when I save the information of the book this has a reference to its author and to the genre and automatically the author has a references to the books that are referenced with the same
Of the values of the front I'm just getting
{name: 'Digital Fortress', numberInStock: '', image: ''}
const create = (req, res, next) => {
if (!req.body) {
return res.status(400).json({
error: 'No data received',
});
}
const book = new Books(req.body);
book.save((err, result) => {
if (err) {
return res.status(400).json({
error: err.message,
});
}
console.log(req.body);
return res.status(201).json({
message: 'Book created succesfully',
result,
});
});
};
Change the name attribute in <select class="form-control" name="<%= genre._id %>"> to name='author' same as in your bookSchema, and in <select class="form-control" name="<%= genre._id %>"> to name='genre'.
The problem is your sending a post request with the following body :
{ name: /*value*/ , <%= author._id%>: /*value*/, <%= genre._id%>: /*value*/, numberInStock: /*value*/, image: /*image*/ }
The constructor of your Book model recognizes name, numberInStock and image, but not <%=author_id%> and <%=genre._id%>.
Change this also:
author: {
type: mongoose.Schema.ObjectId,
ref: 'Author'
},
genre: {
type: mongoose.Schema.ObjectId,
ref: 'Genre'
}
to
author: {
type: mongoose.Schema.Types.ObjectId,
ref: ref: 'Author'
}
genre: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Genre'
}
You also need to pass genre._id instead of genres.name, and author._idas value attributes like this :
<form action="/books/new/create" method="post">
<select name="author">
<% author.forEach(function(authors){ %>
<option value="<%= authors._id %>" ><%= authors.name %></option>
<% }); %>
</select>
<select name="genre">
<% genre.forEach(function(genres){ %>
<option value="<%= genres._id %>" ><%= genres.name %></option>
<% }); %>
</select>
<button type="submit">Success</button>
</form>

"Cast to Number failed for value 1,1 at path"

I'm trying to build a user model with some privileges.
Schema looks like this:
var mongoose = require("mongoose"),
passportLocalMongoose = require("passport-local-mongoose");
let userSchema = new mongoose.Schema({
username:
{type: String,
unique: true
},
password: String,
privileges:
[{
region: { type: Number, unique: true },
read: Number,
write: Number,
edit: Number
}]
});
userSchema.plugin(passportLocalMongoose);
module.exports = mongoose.model("User", userSchema);
POST route looks like this.
router.post('/register', function(req, res)
{
console.log(req.body);
User.register(new User({
username: req.body.username,
privileges:{
region: req.body.privileges['region'],
read: req.body.privileges['read'],
write: req.body.privileges['write'],
edit: req.body.privileges['edit']
}
}), req.body.password, function(err)
{
if(err)
{
console.log(err);
res.redirect("/register");
}
else
{
console.log("fine");
passport.authenticate('local', {
successRedirect: '/',
failureRedirect: '/login'
})
}
})
});
<form action="/register" method="POST">
<input type="text" name="username"><br/>
<input type="password" name="password"><br/>
<input type="text" name="privileges[region]"><br/>
<input type="text" name="privileges[read]"><br/>
<input type="text" name="privileges[write]"><br/>
<input type="text" name="privileges[edit]"><br/>
<input type="text" name="privileges[delete]"><br/>
<button>Submit</button>
</form>
Basically, it should work like this:
From the form I should get an array of privileges.
Now when I enter data in the fields like this:
test
1234
1 2
1 1
1 1
1 1
(test - username, 1234 - password, 1 2 region array, 1 1 - read array, 1 1 write array, 1 1 edit array) I get this error:
Now I get the reason - privileges[edit] is type="text" and it can't be parsed into the DB as a Number. But why does it happen ONLY for edit? I find it strange.
I tried changing input type to number, but after that I can't enter an array anymore.
I think I might need a middleware which transforms the text into numbers. Am I right? If so, how should it do it? Should it transform each element of the array individually or the array as a whole?
Thanks.
In the schema defined for User, privileges is an array of SubDocuments having this schema.
{
region: { type: Number, unique: true },
read: Number,
write: Number,
edit: Number
}
When setting this field, the data provided needs to match that schema. e.g.
new User({
username: req.body.username,
privileges: [
{
region: ":region_value",
read: ":read_value",
write: ":write_value",
edit: ":edit_value"
},
{
region: ":region_value",
read: ":read_value",
write: ":write_value",
edit: ":edit_value"
},
//....
],
})
I assume that the design for privileges was done purposely in this way to allow for a user to have many privileges.
A straightforward way to set privileges is to design the form appropriately. The form field can allow for setting several privileges. For example, to set two privileges, you can achieve that by writing the markup this way:
<input type="text" name="privileges[0][region]"><br/>
<input type="text" name="privileges[0][read]"><br/>
<input type="text" name="privileges[0][write]"><br/>
<input type="text" name="privileges[0][edit]"><br/>
<input type="text" name="privileges[0][delete]"><br/>
<input type="text" name="privileges[1][region]"><br/>
<input type="text" name="privileges[1][read]"><br/>
<input type="text" name="privileges[1][write]"><br/>
<input type="text" name="privileges[1][edit]"><br/>
<input type="text" name="privileges[1][delete]"><br/>
This way privileges in the req.body will have the right format e.g.
{ privileges:
[ { region: '1', read: '2', write: '2', edit: '2', delete: '4' },
{ region: '2', read: '4', write: '4', edit: '4', delete: '4' } ] }
So that you can simply write
new User({
username: req.body.username,
privileges: req.body.privileges
})
It's more straightforward to ensure the client passes the right data than trying to massage the data after the fact.
A limit in the design of the form this way means that the number of privileges that a user can have has to be determined ahead of time. A work around this is to build the form dynamically and give control to the user to add more privileges as the case may be. See the following example to get an idea about how to go about it:
function addPrivilege(e) {
e.preventDefault();
const privileges = $('.privileges');
const lastCount = privileges.length;
console.log($(this).data('template').replace(/:x:/g, lastCount))
const template = $(this).data('template').replace(':x:', lastCount);
privileges.after($('<div class="privileges"></div>').append(template))
}
$(document).ready(function () {
$("#addPrivilegeBtn").on('click', addPrivilege);
});
.privileges {
background: #ccc;
padding: 8px 16px;
margin: 4px 0;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<form>
<div class="privileges">
<input type="text" name="privileges[0][region]"><br/>
<input type="text" name="privileges[0][read]"><br/>
<input type="text" name="privileges[0][write]"><br/>
<input type="text" name="privileges[0][edit]"><br/>
<input type="text" name="privileges[0][delete]"><br/>
</div>
<button
id="addPrivilegeBtn"
data-template='<input type="text"name="privileges[:x:][region]"><br/>
<input type="text" name="privileges[:x:][read]"><br/>
<input type="text" name="privileges[:x:][write]"><br/>
<input type="text" name="privileges[:x:][edit]"><br/>
<input type="text" name="privileges[:x:][delete]"><br/>'
>Add privilege</button>
</form>

Angular/Node/Mongoose - Rendering Post, Comment, Replies

I am creating an Application where people can ask questions. Answer- and reply to answers. I can render Posts and answers no problem, but having trouble getting replies to answers.Can someone please give me tips on the best approach to do this?
html page
<div class="container-fluid" id='post_container' ng-repeat='post in vm.posts' style='border: solid'>
<div class="row">
<div class = 'container'>
<div class='single_post'>
<h3>Topic: {{post.topic}}</h3>
<h5>Posted By: {{post.owner}} </h5>
<p>Description: {{post.description}}</p>
<form class="form-horizontal">
<div class="form-group">
<label for="inputEmail3" class="col-sm-2 control-label">Answer</label>
<div class="col-sm-10">
<textarea class="form-control" rows="3" ng-model='vm.newAnswer[$index]'></textarea>
<button class='btn btn-primary' ng-click = 'vm.Answer(post._id, $index)' style='float:right'>Submit</button>
</div>
</form>
</div>
<em>Answers</em>
<!-- Make API Call to get answers into the container change varialbe -->
<div class='answer_container' ng-repeat = 'answer in post.answers track by $index'>
<h5><strong>{{answer._owner}}</strong>: <em> {{answer.answer}} ID {{answer._id}}</em></h5>
<div class='replies' style='margin-left: 20px'>
-find a way to get comments here
<input class='Reply' ng-model='vm.newComment'>
<input type='submit' ng-click = 'vm.Reply(answer._id)'>
</div>
angular controller
function getPosts() {
PostsFactory.getPosts()
.then(function(data) {
console.log(data)
console.log('getting POSTS')
vm.posts = data
console.log(map(vm.posts, getAnswers));
})
.catch(function(){
console.log('in the single psot controller and could not get posts')
}
Factory
function getPosts() {
var deferred = $q.defer()
$http.get('/getAnswers')
.success(function(data) {
deferred.resolve(data)
})
.error(function() {
console.log('could not get posts')
})
return deferred.promise
}
Models/Routes/Controller
var Post = new mongoose.Schema({
category: String,
topic: String,
description: String,
points: Number,
owner: String,
answers: [{type: ObjectId, ref: 'Answer'}],
date_created: Date
});
Answer
var Answer = new mongoose.Schema({
answer: String,
_post: {type: ObjectId, ref:'Post'},
_owner: String,
points: Number,
comments: [{type: ObjectId, ref: 'Comment'}],
date_created: Date
})
Comments
var Comment = new mongoose.Schema({
comment: String,
_answer: {type: ObjectId, ref: 'Answer'},
_owner: String,
points: Number,
date_created: Date
})
Controller
posts.show = function(req, res) {
Post.find()
.populate('answers')
.exec(function(err, result) {
if(err) {
console.log('error finding post')
} else {
res.json(result)
}
})
}
Mongoose supports "multi-level" population, but you need to supply that information to .populate():
posts.show = function(req,res) {
Post.find()
.populate({
"path": "answers",
"populate": {
"path": "comments",
"model": "Comment"
}
})
.exec(function(err,result) {
if (err) {
console.log(err);
} else {
res.json(result)
}
});
}
So the most that happens with a "single path" is that particular "path" is populated. But by "nesting" the "populate" arguments, then further calls are made for the other referenced items named.

SequelizeJS Passing Array of Values

I am trying to figure out how it is possible to pass an array as the value for the property of an instance. I currently have the dataType set to STRING in my model and have values from jQuery fields insert each form field value into an array that I parse from the body and set to the property, discoverSource. Unfortunately I receive a string violation error that says I can't use an array or object. What does this mean and how can I change the dataType of the field or route to allow me to pass the comma separated values to the field?
E.x. For discoverySource I pass values to two fields (NJ, NY). On submit, the values are combined in an array as ["NJ", "NY"] and the error displays:
Error Message:
{"name":"SequelizeValidationError","message":"string violation: discoverySource cannot be an array or an object","errors":[{"message":"discoverySource cannot be an array or an object","type":"string violation","path":"discoverySource","value":["NJ","NY"]}]}
Here is my model:
module.exports = function(sequelize, DataTypes) {
var Organization = sequelize.define('organization', {
organizationId: {
type: DataTypes.INTEGER,
field: 'organization_id',
autoIncrement: true,
primaryKey: true
},
organizationName: {
type: DataTypes.STRING,
field: 'organization_name'
},
admin: DataTypes.STRING,
discoverySource: {
type: DataTypes.TEXT,
field: 'discovery_source'
},
members: DataTypes.STRING
},{
freezeTableName: true,
classMethods: {
associate: function(db) {
Organization.belongsToMany(db.User, { through: 'member', foreignKey: 'user_id' });
},
},
});
return Organization;
}
Here is the route:
var express = require('express');
var appRoutes = express.Router();
var passport = require('passport');
var localStrategy = require('passport-local').Strategy;
var models = require('../models/db-index');
appRoutes.route('/sign-up/organization')
.get(function(req, res){
models.User.find({
where: {
user_id: req.user.email
}, attributes: [ 'user_id', 'email'
]
}).then(function(user){
res.render('pages/app/sign-up-organization.hbs',{
user: req.user
});
})
})
.post(function(req, res, user){
models.Organization.create({
organizationName: req.body.organizationName,
admin: req.body.admin,
discoverySource: req.body.discoverySource
}).then(function(organization, user){
res.redirect('/app');
}).catch(function(error){
res.send(error);
console.log('Error at Post' + error);
})
});
Here is my view file:
<!DOCTYPE html>
<head>
{{> head}}
</head>
<body>
{{> navigation}}
<div class="container">
<div class="col-md-6 col-md-offset-3">
<form action="/app/sign-up/organization" method="post">
<p>{{user.email}}</p>
<input type="hidden" name="admin" value="{{user.email}}">
<input type="hidden" name="organizationId">
<label for="sign-up-organization">Company/Organization Name</label>
<input type="text" class="form-control" id="sign-up-organization" name="organizationName" value="" placeholder="Company/Organization">
Add Another Discovery Source
<div id="sign-up-organization-discovery-source">
<input type="text" id="discovery-source-field" placeholder="Discovery Source" name="discoverySource[0]">
</div>
<br />
<button type="submit">Submit</button>
</form>
Already have an account? Login here!
</div>
</div>
<script type="text/javascript">
$(function() {
var dataSourceField = $('#sign-up-organization-discovery-source');
var i = $('#sign-up-organization-discovery-source p').size();
var sourceCounter = 1;
$('#sign-up-add-discovery-source').on('click', function() {
$('<p><label for="discovery-source-field"><input type="text" id="discovery-source-field" size="20" name="discoverySource['+ sourceCounter++ +']" value="" placeholder="Discovery Source" /></label> Remove</p>').appendTo(dataSourceField);
i++;
return false;
});
$('#sign-up-organization-discovery-source').on('click', '.remove', function() {
if (i > 1) {
$(this).parent('p').remove();
i--;
}
return false;
});
});
</script>
</body>
To answer the last comment, I need to be able to make the code more readable, so I'm posting it here in a new answer.
Having thought about it a little more, it would make more sense to add it as custom 'getter' function. I'll also include the 'instanceMethods' to demonstrate how that works, as well.
var Organization = sequelize.define('organization', {
...
},{
freezeTableName: true,
classMethods: {
associate: function(db) {
Organization.belongsToMany(db.User, { through: 'member', foreignKey: 'user_id' });
},
},
// Here's where custom getters would go
getterMethods: {
discoverySources: function() {
return this.getDataValue('discoverySource');
}
},
// here's the instance methods
instanceMethods: {
getSourcesArray: function() {
return this.getDataValue('discoverySource');
}
}
});
Both of these options add the functions to each instance created by the Model. The main difference being in how they are accessed.
organization.discoverySources; // -> ['s1', 's2', etc...]
organization.getSourcesArray(); // -> ['s1', 's2', etc...]
note the additional () required on the instanceMethod. Those are added as functions of the instance, the getterMethods get added as properties.
setterMethods work the same way to allow you to define custom setters.
Hope that clarifies things a bit.

Categories