Refactored to typescript & more consistent code
Updated how we interface with figlet and unified error handling & response.
This commit is contained in:
31
.eslintrc.json
Normal file
31
.eslintrc.json
Normal 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
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
|
lib/
|
||||||
|
|
||||||
.DS_Store
|
.DS_Store
|
||||||
|
|||||||
14
package.json
14
package.json
@@ -1,9 +1,19 @@
|
|||||||
{
|
{
|
||||||
|
"version": "1.0.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "node src/app.js"
|
"build": "tsc",
|
||||||
|
"start": "node lib/app.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"eslint": "^8.25.0",
|
||||||
|
"eslint-config-airbnb-base": "^15.0.0",
|
||||||
"express": "^4.18.2",
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
src/app.js
15
src/app.js
@@ -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
17
src/app.ts
Normal 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}.`);
|
||||||
|
});
|
||||||
33
src/ascii.js
33
src/ascii.js
@@ -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;
|
|
||||||
@@ -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;
|
|
||||||
20
src/controllers/figletController.ts
Normal file
20
src/controllers/figletController.ts
Normal 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;
|
||||||
14
src/controllers/fontsController.ts
Normal file
14
src/controllers/fontsController.ts
Normal 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;
|
||||||
@@ -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;
|
|
||||||
29
src/controllers/motdController.ts
Normal file
29
src/controllers/motdController.ts
Normal 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;
|
||||||
@@ -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
47
src/errors.ts
Normal 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
59
src/figletFonts.ts
Normal 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;
|
||||||
7
src/interfaces/IFigletOptions.ts
Normal file
7
src/interfaces/IFigletOptions.ts
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
export default interface IFigletOptions {
|
||||||
|
font: string
|
||||||
|
horizontalLayout: string
|
||||||
|
verticalLayout: string
|
||||||
|
width: number
|
||||||
|
whitespaceBreak: boolean
|
||||||
|
}
|
||||||
@@ -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;
|
|
||||||
30
src/requiredQueryMiddleware.ts
Normal file
30
src/requiredQueryMiddleware.ts
Normal 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
15
tsconfig.json
Normal 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"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user