-
Notifications
You must be signed in to change notification settings - Fork 1
/
index.js
300 lines (277 loc) · 8.35 KB
/
index.js
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
/*
*Logs (W);
*21/01 - created index.js: server app
*21/01 - accessed and parsed data from dataset
*23/01 - testing output format and added error handling
*05/02 - formatted output and refactored Run function
*08/02 - added promts and refactored async functions
*09/02 - added promts and restructured code
*10/02 - retrieval of data by district and locality
*20/02 - display of data by locality
*28/02 - fixed issue 1a,2b,2c, removed redundant code
*22/03 - fixed issue 1b
*
*
*
*Logs (<developer's_initial>);
*
*
*
!Known Bugs/Issues (W);
*Error handling;
*1a. wrong locality input (line 161) ✅ --- fixed
*1b. if no data is found for a locality (line 262) ✅ --- fixed
*
*
*
TODO, Additional features and functionality;
*2a. command to go back - not available
*2b. remove filtering for number in read locality function ✅ --- fixed
*2c. move large lists to a new file ✅ --- fixed
*
*
*
*/
/* enable strict mode for better error handling and security (ES6)
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Strict_mode
*/
"use strict";
/*
*DATA DECLARATIONS;
*/
const PORT = 8080; //port on which server will run
//required packages;
import express from "express";
const app = express();
import fetch from "node-fetch";
import { createInterface } from "readline"; //readline package to read user input
import { available_districts, available_localities } from "./data.js";
//global variables;
const dataset =
"https://raw.githubusercontent.com/MrSunshyne/mauritius-dataset-electricity/main/data/power-outages.json"; /* CEB dataset in JSON format by MrSunshyne - https://github.com/MrSunshyne/ */
let data,
district,
locality,
localities = [],
amt_po = 100; //amount of power outages data to query
const sep = "----------------------------------------";
const exit_msg = "\n👋 Shutting down...\n";
/*
*SERVER;
*/
//read dataset and return as JSON
async function fetch_data() {
try {
const response = await fetch(dataset, {
headers: {
accept: "application/json",
},
});
return response.json();
} catch (error) {
console.log("🚨 Error fetching data from dataset. Please try again later.");
console.log(error);
process.exit(1);
}
}
//default route
app.get("/", (req, res) => {
try {
res.send("\n\nHello there! I am a JSON response 🥸.");
} catch (error) {
console.log(error);
}
});
//route to dataset
app.get("/dataset", async (req, res) => {
try {
data = await fetch_data(dataset);
res.json(data);
} catch (error) {
console.log(error);
}
return data;
});
/*
*USER INPUT;
*/
//create readline interface
const readline = createInterface({
input: process.stdin,
output: process.stdout,
});
//create promise to read user input for district
const readLineAsync_district = (msg) => {
return new Promise((resolve) => {
readline.question(msg, (userRes) => {
if (userRes === "exit") {
console.log(exit_msg);
process.exit(1);
}
userRes = userRes.replace(/\s/g, "").toLowerCase();
userRes = userRes.replace(/[^a-zA-Z ]/g, "");
if (!available_districts.includes(userRes)) {
//check if user input made a typo
let typo = available_districts.find((district) => {
return district.includes(userRes);
});
if (typo) {
console.log(`\n⚠️ Did you mean '${typo}'? Try again.\n`);
resolve(readLineAsync_district(msg));
} else {
console.log("\n❌ District not found. Try again.\n");
resolve(readLineAsync_district(msg));
}
}
resolve(userRes);
});
});
};
//create a promise to get user input for locality
const readLineAsync_locality = async (msg) => {
return new Promise((resolve) => {
readline.question(msg, (userRes) => {
if (userRes === "exit") {
console.log(exit_msg);
process.exit(1);
}
if (userRes === "back") {
resolve(userRes);
//restart the program
}
userRes = userRes.toLowerCase();
//check if user input made a typo
if (!available_localities.includes(userRes)) {
let typo = available_localities.find((locality) => {
return locality.includes(userRes);
});
if (typo) {
console.log(`\n⚠️ Did you mean '${typo}'? Try again.\n`);
resolve(readLineAsync_locality(msg));
} else {
console.log(`\n❌ Locality not found. Try again.\n`);
resolve(readLineAsync_locality(msg));
}
}
resolve(userRes);
});
});
};
//function to get user inputs
let promt_po_data = async () => {
await new Promise((resolve) => setTimeout(resolve, 500));
console.log("\n", sep);
let districts = available_districts.map((district) => {
return district.charAt(0).toUpperCase() + district.slice(1);
});
districts = districts.join(", ");
console.log(`\n🗺️ Available districts: ${districts}.\n`);
//prompt user to select a district then locality
district = await readLineAsync_district(`\n📍 Select a district: `);
await display_localities();
locality = await readLineAsync_locality(`\n📍 Select a locality: `);
readline.close();
return district, locality;
};
/*
*DATA MANIPULATION;
*/
//function to display all localities for a user defined district
let display_localities = async () => {
try {
let _data = await fetch_data(dataset);
for (let i = 0; i < _data[district].length; i++) {
localities.push(_data[district][i].locality);
}
localities = [...new Set(localities)];
localities.sort();
localities = localities.map((locality) => {
return locality.toLowerCase();
});
localities = localities.map((locality) => {
return locality.charAt(0).toUpperCase() + locality.slice(1);
});
localities = localities.map((locality) => {
return locality.replace(/\w\S*/g, function (txt) {
return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
});
});
console.log(`\n🏙️ Available localities: ${localities.join(", ")}\n`);
} catch (error) {
console.log(error);
}
};
//function to fetch data from dataset for user defined parameters
let display_po = async (_data) => {
try {
let found = false; // initialize a flag to track if data was found for the locality
console.log('\n',sep);
//loop through district and return data for the locality
for (let i = 0; i < amt_po; i++) {
var targetLocality = _data[district][i].locality.toLowerCase();
if (locality === targetLocality) {
if (!found) {
// print the message only if data for the locality is found
console.log(
`\n💡 Power outage data fetched for ${
locality.charAt(0).toUpperCase() + locality.slice(1)
}, ${
district.charAt(0).toUpperCase() + district.slice(1)
} (up to ${amt_po} planned power outages); \n`
);
found = true; // set the flag to true
}
console.log(`📅 ${_data[district][i].date}
\n🏙️ ${_data[district][i].locality}, ${_data[district][
i
].district.toUpperCase()}
\n🛣️ ${_data[district][i].streets}
`);
console.log(sep);
}
}
if (!found) {
// if data for the locality is not found, display a message
console.log(
`\n❌ No power outage data found for ${
locality.charAt(0).toUpperCase() + locality.slice(1)
}.\n`
);
}
await new Promise((resolve) => setTimeout(resolve, 1000));
console.log(exit_msg);
} catch (error) {
console.log(error);
}
};
//function to show user a guide on how to use the app
let user_guide = () => {
try {
console.log(
`Hello! I am a power outage app for Mauritius 🇲🇺.\n\n📚 User guide:
\n1. Select a district from the list.
\n2. The app will display the next power outages for that district.
\n3. To exit the app, type "exit".
\n4. To view the dataset, go to http://localhost:${PORT}/dataset.
`
);
} catch (error) {
console.log(error);
}
};
/*
*RUN SERVER;
*/
let run = () => {
try {
user_guide();
promt_po_data().then(() => fetch_data().then((data) => display_po(data)));
} catch (error) {
console.log(error);
fs.appendFile("error.log", error, (err) => {
if (err) throw err;
});
}
};
let popn = run();
//end of file;