implementation of knex crud handler
This commit is contained in:
parent
374940757a
commit
8b8fbb3ec5
5
lib/HttpHandler.ts
Normal file
5
lib/HttpHandler.ts
Normal file
@ -0,0 +1,5 @@
|
||||
import { Router } from 'express';
|
||||
|
||||
export class HttpHandler {
|
||||
public abstract register_handlers(router: Router): void;
|
||||
}
|
@ -1,39 +1,30 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Request, Response, Router } from 'express';
|
||||
import { http } from '@scode/consts';
|
||||
import { try_parse_json } from '@scode/utilities';
|
||||
import { KnexCrudOptions } from './KnexCrudOptions';
|
||||
import { CrudHandler } from './CrudHandler';
|
||||
import { Knex } from './KnexInterface';
|
||||
import { HttpHandler } from './HttpHandler';
|
||||
|
||||
export class KnexCrudHandler implements CrudHandler {
|
||||
export class KnexCrudHandler extends HttpHandler implements CrudHandler {
|
||||
protected table: string;
|
||||
protected columns: Array<string>;
|
||||
protected options: KnexCrudOptions;
|
||||
protected knex: Knex;
|
||||
|
||||
public constructor (
|
||||
table: string,
|
||||
columns: Array<string>,
|
||||
options: KnexCrudOptions = {}
|
||||
) {
|
||||
super ();
|
||||
this.table = table;
|
||||
this.columns = columns;
|
||||
this.options = options;
|
||||
if (this.columns.filter ((val) => val.toLowerCase () === 'id').length > 0) {
|
||||
throw new Error (
|
||||
'the column id cannot be made available to modification'
|
||||
);
|
||||
}
|
||||
|
||||
private call_auth (
|
||||
auth?: Function,
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<boolean> {
|
||||
if (typeof auth === 'undefined')
|
||||
return true;
|
||||
|
||||
const promise = new Promise ((resolve) => {
|
||||
const result = auth (req, res, resolve);
|
||||
if (typeof result !== 'undefined')
|
||||
resolve (result);
|
||||
});
|
||||
|
||||
return promise;
|
||||
}
|
||||
|
||||
protected validate_body (
|
||||
@ -42,32 +33,138 @@ export class KnexCrudHandler implements CrudHandler {
|
||||
): Promise<object> | object {
|
||||
if (typeof req.body === 'undefined') {
|
||||
res.status (http.status_bad_request);
|
||||
res.end ();
|
||||
res.end ('body was undefined');
|
||||
return null;
|
||||
}
|
||||
return try_parse_json (req.body);
|
||||
try {
|
||||
return JSON.parse (req.body);
|
||||
}
|
||||
catch (e) {
|
||||
res.status (http.status_bad_request);
|
||||
res.end ('invalid json input');
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
protected ensure_data (
|
||||
data: object,
|
||||
res: Response,
|
||||
fail_on_undef = true
|
||||
): Promise<object>|object {
|
||||
const obj = {};
|
||||
const keys = Object.keys (data);
|
||||
for (const col of this.columns) {
|
||||
if (!keys.includes (col) && fail_on_undef) {
|
||||
res.status (http.status_bad_request)
|
||||
.end (`missing field: ${col}`);
|
||||
return null;
|
||||
}
|
||||
obj[col] = data[col];
|
||||
}
|
||||
for (const col of this.options.optional_columns)
|
||||
obj[col] = data[col];
|
||||
|
||||
return obj;
|
||||
}
|
||||
|
||||
public async create (req: Request, res: Response): Promise<void> {
|
||||
if (!await this.call_auth (this.options.create_authentication, req, res))
|
||||
return;
|
||||
if (!await this.call_auth (this.options.create_authorization, req, res))
|
||||
if (!await this.options.create_authorization (req, res))
|
||||
return;
|
||||
|
||||
const body_data = await this.validate_body (req, res);
|
||||
if (body_data === null)
|
||||
return;
|
||||
|
||||
const db_data = await this.ensure_data (body_data, res);
|
||||
if (db_data === null)
|
||||
return;
|
||||
|
||||
const inserted = await this.knex (this.table)
|
||||
.returning ('id')
|
||||
.insert (db_data);
|
||||
|
||||
res.status (http.status_created)
|
||||
.end (inserted[0]);
|
||||
}
|
||||
|
||||
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 json = await this.knex (this.table)
|
||||
.select ([
|
||||
'id',
|
||||
...this.columns
|
||||
])
|
||||
.where ({ id: req.headers.id });
|
||||
|
||||
res.status (json.length > 0 ? http.status_ok : http.status_not_found)
|
||||
.json (json[0]);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
const db_data = await this.ensure_data (body_data, res, false);
|
||||
if (db_data === null)
|
||||
return;
|
||||
|
||||
if (typeof req.headers.id === 'undefined') {
|
||||
res.status (http.status_bad_request)
|
||||
.end ('id undefined');
|
||||
return;
|
||||
}
|
||||
|
||||
await this.knex (this.table)
|
||||
.where ({ id: req.headers.id })
|
||||
.update (db_data);
|
||||
|
||||
res.status (http.status_ok)
|
||||
.end (inserted[0]);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
await this.knex (this.table)
|
||||
.where ({ id: req.headers.id })
|
||||
.delete ();
|
||||
|
||||
res.status (http.status_ok)
|
||||
.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);
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,88 @@
|
||||
import { Request, Response } from 'express';
|
||||
|
||||
type Authentication = {
|
||||
(req: Request, res: Response, next: Function);
|
||||
(req: Request, res: Response): Promise<boolean>;
|
||||
}
|
||||
|
||||
type Authorization = {
|
||||
(req: Request, res: Response, next: Function);
|
||||
(req: Request, res: Response): Promise<boolean>;
|
||||
}
|
||||
|
||||
export class KnexCrudOptions {
|
||||
public create_authentication?: Authentication;
|
||||
public read_authentication?: Authentication;
|
||||
public update_authentication?: Authentication;
|
||||
public delete_authentication?: Authentication;
|
||||
type AuthRunner = {
|
||||
(req: Request, res: Response): Promise<boolean>;
|
||||
}
|
||||
|
||||
public create_authorization?: Authorization;
|
||||
public read_authorization?: Authorization;
|
||||
public update_authorization?: Authorization;
|
||||
public delete_authorization?: Authorization;
|
||||
export class KnexCrudOptions {
|
||||
private _general_authorization?: Authorization;
|
||||
private _create_authorization?: Authorization;
|
||||
private _read_authorization?: Authorization;
|
||||
private _update_authorization?: Authorization;
|
||||
private _delete_authorization?: Authorization;
|
||||
|
||||
public optional_columns?: Array<string>;
|
||||
|
||||
private get_auth_runner (
|
||||
auth?: Authorization
|
||||
): AuthRunner {
|
||||
if (typeof auth === 'undefined')
|
||||
return (): Promise<boolean> => new Promise ((r) => r (true));
|
||||
return (): Promise<boolean> => new Promise ((resolve) => {
|
||||
const result = auth (req, res, resolve);
|
||||
if (typeof result !== 'undefined')
|
||||
resolve (result);
|
||||
});
|
||||
}
|
||||
|
||||
public set general_authorization (value: Authorization): void{
|
||||
this._general_authorization = value;
|
||||
}
|
||||
|
||||
public get create_authorization (): AuthRunner {
|
||||
const general = this.get_auth_runner (this._general_authorization);
|
||||
const specific = this.get_auth_runner (this._create_authorization);
|
||||
return async (req, res): Promise<boolean> => {
|
||||
const res = (await general (req, res)) && (await specific (req, res));
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
public set create_authorization (value: Authorization): void{
|
||||
this._create_authorization = value;
|
||||
}
|
||||
|
||||
public get read_authorization (): AuthRunner {
|
||||
const general = this.get_auth_runner (this._general_authorization);
|
||||
const specific = this.get_auth_runner (this._read_authorization);
|
||||
return async (req, res): Promise<boolean> => {
|
||||
const res = (await general (req, res)) && (await specific (req, res));
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
public set read_authorization (value: Authorization): void{
|
||||
this._read_authorization = value;
|
||||
}
|
||||
|
||||
public get update_authorization (): AuthRunner {
|
||||
const general = this.get_auth_runner (this._general_authorization);
|
||||
const specific = this.get_auth_runner (this._update_authorization);
|
||||
return async (req, res): Promise<boolean> => {
|
||||
const res = (await general (req, res)) && (await specific (req, res));
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
public set update_authorization (value: Authorization): void{
|
||||
this._update_authorization = value;
|
||||
}
|
||||
|
||||
public get delete_authorization (): AuthRunner {
|
||||
const general = this.get_auth_runner (this._general_authorization);
|
||||
const specific = this.get_auth_runner (this._delete_authorization);
|
||||
return async (req, res): Promise<boolean> => {
|
||||
const res = (await general (req, res)) && (await specific (req, res));
|
||||
return res;
|
||||
};
|
||||
}
|
||||
|
||||
public set delete_authorization (value: Authorization): void{
|
||||
this._delete_authorization = value;
|
||||
}
|
||||
}
|
||||
|
31
lib/KnexInterface.ts
Normal file
31
lib/KnexInterface.ts
Normal file
@ -0,0 +1,31 @@
|
||||
/* eslint-disable @typescript-eslint/naming-convention */
|
||||
|
||||
interface KnexQuery {
|
||||
insert(data: object): Promise;
|
||||
update(data: object): Promise;
|
||||
select(...columns: Array<string>): Promise;
|
||||
delete(): Promise;
|
||||
where(filter: object): KnexQuery;
|
||||
whereNot(filter: object): KnexQuery;
|
||||
whereIn(filter: object): KnexQuery;
|
||||
whereNotIn(filter: object): KnexQuery;
|
||||
whereNull(filter: object): KnexQuery;
|
||||
whereNotNull(filter: object): KnexQuery;
|
||||
whereExists(filter: object): KnexQuery;
|
||||
whereNotExists(filter: object): KnexQuery;
|
||||
whereBetween(filter: object): KnexQuery;
|
||||
whereNotBetween(filter: object): KnexQuery;
|
||||
join(table: string, col: string, on: string): KnexQuery;
|
||||
leftJoin(table: string, col: string, on: string): KnexQuery;
|
||||
rightJoin(table: string, col: string, on: string): KnexQuery;
|
||||
leftOuterJoin(table: string, col: string, on: string): KnexQuery;
|
||||
rightOuterJoin(table: string, col: string, on: string): KnexQuery;
|
||||
fullOuterJoin(table: string, col: string, on: string): KnexQuery;
|
||||
returning(column: string | Array<string>): KnexQuery;
|
||||
}
|
||||
|
||||
type Knex = {
|
||||
(table: string): KnexQuery;
|
||||
}
|
||||
|
||||
export { Knex, KnexQuery };
|
@ -29,7 +29,6 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@scode/consts": "^1.1.4",
|
||||
"@scode/utilities": "^1.0.21",
|
||||
"@types/express": "^4.17.6",
|
||||
"express": "^4.17.1"
|
||||
}
|
||||
|
@ -155,11 +155,6 @@
|
||||
eslint-plugin-node "^11.0.0"
|
||||
eslint-plugin-sort-requires-by-path "^1.0.2"
|
||||
|
||||
"@scode/utilities@^1.0.21":
|
||||
version "1.0.21"
|
||||
resolved "https://npm.scode.ovh/@scode%2futilities/-/utilities-1.0.21.tgz#adbc50720515df16fc05bc97b56cb9ba35499e0a"
|
||||
integrity sha512-2F5mSTN7/2w5YQgamp2nan6q75Nln6bjAVBOvTrrQMbhWkZnieKzgt3g9M8GnsICVefmk5IPguiZhWPVYLmQxw==
|
||||
|
||||
"@stryker-mutator/api@^3.1.0":
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@stryker-mutator/api/-/api-3.1.0.tgz#7eb6f1e1f2af17ff0425c6aa0d4d244ca822e972"
|
||||
|
Reference in New Issue
Block a user