Refactored to typescript & more consistent code

Updated how we interface with figlet and unified error handling &
response.
This commit is contained in:
2022-11-02 21:14:12 +01:00
parent df172e94ea
commit c8f87ade58
19 changed files with 1442 additions and 115 deletions

31
.eslintrc.json Normal file
View File

@@ -0,0 +1,31 @@
{
"root": true,
"parserOptions": {
"parser": "@typescript-eslint/parser",
"ecmaVersion": 2020,
"sourceType": "module"
},
"extends": [
"eslint-config-airbnb-base",
"plugin:@typescript-eslint/recommended"
],
"plugins": ["@typescript-eslint"],
"rules": {
"lines-between-class-members": [
"error",
"always",
{ "exceptAfterSingleLine": true }
],
"max-classes-per-file": 1,
"no-empty": [
2,
{
"allowEmptyCatch": true
}
],
"no-promise-executor-return": 1,
"no-shadow": "off",
"no-underscore-dangle": "off",
"@typescript-eslint/no-var-requires": "off"
}
}

1
.gitignore vendored
View File

@@ -1,3 +1,4 @@
node_modules/
lib/
.DS_Store

View File

@@ -1,9 +1,19 @@
{
"version": "1.0.0",
"scripts": {
"start": "node src/app.js"
"build": "tsc",
"start": "node lib/app.js"
},
"dependencies": {
"eslint": "^8.25.0",
"eslint-config-airbnb-base": "^15.0.0",
"express": "^4.18.2",
"figlet": "^1.5.2"
"figlet": "^1.5.2",
"typescript": "^4.8.4"
},
"devDependencies": {
"@types/node": "^18.11.2",
"@typescript-eslint/eslint-plugin": "^5.40.1",
"@typescript-eslint/parser": "^5.40.1"
}
}

View File

@@ -1,15 +0,0 @@
const express = require("express");
const asciiFontController = require("./controllers/asciiController.js");
const modtFontController = require("./controllers/motdController.js");
const requiredQueryMiddleware = require("./requiredQueryMiddleware");
const app = express();
const port = 3000;
app.get("/ascii", requiredQueryMiddleware, asciiFontController);
app.get("/motd", requiredQueryMiddleware, modtFontController);
app.listen(port, () => {
console.log(`Font generation application listening on port ${port}.`);
});

17
src/app.ts Normal file
View File

@@ -0,0 +1,17 @@
import express from "express";
import figletontController from "./controllers/figletController";
import modtFontController from "./controllers/motdController";
import fontsController from "./controllers/fontsController";
import requiredQueryMiddleware from "./requiredQueryMiddleware";
const app = express();
const port = 3000;
app.get("/figlet", requiredQueryMiddleware, figletontController);
app.get("/motd", requiredQueryMiddleware, modtFontController);
app.get("/fonts", fontsController);
app.listen(port, () => {
console.log(`Figlet generation application listening on port ${port}.`);
});

View File

@@ -1,33 +0,0 @@
const figlet = require("figlet");
const defaultFontOptions = {
font: "Larry 3D",
horizontalLayout: "default",
verticalLayout: "default",
width: 80,
whitespaceBreak: true,
};
function generateAscii(message, options) {
const _options = {
...defaultFontOptions,
...options,
};
return new Promise((resolve, reject) => {
figlet.text(message, _options, (err, data) => {
if (err) {
console.error("error from figlet:", error);
return reject(err);
}
if (_options.font === "Larry 3D") {
data = data.replaceAll("L", "_");
}
resolve(data);
});
});
}
module.exports = generateAscii;

View File

@@ -1,16 +0,0 @@
const generateAscii = require("../ascii.js");
const asciiFontController = (req, res) => {
const { text } = req.query;
return generateAscii(text)
.then((ascii) => res.send(ascii))
.catch((error) =>
res.status(error?.statusCode || 500).send({
success: false,
message: error?.message || "Unexpected error from font generation",
})
);
};
module.exports = asciiFontController;

View File

@@ -0,0 +1,20 @@
import FigletFonts from "../figletFonts";
import IFigletOptions from "../interfaces/IFigletOptions";
const figletFonts = new FigletFonts();
const figletTextController = async (req, res) => {
const { text, font, width } = req.query;
let options: IFigletOptions | Object = {};
if (font) options = { ...options, font };
if (width) options = { ...options, width };
figletFonts.generateText(text, options as IFigletOptions)
.then(data => res.send(data))
.catch((error) => {
const msg = error?.message || "Unexpected error from generating figlet";
res.status(error?.statusCode || 500).send(msg)
});
};
export default figletTextController;

View File

@@ -0,0 +1,14 @@
import FigletFonts from "../figletFonts.js";
const figletFonts = new FigletFonts();
const fontsController = (req, res) => {
return figletFonts.fonts
.then((fonts) => res.send(fonts))
.catch((error) => {
const msg = error?.message || "Unexpected error from fetching fonts"
res.status(error?.statusCode || 500).send(msg)
});
};
export default fontsController;

View File

@@ -1,17 +0,0 @@
const generateAscii = require("../ascii.js");
const modtFontController = (req, res) => {
const { text } = req.query;
return generateAscii(message)
.then((ascii) => generateCatOutput(ascii))
.then((motd) => res.send(motd))
.catch((error) => {
return res.status(500).send({
success: false,
message: "Unexpected error from font generation",
});
});
};
module.exports = modtFontController;

