-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi.js
More file actions
340 lines (263 loc) · 9.29 KB
/
api.js
File metadata and controls
340 lines (263 loc) · 9.29 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
'use strict';
var express = require('express');
var request = require('request');
var fs = require("fs");
var app = express();
const RELEASE_API_ENDPOINT = "https://www.energy.gov/sites/prod/files/2020/12/f81/code-12-15-2020.json";
const LOCAL_DATA_FILE = __dirname + "/sampledata.json";
//CloudFront is blocking the request. The data will have to be read from a file using the 'loadReleaseDataFile()' function.
function loadReleaseData() {
return new Promise(function (resolve, reject) {
console.log("Loading Release Data");
//Request the release data from the API.
request({
url: RELEASE_API_ENDPOINT, json: true
}, function (error, response, body) {
//Check for an error.
if (error) {
let err = "loadReleaseData: " + JSON.stringify(error);
console.log(err);
return reject(err);
}
//Return the JSON data.
if (body.releases) {
let err = "loadReleaseData: Unable to load releases.";
console.log(err);
return reject(err);
}
return resolve(body.releases);
});
});
}
//Loads the data API data from file since CloudFront is blocking calls.
function loadReleaseDataFile() {
return new Promise(function (resolve, reject) {
fs.readFile(LOCAL_DATA_FILE, 'utf8', function (error, data) {
//Check for an error.
if (error) {
let err = "loadReleaseDataFile: " + JSON.stringify(error);
console.log(err);
return reject(err);
}
//Return the JSON data.
let body = JSON.parse(data);
if (!body.releases) {
let err = "loadReleaseDataFile: Unable to load releases.";
console.log(err);
return reject(err);
}
return resolve(body.releases);
});
});
}
//This method combines the data for each organization.
function aggregateData(releaseData) {
//Make sure we have data.
if (!releaseData || !Array.isArray(releaseData)) {
const err = "aggregateData: Invalid release data.";
console.log(err);
return Promise.reject(err);
}
//Loop through the releases.
let orgData = [];
releaseData.forEach(release => {
const orgIdx = orgData.findIndex(o=> o.organization === release.organization);
if(orgIdx < 0) {
//Create the organization record and add it to the data array.
orgData.push(createOrMergeOrganization(release))
}
else
{
//Merge the existing organization.
orgData[orgIdx] = createOrMergeOrganization(release, orgData[orgIdx]);
}
});
//return the aggregated data.
return Promise.resolve(orgData);
}
//This method combines the data for a single organization.
function createOrMergeOrganization(releaseOrg, mergeWith) {
//Get the licenses for the current item.
let licenses = [];
releaseOrg.permissions.licenses.forEach(license => {
licenses.push(license.name);
});
//Find the created month.
let createdMonth = parseInt(releaseOrg.date.created.split('-')[1]);
//Create our new item.
let newOrg = {
organization: releaseOrg.organization,
release_count: 1,
total_labor_hours: releaseOrg.laborHours,
all_in_production: (releaseOrg.status === "Production"),
licenses: licenses,
most_active_months: [createdMonth]
};
//Merge it with an existing if supplied.
if(mergeWith) {
mergeWith.release_count++;
mergeWith.total_labor_hours += newOrg.total_labor_hours;
mergeWith.licenses = [...new Set(mergeWith.licenses.concat(newOrg.licenses))].sort();
mergeWith.most_active_months = mergeWith.most_active_months.concat(newOrg.most_active_months).sort();
if(releaseOrg.status !== "Production") {
mergeWith.all_in_production = false;
}
return mergeWith;
}
return newOrg;
}
//This method calculates the most active months.
//Only months with the highest number of releases are shown.
function calcMostActiveMonths(orgData) {
//Make sure we have data.
if (!orgData || !Array.isArray(orgData)) {
const err = "calcActiveMonths: Invalid org data.";
console.log(err);
return Promise.reject(err);
}
//Loop the organizations.
orgData.forEach(org => {
let monthCount = [];
let highestCount = 1;
//Loop the months.
org.most_active_months.forEach(month => {
if(monthCount[month]) {
monthCount[month]++;
//If this is a new high, update the highestCount.
if(monthCount[month] > highestCount){
highestCount = monthCount[month];
}
}
else
{
monthCount[month] = 1;
}
});
//Loop the counts.
let returnList = [];
for (let i = 0; i < 12; i++) {
let count = monthCount[i];
//If the count for this month is amongst the highest, add it to the return object.
if(count === highestCount) {
returnList.push(i);
}
}
//Set the org with our new list.
org.most_active_months = returnList;
});
return Promise.resolve(orgData);
}
//This method is used to sort the data.
function sortResults(orgData, field, order){
//Make sure we have data.
if (!orgData || !Array.isArray(orgData)) {
const err = "calcActiveMonths: Invalid org data.";
console.log(err);
return Promise.reject(err);
}
let sorted = orgData;
//Sort the data by "release_count".
if(field === "release_count") {
sorted = orgData.sort((a, b) => a.release_count - b.release_count);
}
//Sort the data by "total_labor_hours".
else if(field === "total_labor_hours") {
sorted = orgData.sort((a, b) => a.total_labor_hours - b.total_labor_hours);
}
//Sort the data by "organization".
else
{
sorted = orgData.sort((a, b) => a.organization.localeCompare(b.organization));
}
if(order === "desc"){
sorted.reverse();
}
return Promise.resolve(sorted);
}
//This method will get data from the API, aggregate, and sort it.
function getAPIData(sortField, sortDir) {
//Load the date from file. (API endpoint is not accessable because of CloudFront rules)
//loadReleaseData()
return loadReleaseDataFile()
//Aggregate the data.
.then(data => aggregateData(data))
//Calculate the most active months.
.then(aData => calcMostActiveMonths(aData))
//Sort the data.
.then(aData => sortResults(aData, sortField, sortDir));
}
//JSON Route
app.get('/organizations', function (req, res) {
let sortField = req.query.sort;
let sortOrder = req.query.order;
//Return the final data.
getAPIData(sortField, sortOrder)
.then(aData => {
let resp = { organizations: [] };
//Add the data to our response.
aData.forEach(org => {
resp.organizations.push(org);
})
//Return the JSON response.
res.type('application/json');
res.end(JSON.stringify(resp, null, 2));
}).catch(err => {
console.log(err);
res.end(JSON.stringify(err));
});
});
//CSV Route
app.get('/organizations.csv', function (req, res) {
let sortField = req.query.sort;
let sortOrder = req.query.order;
//Return the final data.
getAPIData(sortField, sortOrder)
.then(aData => {
//CSV Value Replacer.
const replacer = (key, value) => {
if(value === null) {
return '';
}
if(Array.isArray(value)) {
return value.join('|');
}
return value;
}
//Convert to CSV.
let header = Object.keys(aData[0]);
let csvData = header.join(',');
csvData += "\r\n";
aData.forEach(org => {
//Map the fields to the header.
csvData += header.map(field => JSON.stringify(org[field], replacer)).join(',');
//Add a new line.
csvData += "\r\n";
});
//Return the CSV response.
res.type('csv');
res.end(csvData);
}).catch(err => {
console.log(err);
res.end(JSON.stringify(err));
});
});
//Default routes for the webserver.
app.get('/', function (req, res) {
fs.readFile(__dirname + "/welcome.html", 'utf8', function (error, data) {
res.type('html');
res.end(data);
});
});
app.get('/style.css', function (req, res) {
fs.readFile(__dirname + "/style.css", 'utf8', function (error, data) {
res.type('css');
res.end(data);
});
});
//Start the web API.
var server = app.listen(8080, function () {
let address = server.address();
let host = address.address;
let port = address.port;
console.log("Starting up Jeremy's Code Challenge API. [http://%s:%s]", host, port);
});