handler templates only
This commit is contained in:
		| @@ -1 +0,0 @@ | |||||||
| /test/ |  | ||||||
							
								
								
									
										121
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										121
									
								
								dist/index.js
									
									
									
									
										vendored
									
									
								
							| @@ -1,121 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright (C) Sapphirecode - All Rights Reserved |  | ||||||
|  * This file is part of Requestor which is released under BSD-3-Clause. |  | ||||||
|  * See file 'LICENSE' for full license details. |  | ||||||
|  * Created by Timo Hocker <timo@scode.ovh>, March 2020 |  | ||||||
|  */ |  | ||||||
| /* eslint-disable no-console */ |  | ||||||
| /* eslint-disable no-sync */ |  | ||||||
| 'use strict'; |  | ||||||
| const fs = require ('fs'); |  | ||||||
| const path = require ('path'); |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @typedef {object} options |  | ||||||
|  * @property {any} [opts] object to pass to the handlers |  | ||||||
|  * @property {string} [subdir] subdirectory for all requests |  | ||||||
|  * @property {boolean} [verbose] enable verbose logging |  | ||||||
|  * @property {boolean} [rethrow] rethrow errors (default: true) |  | ||||||
|  */ |  | ||||||
| /** |  | ||||||
|  * @typedef {object} handler_description |  | ||||||
|  * @property {string} module_folder folder the module file is in |  | ||||||
|  * @property {string} file name of the module |  | ||||||
|  * @property {any} opts optional arguments |  | ||||||
|  * @property {boolean} rethrow should errors be rethrown |  | ||||||
|  */ |  | ||||||
| /** |  | ||||||
|  * wrap a requestor handler to be compatible with express |  | ||||||
|  * |  | ||||||
|  * @param _a |  | ||||||
|  * @param {handler_description} data handler data |  | ||||||
|  * @returns {Function} requestor handler |  | ||||||
|  */ |  | ||||||
| function get_handler (_a) { |  | ||||||
|   const { module_folder } = _a; |  | ||||||
|   const { file } = _a; |  | ||||||
|   const { opts } = _a; |  | ||||||
|   const { rethrow } = _a; |  | ||||||
|   // eslint-disable-next-line global-require |  | ||||||
|   const handler = require (path.join (process.cwd (), module_folder, file)); |  | ||||||
|   return function (req, res, next) { |  | ||||||
|     return new Promise ((resolve) => resolve (handler (req, res, next, opts))) |  | ||||||
|       .catch ((e) => { |  | ||||||
|         if (rethrow) |  | ||||||
|           throw e; |  | ||||||
|       }); |  | ||||||
|   }; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * register a handler to the given app |  | ||||||
|  * |  | ||||||
|  * @param {any} app express app |  | ||||||
|  * @param {handler_description} handler_description data for the used handler |  | ||||||
|  * @param {string} method method to respond to |  | ||||||
|  * @param {string} url url to respond to |  | ||||||
|  * @param {boolean} verbose should verbose logging be enabled |  | ||||||
|  */ |  | ||||||
| function register_handler (app, handler_description, method, url, verbose) { |  | ||||||
|   const handler = get_handler (handler_description); |  | ||||||
|   if (verbose) |  | ||||||
|     console.log (`[requestor info] redirecting ${url} to ${handler_description.file}`); |  | ||||||
|  |  | ||||||
|   switch (method) { |  | ||||||
|     case 'post': |  | ||||||
|       app.post (url, handler); |  | ||||||
|       break; |  | ||||||
|     case 'get': |  | ||||||
|       app.get (url, handler); |  | ||||||
|       break; |  | ||||||
|     case 'put': |  | ||||||
|       app.put (url, handler); |  | ||||||
|       break; |  | ||||||
|     case 'delete': |  | ||||||
|       app.delete (url, handler); |  | ||||||
|       break; |  | ||||||
|     case 'all': |  | ||||||
|       app.all (url, handler); |  | ||||||
|       break; |  | ||||||
|     default: |  | ||||||
|       if (verbose) |  | ||||||
|         console.warn (`'${method}' did not match any request method, ignoring`); |  | ||||||
|  |  | ||||||
|       break; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Load all request handlers in the given folder |  | ||||||
|  * |  | ||||||
|  * @param {any} app express app |  | ||||||
|  * @param {string} module_folder folder that contains all modules |  | ||||||
|  * @param {options} options additional options |  | ||||||
|  */ |  | ||||||
| module.exports = function main (app, module_folder, options) { |  | ||||||
|   if (options === void 0) |  | ||||||
|     options = { opts: null, subdir: '', verbose: false, rethrow: true }; |  | ||||||
|   const { opts } = options; |  | ||||||
|   const { subdir } = options; |  | ||||||
|   const { verbose } = options; |  | ||||||
|   const { rethrow } = options; |  | ||||||
|   for (let _i = 0, _a = fs.readdirSync (module_folder); _i < _a.length; _i++) { |  | ||||||
|     const file = _a[_i]; |  | ||||||
|     const regex = /(?<method>.*?)-(?<url>.*?)\.js/u; |  | ||||||
|     const { groups } = regex.exec (file); |  | ||||||
|     if (typeof subdir === 'undefined') |  | ||||||
|       groups.url = `/${groups.url}/`; |  | ||||||
|     else |  | ||||||
|       groups.url = `/${subdir}/${groups.url}/`; |  | ||||||
|     groups.url = groups.url |  | ||||||
|       .replace (/^\/[^/]*\/root/iu, '/') |  | ||||||
|       .replace (/\./gu, '/') |  | ||||||
|       .replace (/\/+/gu, '/'); |  | ||||||
|     register_handler (app, { |  | ||||||
|       file, |  | ||||||
|       module_folder, |  | ||||||
|       opts, |  | ||||||
|       rethrow |  | ||||||
|     }, groups.method, groups.url, verbose); |  | ||||||
|   } |  | ||||||
| }; |  | ||||||
							
								
								
									
										6
									
								
								lib/CrudHandler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								lib/CrudHandler.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,6 @@ | |||||||
|  | export interface CrudHandler { | ||||||
|  |   public create(req: Request, res: Response): Promise<void>; | ||||||
|  |   public read(req: Request, res: Response): Promise<void>; | ||||||
|  |   public update(req: Request, res: Response): Promise<void>; | ||||||
|  |   public delete(req: Request, res: Response): Promise<void>; | ||||||
|  | } | ||||||
							
								
								
									
										19
									
								
								lib/KnexCrudHandler.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								lib/KnexCrudHandler.ts
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | import { CrudHandler } from './CrudHandler'; | ||||||
|  |  | ||||||
|  | export class KnexCrudHandler implements CrudHandler { | ||||||
|  |   private _table: string; | ||||||
|  |   private _columns: Array<string>; | ||||||
|  |   private _options: Record<string, unknown>; | ||||||
|  |  | ||||||
|  |   public get table (): string { return this._table; } | ||||||
|  |  | ||||||
|  |   public constructor ( | ||||||
|  |     table: string, | ||||||
|  |     columns: Array<string>, | ||||||
|  |     options: Record<string, unknown> = {} | ||||||
|  |   ) { | ||||||
|  |     this.table = table; | ||||||
|  |     this.columns = columns; | ||||||
|  |     this.options = options; | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,46 +0,0 @@ | |||||||
| import { Request, Response } from 'express'; |  | ||||||
| import { http } from '@scode/consts'; |  | ||||||
| import Transaction from './Transaction'; |  | ||||||
|  |  | ||||||
| export default abstract class Handler { |  | ||||||
|   private _handlers: Array<Function> = []; |  | ||||||
|   private _method_handlers: Record<string, Function> = {}; |  | ||||||
|  |  | ||||||
|   protected register_handler (f: Function, method?: string): void { |  | ||||||
|     if (typeof method === 'undefined') { |  | ||||||
|       this._handlers.push (f); |  | ||||||
|     } |  | ||||||
|     else { |  | ||||||
|       const m = method.toUpperCase (); |  | ||||||
|       if (typeof this._method_handlers[m] !== 'undefined') |  | ||||||
|         throw new Error (`Handler for ${m} already registered`); |  | ||||||
|       this._method_handlers[m] = f; |  | ||||||
|     } |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private async run_method_handler ( |  | ||||||
|     method: string, |  | ||||||
|     t: Transaction |  | ||||||
|   ): Promise<void> { |  | ||||||
|     const m = method.toUpperCase (); |  | ||||||
|     if (typeof this._method_handlers[m] !== 'undefined') |  | ||||||
|       await this._method_handlers[m] (t); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public async run_http_handler (req: Request, res: Response): Promise<void> { |  | ||||||
|     const t = new Transaction (req, res); |  | ||||||
|     for (const handler of this._handlers) { |  | ||||||
|       // eslint-disable-next-line no-await-in-loop |  | ||||||
|       if (await handler (t) === false) { |  | ||||||
|         if (!t.has_status) |  | ||||||
|           t.status = http.status_internal_server_error; |  | ||||||
|         t.finalize (); |  | ||||||
|         return; |  | ||||||
|       } |  | ||||||
|     } |  | ||||||
|     await this.run_method_handler ('ALL', t); |  | ||||||
|     await this.run_method_handler (t.req.method, t); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public abstract get path(): string; |  | ||||||
| } |  | ||||||
| @@ -1,5 +0,0 @@ | |||||||
| export default class Session { |  | ||||||
|   public user_id?: number; |  | ||||||
|   public user_name?: string; |  | ||||||
|   public permissions?: Array<string>; |  | ||||||
| } |  | ||||||
| @@ -1,33 +0,0 @@ | |||||||
| import { Request, Response } from 'express'; |  | ||||||
| import { http } from '@scode/consts'; |  | ||||||
| import Session from './Session'; |  | ||||||
|  |  | ||||||
| export default class Transaction { |  | ||||||
|   /* private */ |  | ||||||
|   private _req: Request; |  | ||||||
|   private _res: Response; |  | ||||||
|  |  | ||||||
|   /* public */ |  | ||||||
|   public status = -1; |  | ||||||
|   public session?: Session; |  | ||||||
|  |  | ||||||
|   public get req (): Request { return this._req; } |  | ||||||
|   public get res (): Response { return this._res; } |  | ||||||
|   public get has_status (): boolean { |  | ||||||
|     return Object.values (http.status_codes) |  | ||||||
|       .includes (this.status); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /* constructor */ |  | ||||||
|   public constructor (req: Request, res: Response) { |  | ||||||
|     this._req = req; |  | ||||||
|     this._res = res; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   /* methods */ |  | ||||||
|   public finalize (data?: any): void { |  | ||||||
|     if (this.has_status) |  | ||||||
|       this._res.status (this.status); |  | ||||||
|     this._res.end (data); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
							
								
								
									
										20
									
								
								lib/index.ts
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								lib/index.ts
									
									
									
									
									
								
							| @@ -1,20 +0,0 @@ | |||||||
| import { Application } from 'express'; |  | ||||||
| import Handler from './classes/Handler'; |  | ||||||
| import Session from './classes/Session'; |  | ||||||
| import Transaction from './classes/Transaction'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * register an array of handlers to an express app |  | ||||||
|  * |  | ||||||
|  * @param {Application} app express app |  | ||||||
|  * @param {Array<Handler>} handlers handlers to register |  | ||||||
|  */ |  | ||||||
| export default function load_handlers ( |  | ||||||
|   app: Application, |  | ||||||
|   handlers: Array<Handler> |  | ||||||
| ): void { |  | ||||||
|   for (const h of handlers) |  | ||||||
|     app.use (h.path, h.run_http_handler); |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export { Handler, Session, Transaction, load_handlers }; |  | ||||||
							
								
								
									
										16
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								package.json
									
									
									
									
									
								
							| @@ -1,13 +1,12 @@ | |||||||
| { | { | ||||||
|   "name": "@scode/requestor", |   "name": "@scode/requestor", | ||||||
|   "version": "1.0.0", |   "version": "1.0.0", | ||||||
|   "description": "Split express paths into individual files to make api programming more structured", |   "description": "Express handler templates", | ||||||
|   "main": "dist/index.js", |   "main": "dist/index.js", | ||||||
|   "scripts": { |   "scripts": { | ||||||
|     "test": "nyc ava", |     "test": "echo \"no test\"", | ||||||
|     "lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs", |     "lint": "eslint . --ext .js,.jsx,.ts,.tsx,.vue,.mjs", | ||||||
|     "ci": "yarn --frozen-lockfile && node jenkins.js", |     "ci": "yarn --frozen-lockfile && node jenkins.js" | ||||||
|     "mutate": "stryker run" |  | ||||||
|   }, |   }, | ||||||
|   "repository": { |   "repository": { | ||||||
|     "type": "git", |     "type": "git", | ||||||
| @@ -22,18 +21,15 @@ | |||||||
|     "node": ">=10.0.0" |     "node": ">=10.0.0" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@ava/typescript": "^1.1.1", |     "@scode/eslint-config-ts": "^1.0.19", | ||||||
|     "@scode/eslint-config-ts": "^1.0.16", |  | ||||||
|     "@stryker-mutator/core": "^3.1.0", |     "@stryker-mutator/core": "^3.1.0", | ||||||
|     "@stryker-mutator/javascript-mutator": "^3.1.0", |     "@stryker-mutator/javascript-mutator": "^3.1.0", | ||||||
|     "ava": "^3.6.0", |  | ||||||
|     "eslint": "^6.8.0", |     "eslint": "^6.8.0", | ||||||
|     "nyc": "^15.0.1", |  | ||||||
|     "typescript": "^3.8.3" |     "typescript": "^3.8.3" | ||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@scode/consts": "^1.1.2", |     "@scode/consts": "^1.1.4", | ||||||
|     "@types/express": "^4.17.5", |     "@types/express": "^4.17.6", | ||||||
|     "express": "^4.17.1" |     "express": "^4.17.1" | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,24 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright (C) Sapphirecode - All Rights Reserved |  | ||||||
|  * This file is part of Requestor which is released under BSD-3-Clause. |  | ||||||
|  * See file 'LICENSE' for full license details. |  | ||||||
|  * Created by Timo Hocker <timo@scode.ovh>, March 2020 |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| 'use strict'; |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * @type {import('@stryker-mutator/api/core').StrykerOptions} |  | ||||||
|  */ |  | ||||||
| module.exports = { |  | ||||||
|   mutator:          'javascript', |  | ||||||
|   packageManager:   'yarn', |  | ||||||
|   reporters:        [ |  | ||||||
|     'clear-text', |  | ||||||
|     'progress' |  | ||||||
|   ], |  | ||||||
|   testRunner:       'command', |  | ||||||
|   transpilers:      [], |  | ||||||
|   coverageAnalysis: 'all', |  | ||||||
|   mutate:           [ 'lib/*' ] |  | ||||||
| }; |  | ||||||
| @@ -1,17 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright (C) Sapphirecode - All Rights Reserved |  | ||||||
|  * This file is part of Requestor which is released under BSD-3-Clause. |  | ||||||
|  * See file 'LICENSE' for full license details. |  | ||||||
|  * Created by Timo Hocker <timo@scode.ovh>, March 2020 |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| module.exports = { |  | ||||||
|   env: { |  | ||||||
|     commonjs: true, |  | ||||||
|     es6: true, |  | ||||||
|     node: true |  | ||||||
|   }, |  | ||||||
|   extends: [ |  | ||||||
|     '@scode/eslint-config-ts' |  | ||||||
|   ] |  | ||||||
| } |  | ||||||
							
								
								
									
										47
									
								
								test/main.ts
									
									
									
									
									
								
							
							
						
						
									
										47
									
								
								test/main.ts
									
									
									
									
									
								
							| @@ -1,47 +0,0 @@ | |||||||
| /* |  | ||||||
|  * Copyright (C) Sapphirecode - All Rights Reserved |  | ||||||
|  * This file is part of Requestor which is released under BSD-3-Clause. |  | ||||||
|  * See file 'LICENSE' for full license details. |  | ||||||
|  * Created by Timo Hocker <timo@scode.ovh>, March 2020 |  | ||||||
|  */ |  | ||||||
|  |  | ||||||
| /* eslint-disable max-classes-per-file */ |  | ||||||
|  |  | ||||||
| import test from 'ava'; |  | ||||||
| import { Application } from 'express'; |  | ||||||
| import { http } from '@scode/consts'; |  | ||||||
| import { load_handlers, Handler, Transaction } from '../lib/index'; |  | ||||||
|  |  | ||||||
| class MockServer implements Application { |  | ||||||
|   public registered: Record<string, Function> = {}; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| class MockHandler extends Handler { |  | ||||||
|   public get path (): string { |  | ||||||
|     return '/mock'; |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   private handle_all (t: Transaction): void { |  | ||||||
|     t.status = http.status_im_a_teapot; |  | ||||||
|     t.finalize ('foo'); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   public constructor () { |  | ||||||
|     super (); |  | ||||||
|     this.register_handler (this.handle_all); |  | ||||||
|   } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| test ('detect requests on root', async (t) => { |  | ||||||
|   const server = (new MockServer); |  | ||||||
|   load_handlers (server, [ (new MockHandler) ]); |  | ||||||
|  |  | ||||||
|   const keys = [ 'all-mock' ]; |  | ||||||
|  |  | ||||||
|   t.deepEqual (Object.keys (server.registered), keys); |  | ||||||
|   const res = await Promise.all ( |  | ||||||
|     Object.values (server.registered) |  | ||||||
|       .map ((val) => val ()) |  | ||||||
|   ); |  | ||||||
|   t.is (res.filter ((val) => val === 'foo').length, keys.length); |  | ||||||
| }); |  | ||||||
		Reference in New Issue
	
	Block a user