@bee.js/node
Version:
A JavaScript framework for making Node.js API´s
513 lines (396 loc) • 13.9 kB
JavaScript
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;
},
});
};