From d0e7daa697f27bd79f1a008631d50f8efe8f2df9 Mon Sep 17 00:00:00 2001 From: ashish-dehariya Date: Thu, 11 Oct 2018 00:11:33 -0600 Subject: [PATCH 1/2] updated code --- .gitignore | 5 +- README.md | 5 ++ package.json | 13 ++++ src/app.js | 148 +++++++++++++++++++++++++++++++++++++++++++++ src/util/logger.js | 20 ++++++ src/util/util.js | 62 +++++++++++++++++++ 6 files changed, 252 insertions(+), 1 deletion(-) create mode 100644 package.json create mode 100644 src/app.js create mode 100644 src/util/logger.js create mode 100644 src/util/util.js diff --git a/.gitignore b/.gitignore index 72cf9a5..7e956b8 100644 --- a/.gitignore +++ b/.gitignore @@ -2,7 +2,9 @@ logs *.log npm-debug.log* - +*DS_Store +*.DS_Store +package-lock.json # Runtime data pids *.pid @@ -33,3 +35,4 @@ node_modules .node_repl_history .idea +.DS_Store \ No newline at end of file diff --git a/README.md b/README.md index 8cc609d..884b527 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,11 @@ # node-exercise A little exercise using a Star Wars API [https://swapi.co/](https://swapi.co/) +# How to run this application - +``` +npm install +node src/app.js +``` ## Goal We want to know that you can: * Consume and manipulate API data diff --git a/package.json b/package.json new file mode 100644 index 0000000..f8e28ac --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "starwars-code-exercise", + "version": "1.0.0", + "description": "refer Read Me", + "main": "src/app.js", + "dependencies": { + "bunyan": "^1.8.12", + "bunyan-prettystream": "^0.1.3", + "express": "^4.16.3", + "lodash": "^4.17.11", + "request": "^2.88.0" + } +} diff --git a/src/app.js b/src/app.js new file mode 100644 index 0000000..c3a9cd1 --- /dev/null +++ b/src/app.js @@ -0,0 +1,148 @@ +const express = require('express'); +const app = express(); +const http = require("http"); +const https = require('https'); +let logger = require('./util/logger'); +let util = require('./util/util'); +const _ = require("lodash"); +const peopleApiUrl = 'https://swapi.co/api/people/?page=1'; +const planetsApiUrl = 'https://swapi.co/api/planets/?page=1'; + +// please run node src/app.js to run this application +app.get('/', (req, res) => res.send('Welcome to the world of star wars')); + +app.get('/people', async (req, res, next) => { + let sortBy = req.query.sortBy || null; + let sortOptions = ['name', 'height', 'mass']; + + let result; + console.log(sortBy); + try { + result = await getPeopleData(peopleApiUrl); + } catch (error) { + return next(error); + } + // Sort people array by Name, Height or Mass - implemented a sortBy function however same can be acheieved using lodash.sortBy() + // lodash.sortBy would be good if sorting needs to be done based on more than one element ex. _.sortBy(people,['name','height','mass']) + //let contactArray = _.sortBy(people,['name']); + console.log(sortBy); + let contactArray = result.sortBy(sortBy); + let jsonRes= JSON.stringify(contactArray); + return res.send(jsonRes); + +}); + +app.get('/planets', async function (req, res) { + + let people = await getPeopleData(peopleApiUrl); + let planets = []; + let page = 1; + let next = planetsApiUrl; + let map; + for (let i = 0; i < page; i++) { + let data = await util.makeRequest(next); + if (data.next) { + next = data.next; + page++; + } + + var ppl_data = []; + ppl_data.push(data.results); + map = _.map(ppl_data,function(ppl){ + let res_data =[]; + for (k=0; k { + if (error.syscall !== "listen") { + throw error; + } + const bind = typeof addr === "string" ? "pipe " + addr : "port " + port; + switch (error.code) { + case "EACCES": + console.error(bind + " requires elevated privileges"); + process.exit(1); + break; + case "EADDRINUSE": + console.error(bind + " is already in use"); + process.exit(1); + break; + default: + throw error; + } +}; +const onListening = () => { + const addr = server.address(); + const bind = typeof addr === "string" ? "pipe " + addr : "port " + port; + console.log("Listening on " + bind); +}; + + +const port = util.normalizePort(process.env.PORT || "3001"); +app.set("port", port); +const server = http.createServer(app); +server.on("error", onError); +server.on("listening", onListening); +server.listen(port); + +async function getPeopleData(next) { + let people = []; + let page = 1; + try { + for (let i = 0; i < page; i++) { + let res = await util.makeRequest(next); + // if next pageurl, increment page number that will be used for next iterative web service call + if (res.next) { + next = res.next; + page++; + } + people = people.concat(res.results); + } + + }catch(ex){ + console.log(ex); + } +return people; +} + +Array.prototype.sortBy = (function() { + var sorters = { + string: function(a, b) { + if (a < b) { + return -1; + } else if (a > b) { + return 1; + } else { + return 0; + } + }, + + number: function(a, b) { + return a - b; + } + }; + + return function(prop) { + var type = typeof this[0][prop] || 'string'; + return this.sort(function(a, b) { + return sorters[type](a[prop], b[prop]); + }); + }; +})(); +module.exports = app; \ No newline at end of file diff --git a/src/util/logger.js b/src/util/logger.js new file mode 100644 index 0000000..6f96c0a --- /dev/null +++ b/src/util/logger.js @@ -0,0 +1,20 @@ +const bunyan = require('bunyan'); +const PrettyStream = require('bunyan-prettystream'); + +const prettyStdOut = new PrettyStream(); +prettyStdOut.pipe(process.stdout); + +module.exports = bunyan.createLogger({ + name: 'logs', + src: true, + streams: [ + { + period: '1w', + count: 3, + // log debug and above to stdout + level: 'debug', + type: 'raw', + stream: prettyStdOut, + }, + ], +}); \ No newline at end of file diff --git a/src/util/util.js b/src/util/util.js new file mode 100644 index 0000000..cc0efdf --- /dev/null +++ b/src/util/util.js @@ -0,0 +1,62 @@ +let request = require('request'); +let logger = require('./logger'); +async function makeRequest(url) { + let options = { + method: 'GET', + uri: url, + json: true, + }; + + logger.info('**** API OPTIONS: ', options); + + return new Promise(function(resolve, reject) { + return apiCall(options) + .then(function(response) { + resolve(response); + }) + .catch(function(err) { + reject(err); + }); + }); +}; + +let apiCall = function(options) { + return new Promise(function(resolve, reject) { + request(options, function(error, response, body) { + if (error) { + logger.info('API Call failed... API details: ', options, error); + reject(error); + } else { + if (response && (response.statusCode == 200)) { + resolve(body); + } else { + reject(body, response); + } + } + }); + }); +}; + +const normalizePort = val => { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +}; + + + +module.exports = { + apiCall, + makeRequest, + normalizePort +}; \ No newline at end of file From 00ca6749f3e7e52a44a166fd573931c12875a533 Mon Sep 17 00:00:00 2001 From: ashish-dehariya Date: Thu, 11 Oct 2018 16:07:55 -0600 Subject: [PATCH 2/2] updated code for sorting --- src/app.js | 55 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 16 deletions(-) diff --git a/src/app.js b/src/app.js index c3a9cd1..011c535 100644 --- a/src/app.js +++ b/src/app.js @@ -16,19 +16,34 @@ app.get('/people', async (req, res, next) => { let sortOptions = ['name', 'height', 'mass']; let result; - console.log(sortBy); try { result = await getPeopleData(peopleApiUrl); } catch (error) { return next(error); } + +// convert types of height and mass from string to integer for sort them in correct way. + + var updatedResult = _.map(result,function(convert){ + + for(var key in convert){ + if(key === 'mass' || key === 'height'){ + const newVal = parseInt(convert[key]); + delete convert[key]; + convert[key] = newVal; + + } +} +return convert; +}); + // Sort people array by Name, Height or Mass - implemented a sortBy function however same can be acheieved using lodash.sortBy() // lodash.sortBy would be good if sorting needs to be done based on more than one element ex. _.sortBy(people,['name','height','mass']) //let contactArray = _.sortBy(people,['name']); - console.log(sortBy); - let contactArray = result.sortBy(sortBy); - let jsonRes= JSON.stringify(contactArray); - return res.send(jsonRes); + +//let contactArray = (sortBy && sortOptions.indexOf(sortBy) !== -1) ? updatedResult.sortBy(sortBy) :result +let contactArray = (sortBy && sortOptions.indexOf(sortBy) !== -1) ? _.sortBy(updatedResult,[sortBy]) :result +return res.send(contactArray); }); @@ -45,11 +60,11 @@ app.get('/planets', async function (req, res) { next = data.next; page++; } - + // replacing resident urls with actual name for each elements var ppl_data = []; - ppl_data.push(data.results); + ppl_data.push(data.results); map = _.map(ppl_data,function(ppl){ - let res_data =[]; + let res_data; for (k=0; k { const bind = typeof addr === "string" ? "pipe " + addr : "port " + port; switch (error.code) { case "EACCES": - console.error(bind + " requires elevated privileges"); + logger.error(bind + " requires elevated privileges"); process.exit(1); break; case "EADDRINUSE": - console.error(bind + " is already in use"); + logger.error(bind + " is already in use"); process.exit(1); break; default: @@ -90,7 +105,7 @@ const onError = error => { const onListening = () => { const addr = server.address(); const bind = typeof addr === "string" ? "pipe " + addr : "port " + port; - console.log("Listening on " + bind); + logger.info("Listening on " + bind); }; @@ -116,7 +131,7 @@ async function getPeopleData(next) { } }catch(ex){ - console.log(ex); + logger.info("Exception occured in getPeopleData "+ex); } return people; } @@ -134,14 +149,22 @@ Array.prototype.sortBy = (function() { }, number: function(a, b) { - return a - b; + if (a === null && b != null){ + return -1; + }else if (b === null && a!= null ) { + return 1 ; + } else if (b === null && a === null ) { + return 1; + }else { + return a - b; + } } }; return function(prop) { var type = typeof this[0][prop] || 'string'; return this.sort(function(a, b) { - return sorters[type](a[prop], b[prop]); + return sorters[type](a[prop], b[prop]); }); }; })();