Saving to MongoDB when Socket.io event is emitted - javascript

I'm utilizing a MEAN stack and Socket.io to pull images from the real-time Instagram API. Everything is working great, but I now want to begin saving image data to a MongoDB database so I have a "history" of images from locations (rather than simply the most recent photos).
Below is the relevant (working) code I have so far:
Node server-side code to handle new photo updates from Instagram API and emit event to Angular controller:
// for each new post Instagram sends the data
app.post('/callback', function(req, res) {
var data = req.body;
// grab the object_id (as geo_id) of the subscription and send as an argument to the client side
data.forEach(function(data) {
var geo_id = data.object_id;
sendUpdate(geo_id);
});
res.end();
});
// send the url with the geo_id to the client side
// to do the ajax call
function sendUpdate(geo_id) {
io.sockets.emit('newImage', { geo_id: geo_id });
}
Angular controller code when 'newImage' event is received:
socket.on('newImage', function(geo_id) {
// pass geo_id into Instagram API call
Instagram.get(geo_id).success(function(response) {
instagramSuccess(response.geo_id, response);
});
// Instagram API callback
var instagramSuccess = function(scope,res) {
if (res.meta.code !== 200) {
scope.error = res.meta.error_type + ' | ' + res.meta.error_message;
return;
}
if (res.data.length > 0) {
$scope.items = res.data;
} else {
scope.error = "This location has returned no results";
}
};
});
Angular factory to handle calls to Instagram API:
angular.module('InstaFactory', []).factory('Instagram', function($http) {
var base = "https://api.instagram.com/v1";
var client_id = 'MY-CLIENT-ID';
return {
'get': function(geo_id) {
var request = '/geographies/' + geo_id.geo_id + '/media/recent?client_id=' + client_id;
var url = base + request;
var config = {
'params': {
'callback': 'JSON_CALLBACK'
}
};
return $http.jsonp(url, config);
}
};
});
I also have the following Angular Controller which currently GETS details of each location from my Stadia mongoDB model. This model also contains an (empty for now) 'photos' array that I want to PUSH photo details (url, username, user profile url, etc.) onto each time I receive them from Instagram:
angular.module('StadiaFactory', []).factory('Stadia', function($http) {
var base = "http://localhost:6060/api/stadia/";
return {
'get': function(id) {
var request = id;
var url = base + request;
var config = {
'params': {
'callback': 'JSON_CALLBACK'
}
};
return $http.jsonp(url, config);
}
};
});
This is where I get confused. Where do I fire off the PUT request to my Stadia API and does this Node route for my Stadia API look reasonable? Note: I omitted my GET route which works perfectly. PUT is just throwing me for a loop:
// add photos to stadium photos array
app.put('/api/stadia/:stadium_id', function(req, res) {
// use mongoose to get and update stadium
Stadium.findByIdAndUpdate(req.params.stadium_id,
{$push: {"photos": {img: ?, link: ?, username: ?, profile_picture: ?}}},
{safe: true, upsert: true},
function(err, stadium) {
// if there is an error retrieving, send the error. nothing after res.send(err) will execute
if (err)
res.send(err)
res.jsonp(stadium); // return stadium in JSON format
});
});

