Skip to content

Commit

Permalink
Feature vuestorefront/vue-storefront-api#390 ported to SFAPI
Browse files Browse the repository at this point in the history
  • Loading branch information
pkarw committed Jan 30, 2020
1 parent 5df85c6 commit 38f3d72
Show file tree
Hide file tree
Showing 18 changed files with 227 additions and 242 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Added hooks implementation from vue-storefront as a package - @resubaka (#30)
- Added the integration tests - @resubaka (#35)

### Changed / Improved

- The `response_format` query parameter to the `/api/catalog` endpoint. Currently there is just one additional format supported: `response_format=compact`. When used, the response format got optimized by: a) remapping the results, removing the `_source` from the `hits.hits`; b) compressing the JSON fields names according to the `config.products.fieldsToCompact`; c) removing the JSON fields from the `product.configurable_children` when their values === parent product values; overall response size reduced over -70% - @pkarw
- The support for `SearchQuery` instead of the ElasticSearch DSL as for the input to `/api/catalog` - using `storefront-query-builder` package - @pkarw - https://github.com/DivanteLtd/vue-storefront/issues/2167

### Fixed

- Taxcalc backport - special_prices (https://github.com/DivanteLtd/vue-storefront-api/pull/380) - @resubaka
- Check message property instead of errorMessage in apiError function - @cdshotels-liborpansky (#378)
- Replaced the old `crop` function call which has been removed from Sharp image processor - @grimasod (#381)
- Fixed that you can now run the dist folder output and don't get errors with module can't be loaded - @resubaka (#24)
- Changed that only one redis and elasticsearch client is created - @resubaka (#35)
- Send /reset-password with undefined websiteId as default - @gibkigonzo (#382)
- Fixed the restore command restore command of elastic7.ts so it exits when it finished - @resubaka (#35)
- Fixed the restore command restore command of elastic7.ts so it does not crash when it can't find the file it wants to upload - @resubaka (#35)

Expand Down
84 changes: 80 additions & 4 deletions config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,84 @@
"vue_storefront_catalog_de",
"vue_storefront_catalog_it"
],
"apiVersion": "7.2"
"apiVersion": "7.2",

"searchScoring": {
"attributes": {
"attribute_code": {
"scoreValues": { "attribute_value": { "weight": 1 } }
}
},
"fuzziness": 2,
"cutoff_frequency": 0.01,
"max_expansions": 3,
"minimum_should_match": "75%",
"prefix_length": 2,
"boost_mode": "multiply",
"score_mode": "multiply",
"max_boost": 100,
"function_min_score": 1
},
"searchableAttributes": {
"name": {
"boost": 4
},
"sku": {
"boost": 2
},
"category.name": {
"boost": 1
}
}
},
"products": {
"fieldsToCompress": ["max_regular_price", "max_price", "max_regular_price", "minimal_regular_price", "final_price", "price", "special_price", "original_final_price", "original_price", "original_special_price", "final_price_incl_tax", "price_incl_tax", "special_price_incl_tax", "final_price_tax", "price_tax", "special_price_tax", "image", "small_image", "thumbnail"],
"fieldsToCompact": {
"minimal_price": "mp",
"has_options": "ho",
"url_key": "u",
"status": "s",
"required_options": "ro",
"name": "nm",
"tax_class_id": "tci",
"description": "desc",
"minimal_regular_price": "mrp",
"final_price": "fp",
"price": "p",
"special_price": "sp",
"original_final_price": "ofp",
"original_price": "op",
"original_special_price": "osp",
"final_price_incl_tax": "fpit",
"original_price_incl_tax": "opit",
"price_incl_tax": "pit",
"special_price_incl_tax": "spit",
"final_price_tax": "fpt",
"price_tax": "pt",
"special_price_tax": "spt",
"original_price_tax": "opt",
"image": "i",
"small_image": "si",
"thumbnail": "t"
},
"filterFieldMapping": {
"category.name": "category.name.keyword"
},
"filterAggregationSize": {
"default": 10,
"size": 10,
"color": 10
},
"priceFilterKey": "final_price",
"priceFilters": {
"ranges": [
{ "from": 0, "to": 50 },
{ "from": 50, "to": 100 },
{ "from": 100, "to": 150 },
{ "from": 150 }
]
}
},
"redis": {
"host": "localhost",
"port": 6379,
Expand Down Expand Up @@ -67,7 +143,7 @@
"tax": {
"defaultCountry": "DE",
"defaultRegion": "",
"deprecatedPriceFieldsSupport": true,
"deprecatedPriceFieldsSupport": false,
"calculateServerSide": true,
"sourcePriceIncludesTax": true,
"finalPriceIncludesTax": false,
Expand Down Expand Up @@ -104,7 +180,7 @@
"defaultRegion": "",
"calculateServerSide": true,
"sourcePriceIncludesTax": false,
"deprecatedPriceFieldsSupport": true,
"deprecatedPriceFieldsSupport": false,
"finalPriceIncludesTax": false,
"userGroupId": null,
"useOnlyDefaultUserGroupId": false
Expand Down Expand Up @@ -134,7 +210,7 @@
"usePlatformTotals": true,
"setConfigurableProductOptions": true,
"sourcePriceIncludesTax": false,
"deprecatedPriceFieldsSupport": true,
"deprecatedPriceFieldsSupport": false,
"finalPriceIncludesTax": false,
"userGroupId": null,
"useOnlyDefaultUserGroupId": false
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,8 @@
"tsconfig-paths": "^3.9.0",
"typescript": "3.7.*",
"vuepress": "^1.2.0",
"winston": "^3.2.0"
"winston": "^3.2.0",
"storefront-query-builder": "^0.0.9"
},
"devDependencies": {
"@types/body-parser": "^1.17.0",
Expand Down
32 changes: 28 additions & 4 deletions packages/default-catalog/api/catalog.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import { adjustBackendProxyUrl } from '@storefront-api/lib/elastic'
import cache from '@storefront-api/lib/cache-instance'
import { sha3_224 } from 'js-sha3'
import Logger from '@storefront-api/lib/logger'
import bodybuilder from 'bodybuilder'
import { elasticsearch, SearchQuery } from 'storefront-query-builder'

function _cacheStorageHandler (config, result, hash, tags) {
if (config.server.useOutputCache && cache) {
Expand All @@ -18,7 +20,23 @@ function _cacheStorageHandler (config, result, hash, tags) {
}
}

export default ({config, db}) => function (req, res, body) {
function _outputFormatter (responseBody, format = 'standard') {
if (format === 'compact') { // simple formatter
delete responseBody.took
delete responseBody.timed_out
delete responseBody._shards
if (responseBody.hits) {
delete responseBody.hits.max_score
responseBody.total = responseBody.hits.total
responseBody.hits = responseBody.hits.hits.map(hit => {
return Object.assign(hit._source, { _score: hit._score })
})
}
}
return responseBody
}

export default ({config, db}) => async function (req, res, body) {
let groupId = null

// Request method handling: exit if not GET or POST
Expand All @@ -27,15 +45,19 @@ export default ({config, db}) => function (req, res, body) {
throw new Error('ERROR: ' + req.method + ' request method is not supported.')
}

let requestBody: Record<string, any> = {}
let requestBody: Record<string, any> = req.body as Record<string, any>
let responseFormat = 'standard'
if (req.method === 'GET') {
if (req.query.request) { // this is in fact optional
requestBody = JSON.parse(decodeURIComponent(req.query.request))
}
} else {
requestBody = req.body
}

if (req.query.request_format === 'search-query') { // search query and not Elastic DSL - we need to translate it
requestBody = await elasticsearch.buildQueryBodyFromSearchQuery({ config, queryChain: bodybuilder(), searchQuery: new SearchQuery(requestBody) })
}
if (req.query.response_format) responseFormat = req.query.response_format

const urlSegments = req.url.split('/');

let indexName = ''
Expand Down Expand Up @@ -108,6 +130,7 @@ export default ({config, db}) => function (req, res, body) {
if (entityType === 'product') {
resultProcessor.process(_resBody.hits.hits, groupId).then((result) => {
_resBody.hits.hits = result
_resBody = _outputFormatter(_resBody, responseFormat)
_cacheStorageHandler(config, _resBody, reqHash, tagsArray)
res.json(_resBody);
}).catch((err) => {
Expand All @@ -116,6 +139,7 @@ export default ({config, db}) => function (req, res, body) {
} else {
resultProcessor.process(_resBody.hits.hits).then((result) => {
_resBody.hits.hits = result
_resBody = _outputFormatter(_resBody, responseFormat)
_cacheStorageHandler(config, _resBody, reqHash, tagsArray)
res.json(_resBody);
}).catch((err) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { buildQuery } from '../queryBuilder';
import { getIndexName } from '../mapping'
import { adjustQuery, getResponseObject } from '@storefront-api/lib/elastic'

async function listAttributes ({ attributes = null, filter = null, context, rootValue, _sourceInclude, _sourceExclude }) {
async function listAttributes ({ attributes = null, filter = null, context, rootValue, _sourceIncludes, _sourceExcludes }) {
let query = buildQuery({ filter: filter || attributes, pageSize: 150, type: 'attribute' });

const esQuery = {
index: getIndexName(context.req.url),
body: query,
_source_include: _sourceInclude,
_source_exclude: _sourceExclude
_source_includes: _sourceIncludes,
_source_excludes: _sourceExcludes
}

const response = getResponseObject(await client.search(adjustQuery(esQuery, 'attribute', config)));
Expand All @@ -26,11 +26,11 @@ async function listAttributes ({ attributes = null, filter = null, context, root
return response;
}

export async function listSingleAttribute ({ attribute_id, attribute_code, context, rootValue, _sourceInclude, _sourceExclude }) {
export async function listSingleAttribute ({ attribute_id, attribute_code, context, rootValue, _sourceIncludes, _sourceExcludes }) {
const filter = {}
if (attribute_id) filter['attribute_id'] = { eq: attribute_id }
if (attribute_code) filter['attribute_code'] = { eq: attribute_code }
const attrList = await listAttributes({ filter, context, rootValue, _sourceInclude, _sourceExclude })
const attrList = await listAttributes({ filter, context, rootValue, _sourceIncludes, _sourceExcludes })
if (attrList && attrList.items.length > 0) {
return attrList.items[0]
} else {
Expand All @@ -40,8 +40,8 @@ export async function listSingleAttribute ({ attribute_id, attribute_code, conte

const resolver = {
Query: {
customAttributeMetadata: (_, { attributes, _sourceInclude, _sourceExclude }, context, rootValue) => listAttributes({ attributes, context, rootValue, _sourceInclude, _sourceExclude }),
attribute: (_, { attribute_id, attribute_code, _sourceInclude, _sourceExclude }, context, rootValue) => listSingleAttribute({ attribute_id, attribute_code, _sourceInclude, _sourceExclude, context, rootValue })
customAttributeMetadata: (_, { attributes, _sourceIncludes, _sourceExcludes }, context, rootValue) => listAttributes({ attributes, context, rootValue, _sourceIncludes, _sourceExcludes }),
attribute: (_, { attribute_id, attribute_code, _sourceIncludes, _sourceExcludes }, context, rootValue) => listSingleAttribute({ attribute_id, attribute_code, _sourceIncludes, _sourceExcludes, context, rootValue })
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ extend type Query {
filter: AttributeInput

# Specifies which attribute we include in result.
_sourceInclude: [String]
_sourceIncludes: [String]

# Specifies which attribute we exclude in result.
_sourceExclude: [String]
_sourceExcludes: [String]
): CustomAttributeMetadata

# List single attribute metadata by attribute_code or attribute_id
Expand Down
22 changes: 11 additions & 11 deletions packages/default-catalog/graphql/elasticsearch/category/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ import { getIndexName } from '../mapping'
import { list as listProducts } from '../product/resolver'
import { adjustQuery, getResponseObject } from '@storefront-api/lib/elastic'

async function list ({ search, filter, currentPage, pageSize = 200, sort, context, rootValue, _sourceInclude, _sourceExclude = null }) {
async function list ({ search, filter, currentPage, pageSize = 200, sort, context, rootValue, _sourceIncludes, _sourceExcludes = null }) {
let query = buildQuery({ search, filter, currentPage, pageSize, sort, type: 'category' });

const response = getResponseObject(await client.search(adjustQuery({
index: getIndexName(context.req.url),
body: query,
_source_exclude: _sourceExclude,
_source_include: _sourceInclude
_source_excludes: _sourceExcludes,
_source_includes: _sourceIncludes
}, 'category', config)));

// Process hits
Expand Down Expand Up @@ -48,11 +48,11 @@ async function list ({ search, filter, currentPage, pageSize = 200, sort, contex
return response;
}

export async function listSingleCategory ({ id, url_path, context, rootValue, _sourceInclude, _sourceExclude }) {
export async function listSingleCategory ({ id, url_path, context, rootValue, _sourceIncludes, _sourceExcludes }) {
const filter = {}
if (id) filter['id'] = { eq: id }
if (url_path) filter['url_path'] = { eq: url_path }
const categoryList = await list({ search: '', filter, currentPage: 0, pageSize: 1, sort: null, context, rootValue, _sourceInclude, _sourceExclude })
const categoryList = await list({ search: '', filter, currentPage: 0, pageSize: 1, sort: null, context, rootValue, _sourceIncludes, _sourceExcludes })
if (categoryList && categoryList.items.length > 0) {
return categoryList.items[0]
} else {
Expand All @@ -62,14 +62,14 @@ export async function listSingleCategory ({ id, url_path, context, rootValue, _s

const resolver = {
Query: {
categories: (_, { search, filter, currentPage, pageSize, sort, _sourceInclude }, context, rootValue) =>
list({ search, filter, currentPage, pageSize, sort, context, rootValue, _sourceInclude }),
category: (_, { id, url_path, _sourceInclude, _sourceExclude }, context, rootValue) =>
listSingleCategory({ id, url_path, context, rootValue, _sourceInclude, _sourceExclude })
categories: (_, { search, filter, currentPage, pageSize, sort, _sourceIncludes }, context, rootValue) =>
list({ search, filter, currentPage, pageSize, sort, context, rootValue, _sourceIncludes }),
category: (_, { id, url_path, _sourceIncludes, _sourceExcludes }, context, rootValue) =>
listSingleCategory({ id, url_path, context, rootValue, _sourceIncludes, _sourceExcludes })
},
Category: {
products: (_, { search, filter, currentPage, pageSize, sort, _sourceInclude, _sourceExclude }, context, rootValue) => {
return listProducts({ filter: Object.assign({}, filter, { category_ids: { in: _.id } }), sort, currentPage, pageSize, search, context, rootValue, _sourceInclude, _sourceExclude })
products: (_, { search, filter, currentPage, pageSize, sort, _sourceIncludes, _sourceExcludes }, context, rootValue) => {
return listProducts({ filter: Object.assign({}, filter, { category_ids: { in: _.id } }), sort, currentPage, pageSize, search, context, rootValue, _sourceIncludes, _sourceExcludes })
},
children: (_, { search }, context, rootValue) =>
_.children_data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ extend type Query {
sort: CategorySortInput

# Specifies which attribute we include in result.
_sourceInclude: [String]
_sourceIncludes: [String]

# Specifies which attribute we exclude in result.
_sourceExclude: [String]
_sourceExcludes: [String]
): Categories

# Find single category by id ir url_path
Expand All @@ -37,10 +37,10 @@ extend type Query {
url_path: String

# Specifies which attribute we include in result.
_sourceInclude: [String]
_sourceIncludes: [String]

# Specifies which attribute we exclude in result.
_sourceExclude: [String]
_sourceExcludes: [String]
): Category
}

Expand Down Expand Up @@ -156,10 +156,10 @@ type Category {
sort: ProductSortInput

# Specifies which attribute we include in result.
_sourceInclude: [String]
_sourceIncludes: [String]

# Specifies which attribute we exclude in result.
_sourceExclude: [String]
_sourceExcludes: [String]
): Products
}

Expand Down
12 changes: 6 additions & 6 deletions packages/default-catalog/graphql/elasticsearch/cms/resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@ function buildItems (response) {

const resolver = {
Query: {
cmsPages: (_, { filter, currentPage, pageSize, _sourceInclude, type = 'cms_page' }, context) =>
list({ filter, currentPage, pageSize, _source_include: _sourceInclude, type, context }),
cmsBlocks: (_, { filter, currentPage, pageSize, _sourceInclude, type = 'cms_block' }, context) =>
list({ filter, currentPage, pageSize, _source_include: _sourceInclude, type, context }),
cmsHierarchies: (_, { filter, currentPage, pageSize, _sourceInclude, type = 'cms_hierarchy' }, context) =>
list({ filter, currentPage, pageSize, _source_include: _sourceInclude, type, context })
cmsPages: (_, { filter, currentPage, pageSize, _sourceIncludes, type = 'cms_page' }, context) =>
list({ filter, currentPage, pageSize, _source_include: _sourceIncludes, type, context }),
cmsBlocks: (_, { filter, currentPage, pageSize, _sourceIncludes, type = 'cms_block' }, context) =>
list({ filter, currentPage, pageSize, _source_include: _sourceIncludes, type, context }),
cmsHierarchies: (_, { filter, currentPage, pageSize, _sourceIncludes, type = 'cms_hierarchy' }, context) =>
list({ filter, currentPage, pageSize, _source_include: _sourceIncludes, type, context })
}
};

Expand Down
Loading

0 comments on commit 38f3d72

Please sign in to comment.