Compare commits
12 Commits
df172e94ea
...
3c9d62b840
| Author | SHA1 | Date | |
|---|---|---|---|
| 3c9d62b840 | |||
| 4dae70a45f | |||
| 25ae3a2b65 | |||
| ef3a394657 | |||
| d713786685 | |||
| 4311a4c841 | |||
| 48fde2c0b6 | |||
| 3ed57b75f4 | |||
| f8708909b6 | |||
| 2d42605c59 | |||
| 35760c2da2 | |||
| c8f87ade58 |
77
.drone.yml
Normal file
77
.drone.yml
Normal file
@@ -0,0 +1,77 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: figlet-http build
|
||||
|
||||
platform:
|
||||
os: linux
|
||||
arch: amd64
|
||||
|
||||
volumes:
|
||||
- name: cache
|
||||
host:
|
||||
path: /tmp/cache
|
||||
|
||||
steps:
|
||||
- name: Load cached node_modules packages
|
||||
image: sinlead/drone-cache:1.0.0
|
||||
settings:
|
||||
action: load
|
||||
key: yarn.lock
|
||||
mount: node_modules
|
||||
prefix: yarn-modules-figlet-http
|
||||
volumes:
|
||||
- name: cache
|
||||
path: /cache
|
||||
|
||||
- name: Install
|
||||
image: node:18.2.0
|
||||
commands:
|
||||
- node -v
|
||||
- yarn --version
|
||||
- yarn
|
||||
|
||||
- name: Cache node_modules packages
|
||||
image: sinlead/drone-cache:1.0.0
|
||||
settings:
|
||||
action: save
|
||||
key: yarn.lock
|
||||
mount: node_modules
|
||||
prefix: yarn-modules-figlet-http
|
||||
volumes:
|
||||
- name: cache
|
||||
path: /cache
|
||||
|
||||
- name: Build ts
|
||||
image: node:18.2.0
|
||||
commands:
|
||||
- yarn build
|
||||
|
||||
- name: Lint project using eslint
|
||||
image: node:18.2.0
|
||||
commands:
|
||||
- yarn lint
|
||||
failure: ignore
|
||||
|
||||
- name: Build and publish docker image
|
||||
image: plugins/docker
|
||||
settings:
|
||||
registry: ghcr.io
|
||||
repo: ghcr.io/kevinmidboe/figlet-http
|
||||
dockerfile: Dockerfile
|
||||
username:
|
||||
from_secret: GITHUB_USERNAME
|
||||
password:
|
||||
from_secret: GITHUB_PASSWORD
|
||||
tags: latest
|
||||
when:
|
||||
event:
|
||||
- push
|
||||
branch:
|
||||
- master
|
||||
|
||||
trigger:
|
||||
event:
|
||||
include:
|
||||
- push
|
||||
- pull_request
|
||||
41
.eslintrc.json
Normal file
41
.eslintrc.json
Normal file
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"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,
|
||||
"import/extensions": "off",
|
||||
"max-classes-per-file": "off",
|
||||
"no-shadow": "off",
|
||||
"no-underscore-dangle": "off",
|
||||
"@typescript-eslint/no-var-requires": "off"
|
||||
},
|
||||
"settings": {
|
||||
"import/resolver": {
|
||||
"node": {
|
||||
"extensions": [".js", ".ts"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,4 @@
|
||||
node_modules/
|
||||
lib/
|
||||
|
||||
.DS_Store
|
||||
|
||||
10
Dockerfile
10
Dockerfile
@@ -1,16 +1,14 @@
|
||||
FROM node:18
|
||||
LABEL org.opencontainers.image.source https://github.com/KevinMidboe/figlet-http
|
||||
|
||||
RUN mkdir -p /opt/figlet-http/src
|
||||
RUN mkdir -p /opt/figlet-http/lib
|
||||
|
||||
WORKDIR /opt/figlet-http
|
||||
|
||||
COPY src/ src
|
||||
COPY package.json .
|
||||
COPY yarn.lock .
|
||||
COPY lib/ lib
|
||||
|
||||
RUN yarn
|
||||
RUN yarn install --production
|
||||
|
||||
EXPOSE 3000
|
||||
|
||||
CMD ["yarn", "start"]
|
||||
CMD ["node", "lib/app.js"]
|
||||
|
||||
54
README.md
54
README.md
@@ -20,10 +20,23 @@ yarn start
|
||||
|
||||
## Examples
|
||||
|
||||
Generate Larry 3D ascii response from text:
|
||||
Generate Larry 3D figlet response from text:
|
||||
|
||||
```bash
|
||||
curl localhost:3000/ascii\?text\=ragnhild
|
||||
curl localhost:3000/figlet\?text\=ragnhild
|
||||
```
|
||||
|
||||
Outputs:
|
||||
```
|
||||
__ ___ __
|
||||
/\ \ __ /\_ \ /\ \
|
||||
_ __ __ __ ___\ \ \___ /\_\\//\ \ \_\ \
|
||||
/\`'__\/'__`\ /'_ `\ /' _ `\ \ _ `\/\ \ \ \ \ /'_` \
|
||||
\ \ \//\ \L\.\_/\ \L\ \/\ \/\ \ \ \ \ \ \ \ \_\ \_/\ \L\ \
|
||||
\ \_\\ \__/.\_\ \____ \ \_\ \_\ \_\ \_\ \_\/\____\ \___,_\
|
||||
\/_/ \/__/\/_/\/___L\ \/_/\/_/\/_/\/_/\/_/\/____/\/__,_ /
|
||||
/\____/
|
||||
\_/__/
|
||||
```
|
||||
|
||||
Generate motd executable with text and save to file:
|
||||
@@ -32,35 +45,60 @@ Generate motd executable with text and save to file:
|
||||
curl localhost:3000/motd\?text\=ragnhild > 20-hostname
|
||||
```
|
||||
|
||||
### Options
|
||||
Api endpoints `/text` & `/motd` have the following query options:
|
||||
|
||||
| query param | type | description | required |
|
||||
|-------------|--------|--------------------------------------|----------|
|
||||
| text | string | text to generate | yes |
|
||||
| font | string | select font, get list from /fonts | no |
|
||||
| width | number | max character width before linebreak | no |
|
||||
|
||||
## Docker install
|
||||
|
||||
Run as a docker container using:
|
||||
Run as a docker container from github container registry:
|
||||
|
||||
```bash
|
||||
docker run -d \
|
||||
sudo docker run -d \
|
||||
--name figlet-http \
|
||||
-p 3000:3000 \
|
||||
ghcr.io/kevinmidboe/figlet-http
|
||||
```
|
||||
|
||||
Run as docker locally:
|
||||
```bash
|
||||
yarn build; \
|
||||
sudo docker build -t figlet-http .; \
|
||||
sudo docker run -d \
|
||||
--name figlet-http \
|
||||
-p 3000:3000 \
|
||||
figlet-http
|
||||
```
|
||||
|
||||
|
||||
## Systemd service
|
||||
|
||||
Example systemd config for running http server.
|
||||
|
||||
Clone repo to: `/opt/figlet-http`
|
||||
|
||||
|
||||
`/etc/systemd/system/figlet-http.service`
|
||||
|
||||
```
|
||||
[Unit]
|
||||
Description=Http server for running homename-larry
|
||||
Description=Http server for running figlet-http
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/opt/hostname-larry
|
||||
ExecStart=/usr/bin/node index.js
|
||||
WorkingDirectory=/opt/figlet-http
|
||||
ExecStart=/usr/bin/yarn start
|
||||
Restart=always
|
||||
# Restart service after 10 seconds if node service crashes
|
||||
RestartSec=10
|
||||
# Output to syslog
|
||||
StandardOutput=syslog
|
||||
StandardError=syslog
|
||||
SyslogIdentifier=hostname-larry
|
||||
SyslogIdentifier=figlet-http
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
|
||||
14
package.json
14
package.json
@@ -1,9 +1,21 @@
|
||||
{
|
||||
"version": "1.0.0",
|
||||
"scripts": {
|
||||
"start": "node src/app.js"
|
||||
"build": "tsc",
|
||||
"start": "node lib/app.js",
|
||||
"lint": "eslint src"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"figlet": "^1.5.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.2",
|
||||
"@typescript-eslint/eslint-plugin": "^5.40.1",
|
||||
"@typescript-eslint/parser": "^5.40.1",
|
||||
"eslint": "^8.25.0",
|
||||
"eslint-config-airbnb-base": "^15.0.0",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"typescript": "^4.8.4"
|
||||
}
|
||||
}
|
||||
|
||||
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}.`); // eslint-disable-line no-console
|
||||
});
|
||||
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;
|
||||
if (font) options = { ...options, font };
|
||||
if (width) options = { ...options, width };
|
||||
|
||||
figletFonts.generateText(text, options)
|
||||
.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;
|
||||
12
src/controllers/fontsController.ts
Normal file
12
src/controllers/fontsController.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import FigletFonts from '../figletFonts';
|
||||
|
||||
const figletFonts = new FigletFonts();
|
||||
|
||||
const fontsController = (req, res) => 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;
|
||||
if (font) options = { ...options, font };
|
||||
if (width) options = { ...options, width };
|
||||
|
||||
figletFonts.generateText(text, options)
|
||||
.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); // eslint-disable-line no-console
|
||||
console.log(error); // eslint-disable-line no-console
|
||||
}
|
||||
}
|
||||
|
||||
export {
|
||||
MissingTextError,
|
||||
FontNotFoundError,
|
||||
UnexpectedFigletError,
|
||||
};
|
||||
58
src/figletFonts.ts
Normal file
58
src/figletFonts.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
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));
|
||||
}
|
||||
|
||||
return 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));
|
||||
}
|
||||
|
||||
return resolve(fonts);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default FigletFonts;
|
||||
9
src/interfaces/IFigletOptions.ts
Normal file
9
src/interfaces/IFigletOptions.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
interface IFigletOptions {
|
||||
font: string;
|
||||
width: number;
|
||||
horizontalLayout?: string;
|
||||
verticalLayout?: string;
|
||||
whitespaceBreak?: boolean;
|
||||
}
|
||||
|
||||
export default IFigletOptions;
|
||||
@@ -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);
|
||||
|
||||
return 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