const {
promisify
} = require('util');
const zxcvbn = require('zxcvbn');
const mysql = require('mysql2/promise');
const log = require('./log').create_logger("utils");
const status_codes = require('http-status-codes');
const STATUS_CODES = status_codes.StatusCodes;
const fs = require('fs');
const mysql_options = {
connectionLimit: 10,
host: 'localhost',
user: process.env.SQL_USER,
password: process.env.SQL_PASSWORD,
database: 'budget'
};
const MYSQL_POOL = mysql.createPool(mysql_options);
async function query(query, wildcards) {
let args = [query];
log.v("Execute query: ", query);
if (wildcards) {
args.push(wildcards);
log.v("With wildcards:", wildcards);
}
return (await MYSQL_POOL.query(...args))[0];
}
function not_ok_response(status_code, data) {
log.v("Respond with status", status_code, "Data", data);
return Object.assign({
status: status_code
}, data || {});
}
function handle_err_status_res(res) {
return function(error) {
if (!res.headersSent) {
log.general.error("Error response wrapper invoked with unknown error", error);
if (typeof error !== "number" || error < 300) {
error = 500;
}
res.sendStatus(error);
} else {
log.general.error("Response already sent in response error handler");
}
};
}
function create_error_handler(ext_log, non_fatal, message) {
message = message || (x => x);
return function(error) {
if (ext_log.locals && ext_log.locals.log) {
// handle passing in Express res object as logger
ext_log = ext_log.locals.log;
}
ext_log.general.error(error.stack);
if (non_fatal) {
return Promise.resolve(non_fatal);
}
return Promise.reject(message(error));
}
}
module.exports = {
MYSQL_POOL: MYSQL_POOL,
promisify: promisify,
status: STATUS_CODES,
fs: {
mkdir_if_not_exists: function(path) {
return promisify(fs.stat)(path).catch(function(error) {
if (error.code === "ENOENT") {
return promisify(fs.mkdir)(path);
}
return new Promise((_, r) => r(error));
});
}
},
captialize: function(string) {
if (!string) {
return "";
}
return string[0].toUpperCase() + string.slice(1);
},
ok: function(data) {
// ¯\_(ツ)_/¯
return not_ok_response(STATUS_CODES.OK, data);
},
not_ok: function(status, data) {
return not_ok_response(STATUS_CODES[status], data);
},
handle_err: {
sql: (logger, non_fatal) => create_error_handler(logger, non_fatal, error => `MYSQL ERROR: ${error.errno} (${error.code})`),
status_res: handle_err_status_res,
res: function(res, default_error_message) {
// Invoke with (error) or ([error, data])
// Since this is a catch handler and can only have a single argument
return function(arg) {
let error = arg;
let data;
if (Array.isArray(arg) && arg.length === 2) {
error = arg[0];
data = arg[1];
}
if (Object.prototype.hasOwnProperty.call(STATUS_CODES, error)) {
res.locals.log.general.error("Respond with status:", error);
res.send(not_ok_response(STATUS_CODES[error], data || {
message: default_error_message || "Internal Server Error",
color: "red"
}));
} else if (typeof arg === "string") {
res.locals.log.general.error("Respond with status: error, message:", arg);
res.send(not_ok_response(STATUS_CODES.INTERNAL_SERVER_ERROR, {
message: arg,
color: "red"
}));
} else {
handle_err_status_res(res)(arg);
}
};
}
},
mysql_options: mysql_options,
query: query,
/*
Use for "UPDATE table SET param1=value1, param2=value2 WHERE param3=value3 AND param4=value4"
Syntax: "UPDATE table" + utils.set_where({
[param1]: value1,
[param2]: value2
}, {
[param3]: value3,
[param4]: value4
})
This will also escape all params and values
*/
set_where: function(set, where) {
return [
["SET", set, ", "],
["WHERE", where, " AND"]
].reduce(function(SQL_STR, [SQL_OPERATOR, params, DELIMITER]) {
return SQL_STR + Object.entries(params).reduce(function(KEY_VALUE_STR, [param, val], i, arr) {
return KEY_VALUE_STR + ` ${mysql.escapeId(param)}=${mysql.escape(val)}${i < arr.length - 1 ? DELIMITER : ""}`;
}, " " + SQL_OPERATOR);
}, "");
},
validate: {
keys: function(obj, keys) {
for (const key_data of keys) {
let [key, validator, error_message] = Array.isArray(key_data) ? key_data : [key_data];
validator = validator || (x => x);
error_message = error_message || "Invalid Request";
if (!Object.prototype.hasOwnProperty.call(obj, key) || !validator(obj[key])) {
log.general.error("key", key, "missing or invalid in obj", obj);
return Promise.reject(error_message)
}
}
return Promise.resolve(obj);
},
password: function(str) {
return typeof str === "string" && zxcvbn(str.slice(0, Math.min(str.length, 100))).score > 0;
}
},
setIntervalAndTriggerImmediate: function(callback, interval) {
setTimeout(callback);
return setInterval(callback, interval);
}
};