diff --git a/floriday.js b/floriday.js index 01a1e3e..046d4bb 100644 --- a/floriday.js +++ b/floriday.js @@ -6,22 +6,22 @@ import fs from 'fs'; const env = process.env; const flModels = yml.load(fs.readFileSync('./models/models.yml', 'utf8')); -let cycle = 1; +let cycle = 1, stopSchedule = false; class Floriday { async start() { try { await utils.checkConfig(); await utils.checkToken(); - utils.separator('Init'); + await utils.separator('Init'); } catch (err) { - utils.criticalError(err); + utils.handler('critical', err); } }; async tryConn() { while (true) try { - utils.warning(new Error('Awaiting a response from the database...')); + await utils.handler('warning', new Error('Awaiting a response from the database...')); await utils.sleep(env.DB_RECON_TIMEOUT); await checkCon(); await this.schedule(); @@ -34,7 +34,7 @@ class Floriday { const intervalTime = JSON.parse(env.IS_PRODUCTION) ? env.MS_PRODUCTION_SCHEDULE : env.MS_TEST_SCHEDULE; - while (!this.stopSchedule) { + while (!stopSchedule) { try { await this.trunk(); await new Promise(resolve => setTimeout(resolve, intervalTime)); @@ -50,26 +50,27 @@ class Floriday { async trunk() { try{ - for (let model of flModels) - await utils.syncModel(model); - await utils.checkConnections(); - utils.separator(`${cycle++} ${(cycle == 2 ? 'Cycle' : 'Cycles')}`); - } catch (err) { - if (['SequelizeConnectionRefusedError', 'SequelizeConnectionError'].includes(err.name)) - throw err; - utils.criticalError(err); + for (let model of flModels) { + if (stopSchedule) return; + await utils.syncModel(model); + } + + await utils.separator(`${cycle++} ${(cycle == 2 ? 'Cycle' : 'Cycles')}`); + } catch (err) { + if (err?.original?.code === 'ER_SOCKET_UNEXPECTED_CLOSE') throw err; + utils.handler('critical', err); } }; async stop() { try { - this.stopSchedule = false; + stopSchedule = true; await closeCon(); console.warn(chalk.dim('\nBye, come back soon 👋')) } catch (err) { - utils.criticalError(err); + utils.handler('critical', err); } }; }; diff --git a/models/sequelize.js b/models/sequelize.js index c451496..44e9fad 100644 --- a/models/sequelize.js +++ b/models/sequelize.js @@ -19,12 +19,12 @@ console.log(chalk.hex('#06c581')( )) try { - utils.startSpin('Creating database connection...'); + await utils.startSpin('Creating database connection...'); sequelize = await createConn(); await checkCon(); - utils.okSpin(); + await utils.okSpin(); } catch (err) { - utils.criticalSpin(err); + await utils.criticalSpin(err); } // Conf Models @@ -161,13 +161,13 @@ try { }); */ } catch (err) { - utils.criticalError(err); + utils.handler('critical', err); } try { const action = JSON.parse(env.FORCE_SYNC) ? { force: true } : { alter: true }; const actionMsg = JSON.parse(env.FORCE_SYNC) ? 'Forcing' : 'Altering'; - utils.startSpin(`${actionMsg} models...`); + await utils.startSpin(`${actionMsg} models...`); await sequelize.sync(action); /* @@ -177,11 +177,10 @@ try { // Create procedures sequelize.query(fs.readFileSync('routines/procedures/offerRefresh.sql', 'utf-8')); */ - - utils.okSpin(); + await utils.okSpin(); } catch (err) { - utils.criticalSpin(err) + await utils.criticalSpin(err) } /** @@ -219,13 +218,13 @@ async function checkCon() { * Close the connection to the database */ async function closeCon() { - utils.failSpin(null, true); - utils.startSpin('Closing database connection...'); + await utils.failSpin(null, true); + await utils.startSpin('Closing database connection...'); try { await sequelize.close() - utils.okSpin(); + await utils.okSpin(); } catch (err) { - utils.criticalSpin(err); + await utils.criticalSpin(err); } } diff --git a/utils.js b/utils.js index e3475fa..8514ce9 100644 --- a/utils.js +++ b/utils.js @@ -10,7 +10,7 @@ import fs from 'fs'; const env = process.env; const methods = yml.load(fs.readFileSync('./methods.yml', 'utf8')); const flModels = yml.load(fs.readFileSync('./models/models.yml', 'utf8')); -const arrow = '└─────'; +const arrow = '└───────'; let spinner; /** @@ -21,7 +21,7 @@ let spinner; */ export async function checkToken(isForce) { try { - startSpin(`Checking token...`); + await startSpin(`Checking token...`); const clientConfigData = await models.config.findOne(); @@ -57,9 +57,9 @@ export async function checkToken(isForce) { }); } else optionalMsg = 'Using stored token...'; - okSpin(optionalMsg); + await okSpin(optionalMsg); } catch (err) { - failSpin(err); + await await failSpin(err); } }; @@ -76,7 +76,7 @@ export async function getCurrentToken() { * Check the floriday data config. */ export async function checkConfig() { - startSpin(`Checking config...`); + await startSpin(`Checking config...`); const excludedEnvVars = ['VSCODE_GIT_ASKPASS_EXTRA_ARGS']; const requiredEnvVars = Object.keys(env); @@ -84,13 +84,13 @@ export async function checkConfig() { for (const reqEnvVar of filteredEnvVars) if (!process.env[reqEnvVar]) - failSpin(new Error(`You haven't provided the ${reqEnvVar} environment variable`)); + await failSpin(new Error(`You haven't provided the ${reqEnvVar} environment variable`)); const clientConfigData = await models.config.findOne(); if (!clientConfigData) await updateClientConfig(env.CLIENT_ID, env.CLIENT_SECRET); - okSpin(); + await okSpin(); }; /** @@ -125,8 +125,8 @@ export async function updateClientConfig(clientConfig) { * * @param {Integer} ms */ -export function sleep(ms) { - new Promise(resolve => setTimeout(resolve, ms)); +export async function sleep(ms) { + await new Promise(resolve => setTimeout(resolve, ms)); }; /** @@ -135,7 +135,7 @@ export function sleep(ms) { * @param {String} model Supported models (./models/methods.yml) */ export async function syncModel(model) { - startSpin(`Syncing ${model}...`); + await startSpin(`Syncing ${model}...`); try { const dbSeqNum = await models.sequenceNumber.findOne({ where: { model } }) let curSeqNum = dbSeqNum?.maxSequenceNumber ?? 0, i = 0; @@ -155,21 +155,26 @@ export async function syncModel(model) { params = params.toString(); } - const res = (await vnRequest('GET', `${env.API_URL}${methods[model].sync.url}${curSeqNum}${params ? `?${params}` : ''}`)).data; - const data = res.results; - curSeqNum = res.maximumSequenceNumber; + const res = (await vnRequest('GET', `${env.API_URL}${methods[model].sync.url}${curSeqNum}${params ? `?${params}` : ''}`)); + if (res?.response?.status == 403 && res?.response?.data?.title === 'There are no connected suppliers.') { // Forbidden + await await warnSpin(`Syncing ${model}...`, new Error(res.response.data.title), true); + return; + } + + const data = res.data.results; + curSeqNum = res.data.maximumSequenceNumber; misSeqNum = maxSeqNum - curSeqNum; await insertModel(model, data); - txtSpin(`Syncing ${i = i + data.length} ${model}, ${misSeqNum} missing...`); + await txtSpin(`Syncing ${i = i + data.length} ${model}, ${misSeqNum} missing...`); await insertSequenceNumber(model, curSeqNum); } await insertSequenceNumber(model, maxSeqNum); - txtSpin((i) + await txtSpin((i) ? `Syncing ${i} ${model}...` : `Syncing ${model}...`); - okSpin(); + await okSpin(); } catch (err) { - failSpin(err); + await await failSpin(err); } }; @@ -210,12 +215,12 @@ export async function insertModel(model, data) { */ export async function checkConnections(){ try { - startSpin('Checking connections...'); + await startSpin('Checking connections...'); await createConnections(); await deleteConnections(); - if (spinner) okSpin(); + if (spinner) await okSpin(); } catch (err) { - failSpin(err); + await failSpin(err); } }; @@ -669,13 +674,13 @@ export async function createConnections() { connectionsToPut.push(value.organizationId); }); - if (connectionsToPut.length && !spinner) startSpin('Creating connections...'); + if (connectionsToPut.length && !spinner) await startSpin('Creating connections...'); for (let connection of connectionsToPut) { await vnRequest('PUT', `${env.API_URL}${methods.connections.base.url}${connection}`); - txtSpin(`Creating ${i++} connections, ${connectionsToPut.length - i} missing...`); + await txtSpin(`Creating ${i++} connections, ${connectionsToPut.length - i} missing...`); } - if (spinner && i) okSpin(`Creating ${i++} connections...`); + if (spinner && i) await okSpin(`Creating ${i++} connections...`); } catch (err) { throw err; } @@ -697,16 +702,16 @@ export async function deleteConnections() { ghostConnections.push(value.organizationId); }); - if (ghostConnections.length && !spinner) startSpin('Deleting connections...'); + if (ghostConnections.length && !spinner) await startSpin('Deleting connections...'); for (let connection of ghostConnections) { await vnRequest('DELETE', `${env.API_URL}/connections/${connection}`); - txtSpin(`Deleting ${i++} connections, ${ghostConnections.length - i} missing...`) + await txtSpin(`Deleting ${i++} connections, ${ghostConnections.length - i} missing...`) } - if (spinner && i) okSpin(`Deleting ${i++} connections...`); + if (spinner && i) await okSpin(`Deleting ${i++} connections...`); } catch (err) { - criticalSpin(err); + await criticalSpin(err); } }; @@ -737,43 +742,43 @@ export async function vnRequest(method, url, data, headers) { switch (err.code) { case 'ECONNRESET': // Client network socket TLS case 'EAI_AGAIN': // getaddrinfo - warnSpin(null, err, false); - sleep(1000); + await await warnSpin(null, err, false); + await sleep(1000); break; case 'ECONNABORTED': case 'ECONNREFUSED': case 'ERR_BAD_REQUEST': switch (err.response.status) { - case 404: // Not found + case 404, 403: // Not found and Forbidden return err; case 504: case 502: - warnSpin(null, err, false); - sleep(1000); + await warnSpin(null, err, false); + await sleep(1000); break; case 429: // Too Many Requests - warnSpin(null, err, false); - sleep(60000); + await warnSpin(null, err, false); + await sleep(60000); break; case 401: // Unauthorized - warnSpin(null, err, false); + await warnSpin(null, err, false); await checkToken(true); headers.Authorization ? headers.Authorization = `Bearer ${await getCurrentToken()}` - : criticalError(err); + : handler('critical', err);; break; default: - warnSpin(null, err, false); - sleep(env.MS_RETRY_UNHANDLED_ERROR); + await warnSpin(null, err, false); + await sleep(env.MS_RETRY_UNHANDLED_ERROR); break; } break; default: - warnSpin(null, err, false); - sleep(env.MS_RETRY_UNHANDLED_ERROR); + await warnSpin(null, err, false); + await sleep(env.MS_RETRY_UNHANDLED_ERROR); break; } - startSpin(); + await startSpin(); } } }; @@ -784,7 +789,7 @@ export async function vnRequest(method, url, data, headers) { * @param {String} msg Text of spinner * @param {Boolean} isNew Reinstantiate the object **/ -export function startSpin(msg, isKeep) { +export async function startSpin(msg, isKeep) { if (JSON.parse(env.TIME_STAMPS) && msg) msg = `${chalk.gray(`[${new moment().format('YYYY-MM-DD hh:mm:ss A')}]`)} ${msg}`; @@ -803,7 +808,7 @@ export function startSpin(msg, isKeep) { * * @param {String} msg Text of spinner **/ -export function txtSpin(msg) { +export async function txtSpin(msg) { if (JSON.parse(env.TIME_STAMPS) && msg) msg = `${chalk.gray(`[${new moment().format('YYYY-MM-DD hh:mm:ss A')}]`)} ${msg}`; @@ -815,7 +820,7 @@ export function txtSpin(msg) { * * @param {String} msg Text of spinner **/ -export function okSpin(msg) { +export async function okSpin(msg) { if (JSON.parse(env.TIME_STAMPS) && msg) msg = `${chalk.gray(`[${new moment().format('YYYY-MM-DD hh:mm:ss A')}]`)} ${msg ?? ''}`; @@ -832,7 +837,7 @@ export function okSpin(msg) { * @param {Error} err Error object * @param {Boolean} clear Clean the instance **/ -export function warnSpin(msg, err, clear) { +export async function warnSpin(msg, err, clear) { if (JSON.parse(env.TIME_STAMPS) && msg) msg = `${chalk.gray(`[${new moment().format('YYYY-MM-DD hh:mm:ss A')}]`)} ${msg}`; @@ -840,7 +845,7 @@ export function warnSpin(msg, err, clear) { spinner.warn(msg); if (clear) spinner = null; } - if (err) warning(err); + if (err) await handler('warning', err); }; /** @@ -848,7 +853,7 @@ export function warnSpin(msg, err, clear) { * * @param {Error} err Error object **/ -export function failSpin(err) { +export async function failSpin(err) { if (spinner) { spinner.fail(); spinner = null; @@ -861,12 +866,12 @@ export function failSpin(err) { * * @param {Error} err Error object **/ -export function criticalSpin(err) { +export async function criticalSpin(err) { if (spinner) { spinner.fail(); spinner = null; } - criticalError(err); + handler('critical', err); }; /** @@ -874,28 +879,36 @@ export function criticalSpin(err) { * * @param {String} msg String to show **/ -export function separator(msg) { - console.log(chalk.gray(` ──────────────────────── ${msg}`)); +export async function separator(msg) { + console.log(chalk.gray(`↺ ──────────────────────── ${msg}`)); }; - /** - * Critical error. + * Function to handle error messages. * + * @param {String} type Type name * @param {Error} err Error object **/ -export function criticalError(err) { - const msg = `${chalk.red.bold(arrow)} ${chalk.red.bold('[CRITICAL]')}`; - console.log(`${msg} ${chalk.red(err.message)}`); - process.exit(); -}; +export async function handler(type, err) { + let header = (`${arrow} [${type.toUpperCase()}]`); + let msg = (err.response?.status && err.response?.data?.message) + ? `${err.response.status} - ${err.response?.data?.message}` + : err.message; -/** - * Warning. - * - * @param {Error} err - **/ -export function warning(err) { - const msg = `${chalk.yellow.bold(arrow)} ${chalk.yellow.bold('[WARNING]')}`; - console.log(`${msg} ${chalk.yellow((err.response?.status && err.response?.data?.message) ?? err.message)}`); + switch (type) { + case 'critical': + header = chalk.red.bold(header); + msg = chalk.red(msg); + break; + case 'warning': + header = chalk.yellow.bold(header); + msg = chalk.yellow(msg); + break; + default: + console.error('Unhandled handler type'); + process.exit(); + }; + + console.log(header, msg); + if (type === 'critical') process.exit(); }; \ No newline at end of file