Skip to content
Prev Previous commit
Next Next commit
Add plain mode
  • Loading branch information
scinorandex committed Mar 30, 2021
commit 11708dafbe88c4e0894a96eb3ce6e92054c57079
12 changes: 7 additions & 5 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import express from "express";
import morgan from "morgan";
import { errorHandler } from "./api/errorHandler";
import { plainRouter } from "./api/plainRouter";
import { router } from "./api/router";
import { userAgentMiddleware } from "./api/userAgent";

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

app.use(["/basic", "/cmd", "/plain"], plainRouter);
app.use(["/quiet", "/"], router);
app.use(["/quiet", "/"], errorHandler);
app.use("/", errorHandler);

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

app.listen(port, () => {
console.log(`Express listening on port ${port}`);
console.log(`Express listening on port ${port}`);
});
55 changes: 55 additions & 0 deletions src/api/plainRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { Router } from "express";
import handleAsync from "./handleAsync";
import {
globalInformationPlain,
informationPerCountryPlain,
historyPerCountryPlain,
globalHistoryPlain,
} from "../utils/plainHandlers";
export const plainRouter = Router({ mergeParams: true });

plainRouter.get(
"/history/:mode?",
handleAsync(async (req, res, next) => {
// get mode from params
let mode = req.params.mode as "cases" | "deaths" | "recovered";

//default to cases if mode is undefined
mode = mode === undefined ? "cases" : mode;

// if the mode is not in the api then return to next handler
if (!["cases", "deaths", "recovered"].includes(mode)) return next();
res.send(await globalHistoryPlain(mode));
})
);

plainRouter.get(
"/history/:country/:mode?",
handleAsync(async (req, res, next) => {
const country = req.params.country;
// get mode from params
let mode = req.params.mode as "cases" | "deaths" | "recovered";

//default to cases if mode is undefined
mode = mode === undefined ? "cases" : mode;

// if the mode is not in the api then return to next handler
if (!["cases", "deaths", "recovered"].includes(mode)) return next();
res.send(await historyPerCountryPlain(country, mode));
})
);

plainRouter.get(
"/:country",
handleAsync(async (req, res, _next) => {
const country = req.params.country;
res.send(await informationPerCountryPlain(country));
})
);

