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.js
CHANGED
|
@@ -203,6 +203,22 @@ const routes = {
|
|
|
203
203
|
policies: []
|
|
204
204
|
}
|
|
205
205
|
},
|
|
206
|
+
{
|
|
207
|
+
method: "POST",
|
|
208
|
+
path: "/csv/export/:uid",
|
|
209
|
+
handler: "csv.exportCollection",
|
|
210
|
+
config: {
|
|
211
|
+
policies: []
|
|
212
|
+
}
|
|
213
|
+
},
|
|
214
|
+
{
|
|
215
|
+
method: "POST",
|
|
216
|
+
path: "/csv/export-bulk",
|
|
217
|
+
handler: "csv.exportBulk",
|
|
218
|
+
config: {
|
|
219
|
+
policies: []
|
|
220
|
+
}
|
|
221
|
+
},
|
|
206
222
|
{
|
|
207
223
|
method: "GET",
|
|
208
224
|
path: "/csv/export-violations/:ruleId",
|
|
@@ -582,20 +598,46 @@ const csvController = ({ strapi: strapi2 }) => ({
|
|
|
582
598
|
async exportCollection(ctx) {
|
|
583
599
|
try {
|
|
584
600
|
const { uid } = ctx.params;
|
|
585
|
-
const { search, attribute, filterOperator, filterQuery, limit } =
|
|
601
|
+
const { search, attribute, filterOperator, filterQuery, limit, documentIds } = {
|
|
602
|
+
...ctx.query,
|
|
603
|
+
...ctx.request?.body || {}
|
|
604
|
+
};
|
|
586
605
|
const csvService2 = strapi2.plugin("bulk-updater").service("csv");
|
|
606
|
+
let parsedDocIds;
|
|
607
|
+
if (documentIds) {
|
|
608
|
+
parsedDocIds = Array.isArray(documentIds) ? documentIds : String(documentIds).split(",").map((s) => s.trim()).filter(Boolean);
|
|
609
|
+
}
|
|
587
610
|
const result = await csvService2.exportCollectionCsv(uid, {
|
|
588
611
|
search: search ? String(search) : void 0,
|
|
589
612
|
attribute: attribute ? String(attribute) : void 0,
|
|
590
613
|
filterOperator: filterOperator ? String(filterOperator) : void 0,
|
|
591
614
|
filterQuery: filterQuery !== void 0 ? String(filterQuery) : void 0,
|
|
592
|
-
limit: limit ? Number(limit) : void 0
|
|
615
|
+
limit: limit ? Number(limit) : void 0,
|
|
616
|
+
documentIds: parsedDocIds
|
|
593
617
|
});
|
|
594
618
|
ctx.body = {
|
|
595
619
|
success: true,
|
|
596
620
|
filename: result.filename,
|
|
597
621
|
csvContent: result.csvContent,
|
|
598
|
-
count: result.count
|
|
622
|
+
count: result.count,
|
|
623
|
+
fields: result.fields
|
|
624
|
+
};
|
|
625
|
+
} catch (err) {
|
|
626
|
+
ctx.status = 500;
|
|
627
|
+
ctx.body = { success: false, error: err.message };
|
|
628
|
+
}
|
|
629
|
+
},
|
|
630
|
+
async exportBulk(ctx) {
|
|
631
|
+
try {
|
|
632
|
+
const { selectedEntries } = ctx.request.body || {};
|
|
633
|
+
const csvService2 = strapi2.plugin("bulk-updater").service("csv");
|
|
634
|
+
const result = await csvService2.exportBulkCsv(selectedEntries);
|
|
635
|
+
ctx.body = {
|
|
636
|
+
success: true,
|
|
637
|
+
filename: result.filename,
|
|
638
|
+
csvContent: result.csvContent,
|
|
639
|
+
count: result.count,
|
|
640
|
+
isMultiCollection: result.isMultiCollection
|
|
599
641
|
};
|
|
600
642
|
} catch (err) {
|
|
601
643
|
ctx.status = 500;
|
|
@@ -2594,7 +2636,11 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
2594
2636
|
function escapeCsvVal(val) {
|
|
2595
2637
|
if (val === null || val === void 0) return "";
|
|
2596
2638
|
let str2 = typeof val === "object" ? JSON.stringify(val) : String(val);
|
|
2597
|
-
|
|
2639
|
+
str2 = str2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
2640
|
+
if (/^[=+@-]$/.test(str2.charAt(0))) {
|
|
2641
|
+
str2 = `'${str2}`;
|
|
2642
|
+
}
|
|
2643
|
+
if (str2.includes(",") || str2.includes('"') || str2.includes("\n")) {
|
|
2598
2644
|
return `"${str2.replace(/"/g, '""')}"`;
|
|
2599
2645
|
}
|
|
2600
2646
|
return str2;
|
|
@@ -2638,7 +2684,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
2638
2684
|
return {
|
|
2639
2685
|
success: true,
|
|
2640
2686
|
filename,
|
|
2641
|
-
csvContent: rows.join("\n"),
|
|
2687
|
+
csvContent: "\uFEFF" + rows.join("\r\n") + "\r\n",
|
|
2642
2688
|
summary: {
|
|
2643
2689
|
batchId: batch.id,
|
|
2644
2690
|
status: batch.status,
|
|
@@ -3462,23 +3508,34 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
3462
3508
|
function escapeCsvValue(val) {
|
|
3463
3509
|
if (val === null || val === void 0) return "";
|
|
3464
3510
|
let str2 = "";
|
|
3465
|
-
if (
|
|
3511
|
+
if (val instanceof Date) {
|
|
3512
|
+
str2 = val.toISOString();
|
|
3513
|
+
} else if (typeof val === "object") {
|
|
3466
3514
|
str2 = JSON.stringify(val);
|
|
3467
3515
|
} else {
|
|
3468
3516
|
str2 = String(val);
|
|
3469
3517
|
}
|
|
3470
|
-
|
|
3518
|
+
str2 = str2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
3519
|
+
if (/^[=+@-]$/.test(str2.charAt(0))) {
|
|
3520
|
+
str2 = `'${str2}`;
|
|
3521
|
+
}
|
|
3522
|
+
if (str2.includes(",") || str2.includes('"') || str2.includes("\n")) {
|
|
3471
3523
|
return `"${str2.replace(/"/g, '""')}"`;
|
|
3472
3524
|
}
|
|
3473
3525
|
return str2;
|
|
3474
3526
|
}
|
|
3475
3527
|
function parseCsv(csvText) {
|
|
3528
|
+
if (!csvText) return [];
|
|
3529
|
+
let cleanText = csvText;
|
|
3530
|
+
if (cleanText.charCodeAt(0) === 65279) {
|
|
3531
|
+
cleanText = cleanText.slice(1);
|
|
3532
|
+
}
|
|
3476
3533
|
const lines = [];
|
|
3477
3534
|
let current = "";
|
|
3478
3535
|
let inQuotes = false;
|
|
3479
|
-
for (let i = 0; i <
|
|
3480
|
-
const char =
|
|
3481
|
-
const nextChar =
|
|
3536
|
+
for (let i = 0; i < cleanText.length; i++) {
|
|
3537
|
+
const char = cleanText[i];
|
|
3538
|
+
const nextChar = cleanText[i + 1];
|
|
3482
3539
|
if (char === '"') {
|
|
3483
3540
|
if (inQuotes && nextChar === '"') {
|
|
3484
3541
|
current += '"';
|
|
@@ -3538,6 +3595,103 @@ function parseCsv(csvText) {
|
|
|
3538
3595
|
}
|
|
3539
3596
|
return rows;
|
|
3540
3597
|
}
|
|
3598
|
+
function discoverExportFields(ct, allComponents, maxDepth = 4) {
|
|
3599
|
+
const fields2 = [{ key: "documentId", label: "documentId", type: "string" }];
|
|
3600
|
+
const excluded = ["createdBy", "updatedBy", "localizations"];
|
|
3601
|
+
function walkComponent(compUid, pathPrefix, labelPrefix, depth2) {
|
|
3602
|
+
if (depth2 >= maxDepth) return;
|
|
3603
|
+
const comp = allComponents[compUid];
|
|
3604
|
+
if (!comp?.attributes) return;
|
|
3605
|
+
for (const [subKey, subDef] of Object.entries(comp.attributes)) {
|
|
3606
|
+
if (excluded.includes(subKey)) continue;
|
|
3607
|
+
const keyPath = `${pathPrefix}.${subKey}`;
|
|
3608
|
+
const displayLabel = `${labelPrefix} > ${subKey}`;
|
|
3609
|
+
if (subDef.type === "component" && subDef.component) {
|
|
3610
|
+
if (!subDef.repeatable) {
|
|
3611
|
+
walkComponent(subDef.component, keyPath, displayLabel, depth2 + 1);
|
|
3612
|
+
} else {
|
|
3613
|
+
fields2.push({ key: keyPath, label: displayLabel, type: "component" });
|
|
3614
|
+
}
|
|
3615
|
+
} else {
|
|
3616
|
+
fields2.push({ key: keyPath, label: displayLabel, type: subDef.type });
|
|
3617
|
+
}
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3620
|
+
for (const [attrName, attrDef] of Object.entries(ct.attributes || {})) {
|
|
3621
|
+
if (excluded.includes(attrName)) continue;
|
|
3622
|
+
if (attrDef.type === "component" && attrDef.component) {
|
|
3623
|
+
if (!attrDef.repeatable) {
|
|
3624
|
+
walkComponent(attrDef.component, attrName, attrName, 0);
|
|
3625
|
+
} else {
|
|
3626
|
+
fields2.push({ key: attrName, label: attrName, type: "component" });
|
|
3627
|
+
}
|
|
3628
|
+
} else {
|
|
3629
|
+
fields2.push({ key: attrName, label: attrName, type: attrDef.type });
|
|
3630
|
+
}
|
|
3631
|
+
}
|
|
3632
|
+
return fields2;
|
|
3633
|
+
}
|
|
3634
|
+
function buildDeepPopulate(attributes, allComponents, depth2 = 0) {
|
|
3635
|
+
if (depth2 > 4) return true;
|
|
3636
|
+
const populate2 = {};
|
|
3637
|
+
for (const [key, def] of Object.entries(attributes || {})) {
|
|
3638
|
+
if (def.type === "component" && def.component) {
|
|
3639
|
+
const comp = allComponents[def.component];
|
|
3640
|
+
if (comp?.attributes) {
|
|
3641
|
+
populate2[key] = {
|
|
3642
|
+
populate: buildDeepPopulate(comp.attributes, allComponents, depth2 + 1)
|
|
3643
|
+
};
|
|
3644
|
+
} else {
|
|
3645
|
+
populate2[key] = true;
|
|
3646
|
+
}
|
|
3647
|
+
} else if (def.type === "dynamiczone") {
|
|
3648
|
+
populate2[key] = true;
|
|
3649
|
+
} else if (def.type === "relation" || def.type === "media") {
|
|
3650
|
+
populate2[key] = true;
|
|
3651
|
+
}
|
|
3652
|
+
}
|
|
3653
|
+
return Object.keys(populate2).length > 0 ? populate2 : true;
|
|
3654
|
+
}
|
|
3655
|
+
function getNestedExportValue(entry, path2) {
|
|
3656
|
+
if (!entry || !path2) return "";
|
|
3657
|
+
const parts = path2.split(".");
|
|
3658
|
+
let current = entry;
|
|
3659
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3660
|
+
const part = parts[i];
|
|
3661
|
+
if (current === null || current === void 0) return "";
|
|
3662
|
+
if (Array.isArray(current)) {
|
|
3663
|
+
const subPath = parts.slice(i).join(".");
|
|
3664
|
+
return current.map((item) => {
|
|
3665
|
+
if (!item) return "";
|
|
3666
|
+
if (typeof item === "object") {
|
|
3667
|
+
const val = getNestedExportValue(item, subPath);
|
|
3668
|
+
return typeof val === "object" ? JSON.stringify(val) : String(val ?? "");
|
|
3669
|
+
}
|
|
3670
|
+
return String(item);
|
|
3671
|
+
}).filter(Boolean).join("; ");
|
|
3672
|
+
}
|
|
3673
|
+
current = current[part];
|
|
3674
|
+
}
|
|
3675
|
+
if (current === null || current === void 0) return "";
|
|
3676
|
+
if (typeof current === "object" && current.url) {
|
|
3677
|
+
return current.url;
|
|
3678
|
+
}
|
|
3679
|
+
if (typeof current === "object" && !Array.isArray(current)) {
|
|
3680
|
+
if (current.documentId || current.name || current.title) {
|
|
3681
|
+
return current.name || current.title || current.documentId;
|
|
3682
|
+
}
|
|
3683
|
+
}
|
|
3684
|
+
if (Array.isArray(current)) {
|
|
3685
|
+
return current.map((item) => {
|
|
3686
|
+
if (!item) return "";
|
|
3687
|
+
if (typeof item === "object") {
|
|
3688
|
+
return item.name || item.title || item.documentId || JSON.stringify(item);
|
|
3689
|
+
}
|
|
3690
|
+
return String(item);
|
|
3691
|
+
}).filter(Boolean).join("; ");
|
|
3692
|
+
}
|
|
3693
|
+
return current;
|
|
3694
|
+
}
|
|
3541
3695
|
const csvService = ({ strapi: strapi2 }) => ({
|
|
3542
3696
|
/**
|
|
3543
3697
|
* Generates a sample template CSV with schema attribute headers and sample values
|
|
@@ -3586,9 +3740,7 @@ const csvService = ({ strapi: strapi2 }) => ({
|
|
|
3586
3740
|
}
|
|
3587
3741
|
sampleRow.push(sampleVal);
|
|
3588
3742
|
}
|
|
3589
|
-
const csvContent =
|
|
3590
|
-
${sampleRow.map(escapeCsvValue).join(",")}
|
|
3591
|
-
`;
|
|
3743
|
+
const csvContent = "\uFEFF" + headers.map(escapeCsvValue).join(",") + "\r\n" + sampleRow.map(escapeCsvValue).join(",") + "\r\n";
|
|
3592
3744
|
return {
|
|
3593
3745
|
filename: `${ct.info?.singularName || "template"}_template.csv`,
|
|
3594
3746
|
csvContent,
|
|
@@ -3596,65 +3748,60 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3596
3748
|
};
|
|
3597
3749
|
},
|
|
3598
3750
|
/**
|
|
3599
|
-
* Exports entries of a collection to CSV
|
|
3751
|
+
* Exports entries of a collection to CSV with deep nested component support,
|
|
3752
|
+
* RFC-4180 CRLF line endings, and UTF-8 BOM for 100% Microsoft Excel compatibility.
|
|
3600
3753
|
*/
|
|
3601
3754
|
async exportCollectionCsv(uid, options = {}) {
|
|
3602
3755
|
const ct = strapi2.contentTypes[uid];
|
|
3603
3756
|
if (!ct) {
|
|
3604
3757
|
throw new Error(`Content type ${uid} not found`);
|
|
3605
3758
|
}
|
|
3606
|
-
const
|
|
3607
|
-
const
|
|
3608
|
-
const exportFields = ["documentId"];
|
|
3609
|
-
const compFieldsToPopulate = [];
|
|
3610
|
-
for (const [attrName, attrDef] of Object.entries(attrs)) {
|
|
3611
|
-
if (excluded.includes(attrName)) continue;
|
|
3612
|
-
if (["dynamiczone", "media", "relation"].includes(attrDef.type)) continue;
|
|
3613
|
-
if (attrDef.type === "component") {
|
|
3614
|
-
if (!attrDef.repeatable && attrDef.component) {
|
|
3615
|
-
const comp = strapi2.components[attrDef.component];
|
|
3616
|
-
if (comp?.attributes) {
|
|
3617
|
-
compFieldsToPopulate.push(attrName);
|
|
3618
|
-
for (const [subKey, subDef] of Object.entries(comp.attributes)) {
|
|
3619
|
-
if (excluded.includes(subKey)) continue;
|
|
3620
|
-
if (["component", "dynamiczone", "media", "relation"].includes(subDef.type)) continue;
|
|
3621
|
-
exportFields.push(`${attrName}.${subKey}`);
|
|
3622
|
-
}
|
|
3623
|
-
}
|
|
3624
|
-
}
|
|
3625
|
-
continue;
|
|
3626
|
-
}
|
|
3627
|
-
exportFields.push(attrName);
|
|
3628
|
-
}
|
|
3759
|
+
const allComponents = strapi2.components || {};
|
|
3760
|
+
const fields2 = discoverExportFields(ct, allComponents);
|
|
3629
3761
|
const filters2 = {};
|
|
3630
|
-
const { attribute, filterOperator, filterQuery, search } = options;
|
|
3631
|
-
if (
|
|
3632
|
-
|
|
3633
|
-
|
|
3634
|
-
|
|
3635
|
-
|
|
3636
|
-
|
|
3637
|
-
|
|
3638
|
-
|
|
3639
|
-
|
|
3640
|
-
|
|
3641
|
-
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
filters2[
|
|
3647
|
-
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3762
|
+
const { attribute, filterOperator, filterQuery, search, documentIds } = options;
|
|
3763
|
+
if (documentIds && Array.isArray(documentIds) && documentIds.length > 0) {
|
|
3764
|
+
filters2.documentId = { $in: documentIds };
|
|
3765
|
+
}
|
|
3766
|
+
if (attribute && filterOperator && filterOperator !== "none") {
|
|
3767
|
+
const isNested = attribute.includes(".");
|
|
3768
|
+
if (filterOperator === "empty") {
|
|
3769
|
+
if (isNested) {
|
|
3770
|
+
const [parent, child] = attribute.split(".");
|
|
3771
|
+
filters2[parent] = { [child]: { $null: true } };
|
|
3772
|
+
} else {
|
|
3773
|
+
filters2[attribute] = { $null: true };
|
|
3774
|
+
}
|
|
3775
|
+
} else if (filterOperator === "not_empty") {
|
|
3776
|
+
if (isNested) {
|
|
3777
|
+
const [parent, child] = attribute.split(".");
|
|
3778
|
+
filters2[parent] = { [child]: { $notNull: true } };
|
|
3779
|
+
} else {
|
|
3780
|
+
filters2[attribute] = { $notNull: true };
|
|
3781
|
+
}
|
|
3782
|
+
} else if (filterQuery !== void 0 && filterQuery !== "") {
|
|
3783
|
+
const opMap = {
|
|
3784
|
+
equals: "$eq",
|
|
3785
|
+
notEquals: "$ne",
|
|
3786
|
+
contains: "$containsi",
|
|
3787
|
+
notContains: "$notContainsi",
|
|
3788
|
+
startsWith: "$startsWith",
|
|
3789
|
+
endsWith: "$endsWith"
|
|
3790
|
+
};
|
|
3791
|
+
const strapiOp = opMap[filterOperator] || "$eq";
|
|
3792
|
+
if (isNested) {
|
|
3793
|
+
const [parent, child] = attribute.split(".");
|
|
3794
|
+
filters2[parent] = { [child]: { [strapiOp]: filterQuery } };
|
|
3795
|
+
} else {
|
|
3796
|
+
filters2[attribute] = { [strapiOp]: filterQuery };
|
|
3797
|
+
}
|
|
3651
3798
|
}
|
|
3652
3799
|
}
|
|
3653
3800
|
if (search && search.trim() !== "") {
|
|
3654
3801
|
const q = search.trim();
|
|
3655
3802
|
const searchConditions = [];
|
|
3656
3803
|
const searchFields = ["name", "title", "email", "slug", "description"].filter(
|
|
3657
|
-
(f) =>
|
|
3804
|
+
(f) => ct.attributes?.[f] && ["string", "text", "email"].includes(ct.attributes[f].type)
|
|
3658
3805
|
);
|
|
3659
3806
|
for (const field of searchFields) {
|
|
3660
3807
|
searchConditions.push({ [field]: { $containsi: q } });
|
|
@@ -3667,34 +3814,37 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3667
3814
|
}
|
|
3668
3815
|
}
|
|
3669
3816
|
}
|
|
3670
|
-
const
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3817
|
+
const deepPopulate = buildDeepPopulate(ct.attributes || {}, allComponents);
|
|
3818
|
+
let entries = [];
|
|
3819
|
+
if (ct.kind === "singleType") {
|
|
3820
|
+
const doc = await strapi2.documents(uid).findFirst({
|
|
3821
|
+
populate: deepPopulate
|
|
3822
|
+
});
|
|
3823
|
+
entries = doc ? [doc] : [];
|
|
3824
|
+
} else {
|
|
3825
|
+
entries = await strapi2.documents(uid).findMany({
|
|
3826
|
+
filters: Object.keys(filters2).length > 0 ? filters2 : void 0,
|
|
3827
|
+
limit: options.limit || 5e3,
|
|
3828
|
+
sort: { createdAt: "desc" },
|
|
3829
|
+
populate: deepPopulate
|
|
3830
|
+
});
|
|
3831
|
+
}
|
|
3677
3832
|
const lines = [];
|
|
3678
|
-
lines.push(
|
|
3833
|
+
lines.push(fields2.map((f) => escapeCsvValue(f.key)).join(","));
|
|
3679
3834
|
for (const entry of entries || []) {
|
|
3680
|
-
const rowValues =
|
|
3681
|
-
|
|
3682
|
-
if (field.includes(".")) {
|
|
3683
|
-
const [parent, child] = field.split(".");
|
|
3684
|
-
val = entry[parent]?.[child];
|
|
3685
|
-
} else {
|
|
3686
|
-
val = entry[field];
|
|
3687
|
-
}
|
|
3835
|
+
const rowValues = fields2.map((f) => {
|
|
3836
|
+
const val = getNestedExportValue(entry, f.key);
|
|
3688
3837
|
return escapeCsvValue(val);
|
|
3689
3838
|
});
|
|
3690
3839
|
lines.push(rowValues.join(","));
|
|
3691
3840
|
}
|
|
3692
|
-
const csvContent = lines.join("\n");
|
|
3841
|
+
const csvContent = "\uFEFF" + lines.join("\r\n") + "\r\n";
|
|
3693
3842
|
const filename = `${ct.info?.singularName || uid}_export_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
3694
3843
|
return {
|
|
3695
3844
|
filename,
|
|
3696
3845
|
csvContent,
|
|
3697
|
-
count: entries ? entries.length : 0
|
|
3846
|
+
count: entries ? entries.length : 0,
|
|
3847
|
+
fields: fields2.map((f) => f.key)
|
|
3698
3848
|
};
|
|
3699
3849
|
},
|
|
3700
3850
|
/**
|
|
@@ -3734,11 +3884,76 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3734
3884
|
const filename = `violations_${safeRuleName}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
3735
3885
|
return {
|
|
3736
3886
|
filename,
|
|
3737
|
-
csvContent: lines.join("\n"),
|
|
3887
|
+
csvContent: "\uFEFF" + lines.join("\r\n") + "\r\n",
|
|
3738
3888
|
count: scanReport.violations.length,
|
|
3739
3889
|
ruleName: scanReport.rule.name
|
|
3740
3890
|
};
|
|
3741
3891
|
},
|
|
3892
|
+
/**
|
|
3893
|
+
* Bulk Export: Exports selected entries across multiple collections or a multi-collection batch.
|
|
3894
|
+
*/
|
|
3895
|
+
async exportBulkCsv(selectedEntries) {
|
|
3896
|
+
if (!selectedEntries || selectedEntries.length === 0) {
|
|
3897
|
+
throw new Error("No entries selected for bulk export");
|
|
3898
|
+
}
|
|
3899
|
+
const byUid = {};
|
|
3900
|
+
for (const item of selectedEntries) {
|
|
3901
|
+
if (!byUid[item.uid]) byUid[item.uid] = [];
|
|
3902
|
+
byUid[item.uid].push(item.documentId);
|
|
3903
|
+
}
|
|
3904
|
+
const uids = Object.keys(byUid);
|
|
3905
|
+
if (uids.length === 1) {
|
|
3906
|
+
const uid = uids[0];
|
|
3907
|
+
return await this.exportCollectionCsv(uid, { documentIds: byUid[uid] });
|
|
3908
|
+
}
|
|
3909
|
+
const rows = [];
|
|
3910
|
+
const headers = [
|
|
3911
|
+
"Collection UID",
|
|
3912
|
+
"Collection Name",
|
|
3913
|
+
"Document ID",
|
|
3914
|
+
"Record Title / Name",
|
|
3915
|
+
"Primary Identifier",
|
|
3916
|
+
"Created At",
|
|
3917
|
+
"Updated At",
|
|
3918
|
+
"Published Status"
|
|
3919
|
+
];
|
|
3920
|
+
rows.push(headers.map(escapeCsvValue).join(","));
|
|
3921
|
+
let totalExported = 0;
|
|
3922
|
+
for (const uid of uids) {
|
|
3923
|
+
const ct = strapi2.contentTypes[uid];
|
|
3924
|
+
if (!ct) continue;
|
|
3925
|
+
const displayName = ct.info?.displayName || uid;
|
|
3926
|
+
const docIds = byUid[uid];
|
|
3927
|
+
const entries = await strapi2.documents(uid).findMany({
|
|
3928
|
+
filters: { documentId: { $in: docIds } },
|
|
3929
|
+
limit: docIds.length
|
|
3930
|
+
});
|
|
3931
|
+
for (const entry of entries || []) {
|
|
3932
|
+
const titleOrName = entry.title || entry.name || entry.subject || entry.email || `Entry #${entry.documentId}`;
|
|
3933
|
+
const identifier = entry.slug || entry.email || entry.username || entry.documentId;
|
|
3934
|
+
const isPublished = Boolean(entry.publishedAt);
|
|
3935
|
+
const row = [
|
|
3936
|
+
uid,
|
|
3937
|
+
displayName,
|
|
3938
|
+
entry.documentId,
|
|
3939
|
+
titleOrName,
|
|
3940
|
+
identifier,
|
|
3941
|
+
entry.createdAt || "",
|
|
3942
|
+
entry.updatedAt || "",
|
|
3943
|
+
isPublished ? "Published" : "Draft"
|
|
3944
|
+
];
|
|
3945
|
+
rows.push(row.map(escapeCsvValue).join(","));
|
|
3946
|
+
totalExported++;
|
|
3947
|
+
}
|
|
3948
|
+
}
|
|
3949
|
+
const filename = `bulk_export_selected_${totalExported}_entries_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
3950
|
+
return {
|
|
3951
|
+
filename,
|
|
3952
|
+
csvContent: "\uFEFF" + rows.join("\r\n") + "\r\n",
|
|
3953
|
+
count: totalExported,
|
|
3954
|
+
isMultiCollection: true
|
|
3955
|
+
};
|
|
3956
|
+
},
|
|
3742
3957
|
/**
|
|
3743
3958
|
* Imports CSV records to update existing records or insert/override records
|
|
3744
3959
|
*/
|