@nuskin/cs-product-lib 1.0.0 → 1.0.1-cx12-6830.2

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuskin/cs-product-lib",
3
- "version": "1.0.0",
3
+ "version": "1.0.1-cx12-6830.2",
4
4
  "description": "Library for pulling and parsing product content (and applying business logic) retrieved from Contentstack",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -40,6 +40,7 @@
40
40
  "@nuskin/ns-common-lib": "1.4.5",
41
41
  "axios": "1.3.0",
42
42
  "axios-retry": "3.4.0",
43
+ "config": "^3.3.6",
43
44
  "entities": "4.4.0",
44
45
  "lodash": "4.17.21",
45
46
  "turndown": "7.1.1"
@@ -0,0 +1,30 @@
1
+ const axios = require("axios");
2
+ const axiosRetry = require("axios-retry");
3
+
4
+ const API_TIMEOUT = 3000;
5
+ const CONTENT_STACK_DELIVERY_URL = "https://cdn.contentstack.io/v3/";
6
+
7
+
8
+ const deliveryAxios = ({ apiKey, deliveryToken }) => {
9
+ const axiosClient = axios.create({
10
+ baseURL: CONTENT_STACK_DELIVERY_URL,
11
+ timeout: API_TIMEOUT,
12
+ headers: {
13
+ api_key: apiKey,
14
+ access_token: deliveryToken
15
+ }
16
+ });
17
+
18
+ /* istanbul ignore next */
19
+ axiosRetry(axiosClient, {
20
+ retries: 3,
21
+ shouldResetTimeout: true,
22
+ retryCondition: () => true
23
+ });
24
+
25
+ return axiosClient;
26
+ };
27
+
28
+ module.exports = {
29
+ deliveryAxios
30
+ };
package/src/csProduct.js CHANGED
@@ -1,187 +1,354 @@
1
- "use strict"
1
+ 'use strict'
2
2
 
3
- const axios = require('axios');
4
- const axiosRetry = require('axios-retry');
5
- const {arrayString} = require('./helpers')
3
+ const helpers = require('./helpers')
4
+ const { getProductStatus } = require('./status')
5
+ const { deliveryAxios } = require('./axiosClient')
6
+ const { formatTranslations } = require('./formatTranslations')
7
+ const { formatVariant } = require('./formatVariant')
8
+ const { formatProduct } = require('./formatProduct')
9
+ const { transformBundle } = require('./transformBundle')
10
+
11
+ const validateConfig = (config) => {
12
+ if (!config) {
13
+ throw new Error(
14
+ `This will not work without a valid configuration {apiKey, environment, deliveryToken}.`
15
+ )
16
+ }
17
+
18
+ const { apiKey, environment, deliveryToken } = config
19
+ if (!apiKey) throw new Error('apiKey missing in config.')
20
+ else if (!environment) throw new Error('environment missing in config.')
21
+ else if (!deliveryToken) throw new Error('deliveryToken missing in config.')
22
+ }
6
23
 
7
24
  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';
25
+ const DEV_ENVIRONMENT = 'public-dev'
26
+ const VARIANTS = 'variants'
27
+ const KEY_INGREDIENTS = 'ingredients.key_ingredients'
28
+ const PRODUCT_HIGHLIGHTS = 'product_details.highlights'
29
+ const VARIANT_HIGHLIGHTS = 'variants.product_details.highlights'
30
+ const VARIANT_KEY_INGREDIENTS =
31
+ 'variants.ingredients_override.key_ingredients'
32
+ const PRODUCT_FAQS = 'faqs'
33
+ const VARIANT_FAQS = 'variants.faqs'
34
+ const PRODUCT_RESOURCES = 'resources'
35
+ const VARIANT_RESOURCES = 'variants.resources'
36
+ const PRODUCT_CATEGORY = 'categories'
37
+ const PRODUCT_SUBCATEGORIES = 'categories.subcategories'
38
+ const DEFAULT_LOCALE = 'en'
39
+ const PRODUCT_TYPE = 'product'
40
+ const INCLUDE_FALLBACK = '&include_fallback=true'
41
+ const REQUIRED = 'required_products.product.'
42
+ const OPTIONAL = 'optional_products.product.'
43
+ const BUNDLE_TYPE = 'bundle'
28
44
 
29
45
  // 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}`;
