How can I better optimise these DB queries/function flow? - javascript

I've turned my hand from game dev to writing a supporting back-end. Thus far everything seems to work out but when i got to writing a friend system i ran into this function flow which seemed very dirty to me. And i'm certain i'm just hacking it together at this point. Any node.js wizards about to tell me how I can improve this?
Fairly certain i should be caching player lookups in Redis as well.
acceptfriend: function(req, res){
//Find our user
User.findById( req.decoded._id, function(err, user){
//error occured
if(err){
return res.status(401).send(err);
}
//no user found
if(!user){
return res.status(401).json({
succes: false,
message: 'no user found with that id'
} );
}
//Does the request exist?
if( !_.any( user.social.friendRequests, {id: req.params.id} ) ){
return res.status(401).json( {
succes: false,
message: 'friend request not found'
} );
}
//find the user that belongs to the request
User.findById( req.params.id, function(err, friend){
//error occured
if(err){
return res.send(err);
}
//user doesnt exist
if(!friend){
return res.status(401).json({
succes: false,
message: 'no user found with that id'
} );
}
//Pull the request from the friendRequests array
user.social.friendRequests.pull( req.params.id );
//Add the friend
user.social.friends.addToSet( {
user_id: friend._id,
name: friend.username,
corp: 'n/a'
} );
//Add the user to the friends list as well
friend.social.friends.addToSet({
user_id: user._id,
name: user.username,
corp: 'n/a'
});
//save the docs
user.save();
friend.save();
} );
//return success
return res.status(200).json({
success: true,
message: 'friend succesfully added'
});
} );
}

1- First of all, you have a big function. You have to split it into some functions. Doing this you gain the possibility to test them with any testing framework.
2- Delegate the handle of error responses to the controller.
from -> return res.status(401).send(err);
to (with Promises)-> deferred.reject(err);
to (normal way) -> throw new Error(err);
3- You can use Promises to manage the asynchronous behaviour of node to clear the code.
I created an example, maybe is not working at first time, feel free to fix the incorrent references. The User ref, the 'acceptfriend' method...
Gist: https://gist.github.com/aitoraznar/b7099ad88ead0cdab256
var Promise = require('bluebird');
var _ = require('lodash');
//var User = app.models.User;
var ERRORS = {
userNotFoundError: {
code: 401,
success: false,
message: 'no user found with that id'
},
friendRequestNotFoundError: {
code: 401,
success: false,
message: 'friend request not found'
},
friendNotFoundError: {
code: 401,
success: false,
message: 'no friend found with that id'
}
}
var SUCCESS_MESSAGES= {
friendAddedSuccessfully: {
success: true,
message: 'friend succesfully added'
}
};
var userDAO = {
/*
*
*/
getUserById: function(id) {
var deferred = Promise.pending();
User.findById(id, function(err, user) {
//error occured
if (err) {
err.code = 401;
return deferred.reject(err);
}
//no user found
if (!user) {
return deferred.reject(ERRORS.userNotFoundError);
}
deferred.resolve(user);
});
return deferred.promise;
},
/*
* Does the request exist?
*/
checkFriendRequest: function(user, friendId) {
var deferred = Promise.pending();
if (userDAO.haveFriendRequestFrom(user, friendId)) {
deferred.resolve(user, friendId);
} else {
return deferred.reject(ERRORS.friendRequestNotFoundError);
}
return deferred.promise;
},
/*
*
*/
haveFriendRequestFrom: function(user, friendId) {
return _.any(user.social.friendRequests, {id: friendId });
},
/*
*
*/
getFriend: function(user, friendId) {
var deferred = Promise.pending();
userDAO.getUserById(friendId)
.then(function(friend) {
deferred.resolve(user, friend);
},
function(error) {
if (error === ERRORS.userNotFoundError) {
// Then the error is friend not found
// Override the error
error = ERRORS.friendNotFoundError;
}
return deferred.reject(error);
});
return deferred.promise;
},
/*
*
*/
makeFriendship: function(user, friend) {
var deferred = Promise.pending();
//Pull the request from the friendRequests array
user.social.friendRequests.pull(friend._id);
//Add the friend
user.social.friends.addToSet( {
user_id: friend._id,
name: friend.username,
corp: 'n/a'
} );
//Add the user to the friends list as well
friend.social.friends.addToSet({
user_id: user._id,
name: user.username,
corp: 'n/a'
});
//save the docs
user.save();
friend.save();
// Return the new friendship
var friendship = {
user: user,
friend:friend
};
deferred.resolve(friendship);
return deferred.promise;
},
/*
*
*/
friendRequestError: function(err) {
var deferred = Promise.pending();
// Propagate de error
deferred.reject(err);
return deferred.promise;
},
/*
*
*/
friendRequest: function(userId, friendId) {
var deferred = Promise.pending();
// Get user by ID
userDAO.getUserById(userId)
// Check if the user is able to add the friend
.then(userDAO.checkFriendRequest, userDAO.friendRequestError)
// Get the friend to add
.then(userDAO.getFriend, userDAO.friendRequestError)
// Make the friendship
.then(userDAO.makeFriendship, userDAO.friendRequestError)
// Response to the controller
.then(
function(friendship) {
// Resolve with new friendship
// This goes to 'success' function in controller
deferred.resolve(friendship);
}, function(error) {
// This goes to 'error' function in controller
deferred.reject(error);
})
return deferred.promise;
}
};
// Controller
var acceptfriend = function(req, res, next) {
var userId = req.decoded._id;
var friendId = req.params.id;
userDAO.friendRequest(userId, friendId)
.then(function(friendRequest) {
console.log('---> SUCCESS');
//return success
return res.status(200)
.json(SUCCESS_MESSAGES.friendAddedSuccessfully);
}, function(error) {
console.error('---> ERROR', error);
return res.status(error.code).json(error);
});
}
4- Create database indexes in the collection/table
Regards,
Aitor

