@nkhang1902/strapi-plugin-export-import-clsx 1.0.4 → 1.1.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.
@@ -1,79 +1,59 @@
1
- const XLSX = require('xlsx');
2
- const fs = require('fs');
1
+ const XLSX = require("xlsx");
2
+ const fs = require("fs");
3
3
 
4
4
  function toCamel(str) {
5
5
  return str.replace(/_([a-z])/g, (_, c) => c.toUpperCase());
6
6
  }
7
7
 
8
- const SYSTEM_KEYS = [
9
- 'documentId',
10
- 'locale',
11
- 'createdAt',
12
- 'updatedAt',
13
- 'publishedAt',
14
- 'createdBy',
15
- 'updatedBy',
16
- 'localizations',
17
- 'status'
18
- ];
19
-
20
8
  const SHORTCUT_FIELDS = [
21
- 'email','businessEmail','name','title','tickerCode',
22
- ]
23
- async function importData(file) {
24
- let result;
25
- try {
26
- let importData;
27
- // Check file extension
28
- const fileName = file.name || file.originalFilename || 'unknown.json';
29
- const fileExtension = fileName.split('.').pop().toLowerCase();
30
- const filePath = file.path || file.filepath;
31
- if (!filePath) {
32
- throw new Error('File path not found');
33
- }
9
+ "email",
10
+ "businessEmail",
11
+ "name",
12
+ "title",
13
+ "tickerCode",
14
+ ];
15
+ module.exports = ({ strapi }) => ({
16
+ async importData(file, targetContentType = null) {
17
+ let result;
18
+ try {
19
+ let importData;
20
+ // Check file extension
21
+ const fileName = file.name || file.originalFilename || "unknown.json";
22
+ const fileExtension = fileName.split(".").pop().toLowerCase();
23
+ const filePath = file.path || file.filepath;
24
+ if (!filePath) {
25
+ throw new Error("File path not found");
26
+ }
34
27
 
35
- if (fileExtension === 'json') {
36
- const fileContent = fs.readFileSync(filePath, 'utf8');
37
- importData = JSON.parse(fileContent);
38
- strapi.log.info('Parsed JSON data:', Object.keys(importData));
39
- } else if (fileExtension === 'xlsx' || fileExtension === 'xls') {
40
- importData = transformExcelData(filePath);
41
- }
42
- result = await bulkInsertData(importData);
43
- return result;
44
- } catch (error) {
45
- // Clean up uploaded file on error
46
- const filePath = file && (file.path || file.filepath);
47
- if (filePath && fs.existsSync(filePath)) {
48
- fs.unlinkSync(filePath);
28
+ if (fileExtension === "json") {
29
+ const fileContent = fs.readFileSync(filePath, "utf8");
30
+ importData = JSON.parse(fileContent);
31
+ strapi.log.info("Parsed JSON data:", Object.keys(importData));
32
+ } else if (fileExtension === "xlsx" || fileExtension === "xls") {
33
+ importData = this.transformExcelData(filePath, targetContentType);
34
+ }
35
+ result = await this.bulkInsertData(importData);
36
+ return result;
37
+ } catch (error) {
38
+ // Clean up uploaded file on error
39
+ const filePath = file && (file.path || file.filepath);
40
+ if (filePath && fs.existsSync(filePath)) {
41
+ fs.unlinkSync(filePath);
42
+ }
43
+ throw error;
49
44
  }
50
- throw error;
51
- }
52
- }
53
-
45
+ },
54
46
 
55
- function transformExcelData(filePath) {
47
+ transformExcelData(filePath, targetContentType = null) {
56
48
  const workbook = XLSX.readFile(filePath);
57
49
  const importData = {};
58
50
 
59
- const parseJsonIfNeeded = (value) => {
60
- if (typeof value !== 'string') return value;
61
- const trimmed = value.trim();
62
- if (!trimmed.startsWith('[') && !trimmed.startsWith('{')) return value;
63
-
64
- try {
65
- return JSON.parse(trimmed);
66
- } catch {
67
- return value; // keep as string if invalid JSON
68
- }
69
- };
70
-
71
51
  const isComponentField = (key) => {
72
- const parts = key.split('_');
73
- return parts.length === 2; // exactly one underscore
52
+ const parts = key.split("_");
53
+ return parts.length === 2; // exactly one underscore
74
54
  };
75
55
 
76
- function unflattenRow(rows, targetContentType) {
56
+ const unflattenRow = (rows, targetContentType) => {
77
57
  const result = [];
78
58
  const attr = strapi.contentTypes[targetContentType].attributes;
79
59
  for (const row of rows) {
@@ -83,13 +63,13 @@ function transformExcelData(filePath) {
83
63
  if (value === null || value === undefined || value === '') {
84
64
  rowData[key] = null
85
65
  } else if (attr[key] && attr[key].customField && attr[key].type === 'json' && attr[key].default === '[]') {
86
- rowData[key] = parseJsonIfNeeded(value).split('|');
66
+ rowData[key] = value.split('|');
87
67
  } else if (isComponentField(key)) {
88
68
  const [comp, field] = key.split('_');
89
69
  if (!rowData[comp]) rowData[comp] = {};
90
- rowData[comp][field] = parseJsonIfNeeded(value);
70
+ rowData[comp][field] = value;
91
71
  } else {
92
- rowData[key] = parseJsonIfNeeded(value);
72
+ rowData[key] = value;
93
73
  }
94
74
  }
95
75
  result.push(rowData);
@@ -99,280 +79,341 @@ function transformExcelData(filePath) {
99
79
  };
100
80
 
101
81
  const mapSheetNameToContentType = (sheetName) => {
102
- return "api::" + sheetName + "." + sheetName;
82
+ // If targetContentType is provided, use it instead of guessing from sheet name
83
+ if (targetContentType) {
84
+ return targetContentType;
85
+ }
86
+ return "api::" + sheetName + "." + sheetName;
103
87
  };
104
88
 
105
- workbook.SheetNames.forEach(sheetName => {
106
- const worksheet = workbook.Sheets[sheetName];
107
- const rows = XLSX.utils.sheet_to_json(worksheet);
89
+ workbook.SheetNames.forEach((sheetName) => {
90
+ const worksheet = workbook.Sheets[sheetName];
91
+ const rows = XLSX.utils.sheet_to_json(worksheet);
108
92
 
109
- if (!rows.length) return;
93
+ if (!rows.length) return;
110
94
 
111
- const contentTypeName = mapSheetNameToContentType(sheetName);
95
+ const contentTypeName = mapSheetNameToContentType(sheetName);
112
96
 
113
- strapi.log.info(`Reading sheet "${sheetName}" -> ${rows.length} rows`);
114
- strapi.log.info(`Mapped sheet to content-type: ${contentTypeName}`);
97
+ strapi.log.info(`Reading sheet "${sheetName}" -> ${rows.length} rows`);
98
+ strapi.log.info(`Mapped sheet to content-type: ${contentTypeName}`);
115
99
 
116
- if (contentTypeName.startsWith('api::')) {
117
- importData[contentTypeName] = unflattenRow(rows, contentTypeName);
118
- } else {
119
- strapi.log.error(`Unknown content-type: ${contentTypeName}`);
100
+ if (contentTypeName.startsWith("api::")) {
101
+ // Validate that the content type exists
102
+ if (!strapi.contentTypes[contentTypeName]) {
103
+ strapi.log.error(
104
+ `Content type ${contentTypeName} not found. Available types:`,
105
+ Object.keys(strapi.contentTypes)
106
+ );
120
107
  return;
121
108
  }
109
+ importData[contentTypeName] = unflattenRow(rows, contentTypeName);
110
+ } else {
111
+ strapi.log.error(`Unknown content-type: ${contentTypeName}`);
112
+ return;
113
+ }
122
114
  });
123
115
 
124
- strapi.log.info('Final import data keys:', Object.keys(importData));
116
+ strapi.log.info("Final import data keys:", Object.keys(importData));
125
117
  return importData;
126
- }
118
+ },
127
119
 
128
- function getRelationFields(contentType) {
129
- const schema = strapi.contentTypes[contentType];
130
-
131
- if (!schema) {
132
- strapi.log.warn(`Content type ${contentType} not found`);
133
- return [];
134
- }
135
-
136
- return Object.entries(schema.attributes)
137
- .filter(([_, attr]) => attr.type === "relation")
138
- .map(([fieldName, attr]) => ({
139
- field: toCamel(fieldName),
140
- target: attr.target, // e.g. "api::category.category"
141
- relation: attr.relation,
142
- }));
143
- }
120
+ getRelationFields(contentType) {
121
+ const schema = strapi.contentTypes[contentType];
144
122
 
145
- function getComponentFields(contentType) {
146
- const schema = strapi.contentTypes[contentType];
123
+ if (!schema) {
124
+ strapi.log.warn(`Content type ${contentType} not found`);
125
+ return [];
126
+ }
147
127
 
148
- if (!schema) {
149
- strapi.log.warn(`Content type ${contentType} not found`);
150
- return [];
151
- }
128
+ return Object.entries(schema.attributes)
129
+ .filter(([_, attr]) => attr.type === "relation")
130
+ .map(([fieldName, attr]) => ({
131
+ field: toCamel(fieldName),
132
+ target: attr.target, // e.g. "api::category.category"
133
+ relation: attr.relation,
134
+ }));
135
+ },
136
+
137
+ getComponentFields(contentType) {
138
+ const schema = strapi.contentTypes[contentType];
139
+
140
+ if (!schema) {
141
+ strapi.log.warn(`Content type ${contentType} not found`);
142
+ return [];
143
+ }
152
144
 
153
- return Object.entries(schema.attributes)
154
- .filter(([_, attr]) => attr.type === "component")
155
- .map(([fieldName, attr]) => toCamel(fieldName));
156
- }
145
+ return Object.entries(schema.attributes)
146
+ .filter(([_, attr]) => attr.type === "component")
147
+ .map(([fieldName, attr]) => toCamel(fieldName));
148
+ },
149
+
150
+ async handleRelations(entry, contentType) {
151
+ const resolveRelationValue = async (field, value, target) => {
152
+ const targetAttr = strapi.contentTypes[target].attributes;
153
+ for (const field of SHORTCUT_FIELDS) {
154
+ if (!targetAttr[field]) continue;
155
+ const existing = await strapi.documents(target).findFirst({
156
+ filters: { [field]: { $eq: value } },
157
+ });
158
+ if (existing) return { id: existing.id };
159
+ throw new Error(`Data with ${field} ${value} not found`);
160
+ }
161
+ return null;
162
+ };
157
163
 
158
- async function handleRelations(entry, contentType) {
159
- async function resolveRelationValue(field, value, target) {
160
- const targetAttr = strapi.contentTypes[target].attributes;
161
- for (const field of SHORTCUT_FIELDS) {
162
- if (!targetAttr[field]) continue;
163
- const existing = await strapi.documents(target).findFirst({
164
- filters: { [field]: { $eq: value } },
165
- });
166
- if (existing) return {id: existing.id};
167
- throw new Error(`Data with ${field} ${value} not found`);
168
- }
169
- return null;
170
- }
164
+ const relationFields = this.getRelationFields(contentType);
165
+ if (relationFields.length === 0) return entry;
171
166
 
172
- const relationFields = getRelationFields(contentType);
173
- if (relationFields.length === 0) return entry;
167
+ const updatedEntry = { ...entry };
174
168
 
175
- const updatedEntry = { ...entry };
169
+ for (const rel of relationFields) {
170
+ const { field, target, relation } = rel;
176
171
 
177
- for (const rel of relationFields) {
178
- const { field, target, relation } = rel;
172
+ let value = entry[field];
173
+ if (!value || value === "") {
174
+ if (relation === "manyToMany" || relation === "oneToMany") {
175
+ updatedEntry[field] = [];
176
+ } else {
177
+ updatedEntry[field] = null;
178
+ }
179
+ continue;
180
+ }
179
181
 
180
- let value = entry[field];
181
- if (!value || value === "") {
182
- if (relation === "manyToMany" || relation === "oneToMany") {
183
- updatedEntry[field] = [];
184
- } else {
185
- updatedEntry[field] = null;
182
+ // Convert CSV to array
183
+ if (
184
+ typeof value === "string" &&
185
+ (relation === "manyToMany" || relation === "oneToMany")
186
+ ) {
187
+ value = value.split("|");
188
+ } else if (typeof value === "string" && value.includes("|")) {
189
+ throw new Error(
190
+ `Invalid value for field ${field}: ${value}, ${field} is not an array`
191
+ );
186
192
  }
187
- continue;
188
- };
189
193
 
190
- // Convert CSV to array
191
- if (typeof value === "string" && (relation === "manyToMany" || relation === "oneToMany")) {
192
- value = value.split("|");
193
- } else if (typeof value === "string" && value.includes("|")) {
194
- throw new Error(`Invalid value for field ${field}: ${value}, ${field} is not an array`);
195
- }
194
+ const values = Array.isArray(value) ? value : [value];
195
+ try {
196
+ const processed = [];
196
197
 
197
- const values = Array.isArray(value) ? value : [value];
198
- try {
199
- const processed = [];
198
+ for (const v of values) {
199
+ if (!v || v === "") continue;
200
+ const resolved = await resolveRelationValue(field, v, target);
201
+ if (resolved) processed.push(resolved);
202
+ }
200
203
 
201
- for (const v of values) {
202
- if (!v || v === "") continue;
203
- const resolved = await resolveRelationValue(field, v, target);
204
- if (resolved) processed.push(resolved);
204
+ updatedEntry[field] = Array.isArray(value) ? processed : processed[0];
205
+ } catch (err) {
206
+ throw new Error(
207
+ `Failed processing field ${field} with value ${JSON.stringify(value)}: ${err.message}`
208
+ );
205
209
  }
206
-
207
- updatedEntry[field] = Array.isArray(value) ? processed : processed[0];
208
- } catch (err) {
209
- throw new Error(
210
- `Failed processing field ${field} with value ${JSON.stringify(value)}: ${err.message}`
211
- );
212
210
  }
213
- }
214
-
215
- return updatedEntry;
216
- }
217
211
 
218
- function handleComponents(data, existing, contentType) {
219
- // Get the component fields for this content type
220
- const compFields = getComponentFields(contentType);
212
+ return updatedEntry;
213
+ },
221
214
 
222
- for (const field of compFields) {
223
- const newValue = data[field];
224
- const oldValue = existing?.[field];
215
+ handleComponents(data, existing, contentType) {
216
+ // Get the component fields for this content type
217
+ const compFields = this.getComponentFields(contentType);
225
218
 
226
- if (!newValue || !oldValue) continue;
219
+ for (const field of compFields) {
220
+ const newValue = data[field];
221
+ const oldValue = existing?.[field];
227
222
 
228
- //single component
229
- if (!Array.isArray(newValue)) {
230
- if (oldValue?.id) {
231
- data[field].id = oldValue.id;
232
- }
233
- for (const key of Object.keys(data[field])) {
234
- if (Array.isArray(oldValue[key])) {
235
- data[field][key] = data[field][key].split("|");
236
- }
237
- }
238
- continue;
239
- }
223
+ if (!newValue || !oldValue) continue;
240
224
 
241
- //multiple components
242
- if (Array.isArray(newValue) && Array.isArray(oldValue)) {
243
- data[field] = newValue.map((block, i) => {
244
- const oldBlock = oldValue[i];
245
- if (oldBlock?.id) {
246
- return { id: oldBlock.id, ...block };
225
+ //single component
226
+ if (!Array.isArray(newValue)) {
227
+ if (oldValue?.id) {
228
+ data[field].id = oldValue.id;
247
229
  }
248
- for (const key of Object.keys(block)) {
249
- if (Array.isArray(oldBlock[key])) {
250
- block[key] = block[key].split("|");
230
+ for (const key of Object.keys(data[field])) {
231
+ if (Array.isArray(oldValue[key])) {
232
+ data[field][key] = data[field][key].split("|");
251
233
  }
252
234
  }
253
- return block;
254
- });
255
- }
256
- }
257
-
258
- return data;
259
- }
235
+ continue;
236
+ }
260
237
 
261
- function hasChanges(existing, incoming) {
262
- if (!incoming || typeof incoming !== "object") return false;
263
- if (!existing || typeof existing !== "object") return true;
264
- for (const key of Object.keys(incoming)) {
265
- // Skip system keys
266
- if (SYSTEM_KEYS.includes(key)) continue;
267
- const newVal = incoming[key];
268
- const oldVal = existing[key];
269
-
270
- // If incoming defines a field but existing doesn't → change
271
- if (oldVal === undefined || newVal === undefined) {
272
- continue;
238
+ //multiple components
239
+ if (Array.isArray(newValue) && Array.isArray(oldValue)) {
240
+ data[field] = newValue.map((block, i) => {
241
+ const oldBlock = oldValue[i];
242
+ if (oldBlock?.id) {
243
+ return { id: oldBlock.id, ...block };
244
+ }
245
+ for (const key of Object.keys(block)) {
246
+ if (Array.isArray(oldBlock[key])) {
247
+ block[key] = block[key].split("|");
248
+ }
249
+ }
250
+ return block;
251
+ });
252
+ }
273
253
  }
274
254
 
275
- // Primitive comparison
276
- if (newVal === null || typeof newVal !== "object") {
277
- if (oldVal !== newVal) {
278
- return true;
255
+ return data;
256
+ },
257
+
258
+ sanitizeComponent(data, uid, rowIndex, errors, path) {
259
+ const schema = strapi.components[uid];
260
+ if (!schema) return data;
261
+
262
+ const cleaned = {};
263
+
264
+ for (const [key, attr] of Object.entries(schema.attributes)) {
265
+ const value = data?.[key];
266
+ const fieldPath = `${path}.${key}`;
267
+
268
+ if (attr.type === 'component') {
269
+ cleaned[key] = attr.repeatable
270
+ ? (value || []).map((v, i) =>
271
+ sanitizeComponent(v, attr.component, rowIndex, errors, `${fieldPath}[${i}]`)
272
+ )
273
+ : sanitizeComponent(value, attr.component, rowIndex, errors, fieldPath);
274
+ } else {
275
+ cleaned[key] = sanitizePrimitive(value, attr, rowIndex, errors, fieldPath);
279
276
  }
280
- continue;
281
277
  }
282
-
283
- // ARRAY comparison
284
- if (Array.isArray(newVal)) {
285
- if (!Array.isArray(oldVal)) return true;
286
- if (newVal.length !== oldVal.length) return true;
287
- // Compare values shallowly
288
- for (let i = 0; i < newVal.length; i++) {
289
- if (typeof newVal[i] === "object" && typeof oldVal[i] === "object" && hasChanges(oldVal[i], newVal[i])) {
290
- return true;
291
- } else if (typeof newVal[i] !== "object" && typeof oldVal[i] !== "object" && newVal[i] !== oldVal[i]) {
292
- return true;
278
+
279
+ return cleaned;
280
+ },
281
+
282
+ sanitizeEntryBeforeWrite(data, uid, rowIndex, errors, path = '') {
283
+ const schema = strapi.contentTypes[uid];
284
+ const cleaned = {};
285
+
286
+ for (const [key, attr] of Object.entries(schema.attributes)) {
287
+ const value = data[key];
288
+ const fieldPath = path ? `${path}.${key}` : key;
289
+
290
+ if (value === undefined) continue;
291
+
292
+ if (attr.type === 'component') {
293
+ if (!value) {
294
+ cleaned[key] = attr.repeatable ? [] : null;
295
+ continue;
293
296
  }
297
+
298
+ cleaned[key] = attr.repeatable
299
+ ? value.map((v, i) =>
300
+ sanitizeComponent(v, attr.component, rowIndex, errors, `${fieldPath}[${i}]`)
301
+ )
302
+ : sanitizeComponent(value, attr.component, rowIndex, errors, fieldPath);
303
+ continue;
294
304
  }
295
- continue;
305
+
306
+ cleaned[key] = sanitizePrimitive(value, attr, rowIndex, errors, fieldPath);
296
307
  }
297
-
298
- // OBJECT comparison (recursive, but ONLY fields in incoming object)
299
- if (typeof newVal === "object" && typeof oldVal === "object") {
300
- if (hasChanges(oldVal, newVal)) {
301
- return true;
308
+
309
+ return cleaned;
310
+ },
311
+
312
+ sanitizePrimitive(value, attr) {
313
+ if (value === null || value === undefined || value === '') return null;
314
+ switch (attr.type) {
315
+ case 'string':
316
+ case 'text':
317
+ case 'richtext':
318
+ case 'email':
319
+ return String(value).trim();
320
+ case 'boolean':
321
+ if ([true, 'true', 1, '1', 'yes', 'y'].includes(value)) return true;
322
+ if ([false, 'false', 0, '0', 'no', 'n'].includes(value)) return false;
323
+ return false; // fallback
324
+ case 'integer':
325
+ case 'biginteger': {
326
+ const i = parseInt(value, 10);
327
+ return Number.isNaN(i) ? 0 : i;
328
+ }
329
+ case 'float':
330
+ case 'decimal': {
331
+ const f = parseFloat(value);
332
+ return Number.isNaN(f) ? 0 : f;
333
+ }
334
+ case 'date':
335
+ case 'datetime': {
336
+ const d = new Date(value);
337
+ return isNaN(d.getTime()) ? null : d.toISOString();
302
338
  }
303
- continue;
339
+ default:
340
+ return value;
304
341
  }
305
- }
306
-
307
- return false;
308
- }
342
+ },
309
343
 
344
+ async bulkInsertData(importData) {
345
+ const results = {
346
+ created: 0,
347
+ updated: 0,
348
+ errors: [],
349
+ };
310
350
 
311
- async function bulkInsertData(importData) {
312
- const results = {
313
- created: 0,
314
- updated: 0,
315
- errors: [],
316
- };
351
+ for (const [contentType, entries] of Object.entries(importData)) {
352
+ // Validate entries
353
+ if (!strapi.contentTypes[contentType]) {
354
+ results.errors.push(`Content type ${contentType} not found`);
355
+ continue;
356
+ }
357
+ if (!Array.isArray(entries)) {
358
+ results.errors.push(`Invalid data format for ${contentType}`);
359
+ continue;
360
+ }
317
361
 
318
- for (const [contentType, entries] of Object.entries(importData)) {
319
- // Validate entries
320
- if (!strapi.contentTypes[contentType]) {
321
- results.errors.push(`Content type ${contentType} not found`);
322
- continue;
323
- }
324
- if (!Array.isArray(entries)) {
325
- results.errors.push(`Invalid data format for ${contentType}`);
326
- continue;
362
+ try {
363
+ const { created, updated, errors } = await this.importEntries(
364
+ entries,
365
+ contentType
366
+ );
367
+ results.created += created;
368
+ results.updated += updated;
369
+ results.errors = results.errors.concat(errors);
370
+ } catch (err) {
371
+ results.errors.push(err.message);
372
+ }
327
373
  }
328
374
 
329
- try {
330
- const { created, updated, errors } = await importEntries(entries, contentType);
331
- results.created += created;
332
- results.updated += updated;
333
- results.errors = results.errors.concat(errors);
334
- } catch (err) {
335
- results.errors.push(err.message);
336
- }
337
- }
375
+ return results;
376
+ },
338
377
 
339
- return results;
340
- }
378
+ async importEntries(entries, contentType) {
379
+ const results = { created: 0, updated: 0, errors: [] };
341
380
 
342
- async function importEntries(entries, contentType) {
343
- const results = { created: 0, updated: 0, errors: [] };
381
+ await strapi.db.transaction(async ({ trx, rollback, onRollback }) => {
382
+ onRollback(() => {
383
+ strapi.log.error("Transaction rolled back due to an error!");
384
+ strapi.log.error(results.errors);
385
+ });
344
386
 
345
- await strapi.db.transaction(async ({ trx, rollback, onRollback }) => {
346
- onRollback(() => {
347
- strapi.log.error("Transaction rolled back due to an error!");
348
- strapi.log.error(results.errors);
349
- });
387
+ for (let i = 0; i < entries.length; i++) {
388
+ const entry = entries[i];
389
+ let existing = null;
350
390
 
351
- for (let i = 0; i < entries.length; i++) {
352
- const entry = entries[i];
353
- let existing = null;
391
+ try {
392
+ let { id, ...data } = entry;
354
393
 
355
- try {
356
- let { id, ...data } = entry;
357
-
358
- // Check if document exists
359
- if (id && id !== "null" && id !== "undefined") {
360
- existing = await strapi.documents(contentType).findFirst(
361
- {
362
- filters: { id },
363
- populate: "*",
364
- },
365
- { transaction: trx }
366
- );
367
- }
394
+ // Check if document exists
395
+ if (id && id !== "null" && id !== "undefined") {
396
+ existing = await strapi.documents(contentType).findFirst(
397
+ {
398
+ filters: { id },
399
+ populate: "*",
400
+ },
401
+ { transaction: trx }
402
+ );
403
+ }
368
404
 
369
- // Handle relations & components
370
- data = await handleRelations(data, contentType, trx);
371
- data = await handleComponents(data, existing, contentType);
405
+ // Handle relations & components
406
+ data = await this.handleRelations(data, contentType, trx);
407
+ data = await this.handleComponents(data, existing, contentType);
408
+ const sanitizeErrors = [];
409
+ data = sanitizeEntryBeforeWrite(data, contentType, '', sanitizeErrors);
372
410
 
373
- // Update
374
- if (existing) {
375
- if (hasChanges(existing, data)) {
411
+ if (sanitizeErrors.length) {
412
+ throw new Error(`Data validation failed:\n${sanitizeErrors.join('\n')}`);
413
+ }
414
+
415
+ // Update
416
+ if (existing) {
376
417
  await strapi.documents(contentType).update(
377
418
  {
378
419
  documentId: existing.documentId,
@@ -382,35 +423,29 @@ async function importEntries(entries, contentType) {
382
423
  );
383
424
  results.updated++;
384
425
  }
385
- }
386
426
 
387
- // Create
388
- else {
389
- await strapi.documents(contentType).create(
390
- { data },
391
- { transaction: trx }
427
+ // Create
428
+ else {
429
+ await strapi
430
+ .documents(contentType)
431
+ .create({ data }, { transaction: trx });
432
+ results.created++;
433
+ }
434
+ } catch (err) {
435
+ results.errors.push(
436
+ `Failed ${existing ? "updating" : "creating"} on row ${
437
+ i + 2
438
+ }: ${err.message}`
392
439
  );
393
- results.created++;
394
- }
395
- } catch (err) {
396
- results.errors.push(
397
- `Failed ${existing ? "updating" : "creating"} on row ${
398
- i + 2
399
- }: ${err.message}`
400
- );
401
- results.created = 0;
402
- results.updated = 0;
440
+ results.created = 0;
441
+ results.updated = 0;
403
442
 
404
- // IMPORTANT: force rollback
405
- throw err;
443
+ // IMPORTANT: force rollback
444
+ throw err;
445
+ }
406
446
  }
407
- }
408
- });
409
-
410
- return results;
411
- }
412
-
447
+ });
413
448
 
414
- module.exports = {
415
- importData,
416
- };
449
+ return results;
450
+ },
451
+ });