46
+ 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}`
47
+ const requiredIncludes = `&include[]=${REQUIRED}${VARIANTS}&include[]=${REQUIRED}${PRODUCT_FAQS}&include[]=${REQUIRED}${KEY_INGREDIENTS}&include[]=${REQUIRED}${PRODUCT_HIGHLIGHTS}&include[]=${REQUIRED}${PRODUCT_RESOURCES}&include[]=${REQUIRED}${VARIANT_KEY_INGREDIENTS}&include[]=${REQUIRED}${VARIANT_HIGHLIGHTS}&include[]=${REQUIRED}${VARIANT_FAQS}&include[]=${REQUIRED}${VARIANT_RESOURCES}&include[]=${REQUIRED}${PRODUCT_CATEGORY}&include[]=${REQUIRED}${PRODUCT_SUBCATEGORIES}`
48
+ const optionalIncludes = `&include[]=${OPTIONAL}${VARIANTS}&include[]=${OPTIONAL}${PRODUCT_FAQS}&include[]=${OPTIONAL}${KEY_INGREDIENTS}&include[]=${OPTIONAL}${PRODUCT_HIGHLIGHTS}&include[]=${OPTIONAL}${PRODUCT_RESOURCES}&include[]=${OPTIONAL}${VARIANT_KEY_INGREDIENTS}&include[]=${OPTIONAL}${VARIANT_HIGHLIGHTS}&include[]=${OPTIONAL}${VARIANT_FAQS}&include[]=${OPTIONAL}${VARIANT_RESOURCES}&include[]=${OPTIONAL}${PRODUCT_CATEGORY}&include[]=${OPTIONAL}${PRODUCT_SUBCATEGORIES}`
31
49
 
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
- });
50
+ validateConfig(config)
51
+ const { apiKey, environment, deliveryToken } = config
52
+ const axios = deliveryAxios({ apiKey, deliveryToken })
54
53
 
55
54
  const asObject = () => {
56
- return {apiKey, environment, deliveryToken}
55
+ return { apiKey, environment, deliveryToken }
57
56
  }
58
57
 
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'
58
+ const removeInvalidVariants = async (products, locale, translations) => {
59
+ const validSkus = []
60
+ for (const product of products) {
61
+ for (const variant of product?.variants || []) {
62
+ const bestSkuData = helpers?.getBestSkuData(variant) || []
63
+ if (bestSkuData && Object.keys(bestSkuData)?.length > 0) {
64
+ validSkus.push(bestSkuData.sku)
65
+ }
66
+ }
68
67
  }
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;
68
+ if (environment === 'public-dev' && validSkus?.length === 0) return
69
+
70
+ const localeData = helpers?.parseLocale(locale)
71
+ locale = helpers?.reorderLocale(locale)
72
+ let status = {}
73
+ let marketData = {}
74
+ if (validSkus?.length > 0) {
75
+ status = await getProductStatus(validSkus, locale)
76
+ const localeSplit = locale.split('_')
77
+ let market =
78
+ localeSplit?.length > 1 ? localeSplit[1].toUpperCase() : 'XX'
79
+ if (market === 'XX') {
80
+ const split2 = locale.split('-')
81
+ market = split2.length > 1 ? split2[0].toUpperCase() : ''
82
+ }
83
+ marketData = await getEntriesByQuery({
84
+ contentType: 'market',
85
+ query: `{"iso_code_2": "${market}"}`,
86
+ locale: 'en'
87
+ })
88
+ if (marketData?.length > 0) {
89
+ marketData = marketData[0]
90
+ }
91
+ }
92
+ for (const product of products) {
93
+ const validVariants = []
94
+ for (const variant of product?.variants || []) {
95
+ const bestSkuData = helpers?.getBestSkuData(variant)
96
+ if (bestSkuData.sku) {
97
+ const varStatus = status[bestSkuData.sku]
98
+ if (varStatus && varStatus.status.retail) {
99
+ variant.sap_data.sku_data = [bestSkuData]
100
+ validVariants.push({
101
+ ...variant,
102
+ status: varStatus,
103
+ marketData
104
+ })
105
+ }
106
+ }
107
+ }
108
+ product.variants = validVariants
109
+ product.translations = Array.isArray(translations)
110
+ ? translations[0]
111
+ : translations
112
+ product.localeData = localeData
75
113
  }
76
114
  }
77
115
 
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;
116
+ const parseSharedStrings = async (translations) => {
117
+ if (helpers?.isIterable(translations)) {
118
+ for (const translation of translations) {
119
+ translation.common_strings = translation?.common_strings
120
+ ?.map((obj) => {
121
+ const commonStrings = { type: obj.key }
122
+ // eslint-disable-next-line no-sequences
123
+ commonStrings.strings = (
124
+ (obj || {}).strings || []
125
+ ).reduce(
126
+ (result, { key, value }) => (
127
+ (result[key] = value), result
128
+ ),
129
+ {}
130
+ )
131
+ return commonStrings
132
+ })
133
+ .reduce((result, { type, strings }) => {
134
+ result[type] = strings
135
+ return result
136
+ }, {})
137
+ }
94
138
  }
95
139
  }
96
-
140
+
141
+ const formatProductWithSchima = (products = []) => {
142
+ return products?.map((product) => {
143
+ const formatedProduct = formatProduct(product)
144
+ formatedProduct.variants = formatedProduct?.variants?.map(
145
+ (variant) => formatVariant(variant)
146
+ )
147
+ return formatedProduct
148
+ })
149
+ }
150
+
151
+ const formatBundleWithSchima = (products = []) => {
152
+ return products?.map((product) => {
153
+ const formatedProduct = transformBundle(product)
154
+ formatedProduct.variants = formatedProduct?.variants?.map(
155
+ (variant) => formatVariant(variant)
156
+ )
157
+ return formatedProduct
158
+ })
159
+ }
160
+
97
161
  // includes is an array of all the referenced types in the contentType you are requesting that should be resolved
98
162
  // and returned (Example: ['common_strings'])
99
163
  const getEntriesByQuery = async ({
100
- contentType = PRODUCT_TYPE, query = '', locale = DEFAULT_LOCALE, includes, start = 0, limit=50, onlyEntries=true
164
+ contentType = PRODUCT_TYPE,
165
+ query = '',
166
+ locale = DEFAULT_LOCALE,
167
+ includes,
168
+ start = 0,
169
+ limit = 50
101
170
  }) => {
102
171
  // eslint-disable-next-line max-len
103
172
  let url = `content_types/${contentType}/entries?environment=${environment}&locale=${locale}&include_count=true&limit=${limit}&query=${query}`
104
173
  if (contentType === PRODUCT_TYPE) {
105
174
  url += productIncludes
106
175
  }
176
+ if (contentType === BUNDLE_TYPE) {
177
+ url += requiredIncludes
178
+ url += optionalIncludes
179
+ }
107
180
  if (includes) {
108
181
  for (const include of includes) {
109
182
  url += `&include[]=${include}`
110
183
  }
111
184
  }
112
- if (environment === DEV_ENVIRONMENT){
113
- url += '&include_fallback=true'
185
+ if (environment === DEV_ENVIRONMENT) {
186
+ url += INCLUDE_FALLBACK
114
187
  }
115
188
  if (start > 0) {
116
189
  url += `&skip=${start}`
117
190
  }
118
191
  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;
192
+ const res = await axios(url)
193
+ return res?.data?.entries ? res.data.entries : res
124
194
  } catch (error) {
125
- // log.error(error.response);
126
- return error;
195
+ return error
127
196
  }
128
197
  }
129
198
 
130
- const queryProducts = async(query, locale) => {
131
- const products = await getEntriesByQuery({query, locale})
199
+ const queryProducts = async (query, locale) => {
200
+ const products = await getEntriesByQuery({ query, locale })
132
201
  return products
133
202
  }
134
203
 
204
+ const getBundle = async (id, locale) => {
205
+ const query = `{ "$or": [{ "bundle_id": { "$in": ["${id}"]}}, { "uid": { "$in": ["${id}"] }}] }`
206
+ const bundles = await getEntriesByQuery({
207
+ query,
208
+ locale,
209
+ contentType: BUNDLE_TYPE
210
+ })
211
+ const translations = await getTranslations(locale)
212
+
213
+ for (let b = 0; b < bundles.length; b++) {
214
+ for (
215
+ let i = 0;
216
+ i < (bundles[b].required_products || []).length;
217
+ i++
218
+ ) {
219
+ await removeInvalidVariants(
220
+ bundles[b].required_products[i].product,
221
+ locale,
222
+ translations
223
+ )
224
+ }
225
+ for (
226
+ let i = 0;
227
+ i < (bundles[b].optional_products || []).length;
228
+ i++
229
+ ) {
230
+ await removeInvalidVariants(
231
+ bundles[b].optional_products[i].product,
232
+ locale,
233
+ translations
234
+ )
235
+ }
236
+ }
237
+ return formatBundleWithSchima(bundles)
238
+ }
239
+
135
240
  const getProductsBySku = async (skus, locale) => {
136
241
  if (!Array.isArray(skus)) {
137
242
  skus = [skus]
138
243
  }
139
- const query = `{"variants": {"$in_query":{"sap_data.skus": {"$in": ${arrayString([skus])}}}}}`
140
- return await queryProducts(query, locale);
244
+ const query = `{"variants": {"$in_query":{"sap_data.skus": {"$in": ${helpers?.arrayString(
245
+ [skus]
246
+ )}}}}}`
247
+
248
+ let products = await queryProducts(query, locale)
249
+ const translations = await getTranslations(locale)
250
+ // await parseSharedStrings(translations);
251
+ await removeInvalidVariants(products, locale, translations)
252
+ return formatProductWithSchima(products)
141
253
  }
142
254
 
143
255
  const getProductsByGlobalId = async (ids, locale) => {
144
256
  if (!Array.isArray(ids)) {
145
257
  ids = [ids]
146
258
  }
147
- const query = `{"variants": {"$in_query":{"sap_data.global_id": {"$in": ${arrayString([ids])}}}}}`
148
- return await queryProducts(query, locale)
259
+ const query = `{"variants": {"$in_query":{"sap_data.global_id": {"$in": ${helpers?.arrayString(
260
+ [ids]
261
+ )}}}}}`
262
+
263
+ let products = await queryProducts(query, locale)
264
+ const translations = await getTranslations(locale)
265
+ // await parseSharedStrings(translations);
266
+ await removeInvalidVariants(products, locale, translations)
267
+ return formatProductWithSchima(products)
149
268
  }
150
269
 
151
270
  const getProductsBySlug = async (slugs, locale) => {
152
271
  if (!Array.isArray(slugs)) {
153
272
  slugs = [slugs]
154
273
  }
155
- const query = `{"seo_information.slug": {"$in": ${arrayString(slugs)}}}`
156
- return await queryProducts(query, locale)
274
+ const query = `{"seo_information.slug": {"$in": ${helpers?.arrayString(
275
+ slugs
276
+ )}}}`
277
+
278
+ const products = await queryProducts(query, locale)
279
+ const translations = await getTranslations(locale)
280
+ // await parseSharedStrings(translations);
281
+ await removeInvalidVariants(products, locale, translations)
282
+ return formatProductWithSchima(products)
157
283
  }
158
284
 
159
285
  const getProductsByUID = async (ids, locale) => {
160
286
  if (!Array.isArray(ids)) {
161
287
  ids = [ids]
162
288
  }
163
- const query = `{"uid": {"$in": ${arrayString(ids)}}}`
164
- return await queryProducts(query, locale)
289
+ const query = `{"uid": {"$in": ${helpers?.arrayString(ids)}}}`
290
+ let products = await queryProducts(query, locale)
291
+ const translations = await getTranslations(locale)
292
+ // await parseSharedStrings(translations);
293
+ await removeInvalidVariants(products, locale, translations)
294
+ return formatProductWithSchima(products)
165
295
  }
166
296
 
167
297
  const getTranslations = async (locale) => {
168
- const translations = await getEntriesByQuery({contentType: 'strings_product_detail_page', locale, includes: ['common_strings'] })
298
+ const translations = await getEntriesByQuery({
299
+ contentType: 'strings_product_detail_page',
300
+ locale,
301
+ includes: ['common_strings', 'subscriptions.modal']
302
+ })
303
+ await parseSharedStrings(translations)
169
304
  return translations
170
305
  }
171
306
 
307
+ const getProductTranslations = async (locale) => {
308
+ const translations = await getTranslations(locale)
309
+ const formatedTranslations = translations.map((translate) =>
310
+ formatTranslations(translate)
311
+ )
312
+ return formatedTranslations
313
+ }
314
+
315
+ const publishedProducts = async ({ locale, skip = 0, limit = 50 } = {}) => {
316
+ let products =
317
+ (await getEntriesByQuery({
318
+ locale,
319
+ start: skip,
320
+ limit,
321
+ onlyEntries: false
322
+ })) || []
323
+ const count = products.count || 0
324
+ products = products.entries
325
+ const translations = await getTranslations(locale)
326
+ await parseSharedStrings(translations)
327
+ await removeInvalidVariants(products, locale, translations)
328
+ const transformProduct = formatProductWithSchima(products)
329
+ return {
330
+ products: transformProduct,
331
+ validCount: products.length,
332
+ totalCount: count,
333
+ skip
334
+ }
335
+ }
336
+
172
337
  return {
173
338
  asObject,
174
339
  getEntriesByQuery,
175
340
  getProductsBySku,
176
341
  getProductsByGlobalId,
177
342
  getProductsBySlug,
178
- getProductsBySlugs,
179
- getProductsByQuery,
180
343
  getProductsByUID,
181
- getTranslations
344
+ getTranslations,
345
+ publishedProducts,
346
+ formatTranslations,
347
+ getProductTranslations,
348
+ getBundle
182
349
  }
183
350
  }
184
351
 
185
352
  module.exports = {
186
353
  csProduct
187
- }
354
+ }
@@ -0,0 +1,81 @@
1
+ const helpers = require('./helpers')
2
+ const entities = require('entities')
3
+
4
+ /* istanbul ignore next */
5
+ const formatProduct = (parent) => ({
6
+ // Each value is a function with a parameter 'parent' that is the current instance of a Product
7
+ // ContentStack references need to be queried separately (ie: variants)
8
+ id: parent.uid,
9
+ slug: parent?.seo_information?.slug,
10
+ variantSelectLabel: parent.variant_select_label,
11
+ secondaryTitle: (parent) => parent.secondary_title,
12
+ title: parent.name,
13
+ productImages: helpers?.getProductImages(parent),
14
+ salesLabel: parent.sales_label,
15
+ salesText: parent.sales_text,
16
+ description: entities.decodeHTML(parent.short_description || ''),
17
+ salesDisclaimer: helpers?.mapSalesDisclaimer(parent.sales_disclaimer),
18
+ features: helpers?.mapFeatures(parent),
19
+ productDetails: helpers?.mapProductDetails(parent),
20
+ benefitsMedia: {
21
+ image: helpers?.mapImage(parent.benefits_media?.image),
22
+ youTubeVideoId: parent.benefits_media?.youtube_video_id
23
+ },
24
+ benefits: helpers?.mapBenefits(parent),
25
+ benefitsAndFeatures: helpers?.mapBenefitsAndFeatures(parent),
26
+ results: helpers?.mapResults(parent),
27
+ sustainability: helpers?.mapSustainability(parent),
28
+ usage: helpers?.mapUsage(parent, parent.legacy_usage),
29
+ resources: helpers?.mapResources(parent),
30
+ faqs: parent.faqs?.map((faq) => ({
31
+ question: faq.question,
32
+ answers: faq.answer || []
33
+ })),
34
+ categories: parent.categories?.map((category) => ({
35
+ displayName: category.display_name,
36
+ id: category.category_id,
37
+ subcategories: category.subcategories?.map((subcategory) => ({
38
+ displayName: subcategory.display_name,
39
+ id: subcategory.category_id
40
+ }))
41
+ })),
42
+ ingredients: helpers?.mapIngredients(
43
+ parent.ingredients,
44
+ parent.legacy_ingredients
45
+ ),
46
+ legacyBenefits: parent.legacy_benefits,
47
+ legacyLongDescription: parent.legacy_long_description,
48
+ legacyResources: parent.legacy_resources,
49
+ legacyUsage: parent.legacy_usage,
50
+ legacyIngredients: parent.legacy_ingredients,
51
+ legacyImages: parent.legacy_images || [],
52
+ legacyYouTube: parent.legacy_youtube_video,
53
+ disclaimers: parent.disclaimers || [],
54
+ disclaimerWarnings: helpers?.mapDisclaimerWarnings(
55
+ parent.disclaimer_warnings
56
+ ),
57
+ seoInformation: helpers?.mapSeoInformation(parent),
58
+ variants: helpers?.mapVariants(parent),
59
+ refund: helpers?.mapRefund(parent),
60
+ shipping: helpers?.mapShipping(parent),
61
+ generalDisclaimer: parent.translations?.general_disclaimer_text,
62
+ thirdPartyScripts: parent.third_party_scripts
63
+ ?.filter((tps) => tps.enabled && !!tps.script)
64
+ ?.map((tps) => tps.script),
65
+ csAtts: {
66
+ version: parent._version || 0,
67
+ updatedAt: parent.updated_at || '',
68
+ updatedBy: parent.updated_by || '',
69
+ createdAt: parent.created_at || '',
70
+ createdBy: parent.created_by || '',
71
+ locale: parent.locale || ''
72
+ },
73
+ relevancyRank: parent.relevancy_rank || null,
74
+ warranty: (parent) => helpers?.mapWarranty(parent),
75
+ upSellProductIds: (parent) => helpers?.mapUpSellProductIds(parent),
76
+ crossProductIds: (parent) => helpers?.mapCrossProductIds(parent)
77
+ })
78
+
79
+ module.exports = {
80
+ formatProduct
81
+ }