UNPKG

@bucky24/database

Version:

A simple model system for node that allows connecting to multiple types of data source

409 lines (408 loc) 16.1 kB
"use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.Model = void 0; const yup_1 = require("yup"); const connections_1 = require("./connections"); const whereBuilder_1 = require("./whereBuilder"); const types_1 = require("./types"); const modelSchema = (0, yup_1.object)({ table: (0, yup_1.string)().required(), fields: (0, yup_1.lazy)((obj) => { return (0, yup_1.object)(Object.assign({}, Object.keys(obj).reduce((obj, key) => { return Object.assign(Object.assign({}, obj), { [key]: (0, yup_1.object)({ type: (0, yup_1.mixed)().oneOf(Object.values(types_1.FIELD_TYPE)).required(), meta: (0, yup_1.array)().of((0, yup_1.mixed)().oneOf(Object.values(types_1.FIELD_META)).required()), size: (0, yup_1.number)().positive().optional(), foreign: (0, yup_1.object)({ table: (0, yup_1.object)().required(), field: (0, yup_1.string)().required(), }).default(undefined), }) }); }, {}))); }), version: (0, yup_1.number)().required().integer(), }); ; class Model { constructor(settings) { this.table = settings.table; this.fields = settings.fields; this.fieldList = []; Object.keys(settings.fields).forEach((key) => { const field = settings.fields[key]; this.fieldList.push(Object.assign(Object.assign({}, field), { id: key })); }); this.version = settings.version; } static create(settings) { try { const { table, fields, version } = modelSchema.validateSync(settings); const model = new Model({ table, fields: Object.assign({ id: { type: types_1.FIELD_TYPE.INT, meta: [types_1.FIELD_META.AUTO], } }, fields), version, }); return model; } catch (error) { throw new Error(`${error.message} with data ${JSON.stringify(settings)}`); } } createCrudApis(app, options = {}) { let middleware = options.middleware || []; if (!Array.isArray(middleware)) { middleware = [middleware]; } const processBody = (req, res) => { if (!req.body) { res.status(400).json({ error: 'No json body detected', }); return false; } const missingFields = []; for (const field of this.fieldList) { const fieldData = this.getFieldData(field.id); if (req.body[field.id] === undefined && (fieldData === null || fieldData === void 0 ? void 0 : fieldData.meta.includes(types_1.FIELD_META.REQUIRED))) { missingFields.push(field); } } const extraFields = []; for (const field in req.body) { if (!this.getFieldData(field)) { extraFields.push(field); } } if (missingFields.length > 0 || extraFields.length > 0) { res.status(400).json({ missingFields, extraFields, }); return false; } return true; }; app.get('/' + this.table, ...middleware, (req, res) => __awaiter(this, void 0, void 0, function* () { try { const objects = yield this.search({}); const filteredObjects = this.filterForExport(objects); res.status(200).json(filteredObjects); } catch (e) { console.error(e); res.status(500).end(); } })); app.get('/' + this.table + "/:id", ...middleware, (req, res) => __awaiter(this, void 0, void 0, function* () { try { const object = yield this.get(req.params.id); if (!object) { res.status(404).end(); return; } const filteredObject = this.filterForExport(object); res.status(200).json(filteredObject); } catch (e) { console.error(e); res.status(500).end(); } })); app.delete('/' + this.table + "/:id", ...middleware, (req, res) => __awaiter(this, void 0, void 0, function* () { try { const object = yield this.get(req.params.id); if (!object) { res.status(404).end(); return; } yield this.delete(req.params.id); res.status(200).json({ id: req.params.id, }); } catch (e) { console.error(e); res.status(500).end(); } })); app.post('/' + this.table, ...middleware, (req, res) => __awaiter(this, void 0, void 0, function* () { if (!processBody(req, res)) { return; } try { const id = yield this.insert(req.body); const newData = yield this.get(id); res.status(200).json(newData); } catch (e) { console.error(e); res.status(500).end(); } })); app.put('/' + this.table + '/:id', ...middleware, (req, res) => __awaiter(this, void 0, void 0, function* () { const { id } = req.params; if (!processBody(req, res)) { return; } try { yield this.update(id, req.body); const newData = yield this.get(id); res.status(200).json(newData); } catch (e) { console.error(e); res.status(500).end(); } })); } static _getColumnFromType(type) { if (type === types_1.FIELD_TYPE.INT) { return 'INT'; } else if (type === types_1.FIELD_TYPE.STRING) { return 'TEXT'; } else if (type === types_1.FIELD_TYPE.JSON) { return 'TEXT'; } else if (type === types_1.FIELD_TYPE.BIGINT) { return 'BIGINT'; } else if (type === types_1.FIELD_TYPE.BOOLEAN) { return 'BOOLEAN'; } } static _getValueForType(type, value) { if (type === types_1.FIELD_TYPE.STRING) { return `"${value}"`; } return value; } _getFieldsWithMeta(meta) { return this.fieldList.filter((field) => { if (!field.meta) { // if it has no meta, no way it can match any meta return false; } return field.meta.includes(meta); }); } static _getFieldCreationString(fieldName, data, ticks) { let fieldRow = ticks + fieldName + ticks + " " + Model._getColumnFromType(data.type); if (data.meta) { if (data.meta.includes(types_1.FIELD_META.REQUIRED)) { fieldRow += ' NOT NULL'; } if (data.meta.includes(types_1.FIELD_META.AUTO)) { fieldRow += ' AUTO_INCREMENT'; } } return fieldRow; } init() { return __awaiter(this, void 0, void 0, function* () { const connection = (0, connections_1.getDefaultConnection)(); if (connection === null) { throw new Error('No default connection set'); } return connection.initializeTable(this.table, this.fields, this.version); }); } getFieldData(field) { if (!this.fields[field]) { return null; } return Object.assign(Object.assign({}, this.fields[field]), { meta: this.fields[field].meta || [] }); } getTable() { return this.table; } processResult(result) { Object.keys(result).forEach((key) => { const value = result[key]; const data = this.getFieldData(key); if (!data) { // remove the field-it's not part of our fieldset anymore delete result[key]; return; } if (data.type === types_1.FIELD_TYPE.JSON) { result[key] = JSON.parse(value); } else if (data.type === types_1.FIELD_TYPE.BOOLEAN) { // assume it's 1 or 0 result[key] = !!value; } }); return result; } processForSave(value, field) { const data = this.getFieldData(field); if ((data === null || data === void 0 ? void 0 : data.type) === types_1.FIELD_TYPE.JSON) { return JSON.stringify(value); } return value; } get(id) { return __awaiter(this, void 0, void 0, function* () { const connection = (0, connections_1.getDefaultConnection)(); if (connection === null) { throw new Error('No default connection set'); } const result = yield this.search({ id, }); if (result.length === 0) { return null; } return result[0]; }); } search() { return __awaiter(this, arguments, void 0, function* (queryData = {}, order, limit, offset) { const connection = (0, connections_1.getDefaultConnection)(); if (connection === null) { throw new Error('No default connection set'); } let keys = []; // for some weird reason when we convert to JS, the instanceof is no longer working, // so fall back to checking the constructor name if (queryData instanceof whereBuilder_1.WhereBuilder || queryData.constructor.name === "WhereBuilder") { keys = queryData.getAllFields(); } else { keys = Object.keys(queryData); } for (let i = 0; i < keys.length; i++) { const key = keys[0]; const data = this.getFieldData(key); if (data === null) { throw new Error(`No such field '${key}'`); } } const result = yield connection.search(this.table, queryData, order, limit, offset); return result.map((item) => { return this.processResult(item); }); }); } count() { return __awaiter(this, arguments, void 0, function* (queryData = {}) { const connection = (0, connections_1.getDefaultConnection)(); if (connection === null) { throw new Error('No default connection set'); } const keys = Object.keys(queryData); for (let i = 0; i < keys.length; i++) { const key = keys[0]; const data = this.getFieldData(key); if (data === null) { throw new Error(`No such field '${key}'`); } } const result = yield connection.count(this.table, queryData); return result; }); } update(id, fields) { return __awaiter(this, void 0, void 0, function* () { const connection = (0, connections_1.getDefaultConnection)(); if (connection === null) { throw new Error('No default connection set'); } const keys = Object.keys(fields); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const data = this.getFieldData(key); if (data === null) { throw new Error(`No such field '${key}'`); } const value = fields[key]; if (value === null && data.meta.includes(types_1.FIELD_META.REQUIRED)) { throw new Error(`Field '${key}' cannot be set to null`); } fields[key] = this.processForSave(value, key); } const tableFields = Object.keys(this.fields); const handledFields = tableFields.reduce((obj, field) => { return Object.assign(Object.assign({}, obj), { [field]: this.getFieldData(field) }); }, {}); return yield connection.update(this.table, id, fields, handledFields); }); } insert(insertData) { return __awaiter(this, void 0, void 0, function* () { const connection = (0, connections_1.getDefaultConnection)(); if (connection === null) { throw new Error('No default connection set'); } const keys = Object.keys(insertData); for (let i = 0; i < keys.length; i++) { const key = keys[i]; const data = this.getFieldData(key); if (data === null) { throw new Error(`No such field '${key}'`); } const value = insertData[key]; if (value === null && data.meta.includes(types_1.FIELD_META.REQUIRED)) { throw new Error(`Field '${key}' cannot be set to null`); } insertData[key] = this.processForSave(value, key); } const tableFields = Object.keys(this.fields); for (let i = 0; i < tableFields.length; i++) { const key = tableFields[i]; const fieldData = this.getFieldData(key); if (!fieldData) { throw new Error(`Field '${key}' not found in model`); } if (fieldData.meta.includes(types_1.FIELD_META.REQUIRED) && (insertData[key] === null || insertData[key] === undefined)) { throw new Error(`Required field '${key}' not found`); } } return connection.insert(this.table, tableFields.reduce((obj, field) => { return Object.assign(Object.assign({}, obj), { [field]: this.getFieldData(field) }); }, {}), insertData); }); } delete(id) { return __awaiter(this, void 0, void 0, function* () { const connection = (0, connections_1.getDefaultConnection)(); if (connection === null) { throw new Error('No default connection set'); } return connection.delete(this.table, id); }); } filterForExport(data) { if (Array.isArray(data)) { return data.map((item) => { return this.filterForExport(item); }); } const result = Object.assign({}, data); const filterFields = this._getFieldsWithMeta(types_1.FIELD_META.FILTERED); const filterFieldsIds = filterFields.map((field) => { return field.id; }); for (const key in result) { if (filterFieldsIds.includes(key)) { delete result[key]; } } return result; } } exports.Model = Model;