import { Request, Response, Router } from 'express'; import { http } from '@scode/consts'; import { DatabaseCrudOptions } from './DatabaseCrudOptions'; import { CrudHandler } from './CrudHandler'; import { HttpHandler } from './HttpHandler'; import { DatabaseCrudOptionsReader } from './DatabaseCrudOptionsReader'; export class DatabaseCrudHandler extends HttpHandler implements CrudHandler { protected table: string; protected columns: Array; protected options: DatabaseCrudOptionsReader; public constructor ( table: string, columns: Array, options: DatabaseCrudOptions = {} ) { super (); this.table = table; this.columns = columns; this.options = new DatabaseCrudOptionsReader (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> | Record { 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: Record, res: Response, fail_on_undef = true ): Promise> | Record { 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]; } if (typeof this.options.optional_columns !== 'undefined') { 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) .where ({ id: req.headers.id }) .select ( 'id', ...this.columns ); 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 (); } 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); } }