@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,19 +1,34 @@
1
- const XLSX = require('xlsx');
1
+ const XLSX = require("xlsx");
2
2
 
3
3
  module.exports = ({ strapi }) => ({
4
- async exportData(format = 'json', contentType = null, rawFilters = {}, selectedIds = [], selectedField = null) {
4
+ async exportData(format = "json", contentType = null, rawFilters = {}) {
5
+ // Normalize content type - handle both content-manager and event-manager formats
6
+ if (contentType && !contentType.startsWith("api::")) {
7
+ // If it's already in api:: format from event-manager, use as is
8
+ // If it's from content-manager, it should already be in correct format
9
+ contentType = contentType;
10
+ }
11
+
5
12
  // Get only API content types (collections)
6
13
  let contentTypes;
7
14
  if (contentType) {
15
+ // Validate that the content type exists
16
+ if (!strapi.contentTypes[contentType]) {
17
+ strapi.log.error(
18
+ `Content type ${contentType} not found. Available types:`,
19
+ Object.keys(strapi.contentTypes)
20
+ );
21
+ throw new Error(`Content type ${contentType} not found`);
22
+ }
8
23
  contentTypes = [contentType];
9
24
  } else {
10
- contentTypes = Object.keys(strapi.contentTypes).filter(
11
- (key) => key.startsWith('api::')
25
+ contentTypes = Object.keys(strapi.contentTypes).filter((key) =>
26
+ key.startsWith("api::")
12
27
  );
13
28
  }
14
29
 
15
30
  const exportData = {
16
- version: strapi.config.get('info.strapi'),
31
+ version: strapi.config.get("info.strapi"),
17
32
  timestamp: new Date().toISOString(),
18
33
  data: {},
19
34
  };
@@ -23,81 +38,76 @@ module.exports = ({ strapi }) => ({
23
38
  // Parse filters from URL format
24
39
  const parsedFilters = this.parseFilters(rawFilters);
25
40
 
26
- if (rawFilters['_q']) {
27
- parsedFilters._q = rawFilters['_q'];
41
+ if (rawFilters["_q"]) {
42
+ parsedFilters._q = rawFilters["_q"];
28
43
  }
29
44
 
30
- strapi.log.info(`Exporting ${ct} with raw filters: ${JSON.stringify(rawFilters)}`);
45
+ strapi.log.info(
46
+ `Exporting ${ct} with raw filters: ${JSON.stringify(rawFilters)}`
47
+ );
31
48
  strapi.log.info(`Parsed filters: ${JSON.stringify(parsedFilters)}`);
32
- strapi.log.info(`Selected IDs: ${JSON.stringify(selectedIds)}`);
33
49
 
34
50
  let entries = [];
35
51
  let filters = parsedFilters.filters;
36
52
 
37
- // If specific IDs are selected, export only those
38
- if (selectedIds && selectedIds.length > 0) {
39
- strapi.log.info(`Exporting selected: ${JSON.stringify(selectedIds)}, field: ${selectedField}`);
40
- if (selectedField === 'id' || (strapi.contentTypes[ct].attributes[selectedField] &&["number", "integer", "biginteger", "float", "decimal"].includes(strapi.contentTypes[ct].attributes[selectedField].type))) {
41
- selectedIds = selectedIds.map((id) => Number(id));
42
- }
43
- try {
44
- entries = await strapi.documents(ct).findMany({
45
- filters: {
46
- [selectedField]: { $in: selectedIds }
47
- },
48
- populate: '*'
49
- });
50
- } catch (error) {
51
- strapi.log.error(`Failed to export selected entries:`, error);
53
+ // Export all entries with filters
54
+ const searchable = this.getSearchableFields(strapi.contentTypes[ct]);
55
+ const numberSearchable = this.getNumberFields(strapi.contentTypes[ct]);
56
+
57
+ if (parsedFilters._q) {
58
+ strapi.log.info(
59
+ `Applying search query: ${
60
+ parsedFilters._q
61
+ } for fields: ${JSON.stringify([
62
+ ...searchable,
63
+ ...numberSearchable,
64
+ ])}`
65
+ );
66
+ const orConditions = [];
67
+
68
+ if (searchable.length > 0) {
69
+ orConditions.push(
70
+ ...searchable.map((field) => ({
71
+ [field]: { $containsi: parsedFilters._q },
72
+ }))
73
+ );
52
74
  }
53
- } else {
54
- // Export all entries with filters
55
- const searchable = this.getSearchableFields(strapi.contentTypes[ct]);
56
- const numberSearchable = this.getNumberFields(strapi.contentTypes[ct]);
57
-
58
- if (parsedFilters._q) {
59
- strapi.log.info(`Applying search query: ${parsedFilters._q} for fields: ${JSON.stringify([...searchable, ...numberSearchable])}`);
60
- const orConditions = [];
61
-
62
- if (searchable.length > 0) {
63
- orConditions.push(
64
- ...searchable.map((field) => ({
65
- [field]: { $containsi: parsedFilters._q }
66
- }))
67
- );
68
- }
69
-
70
- if (numberSearchable.length > 0 && !isNaN(parsedFilters._q)) {
71
- orConditions.push(
72
- ...numberSearchable.map((field) => ({
73
- [field]: { $eq: Number(parsedFilters._q) }
74
- }))
75
- );
76
- }
77
75
 
78
- if (orConditions.length > 0) {
79
- filters = {
80
- ...filters,
81
- $and: [
82
- ...(filters?.$and || []),
83
- { $or: orConditions }
84
- ]
85
- };
86
- }
76
+ if (numberSearchable.length > 0 && !isNaN(parsedFilters._q)) {
77
+ orConditions.push(
78
+ ...numberSearchable.map((field) => ({
79
+ [field]: { $eq: Number(parsedFilters._q) },
80
+ }))
81
+ );
87
82
  }
88
- strapi.log.info(`Parsed query filters: ${JSON.stringify(filters)}`)
89
- try {
90
- entries = await strapi.documents(ct).findMany({
91
- filters: { ...filters},
92
- populate: '*',
93
- });
94
- strapi.log.info(`EntityService found ${entries?.length || 0} entries`);
95
- } catch (error) {
96
- strapi.log.error(`Failed to query entries:`, error);
83
+
84
+ if (orConditions.length > 0) {
85
+ filters = {
86
+ ...filters,
87
+ $and: [...(filters?.$and || []), { $or: orConditions }],
88
+ };
97
89
  }
98
90
  }
99
91
 
100
- strapi.log.info(`Final result: ${entries?.length || 0} entries for ${ct} (total found: ${entries?.length || 0})`);
92
+ strapi.log.info(`Parsed query filters: ${JSON.stringify(filters)}`);
93
+
94
+ try {
95
+ entries = await strapi.documents(ct).findMany({
96
+ filters: { ...filters },
97
+ populate: "*",
98
+ });
99
+ strapi.log.info(
100
+ `EntityService found ${entries?.length || 0} entries`
101
+ );
102
+ } catch (error) {
103
+ strapi.log.error(`Failed to query entries:`, error);
104
+ }
105
+
106
+ strapi.log.info(
107
+ `Final result: ${
108
+ entries?.length || 0
109
+ } entries for ${ct} (total found: ${entries?.length || 0})`
110
+ );
101
111
 
102
112
  if (entries && entries.length > 0) {
103
113
  exportData.data[ct] = entries;
@@ -107,7 +117,7 @@ module.exports = ({ strapi }) => ({
107
117
  }
108
118
  }
109
119
 
110
- if (format === 'excel') {
120
+ if (format === "excel") {
111
121
  return this.convertToExcel(exportData.data);
112
122
  }
113
123
 
@@ -117,8 +127,15 @@ module.exports = ({ strapi }) => ({
117
127
  getSearchableFields(contentTypeSchema) {
118
128
  const searchable = [];
119
129
 
120
- for (const [fieldName, field] of Object.entries(contentTypeSchema.attributes)) {
121
- if (["string", "text", "richtext", "email", "uid", "enumeration"].includes(field.type) && fieldName !== 'locale') {
130
+ for (const [fieldName, field] of Object.entries(
131
+ contentTypeSchema.attributes
132
+ )) {
133
+ if (
134
+ ["string", "text", "richtext", "email", "uid", "enumeration"].includes(
135
+ field.type
136
+ ) &&
137
+ fieldName !== "locale"
138
+ ) {
122
139
  searchable.push(fieldName);
123
140
  }
124
141
  }
@@ -129,13 +146,19 @@ module.exports = ({ strapi }) => ({
129
146
  getNumberFields(contentTypeSchema) {
130
147
  const numberFields = [];
131
148
 
132
- for (const [fieldName, field] of Object.entries(contentTypeSchema.attributes)) {
133
- if (["number", "integer", "biginteger", "float", "decimal"].includes(field.type)) {
149
+ for (const [fieldName, field] of Object.entries(
150
+ contentTypeSchema.attributes
151
+ )) {
152
+ if (
153
+ ["number", "integer", "biginteger", "float", "decimal"].includes(
154
+ field.type
155
+ )
156
+ ) {
134
157
  numberFields.push(fieldName);
135
158
  }
136
159
  }
137
160
 
138
- numberFields.push('id');
161
+ numberFields.push("id");
139
162
 
140
163
  return numberFields;
141
164
  },
@@ -144,26 +167,39 @@ module.exports = ({ strapi }) => ({
144
167
  const parsed = {};
145
168
  for (const [key, value] of Object.entries(filters)) {
146
169
  // Skip pagination and sorting params
147
- if (['page', 'pageSize', 'sort', 'locale', 'format', 'contentType', '_q'].includes(key)) {
170
+ if (
171
+ [
172
+ "page",
173
+ "pageSize",
174
+ "sort",
175
+ "locale",
176
+ "format",
177
+ "contentType",
178
+ "_q",
179
+ ].includes(key)
180
+ ) {
148
181
  continue;
149
182
  }
150
183
 
151
184
  // Handle URL encoded filter format like filters[$and][0][shortName][$contains]
152
- if (key.startsWith('filters[')) {
185
+ if (key.startsWith("filters[")) {
153
186
  // Extract the actual filter structure
154
- const match = key.match(/filters\[([^\]]+)\](?:\[(\d+)\])?\[([^\]]+)\](?:\[([^\]]+)\])?/);
187
+ const match = key.match(
188
+ /filters\[([^\]]+)\](?:\[(\d+)\])?\[([^\]]+)\](?:\[([^\]]+)\])?/
189
+ );
155
190
  if (match) {
156
191
  const [, operator, index, field, condition] = match;
157
192
 
158
193
  if (!parsed.filters) parsed.filters = {};
159
194
 
160
- if (operator === '$and') {
195
+ if (operator === "$and") {
161
196
  if (!parsed.filters.$and) parsed.filters.$and = [];
162
197
  const idx = parseInt(index) || 0;
163
198
  if (!parsed.filters.$and[idx]) parsed.filters.$and[idx] = {};
164
199
 
165
200
  if (condition) {
166
- if (!parsed.filters.$and[idx][field]) parsed.filters.$and[idx][field] = {};
201
+ if (!parsed.filters.$and[idx][field])
202
+ parsed.filters.$and[idx][field] = {};
167
203
  parsed.filters.$and[idx][field][condition] = value;
168
204
  } else {
169
205
  parsed.filters.$and[idx][field] = value;
@@ -183,49 +219,50 @@ module.exports = ({ strapi }) => ({
183
219
  let hasData = false;
184
220
 
185
221
  const SYSTEM_KEYS = [
186
- 'documentId',
187
- 'locale',
188
- 'createdAt',
189
- 'updatedAt',
190
- 'publishedAt',
191
- 'createdBy',
192
- 'updatedBy',
193
- 'localizations',
194
- 'status'
222
+ "documentId",
223
+ "locale",
224
+ "createdAt",
225
+ "updatedAt",
226
+ "publishedAt",
227
+ "createdBy",
228
+ "updatedBy",
229
+ "localizations",
230
+ "status",
195
231
  ];
196
232
  const SHORTCUT_FIELDS = [
197
- 'email','businessEmail','name','title','tickerCode',
198
- ]
233
+ "email",
234
+ "businessEmail",
235
+ "name",
236
+ "title",
237
+ "tickerCode",
238
+ ];
199
239
 
200
240
  for (const [contentType, entries] of Object.entries(data)) {
201
241
  // Clean sheet name (Excel has restrictions)
202
242
  const sheetName = contentType
203
- .split('.')
243
+ .split(".")
204
244
  .pop()
205
- .replace(/[^\w\s-]/gi, '_')
245
+ .replace(/[^\w\s-]/gi, "_")
206
246
  .substring(0, 31);
207
247
 
208
-
209
248
  if (entries && entries.length > 0) {
210
249
  hasData = true;
211
250
 
212
251
  const attr = strapi.contentTypes[contentType].attributes;
213
252
  const customFields = Object.entries(attr)
214
- .filter(([key, definition]) =>
215
- definition.customField
216
- )
253
+ .filter(([key, definition]) => definition.customField)
217
254
  .map(([key]) => key);
218
255
 
219
256
  const relationFields = Object.entries(attr)
220
- .filter(([key, definition]) => definition.type === 'relation')
257
+ .filter(([key, definition]) => definition.type === "relation")
221
258
  .map(([key]) => key);
222
259
 
223
260
  const skipFields = Object.entries(attr)
224
- .filter(([key, definition]) => definition.type === 'media')
261
+ .filter(([key, definition]) => definition.type === "media")
225
262
  .map(([key]) => key);
226
263
 
227
264
  const componentFields = Object.entries(attr)
228
- .filter(([key, definition]) => definition.type === 'component')
265
+ .filter(([key, definition]) => definition.type === "component")
229
266
  .map(([key]) => key);
230
267
 
231
268
  function handleObject(key, value) {
@@ -237,14 +274,14 @@ module.exports = ({ strapi }) => ({
237
274
  }
238
275
  }
239
276
  }
240
- return undefined
277
+ return undefined;
241
278
  }
242
279
  // Clean and flatten entries for Excel
243
- const cleanedEntries = entries.map(entry => {
280
+ const cleanedEntries = entries.map((entry) => {
244
281
  function cleanAndFlatten(obj) {
245
282
  if (Array.isArray(obj)) {
246
283
  return obj.map(cleanAndFlatten);
247
- } else if (obj !== null && typeof obj === 'object') {
284
+ } else if (obj !== null && typeof obj === "object") {
248
285
  const result = {};
249
286
 
250
287
  for (const key in obj) {
@@ -253,22 +290,23 @@ module.exports = ({ strapi }) => ({
253
290
  // Skip system keys
254
291
  if (SYSTEM_KEYS.includes(key)) continue;
255
292
  if (customFields.includes(key)) continue;
256
- if ([...skipFields, 'wishlist', 'availableSlot'].includes(key)) continue;
293
+ if ([...skipFields, "wishlist", "availableSlot"].includes(key))
294
+ continue;
257
295
 
258
296
  if (componentFields.includes(key)) {
259
297
  for (const subKey in value) {
260
- if (subKey === 'id') continue;
298
+ if (subKey === "id") continue;
261
299
  result[`${key}_${subKey}`] = value[subKey];
262
300
  }
263
301
  continue;
264
302
  }
265
303
 
266
- if (value === null || typeof value !== 'object') {
304
+ if (value === null || typeof value !== "object") {
267
305
  result[key] = value;
268
306
  continue;
269
307
  }
270
308
 
271
- if (!Array.isArray(value) && typeof value === 'object') {
309
+ if (!Array.isArray(value) && typeof value === "object") {
272
310
  let temp = handleObject(key, value);
273
311
  if (temp !== undefined) {
274
312
  result[key] = temp;
@@ -277,7 +315,7 @@ module.exports = ({ strapi }) => ({
277
315
  }
278
316
 
279
317
  if (Array.isArray(value)) {
280
- if (value.length > 0 && typeof value[0] === 'object') {
318
+ if (value.length > 0 && typeof value[0] === "object") {
281
319
  let arrValue = [];
282
320
  for (const subValue in value) {
283
321
  arrValue.push(handleObject(key, value[subValue]));
@@ -311,12 +349,16 @@ module.exports = ({ strapi }) => ({
311
349
  }
312
350
  return result;
313
351
  }
314
- const cleanedFlat = cleanedEntries.map(entry => flattenForXLSX(entry));
352
+ const cleanedFlat = cleanedEntries.map((entry) =>
353
+ flattenForXLSX(entry)
354
+ );
315
355
  const worksheet = XLSX.utils.json_to_sheet(cleanedFlat);
316
356
  XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
317
357
  } else {
318
358
  // Create empty sheet with headers if no data
319
- const worksheet = XLSX.utils.json_to_sheet([{ message: 'No data found' }]);
359
+ const worksheet = XLSX.utils.json_to_sheet([
360
+ { message: "No data found" },
361
+ ]);
320
362
  XLSX.utils.book_append_sheet(workbook, worksheet, sheetName);
321
363
  hasData = true; // Prevent empty workbook error
322
364
  }
@@ -324,28 +366,30 @@ module.exports = ({ strapi }) => ({
324
366
 
325
367
  // If still no data, create a default sheet
326
368
  if (!hasData) {
327
- const worksheet = XLSX.utils.json_to_sheet([{ message: 'No data to export' }]);
328
- XLSX.utils.book_append_sheet(workbook, worksheet, 'NoData');
369
+ const worksheet = XLSX.utils.json_to_sheet([
370
+ { message: "No data to export" },
371
+ ]);
372
+ XLSX.utils.book_append_sheet(workbook, worksheet, "NoData");
329
373
  }
330
374
 
331
- return XLSX.write(workbook, { type: 'buffer', bookType: 'xlsx' });
375
+ return XLSX.write(workbook, { type: "buffer", bookType: "xlsx" });
332
376
  },
333
377
 
334
378
  async exportSingleEntry(contentType, entryId) {
335
379
  try {
336
380
  const entry = await strapi.entityService.findOne(contentType, entryId, {
337
- populate: '*',
381
+ populate: "*",
338
382
  });
339
383
 
340
384
  if (!entry) {
341
- throw new Error('Entry not found');
385
+ throw new Error("Entry not found");
342
386
  }
343
387
 
344
388
  const exportData = {
345
- version: strapi.config.get('info.strapi'),
389
+ version: strapi.config.get("info.strapi"),
346
390
  timestamp: new Date().toISOString(),
347
391
  data: {
348
- [contentType]: [entry]
392
+ [contentType]: [entry],
349
393
  },
350
394
  };
351
395
 
@@ -354,4 +398,4 @@ module.exports = ({ strapi }) => ({
354
398
  throw error;
355
399
  }
356
400
  },
357
- });
401
+ });