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