UNPKG

@bee.js/node

Version:

A JavaScript framework for making Node.js API´s

513 lines (396 loc) 13.9 kB
const log = require("./lib/beeHive/log"); const headers = require("./lib/beeHive/headers"); const beeORM = require("./lib/ORM/beeORM"); const beeDBA = require("./lib/DBA/beeDBA"); const beeJWT = require("./lib/JWT/beeJWT"); const beeTools = require("./tools/beeTools"); module.exports = function hive(req = {}, res = {}, model = null) { model = model && typeof model === "string" ? global.configs.models[model] : model; let script = [ { main: true, model: model, ids: [], select: [], where: [], binds: [], orderBy: [], limit: [], fields: [], }, ]; let data = res.data || {}; let counters = res.counters || {}; let inserts = res.inserts || []; let debug = res.debug || []; let error = res.error || false; let errorCode = res.errorCode || 0; let mainContainer = null; let models = global.models; let relWhere = {}; const dbExec = async function (SQL, binds = [], params = {}, retryCount = 0) { let result = []; const MAX_RETRIES = 2; try { if (global.configs.debug) console.log( SQL.replace( /\b(SELECT|FROM|WHERE|INNER JOIN|LEFT JOIN|RIGHT JOIN|GROUP BY|ORDER BY|LIMIT)\b/gi, (match) => `\n${match.toUpperCase()}` ).replace( /\b(SELECT|FROM|WHERE|INNER|JOIN|LEFT|RIGHT|ON|GROUP BY|ORDER BY|LIMIT|AND|OR|AS|IN|NOT|NULL|IS|DISTINCT|HAVING|UNION|ALL)\b/gi, (match) => `\x1b[1;36m${match.toUpperCase()}\x1b[0m` ) ); global.beeDBPool = global.beeDBPool || beeORM.createPool(global.configs); result = await global.beeDBPool.query(SQL, binds); } catch (_error) { log("####################### DB ERROR #######################"); log(_error); log(SQL); log(binds.join(",")); if (_error?.message?.includes("ECONNRESET") && retryCount < MAX_RETRIES) { log( "####################### DB retry after ECONNRESET... #######################" ); global.beeDBPool = beeORM.createPool(global.configs); return await dbExec(SQL, binds, params, retryCount + 1); } return []; } finally { if (global.configs.debug) debug.push(SQL); if (global.configs.debug && binds) debug.push(binds); if (params.container) { data[params.container] = result[0]; if (counters) counters[params.container] = (result[0] || []).length; } } return result[0]; }; return Object.assign({}, res, { data, counters, inserts, debug, error, errorCode, script, models, beeDBA, token: beeJWT, configs: this.config, headers, dbExec, dbEngine: async function () { error = !script[0].model ? `MODEL NOT FOUND` // TODO refazer : error; if (error) return this; let sqlSelect = beeORM.sqlSelect; let q = beeORM.quote; let relTables = []; let relIds = []; let relNow = null; let relPrev = null; let relJoin = {}; let parentTable = null; let i = -1; for (let _script of script) { i++; let model = _script.model; let binds = _script.binds || []; let modelName = model.name || model.table; data[modelName] = null; if (i === 0) { mainContainer = modelName; ids = model.ids; } else { ids = relTables.filter((a) => a.table == model.table)[0]; //['idsFromFieldB'] ids = ids ? ids.idsFromFieldB : []; parentTable = script[i - 1].model.table; // rels for (let _relField in model.relations) { let array = model.relations[_relField].split("."); let _parentTable = array[0]; let _parentField = array[1].split(" ")[0]; binds = binds.length ? binds : script[0].binds; if (_parentTable !== parentTable) continue; relJoin[_parentTable] = !relJoin[_parentTable] ? `INNER JOIN ${q(_parentTable)} ON ${q( _parentTable )}.${_parentField} = ${models[modelName].table}.${_relField} ` : relJoin[_parentTable] + `AND ${q(_parentTable)}.${_parentField} = ${ models[modelName].table }.${_relField} `; } } let fields = script[0]["fields"].concat( (req.onlyFields || {})[model.table] || [] ); let SQL = sqlSelect(model, ids, { select: script[0]["select"], where: script[0]["where"], orderBy: script[0]["orderBy"], limit: script[0]["limit"], joins: "", fields, middlewareParams: req.middleware, }); if (i > 0) for (let ii = i - 1; ii >= 0; ii--) SQL[2] += relJoin[script[ii].model.table]; relWhere[model.table] = SQL[3]; await dbExec(SQL.join("").trim() + ";", binds, { container: modelName, }); continue; } return; }, relations: function (rels, _default = "") { rels = rels || _default; if (!rels) return this; rels = rels.split("."); for (let model of rels) if (models[model]) script.push({ model: models[model], relation: true }); else (error = `RELATION ${model} NOT FOUND`) && (errorCode = 500); return this; }, select: function (model, ids) { model = typeof model === "string" ? models[model] : model; script.push({ model: model, ids: ids }); return this; }, get: async function () { await this.dbEngine(); return this; }, find: async function (...values) { if (values.length) (model.indexes.keys || Object.keys(model.schema)[0]) .split(",") .map((pk, i) => { console.log(pk); script[0].where.push(`${pk.trim()} = ?`); script[0].binds.push(values[i]); }); await this.dbEngine(); if (data[mainContainer]?.length) data[mainContainer] = data[mainContainer][0]; return this; }, first: async function () { //TODO auditar await this.dbEngine(); if (data[mainContainer]?.length) data[mainContainer] = data[mainContainer][0]; return this; }, last: async function () { await this.dbEngine(); data[mainContainer] = data[mainContainer][data[mainContainer].length - 1]; return this; }, where: function (where, binds) { script[0]["where"].push(where); if (binds) script[0]["binds"] = script[0]["binds"].concat(binds); return this; }, whereIf: function (condition, where, binds) { if (!condition) return this; script[0]["where"].push(typeof where == "function" ? where() : where); if (binds) script[0]["binds"].push(typeof binds === "function" ? binds() : binds); return this; }, whereIn: function (field, array = []) { const values = {}; array.map?.((row) => { const val = row[field]; if (!!!val) return; switch (model.schema[field]?.type) { case "char": case "varchar": case "text": case "string": return (values[`'${val}'`] = true); case "guid": case "uuid": return (values[beeTools.guidToBin(val) ?? "null"] = true); default: return (values[val] = true); } }); script[0]["where"].push( `${model.table}.${field} IN(${Object.keys(values).join(",") ?? "null"})` ); return this; }, binds: function (...params) { params.map((bind) => script[0]["binds"].push(bind)); return this; }, orderBy: function (...fields) { script[0]["orderBy"].push(fields.join(", ")); return this; }, orderByIf: function (condition, ...fields) { if (condition) script[0]["orderBy"].push(fields.join(", ")); return this; }, limit: function (...params) { script[0]["limit"].push(params.join(",")); return this; }, fields: function (...fields) { script[0]["fields"] = fields; return this; }, fieldsIf: function (condition, fields) { if (condition) script[0]["fields"] = fields; return this; }, search: function (string = "", fields = [], relevance = [1, 2, 3, 4, 5]) { if (!string) return this; let where = []; let orderBy = []; let words = string.split(" "); fields = beeORM.parseArray(fields); fields = fields.length ? fields : Object.keys(model.schema); fields.map((field) => { orderBy.push( `WHEN ${field} = '${string}' THEN ${ relevance[0] } WHEN ${field} LIKE '${string}%' THEN ${ relevance[1] } WHEN ${field} LIKE '%${string}%' THEN ${ relevance[2] } WHEN ${field} LIKE '%${string.replace(/ /g, "%")}%' THEN ${ relevance[3] }` ); words.map((word, i) => { where.push(`${field} LIKE '%${word}%'`); if (words.length > 1) orderBy.push( `WHEN ${field} = '${word}' THEN ${ relevance[4] * (i + 1) } WHEN ${field} LIKE '${word}%' THEN ${ relevance[4] * (i + 1) + relevance[1] } WHEN ${field} LIKE '%${word}%' THEN ${ relevance[4] * (i + 1) + relevance[2] }` ); }); }); script[0]["where"].push(`(${where.join(" OR ")})`); script[0]["orderBy"].unshift( `CASE ${orderBy.join(" ")} ELSE ${100000} END ASC` ); return this; }, insert: async function (_data, params = {}) { if (model.relations && params.relations) await Promise.all( Object.keys(model.relations).map(async (field) => { let where = []; let array = model.relations[field].split(" ")[0].split("."); let parentTable = array[0]; let parentField = array[1]; let parentRels = global.configs.models[parentTable].relations; Object.keys(parentRels).map((fieldRel) => where.push( `${fieldRel} = '${params.relations[parentTable][fieldRel]}'` ) ); let SQL = `SELECT ${parentField} FROM ${parentTable} WHERE ${where.join( " AND " )}`; SQL = await dbExec(SQL); Array.isArray(_data) ? _data.map((a) => (a[parentField] = SQL[0][parentField])) : (_data[parentField] = SQL[0][parentField]); }) ); const onlyFields = script[0].fields.concat( (req.onlyFields || {})[model.table] || [] ); const sql = beeORM.sqlInsertUpdate(model, _data, onlyFields, "INSERT"); _data = await dbExec(sql.SQL, sql.binds); inserts.push({ ..._data, model: model.table }); this.insertId = _data?.insertId; return { ...this, result: { ...sql.result, ..._data } }; }, update: async function (data, ids = []) { const onlyFields = script[0].fields.concat( (req.onlyFields || {})[model.table] || [] ); const sql = beeORM.sqlInsertUpdate(model, data, onlyFields, "UPDATE"); sql.SQL += beeORM.modelSqlWhere(model, ids, req.middleware); sql.SQL += script[0]["where"].length ? (!ids.length ? " WHERE " : " AND ") + script[0]["where"].join(" AND ") : ""; data = await dbExec(sql.SQL, sql.binds); return { ...this, result: { ...sql.result, ...data } }; }, delete: async function (ids) { const q = beeORM.quote; ids = ids && typeof ids !== "object" ? ids.toString().split(",") : ids; SQL = "DELETE FROM " + q(model.table); SQL += beeORM.modelSqlWhere(model, ids, req.middleware); SQL += script[0]["where"].length ? (!ids.length ? " WHERE " : " AND ") + script[0]["where"].join(" AND ") : ""; data = await dbExec(SQL); return this; }, table: async function (table, where) { const q = beeORM.quote; SQL = "SELECT * FROM " + q(table); SQL += where ? ` WHERE ${where};` : ""; let result = await dbExec(SQL); data[table] = result; counters[table] = result.length; mainContainer = table; return this; }, query: async function (SQL, binds = [], container = "query") { let result = await dbExec(SQL, binds, { container: container }); counters[container] = result ? result.length : 0; return this; }, response: function (sendData = data, action = null, status) { const out = { data: sendData, counters, action, error }; if (res.headersSent) return console.error("ERROR beejs: headers already sent"); if (inserts.length) out.inserts = inserts; if (global.configs.debug) out.debug = debug; if (status) res.status(status).send(out); else switch (req.method) { case "DELETE": res.status(action ? errorCode || 200 : 204).json(out); break; case "POST": res.status(errorCode || 201).json(out); break; case "PUT": res.status(errorCode || 200).json(out); // TODO deletar props irrelevantes no output break; default: res.status(errorCode || 200).json(out); } }, responseError: function (error, errorCode) { const out = { error: { message: error || "an error has occurred" } }; if (global.configs.debug) out.debug = debug; res?.status?.(errorCode || 500).send(out); }, ifNull(param) { return !this.data ? param : this; }, }); };