Skip to content
3 changes: 3 additions & 0 deletions .editorconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[*]
indent_style = tabs
indent_size = 4
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ debug.log
.DS_Store
.now
.env
dist/
38 changes: 21 additions & 17 deletions now.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,23 @@
{
"version": 2,
"builds": [{
"src": "app.js",
"use": "@now/node-server"
}],
"routes": [{
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, Content-Type, Accept"
},
"src": "/.*",
"dest": "/app.js"
}],
"env": {
"VERSION": "1"
}
"version": 2,
"builds": [
{
"src": "src/api.ts",
"use": "@vercel/node"
}
],
"routes": [
{
"headers": {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
"Access-Control-Allow-Headers": "X-Requested-With, Content-Type, Accept"
},
"src": "/(.*)",
"dest": "src/api.ts"
}
],
"env": {
"VERSION": "1"
}
}
13 changes: 11 additions & 2 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,11 +12,19 @@ const app = express();
app.use(morgan("common"));
app.use(userAgentMiddleware);

app.use("/", router);
/**
* Plain CMD/Basic routes have both quiet and full modes
* Same with regular / routes with ansi color codes
*/
app.use(["/quiet/basic", "/quiet/cmd", "/quiet/plain"], plainRouter);
app.use(["/basic", "/cmd", "/plain"], plainRouter);

app.use(["/quiet", "/"], router);
app.use("/", errorHandler);

// Not found handler
app.use("*", (_req, res) =>
res.send(
res.status(404).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`
)
Expand Down
8 changes: 6 additions & 2 deletions src/api/errorHandler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import { ErrorRequestHandler } from "express";
import { generateColorTable } from "../utils/generateTable";

/**
*
* @param error Error object, received from errors thrown in the code
* @param res Response object from Express
*/
export const errorHandler: ErrorRequestHandler = (error, _, res, _next) => {
const statusCode = res.statusCode === 200 ? 500 : res.statusCode;
res.status(statusCode);
res.send(generateColorTable([error.message], "red"));
res.send(error.message + "\n");
res.end();
};
6 changes: 6 additions & 0 deletions src/api/handleAsync.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { Request, Response, NextFunction } from "express";
type Handler<R> = (req: Request, res: Response, next: NextFunction) => R;

/**
* @example router.use("/path/", handleAsync((req, res, next)=>{res.send("Hello World!")}));
* @param asyncFn An asyncronous function that takes in req, res, and next
* @returns An asyncronous function where errors will be catched and sent to the error handler
*/
const handleAsync: (asyncFn: Handler<Promise<void>>) => Handler<void> = (
asyncFn
) => {
Expand Down
61 changes: 61 additions & 0 deletions src/api/plainRouter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Router } from "express";
import handleAsync from "./handleAsync";
import {
globalInformationPlain,
informationPerCountryPlain,
historyPerCountryPlain,
globalHistoryPlain,
} from "../utils/plainHandlers";
import { isQuiet } from "./router";

/**
* The plainRouter handles all the plain routes such as /basic, /cmd, and /plain
* It also handles the quiet version of these routes
*/
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, isQuiet(req)));
})
);

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, isQuiet(req)));
})
);

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

plainRouter.get(
"/",
handleAsync(async (req, res, _next) => {
res.send(await globalInformationPlain(isQuiet(req)));
})
);
23 changes: 15 additions & 8 deletions src/api/router.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Router } from "express";
import { Request, Router } from "express";
import {
globalHistory,
globalInformation,
Expand All @@ -7,6 +7,14 @@ import {
} from "../utils/handlers";
import handleAsync from "./handleAsync";

/**
*
* @param req Express request
* @returns Boolean if the request starts with /quiet
*/
export const isQuiet: (req: Request) => boolean = (req) =>
req.baseUrl.startsWith("/quiet");

/**
* The rootRouter handles all the processing of the requests *after* passing through
* all middlewares except not found and error handling middleware
Expand All @@ -19,21 +27,20 @@ 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();
res.send(await globalHistory(mode));
res.send(await globalHistory(mode, isQuiet(req)));
})
);

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 @@ -42,21 +49,21 @@ router.get(

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

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

router.get(
"/",
handleAsync(async (_req, res, _next) => {
res.send(await globalInformation());
handleAsync(async (req, res, _next) => {
res.send(await globalInformation(isQuiet(req)));
})
);
14 changes: 13 additions & 1 deletion src/api/userAgent.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Request, Response, NextFunction } from "express";
const { version } = require("../../package.json");

// Type of middleware and handler
export type Handler = (
Expand All @@ -7,17 +8,28 @@ export type Handler = (
next: NextFunction
) => Promise<void> | void;

/**
*
* @param userAgent The user agent of the requester
* @returns A boolean that is true of the user agent provided is from curl / wget / httpie
*/
const isTerminal: (userAgent: string | undefined) => boolean = (userAgent) => {
if (userAgent === undefined) return false;
if (/curl|wget|httpie/i.test(userAgent)) return true;
return false;
};

export const userAgentMiddleware: Handler = (req, res, next) => {
/**
* Get the user agent from the request
* Determine if the user agent is from curl / wget / httpie
* If true then proceed using the next function
* Else return with message
*/
const userAgent = req.headers["user-agent"];
if (!isTerminal(userAgent)) {
res.send(
`Welcome to COVID-19 Tracker CLI v3.9.3 by Waren Gonzaga.\n\nPlease visit: https://warengonza.ga/covid19-tracker-cli`
`Welcome to COVID-19 Tracker CLI v${version} by Waren Gonzaga with Wareneutron Developers\nPlease visit: https://warengonza.ga/covid19-tracker-cli\n`
);
return;
}
Expand Down
47 changes: 36 additions & 11 deletions src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import argv from "minimist";
import { generateColorTable } from "./utils/generateTable";
import {
globalHistory,
globalInformation,
historyPerCountry,
informationPerCountry,
} from "./utils/handlers";
import {
globalHistoryPlain,
globalInformationPlain,
historyPerCountryPlain,
informationPerCountryPlain,
} from "./utils/plainHandlers";

