I'm building a twitter sentiment reader and I am running into issues when I try and send a response back to angular. Everything worked fine before I added res.send() and it logged the sentiment.
Now that I added the res.send() function, it errors out sending the data back to angular. Angular has it as an error 500 and my node console has the error saying POST /postUsername Security Handshake Failed: some library stuff Description: End of TCP Stream".
Express Route
router.post("/postUsername", function(req, res){
console.log(req.body.userName);
var client = new Twitter({
consumer_key: '',
consumer_secret: '',
access_token_key: '',
access_token_secret: ''
});// client
client.get('statuses/user_timeline', {screen_name: req.body.userName, count:20}, function(error, tweets, response) {
tweet = analyzeIt.seperateData(tweets);
var document = language.document(tweet);
if (!error) {
var parsedScore;
var parsedMagnitude;
var finalScore;
var options = {
verbose: true
}; // options
document.detectSentiment(options, function(err, score) {
if (err) {
console.log(err);
} //if err
parsedScore = score.score;
parsedMagnitude = score.magnitude;
finalScore = analyzeIt.finalScore(parsedScore, parsedMagnitude);
console.log(finalScore);
});//detect sentiment
}//if not error
});//client get statuses
res.send(finalScore);
});//router post
Angular Controller
app.controller('myController', function($scope, $http){
$scope.sendData = function() {
var data = { userName: $scope.userName };
console.log($scope.userName);
$http.post('/postUsername', data)
.success(function (data, status, headers, config) {
$scope.PostDataResponse = data;
})
.error(function (data, status, header, config) {
$scope.PostDataResponse = "Data: " + status;
});
};
});
The expected output would be something like "This user trends positive."
Any help would be appreciated!
I see a few problems. First is that you are responding immediately, without waiting for the twitter request to complete.
// Call order: #1
client.get(..., function(error, tweets, response) {
// Call order: #3
// anything in here no longer matters
});
// Call order: #2
res.send(finalScore) //because this executes before the code in the callback above
So essentially when the call is made from angular, express immediately sends back the value of finalScore which is undefined.
The other problem is you aren't really handing the error case. If there is an error with the twitter client, you should respond to the request in a meaningful way, rather than just logging to the console. This way you can see, inside angular what the problem is instead of having to scratch your head and look at your server console:
if(!error) {
//do normal stuff
}
else {
res.status(500).send("Twitter error: " + error);
}
Same goes for detectSentiment:
document.detectSentiment(options, function(err, score) {
if (err) {
console.log(err);
res.status(500).send("Error detecting sentiment: " +err);
}
});
So, to fix your issue, you need to be responding inside your callbacks, not after:
router.post("/postUsername", function(req, res){
...
client.get('statuses/user_timeline', {screen_name: req.body.userName, count:20}, function(error, tweets, response) {
...
if (!error) {
...
document.detectSentiment(options, function(err, score) {
if (err) {
console.log(err);
res.status(500).send("Error detecting sentiment: " + err);
} //if err
...
console.log(finalScore);
res.send(finalScore);
});//detect sentiment
}
else {
res.status(500).send("Twitter error: " + error);
}
});//client get statuses
});//router post
It seems a little weird, at first, that you have to nest your response so deep, but it's not at all. This is the world of javascript. There are ways to use promises and deferred objects to clean up your code, but for now it's best to write it like this so that it really sinks in how asynchronous code in javascript works.
Related
I am sending a Postman POST to a LDAP Server. When I use the GET response for one function it works; however, when I use the POST for another function that involves client.binding. I get no response, "Could not get any response" after 1 minute of waiting. I am using http, with no proxy.
GET that works:
IAMserver.get('/account/list', function(req,res){
//list accounts for a compangy. seraches everyhting under provided compagny node.
let enterprise = req.jwtPayload.enterprise || "Enterprise";
var opts = {
//filter: 'cn='+base,
filter: '(objectclass=person)',
scope: 'sub'
};
console.log(result);
console.log('status: ' + result.status);
respondWithJSON(200, 0, "Success", res, entries);
POST that doesn't work:
IAMserver.post('/account/modify', function(req,res){
if(req.jwtPayload.entityData == null || req.jwtPayload.entityData == {}){
respondWithJSON(400, 400, "No entity data provided or data was not JSON encoded.", res);
return;
}
//Check if exists already.
aSearch(req.jwtPayload.cn).then(
//exists
function(result){
//Bind to admin account.
client.bind(adminCred, adminPass, function(err){
if(err){
respondWithJSON(500,500, err.message, res);
return;
}
});
//define modifications
let changes = new ldap.Change({
operation: 'replace',
modification: req.jwtPayload.entityData
});
//try to modify.
client.modify('cn='+req.jwtPayload.cn+',ou=Enterprise,dc=abc,dc=com', changes, function(err){
if(err){
respondWithJSON(500, 500, err.message, res);
return;
}
respondWithJSON(200, 0, "Changes executed correctly.", res);
});
},
//Doesn't exist, can't modify.
function(result){
respondWithJSON(200,1, "Failure. "+result.message, res);
});
});
So currently the tweet bot is;
var fs = require('fs'),
path = require('path'),
Twit = require('twit'),
config = require(path.join(__dirname, 'config.js'));
var T = new Twit(config);
function pick_random_countermagic(){
var countermagic = [
'Force-of-Will.jpg',
'Cryptic-Commad.jpg',
'Counterspell.jpg',
];
return countermagic[Math.floor(Math.random() * countermagic.length)];
}
function upload_random_image(){
console.log('Opening an image...');
var image_path = path.join(__dirname, '/countermagic/' + pick_random_countermagic()),
b64content = fs.readFileSync(image_path, { encoding: 'base64' });
console.log('Uploading an image...');
T.post('media/upload', { media_data: b64content }, function (err, data, response) {
if (err){
console.log('ERROR');
console.log(err);
}
else{
console.log('Uploaded an image!');
T.post('statuses/update', {
media_ids: new Array(data.media_id_string)
},
function(err, data, response) {
if (err){
console.log('Error!');
console.log(err);
}
else{
console.log('Posted an image!');
}
}
);
}
});
}
setInterval(
upload_random_image,
10000
);
All it does currently posts, at random an image, which is what I want, but instead of just posting it I would like it to post in reply to a targeted tweet from another twitter when that bot tweets, and or when the bot is active reply to all tweets that bot has tweeted that my bot has not already replied to... if you know what I mean.
This is my first time making a twitter bot, and technically my first time really using javascript and node.js. So yeah, I am just lost. So yeah any help would be insurmountably helpful.
You can retrieve mentions using statuses/mentions_timeline API and reply them using statuses/update API by passing mention's id in in_reply_to_status_id. Here is a simple example:
function replyMentions() {
// check for mentions
// you can add parameter 'since_id' to limit returns
T.get('statuses/mentions_timeline', { count: 100 }, function(err, data, response) {
if (data) {
data.forEach(function(mention) {
// reply if mention.id_str is not yet replied
reply = '#' + mention.user.screen_name + ' thanks for reaching out!'
T.post('statuses/update', { status: reply, in_reply_to_status_id: mention.id_str }, function(err, data, response) {
console.log(data)
// mark data.id_str as replied
})
})
}
})
}
You can add some additional logic to store replied id_str in database and use them to avoid redundant replies and limiting mentions retrieval (using since_id parameter).
let's first see the code before I start talking:
var sqlDb = require("mssql");
var settings = require("../settings");
exports.executeSql = function (sql, callback) {
var conn = new sqlDb.Connection(settings.dbConfig);
console.log('db.js Send sql-query');
console.log(" ");
conn.connect()
.then(function () {
var req = new sqlDb.Request(conn);
req.query(sql)
.then(function (recordset) {
callback(recordset);
})
.catch(function (err) {
console.log("here it breaks", err);
callback(null, err); //type error: undefined is not a function
})
})
.catch(function (err) {
console.log(err);
callback(null, err);
}); //
};
This function recieves an sql statement and a callback function. When I run the code I get [Type Error: undefined is not a function].
When I comment out the callback(recordset) it doesnt do anything (no error but also nothing else). So I think that the callback is simply not recognized as if it were out of scope. The weird part is, that the error object is transferred back via the same callback function and that seems to work.
The settings.dbConfig looks like this:
exports.dbConfig = {
user: "username",
password: "pwd",
server: "SERVERNAME", // not localhost
database: "DB-Name",
port: 1433
};
I am quite depressed by now. Would someone be so kind as to have a look at my code? I simply don't see the mistake.
Thank you
EDIT:
I call executeSql like this:
var db = require("./db");
var sql = "SELECT * FROM myTable";
db.executeSql(sql, function(data, err) {
if (err) {
console.log(" Internal Error: error connecting Database", err);
} else {
console.log("success", data);
}
});
I have an App using Parse.com as a backend and an external site that acts as my payment gateway. Upon receiving the customer/subscription webhook data from Stripe I wish to lookup the users email so I can then run a Cloud Code function and change their user status to 'paid'
My webhook receiver is:
Parse.Cloud.define("update_user", function(request, response) {
var data = request.params["data"]
var customer = data.object.customer;
response.success'Working' + request);
});
And I am able to get an email back from stripe from the customer ID using:
Parse.Cloud.define("pay", function(request, response) {
Stripe.initialize(STRIPE_SECRET_KEY);
console.log(JSON.stringify(request.params));
Stripe.Customers.retrieve(
customerId, {
success:function(results) {
console.log(results["email"]);
// alert(results["email"]);
response.success(results);
},
error:function(error) {
response.error("Error:" +error);
}
}
);
});
I need help turning this into a complete function that is run on receipt of every webhook from Stripe. I am also struggling with options for fallback if this does not work for whatever reason.
EDIT
Taking parts of the first answer and I now have:
Parse.Cloud.define("update_user", function(request, response) {
Stripe.initialize(STRIPE_SECRET_KEY);
var data = request.params["data"]
var customerId = data.object.customer;
get_stripe_customer(customerId, 100).then(function(stripeResponse) {
response.success(stripeResponse);
}, function(error) {
response.error(error);
});
});
function get_stripe_customer (customerId) {
Stripe.initialize(STRIPE_SECRET_KEY);
return Stripe.Customers.retrieve(
customerId, {
success:function(results) {
console.log(results["email"]);
},
error:function(error) {
}
}
);
};
My knowledge is really falling down on the Promise side of things and also the callback (success:, error, request response) etc further reading would be appreciated.
This is now working
Out of interest I did this:
Parse.Cloud.define("update_user", function(request, response) {
var data = request.params["data"]
var customerId = data.object.customer;
get_stripe_customer(customerId, 100).then(function(stripeResponse) {
return set_user_status(username, stripeResponse);
}).then(function(username) {
response.success(username);
}, function(error) {
response.error(error);
});
});
function get_stripe_customer (customerId) {
Stripe.initialize(STRIPE_SECRET_KEY);
return Stripe.Customers.retrieve(
customerId, {
success:function(results) {
// console.log(results["email"]);
},
error:function(error) {
}
}
);
};
function set_user_status(stripeResponse) {
Parse.Cloud.useMasterKey();
var emailquery = new Parse.Query(Parse.User);
emailquery.equalTo("username", stripeResponse['email']); // find all the women
return emailquery.first({
success: function(results) {
alert('running set_user_status success');
var user = results;
user.set("tier", "paid");
user.save();
},
error:function(error) {
console.log('error finding user');
}
});
};
open to improvements...
EDIT - I (#danh) cleaned it up a bit. A few notes:
used promises throughout. much easier to read and handle errors
get_stripe_customer requires only one param (that 100 was my idea to charge $100)
set_user_status appears to need only user email as param, which apparently is in the stripeResponse
set_user_status returns a promise to save the user. that will be fulfilled with the user object, not the username
be sure you're clear on how to identify the user. stripe apparently provides email address, but in your user query (in set_user_status) you compare email to "username". some systems set username == email. make sure yours does or change that query.
Parse.Cloud.define("update_user", function(request, response) {
var data = request.params["data"]
var customerId = data.object.customer;
get_stripe_customer(customerId).then(function(stripeResponse) {
var email = stripeResponse.email;
return set_user_status(email);
}).then(function(user) {
response.success(user);
}, function(error) {
response.error(error);
});
});
function get_stripe_customer(customerId) {
Stripe.initialize(STRIPE_SECRET_KEY);
return Stripe.Customers.retrieve(customerId).then(function(results) {
// console.log(results["email"]);
return results;
});
};
function set_user_status(email) {
Parse.Cloud.useMasterKey();
var emailquery = new Parse.Query(Parse.User);
emailquery.equalTo("username", email); // find all the women
return emailquery.first().then(function(user) {
user.set("tier", "paid");
return user.save();
}, function(error) {
console.log('error finding user ' + error.message);
return error;
});
}
Did a quick skim of the docs pertaining to stripe, and it looks like the steps are: (1) make a stripe REST-api call from your client side to get a token, (2) pass that token to a cloud function, (3) call stripe from the parse cloud to finish paying. I understand that you'd like to include a (4) fourth step wherein the transaction is recorded in the data for the paying user.
From the client (assuming a JS client):
var token = // we've retrieved this from Stripe's REST api
Parse.Cloud.run("pay", { stripeToken: token }).then(function(result) {
// success
}, function(error) {
// error
});
On the server:
Parse.Cloud.define("pay", function(request, response) {
var user = request.user;
var stripeToken = request.params.stripeToken;
payStripeWithToken(stripeToken, 100).then(function(stripeResponse) {
return updateUserWithStripeResult(user, stripeResponse);
}).then(function(user) {
response.success(user);
}, function(error) {
response.error(error);
});
});
Now we need only to build promise-returning functions called payStripeWithToken and updateUserWithStripeResult.
// return a promise to pay stripe per their api
function payStripeWithToken(stripeToken, dollarAmt) {
Stripe.initialize(STRIPE_SECRET_KEY); // didn't see this in the docs, borrowed from your code
return Stripe.Charges.create({
amount: dollarAmt * 10, // expressed in cents
currency: "usd",
card: stripeToken //the token id should be sent from the client
});
// caller does the success/error handling
}
// return a promise to update user with stripeResponse
function updateUserWithStripeResult(user, stripeResponse) {
var transactionId = // dig this out of the stripeResponse if you need it
user.set("paid", true);
user.set("transactionId", transactionId);
return user.save();
}
{ text: undefined,
done: false,
_id: 529e16025f5222dc36000002,
__v: 0 }
PUT /api/todos/529e16025f5222dc36000002 200 142ms - 68b
I keep getting this error when trying to do an update for my simple CRUD todo list. When I submit the update, the change doesn't appear on screen, although the put says it's a 200. Not sure what steps to take so that I don't get this "undefined" error and so I can have the update show up on screen.
EDIT: Included more code
This is the back-end node code:
app.put('/api/todos/:_id', function(req, res) {
Todo.findById(req.params._id, function(err, todos){
todos.text = req.body.text;
console.log(todos);
todos.save(function() {
if (!err) {
res.send(todos);
} else if (err) {
res.send(err);
}
Todo.find(function(err, todos) {
if (err)
res.send(err);
res.json(todos);
});
});
});
});
This is the Angular front-end code:
$scope.updateTodo = function(id) {
$scope.newItem = prompt("Please enter your new item:", "");
$http.put('/api/todos/' + id, {formData: $scope.newItem}).success(function(data) {
$scope.todos = data;
});
$http.get('/api/todos').success(function(data) {
$scope.todos = data;
});
};
I think it's because of this:
$http.put('/api/todos/' + id, { formData: $scope.newItem} )
^^^^^^^^
You're passing a single formData parameter with the request, yet in your Express code, you use this:
req.body.text
Either try this:
req.body.formData.text
Or don't use the formData parameter at all and pass $scope.newItem directly.
Besides that, your Express code is a bit messy: it might send back multiple responses and it doesn't check for errors on the save (as #PaulGray also pointed out).