plainRouter.get(
"/",
handleAsync(async (_req, res, _next) => {
res.send(await globalInformationPlain());
})
);
4 changes: 1 addition & 3 deletions src/api/router.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@ router.get(
handleAsync(async (req, res, next) => {
// get mode from params
let mode = req.params.mode as "cases" | "deaths" | "recovered";

//default to cases if mode is undefined
mode = mode === undefined ? "cases" : mode;
console.log(mode);

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

Expand All @@ -55,7 +54,6 @@ router.get(
router.get(
"/:country",
handleAsync(async (req, res, _next) => {
console.log(req.path);
const country = req.params.country;
res.send(
await informationPerCountry(
Expand Down
16 changes: 14 additions & 2 deletions src/utils/generateAsciichart.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
import { plot } from "asciichart";

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

// Generate chart
let chart = plot(casesArray, { height: 10 });
let chart = plot(casesArray, { height });

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

// Remove the padding if the user requests to
if (removePadding === true) {
chart = chart
.split("\n")
.map((str) => str.trimStart())
.join("\n");
}

return chart;
};
84 changes: 84 additions & 0 deletions src/utils/generatePlainOutput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { PlainData } from "./getInformation";
import { getSaying } from "./getSaying";
import { getTimestamp } from "./getTimestamp";
const { version } = require("../../package.json");

export const generatePlainOutput: (
info: PlainData,
chartType: string,
extraRows?: string[]
) => string = ({ data, metainfo }, chartType, extraRows) => {
// Set line depending if it contains a chart or not
let line = extraRows === undefined ? "-".repeat(60) : "-".repeat(68);
line += "\n";

let header = `COVID-19 Tracker CLI v${version} - ${chartType}`;
let timestamp = getTimestamp(metainfo.updated as number);
let saying = getSaying();

// Include GCash message if the query is to the PH
let GCashMessage = chartType.toLowerCase().includes("philippines")
? "(GCash) +639176462753\n"
: "";

// Generate table
let table = "";

// Create columns
let normalizedArray: string[] = [];
Object.keys(data).forEach((key) => {
let value = data[key];
let line = `${key.padEnd(15, " ")}| ${value.padEnd(13, " ")}`; // create a line with length 30;
normalizedArray.push(line);
});

while (normalizedArray.length > 0) {
let left = normalizedArray.shift();
let right = normalizedArray.shift();

//right may be undefined, so default to empty string
if (right === undefined) right = "";

table += `${left}${right}`;
if (normalizedArray.length !== 0) table += `\n`; // do not add whitespace at the end of the table
}

/**
* responseArray is the array of the raw data **before** adding the separator lines
*/
let responseArray: string[] = [header, timestamp, table];

// Add extraRows to responseArray
if (extraRows !== undefined) {
extraRows.forEach((str) => {
responseArray.push(str);
});
}

// Add the help msg and other messages
responseArray = responseArray.concat([
"Help: Try to append the URL with /help to learn more...",
"Source: https://www.worldometers.info/coronavirus/",
"Code: https://github.com/warengonzaga/covid19-tracker-cli",
`\n${saying}\n`,
`Love this project? Help us to help others by means of coffee!\n${GCashMessage}(Buy Me A Coffee) warengonza.ga/coffee4dev`,
"Follow me on twitter for more updates!\n@warengonzaga #covid19trackercli",
]);

// Construct the final output
let response: string = "\n";
responseArray.forEach((str) => {
response += `${line}`;
response += `${str}\n`;
});

// Add padding to the side
response = response
.split("\n")
.map((str) => ` ${str}`)
.join("\n");

response += "\n";

return response;
};
133 changes: 133 additions & 0 deletions src/utils/getInformation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import axios from "axios";
axios.defaults.baseURL = "https://disease.sh/v3/covid-19";

let countryCodes: { [key: string]: string } = {};
(async () => {
countryCodes = (await axios.get("http://country.io/names.json")).data;
})();

export interface PlainData {
data: {
[key: string]: string;
};
metainfo: {
[key: string]: number | string;
};
}

/**
* @param isPlain Set to true to recieve an object containing the responses instead of the rows
* @returns an object containing the data and metainfo **if isPlain is set to true**
* @returns an array in the format of [timestamp, rows] **if isPlain is set to false**
*/
export const getAllInfo: (
isPlain?: boolean
) => Promise<[number, (string[] | string)[]] | PlainData> = async (
isPlain = false
) => {
let { data: globalData } = await axios.get("/all");
let { cases, deaths, recovered, updated } = globalData;

let mortalityPercentage = ((deaths / cases) * 100).toFixed(2) + "%";
let recoveredPercentage = ((recovered / cases) * 100).toFixed(2) + "%";

[cases, deaths, recovered] = [cases, deaths, recovered].map((num: number) =>
num.toLocaleString("en-US", { maximumFractionDigits: 0 })
);

// Return object containing information if isPlain is set to true
if (isPlain) {
return {
data: {
Cases: cases,
Deaths: deaths,
"Mortality %": mortalityPercentage,
Recovered: recovered,
"Recovered %": recoveredPercentage,
},
metainfo: {
updated,
},
};
}

// Return rows if isPlain is set to false
// prettier-ignore
return [updated, [
["Cases".magenta, "Deaths".red,"Recovered".green, "Mortality %".red,"Recovered %".green],
[cases, deaths, recovered, mortalityPercentage, recoveredPercentage]]]
};

/**
* @param country the country code or string that the user provides from req.params or CLI
* @param isPlain Set to true to recieve an object containing the responses instead of the rows
* @returns an object containing the data and metainfo **if isPlain is set to true**
* @returns an array in the format of [timestamp, API countryname, formal countryname, rows[]] **if isPlain is false
*/
export const getCountryInfo: (
country: string,
isPlain?: boolean
) => Promise<
[number, string, string, (string[] | string)[]] | PlainData
> = async (country, isPlain) => {
// Wait 1 second for countryCodes to initialize, needed for CLI
if (Object.keys(countryCodes).length === 0) {
await new Promise((resolve) => {
setTimeout(resolve, 1000);
});
}

country =
country.length < 3 ? countryCodes[country.toUpperCase()] : country; // Convert country code to country name

if (country === undefined || typeof country === "undefined")
throw new Error(`Cannot find provided country`);

try {
let { data: countryData } = await axios.get(`/countries/${country}`);
// prettier-ignore
let { country: countryName, updated, cases, deaths, recovered, active, casesPerOneMillion, todayCases, todayDeaths, critical} = countryData;

let mortalityPercentage = ((deaths / cases) * 100).toFixed(2) + "%";
let recoveredPercentage = ((recovered / cases) * 100).toFixed(2) + "%";

// prettier-ignore
[ cases, deaths, recovered, active, casesPerOneMillion, todayCases, todayDeaths, critical ] =
[ cases, deaths, recovered, active, casesPerOneMillion, todayCases, todayDeaths, critical,
].map((num: number) =>
num.toLocaleString("en-US", { maximumFractionDigits: 0 })
);

// Return object containing information if isPlain is set to true
if (isPlain) {
return {
data: {
Cases: cases,
"Today Cases": todayCases,
Active: active,
Recovered: recovered,
Deaths: deaths,
"Today Deaths": todayDeaths,
Critical: critical,
"Mortality %": mortalityPercentage,
"Recovery %": recoveredPercentage,
"Cases/Million": casesPerOneMillion,
},
metainfo: {
updated,
countryName,
},
};
}

//prettier-ignore
return [updated, country, countryName, [
[ "Cases".magenta, "Deaths".red, "Recovered".green, "Active".blue, "Cases/Million".blue,],
[ cases, deaths, recovered, active, casesPerOneMillion,],
[ "Today Cases".magenta, "Today Deaths".red, "Critical".red, "Mortaility %".red, "Recovery %".green],
[ todayCases, todayDeaths, critical, mortalityPercentage, recoveredPercentage]]
]
} catch {
throw new Error(`Cannot find the provided country`);
}
};
Loading