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..011c535 --- /dev/null +++ b/src/app.js @@ -0,0 +1,171 @@ +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; + 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']); + +//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); + +}); + +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++; + } + // replacing resident urls with actual name for each elements + 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": + logger.error(bind + " requires elevated privileges"); + process.exit(1); + break; + case "EADDRINUSE": + logger.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; + logger.info("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){ + logger.info("Exception occured in getPeopleData "+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) { + 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]); + }); + }; +})(); +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