Skip to content

Commit 11708da

Browse files
author
scinorandex
committed
Add plain mode
1 parent d1373bf commit 11708da

File tree

8 files changed

+380
-94
lines changed

8 files changed

+380
-94
lines changed

src/api.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import express from "express";
22
import morgan from "morgan";
33
import { errorHandler } from "./api/errorHandler";
4+
import { plainRouter } from "./api/plainRouter";
45
import { router } from "./api/router";
56
import { userAgentMiddleware } from "./api/userAgent";
67

@@ -11,16 +12,17 @@ const app = express();
1112
app.use(morgan("common"));
1213
app.use(userAgentMiddleware);
1314

15+
app.use(["/basic", "/cmd", "/plain"], plainRouter);
1416
app.use(["/quiet", "/"], router);
15-
app.use(["/quiet", "/"], errorHandler);
17+
app.use("/", errorHandler);
1618

1719
app.use("*", (_req, res) =>
18-
res.send(
19-
`Welcome to COVID-19 Tracker CLI v${version} by Waren Gonzaga with Wareneutron Developers\n
20+
res.send(
21+
`Welcome to COVID-19 Tracker CLI v${version} by Waren Gonzaga with Wareneutron Developers\n
2022
Please visit: https://warengonza.ga/covid19-tracker-cli\n`
21-
)
23+
)
2224
);
2325

2426
app.listen(port, () => {
25-
console.log(`Express listening on port ${port}`);
27+
console.log(`Express listening on port ${port}`);
2628
});

src/api/plainRouter.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import { Router } from "express";
2+
import handleAsync from "./handleAsync";
3+
import {
4+
globalInformationPlain,
5+
informationPerCountryPlain,
6+
historyPerCountryPlain,
7+
globalHistoryPlain,
8+
} from "../utils/plainHandlers";
9+
export const plainRouter = Router({ mergeParams: true });
10+
11+
plainRouter.get(
12+
"/history/:mode?",
13+
handleAsync(async (req, res, next) => {
14+
// get mode from params
15+
let mode = req.params.mode as "cases" | "deaths" | "recovered";
16+
17+
//default to cases if mode is undefined
18+
mode = mode === undefined ? "cases" : mode;
19+
20+
// if the mode is not in the api then return to next handler
21+
if (!["cases", "deaths", "recovered"].includes(mode)) return next();
22+
res.send(await globalHistoryPlain(mode));
23+
})
24+
);
25+
26+
plainRouter.get(
27+
"/history/:country/:mode?",
28+
handleAsync(async (req, res, next) => {
29+
const country = req.params.country;
30+
// get mode from params
31+
let mode = req.params.mode as "cases" | "deaths" | "recovered";
32+
33+
//default to cases if mode is undefined
34+
mode = mode === undefined ? "cases" : mode;
35+
36+
// if the mode is not in the api then return to next handler
37+
if (!["cases", "deaths", "recovered"].includes(mode)) return next();
38+
res.send(await historyPerCountryPlain(country, mode));
39+
})
40+
);
41+
42+
plainRouter.get(
43+
"/:country",
44+
handleAsync(async (req, res, _next) => {
45+
const country = req.params.country;
46+
res.send(await informationPerCountryPlain(country));
47+
})
48+
);
49+
50+
plainRouter.get(
51+
"/",
52+
handleAsync(async (_req, res, _next) => {
53+
res.send(await globalInformationPlain());
54+
})
55+
);

