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