const args = argv(process.argv.slice(2));
let { history, mode, help } = args;
let { history, mode, help, quiet, plain } = args;
const country = args._[0];

const { version } = require("../package.json");
Expand All @@ -22,29 +27,49 @@ Country: Can be a country name or ISO 3166-1 alpha-2 country code

Options:
--history Show a chart of country's cases of world's cases
--mode Use with --history to make show a chart of cases, deaths, or recovered`;
--mode Use with --history to make show a chart of cases, deaths, or recovered
--quiet Only show necessary information
--plain Enable plain mode`;

let output: string = "";
const main = async () => {
if (help) return console.log(helpMessage);
quiet = quiet === undefined ? false : quiet;

if (history === undefined || typeof history === "undefined") {
if (country === undefined) output = await globalInformation();
else output = await informationPerCountry(country);
if (history === undefined) {
if (country === undefined) {
output =
plain === true
? await globalInformationPlain(quiet)
: await globalInformation(quiet);
} else {
output =
plain === true
? await informationPerCountryPlain(country, quiet)
: await informationPerCountry(country, quiet);
}
}

mode = mode === undefined || typeof mode === "undefined" ? "cases" : mode; // defauilt to cases if mode is not present
mode = mode === undefined ? "cases" : mode; // default to cases if mode is not present
if (!["cases", "deaths", "recovered"].includes(mode)) mode === "cases"; // default to cases if mode is not cases | deaths | recovered

if (history) {
if (country === undefined) output = await globalHistory(mode);
else output = await historyPerCountry(country, mode);
if (country === undefined) {
output =
plain === true
? await globalHistoryPlain(mode, quiet)
: await globalHistory(mode, quiet);
} else {
output =
plain === true
? await historyPerCountryPlain(country, mode, quiet)
: await historyPerCountry(country, mode, quiet);
}
}

console.log(output);
};

main().catch((err) => {
let errorTable = generateColorTable([err.message], "red");
console.log(errorTable);
console.log(err.message + "\n");
});
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;
};
Loading