es6 mongoose nested findById promise - arrays

I'm building a restful api using node express mongoose/mongo etc.. I'm trying to output an array of users that are being followed by a particular user. Here is the schema.
var UserSchema = new mongoose.Schema({
username: {type: String, lowercase: true, unique: true, required: [true, "can't be blank"], match: [/^[a-zA-Z0-9]+$/, 'is invalid'], index: true},
email: {type: String, lowercase: true, unique: true, required: [true, "can't be blank"], match: [/\S+#\S+\.\S+/, 'is invalid'], index: true},
bio: String,
image: String,
following: [{ type: mongoose.Schema.Types.ObjectId, ref: 'User' }]
}, {timestamps: true});
So every User has array of users in an array in the key 'following'. I'm trying to output that list by first finding the user record through it's own id and then mapping through this array to find the followed users for this current user.
router.get('/users/friends', auth.required, function(req, res, next) {
var limit = 20;
var offset = 0;
if(typeof req.query.limit !== 'undefined'){
limit = req.query.limit;
}
if(typeof req.query.offset !== 'undefined'){
offset = req.query.offset;
}
User.findById(req.payload.id)
.then(function(user){
if (!user) { return res.sendStatus(401); }
return res.json({
users: user.following.map(function(username){
User.findById(username)
.then(function(userlist){
console.log('userlist:',userlist.username);
return userlist.username;
})
.catch(next)
})
})
})
.catch(next);
});
Now the console.log in this code outputs the correct data in the js console but I can't seem to find a way to deliver this to the client. So far my efforts bring forth 'null' values in the client. The correct amount of records but just null values. Any ideas how to fix this?
revised my code to this after taking advice below. Now it manages to get the first record to the client but then errors out with
UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Can't set headers after they are sent.
Blockquote
router.get('/users/friends', auth.required, function(req, res, next) {
var limit = 20;
var offset = 0;
if (typeof req.query.limit !== 'undefined') {
limit = req.query.limit;
}
if (typeof req.query.offset !== 'undefined') {
offset = req.query.offset;
}
User.findById(req.payload.id)
.then(function(user) {
if (!user) {
return res.sendStatus(401);
}
Promise.all(
user.following
).then(function(userarray) {
console.log(userarray);
userarray.forEach(function(userid) {
Promise.all([
User.find({
_id: {
$in: userid
}
})
.limit(Number(limit))
.skip(Number(offset))
.populate('author')
.exec()
]).then(function(results) {
userdetails = results[0];
var userdetailsCount = results[1];
return res.json({
userdetails: userdetails.map(function(userdetail){
return userdetail;
})
});
})
})
})
})
.catch(next);
});

Your problem section is:
return res.json({
users: user.following.map(function(username){
User.findById(username)
.then(function(userlist){
console.log('userlist:',userlist.username);
return userlist.username;
})
.catch(next)
})
})
The bit User.findById(username) will return a promise. But you are not awaiting on that promise. I'm guessing you think that the then function following that promise, which logs userlist.username to the console and returns it, should mean that your map function returns a list of userlist.username's. But this is not the case. Your map function is returning an array of promises.
What you really want is a feature like Bluebird's Promise.map: http://bluebirdjs.com/docs/api/promise.map.html (or, look for a similar feature, to deal with arrays of promises, in whichever promise library you happen to be using).

Related

I can't update my contacts

I read some tutorials on MEAN Stack and I'm currently working on one which is almost complete, until I tried my update function. I keep on receiving a 400 message and when i look at the chrome dev tools and under the network tab I read the response which is "_id" is not allowed.
here is the update function
$scope.update = () => {
console.log($scope.contact._id);
$http.put('/api/contacts/updatecontact/' + $scope.contact._id, $scope.contact)
.then(function(data) {
alert('Data was updated Successfully');
refresh();
});
};
here is the api.
app.put('/api/contacts/updatecontact/:id', (req, res) => {
const id = req.params.id;
console.log(req.body.name);
// validation
const { error } = validateInput(req.body);
if (error){
return res.status(400).send(error.details[0].message);
}
const updatedContact = Contact.where({ _id: id });
updatedContact.update({
$set: { name: req.body.name, email: req.body.email
}
}, (err, contact) => {
if(err){
console.log('error occured');
}
res.json(contact);
console.log('contact successfully updated');
});
console.log(updatedContact);
});
hope i can get some help with this.
It seems the node.api is validating the json object that you are passing. Make sure the properties you sent form the client.
Make sure you are not validating or remove the validation on the node.

Callback not working in request npm

I have some problems with the callback when I'm using Request npm.
My project is an API for the users developed by Sailsjs, on the other hand I'm using reactjs for the front-end, and I'm using Request npm to communicate with the API.
I have some issues with the callback.
My code :
handleClick(event) {
var apiBaseUrl = "http://localhost:1337/user";
if (
this.state.first_name.length > 0 &&
this.state.last_name.length > 0 &&
this.state.email.length > 0 &&
this.state.password.length > 0
) {
request.post({url : apiBaseUrl + "/store", form :{
first_name: this.state.first_name,
last_name: this.state.last_name,
email: this.state.email,
password: this.state.password
}}, function(response) {
console.log(response);
if (response.data.code === 200) {
var loginscreen = [];
loginscreen.push(
<Login
parentContext={this}
appContext={this.props.appContext}
key = {rand}
/>
);
var loginmessage = "Not Registered yet.Go to registration";
this.props.parentContext.setState({
loginscreen: loginscreen,
loginmessage: loginmessage,
buttonLabel: "Register",
isLogin: true
});
} else {
console.log("some error ocurred", response.data.code);
}
});
} else {
alert("Input field value is missing");
}
}
The post request is doing well, when I'm checking the database I found the user saved and the response in networking onglet in chrome is good, but when I do console.log(response) it's always null. I don't know if my callback isn't right, or if it's something else.
form
http response
variables in response
Error
Back-end
// Store user
store: function(req, res) {
var params = _.extend(req.query || {}, req.params || {}, req.body || {});
console.log(params);
UserService.store(params, function(err, user) {
if (err) return res.json();
if (!user) return res.json();
return res.send({
"code": 200,
"success": "user registered sucessfully"
});
});
},
Can you help me to fix this problem?
Actually, the problem is on your callback, the callback takes two arguments , it should be like this :
function(error, response) {.....}
the error is the first argument and the response is the second, you are getting null because there is no error ! and the response was treated as the error object.

How to get value from router request?

I have created an angular app. In this app, I want to add a search. for this, I have a textbox and a button. Textbox name is name="search"
I have a get method in API.
router.get('/allActivities', (req, res) => {
Activity.find({ name: req.body.search }, (err, activities) => {
if(err) {
res.json({success: false, message: err});
} else {
if(!activities) {
res.json({success: false, message: 'No recent activities found'});
} else {
res.json({success: true, activities: activities});
}
}
})
});
This is the get request. In this, I'm trying to get the text box value from angular front end
Activity.find({ name: req.body.search }, (err, activities) =>
This is MongoDB
But I'm not getting any output. The matter here is I'm not getting a value for this "req.body.search" which I used to get text box value. can anybody tell me how to do this?
if I put
Activity.find({ name: 'Roshini' }, (err, activities) =>
like this, I'm getting the output. So pretty sure that I'm not getting textbox value to this get method correctly, :/
Html side
<input ng-model="search">
Angular Controller
$http({
method: 'GET',
url: '/allActivities?search='+$scope.search
}).then(function (response) {
$scope.activities = response.data || response
}, function (response) {
alert(response)
}
);
and on backend access it by req.query.search or req.params.search rather than req.body.search

Search in whole collection(mongodb) using nodejs

I want to implement whole search feature in Mongodb collection using Nodejs.
What should I pass to SaleModel.find() so given value search in whole collection?
Here is what I have try so for but it only search product_name and I want to search sale_amount, sale_person, department_name too.
How I can do this?
SaleModel.find({'product_name': 'searched value'});
Schema:
var saleSchema = mongoose.Schema({
product_name:{ type:String, required:true},
sale_amount:{ type:Number, required:true },
sale_date:{ type:Date, default:Date() },
sale_person:{ type:String, required:true },
department:{ type:mongoose.Schema.Types.ObjectId, ref:'department' },
});
module.exports = mongoose.model('sale', saleSchema);
I would highly write more cleaner, below sample uses async.parallel, Promise and Mongoose.Query
function list(req) {
// promise or callback works as well
return new Promise(function(resolve, reject){
// npm install async --save
var async = require('async');
// some validation can be applied
var page = {
skip: req.query.start || 1,
limit: req.query.length || 25,
text: req.query.search || '' // <== this is new property!
};
// reuse Mongoose.Query with search by regex
var Query = Models.SaleModel.find({
product_name: new RegExp(page.text, "i")
});
// run without waiting until the previous function has completed
async.parallel([
function(){
Query.count(callback); // <== count
},
function(){
Query.skip(page.skip).limit(page.limit).exec('find', callback); // <== items
// or the below one should also work, just don't remember
// Query.skip(page.skip).limit(page.limit).exec(callback);
}
]), function(err, results){
if(err){
reject(err);
} else {
resolve({
count: results[0],
data: results[1]
});
}
});
});
}

Sequelize validation throwing error

I'm trying to check that a username is unique, and I gather that I'd need a custom validation for that. I've written the following code, but instead of returning the error in the array returned by .validate(), it just throws the error, which isn't the behaviour described in the docs and isn't what I want.
var User = sequelize.define('User', {
username: {
type: DataTypes.STRING,
validate: {
isUnique: function (username) {
User.find({ where: { username: username }})
.done(function (err, user) {
if (err) {
throw err;
}
if (user) {
throw new Error('Username already in use');
}
});
}
}
},
The validation you are doing is asynchronous. You call the User.find method, and after that the validation method returns. Sequelize has no way of knowing that you are doing something async in your validation, unless you tell it so. Once your find call is done you throw the error, but the validation has completed, so there is no code to catch that error, which means the error is thrown and crashes the app
The way to tell sequelize that you are doing something async is to take a second argument to your function, which is a callback. If you call the callback without any arguments, the validation succeeded, if you give it an argument, the validation failed.
var User = sequelize.define('User', {
username: {
type: DataTypes.STRING,
validate: {
isUnique: function (username, done) {
User.find({ where: { username: username }})
.done(function (err, user) {
if (err) {
done(err);
}
if (user) {
done(new Error('Username already in use'));
}
done();
});
}
}
},
Could you point me to the part of the documentation that mislead you, then we can hopefully correct it :)
Sequelize supports Promise style async operations. Specifically the Bluebird.js lib. Just change your function to specifically use the Promise -> next() pattern.
var User = sequelize.define('User', {
username: {
type: DataTypes.STRING,
validate: {
isUnique: function (username) {
User.find({ where: { username: username }})
.then(function (user) {
if (user) {
throw new Error('Username already in use');
}
});
}
}
},
This will also handle any errors on User.find() for you.
See this example in their codebase
Of course the easiest way to handle unique constraints is by setting unique: true on the field definition itself:
var User = sequelize.define('User', {
username: {
type: DataTypes.STRING,
unique: true
},
But this requires that you are either creating the table using Sequelize or already have a table with the unique constraint set.
This question got a valid answer which explains how the validation option works. But in this situation you don't have to check if a user exists on your own.
Sequelizejs has a findOrCreate() method, that checks if somethings exists before doing anything.
Very useful method :)

Resources