Questions about POST request to node.js from ajax (Error) - node.js

When I first send data (post), it will be stored, but when I send different data second time, it produces the error message
"error": "E11000 duplicate key error index: mongodb-database.users.$name_1 dup key: { : null }"
But, in my db, there is no null data, which I think it shouldn't be duplicated? I am not sure if there is another code that causes error.
Any ideas help me. Thanks.
My route
.post(function(req, res, next){
Users.create(req.body).then(function(user){
res.json(user);
}).catch(next);
});
My model
var userSchema = new Schema({
name: {
type: String,
// required: [true, "Name field is required"],
unique:true
}
});
My index.html (using Ajax)
$.("form").submit(function(event){
var order = {
name: $username.val(),
};
$.ajax({
type: 'POST',
url: '/users',
data: order
success: function(newOrder){
$result.append('<li>name: ' + newOrder.username + '</li>');
},
error: function(){
alert('error saving order');
}
});
});
});
</script>
</head>
<body>
<div id="result"></div>
<form action="/users" method="POST">
<label>Username:<input type="text" id="username"></label>
<input type="submit" value="Submit" id="post_message">
</form>

This error is occurring due to unique key constraint that you added in model definition. Remove unique from model and add require if you want to make name field required. You can redefine your model like this
var userSchema = new Schema({
name: {
type: String,
// required: [true, "Name field is required"],
required: true
}
});

Related

Mongoose: push a string into array

