import { Request, Response, Router } from 'express'; import { http } from '@scode/consts'; import { KnexCrudOptions } from './KnexCrudOptions'; import { CrudHandler } from './CrudHandler'; import { Knex } from './KnexInterface'; import { HttpHandler } from './HttpHandler'; export class KnexCrudHandler extends HttpHandler implements CrudHandler { protected table: string; protected columns: Array; protected options: KnexCrudOptions; protected knex: Knex; public constructor ( table: string, columns: Array, 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' ); } } protected validate_body ( req: Request, res: Response ): Promise | object { 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; } protected ensure_data ( data: object, res: Response, fail_on_undef = true ): Promise|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 { 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 { 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 { 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 { 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 { 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); } }