From 0b32632277e36f399b89f772d11605cfe2c0f82b Mon Sep 17 00:00:00 2001 From: tkostuch Date: Mon, 30 Dec 2019 09:49:45 +0100 Subject: [PATCH 1/3] add url module --- config/default.json | 7 ++++ src/api/index.js | 4 ++ src/api/url.js | 90 +++++++++++++++++++++++++++++++++++++++++++++ src/lib/elastic.js | 14 ++++++- 4 files changed, 114 insertions(+), 1 deletion(-) create mode 100644 src/api/url.js diff --git a/config/default.json b/config/default.json index 0834dacf..63f8fe49 100644 --- a/config/default.json +++ b/config/default.json @@ -282,5 +282,12 @@ "description": 1, "sku": 1, "configurable_children.sku": 1 + }, + "urlModule": { + "map": { + "includeFields": ["url_path", "identifier", "id", "slug", "sku", "type_id"], + "searchedFields": ["url_path", "identifier"], + "searchedEntities": ["product", "category", "cms_page"] + } } } diff --git a/src/api/index.js b/src/api/index.js index bf389fca..603ee434 100755 --- a/src/api/index.js +++ b/src/api/index.js @@ -8,6 +8,7 @@ import review from './review'; import cart from './cart'; import product from './product'; import sync from './sync'; +import url from './url'; export default ({ config, db }) => { let api = Router(); @@ -36,6 +37,9 @@ export default ({ config, db }) => { // mount the sync resource api.use('/sync', sync({ config, db })) + // mount the url resource + api.use('/url', url({ config, db })) + // perhaps expose some API metadata at the root api.get('/', (req, res) => { res.json({ version }); diff --git a/src/api/url.js b/src/api/url.js new file mode 100644 index 00000000..ee09b9bc --- /dev/null +++ b/src/api/url.js @@ -0,0 +1,90 @@ +import { Router } from 'express'; +import { apiStatus } from '../lib/util'; +import { buildMultiEntityUrl } from '../lib/elastic'; +import request from 'request'; +import get from 'lodash/get'; + +/** + * Builds ES query based on config + */ +const buildQuery = ({ value, config }) => { + const searchedFields = get(config, 'urlModule.map.searchedFields', []) + .map((field) => ({ match_phrase: { [field]: { query: value } } })) + const searchedEntities = get(config, 'urlModule.map.searchedEntities', []) + .map((entity) => ({ type: { value: entity } })) + + return { + query: { + bool: { + filter: { + bool: { + should: searchedFields, + filter: { + bool: { + should: searchedEntities + } + } + } + } + } + }, + size: 1 // we need only one record + } +} + +/** + * checks result equality because ES can return record even if searched value is not EXACLY what we want (check `match_phrase` in ES docs) + */ +const checkFieldValueEquality = ({ config, response, value }) => { + const isEqualValue = get(config, 'urlModule.map.searchedFields', []) + .find((field) => response._source[field] === value) + + return Boolean(isEqualValue) +} + +module.exports = ({ config }) => { + const router = Router() + router.post('/map/:index', (req, res) => { + const { url, excludeFields, includeFields } = req.body + if (!url) { + return apiStatus(res, 'Missing url', 500); + } + + const esUrl = buildMultiEntityUrl({ + config, + includeFields: includeFields ? includeFields.concat(get(config, 'urlModule.map.includeFields', [])) : [], + excludeFields + }) + const query = buildQuery({ value: url, config }) + + // Only pass auth if configured + let auth = null; + if (config.elasticsearch.user || config.elasticsearch.password) { + auth = { + user: config.elasticsearch.user, + pass: config.elasticsearch.password + } + } + + // make simple ES search + request({ + uri: esUrl, + method: 'POST', + body: query, + json: true, + auth: auth + }, (_err, _res, _resBody) => { + if (_err) { + console.log(_err) + return apiStatus(res, new Error('ES search error'), 500); + } + const responseRecord = _resBody.hits.hits[0] + if (responseRecord && checkFieldValueEquality({ config, response: responseRecord, value: req.body.url })) { + return res.json(responseRecord) + } + res.json() + }) + }) + + return router +} diff --git a/src/lib/elastic.js b/src/lib/elastic.js index 84b8ad22..ca296219 100644 --- a/src/lib/elastic.js +++ b/src/lib/elastic.js @@ -51,6 +51,17 @@ function adjustBackendProxyUrl (req, indexName, entityType, config) { return url } +/** + * similar to `adjustBackendProxyUrl`, builds multi-entity query url + */ +function buildMultiEntityUrl ({ config, includeFields = [], excludeFields = [] }) { + let url = `${config.elasticsearch.host}:${config.elasticsearch.port}/_search?_source_include=${includeFields.join(',')}&_source_exclude=${excludeFields.join(',')}` + if (!url.startsWith('http')) { + url = config.elasticsearch.protocol + '://' + url + } + return url +} + function adjustQuery (esQuery, entityType, config) { if (parseInt(config.elasticsearch.apiVersion) < 6) { esQuery.type = entityType @@ -261,5 +272,6 @@ module.exports = { getClient, getHits, adjustIndexName, - putMappings + putMappings, + buildMultiEntityUrl } From acd3d93e889d6b475d55f8c50d5d165fcf60bad2 Mon Sep 17 00:00:00 2001 From: tkostuch Date: Mon, 30 Dec 2019 09:53:17 +0100 Subject: [PATCH 2/3] update changelog --- CHANGELOG.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ee6b2e3..e00eeb8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,17 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/) and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [1.12.0-rc1] - UNRELEASED + +### Added +- Add url module - @gibkigonzo (#3942) + +### Fixed + +### Changed / Improved + + ## [1.11.0] - 2019.12.20 ### Fixed From 4c70244a87998611c0356bb5a78752cab4d32afb Mon Sep 17 00:00:00 2001 From: tkostuch Date: Mon, 30 Dec 2019 10:01:10 +0100 Subject: [PATCH 3/3] clean folder structure --- src/api/url/index.js | 10 ++++++++++ src/api/{url.js => url/map.js} | 6 +++--- 2 files changed, 13 insertions(+), 3 deletions(-) create mode 100644 src/api/url/index.js rename src/api/{url.js => url/map.js} (94%) diff --git a/src/api/url/index.js b/src/api/url/index.js new file mode 100644 index 00000000..15562615 --- /dev/null +++ b/src/api/url/index.js @@ -0,0 +1,10 @@ +import { Router } from 'express'; +import createMapRoute from './map'; + +module.exports = ({ config }) => { + const router = Router() + + router.use('/map', createMapRoute({ config })) + + return router +} diff --git a/src/api/url.js b/src/api/url/map.js similarity index 94% rename from src/api/url.js rename to src/api/url/map.js index ee09b9bc..cf9abfb4 100644 --- a/src/api/url.js +++ b/src/api/url/map.js @@ -1,6 +1,6 @@ import { Router } from 'express'; -import { apiStatus } from '../lib/util'; -import { buildMultiEntityUrl } from '../lib/elastic'; +import { apiStatus } from '../../lib/util'; +import { buildMultiEntityUrl } from '../../lib/elastic'; import request from 'request'; import get from 'lodash/get'; @@ -44,7 +44,7 @@ const checkFieldValueEquality = ({ config, response, value }) => { module.exports = ({ config }) => { const router = Router() - router.post('/map/:index', (req, res) => { + router.post('/:index', (req, res) => { const { url, excludeFields, includeFields } = req.body if (!url) { return apiStatus(res, 'Missing url', 500);