I have a website where any logged-in user can leave a review for the shop.
So basically I have two schemas:
const journalSchema = new mongoose.Schema({
title: String,
category: String,
subcategory: String,
rating: Number,
review: [{type: String}],
link: String,
description: String,
});
const userSchema = new mongoose.Schema ({
username: String,
password: String,
journal: [{type: mongoose.Schema.Types.ObjectId, ref: 'Journal'}]
});
const Journal = mongoose.model("Journal", journalSchema);
const User = mongoose.model("User", userSchema);
form from the ejs file:
<div class="container my-3">
<h1>Compose</h1>
<form class="" action="/stats" method="post">
<div class="form-group">
<label for="review">Description</label>
<textarea id="review" class="form-control" name="journalReview" rows="5" cols="30"></textarea>
</div>
<button class="btn btn-primary my-2" type="submit" name="button">Publish</button>
</form>
</div>
post route:
app.post("/stats", function(req, res){
if(req.isAuthenticated()){
const favJournal = req.body.savedJournal;
const userId = req.user.id;
const userReview = req.body.journalReview;
User.findById(userId, function(err, foundUser){
Journal.findById(favJournal, function(err, foundJournal){
if(err){
console.log(err);
}
else{
if(foundUser){
foundJournal.review.push(userReview);
foundJournal.save(function(){
console.log(foundJournal);
});
foundUser.journal.addToSet(foundJournal);
foundUser.save(function(){
res.redirect("/favourite");
});
}
}
});
})
.populate('journal')
.exec(function(err, user){
if(err) return handleError(err);
});
}
else{
res.redirect("/login");
}
});
Every time I try to push review from the ejs file I keep getting this error:
events.js:353
throw er; // Unhandled 'error' event
^
TypeError: Cannot read property 'review' of null
at C:\Users\HelloThere\Desktop\miesto\app.js:337:24
at C:\Users\HelloThere\Desktop\miesto\node_modules\mongoose\lib\model.js:5065:18
at processTicksAndRejections (internal/process/task_queues.js:77:11)
Emitted 'error' event on Function instance at:
at C:\Users\HelloThere\Desktop\miesto\node_modules\mongoose\lib\model.js:5067:15
at processTicksAndRejections (internal/process/task_queues.js:77:11)
I tried different solutions from similar posts. Like mongoose methods: findOneAndUpdate and updateOne, but they just return null.
Instead of getting the shop and manipulating it with JavaScript code and then saving it back to the database, you could achieve this through findOneAnUpdate and $push operator.
For instance, this query
Shop.findById( shopId, (shop) => {
shop.products.push(product);
shop.save();
}
can be done through this query
Shop.findOneAndUpdate(
{ _id: shopId },
{ $push: {
products: product
}
})
$pop, $push, and $pull are very powerful tools to manipulate arrays in Mongoose. Take a look at the docs.
For the error you're getting, I think you're getting because you're passing a wrong journalId to findById. Check with MongoDB Compass if you do have a document with that id favJournal
I think I figured out the cause of the problem, I have two post forms in my ejs file, and since both forms have submit button with nothing differentiating them, only the first form gets called in the post route.

Accessing child methods in parent for mongoose succeeds in array and fails with single child

UPDATE : Solution is at bottom of question
I have an express site using mongoose.
I'll greatly simplify to say that I have adults, kids, and house models. When I create methods on kids, I can call them from within methods on adults and get a result. I can also call them from my .ejs views. However, when I create methods on house, I can only get a result from my .ejs views and get undefined when called from within methods on adults. Example code follows.
adult.js
const mongoose = require('mongoose');
const adultSchema = mongoose.Schema({
name: { type: String },
size: {type: String},
kids: [{type: mongoose.Schema.Types.ObjectId, ref: 'Kid', required: true}]
house:{type: mongoose.Schema.Types.ObjectId, ref: 'House', required: true}
});
adultSchema.method({
getKidsDescription: function() {
if (this.kids.length < 1) {
return 'No kids yet';
} else {
let ev = 'Kids, aged: ';
let kds = this.kids;
kds.forEach(function(k){
ev = ev + 'k.getAge()' // works
})
return ev;
}
},
getHouseDescription: function(){
return 'A fabulous house on '+this.house.getFullStreet(); // does not work
}
})
module.exports = mongoose.model('Adult', adultSchema);
kid.js
const mongoose = require('mongoose');
const kidSchema = mongoose.Schema({
name: { type: String },
size: {type: String},
birthdate: {type:Date}
});
kidSchema.method({
getAge: function() {
return (Math.floor(new Date() - this.birthdate)/(1000*60*60*24*365))
},
})
module.exports = mongoose.model('Kid', kidSchema);
house.js
const mongoose = require('mongoose');
const houseSchema = mongoose.Schema({
name: { type: String },
city: {type: String},
street: {type:String}
});
houseSchema.method({
getFullStreet: function() {
return this.street + ' Road';
},
})
module.exports = mongoose.model('House', houseSchema);
When I make a query for theAdult, it looks like this:
controller.js
exports.main = async (req, res, next) => {
if (req.theAdult) {
try {
const found = await db.fetchAdult(req.theAdult._id)
res.render('/main', {
//theHouse: found.house //below I show this working
});
} catch(e) {
throw new Error(e.message)
}
} else {
res.redirect('/');
}
}
db.js
exports.fetchAdult = (id) => {
return Adult.findById(id)
.populate({ path: 'kids'})
.populate({ path: 'house'})
.exec()
.then(doc => {
return doc;
});
}
Assuming house is passed to view as an object when rendered (commented out above), this works
view.ejs
<p> <%= theHouse.getFullStreet() %></p>
Assuming house populated on the call to load the Adult, this returns undefined.
view.ejs
<p> <%= theAdult.house.getFullStreet() %></p>
At the same time, both of these work
view.ejs
<ul> <% theAdult.kids.forEach(function(k) { %>
<li><%= k.getAge() %> </li>
<% }); %>
</ul>
<p> <% theAdult.getKidsDescription() %> </p>
What I am not understanding is how the method calls work for objects in array and work in the view but do not work for objects on in an array. This is a single child error (for me). If it did not work in the view, I would assume that the method getFullStreet() was the problem, but it works in the view. If the array methods could not be called within the parent, I would assume the issue was with trying to access getFullStreet() in the parent.
What am I missing?
SOLUTION
I was fetching theAdult in my call to show view.ejs, but I was then actually relying on currentAdult which referred to req.adult and did not have the fields populated. My solution was to add a pre hook to the adult schema that always populates house on find.
in adult.js
adultSchema.pre('find', function() {
this.populate('house')
})
Have you tried passing a hydrated theAdult? It might only see the ObjectID, without any other data or methods.

Trying to add followers in Mongoose subarray

I'm learning node.js and I'm trying to figure out how to add users to a subarray in my schema. I'm basically doing a twitter-clone in order to learn how node works.
This is my UserSchema. I want to add users into the "following" field-array.
#Usermodel.js
var UserSchema = mongoose.Schema({
username: {
type: String,
index:true
},
password: {
type: String
},
email: {
type: String
},
name: {
type: String
},
facebook : {
id : String,
token : String
},
resetPasswordToken: {type: String},
resetPasswordExpires: {type: Date},
following: [{type: mongoose.Schema.Types.ObjectId, ref: 'User'}], <-- I want to add users here
posts : [{ type: mongoose.Schema.Types.ObjectId, ref: 'Post' }]
});
UserSchema.index({username: 'text'});
var User = module.exports = mongoose.model('User', UserSchema);
Further down in this file you will find my schema method for adding users into the "following" subarray:
#Usermodel.js
module.exports.addFollowers = function (req, res, next){
User.findOneAndUpdate({_id: req.user._id}, {$push: {following: req.body.id}})
};
I'm querying a route in order to call my schema function. It looks like this:
#routes.js
router.get('/follow', User.addFollowers);
In my ejs front-end, I try to call my schema function by sending a GET-request to my route:
#index.ejs
<ul>
<%results.forEach(function(element){%> <-- Here I'm looping over users
<% if(user.id != element.id) { %> <-- Not show if it is myself
<li>
<%=element.username%></a> <br>
<form action="/follow" method="GET"> <-- Call route form
<input type="hidden" name="id" value=<%=element._id%>>
<button type="submit">Follow</button> <-- Submit GET
</form>
</li>
<% } %>
<br>
<%});%>
</ul>
Not sure what to do from here. When I press the "Follow" button my site keeps loading. Couldn't find any posts here on stackoverflow that could help me more at this stage.
Anyone who knows what is wrong? Is this the right way to do it?
Make some changes in #Usermodel.js
module.exports.addFollowers = function (req, res, next){
User.findOneAndUpdate({_id: req.user._id}, {$push: {following: req.body.id}}, next)
};
Try this code.

Why is 'encryptedPassword' parameter being interpreted as undefined in sails.js project?

I am working on a web app following irlnathan/ponzicoder's Sailscasts series as a guide. I have reached Ep. 5 on creating entries for the 'User' model using a signup action. At this stage in the tutorial (precise time linked to), we see that the response page he receives is the JSON object of the User that was just created using the signup. I, however, get the following for my response page:
{
"error": "E_VALIDATION",
"status": 400,
"summary": "1 attribute is invalid",
"model": "User",
"invalidAttributes": {
"encryptedPassword": [
{
"rule": "string",
"message": "`undefined` should be a string (instead of \"null\", which is a object)"
},
{
"rule": "required",
"message": "\"required\" validation rule failed for input: null"
}
]
}
}
This states that my 'encryptedPassword' parameter is somehow undefined by the time I am returning a response. However, when I log the json object I am returning to the response in my console, I get the following (which is what I expect to see instead of the E_VALIDATION response):
{ firstName: 'First',
lastName: 'Last',
email: 'email#domain.com',
encryptedPassword: '$2a$10$q0TfKWnN47mucsZLybN8WeygPPMpYxp9VMGUgIjA./ipZBn.POuOG',
lastLoggedIn: '2015-08-03T23:45:22.520Z',
gravatarUrl: 'http://www.gravatar.com/avatar/7328fddefd53de471baeb6e2b764f78a?',
createdAt: '2015-08-03T23:45:22.523Z',
updatedAt: '2015-08-03T23:45:22.523Z',
id: 39 }
So encryptedPassword is definitely being correctly created and defined.
This is a problem similar to the one faced by THIS user, except my problem does not lie in missing HTML name attributes. Stranger still, my problem specifically lies in the 'encryptedPassword' parameter and none of the others.
I must admit that since I am not using the exact same fields for my signup form as the tutorial, there may be some discrepancies which are causing the issue. However, I still haven't been able to figure it out after a few hours of debugging. Here's what I'm using
Relevant code:
Signup action (UserController.js <-- following sails.js conventions)
And as a note for each of the console.log statements littered in the signup function, I get a non-null value for them all. I did this to verify that indeed encryptedPassword was not null/undefined
/**
* UserController
*
* #description :: Server-side logic for managing Users
* #help :: See http://sailsjs.org/#!/documentation/concepts/Controllers
*/
module.exports = {
signup: function(req, res) {
var Passwords = require('machinepack-passwords');
// Encrypt a string using the BCrypt algorithm.
Passwords.encryptPassword({
password: req.param('password')
// difficulty: 10,
}).exec({
// An unexpected error occurred.
error: function (err){
return res.negotiate(err);
},
// OK.
success: function (encryptedPassword){
console.log("1", encryptedPassword);
require('machinepack-gravatar').getImageUrl({
emailAddress: req.param('email')
}).exec({
error: function(err) {
return res.negotiate(err);
},
success: function(gravatarUrl) {
console.log("2", encryptedPassword);
console.log("2", gravatarUrl);
User.create({
firstName: req.param('firstName'),
lastName: req.param('lastName'),
email: req.param('email'),
encryptedPassword: encryptedPassword,
lastLoggedIn: new Date(),
gravatarUrl: gravatarUrl
}, function userCreated(err, newUser) {
console.log("NEWUSER", newUser);
console.log("3", encryptedPassword);
console.log("3", gravatarUrl);
if (err) {
console.log("err: ", err);
console.log("err.invalidAttributes: ", err.invalidAttributes)
// If this is a uniqueness error about the email attribute,
// send back an easily parseable status code.
if (err.invalidAttributes && err.invalidAttributes.email && err.invalidAttributes.email[0]
&& err.invalidAttributes.email[0].rule === 'unique') {
return res.emailAddressInUse(); //some error that I have already defined somewhere else in my project
}
return res.negotiate(err);
}
return res.json({
id: newUser.id
});
});
}
});
}
});
}
};
Attributes of the User model (User.js <-- following sails.js conventions)
/**
* User.js
*
* #description :: TODO: You might write a short summary of how this model works and what it represents here.
* #docs :: http://sailsjs.org/#!documentation/models
*/
module.exports = {
schema: true,
attributes: {
//all the other attributes
// The encrypted password for the user
encryptedPassword: {
type: 'string',
required: true
}
//more attributes
}
};
HTML of the signup form
<form ng-submit="submitSignupForm()" id="sign-up-form" name="signup" class="form" role="form">
<!-- irrelevant html -->
<!-- P A S S W O R D -->
<div class="control-group form-group" ng-class="{'has-error':signup.password.$invalid && signup.password.$dirty}">
<input type="password" name="password" value="" class="form-control input-lg" placeholder="Password" ng-model="signupForm.password" id="password" required ng-minlength="12"/>
<span class="help-block has-error" ng-if="signup.password.$dirty">
<span ng-show="signup.password.$error.required">Password is required.</span>
<span ng-show="signup.password.$error.minlength">Password must be at least 12 characters.</span>
</span>
</div>
<!-- more misc html -->
</form>
And the signup controller
angular.module('SignupModule').controller('SignupController', ['$scope', '$http', 'toastr', function($scope, $http, toastr) {
$scope.signupForm = {
loading: false
}
$scope.submitSignupForm = function() {
$scope.signupForm.loading = true;
console.log("clicked!");
$http.post('/signup', {
firstName: $scope.signupForm.firstName,
lastName: $scope.signupForm.lastName,
email: $scope.signupForm.email,
password: $scope.signupForm.password
})
.then(function onSuccess() {
window.location = '/user';
})
.catch(function onError(sailsResponse) {
var emailAddressAlreadyInUse = (sailsResponse.status == 409);
if (emailAddressAlreadyInUse) {
toastr.error('That email address has already been taken, please try again.', 'Error');
return;
}
})
.finally(function eitherWay() {
$scope.signupForm.loading = false;
});
}
}]);
So to reiterate, why on earth is encryptedPassword being interpreted as null?

Create search articles feature in MEAN stack

First of all, let me tell you that I'm a novice in the world of javascript and node.js. I have been searching for help in trying to do what i want but haven't found yet.
I am using the MEAN stack(http://mean.io/) and I am trying to implement a search feature in the included articles model. The search would look for articles with a specific tag and would be implemented in the index page. Follow me and see if you can find what I am missing please.
In the backend:
app/models/
/**
* Article Schema
*/
var ArticleSchema = new Schema({
created: {
type: Date,
default: Date.now
},
title: {
type: String,
default: '',
trim: true
},
content: {
type: String,
default: '',
trim: true
},
tag: {
type: String,
default: '',
trim: true
},
user: {
type: Schema.ObjectId,
ref: 'User'
}
});
app/controllers/
exports.searcharticle = function(req, res) {
Article.find({'tag': req.params.tag}).sort('-created').populate('user', 'name username').exec(function(err, articles) {
if (err) {
res.render('error', {
status: 500
});
} else {
res.jsonp(articles);
}
});
};
Added the route for the search in app/routes/articles.js
app.get('/articles/search/:tag', articles.searcharticle);
In the frontend:
Created the view for the search wich will display the search results - public/views/articles/search.html
<section data-ng-controller="ArticlesController" data-ng-init="searchart()">
<ul class="articles unstyled">
<li data-ng-repeat="article in articles">
<span>{{article.created | date:'medium'}}</span> /
<span>{{article.user.name}}</span>
<h2><a data-ng-href="#!/articles/{{article._id}}">{{article.name}}</a></h2>
<div>{{article.tag}}</div>
</li>
</ul>
<h1 data-ng-hide="!articles || articles.length">Your search hasn't returned any results. <br> Why don't you Create One?</h1>
</section>
The view for the index.html, where the searchbox will be implemented
<section data-ng-controller="ArticlesController">
<form role="form" data-ng-submit="searchart()">
<div>
<div>
<input type="text" id="tag" ng-model="selected" class="form-control" placeholder="Tag">
</div>
<div>
<button type="submit" class="btn btn-default">Submit</button>
</div>
</div>
</form>
Added the route to the config.js
when('/articles/search/:tag', {
templateUrl: 'views/articles/search.html'
}).
And added the search function to the articles controller
$scope.searchart = function() {
Articles.query(function(articles) {
$scope.articles = articles;
});
};
Right now, with this code implemented, when I click in the submit button in the index page, nothing happens.
Can you find what am I missing?
Thanks in advance!
In order to use a URL in your client Article Service, you should define the URL Parameter in the articles service at: packages/articles/public/services/article.js, like the articleId parameter already defined in there like this:
angular.module('mean.articles').factory('Articles', ['$resource',
function($resource) {
return $resource('articles/:articleId', {
articleId: '#_id'
}, {
update: {
method: 'PUT'
}
});
}
]);
Then you need to pass it in your angular controller search method, like the function that gets one by id, like this:
$scope.findOne = function() {
Articles.get({
articleId: $stateParams.articleId
}, function(article) {
$scope.article = article;
});
};
Personally I don't know how to add another parameter to the $resource object in addition to the existing one (articleId), you may have to create another $resource service with the new parameter (:tag) and use it in your search method in your angular controller.
Another way that sounds more simple and flexible to me is to just pass the search parameters in the query method, like this:
$scope.searchart = function() {
Articles.query({tag:$scope.selectedTag}, function(articles) {
$scope.articles = articles;
});
};
and then at the server side controller, read your query parameters like this:
exports.searcharticle = function(req, res) {
Article.find(req.query).sort('-created').populate('user', 'name username').exec(function(err, articles) {
if (err) {
res.render('error', {
status: 500
});
} else {
res.jsonp(articles);
}
});
};

Resources