diff --git a/README.md b/README.md index 5c34937..7ed8355 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,5 @@ # Floriday -Requires [Node.js](https://nodejs.org/en/) v15.14.0 or higher. - The Floriday service project should perform the following tasks: 1. Create / mantain the table structure to allow the storage of the data provided by the Floriday API. @@ -12,13 +10,51 @@ The Floriday service project should perform the following tasks: This is done using the [Sequelize](https://sequelize.org/) ORM and storing the data as it is received from the API, so it can be compared with the previous data. +## Requeriments + +* Git +* Nodejs (v15.14.0 or higher) + +## Installation + +Pull from repository. + +Run this commands on project root directory to install Node dependencies. + +```bash +npm i +``` + +### .env file template + +```bash +#FLORIDAY DATA +CLIENT_ID = xxxxxxxxxx +CLIENT_SECRET = xxxxxxxxxx +API_KEY = xxxxxxxxxx + +#SEQUELIZE CONFIG +DB_SCHEMA = schema +DB_USER = root +DB_PWD = root +DB_HOST = localhost +DB_DIALECT = mariadb + +#GENERAL CONFIG +IS_PRODUCTION = false +SECRETS = true +FORCE_SYNC = true +SYNC_SEQUENCE = true +SYNC_SUPPLIER = true +SYNC_TRADEITEM = true +``` ## Guidelines - In case a new model is created, it should follow the following structure: - /models - - index.js + - main.js - foo.js ### Foo.js @@ -51,7 +87,7 @@ export default (sequelize) => { }; ``` -### Index.js +### main.js ```javascript import foo from "./foo"; @@ -62,32 +98,7 @@ let models = { ``` -To install dependencies: +## Built With -```bash -npm install -``` - -To run: - -```bash -npm start # run the service -npm dev-sync # run and create the db structure -``` - -### .env file - -```bash -# FLORIDAY SERVICE CONFIG -CLIENT_ID = floriday-client_id -CLIENT_SECRET = floriday-client_secret -STATUS = production_or_development - -# SEQUELIZE CONFIG -DB_SCHEMA = edi -DB_USER = root -DB_PWD = root -DB_HOST = localhost -DB_DIALECT = mariadb -FORCE_SYNC = false -``` +* [nodejs](https://nodejs.org/) +* [sequalize](https://sequelize.org/) \ No newline at end of file diff --git a/index.js b/index.js deleted file mode 100644 index 39046c3..0000000 --- a/index.js +++ /dev/null @@ -1,51 +0,0 @@ -import moment from 'moment'; -import * as vnUtils from './utils.js'; -//import cliProgress from 'cli-progress'; - -import dotenv from 'dotenv'; -dotenv.config(); - -import { models } from './models/index.js'; - -console.log = function () { - let args = Array.prototype.slice.call(arguments); - args.unshift(new moment().format('HH:mm:ss') + ' -'); - console.info.apply(console, args); -}; - -let tokenExpirationDate = await vnUtils.getClientToken(models); - -process.env.SYNC_SEQUENCE ? await vnUtils.syncSequence() : null; -process.env.SYNC_SUPPLIER ? await vnUtils.syncSuppliers() : null; -await vnUtils.syncConnections(); -process.env.SYNC_TRADEITEM ? await vnUtils.syncTradeItems() : null; -console.log('Synced trade items'); - -try { - // eslint-disable-next-line no-constant-condition - while (true) { - try{ - console.log('Querying the API to check for new data...'); - console.log('Current token expiration date: ', tokenExpirationDate); - - if (moment().isAfter(tokenExpirationDate)) { - console.log('Token expired, getting a new one...'); - tokenExpirationDate = await vnUtils.getClientToken(models); - } - - await vnUtils.syncSupplyLines(); - - } catch (error) { - console.error(error); - } - - if (process.env.STATUS == 'development') { - await vnUtils.sleep(120000); - } else { - await vnUtils.sleep(300000); - } - } - -} catch (error) { - console.error('Unable to connect to the database:', error); -} \ No newline at end of file diff --git a/main.js b/main.js new file mode 100644 index 0000000..875b02c --- /dev/null +++ b/main.js @@ -0,0 +1,60 @@ +import * as vnUtils from './utils.js'; +import { models } from './models/index.js'; +import moment from 'moment'; + +// Añade la hora a todos los console.log +// console.log = (...args) => console.info(`${new moment().format('HH:mm:ss')} -`, ...args); + +async function main() { + try { + const env = process.env; + let tokenExpirationDate = await vnUtils.getClientToken(models); + + if (JSON.parse(env.SYNC_SEQUENCE)) await vnUtils.syncSequence() + if (JSON.parse(env.SYNC_SUPPLIER)) await vnUtils.syncSuppliers(); + + await vnUtils.syncConnections(); + + if (JSON.parse(env.SYNC_TRADEITEM)) await vnUtils.syncTradeItems(); + + console.log('Synced trade items'); + try { + while (true) { + try{ + console.log('Querying the API to check for new data...'); + + if (moment().isAfter(tokenExpirationDate)) { + console.log('Token expired, getting a new one...'); + tokenExpirationDate = await vnUtils.getClientToken(models); + } + await vnUtils.syncSupplyLines(); + + } catch (err) { + console.error(err); + } + + if (JSON.parse(env.IS_PRODUCTION)) + await vnUtils.sleep(300000); + else + await vnUtils.sleep(120000); + } + } catch (err) { + myErr(err); + } + } catch (err) { + myErr(err); + } +} + +/** + * Creates the connection to the database. + * + * @param {err} + **/ +function myErr(err) { + console.log(`[ERROR]`.red.bold, `${err.name}: ${err.message}`.red); + process.exit(); +} + + +main() \ No newline at end of file diff --git a/models/index.js b/models/index.js index fd4f5a3..2301831 100644 --- a/models/index.js +++ b/models/index.js @@ -1,8 +1,29 @@ import { Sequelize } from 'sequelize'; import dotenv from 'dotenv'; +import colors from 'colors'; +import ora from 'ora'; + dotenv.config(); +console.clear() +console.log( + ` + ███████ ██ ██████ ██████ ██ ██████ █████ ██ ██ ██ + ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ███ + █████ ██ ██ ██ ██████ ██ ██ ██ ███████ ███████ ███ + ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ██ ████ + ██ ██████ ██████ ██ ██ ██ ██████ ██ ██ ███████ ██ + `.green +) let sequelize = createConnection(); +const conSpinner = ora('Creating connection...').start(); +try { + await sequelize.authenticate(); + conSpinner.succeed(); +} catch (err) { + conSpinner.fail(); + myErr(err); +} // Supply Line Models import supplyLine from './supplyLine/supplyLine.js'; @@ -26,9 +47,6 @@ import photos from './tradeItem/photos.js'; import seasonalPeriod from './tradeItem/seasonalPeriod.js'; import characteristics from './tradeItem/characteristics.js'; - - - /** * Contains all the models that are related to the application. * @@ -142,24 +160,45 @@ models.tradeItem.belongsTo(models.supplier, { targetKey: 'organizationId', }); -if (process.env.FORCE_SYNC === 'true') { - console.log('Forcing the models...'); - await sequelize.sync({ force: true }); + +let action, isForce; +if (JSON.parse(process.env.FORCE_SYNC)) { + action = 'Forcing' + isForce = true } else { - console.log('Altering the models...'); - await sequelize.sync({ alter: true }); + action = 'Altering' + isForce = false } -if (process.env.SECRETS) { - await models.clientConfig.findOrCreate({ - where: { - id: 1, - }, - defaults: { - clientId: process.env.CLIENT_ID, - clientSecret: process.env.CLIENT_SECRET, - }, - }); +const modSpinner = ora(`${action} models...`).start(); +try { + await sequelize.sync({ force: isForce }); + if (process.env.SECRETS) { + await models.clientConfig.findOrCreate({ + where: { + id: 1, + }, + defaults: { + clientId: process.env.CLIENT_ID, + clientSecret: process.env.CLIENT_SECRET, + }, + }); + } + modSpinner.succeed(); +} +catch (err) { + modSpinner.fail(); + myErr(err); +} + +/** + * Creates the connection to the database. + * + * @param {err} + **/ +function myErr(err) { + console.log(`[ERROR]`.red.bold, `${err.name}: ${err.message}`.red); + process.exit(); } /** @@ -171,7 +210,6 @@ if (process.env.SECRETS) { * @returns {Sequelize} Sequelize instance with the connection to the database. */ function createConnection() { - console.log('Creating the connection...'); return new Sequelize(process.env.DB_SCHEMA, process.env.DB_USER, process.env.DB_PWD, { host: process.env.DB_HOST, dialect: process.env.DB_DIALECT, @@ -185,4 +223,4 @@ function createConnection() { }); } -export { models } ; +export { models } ; \ No newline at end of file diff --git a/models/supplier/suppliers.js b/models/supplier/suppliers.js index 86ac3ad..27add79 100644 --- a/models/supplier/suppliers.js +++ b/models/supplier/suppliers.js @@ -1,9 +1,15 @@ import { Sequelize } from 'sequelize'; const suppliers = { - isConnected: { - type: Sequelize.BOOLEAN, - defaultValue: false, + sequenceNumber: { + type: Sequelize.INTEGER, + allowNull: false, + }, + companyGln: { + type: Sequelize.STRING, + }, + name: { + type: Sequelize.STRING, }, commercialName: { type: Sequelize.STRING, @@ -18,6 +24,19 @@ const suppliers = { website: { type: Sequelize.STRING, }, + organizationId: { + type: Sequelize.STRING, + primaryKey: true, + }, + rfhRelationId: { + type: Sequelize.INTEGER, + }, + paymentProviders: { + type: Sequelize.STRING, + }, + endDate: { + type: Sequelize.DATE, + }, mailingAddress: { type: Sequelize.JSON, }, @@ -27,31 +46,12 @@ const suppliers = { pythosanitaryNumber: { type: Sequelize.STRING, }, - sequenceNumber: { - type: Sequelize.INTEGER, - allowNull: false, - }, - organizationId: { - type: Sequelize.STRING, - primaryKey: true, - }, - companyGln: { - type: Sequelize.STRING, - }, - name: { - type: Sequelize.STRING, - }, - endDate: { - type: Sequelize.DATE, - }, - rfhRelationId: { - type: Sequelize.INTEGER, - }, organizationType: { type: Sequelize.STRING, }, - paymentProviders: { - type: Sequelize.STRING, + isConnected: { + type: Sequelize.BOOLEAN, + defaultValue: false, }, }; diff --git a/package-lock.json b/package-lock.json index 854eacb..725972f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6,12 +6,12 @@ "": { "name": "floriday", "dependencies": { - "cli-progress": "^3.11.2", "colors": "^1.4.0", "dotenv": "^16.0.3", "mariadb": "^3.0.2", "moment": "^2.29.4", "node-fetch": "^3.3.0", + "ora": "^6.3.0", "sequelize": "^6.26.0", "uuid": "^9.0.0" }, @@ -179,6 +179,7 @@ "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, "engines": { "node": ">=8" } @@ -210,6 +211,35 @@ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/bl": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-5.1.0.tgz", + "integrity": "sha512-tv1ZJHLfTDnXE6tMHv73YgSJaWR2AFuPwMntBe7XL/GBFHnT0CLnsHMogfk5+GzCDC5ZWarSCYaIGATZt9dNsQ==", + "dependencies": { + "buffer": "^6.0.3", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -220,6 +250,29 @@ "concat-map": "0.0.1" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -245,15 +298,37 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "node_modules/cli-progress": { - "version": "3.11.2", - "resolved": "https://registry.npmjs.org/cli-progress/-/cli-progress-3.11.2.tgz", - "integrity": "sha512-lCPoS6ncgX4+rJu5bS3F/iCz17kZ9MPZ6dpuTtI0KXKABkhyXIdYB3Inby1OpaGti3YlI3EeEkM9AuWpelJrVA==", + "node_modules/cli-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-4.0.0.tgz", + "integrity": "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==", "dependencies": { - "string-width": "^4.2.3" + "restore-cursor": "^4.0.0" }, "engines": { - "node": ">=4" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-spinners": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.7.0.tgz", + "integrity": "sha512-qu3pN8Y3qHNgE2AFweciB1IfMnmZ/fsNTEE+NOFjmGB2F/7rLhnhzppvpCnN4FovtP26k8lHyy9ptEbNwWFLzw==", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "engines": { + "node": ">=0.8" } }, "node_modules/color-convert": { @@ -332,6 +407,17 @@ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -365,11 +451,6 @@ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz", "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg==" }, - "node_modules/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" - }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -732,6 +813,25 @@ "node": ">=0.10.0" } }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/ignore": { "version": "5.2.4", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", @@ -787,8 +887,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-extglob": { "version": "2.1.1", @@ -799,14 +898,6 @@ "node": ">=0.10.0" } }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "engines": { - "node": ">=8" - } - }, "node_modules/is-glob": { "version": "4.0.3", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", @@ -819,6 +910,17 @@ "node": ">=0.10.0" } }, + "node_modules/is-interactive": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-2.0.0.tgz", + "integrity": "sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", @@ -828,6 +930,17 @@ "node": ">=8" } }, + "node_modules/is-unicode-supported": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-1.3.0.tgz", + "integrity": "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -907,6 +1020,32 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/log-symbols": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-5.1.0.tgz", + "integrity": "sha512-l0x2DvrW294C9uDCoQe1VSU4gf529FkSZ6leBl4TiqZH/e+0R7hSfHQBNut2mNygDgHwvYHfFLn6Oxb3VWj2rA==", + "dependencies": { + "chalk": "^5.0.0", + "is-unicode-supported": "^1.1.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/lru-cache": { "version": "7.14.1", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.14.1.tgz", @@ -931,6 +1070,14 @@ "node": ">= 12" } }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "engines": { + "node": ">=6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -1017,6 +1164,20 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.1", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", @@ -1034,6 +1195,64 @@ "node": ">= 0.8.0" } }, + "node_modules/ora": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/ora/-/ora-6.3.0.tgz", + "integrity": "sha512-1/D8uRFY0ay2kgBpmAwmSA404w4OoPVhHMqRqtjvrcK/dnzcEZxMJ+V4DUbyICu8IIVRclHcOf5wlD1tMY4GUQ==", + "dependencies": { + "chalk": "^5.0.0", + "cli-cursor": "^4.0.0", + "cli-spinners": "^2.6.1", + "is-interactive": "^2.0.0", + "is-unicode-supported": "^1.1.0", + "log-symbols": "^5.1.0", + "stdin-discarder": "^0.1.0", + "strip-ansi": "^7.0.1", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.2.0.tgz", + "integrity": "sha512-ree3Gqw/nazQAPuJJEy+avdl7QfZMcUvmHIKgEZkGL+xOBzRvup5Hxo6LHuMceSxOabuJLJm5Yp/92R9eMmMvA==", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ora/node_modules/strip-ansi": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.0.1.tgz", + "integrity": "sha512-cXNxvT8dFNRVfhVME3JAe98mkXDYN2O1l7jmcwMnOslDeESg1rF/OZMtK0nRAhiari1unG5cD4jG3rapUAkLbw==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -1146,6 +1365,19 @@ } ] }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/regexpp": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", @@ -1167,6 +1399,21 @@ "node": ">=4" } }, + "node_modules/restore-cursor": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-4.0.0.tgz", + "integrity": "sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/retry-as-promised": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-6.1.0.tgz", @@ -1220,6 +1467,25 @@ "queue-microtask": "^1.2.2" } }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", @@ -1348,23 +1614,38 @@ "node": ">=8" } }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" + }, + "node_modules/stdin-discarder": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/stdin-discarder/-/stdin-discarder-0.1.0.tgz", + "integrity": "sha512-xhV7w8S+bUwlPTb4bAOUQhv8/cSS5offJuX8GQGq32ONF0ZtDWKfkdomM3HMRA+LhX6um/FZ0COqlwsjD53LeQ==", "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" + "bl": "^5.0.0" }, "engines": { - "node": ">=8" + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dependencies": { + "safe-buffer": "~5.2.0" } }, "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -1440,6 +1721,11 @@ "punycode": "^2.1.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, "node_modules/uuid": { "version": "9.0.0", "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", @@ -1456,6 +1742,14 @@ "node": ">= 0.10" } }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "dependencies": { + "defaults": "^1.0.3" + } + }, "node_modules/web-streams-polyfill": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.1.tgz", diff --git a/package.json b/package.json index c59cf80..9bfd4dc 100644 --- a/package.json +++ b/package.json @@ -2,20 +2,13 @@ "name": "floriday", "module": "index.ts", "type": "module", - "scripts": { - "start": "node index.js", - "dev-sync": "FORCE_SYNC=true node --max-old-space-size=4096 index.js", - "dev-secrets": "SECRETS=true node --max-old-space-size=4096 index.js", - "dev-both": "FORCE_SYNC=true SECRETS=true node --max-old-space-size=4096 index.js", - "dev-query": "QUERYSUPPLIERS=true SECRETS=true node --max-old-space-size=4096 index.js" - }, "dependencies": { - "cli-progress": "^3.11.2", "colors": "^1.4.0", "dotenv": "^16.0.3", "mariadb": "^3.0.2", "moment": "^2.29.4", "node-fetch": "^3.3.0", + "ora": "^6.3.0", "sequelize": "^6.26.0", "uuid": "^9.0.0" }, diff --git a/utils.js b/utils.js index d1ece67..1c0d85a 100644 --- a/utils.js +++ b/utils.js @@ -1,18 +1,15 @@ import moment from 'moment'; import fetch from 'node-fetch'; -import dotenv from 'dotenv'; import { models } from './models/index.js'; import { v4 as uuidv4 } from 'uuid'; import colors from 'colors'; -//import cliProgress from 'cli-progress'; -dotenv.config(); +import ora from 'ora'; -/** - * The Endpoint where the Access Token is requested - */ -const _accessTokenEndpoint = 'https://idm.staging.floriday.io/oauth2/ausmw6b47z1BnlHkw0h7/v1/token'; +// The Endpoint where the Token is requested +const tokenEndpoint = 'https://idm.staging.floriday.io/oauth2/ausmw6b47z1BnlHkw0h7/v1/token'; -const BASE_CUSTOMER_URL = 'https://api.staging.floriday.io/customers-api/2022v2/'; +// URL of API +const url = 'https://api.staging.floriday.io/customers-api/2022v2'; /** * Gets the Access Token from the client config table @@ -24,8 +21,10 @@ async function getClientToken() { const clientConfigData = await models.clientConfig.findAll(); if (!clientConfigData[0]) - throw colors.red.bold('Token has expired') + throw colors.red.bold('No data found in the configuration table, ', + 'if you have configured the .env file, declare the variable SECRET as true') + const spinner = ora(`Requesting token...`).start(); const now = moment().format('YYYY-MM-DD HH:mm:ss'); const tokenExpirationDate = clientConfigData[0].tokenExpiration; @@ -33,7 +32,7 @@ async function getClientToken() { let clientId = clientConfigData[0].clientId; let clientSecret = clientConfigData[0].clientSecret; - const tokenRequest = await fetch(_accessTokenEndpoint, { + const tokenRequest = await fetch(tokenEndpoint, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', @@ -44,11 +43,11 @@ async function getClientToken() { const tokenResponse = await tokenRequest.json(); if (tokenRequest.status === 200) { - console.log('Token request successful'); + spinner.succeed(); + } else { - throw new Error( - `Token request failed with status ${tokenRequest.status}` - ); + spinner.fail(); + throw new Error(`Token request failed with status: ${tokenRequest.status} - ${tokenRequest.statusText}`); } const accessToken = tokenResponse.access_token; @@ -67,7 +66,8 @@ async function getClientToken() { return tokenExpirationDate; } else { - console.log('Using the current token...'); + spinner.text = 'Using stored token...' + spinner.succeed(); return tokenExpirationDate; } } @@ -83,7 +83,7 @@ async function getClientToken() { */ async function updateClientConfig(clientId, clientSecret, accessToken, tokenExpirationDate) { try { - console.log('Updating the client config with the new token...'); + const spinner = ora(`Updating config...`).start(); await models.clientConfig.upsert({ id: 1, clientId: clientId, @@ -92,9 +92,9 @@ async function updateClientConfig(clientId, clientSecret, accessToken, tokenExpi tokenExpiration: tokenExpirationDate, requestLimit: 500, }); - console.log('Client config updated, new Token set'); - console.log('New token expiration date: ', tokenExpirationDate); + spinner.succeed(); } catch (error) { + spinner.fail(); console.log('There was a error while updating the client config'); console.log(error); } @@ -168,23 +168,29 @@ async function asyncQueue(fnArray, concurrency = 1) { * @param {Number} maximumSequenceNumber - maximum sequence number * @returns */ -async function syncSequence(current = 0, model = null ,maximumSequenceNumber = 0){ +async function syncSequence(current = 0, model = null , maximumSequenceNumber = 0){ if (model == null && current == 0){ - - let mockModels = ['suppliers','tradeItems','supplyLines',]; - - for (let i = 0; i < mockModels.length; i++) { - const element = mockModels[i]; - console.log('Syncing sequence for: ', element); - await syncSequence(0, element); + try { + let mockModels = [ + 'suppliers', + 'tradeItems', + 'supplyLines', + ]; + const spinner = ora(`Syncing sequence...`).start(); + for (let mockModel in mockModels) { + const element = mockModels[mockModel]; + await syncSequence(0, element); + } + spinner.succeed(); + } catch (err) { + spinner.fail(); + throw(err); } - - } else { + } else if (current) { let tx = await models.sequelize.transaction(); try { - let sequence = await models.sequenceNumber.findOrCreate({ where: { model: model @@ -213,63 +219,74 @@ async function syncSequence(current = 0, model = null ,maximumSequenceNumber = 0 } catch (error) { await tx.rollback(); - console.log('Error while syncing sequence number for: ', model); + console.log(`Error while syncing sequence number for: ${model}: ${error}`); } } } async function syncSuppliers(){ - - let headers = { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${await getJWT()}`, - 'X-Api-Key': process.env.API_KEY - }; - - let maximumSequenceNumber = await fetch(`${BASE_CUSTOMER_URL}organizations/current-max-sequence`, { - method: 'GET', - headers: headers - }); - - maximumSequenceNumber = await maximumSequenceNumber.json(); - - console.log('Maximum sequence number: ', maximumSequenceNumber); - - for (let i = 0; i < maximumSequenceNumber; i++) { - - let query = `${BASE_CUSTOMER_URL}organizations/sync/${i}?organizationType=SUPPLIER&limit=1000`; - let response = await fetch(query, { + try { + const spinner = ora('Preparing to load suppliers...').start(); + let headers = { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${await getJWT()}`, + 'X-Api-Key': process.env.API_KEY + }; + + const response = await fetch(`${url}/organizations/current-max-sequence`, { method: 'GET', headers: headers }); - - let data = await response.json(); - - let suppliers = data.results; - - for (let supplier of suppliers) { - i = supplier.sequenceNumber; - await models.supplier.upsert({ - isConnected: false, - commercialName: supplier.commercialName, - email: supplier.email, - phone: supplier.phone, - website: supplier.website, - mailingAddress: supplier.mailingAddress, - physicalAddress: supplier.physicalAddress, - pythosanitaryNumber: supplier.pythosanitaryNumber, - sequenceNumber: supplier.sequenceNumber, - organizationId: supplier.organizationId, - companyGln: supplier.companyGln, - name: supplier.name, - endDate: supplier.endDate, - rfhRelationId: supplier.rfhRelationId, - organizationType: supplier.organizationType, - paymentProviders: `${supplier.paymentProviders}`, + const maxSequenceNumber = await response.json(); + let timeFinish, timeToGoSec, timeToGoMin, timeLeft; + for (let curSequenceNumber = 0; curSequenceNumber <= maxSequenceNumber; curSequenceNumber++) { + let timeStart = new moment(); + + let query = `${url}/organizations/sync/${curSequenceNumber}?organizationType=SUPPLIER`; + let response = await fetch(query, { + method: 'GET', + headers: headers }); - console.log('INSERTED:\t', supplier.commercialName, '\nsequenceNumber:\t', supplier.sequenceNumber); + let data = await response.json(); + + let suppliers = data.results; + for (let supplier of suppliers) { + curSequenceNumber = supplier.sequenceNumber; + spinner.text = `Loading suppliers, ${maxSequenceNumber - curSequenceNumber} are missing` + if (timeFinish) + spinner.text = spinner.text + ` (${timeLeft})` + await models.supplier.upsert({ + sequenceNumber: supplier.sequenceNumber, + companyGln: supplier.companyGln, + name: supplier.name, + commercialName: supplier.commercialName, + email: supplier.email, + phone: supplier.phone, + website: supplier.website, + organizationId: supplier.organizationId, + rfhRelationId: supplier.rfhRelationId, + paymentProviders: `${supplier.paymentProviders}`, + endDate: supplier.endDate, + mailingAddress: supplier.mailingAddress, + physicalAddress: supplier.physicalAddress, + pythosanitaryNumber: supplier.pythosanitaryNumber, + organizationType: supplier.organizationType + }); + } + await syncSequence(curSequenceNumber, 'suppliers', maxSequenceNumber); + timeFinish = new moment(); + timeToGoSec = (timeFinish.diff(timeStart, 'seconds') * (maxSequenceNumber - curSequenceNumber) / 1000) + timeToGoMin = Math.trunc(timeToGoSec / 60) + if (!timeToGoMin) + timeLeft = `${Math.trunc(timeToGoSec)} sec` + else + timeLeft = `${timeToGoMin} min` } - await syncSequence(i, 'suppliers', maximumSequenceNumber); + spinner.succeed() + } + catch (err) { + spinner.fail(); + throw(err); } } @@ -282,7 +299,7 @@ async function syncConnections(){ 'X-Api-Key': process.env.API_KEY }; - let remoteConnections = await fetch(`${BASE_CUSTOMER_URL}connections`, { + let remoteConnections = await fetch(`${url}/connections`, { method: 'GET', headers: headers }); @@ -298,7 +315,7 @@ async function syncConnections(){ if (remoteConnection == undefined){ console.log('Connection: ', connection, 'does not exist in the remote server'); console.log('Creating remote connection'); - await fetch(`${BASE_CUSTOMER_URL}connections/${connection.organizationId}`, { + await fetch(`${url}/connections/${connection.organizationId}`, { method: 'PUT', headers: headers }); @@ -331,27 +348,23 @@ async function syncConnections(){ } async function syncTradeItems(){ - const suppliers = await models.supplier.findAll(); - - let headers = { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${await getJWT()}`, - 'X-Api-Key': process.env.API_KEY - }; - let i = 0; - console.log('Syncing trade items'); for (let supplier of suppliers) { i++; + if (!supplier.isConnected){ console.log('Supplier: ', supplier.commercialName, 'is not connected'); console.log('Skipping supplier', supplier.commercialName, '(', i, '/', suppliers.length, ')'); continue; } - let query = `${BASE_CUSTOMER_URL}trade-items?supplierOrganizationId=${supplier.organizationId}`; - + let query = `${url}/trade-items?supplierOrganizationId=${supplier.organizationId}`; + let headers = { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${await getJWT()}`, + 'X-Api-Key': process.env.API_KEY + }; try { let request = await fetch(query, { @@ -416,7 +429,8 @@ async function syncSupplyLines(){ 'Content-Type': 'application/json', 'Authorization': `Bearer ${await getJWT()}`, 'X-Api-Key': process.env.API_KEY - }; + }; + // Launch a promise for each supplier for (let tradeItem of tradeItems) { let supplier = suppliers.find(supplier => supplier.organizationId == tradeItem.supplierOrganizationId); @@ -425,7 +439,7 @@ async function syncSupplyLines(){ let promise = new Promise(async (resolve) => { try { - let url = `${BASE_CUSTOMER_URL}supply-lines/sync/0?supplierOrganizationId=${supplier.organizationId}&tradeItemId=${tradeItem.tradeItemId}&limit=100&postFilterSelectedTradeItems=false`; + let url = `${url}/supply-lines/sync/0?supplierOrganizationId=${supplier.organizationId}&tradeItemId=${tradeItem.tradeItemId}&limit=100&postFilterSelectedTradeItems=false`; let request = await fetch(url, { method: 'GET', @@ -472,7 +486,7 @@ async function syncSupplyLines(){ console.log('Trade item not found for supply line: ', line.supplyLineId); console.log('Requesting data for trade item id: ', line.tradeItemId); - let urlTradeItem = `${BASE_CUSTOMER_URL}trade-items?tradeItemIds=${line.tradeItemId}`; + let urlTradeItem = `${url}/trade-items?tradeItemIds=${line.tradeItemId}`; let queryTradeItem = await fetch(urlTradeItem, { method: 'GET',