Getting "Function: Bound" when trying to call a method - node.js

I'm using the Sails.js MVC and I'm trying to setup a service so I can make a call to an Active Directory server and pass the data for a user back to my controller.
I'm using some internal company modules for this which connect to our servers and pass back a user array with all the data for a selected user.
If I do this by making a function directly in the API controller it works fine, but when doing it by calling through a function from a separate file, rather than returning an array of [Function: bound].
Code from controller (LabController.js):
var adGet = require('../services/adGet');
module.exports = {
test: function (req, res) {
console.log(adGet.userData);
res.view({
user: adGet.userData
});
}
}
Code from the service (adGet.js):
module.exports = {
userData: function (req, res) {
var ad = require('active-directory');
ad.setCredentials({
user: 'username_removed',
password: 'password_removed'
});
ad.getUser(req.session.sisso.idsid).then(function (user) {
return (user);
});
}
}
Any help is greatly appreciated.

There's a few issues here.
First, you're trying to use return in your userData service method to return data, but it's an asynchronous function so that return statement is sending the data anywhere. You need to pass in a callback function as an argument to userData that can be called when the data is ready (i.e. when the database query returns):
module.exports = {
// note the new `cb` argument
userData: function (req, res, cb) {
var ad = require('active-directory');
ad.setCredentials({
user: 'username_removed',
password: 'password_removed'
});
ad.getUser(req.session.sisso.idsid)
.then(function (user) {
// Still nice to use `return` to make sure nothing
// gets executed after this statement, but it's the
// callback function that actually gets the data.
// Note the `null` first argument, indicating no errors.
return cb(null,user);
})
.catch(err) {
return cb(err);
});
}
}
Second, you're sending the adGet.userData function to your view as a local variable, but you're not actually calling it to get the data. And since it's an asynchronous function, you won't be able to call it from your view. You need to call it from within the controller and send the result to the view:
var adGet = require('../services/adGet');
module.exports = {
test: function (req, res) {
// Call service function, passing callback function in as argument
adGet.userData(req, res, function(err, user) {
// Handle errors
if (err) {return res.serverError(err);}
// If all goes well, send user data to view
return res.view({
user: user
});
});
}
}
Less importantly, you could refactor the userData method to not accept req and res as arguments--it's overkill. Save req and res for your controllers whenever possible. It would be better to have userData just expect userId and callback as arguments. Also, unless you've turned global services off using the config/globals.js file, you don't need to require the services file at the top of your controller; the adGet variable will be made available to you automatically.

Related

Fetching param.id data from front to backend - React / Node / Express

