refs #4823 Added new functions of ora library

This commit is contained in:
Guillermo Bonet 2023-06-13 12:51:59 +02:00
parent d2e6cab7d3
commit 6e2a304a69
4 changed files with 225 additions and 144 deletions

View File

@ -17,14 +17,14 @@ DB_TIMEZONE = Europe/Madrid
#GENERAL CONFIG
IS_PRODUCTION = false
MS_PRODUCTION_SCHEDULE = 300000
MS_TEST_SCHEDULE = 100000
SECRETS = true
FORCE_SYNC = true
MS_PRODUCTION_SCHEDULE = 900000
MS_TEST_SCHEDULE = 300000
USE_SECRETS_DB = true
FORCE_SYNC = false
TIME_STAMPS = true
#REQUEST CONFIG
MS_RETRY_UNHANDLED_ERROR = 900000
#DEV OPTIONS
ORGS_ALWAYS_CONN = false
APPLY_ORG_FILTER = false

View File

@ -3,9 +3,11 @@ import * as utils from './utils.js';
import moment from 'moment';
import chalk from 'chalk';
// Añade la hora a todos los console.log
console.log = (...args) => console.info(chalk.gray(`[${new moment().format('YYYY-MM-DD hh:mm:ss A')}]`), ...args);
const env = process.env;
if (JSON.parse(env.TIME_STAMPS)) // Add time to all console.log
console.log = (...args) => console.info(chalk.gray(`[${new moment().format('YYYY-MM-DD hh:mm:ss A')}]`), ...args);
class Floriday {
async start() {
try {
@ -14,7 +16,7 @@ class Floriday {
} catch (err) {
utils.criticalError(err);
}
}
};
async tryConn() {
while (true)
@ -24,8 +26,8 @@ class Floriday {
await checkCon();
await this.schedule();
}
catch (err) {}
}
catch (err) {};
};
async schedule () {
try {
@ -42,9 +44,9 @@ class Floriday {
}
}
} catch (err) {
throw new Error(err);
}
}
throw err;
};
};
async trunk() {
try{
@ -58,19 +60,23 @@ class Floriday {
for (let model of models)
await utils.syncModel(model);
await utils.syncConnections();
// await utils.syncConnections();
} catch (err) {
if (err.name === 'SequelizeConnectionRefusedError') throw err;
utils.criticalError(err);
}
}
};
async stop() {
try {
this.stopSchedule = false;
await closeCon();
console.warn(chalk.dim('Bye, come back soon 👋'))
} catch (err) {
utils.criticalError(err);
}
}
};
};
export default Floriday;

View File

