@nkhang1902/strapi-plugin-export-import-clsx 1.0.4 → 1.0.5
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 +1 -1
- package/server/services/import-service.js +102 -75
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nkhang1902/strapi-plugin-export-import-clsx",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.5",
|
|
4
4
|
"description": "A powerful Strapi plugin for exporting and importing data with Excel support and advanced filtering",
|
|
5
5
|
"main": "./strapi-server.js",
|
|
6
6
|
"scripts": {
|
|
@@ -5,18 +5,6 @@ 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
9
|
'email','businessEmail','name','title','tickerCode',
|
|
22
10
|
]
|
|
@@ -56,18 +44,6 @@ function transformExcelData(filePath) {
|
|
|
56
44
|
const workbook = XLSX.readFile(filePath);
|
|
57
45
|
const importData = {};
|
|
58
46
|
|
|
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
47
|
const isComponentField = (key) => {
|
|
72
48
|
const parts = key.split('_');
|
|
73
49
|
return parts.length === 2; // exactly one underscore
|
|
@@ -83,13 +59,13 @@ function transformExcelData(filePath) {
|
|
|
83
59
|
if (value === null || value === undefined || value === '') {
|
|
84
60
|
rowData[key] = null
|
|
85
61
|
} else if (attr[key] && attr[key].customField && attr[key].type === 'json' && attr[key].default === '[]') {
|
|
86
|
-
rowData[key] =
|
|
62
|
+
rowData[key] = value.split('|');
|
|
87
63
|
} else if (isComponentField(key)) {
|
|
88
64
|
const [comp, field] = key.split('_');
|
|
89
65
|
if (!rowData[comp]) rowData[comp] = {};
|
|
90
|
-
rowData[comp][field] =
|
|
66
|
+
rowData[comp][field] = value;
|
|
91
67
|
} else {
|
|
92
|
-
rowData[key] =
|
|
68
|
+
rowData[key] = value;
|
|
93
69
|
}
|
|
94
70
|
}
|
|
95
71
|
result.push(rowData);
|
|
@@ -258,55 +234,101 @@ function handleComponents(data, existing, contentType) {
|
|
|
258
234
|
return data;
|
|
259
235
|
}
|
|
260
236
|
|
|
261
|
-
function
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
const
|
|
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;
|
|
273
|
-
}
|
|
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;
|
|
274
244
|
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
245
|
+
if (value === undefined) continue;
|
|
246
|
+
|
|
247
|
+
if (attr.type === 'component') {
|
|
248
|
+
if (!value) {
|
|
249
|
+
cleaned[key] = attr.repeatable ? [] : null;
|
|
250
|
+
continue;
|
|
279
251
|
}
|
|
252
|
+
|
|
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);
|
|
280
258
|
continue;
|
|
281
259
|
}
|
|
282
260
|
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
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;
|
|
293
|
-
}
|
|
294
|
-
}
|
|
295
|
-
continue;
|
|
261
|
+
if (attr.type === 'relation') {
|
|
262
|
+
cleaned[key] = value;
|
|
263
|
+
} else {
|
|
264
|
+
cleaned[key] = sanitizePrimitive(value, attr, fieldPath, errors);
|
|
296
265
|
}
|
|
266
|
+
}
|
|
297
267
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
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);
|
|
304
289
|
}
|
|
305
290
|
}
|
|
306
291
|
|
|
307
|
-
return
|
|
292
|
+
return cleaned;
|
|
308
293
|
}
|
|
309
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;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
case 'float':
|
|
317
|
+
case 'decimal': {
|
|
318
|
+
const f = parseFloat(value);
|
|
319
|
+
return Number.isNaN(f) ? 0 : f;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
case 'date':
|
|
323
|
+
case 'datetime': {
|
|
324
|
+
const d = new Date(value);
|
|
325
|
+
return isNaN(d.getTime()) ? null : d.toISOString();
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
default:
|
|
329
|
+
return value;
|
|
330
|
+
}
|
|
331
|
+
}
|
|
310
332
|
|
|
311
333
|
async function bulkInsertData(importData) {
|
|
312
334
|
const results = {
|
|
@@ -367,21 +389,26 @@ async function importEntries(entries, contentType) {
|
|
|
367
389
|
}
|
|
368
390
|
|
|
369
391
|
// Handle relations & components
|
|
370
|
-
data = await handleRelations(data, contentType
|
|
392
|
+
data = await handleRelations(data, contentType);
|
|
371
393
|
data = await handleComponents(data, existing, contentType);
|
|
372
394
|
|
|
395
|
+
const sanitizeErrors = [];
|
|
396
|
+
data = sanitizeEntryBeforeWrite(data, contentType, '', sanitizeErrors);
|
|
397
|
+
|
|
398
|
+
if (sanitizeErrors.length) {
|
|
399
|
+
throw new Error(`Data validation failed:\n${sanitizeErrors.join('\n')}`);
|
|
400
|
+
}
|
|
401
|
+
|
|
373
402
|
// Update
|
|
374
403
|
if (existing) {
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
results.updated++;
|
|
384
|
-
}
|
|
404
|
+
await strapi.documents(contentType).update(
|
|
405
|
+
{
|
|
406
|
+
documentId: existing.documentId,
|
|
407
|
+
data,
|
|
408
|
+
},
|
|
409
|
+
{ transaction: trx }
|
|
410
|
+
);
|
|
411
|
+
results.updated++;
|
|
385
412
|
}
|
|
386
413
|
|
|
387
414
|
// Create
|