I am trying to return a user list on a component from my backend and I am having trouble passing my userID param back. I am using useEffect to pull the data from Redux then call an action on the component with what I think is the declared param. The correct userID is being passed correctly as far as I can see however I think the error is occuring when the route is passed the param.
In my action how should I pass the param of the userID that I want to get the data for? I have console.log the param.id/param.userID/userID etc. In the component I have the user.userID (from useSelector) however in the action folder I don't know how to pass it to the backend.
Also in the backend do I always have to set my params as id? can these have the same name as the value on the front-end such as 'userID'? I can only seem to get the backend Postman calls working with :id.
component
const user = useSelector(state => state.auth.user);
const [userDiveLog, setUserDiveLog] = useState({
user: [],
userDiveLogList: [],
expanded: false
})
// get access to dispatch
const dispatch = useDispatch();
useEffect(() => {
dispatch(fetchUserDiveLog(user.userID));
}, []);
action
// pulls the user dive log
export function fetchUserDiveLog(params, credentials, diveLogUserList){
return function(dispatch){
return axios.get("http://localhost:5002/api/divelog/userdiveloglist/" + params.userID)
.then(({data}) => {
dispatch(userDiveLogList(data));
});
};
}
Backend
route
// return an individual dive log
app.get('/api/divelog/userdiveloglist/:userID', controller.userDiveLog);
controller
exports.userDiveLog = (req, res, params, userID) => {
try {
const userID = req.params.userID
diveLog.findAll({
include: {all: true},
where: {diverUserNumber: userID}
})
.then(diveLog => {
const userDiveLogList = [];
for (i = 0; i < diveLog.length; i++) {
userDiveLogList.push(diveLog[i].dataValues);
}
if (!userDiveLogList) {
return res.status(404).send({message: "No dive logs belonging to this user"});
}
res.status(200).send({
data: userDiveLogList
})
})
} catch(err) {
res.status(500).send({
message: "Error retrieving dive log belonging to user id= " + id
});
}
};
Frontend
You're passing the user id (user.userID - a string) to the fetchUserDiveLog function but you're treating it like a params object with the userID property inside the function. params.userID returns undefined since params is a string (it holds the user id). Rename params to userId and add it to the URL.
You can also remove the credentials and diveLogUserList arguments from the fetchUserDiveLog function since they aren't used.
export function fetchUserDiveLog(userId) {
return (dispatch) => {
return axios
.get(`http://localhost:5002/api/divelog/userdiveloglist/${userId}`)
.then(({ data }) => {
dispatch(userDiveLogList(data))
})
}
}
Btw, you shouldn't hardcode the API URL. Use environment variables. If you're using Create React App, you can add environment variables prefixed with REACT_APP_ to .env or you can use dotenv-webpack if you have a custom Webpack setup.
Backend
There are a few issues with the backend code.
The userDiveLog function receives the next function as the third argument but it is named params which is confusing. Since you don't need the next function in the request handler, you should remove the params and userID arguments from the function. You can get access to userID from the req.params object which you're doing correctly.
exports.userDiveLog = (req, res) => {
// ...
}
The if (!userDiveLogList) condition will never be true since userDiveLogList is an array which is truthy in JavaScript. You can actually remove the if block. A response of { data: [] } will be sent if the user doesn't have any Divelogs which is perfectly okay. You can also omit the status(200) call since the status is automatically set to 200. And you can refactor the code by using object destructuring and Array.prototype.map to transform the divelogs.
const { userID } = req.params
const diveLogs = await diveLog.findAll({
include: { all: true },
where: { diverUserNumber: userID },
})
const data = diveLogs.map((log) => log.dataValues)
res.send({ data })
The catch block references the variable id which isn't defined anywhere. It should be userID instead.
The whole code using async/await:
exports.userDiveLog = async (req, res) => {
const { userID } = req.params
try {
const diveLogs = await diveLog.findAll({
include: { all: true },
where: { diverUserNumber: userID },
})
const data = diveLogs.map((log) => log.dataValues)
res.send({ data })
} catch () {
res.status(500).send({
message: `Error retrieving dive log belonging to user id= ${userID}`,
})
}
}
I have identified two questions after reading the description and I am going to answer each of those at a time.
Do I have to set the query parameter as Id?
No, there are no restrictions on the name of query parameter. You can literally name it as "something".
That said, there are some conventions and those dictate that you need to name the parameter to something that is appropriate.
How do I pass userId to my Action Creator?
First of all check the Function that is wrapping your Thunk. It expects 3 parameters: params (POORLY NAMED), credentials and diveLogUserList.
Where as, it is being dispatched with only 1 argument: userID.
Reconfigure this Wrapper Function to just receive the userID as an argument (and send credentials, diveUserList as an extra argument to the Thunk and not the wrapper function; This depends upon the functionality that you desire which is not properly understandable using the Description that you have provided).
After you reconfigured the wrapper function, you will dispatch the same like this:
fetchUserDiveLog(userID).
The Function handling your Route is incorrect
If I am not mistaken, controller.userDiveLog should only receive 3 arguments yet, you have defined your handler with 4 parameters.
The arguments that your handler should expect are: request, response and next.
The User ID that your handler expects will be a query parameter and will be accessible using: request.params.userID.
There is no need to expect userID as an argument to your handler.
Additional Information
I recommend going through these to get a better explanation and along with that, I recommend use of console.log as a method to debug the code. It will help you identify problems such as:
How many arguments is this function receiving?
What is the type of the argument received?
And much more
References
Route Function or Route Handler
Route Parameters
Sending Extra Argument to a Thunk

Document Not Saving with Mongoose Create() Function Called

I have some code to generate a user profile, and then I am attempting to save this profile to my database using Mongoose's create() function. For whatever reason this isn't working. It appears my user profile data is not being passed successfully to the add() function in my controller. It's not clear to me why this is the case.
First off, here's my controller's add() function:
exports.add = async ({
data = {}
} = {}) => {
console.log('data: ', data);
return User.create(data);
};
And here's the route info for the above function:
router.post('/', async ctx => {
const data = ctx.request.body;
console.log('data: ', data);
const user = await controller.add({
data
});
ctx.body = user;
});
And here's the relevant part of the code where I'm calling this function: note I do see the console.log data line running, so I know the function is being called. But for whatever reason data is still an empty object (the default), even though I am passed a JSON version of my user doc:
let formattedUser = new User();
formattedUser = JSON.stringify(userProfile);
console.log('formattedUser: ', formattedUser); // I see this in my console
try {
let createdUser = await UserCtlr.add(formattedUser);
console.log('createdUser: ', createdUser); // this DOES NOT print to the console
} catch (error) {
console.log(error);
}
I'm confused because I can see formattedUser being printed to the console BEFORE I pass it to the add() function. But the add() function only seems to contain an empty object (the default) when that function runs. By the way, I don't get any errors, even though I have this encapsulated in a try/catch.
Another note, I can successfully use Postman to call this add() function and save a user to the database. So I'm unclear why it's not working when I call it programatically. What am I missing here?
In your controller file, you are returning an object created from your User Schema, but not saving that object into the DB.. try returning the following :
User.create(data).save();
In your controller file. Then you created formattedUser, you can try:
formattedUser.save(function (err, createdUser) {
if (err) return console.error(err);
console.log(createdUser); //If created success
});

Why am I getting a warning when using express app.param to pre-load object with sequelize?