Related

Node & MongoDB - TypeError: Cannot set property 'userProfileModel' of undefined

I am getting this error while trying to do a logon test in Postman. I already did a register test and all the input fields were registered with a 200 Status and made sure all the entries are posted using Robomongo. But when I try to do a logon with userName and password, this is where I get the following TypeError:
(node:13962) DeprecationWarning: crypto.pbkdf2 without specifying a digest is deprecated. Please specify a digest
/Users/Wilo/Desktop/myway_stuff/myway_app/MyWay/server/controllers/account.js:63
me.account.userProfileModel = userProfileModel;
^
TypeError: Cannot set property 'userProfileModel' of undefined
at InternalFieldObject.ondone (/Users/Wilo/Desktop/myway_stuff/myway_app/MyWay/server/controllers/account.js:63:49)
Here is a snippet where the logon function is executed within my account.js controller:
AccountController.prototype.logon = function(userName, password, callback) {
var me = this;
me.userModel.findOne({ userName: userName }, function (err, user) {
if (err) {
return callback(err, new me.ApiResponse({ success: false, extras: { msg: me.ApiMessages.DB_ERROR } }));
}
if (user && user.passwordSalt) {
me.hashPassword(password, user.passwordSalt, function (err, passwordHash) {
if (passwordHash == user.passwordHash) {
var userProfileModel = new me.UserProfile({
userName: user.userName,
email: user.email,
firstName: user.firstName,
lastName: user.lastName
});
//Save to HTTP account
me.account.userProfileModel = userProfileModel; // This is Line 63 in the stacktrace
me.account.id = me.uuid.v4();
//Save to persistent account
me.userAccount.userId = user._id;
me.userAccount.accountId = me.account.id;
me.userAccount.save(function (err, accountData, numberAffected) {
if (err) {
return callback(err, new me.ApiResponse({ success: false, extras: { msg: me.ApiMessages.DB_ERROR } }));
}
if (numberAffected === 1) {
// Return the user profile so the router sends it to the client app doing the logon.
return callback(err, new me.ApiResponse({
success: true, extras: {
userProfileModel: userProfileModel,
accountId: me.account.id
}
}));
} else {
return callback(err, new me.ApiResponse({ success: false, extras: { msg: me.ApiMessages.COULD_NOT_CREATE_ACCOUNT } }));
}
});
} else {
return callback(err, new me.ApiResponse({ success: false, extras: { msg: me.ApiMessages.INVALID_PWD } }));
}
});
} else {
return callback(err, new me.ApiResponse({ success: false, extras: { msg: me.ApiMessages.EMAIL_NOT_FOUND } }));
}
});
};
Would appreciate any help regarding this issue.
Looks like you "me" variable does not have account variable/parameter.

meteor server insert data without login

