strapi-plugin-bulk-updater 1.2.12 → 1.3.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/README.md +10 -4
- package/dist/_chunks/{BulkUpdater-CQ9zGdat.js → BulkUpdater-CRvwX326.js} +344 -39
- package/dist/_chunks/BulkUpdater-CRvwX326.js.map +1 -0
- package/dist/_chunks/{BulkUpdater-DvCSr1vU.mjs → BulkUpdater-DowSmA56.mjs} +344 -39
- package/dist/_chunks/BulkUpdater-DowSmA56.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 +480 -170
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +480 -170
- package/dist/server/index.mjs.map +1 -1
- package/package.json +3 -3
- 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",
|
|
@@ -299,12 +315,13 @@ const bulkUpdaterController = ({ strapi: strapi2 }) => ({
|
|
|
299
315
|
async getCollectionEntries(ctx) {
|
|
300
316
|
try {
|
|
301
317
|
const { uid } = ctx.params;
|
|
302
|
-
const { page, pageSize, attribute, filterOperator, filterQuery, search, sortField, sortOrder } = ctx.query;
|
|
318
|
+
const { page, pageSize, attribute, searchAttribute, filterOperator, filterQuery, search, sortField, sortOrder } = ctx.query;
|
|
303
319
|
const service = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
304
320
|
const data = await service.getCollectionEntries(uid, {
|
|
305
321
|
page: page ? Number(page) : 1,
|
|
306
322
|
pageSize: pageSize ? Number(pageSize) : 10,
|
|
307
323
|
attribute: attribute ? String(attribute) : void 0,
|
|
324
|
+
searchAttribute: searchAttribute ? String(searchAttribute) : void 0,
|
|
308
325
|
filterOperator: filterOperator ? String(filterOperator) : void 0,
|
|
309
326
|
filterQuery: filterQuery !== void 0 ? String(filterQuery) : void 0,
|
|
310
327
|
search: search ? String(search) : void 0,
|
|
@@ -569,20 +586,46 @@ const csvController = ({ strapi: strapi2 }) => ({
|
|
|
569
586
|
async exportCollection(ctx) {
|
|
570
587
|
try {
|
|
571
588
|
const { uid } = ctx.params;
|
|
572
|
-
const { search, attribute, filterOperator, filterQuery, limit } =
|
|
589
|
+
const { search, attribute, filterOperator, filterQuery, limit, documentIds } = {
|
|
590
|
+
...ctx.query,
|
|
591
|
+
...ctx.request?.body || {}
|
|
592
|
+
};
|
|
573
593
|
const csvService2 = strapi2.plugin("bulk-updater").service("csv");
|
|
594
|
+
let parsedDocIds;
|
|
595
|
+
if (documentIds) {
|
|
596
|
+
parsedDocIds = Array.isArray(documentIds) ? documentIds : String(documentIds).split(",").map((s) => s.trim()).filter(Boolean);
|
|
597
|
+
}
|
|
574
598
|
const result = await csvService2.exportCollectionCsv(uid, {
|
|
575
599
|
search: search ? String(search) : void 0,
|
|
576
600
|
attribute: attribute ? String(attribute) : void 0,
|
|
577
601
|
filterOperator: filterOperator ? String(filterOperator) : void 0,
|
|
578
602
|
filterQuery: filterQuery !== void 0 ? String(filterQuery) : void 0,
|
|
579
|
-
limit: limit ? Number(limit) : void 0
|
|
603
|
+
limit: limit ? Number(limit) : void 0,
|
|
604
|
+
documentIds: parsedDocIds
|
|
580
605
|
});
|
|
581
606
|
ctx.body = {
|
|
582
607
|
success: true,
|
|
583
608
|
filename: result.filename,
|
|
584
609
|
csvContent: result.csvContent,
|
|
585
|
-
count: result.count
|
|
610
|
+
count: result.count,
|
|
611
|
+
fields: result.fields
|
|
612
|
+
};
|
|
613
|
+
} catch (err) {
|
|
614
|
+
ctx.status = 500;
|
|
615
|
+
ctx.body = { success: false, error: err.message };
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
async exportBulk(ctx) {
|
|
619
|
+
try {
|
|
620
|
+
const { selectedEntries } = ctx.request.body || {};
|
|
621
|
+
const csvService2 = strapi2.plugin("bulk-updater").service("csv");
|
|
622
|
+
const result = await csvService2.exportBulkCsv(selectedEntries);
|
|
623
|
+
ctx.body = {
|
|
624
|
+
success: true,
|
|
625
|
+
filename: result.filename,
|
|
626
|
+
csvContent: result.csvContent,
|
|
627
|
+
count: result.count,
|
|
628
|
+
isMultiCollection: result.isMultiCollection
|
|
586
629
|
};
|
|
587
630
|
} catch (err) {
|
|
588
631
|
ctx.status = 500;
|
|
@@ -637,6 +680,127 @@ const modelsCache = /* @__PURE__ */ new Map();
|
|
|
637
680
|
const MAX_QUERY_CACHE_SIZE = 500;
|
|
638
681
|
const CACHE_TTL_MS = 60 * 1e3;
|
|
639
682
|
let staticSchemaCache = null;
|
|
683
|
+
function buildDeepPopulate$1(attributes, allComponents, depth2 = 0) {
|
|
684
|
+
if (depth2 > 5) return true;
|
|
685
|
+
const populate2 = {};
|
|
686
|
+
for (const [key, def] of Object.entries(attributes || {})) {
|
|
687
|
+
if (def.type === "component" && def.component) {
|
|
688
|
+
const comp = allComponents[def.component];
|
|
689
|
+
if (comp?.attributes) {
|
|
690
|
+
populate2[key] = {
|
|
691
|
+
populate: buildDeepPopulate$1(comp.attributes, allComponents, depth2 + 1)
|
|
692
|
+
};
|
|
693
|
+
} else {
|
|
694
|
+
populate2[key] = true;
|
|
695
|
+
}
|
|
696
|
+
} else if (def.type === "dynamiczone") {
|
|
697
|
+
populate2[key] = true;
|
|
698
|
+
} else if (def.type === "relation" || def.type === "media") {
|
|
699
|
+
populate2[key] = true;
|
|
700
|
+
}
|
|
701
|
+
}
|
|
702
|
+
return Object.keys(populate2).length > 0 ? populate2 : true;
|
|
703
|
+
}
|
|
704
|
+
function discoverComponentAttributes(strapi2, compUid, prefix, currentDepth, maxDepth, systemFields, allContentTypes, relationsByAttr, allRelationsMeta, attributesMeta) {
|
|
705
|
+
if (currentDepth > maxDepth || !compUid) return [];
|
|
706
|
+
const compSchema = strapi2.components[compUid];
|
|
707
|
+
if (!compSchema?.attributes) return [];
|
|
708
|
+
const discoveredAttrs = [];
|
|
709
|
+
for (const [subKey, subAttr] of Object.entries(compSchema.attributes)) {
|
|
710
|
+
if (systemFields.includes(subKey)) continue;
|
|
711
|
+
if (subAttr.type === "dynamiczone") continue;
|
|
712
|
+
const fullPath = prefix ? `${prefix}.${subKey}` : subKey;
|
|
713
|
+
if (subAttr.type === "component") {
|
|
714
|
+
if (!subAttr.repeatable && subAttr.component) {
|
|
715
|
+
const deeper = discoverComponentAttributes(
|
|
716
|
+
strapi2,
|
|
717
|
+
subAttr.component,
|
|
718
|
+
fullPath,
|
|
719
|
+
currentDepth + 1,
|
|
720
|
+
maxDepth,
|
|
721
|
+
systemFields,
|
|
722
|
+
allContentTypes,
|
|
723
|
+
relationsByAttr,
|
|
724
|
+
allRelationsMeta,
|
|
725
|
+
attributesMeta
|
|
726
|
+
);
|
|
727
|
+
discoveredAttrs.push(...deeper);
|
|
728
|
+
}
|
|
729
|
+
} else if (subAttr.type === "relation") {
|
|
730
|
+
const targetUid = subAttr.target;
|
|
731
|
+
if (targetUid && allContentTypes[targetUid]) {
|
|
732
|
+
const targetInfo = allContentTypes[targetUid]?.info;
|
|
733
|
+
const isMulti = subAttr.relation === "oneToMany" || subAttr.relation === "manyToMany";
|
|
734
|
+
const relMeta = {
|
|
735
|
+
isRelation: true,
|
|
736
|
+
relationType: subAttr.relation,
|
|
737
|
+
targetUid,
|
|
738
|
+
targetDisplayName: targetInfo?.displayName || targetUid,
|
|
739
|
+
isMulti
|
|
740
|
+
};
|
|
741
|
+
relationsByAttr[fullPath] = relMeta;
|
|
742
|
+
allRelationsMeta[fullPath] = relMeta;
|
|
743
|
+
attributesMeta[fullPath] = { depth: currentDepth, type: "relation", component: compUid, isRelation: true };
|
|
744
|
+
discoveredAttrs.push(fullPath);
|
|
745
|
+
}
|
|
746
|
+
} else {
|
|
747
|
+
attributesMeta[fullPath] = { depth: currentDepth, type: subAttr.type, component: compUid };
|
|
748
|
+
discoveredAttrs.push(fullPath);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return discoveredAttrs;
|
|
752
|
+
}
|
|
753
|
+
function isDeepComponentField(contentType, allComponents, attribute) {
|
|
754
|
+
if (!attribute || !attribute.includes(".")) return false;
|
|
755
|
+
const parts = attribute.split(".");
|
|
756
|
+
let currentSchema = contentType;
|
|
757
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
758
|
+
const part = parts[i];
|
|
759
|
+
const attrDef = currentSchema?.attributes?.[part];
|
|
760
|
+
if (!attrDef || attrDef.type !== "component" || !attrDef.component) return false;
|
|
761
|
+
currentSchema = allComponents[attrDef.component];
|
|
762
|
+
if (!currentSchema) return false;
|
|
763
|
+
}
|
|
764
|
+
const leafField = parts[parts.length - 1];
|
|
765
|
+
return Boolean(currentSchema?.attributes?.[leafField]);
|
|
766
|
+
}
|
|
767
|
+
function getLeafAttributeSchema(contentType, allComponents, attribute) {
|
|
768
|
+
if (!attribute) return null;
|
|
769
|
+
if (!attribute.includes(".")) return contentType?.attributes?.[attribute] || null;
|
|
770
|
+
const parts = attribute.split(".");
|
|
771
|
+
let currentSchema = contentType;
|
|
772
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
773
|
+
const part = parts[i];
|
|
774
|
+
const attrDef = currentSchema?.attributes?.[part];
|
|
775
|
+
if (!attrDef || attrDef.type !== "component" || !attrDef.component) return null;
|
|
776
|
+
currentSchema = allComponents[attrDef.component];
|
|
777
|
+
if (!currentSchema) return null;
|
|
778
|
+
}
|
|
779
|
+
return currentSchema?.attributes?.[parts[parts.length - 1]] || null;
|
|
780
|
+
}
|
|
781
|
+
function setDeepProperty(obj, pathParts, value) {
|
|
782
|
+
if (pathParts.length === 0) return value;
|
|
783
|
+
const current = { ...obj || {} };
|
|
784
|
+
const [head, ...tail] = pathParts;
|
|
785
|
+
if (tail.length === 0) {
|
|
786
|
+
current[head] = value;
|
|
787
|
+
} else {
|
|
788
|
+
current[head] = setDeepProperty(current[head] || {}, tail, value);
|
|
789
|
+
}
|
|
790
|
+
return current;
|
|
791
|
+
}
|
|
792
|
+
function getDeepProperty(obj, path2) {
|
|
793
|
+
if (!obj || !path2) return void 0;
|
|
794
|
+
if (!path2.includes(".")) return obj[path2];
|
|
795
|
+
return path2.split(".").reduce((acc, part) => acc && typeof acc === "object" ? acc[part] : void 0, obj);
|
|
796
|
+
}
|
|
797
|
+
function buildDeepFilter(pathParts, condition) {
|
|
798
|
+
if (pathParts.length === 0) return condition;
|
|
799
|
+
const [head, ...tail] = pathParts;
|
|
800
|
+
return {
|
|
801
|
+
[head]: buildDeepFilter(tail, condition)
|
|
802
|
+
};
|
|
803
|
+
}
|
|
640
804
|
function getSafeTimezone(timeZone) {
|
|
641
805
|
if (!timeZone || typeof timeZone !== "string") {
|
|
642
806
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
@@ -845,43 +1009,28 @@ async function executeBatchInternal(strapi2, {
|
|
|
845
1009
|
const currentEntry = await getDocumentDeduplicated(target.uid, target.documentId);
|
|
846
1010
|
const entryLabel = currentEntry?.name || currentEntry?.title || currentEntry?.email || `Entry #${target.documentId}`;
|
|
847
1011
|
const isNested = attribute.includes(".");
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
if (isNested) {
|
|
852
|
-
const parts = attribute.split(".");
|
|
853
|
-
parentKey = parts[0];
|
|
854
|
-
childKey = parts[1];
|
|
855
|
-
const parentAttrDef = contentType?.attributes?.[parentKey];
|
|
856
|
-
if (parentAttrDef?.type === "component" && !parentAttrDef.repeatable && parentAttrDef.component) {
|
|
857
|
-
const compSchema = strapi2.components[parentAttrDef.component];
|
|
858
|
-
if (compSchema?.attributes?.[childKey]) {
|
|
859
|
-
isComponentNested = true;
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
}
|
|
1012
|
+
const isComponentNested = isNested && isDeepComponentField(contentType, strapi2.components, attribute);
|
|
1013
|
+
const parts = isNested ? attribute.split(".") : [];
|
|
1014
|
+
const rootKey = isNested ? parts[0] : "";
|
|
863
1015
|
const attrDef = contentType?.attributes?.[attribute];
|
|
864
1016
|
const hasAttribute = Boolean(attrDef) || isComponentNested;
|
|
865
1017
|
if (hasAttribute) {
|
|
866
1018
|
if (isComponentNested) {
|
|
867
1019
|
const currentEntryWithPopulate = await strapi2.documents(target.uid).findOne({
|
|
868
1020
|
documentId: target.documentId,
|
|
869
|
-
populate:
|
|
1021
|
+
populate: buildDeepPopulate$1(contentType.attributes, strapi2.components)
|
|
870
1022
|
});
|
|
871
|
-
const
|
|
872
|
-
const currentVal2 =
|
|
1023
|
+
const existingRoot = currentEntryWithPopulate?.[rootKey] || {};
|
|
1024
|
+
const currentVal2 = getDeepProperty(existingRoot, parts.slice(1).join("."));
|
|
873
1025
|
let newValue2 = value;
|
|
874
1026
|
if (regexObj && typeof currentVal2 === "string") {
|
|
875
1027
|
newValue2 = currentVal2.replace(regexObj, value);
|
|
876
1028
|
}
|
|
877
|
-
const
|
|
878
|
-
...existingParent,
|
|
879
|
-
[childKey]: newValue2
|
|
880
|
-
};
|
|
1029
|
+
const updatedRoot = setDeepProperty(existingRoot, parts.slice(1), newValue2);
|
|
881
1030
|
await strapi2.documents(target.uid).update({
|
|
882
1031
|
documentId: target.documentId,
|
|
883
1032
|
data: {
|
|
884
|
-
[
|
|
1033
|
+
[rootKey]: updatedRoot
|
|
885
1034
|
}
|
|
886
1035
|
});
|
|
887
1036
|
const isDraftAndPublishComp = Boolean(contentType?.options?.draftAndPublish);
|
|
@@ -1316,12 +1465,14 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1316
1465
|
}
|
|
1317
1466
|
const metaByUid = {};
|
|
1318
1467
|
const allRelationsMeta = {};
|
|
1468
|
+
const allAttributesMeta = {};
|
|
1319
1469
|
for (const uid of targetUids) {
|
|
1320
1470
|
const contentType = allContentTypes[uid];
|
|
1321
1471
|
if (!contentType || contentType.kind !== "collectionType" && contentType.kind !== "singleType") continue;
|
|
1322
1472
|
const isSingleType = contentType.kind === "singleType";
|
|
1323
1473
|
const rawAttrs = contentType.attributes || {};
|
|
1324
1474
|
const relationsByAttr = {};
|
|
1475
|
+
const colAttributesMeta = {};
|
|
1325
1476
|
const nestedComponentAttrs = [];
|
|
1326
1477
|
const validAttrs = Object.keys(rawAttrs).filter((attrKey) => {
|
|
1327
1478
|
if (systemFields.includes(attrKey)) return false;
|
|
@@ -1329,14 +1480,19 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1329
1480
|
if (rawAttrs[attrKey].type === "component") {
|
|
1330
1481
|
const compDef = rawAttrs[attrKey];
|
|
1331
1482
|
if (!compDef.repeatable && compDef.component) {
|
|
1332
|
-
const
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1483
|
+
const compAttrs = discoverComponentAttributes(
|
|
1484
|
+
strapi2,
|
|
1485
|
+
compDef.component,
|
|
1486
|
+
attrKey,
|
|
1487
|
+
1,
|
|
1488
|
+
5,
|
|
1489
|
+
systemFields,
|
|
1490
|
+
allContentTypes,
|
|
1491
|
+
relationsByAttr,
|
|
1492
|
+
allRelationsMeta,
|
|
1493
|
+
colAttributesMeta
|
|
1494
|
+
);
|
|
1495
|
+
nestedComponentAttrs.push(...compAttrs);
|
|
1340
1496
|
}
|
|
1341
1497
|
return false;
|
|
1342
1498
|
}
|
|
@@ -1355,12 +1511,19 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1355
1511
|
};
|
|
1356
1512
|
relationsByAttr[attrKey] = relMeta;
|
|
1357
1513
|
allRelationsMeta[attrKey] = relMeta;
|
|
1514
|
+
colAttributesMeta[attrKey] = { depth: 0, type: "relation", isRelation: true };
|
|
1358
1515
|
return true;
|
|
1359
1516
|
}
|
|
1517
|
+
colAttributesMeta[attrKey] = { depth: 0, type: rawAttrs[attrKey].type };
|
|
1360
1518
|
return true;
|
|
1361
1519
|
});
|
|
1362
1520
|
const allColAttrs = [...validAttrs, ...nestedComponentAttrs];
|
|
1363
|
-
allColAttrs.forEach((attr) =>
|
|
1521
|
+
allColAttrs.forEach((attr) => {
|
|
1522
|
+
allAttributesSet.add(attr);
|
|
1523
|
+
if (colAttributesMeta[attr]) {
|
|
1524
|
+
allAttributesMeta[attr] = colAttributesMeta[attr];
|
|
1525
|
+
}
|
|
1526
|
+
});
|
|
1364
1527
|
metaByUid[uid] = {
|
|
1365
1528
|
uid,
|
|
1366
1529
|
kind: contentType.kind,
|
|
@@ -1370,11 +1533,13 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1370
1533
|
pluralName: contentType.info?.pluralName || uid,
|
|
1371
1534
|
displayName: contentType.info?.displayName || uid,
|
|
1372
1535
|
attributes: allColAttrs,
|
|
1536
|
+
attributesMeta: colAttributesMeta,
|
|
1373
1537
|
relations: relationsByAttr
|
|
1374
1538
|
};
|
|
1375
1539
|
}
|
|
1376
1540
|
staticSchemaCache = {
|
|
1377
1541
|
attributes: Array.from(allAttributesSet),
|
|
1542
|
+
attributesMeta: allAttributesMeta,
|
|
1378
1543
|
targetUids: Object.keys(metaByUid),
|
|
1379
1544
|
metaByUid,
|
|
1380
1545
|
relationsMeta: allRelationsMeta
|
|
@@ -1405,17 +1570,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1405
1570
|
let entries = [];
|
|
1406
1571
|
let totalCount = 0;
|
|
1407
1572
|
try {
|
|
1408
|
-
const
|
|
1409
|
-
const relKeys = Object.keys(meta.relations || {});
|
|
1410
|
-
const compKeys = Object.keys(ctAttrs).filter((k) => ctAttrs[k].type === "component");
|
|
1411
|
-
const populateOptionObj = {};
|
|
1412
|
-
relKeys.forEach((k) => {
|
|
1413
|
-
populateOptionObj[k] = true;
|
|
1414
|
-
});
|
|
1415
|
-
compKeys.forEach((k) => {
|
|
1416
|
-
populateOptionObj[k] = true;
|
|
1417
|
-
});
|
|
1418
|
-
const populateOption = Object.keys(populateOptionObj).length > 0 ? populateOptionObj : void 0;
|
|
1573
|
+
const populateOption = buildDeepPopulate$1(allContentTypes[uid]?.attributes || {}, strapi2.components);
|
|
1419
1574
|
if (meta.isSingleType) {
|
|
1420
1575
|
const singleDoc = await strapi2.documents(uid).findFirst({
|
|
1421
1576
|
populate: populateOption
|
|
@@ -1474,8 +1629,10 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1474
1629
|
return {
|
|
1475
1630
|
success: true,
|
|
1476
1631
|
attributes: staticSchemaCache.attributes,
|
|
1632
|
+
attributesMeta: staticSchemaCache.attributesMeta,
|
|
1477
1633
|
collections,
|
|
1478
1634
|
relationsMeta: staticSchemaCache.relationsMeta,
|
|
1635
|
+
maxDiscoveredDepth: 5,
|
|
1479
1636
|
executionWindow: {
|
|
1480
1637
|
...executionConfig,
|
|
1481
1638
|
...windowStatus
|
|
@@ -1494,6 +1651,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1494
1651
|
page = 1,
|
|
1495
1652
|
pageSize = 10,
|
|
1496
1653
|
attribute,
|
|
1654
|
+
searchAttribute,
|
|
1497
1655
|
filterOperator,
|
|
1498
1656
|
filterQuery,
|
|
1499
1657
|
search,
|
|
@@ -1508,8 +1666,9 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1508
1666
|
const currentPage = Math.max(Number(page) || 1, 1);
|
|
1509
1667
|
const start = (currentPage - 1) * limit;
|
|
1510
1668
|
const isSingleType = contentType.kind === "singleType";
|
|
1669
|
+
const targetFilterAttr = searchAttribute || attribute;
|
|
1511
1670
|
const signature = await getCollectionSignature(strapi2, uid);
|
|
1512
|
-
const cacheKey = `${uid}:${limit}:${start}:${attribute || ""}:${filterOperator || ""}:${filterQuery || ""}:${search || ""}:${sortField || ""}:${sortOrder}`;
|
|
1671
|
+
const cacheKey = `${uid}:${limit}:${start}:${attribute || ""}:${searchAttribute || ""}:${filterOperator || ""}:${filterQuery || ""}:${search || ""}:${sortField || ""}:${sortOrder}`;
|
|
1513
1672
|
const cached = queryCache.get(cacheKey);
|
|
1514
1673
|
const etag = `W/"entry-${crypto.createHash("md5").update(`${cacheKey}:${signature}`).digest("hex")}"`;
|
|
1515
1674
|
if (cached && cached.signature === signature && Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
@@ -1524,19 +1683,15 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1524
1683
|
const attrs = contentType.attributes || {};
|
|
1525
1684
|
let colHasAttribute = false;
|
|
1526
1685
|
let isNestedAttr = false;
|
|
1527
|
-
let
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
if (compSchema?.attributes?.[childKey]) {
|
|
1535
|
-
colHasAttribute = true;
|
|
1536
|
-
isNestedAttr = true;
|
|
1537
|
-
}
|
|
1686
|
+
let parts = [];
|
|
1687
|
+
if (targetFilterAttr) {
|
|
1688
|
+
if (targetFilterAttr.includes(".")) {
|
|
1689
|
+
isNestedAttr = isDeepComponentField(contentType, strapi2.components, targetFilterAttr);
|
|
1690
|
+
if (isNestedAttr) {
|
|
1691
|
+
colHasAttribute = true;
|
|
1692
|
+
parts = targetFilterAttr.split(".");
|
|
1538
1693
|
}
|
|
1539
|
-
} else if (attrs[
|
|
1694
|
+
} else if (attrs[targetFilterAttr]) {
|
|
1540
1695
|
colHasAttribute = true;
|
|
1541
1696
|
}
|
|
1542
1697
|
}
|
|
@@ -1561,7 +1716,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1561
1716
|
}
|
|
1562
1717
|
let condition = void 0;
|
|
1563
1718
|
const q = (filterQuery || "").trim();
|
|
1564
|
-
const targetAttrSchema = isNestedAttr ? strapi2.components
|
|
1719
|
+
const targetAttrSchema = isNestedAttr ? getLeafAttributeSchema(contentType, strapi2.components, targetFilterAttr) : attrs[targetFilterAttr];
|
|
1565
1720
|
const isStringLike = ["string", "text", "email", "richtext", "uid"].includes(targetAttrSchema?.type);
|
|
1566
1721
|
if (normOp === "empty") {
|
|
1567
1722
|
condition = isStringLike ? { $or: [{ $null: true }, { $eq: "" }] } : { $null: true };
|
|
@@ -1591,19 +1746,18 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1591
1746
|
}
|
|
1592
1747
|
if (condition !== void 0) {
|
|
1593
1748
|
if (isNestedAttr) {
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
};
|
|
1749
|
+
const deepFilter = buildDeepFilter(parts, condition);
|
|
1750
|
+
Object.assign(filters2, deepFilter);
|
|
1597
1751
|
} else {
|
|
1598
1752
|
if (normOp === "empty" && isStringLike) {
|
|
1599
1753
|
filters2.$or = [
|
|
1600
|
-
{ [
|
|
1601
|
-
{ [
|
|
1754
|
+
{ [targetFilterAttr]: { $null: true } },
|
|
1755
|
+
{ [targetFilterAttr]: { $eq: "" } }
|
|
1602
1756
|
];
|
|
1603
1757
|
} else if (normOp === "notempty" && isStringLike) {
|
|
1604
|
-
filters2[
|
|
1758
|
+
filters2[targetFilterAttr] = { $notNull: true, $ne: "" };
|
|
1605
1759
|
} else {
|
|
1606
|
-
filters2[
|
|
1760
|
+
filters2[targetFilterAttr] = condition;
|
|
1607
1761
|
}
|
|
1608
1762
|
}
|
|
1609
1763
|
}
|
|
@@ -1626,11 +1780,13 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1626
1780
|
}
|
|
1627
1781
|
}
|
|
1628
1782
|
}
|
|
1783
|
+
const populateOption = buildDeepPopulate$1(contentType.attributes, strapi2.components);
|
|
1629
1784
|
if (isSingleType) {
|
|
1630
1785
|
let singleDoc = null;
|
|
1631
1786
|
try {
|
|
1632
1787
|
singleDoc = await strapi2.documents(uid).findFirst({
|
|
1633
|
-
filters: Object.keys(filters2).length > 0 ? filters2 : void 0
|
|
1788
|
+
filters: Object.keys(filters2).length > 0 ? filters2 : void 0,
|
|
1789
|
+
populate: populateOption
|
|
1634
1790
|
});
|
|
1635
1791
|
} catch {
|
|
1636
1792
|
singleDoc = null;
|
|
@@ -1649,11 +1805,11 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1649
1805
|
}
|
|
1650
1806
|
};
|
|
1651
1807
|
} else {
|
|
1652
|
-
|
|
1808
|
+
let sortClause = {};
|
|
1653
1809
|
if (sortField && sortField.includes(".")) {
|
|
1654
|
-
const
|
|
1655
|
-
if (contentType.
|
|
1656
|
-
sortClause
|
|
1810
|
+
const sortParts = sortField.split(".");
|
|
1811
|
+
if (isDeepComponentField(contentType, strapi2.components, sortField)) {
|
|
1812
|
+
sortClause = buildDeepFilter(sortParts, sortOrder);
|
|
1657
1813
|
} else {
|
|
1658
1814
|
sortClause["createdAt"] = "desc";
|
|
1659
1815
|
}
|
|
@@ -1662,10 +1818,6 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1662
1818
|
} else {
|
|
1663
1819
|
sortClause["createdAt"] = "desc";
|
|
1664
1820
|
}
|
|
1665
|
-
const populateAttrs = Object.keys(contentType.attributes || {}).filter(
|
|
1666
|
-
(k) => contentType.attributes[k].type === "relation" || contentType.attributes[k].type === "component"
|
|
1667
|
-
);
|
|
1668
|
-
const populateOption = populateAttrs.length > 0 ? populateAttrs.reduce((acc, k) => ({ ...acc, [k]: true }), {}) : void 0;
|
|
1669
1821
|
const [entries, totalEntries] = await Promise.all([
|
|
1670
1822
|
strapi2.documents(uid).findMany({
|
|
1671
1823
|
start,
|
|
@@ -1787,21 +1939,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1787
1939
|
const displayName = contentType?.info?.displayName || target.uid;
|
|
1788
1940
|
try {
|
|
1789
1941
|
const isNested = attribute.includes(".");
|
|
1790
|
-
|
|
1791
|
-
let parentKey = "";
|
|
1792
|
-
let childKey = "";
|
|
1793
|
-
if (isNested) {
|
|
1794
|
-
const parts = attribute.split(".");
|
|
1795
|
-
parentKey = parts[0];
|
|
1796
|
-
childKey = parts[1];
|
|
1797
|
-
const parentAttrDef = contentType?.attributes?.[parentKey];
|
|
1798
|
-
if (parentAttrDef?.type === "component" && !parentAttrDef.repeatable && parentAttrDef.component) {
|
|
1799
|
-
const compSchema = strapi2.components[parentAttrDef.component];
|
|
1800
|
-
if (compSchema?.attributes?.[childKey]) {
|
|
1801
|
-
isComponentNested = true;
|
|
1802
|
-
}
|
|
1803
|
-
}
|
|
1804
|
-
}
|
|
1942
|
+
const isComponentNested = isNested && isDeepComponentField(contentType, strapi2.components, attribute);
|
|
1805
1943
|
const attrDef = contentType?.attributes?.[attribute];
|
|
1806
1944
|
const hasAttribute = Boolean(attrDef) || isComponentNested;
|
|
1807
1945
|
if (!hasAttribute) {
|
|
@@ -1826,7 +1964,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1826
1964
|
if (isComponentNested) {
|
|
1827
1965
|
currentEntry = await strapi2.documents(target.uid).findOne({
|
|
1828
1966
|
documentId: target.documentId,
|
|
1829
|
-
populate:
|
|
1967
|
+
populate: buildDeepPopulate$1(contentType.attributes, strapi2.components)
|
|
1830
1968
|
});
|
|
1831
1969
|
} else if (attrDef?.type === "relation") {
|
|
1832
1970
|
currentEntry = await strapi2.documents(target.uid).findOne({
|
|
@@ -1840,8 +1978,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1840
1978
|
}
|
|
1841
1979
|
const entryLabel = currentEntry?.name || currentEntry?.title || currentEntry?.email || `Entry #${target.documentId}`;
|
|
1842
1980
|
if (isComponentNested) {
|
|
1843
|
-
const
|
|
1844
|
-
const currentVal2 = existingParent?.[childKey];
|
|
1981
|
+
const currentVal2 = getDeepProperty(currentEntry, attribute);
|
|
1845
1982
|
let newValue2 = value;
|
|
1846
1983
|
if (regexObj && typeof currentVal2 === "string") {
|
|
1847
1984
|
newValue2 = currentVal2.replace(regexObj, value);
|
|
@@ -2581,7 +2718,11 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
2581
2718
|
function escapeCsvVal(val) {
|
|
2582
2719
|
if (val === null || val === void 0) return "";
|
|
2583
2720
|
let str2 = typeof val === "object" ? JSON.stringify(val) : String(val);
|
|
2584
|
-
|
|
2721
|
+
str2 = str2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
2722
|
+
if (/^[=+@-]$/.test(str2.charAt(0))) {
|
|
2723
|
+
str2 = `'${str2}`;
|
|
2724
|
+
}
|
|
2725
|
+
if (str2.includes(",") || str2.includes('"') || str2.includes("\n")) {
|
|
2585
2726
|
return `"${str2.replace(/"/g, '""')}"`;
|
|
2586
2727
|
}
|
|
2587
2728
|
return str2;
|
|
@@ -2625,7 +2766,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
2625
2766
|
return {
|
|
2626
2767
|
success: true,
|
|
2627
2768
|
filename,
|
|
2628
|
-
csvContent: rows.join("\n"),
|
|
2769
|
+
csvContent: "\uFEFF" + rows.join("\r\n") + "\r\n",
|
|
2629
2770
|
summary: {
|
|
2630
2771
|
batchId: batch.id,
|
|
2631
2772
|
status: batch.status,
|
|
@@ -3449,23 +3590,34 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
3449
3590
|
function escapeCsvValue(val) {
|
|
3450
3591
|
if (val === null || val === void 0) return "";
|
|
3451
3592
|
let str2 = "";
|
|
3452
|
-
if (
|
|
3593
|
+
if (val instanceof Date) {
|
|
3594
|
+
str2 = val.toISOString();
|
|
3595
|
+
} else if (typeof val === "object") {
|
|
3453
3596
|
str2 = JSON.stringify(val);
|
|
3454
3597
|
} else {
|
|
3455
3598
|
str2 = String(val);
|
|
3456
3599
|
}
|
|
3457
|
-
|
|
3600
|
+
str2 = str2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
3601
|
+
if (/^[=+@-]$/.test(str2.charAt(0))) {
|
|
3602
|
+
str2 = `'${str2}`;
|
|
3603
|
+
}
|
|
3604
|
+
if (str2.includes(",") || str2.includes('"') || str2.includes("\n")) {
|
|
3458
3605
|
return `"${str2.replace(/"/g, '""')}"`;
|
|
3459
3606
|
}
|
|
3460
3607
|
return str2;
|
|
3461
3608
|
}
|
|
3462
3609
|
function parseCsv(csvText) {
|
|
3610
|
+
if (!csvText) return [];
|
|
3611
|
+
let cleanText = csvText;
|
|
3612
|
+
if (cleanText.charCodeAt(0) === 65279) {
|
|
3613
|
+
cleanText = cleanText.slice(1);
|
|
3614
|
+
}
|
|
3463
3615
|
const lines = [];
|
|
3464
3616
|
let current = "";
|
|
3465
3617
|
let inQuotes = false;
|
|
3466
|
-
for (let i = 0; i <
|
|
3467
|
-
const char =
|
|
3468
|
-
const nextChar =
|
|
3618
|
+
for (let i = 0; i < cleanText.length; i++) {
|
|
3619
|
+
const char = cleanText[i];
|
|
3620
|
+
const nextChar = cleanText[i + 1];
|
|
3469
3621
|
if (char === '"') {
|
|
3470
3622
|
if (inQuotes && nextChar === '"') {
|
|
3471
3623
|
current += '"';
|
|
@@ -3525,6 +3677,103 @@ function parseCsv(csvText) {
|
|
|
3525
3677
|
}
|
|
3526
3678
|
return rows;
|
|
3527
3679
|
}
|
|
3680
|
+
function discoverExportFields(ct, allComponents, maxDepth = 4) {
|
|
3681
|
+
const fields2 = [{ key: "documentId", label: "documentId", type: "string" }];
|
|
3682
|
+
const excluded = ["createdBy", "updatedBy", "localizations"];
|
|
3683
|
+
function walkComponent(compUid, pathPrefix, labelPrefix, depth2) {
|
|
3684
|
+
if (depth2 >= maxDepth) return;
|
|
3685
|
+
const comp = allComponents[compUid];
|
|
3686
|
+
if (!comp?.attributes) return;
|
|
3687
|
+
for (const [subKey, subDef] of Object.entries(comp.attributes)) {
|
|
3688
|
+
if (excluded.includes(subKey)) continue;
|
|
3689
|
+
const keyPath = `${pathPrefix}.${subKey}`;
|
|
3690
|
+
const displayLabel = `${labelPrefix} > ${subKey}`;
|
|
3691
|
+
if (subDef.type === "component" && subDef.component) {
|
|
3692
|
+
if (!subDef.repeatable) {
|
|
3693
|
+
walkComponent(subDef.component, keyPath, displayLabel, depth2 + 1);
|
|
3694
|
+
} else {
|
|
3695
|
+
fields2.push({ key: keyPath, label: displayLabel, type: "component" });
|
|
3696
|
+
}
|
|
3697
|
+
} else {
|
|
3698
|
+
fields2.push({ key: keyPath, label: displayLabel, type: subDef.type });
|
|
3699
|
+
}
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
for (const [attrName, attrDef] of Object.entries(ct.attributes || {})) {
|
|
3703
|
+
if (excluded.includes(attrName)) continue;
|
|
3704
|
+
if (attrDef.type === "component" && attrDef.component) {
|
|
3705
|
+
if (!attrDef.repeatable) {
|
|
3706
|
+
walkComponent(attrDef.component, attrName, attrName, 0);
|
|
3707
|
+
} else {
|
|
3708
|
+
fields2.push({ key: attrName, label: attrName, type: "component" });
|
|
3709
|
+
}
|
|
3710
|
+
} else {
|
|
3711
|
+
fields2.push({ key: attrName, label: attrName, type: attrDef.type });
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
return fields2;
|
|
3715
|
+
}
|
|
3716
|
+
function buildDeepPopulate(attributes, allComponents, depth2 = 0) {
|
|
3717
|
+
if (depth2 > 4) return true;
|
|
3718
|
+
const populate2 = {};
|
|
3719
|
+
for (const [key, def] of Object.entries(attributes || {})) {
|
|
3720
|
+
if (def.type === "component" && def.component) {
|
|
3721
|
+
const comp = allComponents[def.component];
|
|
3722
|
+
if (comp?.attributes) {
|
|
3723
|
+
populate2[key] = {
|
|
3724
|
+
populate: buildDeepPopulate(comp.attributes, allComponents, depth2 + 1)
|
|
3725
|
+
};
|
|
3726
|
+
} else {
|
|
3727
|
+
populate2[key] = true;
|
|
3728
|
+
}
|
|
3729
|
+
} else if (def.type === "dynamiczone") {
|
|
3730
|
+
populate2[key] = true;
|
|
3731
|
+
} else if (def.type === "relation" || def.type === "media") {
|
|
3732
|
+
populate2[key] = true;
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
return Object.keys(populate2).length > 0 ? populate2 : true;
|
|
3736
|
+
}
|
|
3737
|
+
function getNestedExportValue(entry, path2) {
|
|
3738
|
+
if (!entry || !path2) return "";
|
|
3739
|
+
const parts = path2.split(".");
|
|
3740
|
+
let current = entry;
|
|
3741
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3742
|
+
const part = parts[i];
|
|
3743
|
+
if (current === null || current === void 0) return "";
|
|
3744
|
+
if (Array.isArray(current)) {
|
|
3745
|
+
const subPath = parts.slice(i).join(".");
|
|
3746
|
+
return current.map((item) => {
|
|
3747
|
+
if (!item) return "";
|
|
3748
|
+
if (typeof item === "object") {
|
|
3749
|
+
const val = getNestedExportValue(item, subPath);
|
|
3750
|
+
return typeof val === "object" ? JSON.stringify(val) : String(val ?? "");
|
|
3751
|
+
}
|
|
3752
|
+
return String(item);
|
|
3753
|
+
}).filter(Boolean).join("; ");
|
|
3754
|
+
}
|
|
3755
|
+
current = current[part];
|
|
3756
|
+
}
|
|
3757
|
+
if (current === null || current === void 0) return "";
|
|
3758
|
+
if (typeof current === "object" && current.url) {
|
|
3759
|
+
return current.url;
|
|
3760
|
+
}
|
|
3761
|
+
if (typeof current === "object" && !Array.isArray(current)) {
|
|
3762
|
+
if (current.documentId || current.name || current.title) {
|
|
3763
|
+
return current.name || current.title || current.documentId;
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
if (Array.isArray(current)) {
|
|
3767
|
+
return current.map((item) => {
|
|
3768
|
+
if (!item) return "";
|
|
3769
|
+
if (typeof item === "object") {
|
|
3770
|
+
return item.name || item.title || item.documentId || JSON.stringify(item);
|
|
3771
|
+
}
|
|
3772
|
+
return String(item);
|
|
3773
|
+
}).filter(Boolean).join("; ");
|
|
3774
|
+
}
|
|
3775
|
+
return current;
|
|
3776
|
+
}
|
|
3528
3777
|
const csvService = ({ strapi: strapi2 }) => ({
|
|
3529
3778
|
/**
|
|
3530
3779
|
* Generates a sample template CSV with schema attribute headers and sample values
|
|
@@ -3573,9 +3822,7 @@ const csvService = ({ strapi: strapi2 }) => ({
|
|
|
3573
3822
|
}
|
|
3574
3823
|
sampleRow.push(sampleVal);
|
|
3575
3824
|
}
|
|
3576
|
-
const csvContent =
|
|
3577
|
-
${sampleRow.map(escapeCsvValue).join(",")}
|
|
3578
|
-
`;
|
|
3825
|
+
const csvContent = "\uFEFF" + headers.map(escapeCsvValue).join(",") + "\r\n" + sampleRow.map(escapeCsvValue).join(",") + "\r\n";
|
|
3579
3826
|
return {
|
|
3580
3827
|
filename: `${ct.info?.singularName || "template"}_template.csv`,
|
|
3581
3828
|
csvContent,
|
|
@@ -3583,65 +3830,60 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3583
3830
|
};
|
|
3584
3831
|
},
|
|
3585
3832
|
/**
|
|
3586
|
-
* Exports entries of a collection to CSV
|
|
3833
|
+
* Exports entries of a collection to CSV with deep nested component support,
|
|
3834
|
+
* RFC-4180 CRLF line endings, and UTF-8 BOM for 100% Microsoft Excel compatibility.
|
|
3587
3835
|
*/
|
|
3588
3836
|
async exportCollectionCsv(uid, options = {}) {
|
|
3589
3837
|
const ct = strapi2.contentTypes[uid];
|
|
3590
3838
|
if (!ct) {
|
|
3591
3839
|
throw new Error(`Content type ${uid} not found`);
|
|
3592
3840
|
}
|
|
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
|
-
}
|
|
3841
|
+
const allComponents = strapi2.components || {};
|
|
3842
|
+
const fields2 = discoverExportFields(ct, allComponents);
|
|
3616
3843
|
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
|
-
|
|
3844
|
+
const { attribute, filterOperator, filterQuery, search, documentIds } = options;
|
|
3845
|
+
if (documentIds && Array.isArray(documentIds) && documentIds.length > 0) {
|
|
3846
|
+
filters2.documentId = { $in: documentIds };
|
|
3847
|
+
}
|
|
3848
|
+
if (attribute && filterOperator && filterOperator !== "none") {
|
|
3849
|
+
const isNested = attribute.includes(".");
|
|
3850
|
+
if (filterOperator === "empty") {
|
|
3851
|
+
if (isNested) {
|
|
3852
|
+
const [parent, child] = attribute.split(".");
|
|
3853
|
+
filters2[parent] = { [child]: { $null: true } };
|
|
3854
|
+
} else {
|
|
3855
|
+
filters2[attribute] = { $null: true };
|
|
3856
|
+
}
|
|
3857
|
+
} else if (filterOperator === "not_empty") {
|
|
3858
|
+
if (isNested) {
|
|
3859
|
+
const [parent, child] = attribute.split(".");
|
|
3860
|
+
filters2[parent] = { [child]: { $notNull: true } };
|
|
3861
|
+
} else {
|
|
3862
|
+
filters2[attribute] = { $notNull: true };
|
|
3863
|
+
}
|
|
3864
|
+
} else if (filterQuery !== void 0 && filterQuery !== "") {
|
|
3865
|
+
const opMap = {
|
|
3866
|
+
equals: "$eq",
|
|
3867
|
+
notEquals: "$ne",
|
|
3868
|
+
contains: "$containsi",
|
|
3869
|
+
notContains: "$notContainsi",
|
|
3870
|
+
startsWith: "$startsWith",
|
|
3871
|
+
endsWith: "$endsWith"
|
|
3872
|
+
};
|
|
3873
|
+
const strapiOp = opMap[filterOperator] || "$eq";
|
|
3874
|
+
if (isNested) {
|
|
3875
|
+
const [parent, child] = attribute.split(".");
|
|
3876
|
+
filters2[parent] = { [child]: { [strapiOp]: filterQuery } };
|
|
3877
|
+
} else {
|
|
3878
|
+
filters2[attribute] = { [strapiOp]: filterQuery };
|
|
3879
|
+
}
|
|
3638
3880
|
}
|
|
3639
3881
|
}
|
|
3640
3882
|
if (search && search.trim() !== "") {
|
|
3641
3883
|
const q = search.trim();
|
|
3642
3884
|
const searchConditions = [];
|
|
3643
3885
|
const searchFields = ["name", "title", "email", "slug", "description"].filter(
|
|
3644
|
-
(f) =>
|
|
3886
|
+
(f) => ct.attributes?.[f] && ["string", "text", "email"].includes(ct.attributes[f].type)
|
|
3645
3887
|
);
|
|
3646
3888
|
for (const field of searchFields) {
|
|
3647
3889
|
searchConditions.push({ [field]: { $containsi: q } });
|
|
@@ -3654,34 +3896,37 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3654
3896
|
}
|
|
3655
3897
|
}
|
|
3656
3898
|
}
|
|
3657
|
-
const
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3899
|
+
const deepPopulate = buildDeepPopulate(ct.attributes || {}, allComponents);
|
|
3900
|
+
let entries = [];
|
|
3901
|
+
if (ct.kind === "singleType") {
|
|
3902
|
+
const doc = await strapi2.documents(uid).findFirst({
|
|
3903
|
+
populate: deepPopulate
|
|
3904
|
+
});
|
|
3905
|
+
entries = doc ? [doc] : [];
|
|
3906
|
+
} else {
|
|
3907
|
+
entries = await strapi2.documents(uid).findMany({
|
|
3908
|
+
filters: Object.keys(filters2).length > 0 ? filters2 : void 0,
|
|
3909
|
+
limit: options.limit || 5e3,
|
|
3910
|
+
sort: { createdAt: "desc" },
|
|
3911
|
+
populate: deepPopulate
|
|
3912
|
+
});
|
|
3913
|
+
}
|
|
3664
3914
|
const lines = [];
|
|
3665
|
-
lines.push(
|
|
3915
|
+
lines.push(fields2.map((f) => escapeCsvValue(f.key)).join(","));
|
|
3666
3916
|
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
|
-
}
|
|
3917
|
+
const rowValues = fields2.map((f) => {
|
|
3918
|
+
const val = getNestedExportValue(entry, f.key);
|
|
3675
3919
|
return escapeCsvValue(val);
|
|
3676
3920
|
});
|
|
3677
3921
|
lines.push(rowValues.join(","));
|
|
3678
3922
|
}
|
|
3679
|
-
const csvContent = lines.join("\n");
|
|
3923
|
+
const csvContent = "\uFEFF" + lines.join("\r\n") + "\r\n";
|
|
3680
3924
|
const filename = `${ct.info?.singularName || uid}_export_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
3681
3925
|
return {
|
|
3682
3926
|
filename,
|
|
3683
3927
|
csvContent,
|
|
3684
|
-
count: entries ? entries.length : 0
|
|
3928
|
+
count: entries ? entries.length : 0,
|
|
3929
|
+
fields: fields2.map((f) => f.key)
|
|
3685
3930
|
};
|
|
3686
3931
|
},
|
|
3687
3932
|
/**
|
|
@@ -3721,11 +3966,76 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3721
3966
|
const filename = `violations_${safeRuleName}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
3722
3967
|
return {
|
|
3723
3968
|
filename,
|
|
3724
|
-
csvContent: lines.join("\n"),
|
|
3969
|
+
csvContent: "\uFEFF" + lines.join("\r\n") + "\r\n",
|
|
3725
3970
|
count: scanReport.violations.length,
|
|
3726
3971
|
ruleName: scanReport.rule.name
|
|
3727
3972
|
};
|
|
3728
3973
|
},
|
|
3974
|
+
/**
|
|
3975
|
+
* Bulk Export: Exports selected entries across multiple collections or a multi-collection batch.
|
|
3976
|
+
*/
|
|
3977
|
+
async exportBulkCsv(selectedEntries) {
|
|
3978
|
+
if (!selectedEntries || selectedEntries.length === 0) {
|
|
3979
|
+
throw new Error("No entries selected for bulk export");
|
|
3980
|
+
}
|
|
3981
|
+
const byUid = {};
|
|
3982
|
+
for (const item of selectedEntries) {
|
|
3983
|
+
if (!byUid[item.uid]) byUid[item.uid] = [];
|
|
3984
|
+
byUid[item.uid].push(item.documentId);
|
|
3985
|
+
}
|
|
3986
|
+
const uids = Object.keys(byUid);
|
|
3987
|
+
if (uids.length === 1) {
|
|
3988
|
+
const uid = uids[0];
|
|
3989
|
+
return await this.exportCollectionCsv(uid, { documentIds: byUid[uid] });
|
|
3990
|
+
}
|
|
3991
|
+
const rows = [];
|
|
3992
|
+
const headers = [
|
|
3993
|
+
"Collection UID",
|
|
3994
|
+
"Collection Name",
|
|
3995
|
+
"Document ID",
|
|
3996
|
+
"Record Title / Name",
|
|
3997
|
+
"Primary Identifier",
|
|
3998
|
+
"Created At",
|
|
3999
|
+
"Updated At",
|
|
4000
|
+
"Published Status"
|
|
4001
|
+
];
|
|
4002
|
+
rows.push(headers.map(escapeCsvValue).join(","));
|
|
4003
|
+
let totalExported = 0;
|
|
4004
|
+
for (const uid of uids) {
|
|
4005
|
+
const ct = strapi2.contentTypes[uid];
|
|
4006
|
+
if (!ct) continue;
|
|
4007
|
+
const displayName = ct.info?.displayName || uid;
|
|
4008
|
+
const docIds = byUid[uid];
|
|
4009
|
+
const entries = await strapi2.documents(uid).findMany({
|
|
4010
|
+
filters: { documentId: { $in: docIds } },
|
|
4011
|
+
limit: docIds.length
|
|
4012
|
+
});
|
|
4013
|
+
for (const entry of entries || []) {
|
|
4014
|
+
const titleOrName = entry.title || entry.name || entry.subject || entry.email || `Entry #${entry.documentId}`;
|
|
4015
|
+
const identifier = entry.slug || entry.email || entry.username || entry.documentId;
|
|
4016
|
+
const isPublished = Boolean(entry.publishedAt);
|
|
4017
|
+
const row = [
|
|
4018
|
+
uid,
|
|
4019
|
+
displayName,
|
|
4020
|
+
entry.documentId,
|
|
4021
|
+
titleOrName,
|
|
4022
|
+
identifier,
|
|
4023
|
+
entry.createdAt || "",
|
|
4024
|
+
entry.updatedAt || "",
|
|
4025
|
+
isPublished ? "Published" : "Draft"
|
|
4026
|
+
];
|
|
4027
|
+
rows.push(row.map(escapeCsvValue).join(","));
|
|
4028
|
+
totalExported++;
|
|
4029
|
+
}
|
|
4030
|
+
}
|
|
4031
|
+
const filename = `bulk_export_selected_${totalExported}_entries_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
4032
|
+
return {
|
|
4033
|
+
filename,
|
|
4034
|
+
csvContent: "\uFEFF" + rows.join("\r\n") + "\r\n",
|
|
4035
|
+
count: totalExported,
|
|
4036
|
+
isMultiCollection: true
|
|
4037
|
+
};
|
|
4038
|
+
},
|
|
3729
4039
|
/**
|
|
3730
4040
|
* Imports CSV records to update existing records or insert/override records
|
|
3731
4041
|
*/
|