pim-import 2.4.2 → 2.5.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.
@@ -0,0 +1,445 @@
1
+ import type { Entry } from "contentful-management/dist/typings/entities/entry";
2
+ import { Locale } from "contentful-management/types";
3
+ import {
4
+ CfSys,
5
+ TopicDetailsResponse,
6
+ AssetPropFieldsWithoutLocaleKey,
7
+ } from "../types";
8
+ import { getPimTranslations, keysToLowerCase } from "../utils";
9
+ import { log } from "./logs";
10
+ const contentful = require("contentful");
11
+
12
+ let client: any;
13
+ let defaultEnvironmentLocale: Locale;
14
+ let defaultEnvironmentLocaleCode: string;
15
+
16
+ const config = {
17
+ spaceId: process.env.FPI_CTF_SPACE_ID,
18
+ previewAccessToken: process.env.FPI_CTF_PREVIEW_ACCESS_TOKEN,
19
+ accessToken: process.env.FPI_CTF_CDA_ACCESS_TOKEN,
20
+ environment: process.env.FPI_CTF_ENVIRONMENT,
21
+ removeUnresolved: process.env.FPI_CTF_REMOVE_UNRESOLVED === "1",
22
+ resolveLinks: process.env.FPI_CTF_RESOLVE_LINKS === "1",
23
+ previewEnabled: process.env.FPI_CTF_PREVIEW_ENABLED === "1",
24
+ host:
25
+ process.env.FPI_CTF_PREVIEW_ENABLED === "1"
26
+ ? "preview.contentful.com"
27
+ : "cdn.contentful.com",
28
+ };
29
+
30
+ const initClient = async () => {
31
+ client = await contentful.createClient({
32
+ space: config.spaceId,
33
+ accessToken: config.previewEnabled
34
+ ? config.previewAccessToken || ""
35
+ : config.accessToken,
36
+ environment: config.environment || "master",
37
+ removeUnresolved: config.removeUnresolved,
38
+ resolveLinks: config.resolveLinks,
39
+ host: config.previewEnabled
40
+ ? "preview.contentful.com"
41
+ : "cdn.contentful.com",
42
+ });
43
+
44
+ console.log("host:", config.host);
45
+
46
+ return client;
47
+ };
48
+
49
+ export const getClient = async () => {
50
+ if (!client) {
51
+ return await initClient();
52
+ } else {
53
+ return client;
54
+ }
55
+ };
56
+
57
+ /**
58
+ * Get environment default locale
59
+ *
60
+ * @returns
61
+ */
62
+ export const getEnvironmentDefaultLocale = async (): Promise<Locale> => {
63
+ if (defaultEnvironmentLocale) {
64
+ return new Promise((resolve) => resolve(defaultEnvironmentLocale));
65
+ }
66
+
67
+ const client = await getClient();
68
+ const environmentLocales = await client.getLocales();
69
+ if (!environmentLocales) {
70
+ throw new Error("Environment locales not found");
71
+ }
72
+
73
+ const findDefaultEnvironmentLocale: Locale | undefined =
74
+ environmentLocales.items.find((locale: Locale) => locale.default);
75
+
76
+ if (findDefaultEnvironmentLocale) {
77
+ defaultEnvironmentLocale = findDefaultEnvironmentLocale;
78
+ } else {
79
+ throw new Error("Default environment locales not found");
80
+ }
81
+
82
+ return new Promise((resolve) => resolve(defaultEnvironmentLocale));
83
+ };
84
+
85
+ /**
86
+ * Get environment default locale code
87
+ *
88
+ * @returns
89
+ */
90
+ export const getEnvironmentDefaultLocaleCode = async (): Promise<string> => {
91
+ if (defaultEnvironmentLocaleCode) {
92
+ return new Promise((resolve) => resolve(defaultEnvironmentLocaleCode));
93
+ }
94
+
95
+ const defEnvironmentLocale = await getEnvironmentDefaultLocale();
96
+ defaultEnvironmentLocaleCode = defEnvironmentLocale.code;
97
+ return new Promise((resolve) => resolve(defaultEnvironmentLocaleCode));
98
+ };
99
+
100
+ /**
101
+ * Get entry by id
102
+ *
103
+ * @param entryID
104
+ * @param contentTypeId
105
+ * @returns
106
+ */
107
+ export const getEntryByID = async (
108
+ entryID: string,
109
+ contentTypeId: string,
110
+ select?: string
111
+ ): Promise<Entry> => {
112
+ const client = await getClient();
113
+
114
+ const opts = {
115
+ content_type: contentTypeId,
116
+ "sys.id": entryID,
117
+ limit: 1,
118
+ locale: "*",
119
+ select,
120
+ include: 1,
121
+ };
122
+
123
+ const entries = await client.getEntries(opts);
124
+
125
+ return entries.items[0];
126
+ };
127
+
128
+ /**
129
+ * Get all entries
130
+ *
131
+ * @param {string} contentType
132
+ * @param {string} select
133
+ * @param {string} filterKey Ex: `fields.catalog[in]`
134
+ * @param {string} filterValue Ex: `ARCHITECTURAL`
135
+ * @param {number} limit Default: 50
136
+ * @returns
137
+ */
138
+ export const getAllEntries = async (
139
+ contentType: string,
140
+ select?: string,
141
+ filterKey?: string,
142
+ filterValue?: string,
143
+ limit: number = 50
144
+ ): Promise<Entry[]> => {
145
+ // log(
146
+ // `getAllEntries contentType: ${contentType} select: ${select} filterKey: ${filterKey} filterValue: ${filterValue}`,
147
+ // "INFO"
148
+ // );
149
+
150
+ const client = await getClient();
151
+ let allItems: Entry[] = [];
152
+ let othersPagesToFetch = false;
153
+ let skip = 0;
154
+ // let count = 0;
155
+ do {
156
+ const opts: any = {
157
+ content_type: contentType,
158
+ limit,
159
+ skip,
160
+ locale: "*",
161
+ select,
162
+ include: 1,
163
+ };
164
+ if (filterKey) {
165
+ opts[filterKey] = filterValue;
166
+ }
167
+
168
+ const { items, total } = await client.getEntries(opts);
169
+
170
+ allItems = [...allItems, ...(items || [])];
171
+
172
+ if (total && allItems.length < total) {
173
+ othersPagesToFetch = true;
174
+ skip += limit;
175
+
176
+ // if (++count % 7 === 0) {
177
+ // await sleep(200);
178
+ // }
179
+ } else {
180
+ othersPagesToFetch = false;
181
+ }
182
+ } while (othersPagesToFetch);
183
+
184
+ return allItems;
185
+ };
186
+
187
+ /**
188
+ * Get asset details
189
+ *
190
+ * @param assetId
191
+ * @returns
192
+ *
193
+ * // TODO: modificare quanto verrà integrato imagix
194
+ */
195
+ export const getAssetDetails = async (
196
+ assetId: string
197
+ ): Promise<AssetPropFieldsWithoutLocaleKey> => {
198
+ const client = await getClient();
199
+ const thumbnailEntry = await client.getAsset(assetId);
200
+ return thumbnailEntry?.fields
201
+ ? keysToLowerCase(thumbnailEntry.fields, true)
202
+ : {};
203
+ };
204
+
205
+ /**
206
+ * Get entry image details
207
+ *
208
+ * @example
209
+ * await getEntryImageDetails(topicSubModule, 'thumbnail');
210
+ *
211
+ * @param entry The Contentful entry
212
+ * @param fieldKey The image field key of the entry
213
+ * @returns
214
+ *
215
+ * // TODO: modificare quanto verrà integrato imagix
216
+ */
217
+ export const getEntryImageDetails = async (entry: Entry, fieldKey: string) => {
218
+ const assetId =
219
+ entry?.fields?.[fieldKey]?.[defaultEnvironmentLocaleCode]?.sys?.id;
220
+ if (assetId) {
221
+ const assetEntry = await getAssetDetails(assetId);
222
+ return assetEntry?.file?.url || "";
223
+ }
224
+
225
+ return "";
226
+ };
227
+
228
+ /**
229
+ * Get topic details
230
+ *
231
+ * @example const catalogs = await getTopicDetails(topicFamily,"catalogs", "topicCatalog");
232
+ * @returns {
233
+ * names: {
234
+ * de: 'Architectural Lighting',
235
+ * 'en-US': 'Architectural Lighting',
236
+ * ...
237
+ * },
238
+ * code: { 'en-GB': 'ARCHITECTURAL' },
239
+ * slugs: {
240
+ * de: 'architectural lighting',
241
+ * 'en-US': 'architectural lighting'
242
+ * ...
243
+ * }
244
+ * }
245
+ *
246
+ * @param topicEntry
247
+ * @param fieldKey
248
+ * @param contentType
249
+ * @returns
250
+ */
251
+ export const getTopicDetails = async (
252
+ topicEntry: Entry,
253
+ fieldKey: string,
254
+ contentType: string,
255
+ lowerCaseKeys: boolean = false,
256
+ showRelatedEntities: boolean = false
257
+ ): Promise<TopicDetailsResponse[]> => {
258
+ const defEnvLocaleCode = await getEnvironmentDefaultLocaleCode();
259
+ const isHasToManyField = Array.isArray(
260
+ topicEntry?.fields?.[fieldKey]?.[defEnvLocaleCode]
261
+ );
262
+
263
+ const items: TopicDetailsResponse[] = [];
264
+ let entryIds = [];
265
+ if (isHasToManyField) {
266
+ entryIds = topicEntry?.fields?.[fieldKey]?.[defEnvLocaleCode].map(
267
+ (item: CfSys) => item.sys.id
268
+ );
269
+ } else {
270
+ entryIds = [topicEntry?.fields?.[fieldKey]?.[defEnvLocaleCode]?.sys?.id];
271
+ }
272
+
273
+ if (entryIds) {
274
+ let select = "sys,fields.name,fields.code";
275
+ if (contentType === "topicSubFamily" && showRelatedEntities) {
276
+ select += ",fields.catalog,fields.category,fields.parentFamily";
277
+ }
278
+
279
+ const entries = await getAllEntries(
280
+ contentType,
281
+ select,
282
+ "sys.id[in]",
283
+ entryIds.join(",")
284
+ );
285
+
286
+ if (entries) {
287
+ // let count = 0;
288
+ for (const entry of entries) {
289
+ const entryPageId = `${entry.sys.id}_PAGE`;
290
+ const entryPage = await getEntryByID(
291
+ entryPageId,
292
+ "page",
293
+ "fields.slug"
294
+ );
295
+
296
+ const data: TopicDetailsResponse = {
297
+ names:
298
+ entry?.fields?.name && lowerCaseKeys
299
+ ? keysToLowerCase(entry.fields.name)
300
+ : {},
301
+ code: entry?.fields?.code?.[defEnvLocaleCode],
302
+ slugs:
303
+ entryPage?.fields?.slug && lowerCaseKeys
304
+ ? keysToLowerCase(entryPage.fields.slug)
305
+ : {},
306
+ };
307
+
308
+ if (contentType === "topicSubFamily" && showRelatedEntities) {
309
+ data.details = await getSubFamilySlugDetails(entry);
310
+ }
311
+
312
+ // if (++count > 0 && count % 7 === 0) {
313
+ // await sleep(2000, true);
314
+ // }
315
+
316
+ items.push(data);
317
+ }
318
+ }
319
+ } else {
320
+ log(
321
+ `No ${fieldKey} found in the ${contentType} ${topicEntry.sys.id}`,
322
+ "WARN"
323
+ );
324
+ }
325
+
326
+ return items;
327
+ };
328
+
329
+ export const getSubFamilySlugDetails = async (topicSubFamily: Entry) => {
330
+ const defaultEnvironmentLocaleCode = await getEnvironmentDefaultLocaleCode();
331
+ const catalogEntryId =
332
+ topicSubFamily?.fields?.catalog?.[defaultEnvironmentLocaleCode]?.sys?.id;
333
+ if (!catalogEntryId) {
334
+ log(
335
+ `catalogEntryId not found to ${topicSubFamily.sys.id} subFamilyEntry`,
336
+ "WARN"
337
+ );
338
+ }
339
+ const parentFamilyEntryId =
340
+ topicSubFamily?.fields?.parentFamily?.[defaultEnvironmentLocaleCode]?.sys
341
+ ?.id;
342
+ if (!parentFamilyEntryId) {
343
+ log(
344
+ `parentFamilyEntryId not found to ${topicSubFamily.sys.id} subFamilyEntry`,
345
+ "WARN"
346
+ );
347
+ }
348
+ const categoryEntryId =
349
+ topicSubFamily?.fields?.category?.[defaultEnvironmentLocaleCode]?.sys?.id;
350
+ if (!categoryEntryId) {
351
+ log(
352
+ `categoryEntryId not found to ${topicSubFamily.sys.id} subFamilyEntry`,
353
+ "WARN"
354
+ );
355
+ }
356
+ const subFamilyCatalogPage: any = catalogEntryId
357
+ ? await getEntryByID(`${catalogEntryId}_PAGE`, "page", "fields.slug")
358
+ : {};
359
+ const subFamilyFamilyPage: any = parentFamilyEntryId
360
+ ? await getEntryByID(`${parentFamilyEntryId}_PAGE`, "page", "fields.slug")
361
+ : {};
362
+ const subFamilyCategoryPage: any = categoryEntryId
363
+ ? await getEntryByID(`${categoryEntryId}_PAGE`, "page", "fields.slug")
364
+ : {};
365
+
366
+ return {
367
+ catalogPageSlugs: keysToLowerCase(subFamilyCatalogPage?.fields?.slug) || {},
368
+ familyPageSlugs: keysToLowerCase(subFamilyFamilyPage?.fields?.slug) || {},
369
+ categoryPageSlugs:
370
+ keysToLowerCase(subFamilyCategoryPage?.fields?.slug) || {},
371
+ };
372
+ };
373
+
374
+ export const getDictionaryJson = async () => {
375
+ const defEnvLocaleCode = await getEnvironmentDefaultLocaleCode();
376
+ const entry = await getEntryByID("globalJsonProductFields", "globalJsonFile");
377
+ return entry?.fields?.json?.[defEnvLocaleCode];
378
+ };
379
+
380
+ export const getDictionaryValue = async (
381
+ fieldKey: string,
382
+ fieldValue: string,
383
+ fieldParent: string = "",
384
+ dictionaryJsonFile: any = {}
385
+ ) => {
386
+ const dictionaryJson = Object.entries(dictionaryJsonFile)
387
+ ? dictionaryJsonFile
388
+ : await getDictionaryJson();
389
+ let field = null;
390
+
391
+ if (fieldParent && dictionaryJson?.[fieldParent]?.[fieldKey]) {
392
+ field = dictionaryJson[fieldParent][fieldKey];
393
+ } else if (dictionaryJson?.[fieldKey]) {
394
+ field = dictionaryJson[fieldKey];
395
+ }
396
+
397
+ if (field) {
398
+ if (Array.isArray(field)) {
399
+ return getPimTranslations(
400
+ field.find((item: any) => item.code === fieldValue)
401
+ );
402
+ } else if (typeof field === "object" && field?.code) {
403
+ return getPimTranslations(field);
404
+ } else {
405
+ return field;
406
+ }
407
+ } else {
408
+ log(
409
+ `No field found with fieldKey: ${fieldKey} fieldValue: ${fieldValue} fieldParent: ${fieldParent}`
410
+ );
411
+ }
412
+ };
413
+
414
+ /**
415
+ *
416
+ * @example
417
+ * const lampCategoriesLocalizedValue = await getDictionaryLocaleValue(locale, "lampCategories", productFileds.electrical.lampCategories.code, "electrical");
418
+ *
419
+ * @param locale
420
+ * @param fieldKey
421
+ * @param fieldValue
422
+ * @param fieldParent
423
+ * @param dictionaryJsonFile
424
+ * @returns
425
+ */
426
+ export const getDictionaryLocaleValue = async (
427
+ locale: string,
428
+ fieldKey: string,
429
+ fieldValue: string,
430
+ fieldParent: string = "",
431
+ dictionaryJsonFile: any = {}
432
+ ) => {
433
+ const defEnvLocaleCode = await getEnvironmentDefaultLocaleCode();
434
+ const values = await getDictionaryValue(
435
+ fieldKey,
436
+ fieldValue,
437
+ fieldParent,
438
+ dictionaryJsonFile
439
+ );
440
+ if (typeof values === "object") {
441
+ return values?.[locale] || values?.[defEnvLocaleCode];
442
+ } else {
443
+ return values;
444
+ }
445
+ };
@@ -77,7 +77,7 @@ const createOrUpdatePage = async (modelEntry: Entry): Promise<Entry> => {
77
77
  `${
78
78
  modelEntry.fields.name[locale] ||
79
79
  modelEntry.fields.name[defaultEnvironmentLocaleCode]
80
- }`.toLowerCase()
80
+ }-${modelEntry.fields.code[defaultEnvironmentLocaleCode]}`.toLowerCase()
81
81
  );
82
82
  }
83
83
 
@@ -177,9 +177,34 @@ export const createOrUpdatePage = async (
177
177
  subFamilyEntry.fields.name[defaultEnvironmentLocaleCode];
178
178
  }
179
179
 
180
+ // Category page
181
+ let categoryPage: Entry = {} as Entry;
182
+ const categoryEntryId =
183
+ subFamilyEntry?.fields?.category?.[defaultEnvironmentLocaleCode]?.sys?.id;
184
+ if (categoryEntryId) {
185
+ log(
186
+ `Add ${categoryEntryId} category page slug to ${subFamilyEntry.sys.id} subfamily page slug`
187
+ );
188
+ categoryPage = await getEntryByID(`${categoryEntryId}_PAGE`, "page");
189
+ if (!categoryPage) {
190
+ log(
191
+ `No category page found with page id: ${categoryEntryId}_PAGE`,
192
+ "WARN"
193
+ );
194
+ }
195
+ } else {
196
+ log(`No category found with id: ${categoryEntryId}`, "WARN");
197
+ }
198
+
180
199
  for (const locale of cfLocales) {
200
+ let slugPrefix = "";
201
+ if (categoryPage?.fields?.slug) {
202
+ slugPrefix +=
203
+ (categoryPage.fields.slug[locale] ||
204
+ categoryPage.fields.slug[defaultEnvironmentLocaleCode]) + "-";
205
+ }
181
206
  slugs[locale] = stringToSlug(
182
- `${
207
+ `${slugPrefix}${
183
208
  subFamilyEntry.fields.name[locale] ||
184
209
  subFamilyEntry.fields.name[defaultEnvironmentLocaleCode]
185
210
  }`.toLowerCase()
package/src/types.ts CHANGED
@@ -147,6 +147,29 @@ export type AssetPropFields = {
147
147
  };
148
148
  };
149
149
 
150
+ export type AssetPropFieldsWithoutLocaleKey = {
151
+ /** Title for this asset */
152
+ title: {
153
+ [key: string]: string;
154
+ };
155
+ /** Description for this asset */
156
+ description?: {
157
+ [key: string]: string;
158
+ };
159
+ /** File object for this asset */
160
+ file: {
161
+ fileName: string;
162
+ contentType: string;
163
+ /** Url where the file is available to be downloaded from, into the Contentful asset system. After the asset is processed this field is gone. */
164
+ upload?: string;
165
+ /** Url where the file is available at the Contentful media asset system. This field won't be available until the asset is processed. */
166
+ url?: string;
167
+ /** Details for the file, depending on file type (example: image size in bytes, etc) */
168
+ details?: Record<string, any>;
169
+ uploadFrom?: Record<string, any>;
170
+ };
171
+ };
172
+
150
173
  export type TopicProductFieldsResponse = {
151
174
  names: CfLocalizedEntryField;
152
175
  code: string;