src/api/router.ts

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@ router.get(
1919
handleAsync(async (req, res, next) => {
2020
// get mode from params
2121
let mode = req.params.mode as "cases" | "deaths" | "recovered";
22+
2223
//default to cases if mode is undefined
2324
mode = mode === undefined ? "cases" : mode;
24-
console.log(mode);
2525

2626
// if the mode is not in the api then return to next handler
2727
if (!["cases", "deaths", "recovered"].includes(mode)) return next();
@@ -33,7 +33,6 @@ router.get(
3333
"/history/:country/:mode?",
3434
handleAsync(async (req, res, next) => {
3535
const country = req.params.country;
36-
console.log("eere");
3736
// get mode from params
3837
let mode = req.params.mode as "cases" | "deaths" | "recovered";
3938

@@ -55,7 +54,6 @@ router.get(
5554
router.get(
5655
"/:country",
5756
handleAsync(async (req, res, _next) => {
58-
console.log(req.path);
5957
const country = req.params.country;
6058
res.send(
6159
await informationPerCountry(

src/utils/generateAsciichart.ts

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import { plot } from "asciichart";
22

3-
export const generateAsciichart: (data: number[]) => string = (data) => {
3+
export const generateAsciichart: (
4+
data: number[],
5+
removePadding?: boolean,
6+
height?: number
7+
) => string = (data, removePadding, height = 10) => {
48
// Divide the data by 100 since asciichart runs out of ram
59
let casesArray: number[] = [];
610
data.forEach((int) => {
@@ -9,7 +13,7 @@ export const generateAsciichart: (data: number[]) => string = (data) => {
913
});
1014

1115
// Generate chart
12-
let chart = plot(casesArray, { height: 10 });
16+
let chart = plot(casesArray, { height });
1317

1418
// Get and normalize the floats
1519
let floatsInAsciiChart: string[] = chart.match(/[+-]?\d+(\.\d+)?/g)!; // Get floats from asciichart
@@ -25,5 +29,13 @@ export const generateAsciichart: (data: number[]) => string = (data) => {
2529
chart = chart.replace(key, value);
2630
});
2731

32+
// Remove the padding if the user requests to
33+
if (removePadding === true) {
34+
chart = chart
35+
.split("\n")
36+
.map((str) => str.trimStart())
37+
.join("\n");
38+
}
39+
2840
return chart;
2941
};

src/utils/generatePlainOutput.ts

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
import { PlainData } from "./getInformation";
2+
import { getSaying } from "./getSaying";
3+
import { getTimestamp } from "./getTimestamp";
4+
const { version } = require("../../package.json");
5+
6+
export const generatePlainOutput: (
7+
info: PlainData,
8+
chartType: string,
9+
extraRows?: string[]
10+
) => string = ({ data, metainfo }, chartType, extraRows) => {
11+
// Set line depending if it contains a chart or not
12+
let line = extraRows === undefined ? "-".repeat(60) : "-".repeat(68);
13+
line += "\n";
14+
15+
let header = `COVID-19 Tracker CLI v${version} - ${chartType}`;
16+
let timestamp = getTimestamp(metainfo.updated as number);
17+
let saying = getSaying();
18+
19+
// Include GCash message if the query is to the PH
20+
let GCashMessage = chartType.toLowerCase().includes("philippines")
21+
? "(GCash) +639176462753\n"
22+
: "";
23+
24+
// Generate table
25+
let table = "";
26+
27+
// Create columns
28+
let normalizedArray: string[] = [];
29+
Object.keys(data).forEach((key) => {
30+
let value = data[key];
31+
let line = `${key.padEnd(15, " ")}| ${value.padEnd(13, " ")}`; // create a line with length 30;
32+
normalizedArray.push(line);
33+
});
34+
35+
while (normalizedArray.length > 0) {
36+
let left = normalizedArray.shift();
37+
let right = normalizedArray.shift();
38+
39+
//right may be undefined, so default to empty string
40+
if (right === undefined) right = "";
41+
42+
table += `${left}${right}`;
43+
if (normalizedArray.length !== 0) table += `\n`; // do not add whitespace at the end of the table
44+
}
45+
46+
/**
47+
* responseArray is the array of the raw data **before** adding the separator lines
48+
*/
49+
let responseArray: string[] = [header, timestamp, table];
50+
51+
// Add extraRows to responseArray
52+
if (extraRows !== undefined) {
53+
extraRows.forEach((str) => {
54+
responseArray.push(str);
55+
});
56+
}
57+
58+
// Add the help msg and other messages
59+
responseArray = responseArray.concat([
60+
"Help: Try to append the URL with /help to learn more...",
61+
"Source: https://www.worldometers.info/coronavirus/",
62+
"Code: https://github.com/warengonzaga/covid19-tracker-cli",
63+
`\n${saying}\n`,
64+
`Love this project? Help us to help others by means of coffee!\n${GCashMessage}(Buy Me A Coffee) warengonza.ga/coffee4dev`,
65+
"Follow me on twitter for more updates!\n@warengonzaga #covid19trackercli",
66+
]);
67+
68+
// Construct the final output
69+
let response: string = "\n";
70+
responseArray.forEach((str) => {
71+
response += `${line}`;
72+
response += `${str}\n`;
73+
});
74+
75+
// Add padding to the side
76+
response = response
77+
.split("\n")
78+
.map((str) => ` ${str}`)
79+
.join("\n");
80+
81+
response += "\n";
82+
83+
return response;
84+
};

src/utils/getInformation.ts

Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import axios from "axios";
2+
axios.defaults.baseURL = "https://disease.sh/v3/covid-19";
3+
4+
let countryCodes: { [key: string]: string } = {};
5+
(async () => {
6+
countryCodes = (await axios.get("http://country.io/names.json")).data;
7+
})();
8+
9+
export interface PlainData {
10+
data: {
11+
[key: string]: string;
12+
};
13+
metainfo: {
14+
[key: string]: number | string;
15+
};
16+
}
17+
18+
/**
19+
* @param isPlain Set to true to recieve an object containing the responses instead of the rows
20+
* @returns an object containing the data and metainfo **if isPlain is set to true**
21+
* @returns an array in the format of [timestamp, rows] **if isPlain is set to false**
22+
*/
23+
export const getAllInfo: (
24+
isPlain?: boolean
25+
) => Promise<[number, (string[] | string)[]] | PlainData> = async (
26+
isPlain = false
27+
) => {
28+
let { data: globalData } = await axios.get("/all");
29+
let { cases, deaths, recovered, updated } = globalData;
30+
31+
let mortalityPercentage = ((deaths / cases) * 100).toFixed(2) + "%";
32+
let recoveredPercentage = ((recovered / cases) * 100).toFixed(2) + "%";
33+
34+
[cases, deaths, recovered] = [cases, deaths, recovered].map((num: number) =>
35+
num.toLocaleString("en-US", { maximumFractionDigits: 0 })
36+
);
37+
38+
// Return object containing information if isPlain is set to true
39+
if (isPlain) {
40+
return {
41+
data: {
42+
Cases: cases,
43+
Deaths: deaths,
44+
"Mortality %": mortalityPercentage,
45+
Recovered: recovered,
46+
"Recovered %": recoveredPercentage,
47+
},
48+
metainfo: {
49+
updated,
50+
},
51+
};
52+
}
53+
54+
// Return rows if isPlain is set to false
55+
// prettier-ignore
56+
return [updated, [
57+
["Cases".magenta, "Deaths".red,"Recovered".green, "Mortality %".red,"Recovered %".green],
58+
[cases, deaths, recovered, mortalityPercentage, recoveredPercentage]]]
59+
};
60+
61+
/**
62+
* @param country the country code or string that the user provides from req.params or CLI
63+
* @param isPlain Set to true to recieve an object containing the responses instead of the rows
64+
* @returns an object containing the data and metainfo **if isPlain is set to true**
65+
* @returns an array in the format of [timestamp, API countryname, formal countryname, rows[]] **if isPlain is false
66+
*/
67+
export const getCountryInfo: (
68+
country: string,
69+
isPlain?: boolean
70+
) => Promise<
71+
[number, string, string, (string[] | string)[]] | PlainData
72+
> = async (country, isPlain) => {
73+
// Wait 1 second for countryCodes to initialize, needed for CLI
74+
if (Object.keys(countryCodes).length === 0) {
75+
await new Promise((resolve) => {
76+
setTimeout(resolve, 1000);
77+
});
78+
}
79+
80+
country =
81+
country.length < 3 ? countryCodes[country.toUpperCase()] : country; // Convert country code to country name
82+
83+
if (country === undefined || typeof country === "undefined")
84+
throw new Error(`Cannot find provided country`);
85+
86+
try {
87+
let { data: countryData } = await axios.get(`/countries/${country}`);
88+
// prettier-ignore
89+
let { country: countryName, updated, cases, deaths, recovered, active, casesPerOneMillion, todayCases, todayDeaths, critical} = countryData;
90+
91+
let mortalityPercentage = ((deaths / cases) * 100).toFixed(2) + "%";
92+
let recoveredPercentage = ((recovered / cases) * 100).toFixed(2) + "%";
93+
94+
// prettier-ignore
95+
[ cases, deaths, recovered, active, casesPerOneMillion, todayCases, todayDeaths, critical ] =
96+
[ cases, deaths, recovered, active, casesPerOneMillion, todayCases, todayDeaths, critical,
97+
].map((num: number) =>
98+
num.toLocaleString("en-US", { maximumFractionDigits: 0 })
99+
);
100+
101+
// Return object containing information if isPlain is set to true
102+
if (isPlain) {
103+
return {
104+
data: {
105+
Cases: cases,
106+
"Today Cases": todayCases,
107+
Active: active,
108+
Recovered: recovered,
109+
Deaths: deaths,
110+
"Today Deaths": todayDeaths,
111+
Critical: critical,
112+
"Mortality %": mortalityPercentage,
113+
"Recovery %": recoveredPercentage,
114+
"Cases/Million": casesPerOneMillion,
115+
},
116+
metainfo: {
117+
updated,
118+
countryName,
119+
},
120+
};
121+
}
122+
123+
//prettier-ignore
124+
return [updated, country, countryName, [
125+
[ "Cases".magenta, "Deaths".red, "Recovered".green, "Active".blue, "Cases/Million".blue,],
126+
[ cases, deaths, recovered, active, casesPerOneMillion,],
127+
[ "Today Cases".magenta, "Today Deaths".red, "Critical".red, "Mortaility %".red, "Recovery %".green],
128+
[ todayCases, todayDeaths, critical, mortalityPercentage, recoveredPercentage]]
129+
]
130+
} catch {
131+
throw new Error(`Cannot find the provided country`);
132+
}
133+
};

0 commit comments

Comments
 (0)