Well there are a few problems with your current structure.
When your callback route is called, with a possibility of N objects in it, you're triggering your socket event and retrieving all the last photos of your geography each time. So let's say you will have 3 new objects, you will call 3 times the same thing to get the same data, which is a bit loss when you have the power of the sockets.
You can also have problems if you try to get the object data from the client-side and PUTing it to your server, since all your clients may receive the socket and you could end-up with duplicates, not to mention that this is a lot of traffic for not much, and this will burn your quota API limit, which is also not safe on the client-side since everyone can see your key.
To me, a good way to get something working (even if I don't really know what your :stadium_id param stands for) is to get the info you want directly on the server side in your callback using the request module.
You should only get the pictures, because you can retrieve a lot of things like users, tags or videos that you may don't want to get. So you will have to listen for the image objects, and nothing else.
You could have something like this:
var request = require('request');
var CLIENT_ID = 'yourId';
function newImage(data) {
io.sockets.emit('newImage', data);
}
app.post('/callback', function (req, res) {
//loop in all the new objects
req.body.forEach(function (data) {
if (data.type !== 'image') { return ; }
//BTW I think you should try with the id property instead of object_id
request('https://api.instagram.com/v1/media/' + data.object_id + '?access_token=' + CLIENT_ID,
function (error, response, body) {
if (error) { return ; }
//Here we have one JSON object with all the info about the image
var image = JSON.parse(body);
//Save the new object to your DB. (replace the STADIUM_ID)
Stadium.findByIdAndUpdate(STADIUM_ID, { $push: {'photos':
{ img: image.images.standard_resolution.url,
link: image.link,
username: image.user.username,
profile_picture: image.user.profile_picture
}}},
{ safe: true, upsert: true });
//Send a socket to your client with the new image
newImage({
id: image.id,
img: image.images.standard_resolution.url,
link: image.link,
username: image.user.username,
profile: image.user.profile_picture
});
}
});
res.end();
});
And then in your client, you will only have to push the new images received in the newImage socket event in the $scope.items.

Related

Building an array with an asynchronous function then passing the array into a GET method in Node JS

I'm using the Twitter node package for a GET request to retrieve a bunch of tweets. I put all the info I want into an object call 'element' and push into an array call 'ary'. I plan on using this array to dynamically build an ordered list. How would I pass this ary into a webpage so that it would build a new 'ary' every time it loads the webpage and so the server would wait until it gets back the 'ary' before it renders the page. Here's my newbie attempt with little understanding of how to use asynchronous function below. Btw "client" is just a new Twitter() object.
let buildAry = function() {
let ary = [];
client.get('search/tweets', {q: '#CSC365'})
.then(function (tweet) {
tweet.statuses.forEach(function(item) {
let element = {
name : item.user.name,
text : item.text,
pic : item.user.profile_image_url
};
ary.push(element);
});
})
.catch(function (error) {
throw error;
})
return ary;
}
app.get('/profile', function(req, res) {
res.render('index.pug', buildAry());
});
You can't return ary, because at the time return ary is called, the search/tweet request (which is run asychronously) has not yet received a response.
You need to
1/ Pass the response object to buildAry:
app.get('/profile', function(req, res) {
buildAry(res);
});
2/ Accept the response object as a parameter of buildAry
let buildAry = function(res) {
...
3/ Send ary to the client in the success callback of the search/tweets get request
client.get('search/tweets', {q: '#CSC365'})
.then(function (tweet) {
tweet.statuses.forEach(function(item) {
let element = {
name : item.user.name,
text : item.text,
pic : item.user.profile_image_url
};
ary.push(element);
});
res.send(ary); // send ary in the HTTP response
})
You could also rename buildAry to sendAry, since it's what it does now.

Redirect client while performing a function with Express

Right now, I have an Express route that is posted to by a form, below is a truncated example. The initial form is inside an iframe, so after I receive a response from http://example.com/endpoint, I send a response back to the iframe with a link going to a "signing" page, targeting the parent frame.
Unfortunately, the response from http://example.com/endpoint can take pretty long, which causes the iframe to timeout and never receive a response. What I'd like to do is send some type of response back to the iframe immediately and redirect the page to some sort of "loading page" – this would be shown while the router waits for a response from http://example.com/endpoint.
I'm using Express to serve the page that contains the iframe to the user right now – all the views are controlled on the server side.
I'm wondering if there's any resources somebody could point me towards, or something to nudge me in the right direction.
router.post('/api/orders', function(req, res) {
var order = {
'model': req.body.model,
'options': optionsArray
}
request.post({
url: 'http://example.com/endpoint,
body: order,
json: true
}, function(err, response, body) {
if (!error && response.statusCode === 200) {
if (!body.isCustom) {
hellosign.embedded.getSignUrl(body.signatureId)
.then(function(response) {
var signatureUrl = response.embedded.sign_url;
var resSignatureUrl = encodeURIComponent(signatureUrl);
res.send('Click to sign');
})
.catch(function(err) {
console.log(err);
})
} else {
res.send('You selected custom options.');
}
}
if (error || response.statusCode === 403) {
res.json({
message: 'something went wrong with your order',
errorCode: response.statusCode,
errorMessage: body.message
});
}
});
});
I would put the hellosign/whatever long running API call in its own module. Test that separately to make sure its working.
Then your iframe or whatever (do you really need an iframe?) sends the request which is just a 'start order' request which gets an order id from the hellosign module. And then use a setInterval or something to check a new endpoint which is an 'orderstatus' endpoint.
Your orderstatus endpoint accesses your new hellosign module to check the status of the order.
So it could be something like:
post('/start', function(req,res) {
var id = hellosign.startorder(req.body.model);
res.send(id);
});
get('/status', function(req,res) {
res.send(hellosign.checkstatus(req.body.id));
}
// hellosign.js
var status = {};
exports.startorder = function(model) {
var id = uuid.v4(); // some unique id;
status[id] = 'started';
request.post(api, /* ... */ ).then(function(signurl) { status[id] = signurl });
return id;
}
exports.checkstatus = function(id) {
return status[id];
}

Sails + Braintree : Unable to send the transactions details to client

I'm developing an application using Sails JS and Braintree. I'm trying to send the all past transaction details that the customer has made.
Here is my getTransaction action
getTransaction: function(req, res) {
var customerId = req.param('customerId');
var gateway = setUpGateway();
var stream = gateway.transaction.search(function(search) {
search.customerId().is(customerId);
}, function(err, response) {
if (err) {
return res.serverError(err);
}
res.send(response);
});
},
But the problem is, if I directly send the response which I got from braintree server, it throws the circular dependency error. So, to overcome that error I'm fetching only those details that I need from response like this
getTransaction: function(req, res) {
var customerId = req.param('customerId');
var gateway = setUpGateway();
var stream = gateway.transaction.search(function(search) {
search.customerId().is(customerId);
}, function(err, response) {
if (err) {
return res.serverError(err);
}
var transactions = [];
response.each(function(err, transaction) {
var temp = [];
temp.push(transaction.id);
temp.push(transaction.amount);
temp.push(transaction.createdAt);
transactions.push(temp);
});
res.send(transactions);
});
},
But here the .each function is getting executed asynchronously and hence res.send returns the empty array. So what should I do to return all the transaction that the user has made?
Full disclosure: I work at Braintree. If you have any further questions, feel free to contact our support team.
You are correct that the iterator executes asynchronously. You should use Node's stream semantics to process the request
getTransaction: function(req, res) {
var customerId = req.param('customerId');
var gateway = setUpGateway();
var transactions = [];
var stream = gateway.transaction.search(function(search) {
search.customerId().is(customerId);
});
stream.on('data', function (transaction) {
transactions.push(transaction);
});
stream.on('end', function () {
res.send(transactions);
});
},
This will wait until all transactions have been processed before sending the result.
This page provides more information about searching using our Node client library and Node's Stream API.

Node JS, make HTTPS request synchronously from two links

I want to make a HTTPS request to an external link through Node JS. On my first call, I need to fetch user id by looping through several users. On my second call, I need to input that user id in the URL link and fetch user properties. Keep repeating this process till I go through all users. The end goal is to store data of every user in a JSON format. There is no front-end involved. Any direction/advice is much appreciated.
I can't share the actual link due to api keys. But here is the hypothetical scenario. I only show 2 users here. I have about 10,000 users in my actual data set.
Link 1
https://www.google.com/all_users
JSON Output
{
"name": "joe",
"uri": "/id/UserObject/User/1234-1234",
},
{
"name": "matt",
"uri": "/id/UserObject/User/5678-5678",
}
Link 2
https://www.google.com//id/UserObject/User/1234-1234
JSON Output
{
"name": "joe",
"uri": "/id/UserObject/User/1234-1234",
"Property Values": {
"height": "2",
"location": "canada"
},
"Other Values": {
"work": "google",
"occupation": "developer"
}
}
Nested JSON
{
"PropertySetClassChildrenResponse": {
"PropertySetClassChildren": {
"PropertySetInstances": {
"totalCount": "1",
"Elements": [
{
"name": "SystemObject",
"uri": "/type/PropertySetClasses/SystemObject"
}
]
}
}
}
}
Not tested, but this should point you in the right direction. It uses Promises and assumes that run in an ES6 environment:
const rp = require('request-promise');
const Promise = require('bluebird');
fetchAllUsers()
.then(extractUserUris)
.then(extractUserIds)
.then(buildUserDetailRequests)
.then(Promise.all) // run all the user detail requests in parallel
.then(allUserData => {
// allUserData is an array of all users' data
});
function fetchAllUsers() {
return rp('https://api.whatever.com/all_users');
}
function extractUserUris(users) {
return users.map(user => user.uri);
}
function extractUserIds(userUris) {
return userUris.map(userUri => userUri.split('/').pop());
}
function buildUserDetailRequests(userIds) {
return userIds.map(userId => rp("https://api.whatever.com/user/" + userId));
}
I'd suggest using the request package to make your HTTP requests easier.
> npm install request
Then you would obtain a list of all users with something like this:
var request = require('request');
request.get({url: "https://example.org/all_users"}, handleUsersResponse);
You'd handle the request response like this:
function(err, response, body) {
if (!err && response.statusCode == 200) {
// parse json (assuming array of users)
var users = JSON.parse(body);
// iterate through each user and obtain user info
for(var i = 0; i < users.length; i++) {
var userUri = users[i].uri;
obtainUserInfo(userUri)
}
}
}
obtainUserInfo function would be similar to the above code.
One important thing to keep in mind is that since the HTTP requests are being made asynchronously, when you make the requests in a loop, the next iteration of the loop does not wait until the work is finished before moving to the next iteration and starting the next request. So in effect, your loop would start all the HTTP requests nearly in parallel. This can easily overwhelm both your client and the server. One way to get around this is to use a worker queue to enqueue the work and ensure that only a maximum number of HTTP requests are being executed at any given time.
You don't want to do synchronous calls, it defeats the purpose of using Node. So by the Node powers invested in me by the State of Texas I hereby cast that synchronous way I thinking out of you!
Just kidding :), but let's do this the Node way.
Install these two libraries:
sudo npm install Promise
sudo npm install request
And set your code to look like:
var Promise = require('promise');
var request = require('request');
//Get your user data, and print the data in JSON:
getUserData()
.then(function(userData) {
console.log(JSON.stringify(userData));
}).catch(function(err) {
console.log('Error: ' +err);
});
/**
* Prepares an Object containing data for all users.
* #return Promise - Contains object with all user data.
*/
function getUserData() {
return new Promise(function(fulfill, reject) {
// Make the first request to get the user IDs:
var url1 = 'https://www.google.com/all_users';
get(url1)
.then(function(res) {
res = JSON.parse(res);
// Loop through the object to get what you need:
// Set a counter though so we know once we are done.
var counter = 0;
for (x=0; x<res.users.length; x++) {
var url2 = 'https://www.google.com//id/UserObject/User/';
url2 = url2 + res.users.id; //Wherever the individual ID is stored.
var returnDataArr = [];
get(url2)
.then(function(res2) {
// Get what you need from the response from the 2nd URL.
returnDataArr.push(res2);
counter++;
if (counter === res.users.length) {
fulfill({data: returnDataArr}); //Return/Fulfill an object containing an array of the user data.
}
}).catch(function(err) {
// Catch any errors from the 2nd HTTPS request:
reject('Error: ' +err);
});
}).catch(function(err) {
// Catch any errors from the 1st HTTPS request:
reject('Error: ' +err);
});
}
/**
* Your HTTPS GET Request Function
* #param url - The url to GET
* #return Promise - Promise containing the JSON response.
*/
function get(url) {
return new Promise(function(fulfill, reject) {
var options = {
url: url,
headers: {
'Header Name': 'Header Value',
'Accept': 'application/json',
'Content-Type': 'application/json'
};
request(options, function(err, res, body) {
if (err) {
reject(err);
} else {
fulfill(body);
}
});
});
}
So what this Promise does, is that it returns the value once we actually have it. In the code above, we are first getting that list of users, and then as we parse through it, we are making a new asynchronous HTTP request to get the additional data on it. Once we get the user data, we push it to an array.
Finally, once our counter hits its endpoint, we know that we have gotten all the user data, and so we call fulfill which essentially means return, and it returns an object containing an array of the user data.
Let me know if this makes sense.
The answers above helped me go further with my solution and get the desired outcome. However, I spent a lot of time trying to understand node, promises in node, making an API call, etc. Hopefully, this will help to a beginner level node developer.
NODE
Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world.
If you are a JavaScript developer, you would prefer to use Node as you wouldn't have to spend time learning a new language like Java or Python.
GOAL
Make a HTTPS call to an external link to fetch all server URIs. Pass in the URI as a param to create a second link to fetch all server properties. Loop through to all server uris and properties. Refer the original post on the top for the data structure. The external link also required basic auth and headers.
CODE
Install NPM modules request (https call), bluebird (promises) and lodash(utility) and express(node framework).
/
********************** MODULES/DEPENDENCIES **********************/
var express = require('express');
var request = require('request');
var Promise = require('bluebird');
var _ = require("lodash");
/********************** INITIATE APP **********************/
var app = express();
console.log("Starting node server...");
/**
* Your HTTPS GET Request Function
* #param url - The url to GET
* #return Promise - Promise containing the JSON response.
*/
function get(url) {
return new Promise(function(resolve, reject) {
// var auth = "Basic " + new Buffer(username + ':' + password).toString("base64");
var options = {
url: url,
headers: {
// 'Authorization': auth,
'Content-Type': 'application/json',
'Accept': 'application/json'
}
};
console.log("Calling GET: ", url);
if ('development' == app.get('env')) {
console.log("Rejecting node tls");
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
}
request(options, function(error, response, body) {
if (error) {
reject(error);
} else {
// console.log("THIS IS BODY: ", body);
resolve(body);
}
});
});
}
/********************** GET DATA FUNCTION **********************/
function getServerData() {
/********************** URI VARIABLES **********************/
var username = 'username',
password = 'password',
role = 'Read-Only',
url_host = 'https://link.com:10843';
/********************** URL 1 **********************/
var url1 = url_host + '/type/PropertySetClasses/SystemObject/Server/?maxResults=1000&username=' + username + '&password=' + password + '&role=' + role;
console.log("Getting server data...", url1);
/********************** GET REQUEST 1 **********************/
return get(url1)
.then(function(res) {
console.log("Got response!");
res = JSON.parse(res);
res = res.PropertySetClassChildrenResponse.PropertySetClassChildren.PropertySetInstances.Elements;
// console.log("THIS IS RES: ", res);
/********************** FETCH URI FROM RES NESTED OBJECT **********************/
var server_ids = _.map(res, function(server) {
return server.uri;
});
console.log("Calling server urls", server_ids);
// Loop through the object to get what you need:
// Set a counter though so we know once we are done.
return Promise.map(server_ids, function (id) {
var url2 = url_host + id + '?username=' + username + '&password=' + password + '&role=' + role;
console.log("Calling URL", url2);
return get(url2)
.then(function(res2) {
res2 = JSON.parse(res2);
var elements = res2.PropertySetInstanceResponse.PropertySetInstance.PropertyValues.Elements;
console.log("Got second response", res2, elements);
return elements;
});
})
.then(function (allUrls) {
console.log("Got all URLS", allUrls);
return allUrls;
});
})
.catch(function(err) {
console.error(err);
throw err;
});
};
app.listen(8080, function() {
console.log("Server listening and booted on: " + 8080);
app.get("/serverInfo", function (req, res) {
console.log("Calling server info");
return getServerData()
.then(function(userData) {
var userData = JSON.stringify(userData, null, "\t");
console.log("This is USERDATA Data: ", userData);
res.send(userData);
})
.catch(function(err) {
console.error(err);
res.send({
__error: err,
message: err.message
});
});
});
});

400 Error when Cloud Code from Twilio

We've configured our Twilio number to post to the following parse url but it's returning a 400 error: https://myAppId:javascript-key=myJSkey#api.parse.com/1/functions/sendMsgFromTwilio
The parse api says we need a content type header. Does anyone know what the problem might be?
Here's our code:
Parse.Cloud.define("sendMsgFromTwilio", function (req, res) {
//use From phone number param to get client object
ParseUtils.getUserAccount(Crypto.hash(req.params.From)).then(function(result) {
//get providerId from ProviderClient table
var clientId = result.id;
ParseUtils.getProviderClient(clientId).then(function(result) {
var providerId = result.providerId;
//make sure we're actually passing on a message
if(req.params.Body.length > 0) {
var messageType = 0; //text message type
//prepare parameters in hash as done in sendMsg function
var params = {
params : {
providerId: providerId,
clientId: clientId,
payload: {
type: messageType,
content: req.params.Body
}
}
}
//pass message to helper function to send to pubnub
Messaging.sendMsgAsIs(params).then(function (result) {
res.success(result);
}, function (error) {
res.error(error);
});
}
})
});
});
Thanks in advance.
The correct way to implement this is not as a cloud function, but as a custom endpoint. On the hosted Parse solution, this is documented here: https://parse.com/docs/cloudcode/guide#hosting-dynamic-websites
Now with Parse Server, it would be just a standard express route alongside your server. Find out more about Parse Server here: http://blog.parse.com/announcements/what-is-parse-server/

Categories

Resources