I'm using express app.param to load objects from db (app.param('userId', users.userById);):
exports.userById = function (req, res, next, id) {
return user.findOne({
where: { id: id }
}).then(function (result) {
req.user = result;
next();
}).catch(function (error) {
next(error);
});
};
After that I update the loaded object with the following code.
exports.update = function (req, res) {
var user = req.user;
return user.update({
//update properties
}).then(function () {
res.end();
}).catch(function (error) {
//error handling
});
};
For some reason I get the warning that "a promise was created in a handler but was not returned from it".
I can't see why, but that always happen when I use a routing parameter that uses sequelize before making the actual changes to the database.
What is the correct way to do this?
I'm using sequelize v3.23.3 with Postgres.
EDIT
I changed the code to a more simple example that throws the same warning.
If you forget to return that request promise, the next handler executes immediately with an argument of undefined - which is completely valid for the Promises/A+ spec, but I don't think that it is what you are looking for.
See How to execute code after loop completes for solutions.

Testing Express and Mongoose with Mocha

I'm trying to test my REST API endpoint handlers using Mocha and Chai, the application was built using Express and Mongoose. My handlers are mostly of the form:
var handler = function (req, res, next) {
// Process the request, prepare the variables
// Call a Mongoose function
Model.operation({'search': 'items'}, function(err, results) {
// Process the results, send call next(err) if necessary
// Return the object or objects
return res.send(results)
}
}
For example:
auth.getUser = function (req, res, next) {
// Find the requested user
User.findById(req.params.id, function (err, user) {
// If there is an error, cascade down
if (err) {
return next(err);
}
// If the user was not found, return 404
else if (!user) {
return res.status(404).send('The user could not be found');
}
// If the user was found
else {
// Remove the password
user = user.toObject();
delete user.password;
// If the user is not the authenticated user, remove the email
if (!(req.isAuthenticated() && (req.user.username === user.username))) {
delete user.email;
}
// Return the user
return res.send(user);
}
});
};
The problem with this is that the function returns as it calls the Mongoose method and test cases like this:
it('Should create a user', function () {
auth.createUser(request, response);
var data = JSON.parse(response._getData());
data.username.should.equal('some_user');
});
never pass as the function is returning before doing anything. Mongoose is mocked using Mockgoose and the request and response objects are mocked with Express-Mocks-HTTP.
While using superagent and other request libraries is fairly common, I would prefer to test the functions in isolation, instead of testing the whole framework.
Is there a way to make the test wait before evaluating the should statements without changing the code I'm testing to return promises?
You should use an asynchronous version of the test, by providing a function with a done argument to it.
For more details refer to http://mochajs.org/#asynchronous-code.
Since you don't want to modify your code, one way to do that could be by using setTimeout in the test to wait before to call done.
I would try something like this:
it('Should create a user', function (done) {
auth.createUser(request, response);
setTimeout(function(){
var data = JSON.parse(response._getData());
data.username.should.equal('some_user');
done();
}, 1000); // waiting one second to perform the test
});
(There might be better way)
Apparently, express-mocks-http was abandoned a while ago and the new code is under node-mocks-http. Using this new library it is possible to do what I was asking for using events. It's not documented but looking at the code you can figure it out.
When creating the response object you have to pass the EventEmitter object:
var EventEmitter = require('events').EventEmitter;
var response = NodeMocks.createResponse({eventEmitter: EventEmitter});
Then, on the test, you add a listener to the event 'end' or 'send' as both of them are triggered when the call to res.send. 'end' covers more than 'send', in case you have calls other than res.send (for example, res.status(404).end().
The test would look something like this:
it('Should return the user after creation', function (done) {
auth.createUser(request, response);
response.on('send', function () {
var data = response._getData();
data.username.should.equal('someone');
data.email.should.equal('asdf2#asdf.com');
done();
});
});

Pass function to next function, fire depending on condition. nodejs

Im using express.js to create a node.js REST server, as a part o this i am also creating a simple session system. I have 3 modules:
app.js
highscoreMan.js
userSession.js
The app.js url for http://localhost/api/highscores now calls userSession with given parameters:
//Get all highscores
app.get('/api/highscores', function (req, res) {
userSession.checkValidity(req.query['username'], req.query['sessionid'], highscoreMan.getAll(req, res));
});
However, in checkValidity the function that i pass is automatically called:
function checkValidity(username, sessionId, callback) {
userSession.findOne({ userid: username, sessionid: sessionId }, function (err, result) {
if (err) {
console.log(err);
}
if(result) {
callback;
}
});
}
I only want to run the function being passed given that i get the proper results from the database(other checks will be added later for session dates etc.). How would i do this?
To delay calling highscoreMan.getAll(), you'll need to make it a statement of another function that can be called later:
app.get('/api/highscores', function (req, res) {
userSession.checkValidity(req.query['username'], req.query['sessionid'], function () {
highscoreMan.getAll(req, res);
});
});
Otherwise, it's being called immediately and its return value is instead being passed to userSession.checkValidity().
Note that you'll also need to adjust checkValidity to call the passed callback:
// ...
if(result) {
callback();
}
// ...
Unless I don't fully understand your problem, couldn't you just do something like this?
if (result && some_validator(result)) {
callback();
}

Resources