View File

@@ -0,0 +1,29 @@
import FigletFonts from "../figletFonts";
import IFigletOptions from "../interfaces/IFigletOptions";
const figletFonts = new FigletFonts()
function generateCatOutput(data: string) {
return Promise.resolve(`#!/bin/sh
cat << 'EOF'
${data}
EOF
`)
}
const modtFontController = (req, res) => {
const { text, font, width } = req.query;
let options: IFigletOptions | Object = {};
if (font) options = { ...options, font };
if (width) options = { ...options, width };
figletFonts.generateText(text, options as IFigletOptions)
.then((data) => generateCatOutput(data))
.then((motd) => res.send(motd))
.catch((error) => {
const msg = error?.message || "Unexpected error from generating motd figlet"
res.status(error?.statusCode || 500).send(msg)
});
};
export default modtFontController;

View File

@@ -1,13 +0,0 @@
class MissingTextError extends Error {
constructor(error = null) {
const message = `Missing query parameter 'text'`;
super(message);
this.error = error;
this.statusCode = 400;
}
}
module.exports = {
MissingTextError,
};

47
src/errors.ts Normal file
View File

@@ -0,0 +1,47 @@
class MissingTextError extends Error {
error: string
statusCode: number
constructor(error = null) {
const message = `Missing query parameter 'text'.`;
super(message);
this.error = error;
this.statusCode = 400;
}
}
class FontNotFoundError extends Error {
error: string
statusCode: number
constructor(font, error = null) {
const message = `Font '${font}' not found. Check /fonts.`;
super(message);
this.error = error;
this.statusCode = 404;
}
}
class UnexpectedFigletError extends Error {
error: string
statusCode: number
constructor(error) {
const message = "Unexpected error from figlet!"
super(message)
this.error = error;
this.statusCode = 500
console.log(message)
console.log(error)
}
}
export {
MissingTextError,
FontNotFoundError,
UnexpectedFigletError
};

59
src/figletFonts.ts Normal file
View File

@@ -0,0 +1,59 @@
import figlet from "figlet";
import IFigletOptions from "./interfaces/IFigletOptions";
import { UnexpectedFigletError } from './errors'
class FigletFonts {
cachedFonts: Array<string>;
defaultFontOptions: IFigletOptions
constructor() {
this.cachedFonts = [];
this.defaultFontOptions = {
font: "Larry 3D",
horizontalLayout: "default",
verticalLayout: "default",
width: 80,
whitespaceBreak: true,
};
}
static sanitizeLarry(text: string) {
return text.replaceAll("L", "_");
}
generateText(text: string, options: IFigletOptions): Promise<string> {
const _options: IFigletOptions = {
...this.defaultFontOptions,
...options,
};
return new Promise((resolve, reject) => {
figlet.text(text, _options, (err, data) => {
if (err) {
return reject(new UnexpectedFigletError(err));
}
resolve(data);
});
});
}
get fonts(): Promise<Array<string>> {
if (this.cachedFonts.length > 0) {
return Promise.resolve(this.cachedFonts);
}
return new Promise((resolve, reject) => {
figlet.fonts((err, fonts) => {
if (err) {
return reject(new UnexpectedFigletError(err));
}
resolve(fonts);
});
});
}
}
export default FigletFonts;

View File

@@ -0,0 +1,7 @@
export default interface IFigletOptions {
font: string
horizontalLayout: string
verticalLayout: string
width: number
whitespaceBreak: boolean
}

View File

@@ -1,14 +0,0 @@
const { MissingTextError } = require("./errors.js");
const requiredQueryMiddleware = (req, res, next) => {
const { text } = req.query;
if (!text || text?.length === 0) {
const error = new MissingTextError();
return res.status(error?.statusCode || 500).send(error?.message);
}
next();
};
module.exports = requiredQueryMiddleware;

View File

@@ -0,0 +1,30 @@
import FigletFonts from "./figletFonts";
import { MissingTextError, FontNotFoundError } from "./errors";
const figletFonts = new FigletFonts();
async function doesFontExist(font: string) {
const fontLowercased = font.toLowerCase();
const fonts: Array<string> = await figletFonts.fonts
return fonts.findIndex((_font) => _font.toLowerCase() === fontLowercased) === -1;
}
const requiredQueryMiddleware = async (req, res, next) => {
const { text, font = "" } = req.query;
let error = null;
if (!text || text?.length === 0) {
error = new MissingTextError();
}
if (font && (await doesFontExist(font))) {
error = new FontNotFoundError(font, error);
}
if (error) return res.status(error?.statusCode || 500).send(error?.message);
next();
};
export default requiredQueryMiddleware;

15
tsconfig.json Normal file
View File

@@ -0,0 +1,15 @@
{
"compilerOptions": {
"allowJs": true,
"checkJs": false,
"target": "esnext",
"module": "commonjs",
"moduleResolution": "node",
"strict": false,
"esModuleInterop": true,
"lib": ["esnext", "dom"],
"outDir": "lib"
},
"include": ["src/**/*.js", "src/**/*.ts", "src/**/*.d.ts"],
"exclude": ["node_modules"]
}

1165
yarn.lock

File diff suppressed because it is too large Load Diff