pim-import 2.12.0 → 2.16.0

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,230 @@
1
+ import { log } from "../libs/logs";
2
+ import {
3
+ getClient,
4
+ getEnvironmentDefaultLocaleCode,
5
+ getEntryByID,
6
+ getTopicPage,
7
+ getEntryImageDetails,
8
+ } from "../libs/contentful-cda";
9
+ import { getIndex, AvailableIndicesKey, removeIndexObject } from "./config";
10
+ import { getLocalISOTime, secondBetweenTwoDate } from "../utils";
11
+ import type { Entry } from "contentful-management/dist/typings/entities/entry";
12
+ import { CfLocalizedEntryField, AlgoliaPaginateRecords } from "../types";
13
+ const indexKey: AvailableIndicesKey = "projects";
14
+
15
+ export type AlgoliaProjectRecord = {
16
+ objectID: string;
17
+ name?: CfLocalizedEntryField;
18
+ slugs?: {};
19
+ isBespoke?: boolean;
20
+ categories?: string[];
21
+ applications?: string[];
22
+ locations?: string[];
23
+ thumbnail?: string;
24
+ fullPageImage?: string;
25
+ locationLabel?: string;
26
+ casambiUsed?: boolean;
27
+ lastSyncDate?: string;
28
+ };
29
+
30
+ const getObject = async (
31
+ topicProject: Entry
32
+ ): Promise<AlgoliaProjectRecord> => {
33
+ const defaultEnvironmentLocaleCode = await getEnvironmentDefaultLocaleCode();
34
+ log(`Sync the ${topicProject.sys.id} topicProject...`);
35
+ let topicProjectWithFields: Entry = topicProject;
36
+
37
+ if (!topicProjectWithFields?.fields) {
38
+ log(`Get the ${topicProject.sys.id} topicProject details...`);
39
+ topicProjectWithFields = await getEntryByID(
40
+ topicProject.sys.id,
41
+ "topicProject"
42
+ );
43
+ }
44
+
45
+ if (!topicProjectWithFields) {
46
+ log(`The topicProject ${topicProject.sys.id} not found`, "WARN");
47
+
48
+ const recordFail: AlgoliaProjectRecord = {
49
+ objectID: topicProject?.sys?.id,
50
+ };
51
+
52
+ return recordFail; // return objectID to delete the record if it exists
53
+ }
54
+
55
+ log(`Get page details...`);
56
+ const page = await getTopicPage(topicProject.sys.id, "fields.slug");
57
+ const slugs = page?.fields.slug || {};
58
+
59
+ log(`Get thumbnail details...`);
60
+ const thumbnail = await getEntryImageDetails(
61
+ topicProjectWithFields,
62
+ "thumbnail"
63
+ );
64
+
65
+ log(`Get fullPageImage details...`);
66
+ const fullPageImage = await getEntryImageDetails(
67
+ topicProjectWithFields,
68
+ "fullPageImage"
69
+ );
70
+
71
+ // Single record
72
+ const record: AlgoliaProjectRecord = {
73
+ objectID: topicProjectWithFields.sys.id,
74
+ name:
75
+ topicProjectWithFields?.fields?.title?.[defaultEnvironmentLocaleCode] ||
76
+ "",
77
+ slugs,
78
+ isBespoke:
79
+ !!topicProjectWithFields?.fields?.isBespokeProject?.[
80
+ defaultEnvironmentLocaleCode
81
+ ],
82
+ categories:
83
+ topicProjectWithFields?.fields?.categories?.[
84
+ defaultEnvironmentLocaleCode
85
+ ] || [],
86
+ applications:
87
+ topicProjectWithFields?.fields?.applications?.[
88
+ defaultEnvironmentLocaleCode
89
+ ] || [],
90
+ locations:
91
+ topicProjectWithFields?.fields?.locations?.[
92
+ defaultEnvironmentLocaleCode
93
+ ] || [],
94
+ locationLabel:
95
+ topicProjectWithFields?.fields?.locationLabel?.[
96
+ defaultEnvironmentLocaleCode
97
+ ] || "",
98
+ casambiUsed:
99
+ topicProjectWithFields?.fields?.casambiUsed?.[
100
+ defaultEnvironmentLocaleCode
101
+ ] || false,
102
+ thumbnail,
103
+ fullPageImage,
104
+ lastSyncDate: getLocalISOTime(),
105
+ };
106
+
107
+ return record;
108
+ };
109
+
110
+ export const reindexProject = async (topicProjectId: string) => {
111
+ const defaultEnvironmentLocaleCode = await getEnvironmentDefaultLocaleCode();
112
+ const timeStart = new Date();
113
+
114
+ log(`reindexProject - entryId: ${topicProjectId} `);
115
+
116
+ const topicProject = await getEntryByID(topicProjectId, "topicProject");
117
+
118
+ if (!topicProject) {
119
+ log(`No topicProjectId found with id ${topicProjectId}`, "WARN");
120
+ return false;
121
+ }
122
+
123
+ const object = await getObject(topicProject);
124
+
125
+ // Save record to Algolia
126
+ const index = getIndex(indexKey);
127
+ let record = null;
128
+ const slugs: any = object?.slugs;
129
+ if (slugs?.[defaultEnvironmentLocaleCode]) {
130
+ log(`Save object`);
131
+ record = await index.saveObject(object);
132
+ } else {
133
+ log(`Delete object`);
134
+ record = await index.deleteObject(object.objectID);
135
+ }
136
+ const timeEnd = new Date();
137
+ const seconds = secondBetweenTwoDate(timeStart, timeEnd);
138
+ log(`Execution time: ${seconds} seconds`);
139
+
140
+ return { ...record, ...object };
141
+ };
142
+
143
+ /**
144
+ * Get Objects
145
+ *
146
+ * @param offset
147
+ * @param limit
148
+ * @param filterKey
149
+ * @param filterValue
150
+ * @returns
151
+ */
152
+ const getObjects = async (
153
+ offset: number,
154
+ limit: number,
155
+ filterKey?: string,
156
+ filterValue?: string
157
+ ): Promise<AlgoliaPaginateRecords> => {
158
+ const client = await getClient();
159
+
160
+ const opts: any = {
161
+ content_type: "topicProject",
162
+ limit,
163
+ skip: offset,
164
+ locale: "*",
165
+ // select: "",
166
+ };
167
+ if (filterKey && filterValue) {
168
+ opts[filterKey] = filterValue;
169
+ }
170
+ const { items, total } = await client.getEntries(opts);
171
+
172
+ const objects: AlgoliaProjectRecord[] = [];
173
+ let count: number = Number(offset);
174
+ for (const topicProduct of items) {
175
+ log(`${++count} of ${total}`, "INFO");
176
+ const timeStart = new Date();
177
+ const record = await getObject(topicProduct);
178
+ const timeEnd = new Date();
179
+ const seconds = secondBetweenTwoDate(timeStart, timeEnd);
180
+ log(`Execution time: ${seconds} seconds`);
181
+ objects.push(record);
182
+ }
183
+
184
+ return {
185
+ objects,
186
+ offset: Number(offset) + Number(limit),
187
+ limit: Number(limit),
188
+ completed: count >= total, // if is true the import is completed
189
+ };
190
+ };
191
+
192
+ export const reindexProjects = async (
193
+ offset: number = 0,
194
+ limit: number = 50,
195
+ filterKey: string = "",
196
+ filterValue: string = ""
197
+ ) => {
198
+ const defaultEnvironmentLocaleCode = await getEnvironmentDefaultLocaleCode();
199
+ const timeStart = new Date();
200
+ log(
201
+ `reindexProjects - filterKey: ${filterKey} filterValue: ${filterValue} offset: ${offset} limit: ${limit} `
202
+ );
203
+
204
+ const records = await getObjects(offset, limit, filterKey, filterValue);
205
+ const objectsToSave = records.objects.filter(
206
+ (object) => object?.slugs?.[defaultEnvironmentLocaleCode]
207
+ );
208
+ const objectIdsToDelete = records.objects
209
+ .filter((object) => !object?.slugs?.[defaultEnvironmentLocaleCode])
210
+ .map((object) => object.objectID);
211
+
212
+ // Save records to Algolia
213
+ const index = getIndex(indexKey);
214
+ log(`Save ${objectsToSave.length} objects to ${index.indexName} index`);
215
+ const savedObjectIDs = await index.saveObjects(objectsToSave);
216
+ log(
217
+ `Delete ${objectIdsToDelete.length} objects from ${index.indexName} index`
218
+ );
219
+ const deletedObjectIDs = await index.deleteObjects(objectIdsToDelete);
220
+
221
+ const timeEnd = new Date();
222
+ const seconds = secondBetweenTwoDate(timeStart, timeEnd);
223
+ log(`Execution time: ${seconds} seconds`);
224
+
225
+ return { ...records, ...savedObjectIDs, ...deletedObjectIDs };
226
+ };
227
+
228
+ export const removeProjectObject = async (objectId: string) => {
229
+ return removeIndexObject(objectId, indexKey);
230
+ };
package/src/index.ts CHANGED
@@ -71,6 +71,11 @@ export {
71
71
  reindexDownloads,
72
72
  removeDownloadObject,
73
73
  } from "./algolia/downloads";
