@nuskin/cs-product-lib 1.0.0-ian-001.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +43 -0
- package/package.json +47 -0
- package/src/csProduct.js +187 -0
- package/src/helpers.js +558 -0
- package/src/index.js +7 -0
- package/src/productStatus.js +167 -0
package/README.md
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# @nuskin/cs-product-lib
|
|
2
|
+
|
|
3
|
+
Library for pulling and parsing product content (and applying business logic) retrieved from Contentstack.
|
|
4
|
+
|
|
5
|
+
## Installing
|
|
6
|
+
|
|
7
|
+
Using npm:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm add @nuskin/cs-product-lib
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Usng yarn:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
yarn add @nuskin/cs-product-lib
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Example usage
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
const { tbd } = require('@nuskin/cs-product-lib')
|
|
23
|
+
tbd.doSomething()
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Resources
|
|
28
|
+
|
|
29
|
+
- Changelog could go here
|
|
30
|
+
|
|
31
|
+
## License
|
|
32
|
+
|
|
33
|
+
[MIT](https://opensource.org/licenses/MIT)
|
|
34
|
+
|
|
35
|
+
#### 7. Turning on your CI/CD pipeline
|
|
36
|
+
|
|
37
|
+
Once you are ready for your project to start running the CI/CD pipeline, you should rename the `gitlab-ci.yml`
|
|
38
|
+
to `.gitlab-ci.yml`.
|
|
39
|
+
```bash
|
|
40
|
+
git mv gitlab-ci.yml .gitlab-ci.yml
|
|
41
|
+
git commit -am"Chore: renaming gitlab-ci.yml to .gitlab-ci.yml so my pipeline runs"
|
|
42
|
+
git push
|
|
43
|
+
```
|
package/package.json
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@nuskin/cs-product-lib",
|
|
3
|
+
"version": "1.0.0-ian-001.1",
|
|
4
|
+
"description": "Library for pulling and parsing product content (and applying business logic) retrieved from Contentstack",
|
|
5
|
+
"main": "src/index.js",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"audit": "yarn audit --level high",
|
|
8
|
+
"jsdoc": "node_modules/.bin/jsdoc -a all -c jsdoc.json -r -d jsdocs",
|
|
9
|
+
"lint": "eslint src __tests__",
|
|
10
|
+
"test": "jest --coverage"
|
|
11
|
+
},
|
|
12
|
+
"publishConfig": {
|
|
13
|
+
"access": "public"
|
|
14
|
+
},
|
|
15
|
+
"repository": {
|
|
16
|
+
"type": "git",
|
|
17
|
+
"url": "git@code.tls.nuskin.io:ns-am/product/js-libs/cs-product-lib.git"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [],
|
|
20
|
+
"author": "Nuskin BFF Team",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"homepage": "https://code.tls.nuskin.io/ns-am/product/js-libs/cs-product-lib/blob/master/README.md",
|
|
23
|
+
"devDependencies": {
|
|
24
|
+
"@nuskin/docdash": "1.0.1",
|
|
25
|
+
"eslint": "7.21.0",
|
|
26
|
+
"eslint-config-google": "0.14.0",
|
|
27
|
+
"eslint-config-prettier": "8.1.0",
|
|
28
|
+
"eslint-plugin-json": "2.1.2",
|
|
29
|
+
"eslint-plugin-prettier": "3.3.1",
|
|
30
|
+
"jest": "29.4.1",
|
|
31
|
+
"jest-sonar-reporter": "2.0.0",
|
|
32
|
+
"jsdoc": "3.6.6",
|
|
33
|
+
"prettier": "2.2.1"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"src/"
|
|
37
|
+
],
|
|
38
|
+
"dependencies": {
|
|
39
|
+
"@contentstack/json-rte-serializer": "2.0.1",
|
|
40
|
+
"@nuskin/ns-common-lib": "1.4.5",
|
|
41
|
+
"axios": "1.3.0",
|
|
42
|
+
"axios-retry": "3.4.0",
|
|
43
|
+
"entities": "4.4.0",
|
|
44
|
+
"lodash": "4.17.21",
|
|
45
|
+
"turndown": "7.1.1"
|
|
46
|
+
}
|
|
47
|
+
}
|
package/src/csProduct.js
ADDED
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const axios = require('axios');
|
|
4
|
+
const axiosRetry = require('axios-retry');
|
|
5
|
+
const {arrayString} = require('./helpers')
|
|
6
|
+
|
|
7
|
+
const csProduct = (config) => {
|
|
8
|
+
let apiKey = ''
|
|
9
|
+
let environment = ''
|
|
10
|
+
let deliveryToken = ''
|
|
11
|
+
|
|
12
|
+
// Query Constants (includes, etc.)
|
|
13
|
+
const DEV_ENVIRONMENT = 'public-dev';
|
|
14
|
+
const CONTENT_STACK_DELIVERY_URL = 'https://cdn.contentstack.io/v3/';
|
|
15
|
+
const VARIANTS = 'variants';
|
|
16
|
+
const KEY_INGREDIENTS = 'ingredients.key_ingredients';
|
|
17
|
+
const PRODUCT_HIGHLIGHTS = 'product_details.highlights';
|
|
18
|
+
const VARIANT_HIGHLIGHTS = 'variants.product_details.highlights';
|
|
19
|
+
const VARIANT_KEY_INGREDIENTS = 'variants.ingredients_override.key_ingredients';
|
|
20
|
+
const PRODUCT_FAQS = 'faqs';
|
|
21
|
+
const VARIANT_FAQS = 'variants.faqs';
|
|
22
|
+
const PRODUCT_RESOURCES = 'resources';
|
|
23
|
+
const VARIANT_RESOURCES = 'variants.resources';
|
|
24
|
+
const PRODUCT_CATEGORY = 'categories';
|
|
25
|
+
const PRODUCT_SUBCATEGORIES = 'categories.subcategories';
|
|
26
|
+
const DEFAULT_LOCALE = 'en';
|
|
27
|
+
const PRODUCT_TYPE = 'product';
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line max-len
|
|
30
|
+
const productIncludes = `&include[]=${VARIANTS}&include[]=${PRODUCT_FAQS}&include[]=${KEY_INGREDIENTS}&include[]=${PRODUCT_HIGHLIGHTS}&include[]=${PRODUCT_RESOURCES}&include[]=${VARIANT_KEY_INGREDIENTS}&include[]=${VARIANT_HIGHLIGHTS}&include[]=${VARIANT_FAQS}&include[]=${VARIANT_RESOURCES}&include[]=${PRODUCT_CATEGORY}&include[]=${PRODUCT_SUBCATEGORIES}`;
|
|
31
|
+
|
|
32
|
+
if (!config) {
|
|
33
|
+
throw new Error(`This will not work without a valid configuration {apiKey, environment, deliveryToken}.`)
|
|
34
|
+
} else {
|
|
35
|
+
apiKey = config.apiKey
|
|
36
|
+
environment = config.environment
|
|
37
|
+
deliveryToken = config.deliveryToken
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const deliveryAxios = axios.create({
|
|
41
|
+
baseURL: CONTENT_STACK_DELIVERY_URL,
|
|
42
|
+
timeout: 3000,
|
|
43
|
+
headers: {
|
|
44
|
+
api_key: apiKey,
|
|
45
|
+
access_token: deliveryToken
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
axiosRetry(deliveryAxios, {
|
|
49
|
+
retries: 3,
|
|
50
|
+
shouldResetTimeout: true,
|
|
51
|
+
// eslint-disable-next-line no-unused-vars
|
|
52
|
+
retryCondition: (_error) => true
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const asObject = () => {
|
|
56
|
+
return {apiKey, environment, deliveryToken}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const getProductsBySlugs = async ({
|
|
60
|
+
slugs = [], locale = DEFAULT_LOCALE
|
|
61
|
+
}) => {
|
|
62
|
+
if (!Array.isArray(slugs) || slugs.length < 1) throw new Error(`Invalid Slugs: ${ slugs}`);
|
|
63
|
+
// eslint-disable-next-line max-len
|
|
64
|
+
let url = `content_types/${PRODUCT_TYPE}/entries?environment=${environment}&locale=${locale}&include_count=true&query={"seo_information.slug": {"$in": ${arrayString(slugs)}}}`;
|
|
65
|
+
url += productIncludes
|
|
66
|
+
if (environment === DEV_ENVIRONMENT){
|
|
67
|
+
url += '&include_fallback=true'
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const res = await deliveryAxios.get(url);
|
|
71
|
+
return res && res.data && res.data.entries ? res.data.entries : res;
|
|
72
|
+
} catch (error) {
|
|
73
|
+
// log.error(error.response);
|
|
74
|
+
return error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const getProductsByQuery = async ({
|
|
79
|
+
query, locale = DEFAULT_LOCALE
|
|
80
|
+
}) => {
|
|
81
|
+
if (!query) throw new Error(`Invalid Query: ${ query }`);
|
|
82
|
+
// eslint-disable-next-line max-len
|
|
83
|
+
let url = `content_types/${PRODUCT_TYPE}/entries?environment=${environment}&locale=${locale}&include_count=true&query=${query}`
|
|
84
|
+
url += productIncludes
|
|
85
|
+
if (environment === DEV_ENVIRONMENT){
|
|
86
|
+
url += '&include_fallback=true'
|
|
87
|
+
}
|
|
88
|
+
try {
|
|
89
|
+
const res = await deliveryAxios.get(url);
|
|
90
|
+
return res && res.data && res.data.entries ? res.data.entries : res;
|
|
91
|
+
} catch (error) {
|
|
92
|
+
// log.error(error.response);
|
|
93
|
+
return error;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// includes is an array of all the referenced types in the contentType you are requesting that should be resolved
|
|
98
|
+
// and returned (Example: ['common_strings'])
|
|
99
|
+
const getEntriesByQuery = async ({
|
|
100
|
+
contentType = PRODUCT_TYPE, query = '', locale = DEFAULT_LOCALE, includes, start = 0, limit=50, onlyEntries=true
|
|
101
|
+
}) => {
|
|
102
|
+
// eslint-disable-next-line max-len
|
|
103
|
+
let url = `content_types/${contentType}/entries?environment=${environment}&locale=${locale}&include_count=true&limit=${limit}&query=${query}`
|
|
104
|
+
if (contentType === PRODUCT_TYPE) {
|
|
105
|
+
url += productIncludes
|
|
106
|
+
}
|
|
107
|
+
if (includes) {
|
|
108
|
+
for (const include of includes) {
|
|
109
|
+
url += `&include[]=${include}`
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (environment === DEV_ENVIRONMENT){
|
|
113
|
+
url += '&include_fallback=true'
|
|
114
|
+
}
|
|
115
|
+
if (start > 0) {
|
|
116
|
+
url += `&skip=${start}`
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const res = await deliveryAxios(url);
|
|
120
|
+
if (onlyEntries) {
|
|
121
|
+
return res && res.data && res.data.entries ? res.data.entries : res;
|
|
122
|
+
}
|
|
123
|
+
return res && res.data && res.data.entries ? res.data : res;
|
|
124
|
+
} catch (error) {
|
|
125
|
+
// log.error(error.response);
|
|
126
|
+
return error;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const queryProducts = async(query, locale) => {
|
|
131
|
+
const products = await getEntriesByQuery({query, locale})
|
|
132
|
+
return products
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const getProductsBySku = async (skus, locale) => {
|
|
136
|
+
if (!Array.isArray(skus)) {
|
|
137
|
+
skus = [skus]
|
|
138
|
+
}
|
|
139
|
+
const query = `{"variants": {"$in_query":{"sap_data.skus": {"$in": ${arrayString([skus])}}}}}`
|
|
140
|
+
return await queryProducts(query, locale);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const getProductsByGlobalId = async (ids, locale) => {
|
|
144
|
+
if (!Array.isArray(ids)) {
|
|
145
|
+
ids = [ids]
|
|
146
|
+
}
|
|
147
|
+
const query = `{"variants": {"$in_query":{"sap_data.global_id": {"$in": ${arrayString([ids])}}}}}`
|
|
148
|
+
return await queryProducts(query, locale)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const getProductsBySlug = async (slugs, locale) => {
|
|
152
|
+
if (!Array.isArray(slugs)) {
|
|
153
|
+
slugs = [slugs]
|
|
154
|
+
}
|
|
155
|
+
const query = `{"seo_information.slug": {"$in": ${arrayString(slugs)}}}`
|
|
156
|
+
return await queryProducts(query, locale)
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const getProductsByUID = async (ids, locale) => {
|
|
160
|
+
if (!Array.isArray(ids)) {
|
|
161
|
+
ids = [ids]
|
|
162
|
+
}
|
|
163
|
+
const query = `{"uid": {"$in": ${arrayString(ids)}}}`
|
|
164
|
+
return await queryProducts(query, locale)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const getTranslations = async (locale) => {
|
|
168
|
+
const translations = await getEntriesByQuery({contentType: 'strings_product_detail_page', locale, includes: ['common_strings'] })
|
|
169
|
+
return translations
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return {
|
|
173
|
+
asObject,
|
|
174
|
+
getEntriesByQuery,
|
|
175
|
+
getProductsBySku,
|
|
176
|
+
getProductsByGlobalId,
|
|
177
|
+
getProductsBySlug,
|
|
178
|
+
getProductsBySlugs,
|
|
179
|
+
getProductsByQuery,
|
|
180
|
+
getProductsByUID,
|
|
181
|
+
getTranslations
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
module.exports = {
|
|
186
|
+
csProduct
|
|
187
|
+
}
|
package/src/helpers.js
ADDED
|
@@ -0,0 +1,558 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { stripHtml } = require('@nuskin/ns-common-lib');
|
|
4
|
+
const TurndownService = require('turndown')
|
|
5
|
+
const {jsonToHtml} = require('@contentstack/json-rte-serializer')
|
|
6
|
+
const entities = require('entities');
|
|
7
|
+
const template = require('lodash/template')
|
|
8
|
+
const templateSettings = require('lodash/templateSettings')
|
|
9
|
+
|
|
10
|
+
function isIterable(obj) {
|
|
11
|
+
// checks for null and undefined
|
|
12
|
+
if (obj == null) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
return typeof obj[Symbol.iterator] === 'function';
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function decodeHTML(decodeMe) {
|
|
19
|
+
if (Array.isArray(decodeMe)) {
|
|
20
|
+
for (const item of decodeMe) {
|
|
21
|
+
entities.decodeHTML(item);
|
|
22
|
+
}
|
|
23
|
+
} else if (typeof(decodeMe) === 'string') {
|
|
24
|
+
return entities.decodeHTML(decodeMe);
|
|
25
|
+
}
|
|
26
|
+
return decodeMe;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function rteHasContent(rteField) {
|
|
30
|
+
const markdown = createMarkdownFromRTE(rteField);
|
|
31
|
+
return !!markdown;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function mapSubscriptions(subscriptions) {
|
|
35
|
+
templateSettings.interpolate = /{([\s\S]+?)}/g;
|
|
36
|
+
|
|
37
|
+
const [{content = [], footer} = {}] = subscriptions?.modal || []
|
|
38
|
+
const parseContent = content.map(({_metadata : {uid}, ...props}) => ({
|
|
39
|
+
uid, ...props
|
|
40
|
+
}))
|
|
41
|
+
|
|
42
|
+
const buildLink = ({href = '#', title = ''} = {}) => `<a href='${href}' target='blank'>${title}</a>`
|
|
43
|
+
|
|
44
|
+
const footerTemplate = template(footer?.content_text)
|
|
45
|
+
const parseFooter = footerTemplate({
|
|
46
|
+
terms_conditions: buildLink(footer?.terms_conditions),
|
|
47
|
+
return_policy: buildLink(footer?.return_policy),
|
|
48
|
+
faq: buildLink(footer?.faq)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
message : subscriptions?.message,
|
|
53
|
+
triggerText: subscriptions?.trigger_text,
|
|
54
|
+
modal : {
|
|
55
|
+
content: parseContent,
|
|
56
|
+
footer: htmlToMarkdown(parseFooter)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function getBestSkuData(variant) {
|
|
62
|
+
if (variant.sap_data && variant.sap_data.sku_data && variant.sap_data.sku_data.length > 0) {
|
|
63
|
+
if (variant.sap_data.sku_data.length > 1) {
|
|
64
|
+
let bestSKUData = variant.sap_data.sku_data.find(s=>s.status === 10);
|
|
65
|
+
bestSKUData = variant.sap_data.sku_data.find(s=>(s.status_selections || {}).status === 'sellable') || bestSKUData;
|
|
66
|
+
if (!bestSKUData) {
|
|
67
|
+
bestSKUData = variant.sap_data.sku_data.find(s=>s.status === 9);
|
|
68
|
+
bestSKUData = variant.sap_data.sku_data.find(s=>(s.status_selections || {}).status === 'inactive') || bestSKUData;
|
|
69
|
+
if (!bestSKUData) {
|
|
70
|
+
bestSKUData = variant.sap_data.sku_data.find(s=>s.status !== 2);
|
|
71
|
+
bestSKUData = variant.sap_data.sku_data.find(s=>(s.status_selections || {}).status !== 'discontinued') || bestSKUData;
|
|
72
|
+
}
|
|
73
|
+
if (!bestSKUData) {
|
|
74
|
+
bestSKUData = variant.sap_data.sku_data[0];
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return bestSKUData;
|
|
78
|
+
}
|
|
79
|
+
return variant.sap_data.sku_data[0] || {};
|
|
80
|
+
}
|
|
81
|
+
else if (variant.sap_data && Array.isArray(variant.sap_data.sku)) {
|
|
82
|
+
if (variant.sap_data.sku.length > 1) {
|
|
83
|
+
let bestSKUData = variant.sap_data.sku.find(s=>s.retail_status === 10);
|
|
84
|
+
if (!bestSKUData) {
|
|
85
|
+
bestSKUData = variant.sap_data.sku.find(s=>s.retail_status === 9);
|
|
86
|
+
if (!bestSKUData) {
|
|
87
|
+
bestSKUData = variant.sap_data.sku.find(s=>s.retail_status !== 2);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
bestSKUData.status = bestSKUData.retail_status;
|
|
91
|
+
return bestSKUData;
|
|
92
|
+
}
|
|
93
|
+
if (variant.sap_data.sku[0]) {
|
|
94
|
+
variant.sap_data.sku[0].status = variant.sap_data.sku[0].retail_status;
|
|
95
|
+
}
|
|
96
|
+
return variant.sap_data.sku[0] || {};
|
|
97
|
+
} else if (variant.sap_data && variant.sap_data.sku) {
|
|
98
|
+
variant.sap_data.sku.status = variant.sap_data.sku.retail_status;
|
|
99
|
+
return variant.sap_data.sku;
|
|
100
|
+
}
|
|
101
|
+
return {};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function mapCsStatus(variant) {
|
|
105
|
+
const bestSKU = getBestSkuData(variant);
|
|
106
|
+
const status = {isBackordered: false, backorderedAvailableDate: null};
|
|
107
|
+
|
|
108
|
+
if (bestSKU.status_selections) {
|
|
109
|
+
status.status = status.retail = status.wholesale = bestSKU.status_selections.status;
|
|
110
|
+
return status;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
switch (bestSKU.status) {
|
|
114
|
+
case 10:
|
|
115
|
+
status.status = status.retail = status.wholesale = 'sellable';
|
|
116
|
+
break;
|
|
117
|
+
case 9:
|
|
118
|
+
status.status = status.retail = status.wholesale = 'inactive';
|
|
119
|
+
break;
|
|
120
|
+
case 5:
|
|
121
|
+
status.status = status.retail = status.wholesale = 'stopped';
|
|
122
|
+
break;
|
|
123
|
+
case 4:
|
|
124
|
+
status.status = status.retail = status.wholesale = 'bundleOnly';
|
|
125
|
+
break;
|
|
126
|
+
case 2:
|
|
127
|
+
status.status = status.retail = status.wholesale = 'discontinued';
|
|
128
|
+
break;
|
|
129
|
+
default:
|
|
130
|
+
status.status = status.retail = status.wholesale = 'inactive';
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
return status;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function mapImage(image) {
|
|
137
|
+
if (Array.isArray(image)) {
|
|
138
|
+
image = image[0];
|
|
139
|
+
}
|
|
140
|
+
if ((image || {}).type) {
|
|
141
|
+
if (image.type === 'VIDEO') {
|
|
142
|
+
const derivatives = image.derivatives || {};
|
|
143
|
+
if ((image.previewUrls || []).length > 0) {
|
|
144
|
+
return {url: image.previewUrls[0], alt: image.description || image.name || '', thumbnail: derivatives.thumbnail};
|
|
145
|
+
}
|
|
146
|
+
return {url: derivatives.webImage, alt: image.description || image.name || '', thumbnail: derivatives.thumbnail};
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if ((image || {}).derivatives) {
|
|
150
|
+
return {url: (image.derivatives || {}).webImage, alt: image.description || image.name || '', thumbnail: (image.derivatives || {}).thumbnail};
|
|
151
|
+
} else if ((image || {}).files) {
|
|
152
|
+
return {url: (image.files.webImage || {}).url, alt: image.description || image.name || '', thumbnail: (image.files.thumbnail || {}).url};
|
|
153
|
+
}
|
|
154
|
+
return (image || {}).url ? { url: image.url, alt: image.description || image.title || '', thumbnail: image.url } : null;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function mapLink(link) {
|
|
158
|
+
return (link || {}).href ? { url: link.href, text: link.title || '' } : null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function mapProductDetails(productOrVariant) {
|
|
162
|
+
const productDetails = (productOrVariant || {}).product_details || {};
|
|
163
|
+
if ((productOrVariant.long_description_override || []).length > 0) {
|
|
164
|
+
productDetails.long_description = productOrVariant.long_description_override;
|
|
165
|
+
}
|
|
166
|
+
if ((productOrVariant.included_items_override || []).length > 0) {
|
|
167
|
+
productDetails.included_items = productOrVariant.included_items_override;
|
|
168
|
+
}
|
|
169
|
+
let description = productDetails.long_description;
|
|
170
|
+
if (!Array.isArray(description)) {
|
|
171
|
+
description = createMarkdown(description && description.type ? description : null, description);
|
|
172
|
+
description = description.type || description === '' || description === 'undefined' || description === null ? [] : [description];
|
|
173
|
+
}
|
|
174
|
+
const includedItems = productDetails.included_items;
|
|
175
|
+
const highlights = (productDetails.highlights || []).map(highlight => ({
|
|
176
|
+
iconUrl: (highlight.icon || {}).url,
|
|
177
|
+
label: highlight.text
|
|
178
|
+
}));
|
|
179
|
+
const originCountry = productDetails.origin_country;
|
|
180
|
+
const importer = productDetails.importer;
|
|
181
|
+
const warnings = productDetails.warnings;
|
|
182
|
+
return {
|
|
183
|
+
description: decodeHTML(description),
|
|
184
|
+
includedItems: decodeHTML(includedItems), highlights, originCountry, importer, warnings
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function featureImage(feature) {
|
|
189
|
+
let image = Array.isArray(feature.image) ? feature.image[0] : feature.image;
|
|
190
|
+
if(Array.isArray(feature.image_bynder) && feature.image_bynder.length > 0) {
|
|
191
|
+
image = feature.image_bynder;
|
|
192
|
+
}
|
|
193
|
+
return image;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function mapFeatures(productOrVariant) {
|
|
197
|
+
return ((productOrVariant || {}).features || []).map((feature = {}) => ({
|
|
198
|
+
// eslint-disable-next-line no-nested-ternary
|
|
199
|
+
image: mapImage(featureImage(feature)),
|
|
200
|
+
subtitle: feature.header,
|
|
201
|
+
features: feature.features,
|
|
202
|
+
backgroundColor: feature.background_color
|
|
203
|
+
}))
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
function benefitsToMarkdown(legacyBenefits, jsonBenefits) {
|
|
207
|
+
let benefits = '';
|
|
208
|
+
if (legacyBenefits.length > 0) {
|
|
209
|
+
const leg = legacyBenefits.map(benefit => ({
|
|
210
|
+
title: benefit.header,
|
|
211
|
+
benefits: benefit.benefits || []
|
|
212
|
+
}));
|
|
213
|
+
for (let i = 0; i < leg.length; i++) {
|
|
214
|
+
if (leg[i].title) {
|
|
215
|
+
benefits += `**${leg[i].title.trim().replace(/\*/g, '\\*')}**\n`;
|
|
216
|
+
}
|
|
217
|
+
for (const benefit of leg[i].benefits) {
|
|
218
|
+
benefits += `\n* ${benefit.trim().replace(/\*/g, '\\*')}`;
|
|
219
|
+
}
|
|
220
|
+
if (i < leg.length-1) {
|
|
221
|
+
benefits += '\n\n';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (jsonBenefits && rteHasContent(jsonBenefits)) {
|
|
226
|
+
benefits = createMarkdown(jsonBenefits);
|
|
227
|
+
}
|
|
228
|
+
return decodeHTML(benefits);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function mapBenefits(productOrVariant) {
|
|
232
|
+
let benefitsMedia = (productOrVariant || {}).benefits_media || {};
|
|
233
|
+
const legacyBenefits = Array.isArray((productOrVariant || {}).benefits) ? productOrVariant.benefits : [];
|
|
234
|
+
if ((productOrVariant || {}).benefits && (productOrVariant.benefits.image || productOrVariant.benefits.youtube_video_id)) {
|
|
235
|
+
benefitsMedia = {
|
|
236
|
+
image: Array.isArray(productOrVariant.benefits.image) ? productOrVariant.benefits.image[0] : productOrVariant.benefits.image,
|
|
237
|
+
youTubeVideoId: productOrVariant.benefits.youtube_video_id
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
// Account for new section in Product
|
|
241
|
+
if ((productOrVariant || {}).benefits_and_key_features && rteHasContent((productOrVariant || {}).benefits_and_key_features) && (productOrVariant.benefits_and_key_features.image || productOrVariant.benefits_and_key_features.youtube_video_id)) {
|
|
242
|
+
benefitsMedia = {
|
|
243
|
+
image: Array.isArray(productOrVariant.benefits_and_key_features.image) ? productOrVariant.benefits_and_key_features.image[0] : productOrVariant.benefits_and_key_features.image,
|
|
244
|
+
youTubeVideoId: productOrVariant.benefits_and_key_features.youtube_video_id
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
let jsonBenefits = (productOrVariant || {}).benefits &&
|
|
248
|
+
productOrVariant.benefits.benefits &&
|
|
249
|
+
productOrVariant.benefits.benefits.type ? productOrVariant.benefits.benefits : null;
|
|
250
|
+
// Account for new section in Product
|
|
251
|
+
jsonBenefits= (productOrVariant || {}).benefits_and_key_features &&
|
|
252
|
+
productOrVariant.benefits_and_key_features.benefits ? productOrVariant.benefits_and_key_features.benefits : jsonBenefits;
|
|
253
|
+
|
|
254
|
+
return {
|
|
255
|
+
image: mapImage(benefitsMedia.image),
|
|
256
|
+
youTubeVideoId: benefitsMedia.youTubeVideoId,
|
|
257
|
+
benefits: benefitsToMarkdown(legacyBenefits, jsonBenefits)
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function mapResults(productOrVariant) {
|
|
262
|
+
const results = (productOrVariant || {}).results || {};
|
|
263
|
+
const summary = results.summary ? [results.summary] : [];
|
|
264
|
+
return {
|
|
265
|
+
summary: Array.isArray(results.summary) ? results.summary : summary,
|
|
266
|
+
results: (results.results || []).map(result => ({
|
|
267
|
+
percentage: result.number,
|
|
268
|
+
text: decodeHTML(result.description)
|
|
269
|
+
})),
|
|
270
|
+
report: {
|
|
271
|
+
url: results.report_url,
|
|
272
|
+
text: results.report_text
|
|
273
|
+
},
|
|
274
|
+
image: mapImage((Array.isArray(results.image_or_video) && results.image_or_video.length > 0) || results.image),
|
|
275
|
+
youTubeVideoId: results.youtube_video_id
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
function mapSustainability(product) {
|
|
280
|
+
const sustainability = (product || {}).sustainability || {};
|
|
281
|
+
const youTubeVideoId = sustainability.youtube_video;
|
|
282
|
+
const image = mapImage(Array.isArray(sustainability.image) ? sustainability.image[0] : sustainability.image);
|
|
283
|
+
const richText = sustainability.description;
|
|
284
|
+
const description = hasRichTextContent(richText) ? createMarkdown(richText) : null;
|
|
285
|
+
const highlights = (sustainability.highlights || []).map(highlight => ({
|
|
286
|
+
image: mapImage((Array.isArray(highlight.image) ? highlight.image[0] : highlight.image)),
|
|
287
|
+
description: hasRichTextContent(highlight.description) ? createMarkdown(highlight.description) : null
|
|
288
|
+
}));
|
|
289
|
+
return {
|
|
290
|
+
youTubeVideoId,
|
|
291
|
+
image,
|
|
292
|
+
description,
|
|
293
|
+
highlights
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function mapUsage(productOrVariant, legacyUsage) {
|
|
298
|
+
const usage = (productOrVariant || {}).usage || {};
|
|
299
|
+
return {
|
|
300
|
+
video: {
|
|
301
|
+
url: usage.video_url,
|
|
302
|
+
posterUrl: usage.video_thumbnail_url
|
|
303
|
+
},
|
|
304
|
+
steps: decodeHTML(usage.steps),
|
|
305
|
+
recommendations: decodeHTML(usage.recommendations),
|
|
306
|
+
warnings: decodeHTML(usage.warnings),
|
|
307
|
+
additionalText: usage.additional_text,
|
|
308
|
+
image: mapImage(usage.image_or_video || usage.image),
|
|
309
|
+
youTubeVideoId: usage.youtube_video_id,
|
|
310
|
+
markdown: legacyUsage ? htmlToMarkdown(legacyUsage) : null
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
function mapResource(resource) {
|
|
315
|
+
if (resource.bynder_document && resource.bynder_document.length > 0) {
|
|
316
|
+
const bynder = resource.bynder_document[0];
|
|
317
|
+
return {
|
|
318
|
+
title: bynder.name || bynder.description || '',
|
|
319
|
+
url: (bynder.derivatives || {}).original,
|
|
320
|
+
image: {
|
|
321
|
+
url: (bynder.derivatives || {}).thumbnail,
|
|
322
|
+
alt: (bynder.meta || {}).SEO_Tags || bynder.description || bynder.name || resource.title || ''
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
title: (resource.download_link || {}).title,
|
|
329
|
+
url: (resource.download_link || {}).href,
|
|
330
|
+
image: {
|
|
331
|
+
url: (resource.link_image || {}).url,
|
|
332
|
+
alt: (resource.link_image || {}).title
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function mapResources(productOrVariant) {
|
|
338
|
+
if (productOrVariant.resources && Array.isArray(productOrVariant.resources) && productOrVariant.resources.length > 0) {
|
|
339
|
+
return productOrVariant.resources.map(resource => (
|
|
340
|
+
mapResource(resource)
|
|
341
|
+
));
|
|
342
|
+
}
|
|
343
|
+
try {
|
|
344
|
+
const legacyResources = JSON.parse((productOrVariant.legacy_resources || '[]'));
|
|
345
|
+
return (legacyResources || []).map(resource => ({
|
|
346
|
+
title: resource.title,
|
|
347
|
+
url: resource.url,
|
|
348
|
+
image: {
|
|
349
|
+
url: (resource.link_image || {}).url,
|
|
350
|
+
alt: (resource.link_image || {}).title
|
|
351
|
+
}
|
|
352
|
+
}));
|
|
353
|
+
} catch(error) {
|
|
354
|
+
return [];
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
function mapIngredients(ings, legacyIngredients) {
|
|
359
|
+
if (Array.isArray(ings)) {
|
|
360
|
+
return ings.map(ingredients => ({
|
|
361
|
+
keyIngredients: ((ingredients || {}).key_ingredients || []).map(keyIngredient => ({
|
|
362
|
+
image: {url:(keyIngredient.icon || {}).url, alt:(keyIngredient.icon || {}).title},
|
|
363
|
+
name: keyIngredient.header,
|
|
364
|
+
description: keyIngredient.description
|
|
365
|
+
})),
|
|
366
|
+
nutritionInformationImage: (ingredients || {}).nutrition_information_image
|
|
367
|
+
? { url: ingredients.nutrition_information_image.url, alt: ingredients.nutrition_information_image.title }
|
|
368
|
+
: null,
|
|
369
|
+
productName: (ingredients || {}).product_name,
|
|
370
|
+
allIngredients: decodeHTML((ingredients || {}).all_ingredients),
|
|
371
|
+
otherIngredients: (ingredients || {}).other_ingredients,
|
|
372
|
+
activeIngredients: (ingredients || {}).active_ingredients,
|
|
373
|
+
inactiveIngredients: (ingredients || {}).inactive_ingredients,
|
|
374
|
+
ingredientDisclaimers: (ingredients || {}).ingredient_disclaimers || [],
|
|
375
|
+
markdown: legacyIngredients ? htmlToMarkdown(legacyIngredients) : null
|
|
376
|
+
}))
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
const keyIngredients = (ings || {}).key_ingredients || [];
|
|
380
|
+
if (keyIngredients.length > 0) {
|
|
381
|
+
return [{
|
|
382
|
+
keyIngredients: keyIngredients.map(keyIngredient => ({
|
|
383
|
+
image: {url:(keyIngredient.icon || {}).url, alt:(keyIngredient.icon || {}).title},
|
|
384
|
+
name: keyIngredient.header,
|
|
385
|
+
description: keyIngredient.description
|
|
386
|
+
})),
|
|
387
|
+
nutritionInformationImage: (ings || {}).nutrition_information_image
|
|
388
|
+
? { url: ings.nutrition_information_image.url, alt: ings.nutrition_information_image.title }
|
|
389
|
+
: null,
|
|
390
|
+
allIngredients: decodeHTML((ings || {}).all_ingredients),
|
|
391
|
+
otherIngredients: (ings || {}).other_ingredients,
|
|
392
|
+
activeIngredients: (ings || {}).active_ingredients,
|
|
393
|
+
inactiveIngredients: (ings || {}).inactive_ingredients,
|
|
394
|
+
ingredientDisclaimers: (ings || {}).ingredient_disclaimers || [],
|
|
395
|
+
markdown: legacyIngredients ? htmlToMarkdown(legacyIngredients) : null
|
|
396
|
+
}]
|
|
397
|
+
}
|
|
398
|
+
return [];
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
function mapSalesDisclaimer(salesDisclaimer) {
|
|
402
|
+
salesDisclaimer = salesDisclaimer || {};
|
|
403
|
+
const text = stripHtml(salesDisclaimer.text || '');
|
|
404
|
+
const richText = salesDisclaimer.rich_text;
|
|
405
|
+
if (!text && !hasRichTextContent(richText)) {
|
|
406
|
+
return null;
|
|
407
|
+
}
|
|
408
|
+
const boldText = salesDisclaimer.bold_text || '';
|
|
409
|
+
const link = mapLink(salesDisclaimer.link);
|
|
410
|
+
const markdownText = createMarkdown(richText, text, boldText, link);
|
|
411
|
+
return {
|
|
412
|
+
text,
|
|
413
|
+
boldText,
|
|
414
|
+
link,
|
|
415
|
+
markdownText
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function mapDisclaimerWarnings(disclaimerWarnings) {
|
|
420
|
+
disclaimerWarnings = disclaimerWarnings || [];
|
|
421
|
+
return disclaimerWarnings
|
|
422
|
+
.map((disclaimerWarning) => {
|
|
423
|
+
const text = stripHtml(disclaimerWarning.text || '');
|
|
424
|
+
const richText = disclaimerWarning.rich_text;
|
|
425
|
+
if (!text && !hasRichTextContent(richText)) {
|
|
426
|
+
return null;
|
|
427
|
+
}
|
|
428
|
+
const icon = mapImage(disclaimerWarning.icon);
|
|
429
|
+
const boldText = disclaimerWarning.bold_text || '';
|
|
430
|
+
const link = mapLink(disclaimerWarning.link);
|
|
431
|
+
const markdownText = createMarkdown(richText, text, boldText, link);
|
|
432
|
+
return {
|
|
433
|
+
icon,
|
|
434
|
+
text,
|
|
435
|
+
boldText,
|
|
436
|
+
link,
|
|
437
|
+
markdownText
|
|
438
|
+
};
|
|
439
|
+
})
|
|
440
|
+
.filter((disclaimerWarning) => !!disclaimerWarning);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
function hasRichTextContent(richText) {
|
|
444
|
+
// when cleared, the rich text json still contains children, this checks for any text or links
|
|
445
|
+
return ((richText || {}).children || []).some((line) =>
|
|
446
|
+
(line.children || []).some(
|
|
447
|
+
(block) => (!!block.text && block.text !=='This block is deleted,Remove this placeholder') || (block.children || []).some((part) => (!!part.text && part.text !== 'This block is deleted,Remove this placeholder'))
|
|
448
|
+
)
|
|
449
|
+
);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
function createMarkdownWithModifiers(text, bold, italic) {
|
|
453
|
+
let modifier = '';
|
|
454
|
+
if(bold) {
|
|
455
|
+
modifier += '**';
|
|
456
|
+
}
|
|
457
|
+
if(italic){
|
|
458
|
+
modifier += '_';
|
|
459
|
+
}
|
|
460
|
+
return `${modifier}${text}${modifier}`;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function createMarkdownFromRichText(richText) {
|
|
464
|
+
let markdown = '';
|
|
465
|
+
for (const [index, line] of ((richText || {}).children || []).entries()) {
|
|
466
|
+
if (index > 0) {
|
|
467
|
+
markdown += '\n';
|
|
468
|
+
}
|
|
469
|
+
for (const block of line.children) {
|
|
470
|
+
if (block.type === 'a') {
|
|
471
|
+
const url = (block.attrs || {}).url || '';
|
|
472
|
+
for (const part of block.children) {
|
|
473
|
+
const linkMarkdown = `[${part.text}](${url})`;
|
|
474
|
+
markdown += createMarkdownWithModifiers(linkMarkdown, part.bold, part.italic);
|
|
475
|
+
}
|
|
476
|
+
} else if (block.text) {
|
|
477
|
+
markdown += createMarkdownWithModifiers(block.text, block.bold, block.italic);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
return stripHtml(markdown);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function createMarkdownFromRTE(richText) {
|
|
485
|
+
if (!richText) {
|
|
486
|
+
return null;
|
|
487
|
+
}
|
|
488
|
+
const turndownService = new TurndownService();
|
|
489
|
+
const htmlVal = jsonToHtml(richText);
|
|
490
|
+
const markdown = turndownService.turndown(htmlVal);
|
|
491
|
+
if (markdown === 'This block is deleted,Remove this placeholder') {
|
|
492
|
+
return null
|
|
493
|
+
}
|
|
494
|
+
return stripHtml(markdown);
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
function htmlToMarkdown(html) {
|
|
498
|
+
const turndownService = new TurndownService();
|
|
499
|
+
const markdown = turndownService.turndown(html);
|
|
500
|
+
return stripHtml(markdown);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
function createMarkdownFromReplacement(text, boldText, link) {
|
|
504
|
+
let markdown = text || '';
|
|
505
|
+
if (boldText) {
|
|
506
|
+
markdown = markdown.replace('%boldText%', `**${boldText}**`);
|
|
507
|
+
}
|
|
508
|
+
if (link && link.text && link.url) {
|
|
509
|
+
markdown = markdown.replace('%linkText%', `[${link.text}](${link.url})`);
|
|
510
|
+
}
|
|
511
|
+
return markdown;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
function createMarkdown(richText, text, boldText, link) {
|
|
515
|
+
if (hasRichTextContent(richText)) {
|
|
516
|
+
return createMarkdownFromRTE(richText);
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
return createMarkdownFromReplacement(text, boldText, link);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
const arrayString = (array) => {
|
|
523
|
+
let retString = '['
|
|
524
|
+
if (array[0]) {
|
|
525
|
+
retString += `"${ array[0] }"`;
|
|
526
|
+
}
|
|
527
|
+
for (let index = 1; index < array.length; index++) {
|
|
528
|
+
retString += `, "${ array[index] }"`;
|
|
529
|
+
}
|
|
530
|
+
retString += ']'
|
|
531
|
+
return retString
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
module.exports = {
|
|
535
|
+
getBestSkuData,
|
|
536
|
+
mapImage,
|
|
537
|
+
mapLink,
|
|
538
|
+
mapFeatures,
|
|
539
|
+
mapProductDetails,
|
|
540
|
+
mapSalesDisclaimer,
|
|
541
|
+
mapDisclaimerWarnings,
|
|
542
|
+
mapBenefits,
|
|
543
|
+
mapResults,
|
|
544
|
+
mapSustainability,
|
|
545
|
+
mapUsage,
|
|
546
|
+
mapResources,
|
|
547
|
+
mapIngredients,
|
|
548
|
+
mapCsStatus,
|
|
549
|
+
hasRichTextContent,
|
|
550
|
+
createMarkdownWithModifiers,
|
|
551
|
+
createMarkdownFromRichText,
|
|
552
|
+
createMarkdownFromReplacement,
|
|
553
|
+
createMarkdown,
|
|
554
|
+
isIterable,
|
|
555
|
+
rteHasContent,
|
|
556
|
+
mapSubscriptions,
|
|
557
|
+
arrayString
|
|
558
|
+
};
|
package/src/index.js
ADDED
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// WARNING: Set to true if running locally to avoid Redis, don't forget to change back
|
|
4
|
+
// const localDev = false;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @typedef { import("aws-lambda") } AWSLambda
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const axios = require('axios');
|
|
11
|
+
const axiosRetry = require('axios-retry');
|
|
12
|
+
|
|
13
|
+
axiosRetry(axios, { retries: 1 });
|
|
14
|
+
|
|
15
|
+
const config = require('config');
|
|
16
|
+
|
|
17
|
+
const MarketRegionMappings = require('./resources/marketRegionMappings');
|
|
18
|
+
|
|
19
|
+
const { isNullOrEmpty, getLocaleInfo } = require('@nuskin/ns-common-lib');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Gets a list of skus.
|
|
23
|
+
* @param {string[]} skus the skus to be retrieved
|
|
24
|
+
* @param {string} locale the country code and language (ex: en_US)
|
|
25
|
+
*/
|
|
26
|
+
async function getProductStatus(skus, locale) {
|
|
27
|
+
validateRequest(skus, locale);
|
|
28
|
+
|
|
29
|
+
const localeInfo = getLocaleInfo(locale);
|
|
30
|
+
|
|
31
|
+
// get market configuration from AEM
|
|
32
|
+
const marketConfig = await getMarketConfig(localeInfo);
|
|
33
|
+
|
|
34
|
+
// if (products.length) {
|
|
35
|
+
// get product status data for each product
|
|
36
|
+
const productStatusResponse = await getProductStatusResponse(
|
|
37
|
+
marketConfig,
|
|
38
|
+
localeInfo,
|
|
39
|
+
skus
|
|
40
|
+
);
|
|
41
|
+
if (productStatusResponse && productStatusResponse.items) {
|
|
42
|
+
for (let i = 0; i < skus.length; i++) {
|
|
43
|
+
if (productStatusResponse.items[skus[i]]) {
|
|
44
|
+
const productStatus = productStatusResponse.items[skus[i]];
|
|
45
|
+
const overrideMaxQuantity = marketConfig.skuMaxQty ? marketConfig.skuMaxQty[skus[i]] : undefined;
|
|
46
|
+
const availableQuantity = productStatus.availableQuantity < 999 ? productStatus.availableQuantity : 999;
|
|
47
|
+
productStatus.maxQuantity = overrideMaxQuantity || availableQuantity;
|
|
48
|
+
productStatus.availableQuantity = availableQuantity;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return productStatusResponse && productStatusResponse.items || {};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Validates the post request paramaters are acceptible
|
|
57
|
+
* @param {string[]} skus
|
|
58
|
+
* @param {string} locale
|
|
59
|
+
*/
|
|
60
|
+
function validateRequest(skus, locale) {
|
|
61
|
+
const errorMessages = [];
|
|
62
|
+
if (!skus || !Array.isArray(skus) || skus.length === 0) {
|
|
63
|
+
errorMessages.push("Required parameter 'skus' is invalid.");
|
|
64
|
+
}
|
|
65
|
+
if (!locale || !locale.toString().match(/^[a-z]{2}_[A-Za-z]{2}$/)) {
|
|
66
|
+
errorMessages.push("Required parameter 'locale' is invalid.");
|
|
67
|
+
}
|
|
68
|
+
if (errorMessages.length) {
|
|
69
|
+
// eslint-disable-next-line no-throw-literal
|
|
70
|
+
throw {
|
|
71
|
+
status: 400,
|
|
72
|
+
message: `Bad Request. ${errorMessages.join(' ')}`
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Get AEM market configuration
|
|
79
|
+
* @param {{language: string, countryCode: string}} localeInfo
|
|
80
|
+
*/
|
|
81
|
+
async function getMarketConfig(localeInfo) {
|
|
82
|
+
const siteHome = config.properties.home;
|
|
83
|
+
const region = MarketRegionMappings[localeInfo.countryCode];
|
|
84
|
+
const marketConfigUrl = `${siteHome}/content/configuration/global/markets/${region}.service.${localeInfo.countryCode}.json`;
|
|
85
|
+
|
|
86
|
+
let marketConfigResponse = null;
|
|
87
|
+
let marketConfig;
|
|
88
|
+
try {
|
|
89
|
+
if (isNullOrEmpty(marketConfigResponse)) {
|
|
90
|
+
marketConfigResponse = await axios.get(marketConfigUrl);
|
|
91
|
+
marketConfig = marketConfigResponse.data;
|
|
92
|
+
} else if (typeof marketConfigResponse === 'string') {
|
|
93
|
+
marketConfig = JSON.parse(marketConfigResponse);
|
|
94
|
+
} else {
|
|
95
|
+
marketConfig = marketConfigResponse;
|
|
96
|
+
}
|
|
97
|
+
} catch (error) {
|
|
98
|
+
// log.warn({ marketConfigFailed: error });
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (marketConfig && !isNullOrEmpty(marketConfig)) {
|
|
102
|
+
marketConfig.productStatusRequestedQuantity =
|
|
103
|
+
Number(marketConfig.productStatusRequestedQuantity) || 999;
|
|
104
|
+
|
|
105
|
+
if (Array.isArray(marketConfig.skuMaxQty)) {
|
|
106
|
+
const skuMaxQuantityMapping = {};
|
|
107
|
+
const skuMaxQuantities = marketConfig.skuMaxQty.map((smq) => JSON.parse(smq));
|
|
108
|
+
for (const skuMaxQuantity of skuMaxQuantities) {
|
|
109
|
+
skuMaxQuantityMapping[skuMaxQuantity.sku] = skuMaxQuantity.maxQty;
|
|
110
|
+
}
|
|
111
|
+
marketConfig.skuMaxQty = skuMaxQuantityMapping;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return marketConfig;
|
|
115
|
+
}
|
|
116
|
+
return {};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Get product status for each sku
|
|
122
|
+
* @param {object} marketConfig
|
|
123
|
+
* @param {{language: string, countryCode: string}} localeInfo
|
|
124
|
+
* @param {array} skus an array of all variant skus to get status for
|
|
125
|
+
*/
|
|
126
|
+
async function getProductStatusResponse(marketConfig, localeInfo, skus) {
|
|
127
|
+
const marketRequestedQuantity = marketConfig.productStatusRequestedQuantity || 999;
|
|
128
|
+
|
|
129
|
+
const skusRequest = skus.reduce((result, sku) => {
|
|
130
|
+
result[sku] = {
|
|
131
|
+
requestedQuantity: marketRequestedQuantity
|
|
132
|
+
};
|
|
133
|
+
return result
|
|
134
|
+
}, {});
|
|
135
|
+
|
|
136
|
+
const productStatusUrl = `${config.properties.awsBase}/product/v2/status?country=${localeInfo.countryCode}`;
|
|
137
|
+
const productStatusPayload = JSON.stringify(skusRequest);
|
|
138
|
+
|
|
139
|
+
try {
|
|
140
|
+
const axiosInstance = axios.create({
|
|
141
|
+
baseURL: productStatusUrl,
|
|
142
|
+
timeout: 10000,
|
|
143
|
+
headers: {
|
|
144
|
+
client_id: config.properties.clientId
|
|
145
|
+
// 'x-correlation-id': correlate.retrieveCorrelationId(),
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
axiosRetry(axiosInstance, {
|
|
149
|
+
retries: 2,
|
|
150
|
+
shouldResetTimeout: true,
|
|
151
|
+
// eslint-disable-next-line no-unused-vars
|
|
152
|
+
retryCondition: (_error) => true
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const productStatusResponse = await axiosInstance.post('', productStatusPayload);
|
|
156
|
+
|
|
157
|
+
return productStatusResponse.data;
|
|
158
|
+
} catch (error) {
|
|
159
|
+
console.log({ productStatusFailed: error });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
module.exports = {
|
|
166
|
+
getProductStatus
|
|
167
|
+
};
|