diff --git a/CHANGELOG.md b/CHANGELOG.md index 77b0e328..08fa03fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,8 +14,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added jest support - @resubaka (#321) - Added caching factory and action factory for the image endpoint. This gives the possibility to use other services for caching or image processing - @resubaka (#317, #315) - Added support for tax calculation where the values from customer_tax_class_ids is used - @resubaka (#307) +- The `db` context object - passed to every api endpoint now has two usefull methods: `getElasticClient` and `getRedisClient` for accesing the data stores - @pkarw (#328) +- The `lib/utils` got two new methods `getStoreCode(req: Express.Request)` and `getStoreView(code: string)` for getting the current multistore context from `vue-storefront` frontend requests - @pkarw ### Fixed +- The way Elastic and Redis clients have been fixed and code duplication removed across the app - @pkarw (#327) - The `product.price_*` fields have been normalized with the backward compatibility support (see `config.tax.deprecatedPriceFieldsSupport` which is by default true) - @pkarw (#289) - The `product.final_price` field is now being taken into product price calcualtion. Moreover, we've added the `config.tax.finalPriceIncludesTax` - which is set to `true` by default. All the `price`, `original_price` and `special_price` fields are calculated accordingly. It was required as Magento2 uses `final_price` to set the catalog pricing rules after-prices - @pkarw (#289) - Force ES connections to use protocol config option - @cewald (#303, #304) diff --git a/migrations/.common.js b/migrations/.common.js index 8c49be35..7974668a 100644 --- a/migrations/.common.js +++ b/migrations/.common.js @@ -1,24 +1,9 @@ -let config = require('config') -let kue = require('kue') -let queue = kue.createQueue(Object.assign(config.kue, { redis: config.redis })) - -let es = require('elasticsearch') -const esConfig = { - host: { - host: config.elasticsearch.host, - port: config.elasticsearch.port, - protocol: config.elasticsearch.protocol - }, - log: 'debug', - apiVersion: config.elasticsearch.apiVersion, - requestTimeout: 1000 * 60 * 60, - keepAlive: false -} -if (config.elasticsearch.user) { - esConfig.httpAuth = config.elasticsearch.user + ':' + config.elasticsearch.password -} -let client = new es.Client(esConfig) +const config = require('config') +const kue = require('kue') +const queue = kue.createQueue(Object.assign(config.kue, { redis: config.redis })) +const es = require('../src/lib/elastic') +const client = es.getClient(config) exports.db = client exports.queue = queue diff --git a/src/api/extensions/elastic-stock/index.js b/src/api/extensions/elastic-stock/index.js index 4441f40c..52b742d6 100644 --- a/src/api/extensions/elastic-stock/index.js +++ b/src/api/extensions/elastic-stock/index.js @@ -1,34 +1,14 @@ -import { apiStatus } from '../../../lib/util'; +import { apiStatus, getCurrentStoreView, getCurrentStoreCode } from '../../../lib/util'; +import { getClient as getElasticClient } from '../../../lib/elastic' import { Router } from 'express'; -const es = require('elasticsearch') const bodybuilder = require('bodybuilder') module.exports = ({ config, db }) => { let api = Router(); - const getElasticClient = (config) => { - const esConfig = { // as we're runing tax calculation and other data, we need a ES indexer - host: { - host: config.elasticsearch.host, - port: config.elasticsearch.port, - protocol: config.elasticsearch.protocol - }, - apiVersion: config.elasticsearch.apiVersion, - requestTimeout: 5000 - } - if (config.elasticsearch.user) { - esConfig.httpAuth = config.elasticsearch.user + ':' + config.elasticsearch.password - } - return new es.Client(esConfig) - } - const getStockList = (storeCode, skus) => { - let storeView = config - if (storeCode && config.storeViews[storeCode]) { - storeView = config.storeViews[storeCode] - } - + let storeView = getCurrentStoreView(storeCode) const esQuery = { index: storeView.elasticsearch.indexName, // current index name type: 'product', @@ -52,7 +32,7 @@ module.exports = ({ config, db }) => { return apiStatus(res, 'sku parameter is required', 500); } - getStockList(req.params.storeCode, [req.params.sku]).then((result) => { + getStockList(getCurrentStoreCode(req), [req.params.sku]).then((result) => { if (result && result.length > 0) { apiStatus(res, result[0], 200); } else { @@ -70,7 +50,7 @@ module.exports = ({ config, db }) => { if (!req.query.sku) { return apiStatus(res, 'sku parameter is required', 500); } - getStockList(req.params.storeCode, [req.query.sku]).then((result) => { + getStockList(getCurrentStoreCode(req), [req.query.sku]).then((result) => { if (result && result.length > 0) { apiStatus(res, result[0], 200); } else { @@ -89,7 +69,7 @@ module.exports = ({ config, db }) => { return apiStatus(res, 'skus parameter is required', 500); } const skuArray = req.query.skus.split(',') - getStockList(req.params.storeCode, skuArray).then((result) => { + getStockList(getCurrentStoreCode(req), skuArray).then((result) => { if (result && result.length > 0) { apiStatus(res, result, 200); } else { diff --git a/src/api/sync.js b/src/api/sync.js index 09b7e91a..0191861e 100755 --- a/src/api/sync.js +++ b/src/api/sync.js @@ -1,4 +1,5 @@ -import { apiStatus, apiError } from '../lib/util'; import { Router } from 'express'; +import { apiStatus } from '../lib/util'; import { Router } from 'express'; +import * as redis from '../lib/redis' export default ({ config, db }) => { let syncApi = Router(); @@ -7,14 +8,7 @@ export default ({ config, db }) => { * GET get stock item */ syncApi.get('/order/:order_id', (req, res) => { - const Redis = require('redis'); - let redisClient = Redis.createClient(config.redis); // redis client - redisClient.on('error', (err) => { // workaround for https://github.com/NodeRedis/node_redis/issues/713 - redisClient = Redis.createClient(config.redis); // redis client - }); - if (config.redis.auth) { - redisClient.auth(config.redis.auth); - } + const redisClient = db.getRedisClient(config) redisClient.get('order$$id$$' + req.param('order_id'), (err, reply) => { const orderMetaData = JSON.parse(reply) diff --git a/src/db.js b/src/db.js index e27ef5fd..a8279bf5 100755 --- a/src/db.js +++ b/src/db.js @@ -1,4 +1,12 @@ +import config from 'config' +import * as redis from '../src/lib/redis' +import * as elastic from '../src/lib/elastic' + export default callback => { // connect to a database if needed, then pass it to `callback`: - callback(); + const dbContext = { + getRedisClient: () => redis.getClient(config), + getElasticClient: () => elastic.getClient(config) + } + callback(dbContext); } diff --git a/src/lib/elastic.js b/src/lib/elastic.js index 327936ea..2e791ba7 100644 --- a/src/lib/elastic.js +++ b/src/lib/elastic.js @@ -2,6 +2,23 @@ const path = require('path') const _ = require('lodash') const fs = require('fs'); const jsonFile = require('jsonfile') +const es = require('elasticsearch') + +function getClient(config) { + const esConfig = { // as we're runing tax calculation and other data, we need a ES indexer + host: { + host: config.elasticsearch.host, + port: config.elasticsearch.port, + protocol: config.elasticsearch.protocol + }, + apiVersion: config.elasticsearch.apiVersion, + requestTimeout: 5000 + } + if (config.elasticsearch.user) { + esConfig.httpAuth = config.elasticsearch.user + ':' + config.elasticsearch.password + } + return new es.Client(esConfig) +} function putAlias (db, originalName, aliasName, next) { let step2 = () => { @@ -182,6 +199,7 @@ function putMappings (db, indexName, next) { } module.exports = { + getClient, putMappings, putAlias, createIndex, diff --git a/src/lib/redis.js b/src/lib/redis.js new file mode 100644 index 00000000..122024c6 --- /dev/null +++ b/src/lib/redis.js @@ -0,0 +1,16 @@ +import Redis from 'redis' + +/** + * Return Redis Client + * @param {config} config + */ +export function getClient(config) { + let redisClient = Redis.createClient(config.redis); // redis client + redisClient.on('error', (err) => { // workaround for https://github.com/NodeRedis/node_redis/issues/713 + redisClient = Redis.createClient(config.redis); // redis client + }); + if (config.redis.auth) { + redisClient.auth(config.redis.auth); + } + return redisClient +} \ No newline at end of file diff --git a/src/lib/util.js b/src/lib/util.js index ad583c4c..564d33b5 100755 --- a/src/lib/util.js +++ b/src/lib/util.js @@ -2,6 +2,32 @@ import config from 'config'; import crypto from 'crypto'; const algorithm = 'aes-256-ctr'; +/** + * Get current store code from parameter passed from the vue storefront frotnend app + * @param {Express.Request} req + */ +export function getCurrentStoreCode (req) { + if (req.headers['x-vs-store-code']) { + return req.headers['x-vs-store'] + } + if (req.query.storeCode) { + return req.query.storeCode + } + return null +} + +/** + * Get the config.storeViews[storeCode] + * @param {string} storeCode + */ +export function getCurrentStoreView (storeCode = null) { + if (storeCode && config.storeViews[storeCode]) { + storeView = config.storeViews[storeCode] + } + return config // main config is used as default storeview +} + + /** Creates a callback that proxies node callback style arguments to an Express Response object. * @param {express.Response} res Express HTTP Response * @param {number} [status=200] Status code to send on success diff --git a/src/platform/magento1/tax.js b/src/platform/magento1/tax.js index 1ff3c392..17db8bb6 100644 --- a/src/platform/magento1/tax.js +++ b/src/platform/magento1/tax.js @@ -1,8 +1,8 @@ import AbstractTaxProxy from '../abstract/tax' import { calculateProductTax, checkIfTaxWithUserGroupIsActive, getUserGroupIdToUse } from '../../lib/taxcalc' import TierHelper from '../../helpers/priceTiers' -const es = require('elasticsearch') -const bodybuilder = require('bodybuilder') +import bodybuilder from bodybuilder +import es from '../../lib/elastic' class TaxProxy extends AbstractTaxProxy { constructor (config, entityType, indexName, taxCountry, taxRegion = '', sourcePriceInclTax = null, finalPriceInclTax = null) { @@ -72,21 +72,7 @@ class TaxProxy extends AbstractTaxProxy { inst.applyTierPrices(productList, groupId) if (this._config.tax.calculateServerSide) { - const esConfig = { // as we're runing tax calculation and other data, we need a ES indexer - host: { - host: this._config.elasticsearch.host, - port: this._config.elasticsearch.port, - protocol: this._config.elasticsearch.protocol - }, - log: 'debug', - apiVersion: this._config.elasticsearch.apiVersion, - requestTimeout: 5000 - } - if (this._config.elasticsearch.user) { - esConfig.httpAuth = this._config.elasticsearch.user + ':' + this._config.elasticsearch.password - } - - const client = new es.Client(esConfig) + const client = es.getClient(this._config) const esQuery = { index: this._indexName, type: 'taxrule', diff --git a/src/platform/magento1/util.js b/src/platform/magento1/util.js index 28f04f7b..07a7de02 100644 --- a/src/platform/magento1/util.js +++ b/src/platform/magento1/util.js @@ -1,4 +1,5 @@ import config from 'config' +import { getCurrentStoreCode } from '../../lib/util' /** * Adjust the config provided to the current store selected via request params * @param Object config configuration @@ -6,15 +7,7 @@ import config from 'config' */ export function multiStoreConfig (apiConfig, req) { let confCopy = Object.assign({}, apiConfig) - let storeCode = '' - - if (req.headers['x-vs-store-code']) { - storeCode = req.headers['x-vs-store'] - } - if (req.query.storeCode) { - storeCode = req.query.storeCode - } - + let storeCode = getCurrentStoreCode(req) if (storeCode && config.availableStores.indexOf(storeCode) >= 0) { if (config.magento1['api_' + storeCode]) { confCopy = Object.assign({}, config.magento1['api_' + storeCode]) // we're to use the specific api configuration - maybe even separate magento instance diff --git a/src/platform/magento2/o2m.js b/src/platform/magento2/o2m.js index 7ef15fbb..67afff1c 100644 --- a/src/platform/magento2/o2m.js +++ b/src/platform/magento2/o2m.js @@ -2,14 +2,8 @@ const Magento2Client = require('magento2-rest-client').Magento2Client; const config = require('config') -const Redis = require('redis'); -let redisClient = Redis.createClient(config.redis); // redis client -redisClient.on('error', (err) => { // workaround for https://github.com/NodeRedis/node_redis/issues/713 - redisClient = Redis.createClient(config.redis); // redis client -}); -if (config.redis.auth) { - redisClient.auth(config.redis.auth); -} +const redis = require('../../lib/redis'); +const redisClient = redis.getClient(config) const countryMapper = require('../../lib/countrymapper') const Ajv = require('ajv'); // json validator const fs = require('fs'); @@ -228,14 +222,16 @@ function processSingleOrder (orderData, config, job, done, logger = console) { logger.info(THREAD_ID + '[OK] Order placed with ORDER ID', result); logger.debug(THREAD_ID + result) - redisClient.set('order$$id$$' + orderData.order_id, JSON.stringify({ - platform_order_id: result, - transmited: true, - transmited_at: new Date(), - platform: 'magento2', - order: orderData - })); - redisClient.set('order$$totals$$' + orderData.order_id, JSON.stringify(result[1])); + if (orderData.order_id) { + redisClient.set('order$$id$$' + orderData.order_id, JSON.stringify({ + platform_order_id: result, + transmited: true, + transmited_at: new Date(), + platform: 'magento2', + order: orderData + })); + redisClient.set('order$$totals$$' + orderData.order_id, JSON.stringify(result[1])); + } let orderIncrementId = null; api.orders.incrementIdById(result).then(result => { orderIncrementId = result.increment_id diff --git a/src/platform/magento2/tax.js b/src/platform/magento2/tax.js index 7d8c33cf..5a3446df 100644 --- a/src/platform/magento2/tax.js +++ b/src/platform/magento2/tax.js @@ -1,8 +1,8 @@ import AbstractTaxProxy from '../abstract/tax' import { calculateProductTax, checkIfTaxWithUserGroupIsActive, getUserGroupIdToUse } from '../../lib/taxcalc'; import TierHelper from '../../helpers/priceTiers' -const es = require('elasticsearch') -const bodybuilder = require('bodybuilder') +import es from '../../lib/elastic' +import bodybuilder from 'bodybuilder' class TaxProxy extends AbstractTaxProxy { constructor (config, entityType, indexName, taxCountry, taxRegion = '', sourcePriceInclTax = null, finalPriceInclTax = null) { @@ -71,21 +71,7 @@ class TaxProxy extends AbstractTaxProxy { inst.applyTierPrices(productList, groupId) if (this._config.tax.calculateServerSide) { - const esConfig = { // as we're runing tax calculation and other data, we need a ES indexer - host: { - host: this._config.elasticsearch.host, - port: this._config.elasticsearch.port, - protocol: this._config.elasticsearch.protocol - }, - log: 'debug', - apiVersion: this._config.elasticsearch.apiVersion, - requestTimeout: 5000 - } - if (this._config.elasticsearch.user) { - esConfig.httpAuth = this._config.elasticsearch.user + ':' + this._config.elasticsearch.password - } - - let client = new es.Client(esConfig) + const client = es.getClient(this._config) const esQuery = { index: this._indexName, type: 'taxrule', diff --git a/src/platform/magento2/util.js b/src/platform/magento2/util.js index 644d5a16..b6975b4d 100644 --- a/src/platform/magento2/util.js +++ b/src/platform/magento2/util.js @@ -1,4 +1,5 @@ import config from 'config' +import { getCurrentStoreCode } from '../../lib/util' /** * Adjust the config provided to the current store selected via request params * @param Object config configuration @@ -6,14 +7,7 @@ import config from 'config' */ export function multiStoreConfig (apiConfig, req) { let confCopy = Object.assign({}, apiConfig) - let storeCode = '' - - if (req.headers['x-vs-store-code']) { - storeCode = req.headers['x-vs-store'] - } - if (req.query.storeCode) { - storeCode = req.query.storeCode - } + let storeCode = getCurrentStoreCode(req) if (storeCode && config.availableStores.indexOf(storeCode) >= 0) { if (config.magento2['api_' + storeCode]) {