strapi-plugin-bulk-updater 1.2.12 → 1.2.13
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/dist/_chunks/{BulkUpdater-CQ9zGdat.js → BulkUpdater-CWJYmuGU.js} +84 -7
- package/dist/_chunks/BulkUpdater-CWJYmuGU.js.map +1 -0
- package/dist/_chunks/{BulkUpdater-DvCSr1vU.mjs → BulkUpdater-Cfig2f7d.mjs} +84 -7
- package/dist/_chunks/BulkUpdater-Cfig2f7d.mjs.map +1 -0
- package/dist/_chunks/{SettingsPage-BRGTrW9H.js → SettingsPage-BAvuOb7V.js} +2 -2
- package/dist/_chunks/{SettingsPage-BRGTrW9H.js.map → SettingsPage-BAvuOb7V.js.map} +1 -1
- package/dist/_chunks/{SettingsPage-ieu-Out8.mjs → SettingsPage-BWo4l6Gd.mjs} +2 -2
- package/dist/_chunks/{SettingsPage-ieu-Out8.mjs.map → SettingsPage-BWo4l6Gd.mjs.map} +1 -1
- package/dist/_chunks/{ValidationRulesTab-BoqhoTjo.js → ValidationRulesTab-BLZWPGrZ.js} +3 -2
- package/dist/_chunks/{ValidationRulesTab-BoqhoTjo.js.map → ValidationRulesTab-BLZWPGrZ.js.map} +1 -1
- package/dist/_chunks/{ValidationRulesTab-BGcqM9cN.mjs → ValidationRulesTab-Dt53h-Ke.mjs} +3 -2
- package/dist/_chunks/{ValidationRulesTab-BGcqM9cN.mjs.map → ValidationRulesTab-Dt53h-Ke.mjs.map} +1 -1
- package/dist/admin/index.js +2 -2
- package/dist/admin/index.mjs +2 -2
- package/dist/server/index.js +293 -78
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +293 -78
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/_chunks/BulkUpdater-CQ9zGdat.js.map +0 -1
- package/dist/_chunks/BulkUpdater-DvCSr1vU.mjs.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -190,6 +190,22 @@ const routes = {
|
|
|
190
190
|
policies: []
|
|
191
191
|
}
|
|
192
192
|
},
|
|
193
|
+
{
|
|
194
|
+
method: "POST",
|
|
195
|
+
path: "/csv/export/:uid",
|
|
196
|
+
handler: "csv.exportCollection",
|
|
197
|
+
config: {
|
|
198
|
+
policies: []
|
|
199
|
+
}
|
|
200
|
+
},
|
|
201
|
+
{
|
|
202
|
+
method: "POST",
|
|
203
|
+
path: "/csv/export-bulk",
|
|
204
|
+
handler: "csv.exportBulk",
|
|
205
|
+
config: {
|
|
206
|
+
policies: []
|
|
207
|
+
}
|
|
208
|
+
},
|
|
193
209
|
{
|
|
194
210
|
method: "GET",
|
|
195
211
|
path: "/csv/export-violations/:ruleId",
|
|
@@ -569,20 +585,46 @@ const csvController = ({ strapi: strapi2 }) => ({
|
|
|
569
585
|
async exportCollection(ctx) {
|
|
570
586
|
try {
|
|
571
587
|
const { uid } = ctx.params;
|
|
572
|
-
const { search, attribute, filterOperator, filterQuery, limit } =
|
|
588
|
+
const { search, attribute, filterOperator, filterQuery, limit, documentIds } = {
|
|
589
|
+
...ctx.query,
|
|
590
|
+
...ctx.request?.body || {}
|
|
591
|
+
};
|
|
573
592
|
const csvService2 = strapi2.plugin("bulk-updater").service("csv");
|
|
593
|
+
let parsedDocIds;
|
|
594
|
+
if (documentIds) {
|
|
595
|
+
parsedDocIds = Array.isArray(documentIds) ? documentIds : String(documentIds).split(",").map((s) => s.trim()).filter(Boolean);
|
|
596
|
+
}
|
|
574
597
|
const result = await csvService2.exportCollectionCsv(uid, {
|
|
575
598
|
search: search ? String(search) : void 0,
|
|
576
599
|
attribute: attribute ? String(attribute) : void 0,
|
|
577
600
|
filterOperator: filterOperator ? String(filterOperator) : void 0,
|
|
578
601
|
filterQuery: filterQuery !== void 0 ? String(filterQuery) : void 0,
|
|
579
|
-
limit: limit ? Number(limit) : void 0
|
|
602
|
+
limit: limit ? Number(limit) : void 0,
|
|
603
|
+
documentIds: parsedDocIds
|
|
580
604
|
});
|
|
581
605
|
ctx.body = {
|
|
582
606
|
success: true,
|
|
583
607
|
filename: result.filename,
|
|
584
608
|
csvContent: result.csvContent,
|
|
585
|
-
count: result.count
|
|
609
|
+
count: result.count,
|
|
610
|
+
fields: result.fields
|
|
611
|
+
};
|
|
612
|
+
} catch (err) {
|
|
613
|
+
ctx.status = 500;
|
|
614
|
+
ctx.body = { success: false, error: err.message };
|
|
615
|
+
}
|
|
616
|
+
},
|
|
617
|
+
async exportBulk(ctx) {
|
|
618
|
+
try {
|
|
619
|
+
const { selectedEntries } = ctx.request.body || {};
|
|
620
|
+
const csvService2 = strapi2.plugin("bulk-updater").service("csv");
|
|
621
|
+
const result = await csvService2.exportBulkCsv(selectedEntries);
|
|
622
|
+
ctx.body = {
|
|
623
|
+
success: true,
|
|
624
|
+
filename: result.filename,
|
|
625
|
+
csvContent: result.csvContent,
|
|
626
|
+
count: result.count,
|
|
627
|
+
isMultiCollection: result.isMultiCollection
|
|
586
628
|
};
|
|
587
629
|
} catch (err) {
|
|
588
630
|
ctx.status = 500;
|
|
@@ -2581,7 +2623,11 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
2581
2623
|
function escapeCsvVal(val) {
|
|
2582
2624
|
if (val === null || val === void 0) return "";
|
|
2583
2625
|
let str2 = typeof val === "object" ? JSON.stringify(val) : String(val);
|
|
2584
|
-
|
|
2626
|
+
str2 = str2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
2627
|
+
if (/^[=+@-]$/.test(str2.charAt(0))) {
|
|
2628
|
+
str2 = `'${str2}`;
|
|
2629
|
+
}
|
|
2630
|
+
if (str2.includes(",") || str2.includes('"') || str2.includes("\n")) {
|
|
2585
2631
|
return `"${str2.replace(/"/g, '""')}"`;
|
|
2586
2632
|
}
|
|
2587
2633
|
return str2;
|
|
@@ -2625,7 +2671,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
2625
2671
|
return {
|
|
2626
2672
|
success: true,
|
|
2627
2673
|
filename,
|
|
2628
|
-
csvContent: rows.join("\n"),
|
|
2674
|
+
csvContent: "\uFEFF" + rows.join("\r\n") + "\r\n",
|
|
2629
2675
|
summary: {
|
|
2630
2676
|
batchId: batch.id,
|
|
2631
2677
|
status: batch.status,
|
|
@@ -3449,23 +3495,34 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
3449
3495
|
function escapeCsvValue(val) {
|
|
3450
3496
|
if (val === null || val === void 0) return "";
|
|
3451
3497
|
let str2 = "";
|
|
3452
|
-
if (
|
|
3498
|
+
if (val instanceof Date) {
|
|
3499
|
+
str2 = val.toISOString();
|
|
3500
|
+
} else if (typeof val === "object") {
|
|
3453
3501
|
str2 = JSON.stringify(val);
|
|
3454
3502
|
} else {
|
|
3455
3503
|
str2 = String(val);
|
|
3456
3504
|
}
|
|
3457
|
-
|
|
3505
|
+
str2 = str2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
3506
|
+
if (/^[=+@-]$/.test(str2.charAt(0))) {
|
|
3507
|
+
str2 = `'${str2}`;
|
|
3508
|
+
}
|
|
3509
|
+
if (str2.includes(",") || str2.includes('"') || str2.includes("\n")) {
|
|
3458
3510
|
return `"${str2.replace(/"/g, '""')}"`;
|
|
3459
3511
|
}
|
|
3460
3512
|
return str2;
|
|
3461
3513
|
}
|
|
3462
3514
|
function parseCsv(csvText) {
|
|
3515
|
+
if (!csvText) return [];
|
|
3516
|
+
let cleanText = csvText;
|
|
3517
|
+
if (cleanText.charCodeAt(0) === 65279) {
|
|
3518
|
+
cleanText = cleanText.slice(1);
|
|
3519
|
+
}
|
|
3463
3520
|
const lines = [];
|
|
3464
3521
|
let current = "";
|
|
3465
3522
|
let inQuotes = false;
|
|
3466
|
-
for (let i = 0; i <
|
|
3467
|
-
const char =
|
|
3468
|
-
const nextChar =
|
|
3523
|
+
for (let i = 0; i < cleanText.length; i++) {
|
|
3524
|
+
const char = cleanText[i];
|
|
3525
|
+
const nextChar = cleanText[i + 1];
|
|
3469
3526
|
if (char === '"') {
|
|
3470
3527
|
if (inQuotes && nextChar === '"') {
|
|
3471
3528
|
current += '"';
|
|
@@ -3525,6 +3582,103 @@ function parseCsv(csvText) {
|
|
|
3525
3582
|
}
|
|
3526
3583
|
return rows;
|
|
3527
3584
|
}
|
|
3585
|
+
function discoverExportFields(ct, allComponents, maxDepth = 4) {
|
|
3586
|
+
const fields2 = [{ key: "documentId", label: "documentId", type: "string" }];
|
|
3587
|
+
const excluded = ["createdBy", "updatedBy", "localizations"];
|
|
3588
|
+
function walkComponent(compUid, pathPrefix, labelPrefix, depth2) {
|
|
3589
|
+
if (depth2 >= maxDepth) return;
|
|
3590
|
+
const comp = allComponents[compUid];
|
|
3591
|
+
if (!comp?.attributes) return;
|
|
3592
|
+
for (const [subKey, subDef] of Object.entries(comp.attributes)) {
|
|
3593
|
+
if (excluded.includes(subKey)) continue;
|
|
3594
|
+
const keyPath = `${pathPrefix}.${subKey}`;
|
|
3595
|
+
const displayLabel = `${labelPrefix} > ${subKey}`;
|
|
3596
|
+
if (subDef.type === "component" && subDef.component) {
|
|
3597
|
+
if (!subDef.repeatable) {
|
|
3598
|
+
walkComponent(subDef.component, keyPath, displayLabel, depth2 + 1);
|
|
3599
|
+
} else {
|
|
3600
|
+
fields2.push({ key: keyPath, label: displayLabel, type: "component" });
|
|
3601
|
+
}
|
|
3602
|
+
} else {
|
|
3603
|
+
fields2.push({ key: keyPath, label: displayLabel, type: subDef.type });
|
|
3604
|
+
}
|
|
3605
|
+
}
|
|
3606
|
+
}
|
|
3607
|
+
for (const [attrName, attrDef] of Object.entries(ct.attributes || {})) {
|
|
3608
|
+
if (excluded.includes(attrName)) continue;
|
|
3609
|
+
if (attrDef.type === "component" && attrDef.component) {
|
|
3610
|
+
if (!attrDef.repeatable) {
|
|
3611
|
+
walkComponent(attrDef.component, attrName, attrName, 0);
|
|
3612
|
+
} else {
|
|
3613
|
+
fields2.push({ key: attrName, label: attrName, type: "component" });
|
|
3614
|
+
}
|
|
3615
|
+
} else {
|
|
3616
|
+
fields2.push({ key: attrName, label: attrName, type: attrDef.type });
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
return fields2;
|
|
3620
|
+
}
|
|
3621
|
+
function buildDeepPopulate(attributes, allComponents, depth2 = 0) {
|
|
3622
|
+
if (depth2 > 4) return true;
|
|
3623
|
+
const populate2 = {};
|
|
3624
|
+
for (const [key, def] of Object.entries(attributes || {})) {
|
|
3625
|
+
if (def.type === "component" && def.component) {
|
|
3626
|
+
const comp = allComponents[def.component];
|
|
3627
|
+
if (comp?.attributes) {
|
|
3628
|
+
populate2[key] = {
|
|
3629
|
+
populate: buildDeepPopulate(comp.attributes, allComponents, depth2 + 1)
|
|
3630
|
+
};
|
|
3631
|
+
} else {
|
|
3632
|
+
populate2[key] = true;
|
|
3633
|
+
}
|
|
3634
|
+
} else if (def.type === "dynamiczone") {
|
|
3635
|
+
populate2[key] = true;
|
|
3636
|
+
} else if (def.type === "relation" || def.type === "media") {
|
|
3637
|
+
populate2[key] = true;
|
|
3638
|
+
}
|
|
3639
|
+
}
|
|
3640
|
+
return Object.keys(populate2).length > 0 ? populate2 : true;
|
|
3641
|
+
}
|
|
3642
|
+
function getNestedExportValue(entry, path2) {
|
|
3643
|
+
if (!entry || !path2) return "";
|
|
3644
|
+
const parts = path2.split(".");
|
|
3645
|
+
let current = entry;
|
|
3646
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3647
|
+
const part = parts[i];
|
|
3648
|
+
if (current === null || current === void 0) return "";
|
|
3649
|
+
if (Array.isArray(current)) {
|
|
3650
|
+
const subPath = parts.slice(i).join(".");
|
|
3651
|
+
return current.map((item) => {
|
|
3652
|
+
if (!item) return "";
|
|
3653
|
+
if (typeof item === "object") {
|
|
3654
|
+
const val = getNestedExportValue(item, subPath);
|
|
3655
|
+
return typeof val === "object" ? JSON.stringify(val) : String(val ?? "");
|
|
3656
|
+
}
|
|
3657
|
+
return String(item);
|
|
3658
|
+
}).filter(Boolean).join("; ");
|
|
3659
|
+
}
|
|
3660
|
+
current = current[part];
|
|
3661
|
+
}
|
|
3662
|
+
if (current === null || current === void 0) return "";
|
|
3663
|
+
if (typeof current === "object" && current.url) {
|
|
3664
|
+
return current.url;
|
|
3665
|
+
}
|
|
3666
|
+
if (typeof current === "object" && !Array.isArray(current)) {
|
|
3667
|
+
if (current.documentId || current.name || current.title) {
|
|
3668
|
+
return current.name || current.title || current.documentId;
|
|
3669
|
+
}
|
|
3670
|
+
}
|
|
3671
|
+
if (Array.isArray(current)) {
|
|
3672
|
+
return current.map((item) => {
|
|
3673
|
+
if (!item) return "";
|
|
3674
|
+
if (typeof item === "object") {
|
|
3675
|
+
return item.name || item.title || item.documentId || JSON.stringify(item);
|
|
3676
|
+
}
|
|
3677
|
+
return String(item);
|
|
3678
|
+
}).filter(Boolean).join("; ");
|
|
3679
|
+
}
|
|
3680
|
+
return current;
|
|
3681
|
+
}
|
|
3528
3682
|
const csvService = ({ strapi: strapi2 }) => ({
|
|
3529
3683
|
/**
|
|
3530
3684
|
* Generates a sample template CSV with schema attribute headers and sample values
|
|
@@ -3573,9 +3727,7 @@ const csvService = ({ strapi: strapi2 }) => ({
|
|
|
3573
3727
|
}
|
|
3574
3728
|
sampleRow.push(sampleVal);
|
|
3575
3729
|
}
|
|
3576
|
-
const csvContent =
|
|
3577
|
-
${sampleRow.map(escapeCsvValue).join(",")}
|
|
3578
|
-
`;
|
|
3730
|
+
const csvContent = "\uFEFF" + headers.map(escapeCsvValue).join(",") + "\r\n" + sampleRow.map(escapeCsvValue).join(",") + "\r\n";
|
|
3579
3731
|
return {
|
|
3580
3732
|
filename: `${ct.info?.singularName || "template"}_template.csv`,
|
|
3581
3733
|
csvContent,
|
|
@@ -3583,65 +3735,60 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3583
3735
|
};
|
|
3584
3736
|
},
|
|
3585
3737
|
/**
|
|
3586
|
-
* Exports entries of a collection to CSV
|
|
3738
|
+
* Exports entries of a collection to CSV with deep nested component support,
|
|
3739
|
+
* RFC-4180 CRLF line endings, and UTF-8 BOM for 100% Microsoft Excel compatibility.
|
|
3587
3740
|
*/
|
|
3588
3741
|
async exportCollectionCsv(uid, options = {}) {
|
|
3589
3742
|
const ct = strapi2.contentTypes[uid];
|
|
3590
3743
|
if (!ct) {
|
|
3591
3744
|
throw new Error(`Content type ${uid} not found`);
|
|
3592
3745
|
}
|
|
3593
|
-
const
|
|
3594
|
-
const
|
|
3595
|
-
const exportFields = ["documentId"];
|
|
3596
|
-
const compFieldsToPopulate = [];
|
|
3597
|
-
for (const [attrName, attrDef] of Object.entries(attrs)) {
|
|
3598
|
-
if (excluded.includes(attrName)) continue;
|
|
3599
|
-
if (["dynamiczone", "media", "relation"].includes(attrDef.type)) continue;
|
|
3600
|
-
if (attrDef.type === "component") {
|
|
3601
|
-
if (!attrDef.repeatable && attrDef.component) {
|
|
3602
|
-
const comp = strapi2.components[attrDef.component];
|
|
3603
|
-
if (comp?.attributes) {
|
|
3604
|
-
compFieldsToPopulate.push(attrName);
|
|
3605
|
-
for (const [subKey, subDef] of Object.entries(comp.attributes)) {
|
|
3606
|
-
if (excluded.includes(subKey)) continue;
|
|
3607
|
-
if (["component", "dynamiczone", "media", "relation"].includes(subDef.type)) continue;
|
|
3608
|
-
exportFields.push(`${attrName}.${subKey}`);
|
|
3609
|
-
}
|
|
3610
|
-
}
|
|
3611
|
-
}
|
|
3612
|
-
continue;
|
|
3613
|
-
}
|
|
3614
|
-
exportFields.push(attrName);
|
|
3615
|
-
}
|
|
3746
|
+
const allComponents = strapi2.components || {};
|
|
3747
|
+
const fields2 = discoverExportFields(ct, allComponents);
|
|
3616
3748
|
const filters2 = {};
|
|
3617
|
-
const { attribute, filterOperator, filterQuery, search } = options;
|
|
3618
|
-
if (
|
|
3619
|
-
|
|
3620
|
-
|
|
3621
|
-
|
|
3622
|
-
|
|
3623
|
-
|
|
3624
|
-
|
|
3625
|
-
|
|
3626
|
-
|
|
3627
|
-
|
|
3628
|
-
|
|
3629
|
-
|
|
3630
|
-
|
|
3631
|
-
|
|
3632
|
-
|
|
3633
|
-
filters2[
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3749
|
+
const { attribute, filterOperator, filterQuery, search, documentIds } = options;
|
|
3750
|
+
if (documentIds && Array.isArray(documentIds) && documentIds.length > 0) {
|
|
3751
|
+
filters2.documentId = { $in: documentIds };
|
|
3752
|
+
}
|
|
3753
|
+
if (attribute && filterOperator && filterOperator !== "none") {
|
|
3754
|
+
const isNested = attribute.includes(".");
|
|
3755
|
+
if (filterOperator === "empty") {
|
|
3756
|
+
if (isNested) {
|
|
3757
|
+
const [parent, child] = attribute.split(".");
|
|
3758
|
+
filters2[parent] = { [child]: { $null: true } };
|
|
3759
|
+
} else {
|
|
3760
|
+
filters2[attribute] = { $null: true };
|
|
3761
|
+
}
|
|
3762
|
+
} else if (filterOperator === "not_empty") {
|
|
3763
|
+
if (isNested) {
|
|
3764
|
+
const [parent, child] = attribute.split(".");
|
|
3765
|
+
filters2[parent] = { [child]: { $notNull: true } };
|
|
3766
|
+
} else {
|
|
3767
|
+
filters2[attribute] = { $notNull: true };
|
|
3768
|
+
}
|
|
3769
|
+
} else if (filterQuery !== void 0 && filterQuery !== "") {
|
|
3770
|
+
const opMap = {
|
|
3771
|
+
equals: "$eq",
|
|
3772
|
+
notEquals: "$ne",
|
|
3773
|
+
contains: "$containsi",
|
|
3774
|
+
notContains: "$notContainsi",
|
|
3775
|
+
startsWith: "$startsWith",
|
|
3776
|
+
endsWith: "$endsWith"
|
|
3777
|
+
};
|
|
3778
|
+
const strapiOp = opMap[filterOperator] || "$eq";
|
|
3779
|
+
if (isNested) {
|
|
3780
|
+
const [parent, child] = attribute.split(".");
|
|
3781
|
+
filters2[parent] = { [child]: { [strapiOp]: filterQuery } };
|
|
3782
|
+
} else {
|
|
3783
|
+
filters2[attribute] = { [strapiOp]: filterQuery };
|
|
3784
|
+
}
|
|
3638
3785
|
}
|
|
3639
3786
|
}
|
|
3640
3787
|
if (search && search.trim() !== "") {
|
|
3641
3788
|
const q = search.trim();
|
|
3642
3789
|
const searchConditions = [];
|
|
3643
3790
|
const searchFields = ["name", "title", "email", "slug", "description"].filter(
|
|
3644
|
-
(f) =>
|
|
3791
|
+
(f) => ct.attributes?.[f] && ["string", "text", "email"].includes(ct.attributes[f].type)
|
|
3645
3792
|
);
|
|
3646
3793
|
for (const field of searchFields) {
|
|
3647
3794
|
searchConditions.push({ [field]: { $containsi: q } });
|
|
@@ -3654,34 +3801,37 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3654
3801
|
}
|
|
3655
3802
|
}
|
|
3656
3803
|
}
|
|
3657
|
-
const
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3804
|
+
const deepPopulate = buildDeepPopulate(ct.attributes || {}, allComponents);
|
|
3805
|
+
let entries = [];
|
|
3806
|
+
if (ct.kind === "singleType") {
|
|
3807
|
+
const doc = await strapi2.documents(uid).findFirst({
|
|
3808
|
+
populate: deepPopulate
|
|
3809
|
+
});
|
|
3810
|
+
entries = doc ? [doc] : [];
|
|
3811
|
+
} else {
|
|
3812
|
+
entries = await strapi2.documents(uid).findMany({
|
|
3813
|
+
filters: Object.keys(filters2).length > 0 ? filters2 : void 0,
|
|
3814
|
+
limit: options.limit || 5e3,
|
|
3815
|
+
sort: { createdAt: "desc" },
|
|
3816
|
+
populate: deepPopulate
|
|
3817
|
+
});
|
|
3818
|
+
}
|
|
3664
3819
|
const lines = [];
|
|
3665
|
-
lines.push(
|
|
3820
|
+
lines.push(fields2.map((f) => escapeCsvValue(f.key)).join(","));
|
|
3666
3821
|
for (const entry of entries || []) {
|
|
3667
|
-
const rowValues =
|
|
3668
|
-
|
|
3669
|
-
if (field.includes(".")) {
|
|
3670
|
-
const [parent, child] = field.split(".");
|
|
3671
|
-
val = entry[parent]?.[child];
|
|
3672
|
-
} else {
|
|
3673
|
-
val = entry[field];
|
|
3674
|
-
}
|
|
3822
|
+
const rowValues = fields2.map((f) => {
|
|
3823
|
+
const val = getNestedExportValue(entry, f.key);
|
|
3675
3824
|
return escapeCsvValue(val);
|
|
3676
3825
|
});
|
|
3677
3826
|
lines.push(rowValues.join(","));
|
|
3678
3827
|
}
|
|
3679
|
-
const csvContent = lines.join("\n");
|
|
3828
|
+
const csvContent = "\uFEFF" + lines.join("\r\n") + "\r\n";
|
|
3680
3829
|
const filename = `${ct.info?.singularName || uid}_export_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
3681
3830
|
return {
|
|
3682
3831
|
filename,
|
|
3683
3832
|
csvContent,
|
|
3684
|
-
count: entries ? entries.length : 0
|
|
3833
|
+
count: entries ? entries.length : 0,
|
|
3834
|
+
fields: fields2.map((f) => f.key)
|
|
3685
3835
|
};
|
|
3686
3836
|
},
|
|
3687
3837
|
/**
|
|
@@ -3721,11 +3871,76 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3721
3871
|
const filename = `violations_${safeRuleName}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
3722
3872
|
return {
|
|
3723
3873
|
filename,
|
|
3724
|
-
csvContent: lines.join("\n"),
|
|
3874
|
+
csvContent: "\uFEFF" + lines.join("\r\n") + "\r\n",
|
|
3725
3875
|
count: scanReport.violations.length,
|
|
3726
3876
|
ruleName: scanReport.rule.name
|
|
3727
3877
|
};
|
|
3728
3878
|
},
|
|
3879
|
+
/**
|
|
3880
|
+
* Bulk Export: Exports selected entries across multiple collections or a multi-collection batch.
|
|
3881
|
+
*/
|
|
3882
|
+
async exportBulkCsv(selectedEntries) {
|
|
3883
|
+
if (!selectedEntries || selectedEntries.length === 0) {
|
|
3884
|
+
throw new Error("No entries selected for bulk export");
|
|
3885
|
+
}
|
|
3886
|
+
const byUid = {};
|
|
3887
|
+
for (const item of selectedEntries) {
|
|
3888
|
+
if (!byUid[item.uid]) byUid[item.uid] = [];
|
|
3889
|
+
byUid[item.uid].push(item.documentId);
|
|
3890
|
+
}
|
|
3891
|
+
const uids = Object.keys(byUid);
|
|
3892
|
+
if (uids.length === 1) {
|
|
3893
|
+
const uid = uids[0];
|
|
3894
|
+
return await this.exportCollectionCsv(uid, { documentIds: byUid[uid] });
|
|
3895
|
+
}
|
|
3896
|
+
const rows = [];
|
|
3897
|
+
const headers = [
|
|
3898
|
+
"Collection UID",
|
|
3899
|
+
"Collection Name",
|
|
3900
|
+
"Document ID",
|
|
3901
|
+
"Record Title / Name",
|
|
3902
|
+
"Primary Identifier",
|
|
3903
|
+
"Created At",
|
|
3904
|
+
"Updated At",
|
|
3905
|
+
"Published Status"
|
|
3906
|
+
];
|
|
3907
|
+
rows.push(headers.map(escapeCsvValue).join(","));
|
|
3908
|
+
let totalExported = 0;
|
|
3909
|
+
for (const uid of uids) {
|
|
3910
|
+
const ct = strapi2.contentTypes[uid];
|
|
3911
|
+
if (!ct) continue;
|
|
3912
|
+
const displayName = ct.info?.displayName || uid;
|
|
3913
|
+
const docIds = byUid[uid];
|
|
3914
|
+
const entries = await strapi2.documents(uid).findMany({
|
|
3915
|
+
filters: { documentId: { $in: docIds } },
|
|
3916
|
+
limit: docIds.length
|
|
3917
|
+
});
|
|
3918
|
+
for (const entry of entries || []) {
|
|
3919
|
+
const titleOrName = entry.title || entry.name || entry.subject || entry.email || `Entry #${entry.documentId}`;
|
|
3920
|
+
const identifier = entry.slug || entry.email || entry.username || entry.documentId;
|
|
3921
|
+
const isPublished = Boolean(entry.publishedAt);
|
|
3922
|
+
const row = [
|
|
3923
|
+
uid,
|
|
3924
|
+
displayName,
|
|
3925
|
+
entry.documentId,
|
|
3926
|
+
titleOrName,
|
|
3927
|
+
identifier,
|
|
3928
|
+
entry.createdAt || "",
|
|
3929
|
+
entry.updatedAt || "",
|
|
3930
|
+
isPublished ? "Published" : "Draft"
|
|
3931
|
+
];
|
|
3932
|
+
rows.push(row.map(escapeCsvValue).join(","));
|
|
3933
|
+
totalExported++;
|
|
3934
|
+
}
|
|
3935
|
+
}
|
|
3936
|
+
const filename = `bulk_export_selected_${totalExported}_entries_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
3937
|
+
return {
|
|
3938
|
+
filename,
|
|
3939
|
+
csvContent: "\uFEFF" + rows.join("\r\n") + "\r\n",
|
|
3940
|
+
count: totalExported,
|
|
3941
|
+
isMultiCollection: true
|
|
3942
|
+
};
|
|
3943
|
+
},
|
|
3729
3944
|
/**
|
|
3730
3945
|
* Imports CSV records to update existing records or insert/override records
|
|
3731
3946
|
*/
|