Compare commits
58 Commits
999d493c68
...
master
Author | SHA1 | Date | |
---|---|---|---|
5d9f4754a5 | |||
2cac260954 | |||
d298b8e842 | |||
bd63607477 | |||
55d461fb8e | |||
895893e76a | |||
39fbc73c74 | |||
e1c26e2c5e | |||
d3a93108e2 | |||
7a79aca59b | |||
ffb086a774 | |||
d1499bb6d4 | |||
a6d4a35588 | |||
2e4bb21354 | |||
3ed5253fc4 | |||
274f2b4b7a | |||
6eae1742d6 | |||
cc59df51d0 | |||
2a950310be | |||
9b4ad340a2 | |||
dfc0bc3b7e | |||
c2ea65a992 | |||
3de5485650 | |||
dfd3df544c | |||
7fe6dee81c | |||
1d7791c2b0 | |||
3c8dcf3ad2 | |||
ab70c46501 | |||
3af8b97258 | |||
431e21806b | |||
1541018701 | |||
0eea261e56 | |||
00546001eb | |||
db3ae308e6 | |||
20394e65f8 | |||
7b9ce83c71 | |||
fccd69db74 | |||
95814c5541 | |||
27cec2cb0b | |||
3716e80674 | |||
3b9e3d416a | |||
8b8fbb3ec5 | |||
374940757a | |||
228c204255 | |||
a1715aa117 | |||
c053340e5e | |||
a208a2fb50 | |||
52597ca46f | |||
efa44f0405 | |||
2dc969a47d | |||
bc50897afc | |||
64ec1b5db5 | |||
1a98e86954 | |||
ce4e78e0d5 | |||
bda39f279f | |||
111df45892 | |||
bff13c3364 | |||
6f242fb79f |
@ -2,7 +2,7 @@
|
|||||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||||
* This file is part of Requestor which is released under BSD-3-Clause.
|
* This file is part of Requestor which is released under BSD-3-Clause.
|
||||||
* See file 'LICENSE' for full license details.
|
* See file 'LICENSE' for full license details.
|
||||||
* Created by Timo Hocker <timo@scode.ovh>, March 2020
|
* Created by Timo Hocker <timo@sapphirecode.ovh>, March 2020
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -12,6 +12,6 @@ module.exports = {
|
|||||||
node: true
|
node: true
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'@scode'
|
'@sapphirecode'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -1 +0,0 @@
|
|||||||
/test/
|
|
2
Jenkinsfile
vendored
2
Jenkinsfile
vendored
@ -5,7 +5,7 @@ pipeline {
|
|||||||
VERSION = VersionNumber([
|
VERSION = VersionNumber([
|
||||||
versionNumberString:
|
versionNumberString:
|
||||||
'${BUILDS_ALL_TIME}',
|
'${BUILDS_ALL_TIME}',
|
||||||
versionPrefix: '1.1.',
|
versionPrefix: '2.0.',
|
||||||
worstResultForIncrement: 'SUCCESS'
|
worstResultForIncrement: 'SUCCESS'
|
||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
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);
|
|
||||||
}
|
|
||||||
};
|
|
39
jenkins.js
39
jenkins.js
@ -1,33 +1,22 @@
|
|||||||
/*
|
|
||||||
* 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-process-exit */
|
|
||||||
/* eslint-disable no-console */
|
|
||||||
/* eslint-disable no-sync */
|
|
||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
|
const https = require ('https');
|
||||||
const fs = require ('fs');
|
const fs = require ('fs');
|
||||||
const child_process = require ('child_process');
|
const { execSync: exec_sync } = require ('child_process');
|
||||||
|
|
||||||
const pkg = JSON.parse (fs.readFileSync ('package.json', 'utf-8'));
|
const run_file = fs.createWriteStream ('.jenkins.run.js');
|
||||||
[
|
|
||||||
,, pkg.version
|
const [
|
||||||
|
,, ...args
|
||||||
] = process.argv;
|
] = process.argv;
|
||||||
fs.writeFileSync ('package.json', JSON.stringify (pkg, null, 2));
|
|
||||||
|
|
||||||
child_process.execSync ('yarn lint', { stdio: 'inherit' });
|
run_file.on ('close', () => {
|
||||||
if (typeof pkg.scripts !== 'undefined' && typeof pkg.scripts.test === 'string')
|
exec_sync (`node .jenkins.run.js ${args.join (' ')}`, { stdio: 'inherit' });
|
||||||
child_process.execSync ('yarn test', { stdio: 'inherit' });
|
});
|
||||||
|
|
||||||
child_process.exec ('git log -1 | grep \'\\[no publish\\]\'')
|
https.get (
|
||||||
.addListener ('exit', (code) => {
|
'https://git.scode.ovh/Timo/standard/raw/branch/master/jenkins.run.js',
|
||||||
if (code === 0) {
|
(msg) => {
|
||||||
console.log ('build not marked for deployment');
|
msg.pipe (run_file);
|
||||||
process.exit (1);
|
|
||||||
}
|
}
|
||||||
else { child_process.execSync ('yarn publish'); }
|
);
|
||||||
});
|
|
||||||
|
@ -2,7 +2,7 @@
|
|||||||
* Copyright (C) Sapphirecode - All Rights Reserved
|
* Copyright (C) Sapphirecode - All Rights Reserved
|
||||||
* This file is part of Requestor which is released under BSD-3-Clause.
|
* This file is part of Requestor which is released under BSD-3-Clause.
|
||||||
* See file 'LICENSE' for full license details.
|
* See file 'LICENSE' for full license details.
|
||||||
* Created by Timo Hocker <timo@scode.ovh>, March 2020
|
* Created by Timo Hocker <timo@sapphirecode.ovh>, March 2020
|
||||||
*/
|
*/
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@ -12,6 +12,6 @@ module.exports = {
|
|||||||
node: true
|
node: true
|
||||||
},
|
},
|
||||||
extends: [
|
extends: [
|
||||||
'@scode/eslint-config-ts'
|
'@sapphirecode/eslint-config-ts'
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
8
lib/CrudHandler.ts
Normal file
8
lib/CrudHandler.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
|
||||||
|
export interface CrudHandler {
|
||||||
|
create(req: Request, res: Response): Promise<void>;
|
||||||
|
read(req: Request, res: Response): Promise<void>;
|
||||||
|
update(req: Request, res: Response): Promise<void>;
|
||||||
|
delete(req: Request, res: Response): Promise<void>;
|
||||||
|
}
|
160
lib/DatabaseCrudHandler.ts
Normal file
160
lib/DatabaseCrudHandler.ts
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
import { Request, Response, Router } from 'express';
|
||||||
|
import { http } from '@sapphirecode/consts';
|
||||||
|
import { ControlModel, DatabaseModel } from '@sapphirecode/modelling';
|
||||||
|
import { CrudHandler } from './CrudHandler';
|
||||||
|
import { HttpHandler } from './HttpHandler';
|
||||||
|
import { DatabaseCrudOptionsReader } from './DatabaseCrudOptionsReader';
|
||||||
|
import { DatabaseCrudOptions } from './DatabaseCrudOptions';
|
||||||
|
|
||||||
|
export class DatabaseCrudHandler extends HttpHandler implements CrudHandler {
|
||||||
|
protected cm:
|
||||||
|
new (object: Record<string, string|number|boolean>) => ControlModel;
|
||||||
|
|
||||||
|
protected dm: new (id?: number) => DatabaseModel;
|
||||||
|
protected options: DatabaseCrudOptionsReader;
|
||||||
|
|
||||||
|
public constructor (
|
||||||
|
cm: new (object: Record<string, string|number|boolean>) => ControlModel,
|
||||||
|
dm: new (id?: number) => DatabaseModel,
|
||||||
|
options: DatabaseCrudOptions = {}
|
||||||
|
) {
|
||||||
|
super ();
|
||||||
|
this.cm = cm;
|
||||||
|
this.dm = dm;
|
||||||
|
this.options = new DatabaseCrudOptionsReader (options);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected validate_body (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<Record<string, unknown> | null> | Record<string, unknown> | null {
|
||||||
|
if (typeof req.body === 'undefined') {
|
||||||
|
res.status (http.status_bad_request);
|
||||||
|
res.end ('body was undefined');
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
return JSON.parse (req.body);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
res.status (http.status_bad_request);
|
||||||
|
res.end ('invalid json input');
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async create (req: Request, res: Response): Promise<void> {
|
||||||
|
if (!await this.options.create_authorization (req, res))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const body_data = await this.validate_body (req, res);
|
||||||
|
if (body_data === null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const cm = new this.cm (body_data as Record<string|string, number|boolean>);
|
||||||
|
cm.update ();
|
||||||
|
|
||||||
|
const dm = new this.dm;
|
||||||
|
dm.assign (cm);
|
||||||
|
await dm.write ();
|
||||||
|
|
||||||
|
res.status (http.status_created)
|
||||||
|
.end (dm.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async read (req: Request, res: Response): Promise<void> {
|
||||||
|
if (!await this.options.read_authorization (req, res))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (typeof req.headers.id === 'undefined') {
|
||||||
|
res.status (http.status_bad_request)
|
||||||
|
.end ('id undefined');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dm = new this.dm (parseInt (req.headers.id as string));
|
||||||
|
const found = await dm.read ();
|
||||||
|
|
||||||
|
const cm = new this.cm (dm.get_data ());
|
||||||
|
cm.update ();
|
||||||
|
|
||||||
|
res.status (found ? http.status_ok : http.status_not_found)
|
||||||
|
.json (cm.get_data ());
|
||||||
|
}
|
||||||
|
|
||||||
|
public async update (req: Request, res: Response): Promise<void> {
|
||||||
|
if (!await this.options.update_authorization (req, res))
|
||||||
|
return;
|
||||||
|
|
||||||
|
const body_data = await this.validate_body (req, res);
|
||||||
|
if (body_data === null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (typeof req.headers.id === 'undefined') {
|
||||||
|
res.status (http.status_bad_request)
|
||||||
|
.end ('id undefined');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dm = new this.dm (parseInt (req.headers.id as string));
|
||||||
|
const found = await dm.read ();
|
||||||
|
if (!found) {
|
||||||
|
res.status (http.status_not_found)
|
||||||
|
.end ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const cm = new this.cm (dm.get_data ());
|
||||||
|
cm.update ();
|
||||||
|
|
||||||
|
cm.assign_object (body_data);
|
||||||
|
cm.update ();
|
||||||
|
|
||||||
|
dm.assign (cm);
|
||||||
|
|
||||||
|
const written = await dm.write ();
|
||||||
|
|
||||||
|
res.status (written ? http.status_ok : http.status_internal_server_error)
|
||||||
|
.end ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public async delete (req: Request, res: Response): Promise<void> {
|
||||||
|
if (!await this.options.delete_authorization (req, res))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (typeof req.headers.id === 'undefined') {
|
||||||
|
res.status (http.status_bad_request)
|
||||||
|
.end ('id undefined');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const dm = new this.dm (parseInt (req.headers.id as string));
|
||||||
|
const found = await dm.read ();
|
||||||
|
if (!found) {
|
||||||
|
res.status (http.status_not_found)
|
||||||
|
.end ();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleted = await dm.delete ();
|
||||||
|
|
||||||
|
res.status (deleted ? http.status_ok : http.status_internal_server_error)
|
||||||
|
.end ();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async create_or_update (
|
||||||
|
req: Request,
|
||||||
|
res: Response
|
||||||
|
): Promise<void> {
|
||||||
|
if (typeof req.headers.id === 'undefined')
|
||||||
|
await this.create (req, res);
|
||||||
|
else
|
||||||
|
await this.update (req, res);
|
||||||
|
}
|
||||||
|
|
||||||
|
public register_handlers (router: Router): void {
|
||||||
|
router.post ('/', this.create_or_update);
|
||||||
|
router.get ('/', this.read);
|
||||||
|
router.delete ('/', this.delete);
|
||||||
|
}
|
||||||
|
}
|
17
lib/DatabaseCrudOptions.ts
Normal file
17
lib/DatabaseCrudOptions.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
|
||||||
|
type Authorization = {
|
||||||
|
(req: Request, res: Response): Promise<boolean>;
|
||||||
|
(req: Request, res: Response, next: Function): unknown;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DatabaseCrudOptions {
|
||||||
|
general_authorization?: Authorization;
|
||||||
|
create_authorization?: Authorization;
|
||||||
|
read_authorization?: Authorization;
|
||||||
|
update_authorization?: Authorization;
|
||||||
|
delete_authorization?: Authorization;
|
||||||
|
optional_columns?: Array<string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Authorization, DatabaseCrudOptions };
|
74
lib/DatabaseCrudOptionsReader.ts
Normal file
74
lib/DatabaseCrudOptionsReader.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import { Request, Response } from 'express';
|
||||||
|
import { DatabaseCrudOptions, Authorization } from './DatabaseCrudOptions';
|
||||||
|
|
||||||
|
type AuthRunner = {
|
||||||
|
(req: Request, res: Response): Promise<boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DatabaseCrudOptionsReader {
|
||||||
|
private _options: DatabaseCrudOptions;
|
||||||
|
|
||||||
|
public constructor (options: DatabaseCrudOptions) {
|
||||||
|
this._options = options;
|
||||||
|
}
|
||||||
|
|
||||||
|
private get_auth_runner (
|
||||||
|
auth?: Authorization
|
||||||
|
): AuthRunner {
|
||||||
|
if (typeof auth === 'undefined')
|
||||||
|
return (): Promise<boolean> => new Promise ((r) => r (true));
|
||||||
|
return (req, res): Promise<boolean> => new Promise (
|
||||||
|
(resolve: (value: boolean) => void) => {
|
||||||
|
(async (): Promise<void> => {
|
||||||
|
let resolved = false;
|
||||||
|
const result = await auth (req, res, (cb: unknown) => {
|
||||||
|
resolved = true;
|
||||||
|
resolve (typeof cb === 'undefined' || cb === true);
|
||||||
|
});
|
||||||
|
if (!resolved)
|
||||||
|
resolve (result === true);
|
||||||
|
}) ();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public get optional_columns (): Array<string> | undefined {
|
||||||
|
return this._options.optional_columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
public get create_authorization (): Authorization {
|
||||||
|
const general = this.get_auth_runner (this._options.general_authorization);
|
||||||
|
const specific = this.get_auth_runner (this._options.create_authorization);
|
||||||
|
return async (req: Request, res: Response): Promise<boolean> => {
|
||||||
|
const result = (await general (req, res)) && (await specific (req, res));
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public get read_authorization (): Authorization {
|
||||||
|
const general = this.get_auth_runner (this._options.general_authorization);
|
||||||
|
const specific = this.get_auth_runner (this._options.read_authorization);
|
||||||
|
return async (req: Request, res: Response): Promise<boolean> => {
|
||||||
|
const result = (await general (req, res)) && (await specific (req, res));
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public get update_authorization (): Authorization {
|
||||||
|
const general = this.get_auth_runner (this._options.general_authorization);
|
||||||
|
const specific = this.get_auth_runner (this._options.update_authorization);
|
||||||
|
return async (req: Request, res: Response): Promise<boolean> => {
|
||||||
|
const result = (await general (req, res)) && (await specific (req, res));
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public get delete_authorization (): Authorization {
|
||||||
|
const general = this.get_auth_runner (this._options.general_authorization);
|
||||||
|
const specific = this.get_auth_runner (this._options.delete_authorization);
|
||||||
|
return async (req: Request, res: Response): Promise<boolean> => {
|
||||||
|
const result = (await general (req, res)) && (await specific (req, res));
|
||||||
|
return result;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
11
lib/HttpHandler.ts
Normal file
11
lib/HttpHandler.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import { Router } from 'express';
|
||||||
|
|
||||||
|
export abstract class HttpHandler {
|
||||||
|
public abstract register_handlers(router: Router): void;
|
||||||
|
|
||||||
|
public get_router (): Router {
|
||||||
|
const r = Router ();
|
||||||
|
this.register_handlers (r);
|
||||||
|
return r;
|
||||||
|
}
|
||||||
|
}
|
@ -1,41 +0,0 @@
|
|||||||
import { Request, Response } from '@types/express';
|
|
||||||
import Transaction from './Transaction';
|
|
||||||
|
|
||||||
export default abstract class Handler {
|
|
||||||
private _handlers: array<Function> = [];
|
|
||||||
private _method_handlers: Record<string, Function> = {};
|
|
||||||
|
|
||||||
protected register_handler (f: Function): void {
|
|
||||||
this._handlers.push (f);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected register_handler (method: string, f: Function): void {
|
|
||||||
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): void {
|
|
||||||
const m = method.toUpperCase ();
|
|
||||||
if (this._method_handlers[m] !== 'undefined')
|
|
||||||
await this._method_handlers[m] (t);
|
|
||||||
}
|
|
||||||
|
|
||||||
public async run_http_handler (req: Request, res: Response): 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.status.has_status)
|
|
||||||
t.status.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,47 +0,0 @@
|
|||||||
import http from '@scode/consts';
|
|
||||||
|
|
||||||
export default class Status {
|
|
||||||
private _status = -1;
|
|
||||||
|
|
||||||
public get status (): number {
|
|
||||||
if (this._status === -1)
|
|
||||||
throw new Error ('status undefined');
|
|
||||||
return this._status;
|
|
||||||
}
|
|
||||||
|
|
||||||
public set status (value: number) {
|
|
||||||
this._status = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public get has_status (): boolean {
|
|
||||||
return this._status !== -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Status setters
|
|
||||||
*/
|
|
||||||
|
|
||||||
public ok (): void {
|
|
||||||
this._status = http.status_ok;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ok_no_content (): void {
|
|
||||||
this._status = http.status_ok_no_content;
|
|
||||||
}
|
|
||||||
|
|
||||||
public bad_request (): void {
|
|
||||||
this._status = http.status_bad_request;
|
|
||||||
}
|
|
||||||
|
|
||||||
public unauthorized (): void {
|
|
||||||
this._status = http.status_unauthorized;
|
|
||||||
}
|
|
||||||
|
|
||||||
public forbidden (): void {
|
|
||||||
this._status = http.status_forbidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
public not_found (): void {
|
|
||||||
this._status = http.status_not_found;
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,30 +0,0 @@
|
|||||||
import { Request, Response } from 'express';
|
|
||||||
import Status from './Status';
|
|
||||||
import Session from './Session';
|
|
||||||
|
|
||||||
export default class Transaction {
|
|
||||||
/* private */
|
|
||||||
private _req: Request;
|
|
||||||
private _res: Response;
|
|
||||||
private _status: Status;
|
|
||||||
|
|
||||||
/* public */
|
|
||||||
public get req (): Request { return this._req; }
|
|
||||||
public get res (): Response { return this._res; }
|
|
||||||
public get status (): Status { return this._status; }
|
|
||||||
public session?: Session;
|
|
||||||
|
|
||||||
/* constructor */
|
|
||||||
public constructor (req: Request, res: Response) {
|
|
||||||
this._req = req;
|
|
||||||
this._res = res;
|
|
||||||
this._status = new Status;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* methods */
|
|
||||||
public finalize (data: any): void {
|
|
||||||
if (this._status.has_status)
|
|
||||||
this._res.status (this.status.status);
|
|
||||||
this._res.end (data);
|
|
||||||
}
|
|
||||||
}
|
|
24
lib/index.ts
24
lib/index.ts
@ -1,21 +1,3 @@
|
|||||||
import { App } from 'express';
|
export { HttpHandler } from './HttpHandler';
|
||||||
import Handler from './classes/Handler';
|
export { CrudHandler } from './CrudHandler';
|
||||||
|
export { DatabaseCrudHandler } from './DatabaseCrudHandler';
|
||||||
/**
|
|
||||||
* register an array of handlers to an express app
|
|
||||||
*
|
|
||||||
* @param {App} app express app
|
|
||||||
* @param {Array<Handler>} handlers handlers to register
|
|
||||||
*/
|
|
||||||
export default function load_handlers (
|
|
||||||
app: App,
|
|
||||||
handlers: Array<Handler>
|
|
||||||
): void {
|
|
||||||
for (const h of handlers)
|
|
||||||
app.use (h.path, h.run_http_handler);
|
|
||||||
}
|
|
||||||
|
|
||||||
export * from './classes/Session';
|
|
||||||
export * from './classes/Status';
|
|
||||||
export * from './classes/Transaction';
|
|
||||||
export * from './classes/Handler';
|
|
||||||
|
28
package.json
28
package.json
@ -1,13 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "@scode/requestor",
|
"name": "@sapphirecode/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\"",
|
||||||
|
"compile": "tsc",
|
||||||
"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",
|
||||||
@ -23,16 +23,18 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@ava/typescript": "^1.1.1",
|
"@ava/typescript": "^1.1.1",
|
||||||
"@scode/eslint-config-ts": "^1.0.12",
|
"@sapphirecode/eslint-config-ts": "^1.0.29",
|
||||||
"@stryker-mutator/core": "^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.0.15",
|
"@sapphirecode/consts": "^1.1.9",
|
||||||
"@types/express": "^4.17.4"
|
"@sapphirecode/modelling": "^1.0.19",
|
||||||
}
|
"@types/express": "^4.17.6",
|
||||||
|
"express": "^4.17.1"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"LICENSE",
|
||||||
|
"/dist/"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
@ -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'
|
|
||||||
]
|
|
||||||
}
|
|
@ -1,5 +0,0 @@
|
|||||||
import test from 'ava';
|
|
||||||
|
|
||||||
test ('testing', (t) => {
|
|
||||||
t.is (true, true);
|
|
||||||
});
|
|
@ -8,7 +8,7 @@
|
|||||||
// "allowJs": true, /* Allow javascript files to be compiled. */
|
// "allowJs": true, /* Allow javascript files to be compiled. */
|
||||||
// "checkJs": true, /* Report errors in .js files. */
|
// "checkJs": true, /* Report errors in .js files. */
|
||||||
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
|
||||||
// "declaration": true, /* Generates corresponding '.d.ts' file. */
|
"declaration": true, /* Generates corresponding '.d.ts' file. */
|
||||||
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
|
||||||
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
// "sourceMap": true, /* Generates corresponding '.map' file. */
|
||||||
// "outFile": "./", /* Concatenate and emit output to single file. */
|
// "outFile": "./", /* Concatenate and emit output to single file. */
|
||||||
|
1922
yarn-error.log
Normal file
1922
yarn-error.log
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user