Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
logs
*.log
npm-debug.log*

*DS_Store
*.DS_Store
package-lock.json
# Runtime data
pids
*.pid
Expand Down Expand Up @@ -33,3 +35,4 @@ node_modules
.node_repl_history

.idea
.DS_Store
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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
Expand Down
13 changes: 13 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -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"
}
}
171 changes: 171 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -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<ppl.length; k++){
res_data= ppl[k].residents;
for(j=0; j<res_data.length; j++){
let pplObj= _.find(people, ['url', res_data[j]]);
if(pplObj && pplObj!= null && pplObj != undefined){
res_data[j] = pplObj.name;
}
}
}

return ppl;
});

planets=_.concat(planets, data.results);
}
res.send(planets);
// });
});

const onError = error => {
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;
20 changes: 20 additions & 0 deletions src/util/logger.js
Original file line number Diff line number Diff line change
@@ -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,
},
],
});
62 changes: 62 additions & 0 deletions src/util/util.js
Original file line number Diff line number Diff line change
@@ -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
};