I want to create API that allow other app to insert(create) new data. But so far I tried, this not work with error message "User id is required", I know that happen because no login user found when insert new data. Is it possible to insert new data without login or any possible way to login from server side if using accounts-password's package or any possible way to make this work?
code on server
Picker.route('/createFood/:title', function(params, req, res, next) {
console.log('-> params : ',params);
let username = (new Date()).getTime().toString();
function createFood() {
Fiber(function() {
console.log("-> username : ",username);
let acc = Accounts.createUser({
username: username,
email: username +'#foodie.com',
password: username
});
if (acc) {
console.log("-> acc : ",acc);
// Meteor.call("setUserId", acc);
Menus.insert({
title: params.title,
}, function(err, foodId) {
console.log("-> abs : ",Meteor.absoluteUrl());
console.log("-> err : ",err.message);
console.log("-> foodId : ",foodId);
let code, result;
if (err) {
code = 500;
result = {
error: err.message
}
} else {
code = 200;
result = {
foodId: foodId,
}
}
res.setHeader( 'Content-Type', 'application/json' );
res.statusCode = code;
res.end( JSON.stringify( result ) );
})
}
}).run();
}
if (params.title)
createFood();
});
code food model, there is userId owner here
if (Meteor.isServer) {
Menus.allow({
insert: function() {
return true;
},
update: function() {
return true;
},
remove: function() {
return true;
},
fetch: ['foodId'],
});
Menus.after.insert((userId, doc) => {
....
})
}
There is no reason why you can't insert to the database without logging in. You don't even have to include the accounts package if you don't want to .
Your current code doesn't insert unless a user is present, you can simplify it to this...
function createFood() {
Menus.insert({
title: params.title,

getting an error once trying to retrieve data with NODEJS

I have a little issue trying to perform a get in my app.
first this is the error in the browser console
GET http://localhost:1337/sports/getChecked/12787fb1e00a01337f0508ca47223d15
401 (Unauthorized)
code.min.js:875 Object {err: "JSON request needed"}
code.min.js:543 Error {stack: (...)}
code.min.js:967 ["3042", "59321H", "5932"]
code.min.js:690 ["3042", "59321H", "5932"]
I am doing a post and in the post everything is fine so far, the issue is in the get, I am working with Json web tokens (JWT)...
these are the routes for post and get
module.exports.routes = {
'post /sports/checked': 'SetSportsController.setCheck',
'get /sports/getChecked/:user': 'SetSportsController.retrieveSetCheck'
};
here the policies
module.exports.policies = {
setCheck: ['jwtAuth', 'sanitizerPolicy', 'headersPolicy', 'sessionKiller'],
retrieveSetCheck: ['jwtAuth', 'sanitizerPolicy', 'sessionKiller']
};
get on SetSportController.js
retrieveSetCheck: function(req, res) {
if (req.params) {
SportSelectionService.getSportChecked(req.params).then(function(sportChecked) {
console.log(sportChecked.sport);
res.json(200, sportChecked);
}, function(err) {
res.json(400, err);
});
}else {
res.json(400, {error: 'Error retrieving Sports'});
}
}
SportSelectionService.js
getSportChecked: function(params) {
var Promise = require('bluebird');
return new Promise(function(fullfill, reject) {
console.time('sportChecked_findOne');
SportSelection.find({
user: params.user
}).exec(function(err, sportChecked) {
console.timeEnd('sportChecked_findOne');
if (err) {
reject(new Error('Error finding favorite leagues'));
console.error(err);
}else {
if (sportChecked) {
fullfill(sportChecked.sport);
}else {
console.time('sportChecked_create');
SportSelection.create({
sport: [],
user: params.user
}).exec(function(err, created) {
console.timeEnd('sportChecked_create');
console.log(err);
console.log(created);
if (err) {
reject(new Error('Error on sportChecked'));
}else {
fullfill(created);
fullfill(created.sport);
}
});
}
}
});
});
}
this is the FRONT-END part
<ion-item ng-click="toggleSportSelection(sport)">
{{:: sport.name}}
</ion-item>
controller.js
SportsFactory.getSportChecked(customer).then(function(sportChecked) {
console.log(sportChecked);
_.each(sports, function(sport) {
var sportIds = _.pluck(sport, 'id'),
intersectedSports = _.intersection(sportIds, sportChecked),
checkedSportObjects = _.filter(sport, function(sportObj) {
return _.includes(intersectedSports, sportObj.sportIds);
});
_.each(checkedSportObjects, function(sport) {
$scope.sportObj.push(sport);
});
});
});
$scope.toggleSportSelection = function(sport) {
var params = {};
params.user = $scope.customer.customer;
params.sport = sport.id;
sport.checked = !sport.checked;
SportsFactory.setSportChecked(params);
};
service.js
getSportChecked: function(customer) {
var defer = $q.defer(),
user,
rejection = function(err) {
console.log(err);
defer.reject(err);
};
LocalForageFactory.retrieve(CONSTANT_VARS.LOCALFORAGE_SPORTS_CHECKED)
.then(function(sportChecked) {
user = customer.customer;
if (!_.isNull(sportChecked)) {
defer.resolve(sportChecked);
}else {
$http.get(CONSTANT_VARS.BACKEND_URL + '/sports/getChecked/' + user)
.success(function(sportChecked) {
LocalForageFactory.set(CONSTANT_VARS.LOCALFORAGE_FAVORITE_LEAGUES, sportChecked);
defer.resolve(sportChecked);
})
.error(rejection);
}
}, rejection);
return defer.promise;
}
looking on the files project I got this file named headersPolicy.js which looks like this
module.exports = function(req, res, next) {
var _ = require('lodash');
if (!_.isNull(req.headers)) {
/* This API only accepts JSON, we could send 406 response, but we don't want
* to give any kind of clues ;)
*/
if (!req.is('json')) {
return res.json(401, {err: 'JSON request needed'});
}
next();
}else {
/*
* No headers, wrong request
* */
return res.json(401, {err: 'Headers not present'});
}
};
so where is this error coming from ? what am I doing wrong ?
The middleware below is checking (presumably) whether the Content-Type header is set to 'application/json'. Since you're sending a GET and not a POST request, that header is most likely not application/json.
if (!req.is('json')) {
return res.json(401, {err: 'JSON request needed'});
}

How to use q.defer in node.js to chain promises?

exports.list = function(req, res) {
var location_parent_id = req.params.location_parent_id;
var query = {
company_id: req.company_id
};
if(location_parent_id) {
query.location_parent_id = location_parent_id;
Location.findOne({someQuery}, function(err, location) {
response.location = location;
});
} else {
query.location_parent_id = {
'$exists': false
}
}
Location.find(query, function(err, locations) {
if(err) {
response = {
status: 'error',
error: err
}
} else if(!locations) {
response = {
status: 'error',
error: 'Location not found'
}
} else {
response = {
status: 'ok',
locations: locations
}
return res.json(response);
}
});
}
That's my code. If there is a location_parent_id, then I want to return that location as well. Rather than getting into async and callback hell, I figure promises are a good way to execute what I want. Just not sure of exactly how.
You don't need to use q.defer at all. You can use the Node-callback interface methods to get promises right away. To chain the methods, use .then().
exports.list = function(req, res) {
var result = Q.ninvoke(Location, "find", {
company_id: req.company_id,
location_parent_id: req.params.location_parent_id || {'$exists': false}
}).then(function(locations) {
if (!locations)
throw new Error('Location not found');
return {
status: 'ok',
locations: locations
};
});
if (req.params.location_parent_id) {
// insert the step to wait for the findOne (in parallel), and merge into res
result = Q.all([result, Q.ninvoke(Location, "findOne", {someQuery})])
.spread(function(res, location) {
res.location = location;
return res;
});
}
result.catch(function(err) {
return {
status: 'error',
error: err.message
};
}).done(function(response) {
res.json(response);
});
}

Async waterfall equivalent with Q

I've got a single page which is an account settings page. In it, I allow my users to update their avatar (if they've attached an image), change their email (if it has been changed from the original), and change their name and password.
Right now, I'm using async's waterfall method, but am swapping out async for Q since I prefer the syntax (and api). I'm wondering if this is the way that I should be using Q in replacement of async's waterfall.
I'm doing something like this:
exports.settingsAccountPOST = function(req, res) {
var doesEmailExist = function() {
var deferred = Q.defer();
User.findByEmail({
email: req.body.email
}, function(err, user) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(user);
}
});
return deferred.promise;
};
var updateEmail = function(email) {
var deferred = Q.defer();
User.updateEmail({
userId : req.session.user.id,
email : req.body.email
}, function(err, updated) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(updated);
}
});
return deferred.promise;
};
var updateName = function() {
var deferred = Q.defer();
if (req.body.name) {
User.updateName({
userId: req.session.user.id,
name: req.body.name
}, function(err, updated) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(updated);
}
});
return deferred.promise;
}
};
doesEmailExist().then(function(email) {
if (!email) {
return(updateEmail(email));
}
}).then(function() {
return(updateName())
}).then(function() {
res.redirect('/account')
});
};
Say that there is an error with the email address being used. Is there a way to "pass" it to the final call? Use case: Updated password properly, but email update didn't work, so I want to show a session flash to the user telling them they updated their password properly, but there was an issue with updating their email.
I was looking in the docs and it seems I may need to use:
.fin(function () {
});
Is this correct? If so, what should I be passing into that? Just push to an object the error that occurred within the chain and then loop through all errors and display them to the user? Or just return immediately and display the error?
If you are using Q.defer you are generally doing something wrong.
var findByEmail = Q.nbind(User.findByEmail, User);
var updateEmail = Q.nbind(User.updateEmail, User);
var updateName = Q.nbind(User.updateName, User);
//later on...
exports.settingsAccountPOST = function (req, res) {
findByEmail({
email: req.body.email
})
.then(function (user) {
if (!user) {
return updateEmail({
userId: req.session.user.id,
email: req.body.email
});
}
})
.then(function () {
return updateName({
userId: req.session.user.id,
name: req.body.name
})
})
.then(function () {
res.redirect("/account");
})
.catch(function(e){
//Handle any error
});
};

Categories