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