@ -1,13 +1,12 @@
import { Sequelize } from 'sequelize';
import dotenv from 'dotenv';
import chalk from 'chalk';
import ora from 'ora';
import fs from 'fs';
import { criticalError } from '../utils.js';
import * as utils from '../utils.js';
dotenv.config();
const env = process.env;
let sequelize;
console.clear()
console.log(chalk.hex('#06c581')(
`
@ -19,15 +18,13 @@ console.log(chalk.hex('#06c581')(
`
))
let sequelize, spinner;
try {
spinner = ora('Creating database connection...').start();
sequelize = createConn();
await utils.startSpin('Creating database connection...', true);
sequelize = await createConn();
await checkCon();
spinner.succeed();
await utils.okSpin();
} catch (err) {
spinner.fail();
criticalError(err);
await utils.criticalSpin(err);
}
// Conf Models
@ -56,6 +53,7 @@ import packingConfiguration from './tradeItem/packingConfiguration.js';
import photo from './tradeItem/photo.js';
import seasonalPeriod from './tradeItem/seasonalPeriod.js';
import characteristic from './tradeItem/characteristic.js';
import { start } from 'repl';
/**
* Contains all the models that are related to the application.
@ -162,13 +160,13 @@ try {
targetKey: 'organizationId',
});*/
} catch (err) {
criticalError(err);
utils.criticalError(err);
}
try {
const action = JSON.parse(env.FORCE_SYNC) ? { force: true } : { alter: true };
const actionMsg = JSON.parse(env.FORCE_SYNC) ? 'Forcing' : 'Altering';
const spinner = ora(`${actionMsg} models...`).start();
await utils.startSpin(`${actionMsg} models...`, true);
await sequelize.sync(action);
// Create views
@ -177,11 +175,10 @@ try {
// Create procedures
sequelize.query(fs.readFileSync('routines/procedures/offerRefresh.sql', 'utf-8'));
spinner.succeed();
await utils.okSpin();
}
catch (err) {
if (spinner) spinner.fail();
criticalError(err);
await utils.criticalSpin(err)
}
/**
@ -189,7 +186,7 @@ catch (err) {
*
* @returns {Sequelize} Sequelize instance with the connection to the database.
*/
function createConn() {
async function createConn() {
return new Sequelize(env.DB_SCHEMA, env.DB_USER, env.DB_PWD, {
host: env.DB_HOST,
port: env.DB_PORT,
@ -219,13 +216,12 @@ async function checkCon() {
* Close the connection to the database
*/
async function closeCon() {
const spinner = ora('Closing database connection...').start();
utils.startSpin('Closing database connection...', true);
try {
await sequelize.close()
spinner.succeed();
await utils.okSpin();
} catch (err) {
spinner.fail();
criticalError(err)
await utils.criticalSpin(err);
}
}

281
utils.js
View File

@ -7,6 +7,7 @@ import chalk from 'chalk';
import ora from 'ora';
const env = process.env;
let spinner;
/**
* Gets the Access Token.
@ -14,7 +15,8 @@ const env = process.env;
* @param {Boolean} isForce Force to request new token
*/
export async function requestToken(isForce = false) {
let spinner = ora(`Requesting new token...`).start();
await startSpin(`Requesting new token...`, true);
let optionalMsg;
try {
const clientConfigData = await models.config.findOne();
@ -35,7 +37,7 @@ export async function requestToken(isForce = false) {
scope: 'role:app catalog:read supply:read organization:read network:write network:read'
}).toString();
const headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
const response = (await vnRequest('POST', env.API_ENDPOINT, data, headers, spinner)).data;
const response = (await vnRequest('POST', env.API_ENDPOINT, data, headers)).data;
const tokenExpiration = moment()
.add(response.expires_in, 's')
@ -47,55 +49,53 @@ export async function requestToken(isForce = false) {
currentToken: response.access_token,
tokenExpiration,
});
spinner.succeed();
} else
spinner.succeed('Using stored token...');
optionalMsg = 'Using stored token...';
await okSpin(optionalMsg, true);
} catch (err) {
spinner.fail();
throw err;
}
await failSpin(err, true);
}
};
/**
* Returns the current token.
*
* @returns {string}
* @returns {String} The current token
*/
export async function getCurrentToken() {
return (await models.config.findOne()).currentToken;
}
};
/**
* Check the floriday data config.
*/
export async function checkConfig() {
const spinner = ora(`Checking config...`).start();
await startSpin(`Checking config...`, true);
const excludedEnvVars = ['VSCODE_GIT_ASKPASS_EXTRA_ARGS'];
const requiredEnvVars = Object.keys(env);
const filteredEnvVars = requiredEnvVars.filter(reqEnvVar => !excludedEnvVars.includes(reqEnvVar));
for (const reqEnvVar of filteredEnvVars) {
if (!process.env[reqEnvVar]) {
spinner.fail();
throw new Error(`You haven't provided the ${reqEnvVar} environment variable`);
}
}
for (const reqEnvVar of filteredEnvVars)
if (!process.env[reqEnvVar])
await failSpin(new Error(`You haven't provided the ${reqEnvVar} environment variable`), true);
const clientConfigData = await models.config.findOne();
if (!clientConfigData)
await updateClientConfig(env.CLIENT_ID, env.CLIENT_SECRET);
spinner.succeed();
}
await okSpin(null, true);
};
/**
* Returns the expiration of current token.
*
* @returns {string}
* @returns {String} The expiration of current token
*/
export async function getCurrentTokenExpiration() {
return (await models.config.findOne()).tokenExpiration;
}
};
/**
* Updates the access token in the client config table.
@ -113,7 +113,7 @@ export async function updateClientConfig(clientConfig) {
} catch (err) {
throw(err);
}
}
};
/**
* Pauses the execution of the script for the specified number of milliseconds.
@ -122,15 +122,15 @@ export async function updateClientConfig(clientConfig) {
*/
export async function sleep(ms) {
await new Promise(resolve => setTimeout(resolve, ms));
}
};
/**
* Sync a model.
*
* @param {String} model (organization | tradeItem | supplyLine | clockPresaleSupply)
* @param {String} model Supported models (organization | warehouse | tradeItem | supplyLine | clockPresaleSupply)
*/
export async function syncModel(model) {
let spinner = ora(`Syncing ${model}...`).start();
await startSpin(`Syncing ${model}...`, true);
let i = 1;
try {
const dbSeqNum = await models.sequenceNumber.findOne({ where: { model } })
@ -162,7 +162,7 @@ export async function syncModel(model) {
throw new Error('Unsupported model');
}
const maxSeqNum = (await vnRequest('GET', maxSeqUrl, null, null, spinner)).data;
const maxSeqNum = (await vnRequest('GET', maxSeqUrl)).data;
for (curSeqNum; curSeqNum < maxSeqNum; curSeqNum++) {
let params, misSeqNum;
if (model === 'organization')
@ -170,11 +170,11 @@ export async function syncModel(model) {
else if (model === 'supplyLine')
params = new URLSearchParams({postFilterSelectedTradeItems: false}).toString();
const res = (await vnRequest('GET', `${syncUrl}${curSeqNum}${params ? `?${params}` : ''}`, null, null, spinner)).data;
const res = (await vnRequest('GET', `${syncUrl}${curSeqNum}${params ? `?${params}` : ''}`)).data;
curSeqNum = res.maximumSequenceNumber;
const objects = res.results;
misSeqNum = maxSeqNum - curSeqNum;
spinner.text = `Syncing ${i - 1} ${model}, ${misSeqNum} missing...`;
txtSpin(`Syncing ${i - 1} ${model}, ${misSeqNum} missing...`);
for (let object of objects) {
switch (model) {
case 'organization':
@ -196,22 +196,20 @@ export async function syncModel(model) {
throw new Error('Unsupported model');
}
spinner.text = `Syncing ${i++} ${model}, ${misSeqNum} missing...`
txtSpin(`Syncing ${i++} ${model}, ${misSeqNum} missing...`);
};
await insertSequenceNumber(model, curSeqNum);
}
if (curSeqNum < maxSeqNum)
await insertSequenceNumber(model, maxSeqNum);
spinner.text = (i != 1)
txtSpin((i != 1)
? `Syncing ${i} ${model}...`
: `Syncing ${model}... ${chalk.gray('(Not found)')}`;
spinner.succeed();
: `Syncing ${model}...`);
await okSpin(null, true);
} catch (err) {
spinner.fail();
throw err;
}
await failSpin(err, true);
}
};
/**
@ -220,7 +218,6 @@ export async function syncModel(model) {
export async function syncConnections(){
await deleteConnections();
let spinner;
try {
let connectionsInDb = await models.organization.findAll({
where: {
@ -234,7 +231,7 @@ export async function syncConnections(){
}
});
const connectionsInFloriday = (await vnRequest('GET', `${env.API_URL}/connections`, null, null, spinner)).data;
const connectionsInFloriday = (await vnRequest('GET', `${env.API_URL}/connections`)).data;
let isExists = false, connectionsToPut = [];
for (let connectionInDb of connectionsInDb) {
@ -247,25 +244,25 @@ export async function syncConnections(){
isExists = false;
}
if (connectionsToPut.length) spinner = ora(`Creating connections in Floriday...`).start();
if (connectionsToPut.length)
await startSpin(`Creating connections in Floriday...`, true);
let i = 1;
for (let connection of connectionsToPut) {
spinner.text = `Creating ${i++} of ${connectionsToPut.length} connections in Floriday...`
await vnRequest('PUT', `${env.API_URL}/connections/${connection}`, null, null, spinner);
txtSpin(`Creating ${i++} of ${connectionsToPut.length} connections in Floriday...`);
await vnRequest('PUT', `${env.API_URL}/connections/${connection}`);
}
if (spinner) spinner.succeed();
await okSpin(null, true);
} catch (err) {
if (spinner) spinner.fail();
throw new Error(err);
}
await failSpin(err, true);
}
};
/**
* Insert sequence number in the database.
*
* @param {String} model
* @param {Number} sequenceNumber
* @param {String} model The model identifier
* @param {Number} sequenceNumber The sequence number
*/
export async function insertSequenceNumber(model, sequenceNumber) {
const tx = await models.sequelize.transaction();
@ -279,12 +276,12 @@ export async function insertSequenceNumber(model, sequenceNumber) {
await tx.rollback();
throw err;
}
}
};
/**
* Insert trade item and dependences in the database.
*
* @param {Array} tradeItem
* @param {Array} tradeItem An array containing the tradeItem data to be inserted
*/
export async function insertTradeItem(tradeItem) {
const tx = await models.sequelize.transaction();
@ -371,12 +368,12 @@ export async function insertTradeItem(tradeItem) {
await tx.rollback();
throw err;
}
}
};
/**
* Insert clock presales supply in the database.
*
* @param {Array} clockPresaleSupply
* @param {Array} clockPresaleSupply An array containing the clockPresaleSupply data to be inserted
*/
export async function insertClockPresalesSupply(clockPresaleSupply) {
const tx = await models.sequelize.transaction();
@ -392,12 +389,12 @@ export async function insertClockPresalesSupply(clockPresaleSupply) {
await tx.rollback();
throw err;
}
}
};
/**
* Insert warehouse in the database.
*
* @param {Array} warehouse
* @param {Array} warehouse An array containing the warehouse data to be inserted
*/
export async function insertWarehouse(warehouse) {
const tx = await models.sequelize.transaction();
@ -417,12 +414,12 @@ export async function insertWarehouse(warehouse) {
await tx.rollback();
throw err;
}
}
};
/**
* Insert organization in the database.
*
* @param {Array} organization
* @param {Array} organization An array containing the organization data to be inserted
*/
export async function insertOrganization(organization) {
const tx = await models.sequelize.transaction();
@ -437,12 +434,12 @@ export async function insertOrganization(organization) {
await tx.rollback();
throw err;
}
}
};
/**
* Insert supply line and dependences in the database.
*
* @param {Array} supplyLine
* @param {Array} supplyLine An array containing the supply line data to be inserted
*/
export async function insertSupplyLine(supplyLine) {
const tx = await models.sequelize.transaction();
@ -452,14 +449,14 @@ export async function insertSupplyLine(supplyLine) {
where: { warehouseId: supplyLine.warehouseId }
});
if (!warehouse) {
let warehouse = (await vnRequest('GET', `${env.API_URL}/warehouses/${supplyLine.warehouseId}`, null, null, null)).data;
let warehouse = (await vnRequest('GET', `${env.API_URL}/warehouses/${supplyLine.warehouseId}`)).data;
// Check if the organization exists, and if it doesn't, create it
let organization = await models.organization.findOne({
where: { organizationId: warehouse.organizationId }
}, { transaction: tx });
if (!organization) {
let organization = (await vnRequest('GET', `${env.API_URL}/organizations/${warehouse.organizationId}`, null, null, null)).data;
let organization = (await vnRequest('GET', `${env.API_URL}/organizations/${warehouse.organizationId}`)).data;
await insertOrganization(organization);
}
@ -471,7 +468,7 @@ export async function insertSupplyLine(supplyLine) {
where: { tradeItemId: supplyLine.tradeItemId }
}, { transaction: tx });
if (!tradeItem) {
let tradeItem = (await vnRequest('GET', `${env.API_URL}/trade-items/${supplyLine.tradeItemId}`, null, null, null)).data;
let tradeItem = (await vnRequest('GET', `${env.API_URL}/trade-items/${supplyLine.tradeItemId}`)).data;
await insertTradeItem(tradeItem);
}
@ -512,16 +509,15 @@ export async function insertSupplyLine(supplyLine) {
await tx.rollback();
throw err;
}
}
};
/**
* Removes Floriday connections that we don't have in the database.
**/
export async function deleteConnections() {
let spinner;
try {
let i = 1;
const connectionsInFloriday = (await vnRequest('GET', `${env.API_URL}/connections`, null, null, spinner)).data;
const connectionsInFloriday = (await vnRequest('GET', `${env.API_URL}/connections`)).data;
const connectionsInDb = await models.organization.findAll({
attributes: ['organizationId'],
where: { isConnected: true }
@ -538,31 +534,30 @@ export async function deleteConnections() {
isExists = false;
}
if (ghostConnections.length) spinner = ora(`Deleting connections that aren't in the db...`).start();
if (ghostConnections.length)
await startSpin(`Deleting connections that aren't in the db...`, true);
for (let connection of ghostConnections) {
await vnRequest('DELETE', `${env.API_URL}/connections/${connection}`, null, null, spinner);
spinner.text = `Deleting ${i++} of ${ghostConnections.length} connections that aren't in the db...`
await vnRequest('DELETE', `${env.API_URL}/connections/${connection}`);
txtSpin(`Deleting ${i++} of ${ghostConnections.length} connections that aren't in the db...`);
}
if (spinner) spinner.succeed();
await okSpin(null, true);
} catch (err) {
if (spinner) spinner.fail();
criticalError(err);
}
await criticalSpin(err);
}
};
/**
* Perform a REST request.
*
* @param {String} url
* @param {String} method
* @param {Array} body
* @param {Array} header
* @param {Array} spinner
* @param {String} url The URL of the REST request
* @param {String} method The HTTP method of the request (e.g., GET, POST, PUT, DELETE)
* @param {Array} body The body of the request, typically an array of data to be sent
* @param {Array} header The headers of the request, typically an array of key-value pairs
*
* @return {Array}
* @return {Array} An array containing the response data from the REST request
**/
export async function vnRequest(method, url, data, headers, spinner) {
export async function vnRequest(method, url, data, headers) {
if (!headers)
headers = {
'Content-Type': 'application/json',
@ -579,10 +574,9 @@ export async function vnRequest(method, url, data, headers, spinner) {
switch (err.code) {
case 'ECONNRESET': // Client network socket TLS
case 'EAI_AGAIN': // getaddrinfo
if (spinner) spinner.warn();
warning(err);
await warnSpin(null, err, false);
await sleep(1000);
if (spinner) spinner.start();
await startSpin(null, false);
break;
case 'ECONNABORTED':
case 'ECONNREFUSED':
@ -592,62 +586,147 @@ export async function vnRequest(method, url, data, headers, spinner) {
return err;
case 504:
case 502:
if (spinner) spinner.warn();
warning(err);
await warnSpin(null, err, false);
await sleep(1000);
if (spinner) spinner.start();
await startSpin(null, false);
break;
case 429: // Too Many Requests
if (spinner) spinner.warn();
warning(err);
await warnSpin(null, err, false);
await sleep(60000);
if (spinner) spinner.start();
await startSpin(null, false);
break;
case 401: // Unauthorized
if (spinner) spinner.warn();
warning(err);
await warnSpin(null, err, false);
await requestToken(true);
headers.Authorization
? headers.Authorization = `Bearer ${await getCurrentToken()}`
: criticalError(err);
if (spinner) spinner.start();
await startSpin(null, false);
break;
default:
if (spinner) spinner.warn();
warning(err);
await warnSpin(null, err, false);
await sleep(env.MS_RETRY_UNHANDLED_ERROR);
if (spinner) spinner.start();
await startSpin(null, false);
break;
}
break;
default:
if (spinner) spinner.warn();
warning(err);
await warnSpin(null, err, false);
await sleep(env.MS_RETRY_UNHANDLED_ERROR);
if (spinner) spinner.start();
await startSpin(null, false);
break;
}
}
}
};
/**
* Sets the text of spinner.
*
* @param {String} msg Text of spinner
* @param {Boolean} isNew Reinstantiate the object
**/
export async function startSpin(msg, isNew) {
if (JSON.parse(env.TIME_STAMPS) && msg)
msg = `${chalk.gray(`[${new moment().format('YYYY-MM-DD hh:mm:ss A')}]`)} ${msg}`;
(isNew)
? spinner = ora(msg).start()
: spinner.start();
};
/**
* Sets the text of spinner.
*
* @param {String} msg Text of spinner
* @param {Boolean} clear Clean the instance
**/
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}`;
spinner.text = msg
};
/**
* Sets the spinner to ok.
*
* @param {String} msg Text of spinner
* @param {Boolean} clear Clean the instance
**/
export async function okSpin(msg, clear) {
if (JSON.parse(env.TIME_STAMPS) && msg)
msg = `${chalk.gray(`[${new moment().format('YYYY-MM-DD hh:mm:ss A')}]`)} ${msg ?? ''}`;
if (spinner) {
spinner.succeed(msg);
if (clear)
spinner.clear();
}
};
/**
* Sets the spinner to waning and throw a warning.
*
* @param {String} msg Text of spinner
* @param {Error} err Error object
* @param {Boolean} clear Clean the instance
**/
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}`;
if (spinner) {
spinner.warn(msg);
if (clear)
spinner.clear();
}
if (err) await warning(err);
};
/**
* Sets the spinner to fail and throw a error.
*
* @param {Error} err Error object
* @param {Boolean} clear Clean the instance
**/
export async function failSpin(err, clear) {
if (spinner) {
spinner.fail();
if (clear)
spinner.clear();
}
throw err;
};
/**
* Sets the spinner to fail and throw a critical error.
*
* @param {Error} err Error object
**/
export async function criticalSpin(err) {
if (spinner) {
spinner.fail();
spinner.clear();
}
await criticalError(err);
};
/**
* Critical error.
*
* @param {err}
* @param {Error} err Error object
**/
export async function criticalError(err) {
console.log(chalk.red.bold(`[CRITICAL]`), chalk.red(err.message));
process.exit();
}
};
/**
* Warning.
*
* @param {err}
* @param {Error} err
**/
export async function warning(err) {
(err.response?.status && err.response?.data?.message)
? (console.log(chalk.yellow.bold(`[WARNING]`), chalk.yellow(`${err.response.status} - ${err.response.data.message}`)))
: (console.log(chalk.yellow.bold(`[WARNING]`), chalk.yellow(err.message)));
}
console.log(chalk.yellow.bold(`[WARNING]`), (err.response?.status && err.response?.data?.message) ?? chalk.yellow(err.message));
};