74
+ export {
75
+ reindexProject,
76
+ reindexProjects,
77
+ removeProjectObject,
78
+ } from "./algolia/projects";
74
79
  // export { log } from "./libs/logs";
75
80
  // export { initSentry } from "./libs/sentry";
76
81
 
@@ -455,6 +455,13 @@ export const getDictionaryLocaleValue = async (
455
455
  }
456
456
  };
457
457
 
458
+ /**
459
+ * Get topic page
460
+ *
461
+ * @param topicId
462
+ * @param select Default: "sys,fields"
463
+ * @returns
464
+ */
458
465
  export const getTopicPage = async (
459
466
  topicId: string,
460
467
  select: string = "sys,fields"
@@ -20,6 +20,7 @@ import {
20
20
  TopicDetailsResponse,
21
21
  AssetPropFields,
22
22
  OtherFilters,
23
+ WrapperImageFields,
23
24
  } from "../types";
24
25
  import {
25
26
  sleep,
@@ -1233,3 +1234,46 @@ export const getTopicPage = async (
1233
1234
 
1234
1235
  return items?.[0];
1235
1236
  };
1237
+
1238
+ /**
1239
+ * Create wrapperImgix if not exists or get it if already exists
1240
+ *
1241
+ * @param id The wrapperImgix ID
1242
+ * @param data See WrapperImageFields
1243
+ * @returns
1244
+ */
1245
+ export const createWrapperImgix = async (
1246
+ id: string,
1247
+ data: WrapperImageFields
1248
+ ) => {
1249
+ let wrapperImgix = await getEntryByID(id, "wrapperImgix");
1250
+ if (wrapperImgix) {
1251
+ log(`wrapperImgix with id ${id} already exists`);
1252
+ return wrapperImgix;
1253
+ }
1254
+
1255
+ log(`wrapperImgix with id ${id} not found, create it...`);
1256
+
1257
+ const defaultEnvCode = await getEnvironmentDefaultLocaleCode();
1258
+ const entryData: any = {
1259
+ fields: {},
1260
+ };
1261
+ for (const [key, value] of Object.entries(data)) {
1262
+ const fieldValue: any = value;
1263
+ if (!entryData.fields?.[key]) {
1264
+ entryData.fields[key] = {};
1265
+ }
1266
+
1267
+ if (fieldValue?.[defaultEnvCode] || fieldValue?.[defaultEnvCode] === null) {
1268
+ fieldValue[defaultEnvCode] =
1269
+ fieldValue[defaultEnvCode] === null ? "" : fieldValue[defaultEnvCode];
1270
+ entryData.fields[key] = fieldValue;
1271
+ } else {
1272
+ entryData.fields[key][defaultEnvCode] = fieldValue;
1273
+ }
1274
+ }
1275
+
1276
+ wrapperImgix = await createEntryWithId("wrapperImgix", id, entryData, true);
1277
+
1278
+ return wrapperImgix;
1279
+ };
@@ -0,0 +1,106 @@
1
+ import ImgixAPI from "imgix-management-js";
2
+ import { getPimDomain } from "../pim/endpoints";
3
+ import { log } from "./logs";
4
+ import { ImgixAttributes, ImgixData } from "../types";
5
+
6
+ let imgix: ImgixAPI;
7
+ export const getImgix = () => {
8
+ if (imgix) {
9
+ return imgix;
10
+ }
11
+
12
+ imgix = new ImgixAPI({
13
+ apiKey: process.env.FPI_IMGIX_API_KEY || "",
14
+ });
15
+
16
+ return imgix;
17
+ };
18
+
19
+ export const getSources = async () => {
20
+ const imgix = getImgix();
21
+ return await imgix.request("sources");
22
+ };
23
+
24
+ /**
25
+ * Get origin path by pim url
26
+ *
27
+ * @axample
28
+ * from: https://dam.flos.net/damflos/products//ARCHITECTURAL/ARCHITECTURAL_SYSTEMS_LED-CURTAIN/03.3901.30C/FLOS-LED-CURTAIN-WHITE-1950X1950.JPG
29
+ * to: damflos/products//ARCHITECTURAL/ARCHITECTURAL_SYSTEMS_LED-CURTAIN/03.3901.30C/FLOS-LED-CURTAIN-WHITE-1950X1950.JPG
30
+ *
31
+ * @param pimImgUrl
32
+ * @returns
33
+ */
34
+ export const getOriginPathByPimUrl = (pimImgUrl: string) => {
35
+ const pimDomain = getPimDomain();
36
+ return pimImgUrl.substring(pimImgUrl.indexOf(pimDomain) + pimDomain.length);
37
+ };
38
+
39
+ export const getImageAttributesByOriginPath = async (
40
+ originPath: string,
41
+ sourceId: string
42
+ ) => {
43
+ const url = `assets/${sourceId}${originPath}`;
44
+ log(`getImageAttributesByOriginPath: ${url}`);
45
+ const imgix = getImgix();
46
+ return await imgix
47
+ .request(url)
48
+ .then((response) => {
49
+ const imgDetails: any = response;
50
+ return imgDetails?.data?.attributes as ImgixAttributes;
51
+ })
52
+ .catch((err) => {
53
+ console.log(err.response.errors);
54
+ log(
55
+ `${err.response.errors[0].detail} sourceId: ${sourceId} originPath: ${originPath}`,
56
+ "WARN"
57
+ );
58
+ return {};
59
+ });
60
+ };
61
+
62
+ export const getImageAttributesByPimUrl = async (pimUrl: string) => {
63
+ const originPath = getOriginPathByPimUrl(pimUrl);
64
+ const sourceId = process.env.FPI_IMGIX_PIM_SOURCE_ID || "";
65
+
66
+ return (await getImageAttributesByOriginPath(
67
+ originPath,
68
+ sourceId
69
+ )) as ImgixAttributes;
70
+ };
71
+
72
+ /**
73
+ * Get imgix URL by origin path
74
+ *
75
+ * @axample
76
+ * from: /damflos/products//ARCHITECTURAL/ARCHITECTURAL_SYSTEMS_LED-CURTAIN/03.3901.30C/FLOS-LED-CURTAIN-WHITE-1950X1950.JPG
77
+ * to: https://flos-pim.imgix.net/damflos/products//ARCHITECTURAL/ARCHITECTURAL_SYSTEMS_LED-CURTAIN/03.3901.30C/FLOS-LED-CURTAIN-WHITE-1950X1950.JPG
78
+ *
79
+ * @param originPath
80
+ * @returns
81
+ */
82
+ const getImgixUrlByOriginPath = (originPath: string) => {
83
+ return `https://${process.env.FPI_IMGIX_PIM_IMAGE_DOMAIN}${originPath}`;
84
+ };
85
+
86
+ export const getWrapperImgixAttributesByPimUrl = async (pimUrl: string) => {
87
+ const allImgAttributes = await getImageAttributesByPimUrl(pimUrl);
88
+ let imgixData: ImgixData | null = null;
89
+
90
+ if (Object.entries(allImgAttributes).length) {
91
+ imgixData = {
92
+ url: getImgixUrlByOriginPath(allImgAttributes.origin_path),
93
+ details: {
94
+ size: allImgAttributes.file_size,
95
+ image: {
96
+ width: allImgAttributes.media_width,
97
+ height: allImgAttributes.media_height,
98
+ },
99
+ },
100
+ fileName: allImgAttributes?.name || "",
101
+ contentType: allImgAttributes.content_type,
102
+ };
103
+ }
104
+
105
+ return imgixData;
106
+ };
@@ -15,6 +15,19 @@ import { checkConfig, config } from "./config";
15
15
 
16
16
  type AvailableOtherCatalogDataEndpoint = "models" | "submodels" | "subfamilies";
17
17
 
18
+ /**
19
+ * Get pim domain by pim base url
20
+ *
21
+ * @returns
22
+ */
23
+ export const getPimDomain = () => {
24
+ const pimBaseUrl = process.env.FPI_PIM_BASE_URL || "";
25
+ return pimBaseUrl.substring(
26
+ pimBaseUrl.indexOf("://") + 3,
27
+ pimBaseUrl.indexOf("/.rest")
28
+ );
29
+ };
30
+
18
31
  /**
19
32
  * Get the catalog taxonomy hierarchy
20
33
  *
@@ -240,36 +240,30 @@ export const importDictionaryFields = async (
240
240
 
241
241
  // Get dictionary data
242
242
  log("Get dictionary data");
243
- const pimDictionaryData: DictionaryRecord[] = await getDictionary();
243
+ const pimDictionaryData: Partial<Record<string, DictionaryRecord[]>> =
244
+ await getDictionary();
244
245
 
245
246
  if (pimDictionaryData) {
246
247
  // Filter required fields
247
248
  log("Filter required fields");
248
- const dictionaryData = Object.entries(pimDictionaryData);
249
249
  let count: number = 0;
250
250
  const data: ObjectPartial = {};
251
- for (const [key, value] of dictionaryData) {
251
+ for (const productFieldRequiredData of productFieldsRequiredData) {
252
252
  if (offset <= count || limit === -1) {
253
- if (value) {
254
- const currentRequiredField: ProductFieldsRequiredData | undefined =
255
- productFieldsRequiredData.find((e) => e.dictionary === key);
256
- if (typeof currentRequiredField !== "undefined") {
257
- const productFieldKey = currentRequiredField.key;
258
- if (productFieldKey) {
259
- const dictionaryRecord: DictionaryRecord = value;
260
- if (currentRequiredField.parent) {
261
- if (!data[currentRequiredField.parent]) {
262
- data[currentRequiredField.parent] = {};
263
- }
264
- data[currentRequiredField.parent][productFieldKey] =
265
- dictionaryRecord;
266
- } else {
267
- data[productFieldKey] = dictionaryRecord;
268
- }
253
+ if (pimDictionaryData?.[productFieldRequiredData.dictionary]) {
254
+ const dictionaryRecord =
255
+ pimDictionaryData?.[productFieldRequiredData.dictionary];
256
+ if (productFieldRequiredData.parent) {
257
+ if (!data[productFieldRequiredData.parent]) {
258
+ data[productFieldRequiredData.parent] = {};
269
259
  }
260
+ data[productFieldRequiredData.parent][
261
+ productFieldRequiredData.key
262
+ ] = dictionaryRecord;
263
+ } else {
264
+ data[productFieldRequiredData.key] = dictionaryRecord;
270
265
  }
271
266
  }
272
-
273
267
  if (limit !== -1 && count + 1 - offset >= limit) {
274
268
  break;
275
269
  }
@@ -277,20 +271,27 @@ export const importDictionaryFields = async (
277
271
  count++;
278
272
  }
279
273
 
280
- log("Create certifications");
281
- let certifications: DictionaryRecord[] = [];
282
- const pimCertifications: { string: DictionaryRecord[] } =
283
- data.certification;
284
- for (const [key, values] of Object.entries(pimCertifications)) {
285
- log(`Add ${key} values to certifications`);
286
- certifications = certifications.concat(values);
274
+ const completed = count >= productFieldsRequiredData.length;
275
+
276
+ if (completed) {
277
+ log("Create certifications");
278
+ let certifications: DictionaryRecord[] = [];
279
+ const pimCertifications: { string: DictionaryRecord[] } =
280
+ data.certification;
281
+ for (const [key, values] of Object.entries(pimCertifications)) {
282
+ log(`Add ${key} values to certifications`);
283
+ certifications = certifications.concat(values);
284
+ }
285
+ log(`Remove unwanted values to certifications`);
286
+ certifications = certifications.filter((value) => {
287
+ return sanitizeValue(value?.code);
288
+ });
289
+ log(`Add certifications to data`);
290
+ data.certifications = certifications;
291
+
292
+ log("Create catalogs");
293
+ data.catalogs = pimDictionaryData.Catalog;
287
294
  }
288
- log(`Remove unwanted values to certifications`);
289
- certifications = certifications.filter((value) => {
290
- return sanitizeValue(value?.code);
291
- });
292
- log(`Add certifications to data`);
293
- data.certifications = certifications;
294
295
 
295
296
  if (Object.entries(data).length) {
296
297
  // Create/Update Contentful entry
@@ -303,7 +304,7 @@ export const importDictionaryFields = async (
303
304
  return {
304
305
  offset: Number(offset) + Number(limit),
305
306
  limit: Number(limit),
306
- completed: count >= dictionaryData.length, // if is true the import is completed
307
+ completed, // if is true the import is completed
307
308
  };
308
309
  } else {
309
310
  log("no data found in the dictionary", "ERROR");
@@ -13,6 +13,7 @@ import {
13
13
  CfSys,
14
14
  ObjectPartial,
15
15
  ContentfulAssetFile,
16
+ WrapperImageFields,
16
17
  } from "../../types";
17
18
  import { log } from "../../libs/logs";
18
19
  import {
@@ -26,6 +27,7 @@ import {
26
27
  getEnvironment,
27
28
  addFieldValue,
28
29
  addToRelationFields,
30
+ createWrapperImgix,
29
31
  } from "../../libs/contentful";
30
32
  import type {
31
33
  Entry,
@@ -52,6 +54,7 @@ import { getAudit, getProductDetails } from "../endpoints";
52
54
  import { getCategoryTopicCode } from "./catalogs";
53
55
  import productFieldsRequiredData from "../data/productFields.json";
54
56
  import mime from "mime-types";
57
+ import { getWrapperImgixAttributesByPimUrl } from "../../libs/imgix";
55
58
 
56
59
  export type AvailableProductStatus =
57
60
  | "To be review"
@@ -873,6 +876,48 @@ const getProductData = async (
873
876
  "Asset"
874
877
  );
875
878
  }
879
+
880
+ // THUMB WRAPPER IMGIX
881
+ const wrapperImgixID = pimAssetThumb.md5;
882
+ let wrapperImgix = await getEntryByID(wrapperImgixID, "wrapperImgix");
883
+ if (wrapperImgix) {
884
+ log(`wrapperImgix with id ${wrapperImgixID} already exists`);
885
+ } else {
886
+ log(
887
+ `Add thumbnail imgix wrapper with id ${wrapperImgixID} to Contentful`
888
+ );
889
+ const altText: any = getPimTranslations(pimAssetThumb, "title");
890
+ if (altText[defaultEnvironmentLocaleCode] === null) {
891
+ altText[defaultEnvironmentLocaleCode] = "Thumbnail";
892
+ }
893
+ const wrapperImgixAttributes = await getWrapperImgixAttributesByPimUrl(
894
+ pimAssetThumb.url
895
+ );
896
+ if (wrapperImgixAttributes) {
897
+ const wrapperImgixFields: WrapperImageFields = {
898
+ internalName: `${pimAssetThumb?.assetType?.code}_${pimAssetThumb.md5}`,
899
+ imgixData: wrapperImgixAttributes,
900
+ altText,
901
+ };
902
+ wrapperImgix = await createWrapperImgix(
903
+ wrapperImgixID,
904
+ wrapperImgixFields
905
+ );
906
+ } else {
907
+ log(
908
+ `Unable to create wrapperimagix because the image was not found on imgix. Image URL: ${pimAssetThumb.url}`,
909
+ "WARN"
910
+ );
911
+ }
912
+ }
913
+
914
+ if (wrapperImgix?.sys?.id) {
915
+ data.fields = await addToRelationFields(
916
+ data,
917
+ "thumbnailImgix",
918
+ wrapperImgix.sys.id
919
+ );
920
+ }
876
921
  } else {
877
922
  log(`No thumbnail found`, "WARN");
878
923
  }
package/src/types.ts CHANGED
@@ -194,3 +194,61 @@ export interface OtherFilters {
194
194
  key: string;
195
195
  value: string;
196
196
  }
197
+
198
+ export interface ImgixData {
199
+ url: string;
200
+ details: ImgixDataDetails;
201
+ fileName: string;
202
+ contentType: string;
203
+ }
204
+ export interface ImgixDataDetails {
205
+ size: number;
206
+ image: ImgixDataImageDetails;
207
+ }
208
+ interface ImgixDataImageDetails {
209
+ width: number;
210
+ height: number;
211
+ }
212
+
213
+ export type WrapperImageFields = {
214
+ internalName: string;
215
+ imgixData: ImgixData;
216
+ altText: CfLocalizedEntryField;
217
+ seoTitleTag?: "h1" | "h2" | "h3" | "h4";
218
+ caption?: CfLocalizedEntryField;
219
+ blurhash?: string;
220
+ };
221
+
222
+ export interface ImgixAttributes {
223
+ analyzed_content_warnings: boolean;
224
+ analyzed_faces: boolean;
225
+ analyzed_tags: boolean;
226
+ categories?: null[] | null;
227
+ color_model: string;
228
+ color_profile: string;
229
+ colors: string;
230
+ content_type: string;
231
+ custom_fields?: null;
232
+ date_created: number;
233
+ date_modified: number;
234
+ description?: null;
235
+ dpi_height: number;
236
+ dpi_width: number;
237
+ face_count?: null;
238
+ file_size: number;
239
+ has_frames: boolean;
240
+ media_height: number;
241
+ media_kind: string;
242
+ media_width: number;
243
+ name?: null;
244
+ origin_path: string;
245
+ source_id: string;
246
+ tags: Record<string, any>;
247
+ uploaded_by: string;
248
+ uploaded_by_api: boolean;
249
+ warning_adult: number;
250
+ warning_medical: number;
251
+ warning_racy: number;
252
+ warning_spoof: number;
253
+ warning_violence: number;
254
+ }