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.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",
|
|
@@ -312,12 +328,13 @@ const bulkUpdaterController = ({ strapi: strapi2 }) => ({
|
|
|
312
328
|
async getCollectionEntries(ctx) {
|
|
313
329
|
try {
|
|
314
330
|
const { uid } = ctx.params;
|
|
315
|
-
const { page, pageSize, attribute, filterOperator, filterQuery, search, sortField, sortOrder } = ctx.query;
|
|
331
|
+
const { page, pageSize, attribute, searchAttribute, filterOperator, filterQuery, search, sortField, sortOrder } = ctx.query;
|
|
316
332
|
const service = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
317
333
|
const data = await service.getCollectionEntries(uid, {
|
|
318
334
|
page: page ? Number(page) : 1,
|
|
319
335
|
pageSize: pageSize ? Number(pageSize) : 10,
|
|
320
336
|
attribute: attribute ? String(attribute) : void 0,
|
|
337
|
+
searchAttribute: searchAttribute ? String(searchAttribute) : void 0,
|
|
321
338
|
filterOperator: filterOperator ? String(filterOperator) : void 0,
|
|
322
339
|
filterQuery: filterQuery !== void 0 ? String(filterQuery) : void 0,
|
|
323
340
|
search: search ? String(search) : void 0,
|
|
@@ -582,20 +599,46 @@ const csvController = ({ strapi: strapi2 }) => ({
|
|
|
582
599
|
async exportCollection(ctx) {
|
|
583
600
|
try {
|
|
584
601
|
const { uid } = ctx.params;
|
|
585
|
-
const { search, attribute, filterOperator, filterQuery, limit } =
|
|
602
|
+
const { search, attribute, filterOperator, filterQuery, limit, documentIds } = {
|
|
603
|
+
...ctx.query,
|
|
604
|
+
...ctx.request?.body || {}
|
|
605
|
+
};
|
|
586
606
|
const csvService2 = strapi2.plugin("bulk-updater").service("csv");
|
|
607
|
+
let parsedDocIds;
|
|
608
|
+
if (documentIds) {
|
|
609
|
+
parsedDocIds = Array.isArray(documentIds) ? documentIds : String(documentIds).split(",").map((s) => s.trim()).filter(Boolean);
|
|
610
|
+
}
|
|
587
611
|
const result = await csvService2.exportCollectionCsv(uid, {
|
|
588
612
|
search: search ? String(search) : void 0,
|
|
589
613
|
attribute: attribute ? String(attribute) : void 0,
|
|
590
614
|
filterOperator: filterOperator ? String(filterOperator) : void 0,
|
|
591
615
|
filterQuery: filterQuery !== void 0 ? String(filterQuery) : void 0,
|
|
592
|
-
limit: limit ? Number(limit) : void 0
|
|
616
|
+
limit: limit ? Number(limit) : void 0,
|
|
617
|
+
documentIds: parsedDocIds
|
|
593
618
|
});
|
|
594
619
|
ctx.body = {
|
|
595
620
|
success: true,
|
|
596
621
|
filename: result.filename,
|
|
597
622
|
csvContent: result.csvContent,
|
|
598
|
-
count: result.count
|
|
623
|
+
count: result.count,
|
|
624
|
+
fields: result.fields
|
|
625
|
+
};
|
|
626
|
+
} catch (err) {
|
|
627
|
+
ctx.status = 500;
|
|
628
|
+
ctx.body = { success: false, error: err.message };
|
|
629
|
+
}
|
|
630
|
+
},
|
|
631
|
+
async exportBulk(ctx) {
|
|
632
|
+
try {
|
|
633
|
+
const { selectedEntries } = ctx.request.body || {};
|
|
634
|
+
const csvService2 = strapi2.plugin("bulk-updater").service("csv");
|
|
635
|
+
const result = await csvService2.exportBulkCsv(selectedEntries);
|
|
636
|
+
ctx.body = {
|
|
637
|
+
success: true,
|
|
638
|
+
filename: result.filename,
|
|
639
|
+
csvContent: result.csvContent,
|
|
640
|
+
count: result.count,
|
|
641
|
+
isMultiCollection: result.isMultiCollection
|
|
599
642
|
};
|
|
600
643
|
} catch (err) {
|
|
601
644
|
ctx.status = 500;
|
|
@@ -650,6 +693,127 @@ const modelsCache = /* @__PURE__ */ new Map();
|
|
|
650
693
|
const MAX_QUERY_CACHE_SIZE = 500;
|
|
651
694
|
const CACHE_TTL_MS = 60 * 1e3;
|
|
652
695
|
let staticSchemaCache = null;
|
|
696
|
+
function buildDeepPopulate$1(attributes, allComponents, depth2 = 0) {
|
|
697
|
+
if (depth2 > 5) return true;
|
|
698
|
+
const populate2 = {};
|
|
699
|
+
for (const [key, def] of Object.entries(attributes || {})) {
|
|
700
|
+
if (def.type === "component" && def.component) {
|
|
701
|
+
const comp = allComponents[def.component];
|
|
702
|
+
if (comp?.attributes) {
|
|
703
|
+
populate2[key] = {
|
|
704
|
+
populate: buildDeepPopulate$1(comp.attributes, allComponents, depth2 + 1)
|
|
705
|
+
};
|
|
706
|
+
} else {
|
|
707
|
+
populate2[key] = true;
|
|
708
|
+
}
|
|
709
|
+
} else if (def.type === "dynamiczone") {
|
|
710
|
+
populate2[key] = true;
|
|
711
|
+
} else if (def.type === "relation" || def.type === "media") {
|
|
712
|
+
populate2[key] = true;
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
return Object.keys(populate2).length > 0 ? populate2 : true;
|
|
716
|
+
}
|
|
717
|
+
function discoverComponentAttributes(strapi2, compUid, prefix, currentDepth, maxDepth, systemFields, allContentTypes, relationsByAttr, allRelationsMeta, attributesMeta) {
|
|
718
|
+
if (currentDepth > maxDepth || !compUid) return [];
|
|
719
|
+
const compSchema = strapi2.components[compUid];
|
|
720
|
+
if (!compSchema?.attributes) return [];
|
|
721
|
+
const discoveredAttrs = [];
|
|
722
|
+
for (const [subKey, subAttr] of Object.entries(compSchema.attributes)) {
|
|
723
|
+
if (systemFields.includes(subKey)) continue;
|
|
724
|
+
if (subAttr.type === "dynamiczone") continue;
|
|
725
|
+
const fullPath = prefix ? `${prefix}.${subKey}` : subKey;
|
|
726
|
+
if (subAttr.type === "component") {
|
|
727
|
+
if (!subAttr.repeatable && subAttr.component) {
|
|
728
|
+
const deeper = discoverComponentAttributes(
|
|
729
|
+
strapi2,
|
|
730
|
+
subAttr.component,
|
|
731
|
+
fullPath,
|
|
732
|
+
currentDepth + 1,
|
|
733
|
+
maxDepth,
|
|
734
|
+
systemFields,
|
|
735
|
+
allContentTypes,
|
|
736
|
+
relationsByAttr,
|
|
737
|
+
allRelationsMeta,
|
|
738
|
+
attributesMeta
|
|
739
|
+
);
|
|
740
|
+
discoveredAttrs.push(...deeper);
|
|
741
|
+
}
|
|
742
|
+
} else if (subAttr.type === "relation") {
|
|
743
|
+
const targetUid = subAttr.target;
|
|
744
|
+
if (targetUid && allContentTypes[targetUid]) {
|
|
745
|
+
const targetInfo = allContentTypes[targetUid]?.info;
|
|
746
|
+
const isMulti = subAttr.relation === "oneToMany" || subAttr.relation === "manyToMany";
|
|
747
|
+
const relMeta = {
|
|
748
|
+
isRelation: true,
|
|
749
|
+
relationType: subAttr.relation,
|
|
750
|
+
targetUid,
|
|
751
|
+
targetDisplayName: targetInfo?.displayName || targetUid,
|
|
752
|
+
isMulti
|
|
753
|
+
};
|
|
754
|
+
relationsByAttr[fullPath] = relMeta;
|
|
755
|
+
allRelationsMeta[fullPath] = relMeta;
|
|
756
|
+
attributesMeta[fullPath] = { depth: currentDepth, type: "relation", component: compUid, isRelation: true };
|
|
757
|
+
discoveredAttrs.push(fullPath);
|
|
758
|
+
}
|
|
759
|
+
} else {
|
|
760
|
+
attributesMeta[fullPath] = { depth: currentDepth, type: subAttr.type, component: compUid };
|
|
761
|
+
discoveredAttrs.push(fullPath);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
return discoveredAttrs;
|
|
765
|
+
}
|
|
766
|
+
function isDeepComponentField(contentType, allComponents, attribute) {
|
|
767
|
+
if (!attribute || !attribute.includes(".")) return false;
|
|
768
|
+
const parts = attribute.split(".");
|
|
769
|
+
let currentSchema = contentType;
|
|
770
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
771
|
+
const part = parts[i];
|
|
772
|
+
const attrDef = currentSchema?.attributes?.[part];
|
|
773
|
+
if (!attrDef || attrDef.type !== "component" || !attrDef.component) return false;
|
|
774
|
+
currentSchema = allComponents[attrDef.component];
|
|
775
|
+
if (!currentSchema) return false;
|
|
776
|
+
}
|
|
777
|
+
const leafField = parts[parts.length - 1];
|
|
778
|
+
return Boolean(currentSchema?.attributes?.[leafField]);
|
|
779
|
+
}
|
|
780
|
+
function getLeafAttributeSchema(contentType, allComponents, attribute) {
|
|
781
|
+
if (!attribute) return null;
|
|
782
|
+
if (!attribute.includes(".")) return contentType?.attributes?.[attribute] || null;
|
|
783
|
+
const parts = attribute.split(".");
|
|
784
|
+
let currentSchema = contentType;
|
|
785
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
786
|
+
const part = parts[i];
|
|
787
|
+
const attrDef = currentSchema?.attributes?.[part];
|
|
788
|
+
if (!attrDef || attrDef.type !== "component" || !attrDef.component) return null;
|
|
789
|
+
currentSchema = allComponents[attrDef.component];
|
|
790
|
+
if (!currentSchema) return null;
|
|
791
|
+
}
|
|
792
|
+
return currentSchema?.attributes?.[parts[parts.length - 1]] || null;
|
|
793
|
+
}
|
|
794
|
+
function setDeepProperty(obj, pathParts, value) {
|
|
795
|
+
if (pathParts.length === 0) return value;
|
|
796
|
+
const current = { ...obj || {} };
|
|
797
|
+
const [head, ...tail] = pathParts;
|
|
798
|
+
if (tail.length === 0) {
|
|
799
|
+
current[head] = value;
|
|
800
|
+
} else {
|
|
801
|
+
current[head] = setDeepProperty(current[head] || {}, tail, value);
|
|
802
|
+
}
|
|
803
|
+
return current;
|
|
804
|
+
}
|
|
805
|
+
function getDeepProperty(obj, path2) {
|
|
806
|
+
if (!obj || !path2) return void 0;
|
|
807
|
+
if (!path2.includes(".")) return obj[path2];
|
|
808
|
+
return path2.split(".").reduce((acc, part) => acc && typeof acc === "object" ? acc[part] : void 0, obj);
|
|
809
|
+
}
|
|
810
|
+
function buildDeepFilter(pathParts, condition) {
|
|
811
|
+
if (pathParts.length === 0) return condition;
|
|
812
|
+
const [head, ...tail] = pathParts;
|
|
813
|
+
return {
|
|
814
|
+
[head]: buildDeepFilter(tail, condition)
|
|
815
|
+
};
|
|
816
|
+
}
|
|
653
817
|
function getSafeTimezone(timeZone) {
|
|
654
818
|
if (!timeZone || typeof timeZone !== "string") {
|
|
655
819
|
return Intl.DateTimeFormat().resolvedOptions().timeZone || "UTC";
|
|
@@ -858,43 +1022,28 @@ async function executeBatchInternal(strapi2, {
|
|
|
858
1022
|
const currentEntry = await getDocumentDeduplicated(target.uid, target.documentId);
|
|
859
1023
|
const entryLabel = currentEntry?.name || currentEntry?.title || currentEntry?.email || `Entry #${target.documentId}`;
|
|
860
1024
|
const isNested = attribute.includes(".");
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
if (isNested) {
|
|
865
|
-
const parts = attribute.split(".");
|
|
866
|
-
parentKey = parts[0];
|
|
867
|
-
childKey = parts[1];
|
|
868
|
-
const parentAttrDef = contentType?.attributes?.[parentKey];
|
|
869
|
-
if (parentAttrDef?.type === "component" && !parentAttrDef.repeatable && parentAttrDef.component) {
|
|
870
|
-
const compSchema = strapi2.components[parentAttrDef.component];
|
|
871
|
-
if (compSchema?.attributes?.[childKey]) {
|
|
872
|
-
isComponentNested = true;
|
|
873
|
-
}
|
|
874
|
-
}
|
|
875
|
-
}
|
|
1025
|
+
const isComponentNested = isNested && isDeepComponentField(contentType, strapi2.components, attribute);
|
|
1026
|
+
const parts = isNested ? attribute.split(".") : [];
|
|
1027
|
+
const rootKey = isNested ? parts[0] : "";
|
|
876
1028
|
const attrDef = contentType?.attributes?.[attribute];
|
|
877
1029
|
const hasAttribute = Boolean(attrDef) || isComponentNested;
|
|
878
1030
|
if (hasAttribute) {
|
|
879
1031
|
if (isComponentNested) {
|
|
880
1032
|
const currentEntryWithPopulate = await strapi2.documents(target.uid).findOne({
|
|
881
1033
|
documentId: target.documentId,
|
|
882
|
-
populate:
|
|
1034
|
+
populate: buildDeepPopulate$1(contentType.attributes, strapi2.components)
|
|
883
1035
|
});
|
|
884
|
-
const
|
|
885
|
-
const currentVal2 =
|
|
1036
|
+
const existingRoot = currentEntryWithPopulate?.[rootKey] || {};
|
|
1037
|
+
const currentVal2 = getDeepProperty(existingRoot, parts.slice(1).join("."));
|
|
886
1038
|
let newValue2 = value;
|
|
887
1039
|
if (regexObj && typeof currentVal2 === "string") {
|
|
888
1040
|
newValue2 = currentVal2.replace(regexObj, value);
|
|
889
1041
|
}
|
|
890
|
-
const
|
|
891
|
-
...existingParent,
|
|
892
|
-
[childKey]: newValue2
|
|
893
|
-
};
|
|
1042
|
+
const updatedRoot = setDeepProperty(existingRoot, parts.slice(1), newValue2);
|
|
894
1043
|
await strapi2.documents(target.uid).update({
|
|
895
1044
|
documentId: target.documentId,
|
|
896
1045
|
data: {
|
|
897
|
-
[
|
|
1046
|
+
[rootKey]: updatedRoot
|
|
898
1047
|
}
|
|
899
1048
|
});
|
|
900
1049
|
const isDraftAndPublishComp = Boolean(contentType?.options?.draftAndPublish);
|
|
@@ -1329,12 +1478,14 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1329
1478
|
}
|
|
1330
1479
|
const metaByUid = {};
|
|
1331
1480
|
const allRelationsMeta = {};
|
|
1481
|
+
const allAttributesMeta = {};
|
|
1332
1482
|
for (const uid of targetUids) {
|
|
1333
1483
|
const contentType = allContentTypes[uid];
|
|
1334
1484
|
if (!contentType || contentType.kind !== "collectionType" && contentType.kind !== "singleType") continue;
|
|
1335
1485
|
const isSingleType = contentType.kind === "singleType";
|
|
1336
1486
|
const rawAttrs = contentType.attributes || {};
|
|
1337
1487
|
const relationsByAttr = {};
|
|
1488
|
+
const colAttributesMeta = {};
|
|
1338
1489
|
const nestedComponentAttrs = [];
|
|
1339
1490
|
const validAttrs = Object.keys(rawAttrs).filter((attrKey) => {
|
|
1340
1491
|
if (systemFields.includes(attrKey)) return false;
|
|
@@ -1342,14 +1493,19 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1342
1493
|
if (rawAttrs[attrKey].type === "component") {
|
|
1343
1494
|
const compDef = rawAttrs[attrKey];
|
|
1344
1495
|
if (!compDef.repeatable && compDef.component) {
|
|
1345
|
-
const
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1496
|
+
const compAttrs = discoverComponentAttributes(
|
|
1497
|
+
strapi2,
|
|
1498
|
+
compDef.component,
|
|
1499
|
+
attrKey,
|
|
1500
|
+
1,
|
|
1501
|
+
5,
|
|
1502
|
+
systemFields,
|
|
1503
|
+
allContentTypes,
|
|
1504
|
+
relationsByAttr,
|
|
1505
|
+
allRelationsMeta,
|
|
1506
|
+
colAttributesMeta
|
|
1507
|
+
);
|
|
1508
|
+
nestedComponentAttrs.push(...compAttrs);
|
|
1353
1509
|
}
|
|
1354
1510
|
return false;
|
|
1355
1511
|
}
|
|
@@ -1368,12 +1524,19 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1368
1524
|
};
|
|
1369
1525
|
relationsByAttr[attrKey] = relMeta;
|
|
1370
1526
|
allRelationsMeta[attrKey] = relMeta;
|
|
1527
|
+
colAttributesMeta[attrKey] = { depth: 0, type: "relation", isRelation: true };
|
|
1371
1528
|
return true;
|
|
1372
1529
|
}
|
|
1530
|
+
colAttributesMeta[attrKey] = { depth: 0, type: rawAttrs[attrKey].type };
|
|
1373
1531
|
return true;
|
|
1374
1532
|
});
|
|
1375
1533
|
const allColAttrs = [...validAttrs, ...nestedComponentAttrs];
|
|
1376
|
-
allColAttrs.forEach((attr) =>
|
|
1534
|
+
allColAttrs.forEach((attr) => {
|
|
1535
|
+
allAttributesSet.add(attr);
|
|
1536
|
+
if (colAttributesMeta[attr]) {
|
|
1537
|
+
allAttributesMeta[attr] = colAttributesMeta[attr];
|
|
1538
|
+
}
|
|
1539
|
+
});
|
|
1377
1540
|
metaByUid[uid] = {
|
|
1378
1541
|
uid,
|
|
1379
1542
|
kind: contentType.kind,
|
|
@@ -1383,11 +1546,13 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1383
1546
|
pluralName: contentType.info?.pluralName || uid,
|
|
1384
1547
|
displayName: contentType.info?.displayName || uid,
|
|
1385
1548
|
attributes: allColAttrs,
|
|
1549
|
+
attributesMeta: colAttributesMeta,
|
|
1386
1550
|
relations: relationsByAttr
|
|
1387
1551
|
};
|
|
1388
1552
|
}
|
|
1389
1553
|
staticSchemaCache = {
|
|
1390
1554
|
attributes: Array.from(allAttributesSet),
|
|
1555
|
+
attributesMeta: allAttributesMeta,
|
|
1391
1556
|
targetUids: Object.keys(metaByUid),
|
|
1392
1557
|
metaByUid,
|
|
1393
1558
|
relationsMeta: allRelationsMeta
|
|
@@ -1418,17 +1583,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1418
1583
|
let entries = [];
|
|
1419
1584
|
let totalCount = 0;
|
|
1420
1585
|
try {
|
|
1421
|
-
const
|
|
1422
|
-
const relKeys = Object.keys(meta.relations || {});
|
|
1423
|
-
const compKeys = Object.keys(ctAttrs).filter((k) => ctAttrs[k].type === "component");
|
|
1424
|
-
const populateOptionObj = {};
|
|
1425
|
-
relKeys.forEach((k) => {
|
|
1426
|
-
populateOptionObj[k] = true;
|
|
1427
|
-
});
|
|
1428
|
-
compKeys.forEach((k) => {
|
|
1429
|
-
populateOptionObj[k] = true;
|
|
1430
|
-
});
|
|
1431
|
-
const populateOption = Object.keys(populateOptionObj).length > 0 ? populateOptionObj : void 0;
|
|
1586
|
+
const populateOption = buildDeepPopulate$1(allContentTypes[uid]?.attributes || {}, strapi2.components);
|
|
1432
1587
|
if (meta.isSingleType) {
|
|
1433
1588
|
const singleDoc = await strapi2.documents(uid).findFirst({
|
|
1434
1589
|
populate: populateOption
|
|
@@ -1487,8 +1642,10 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1487
1642
|
return {
|
|
1488
1643
|
success: true,
|
|
1489
1644
|
attributes: staticSchemaCache.attributes,
|
|
1645
|
+
attributesMeta: staticSchemaCache.attributesMeta,
|
|
1490
1646
|
collections,
|
|
1491
1647
|
relationsMeta: staticSchemaCache.relationsMeta,
|
|
1648
|
+
maxDiscoveredDepth: 5,
|
|
1492
1649
|
executionWindow: {
|
|
1493
1650
|
...executionConfig,
|
|
1494
1651
|
...windowStatus
|
|
@@ -1507,6 +1664,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1507
1664
|
page = 1,
|
|
1508
1665
|
pageSize = 10,
|
|
1509
1666
|
attribute,
|
|
1667
|
+
searchAttribute,
|
|
1510
1668
|
filterOperator,
|
|
1511
1669
|
filterQuery,
|
|
1512
1670
|
search,
|
|
@@ -1521,8 +1679,9 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1521
1679
|
const currentPage = Math.max(Number(page) || 1, 1);
|
|
1522
1680
|
const start = (currentPage - 1) * limit;
|
|
1523
1681
|
const isSingleType = contentType.kind === "singleType";
|
|
1682
|
+
const targetFilterAttr = searchAttribute || attribute;
|
|
1524
1683
|
const signature = await getCollectionSignature(strapi2, uid);
|
|
1525
|
-
const cacheKey = `${uid}:${limit}:${start}:${attribute || ""}:${filterOperator || ""}:${filterQuery || ""}:${search || ""}:${sortField || ""}:${sortOrder}`;
|
|
1684
|
+
const cacheKey = `${uid}:${limit}:${start}:${attribute || ""}:${searchAttribute || ""}:${filterOperator || ""}:${filterQuery || ""}:${search || ""}:${sortField || ""}:${sortOrder}`;
|
|
1526
1685
|
const cached = queryCache.get(cacheKey);
|
|
1527
1686
|
const etag = `W/"entry-${crypto__default.default.createHash("md5").update(`${cacheKey}:${signature}`).digest("hex")}"`;
|
|
1528
1687
|
if (cached && cached.signature === signature && Date.now() - cached.timestamp < CACHE_TTL_MS) {
|
|
@@ -1537,19 +1696,15 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1537
1696
|
const attrs = contentType.attributes || {};
|
|
1538
1697
|
let colHasAttribute = false;
|
|
1539
1698
|
let isNestedAttr = false;
|
|
1540
|
-
let
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
if (compSchema?.attributes?.[childKey]) {
|
|
1548
|
-
colHasAttribute = true;
|
|
1549
|
-
isNestedAttr = true;
|
|
1550
|
-
}
|
|
1699
|
+
let parts = [];
|
|
1700
|
+
if (targetFilterAttr) {
|
|
1701
|
+
if (targetFilterAttr.includes(".")) {
|
|
1702
|
+
isNestedAttr = isDeepComponentField(contentType, strapi2.components, targetFilterAttr);
|
|
1703
|
+
if (isNestedAttr) {
|
|
1704
|
+
colHasAttribute = true;
|
|
1705
|
+
parts = targetFilterAttr.split(".");
|
|
1551
1706
|
}
|
|
1552
|
-
} else if (attrs[
|
|
1707
|
+
} else if (attrs[targetFilterAttr]) {
|
|
1553
1708
|
colHasAttribute = true;
|
|
1554
1709
|
}
|
|
1555
1710
|
}
|
|
@@ -1574,7 +1729,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1574
1729
|
}
|
|
1575
1730
|
let condition = void 0;
|
|
1576
1731
|
const q = (filterQuery || "").trim();
|
|
1577
|
-
const targetAttrSchema = isNestedAttr ? strapi2.components
|
|
1732
|
+
const targetAttrSchema = isNestedAttr ? getLeafAttributeSchema(contentType, strapi2.components, targetFilterAttr) : attrs[targetFilterAttr];
|
|
1578
1733
|
const isStringLike = ["string", "text", "email", "richtext", "uid"].includes(targetAttrSchema?.type);
|
|
1579
1734
|
if (normOp === "empty") {
|
|
1580
1735
|
condition = isStringLike ? { $or: [{ $null: true }, { $eq: "" }] } : { $null: true };
|
|
@@ -1604,19 +1759,18 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1604
1759
|
}
|
|
1605
1760
|
if (condition !== void 0) {
|
|
1606
1761
|
if (isNestedAttr) {
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
};
|
|
1762
|
+
const deepFilter = buildDeepFilter(parts, condition);
|
|
1763
|
+
Object.assign(filters2, deepFilter);
|
|
1610
1764
|
} else {
|
|
1611
1765
|
if (normOp === "empty" && isStringLike) {
|
|
1612
1766
|
filters2.$or = [
|
|
1613
|
-
{ [
|
|
1614
|
-
{ [
|
|
1767
|
+
{ [targetFilterAttr]: { $null: true } },
|
|
1768
|
+
{ [targetFilterAttr]: { $eq: "" } }
|
|
1615
1769
|
];
|
|
1616
1770
|
} else if (normOp === "notempty" && isStringLike) {
|
|
1617
|
-
filters2[
|
|
1771
|
+
filters2[targetFilterAttr] = { $notNull: true, $ne: "" };
|
|
1618
1772
|
} else {
|
|
1619
|
-
filters2[
|
|
1773
|
+
filters2[targetFilterAttr] = condition;
|
|
1620
1774
|
}
|
|
1621
1775
|
}
|
|
1622
1776
|
}
|
|
@@ -1639,11 +1793,13 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1639
1793
|
}
|
|
1640
1794
|
}
|
|
1641
1795
|
}
|
|
1796
|
+
const populateOption = buildDeepPopulate$1(contentType.attributes, strapi2.components);
|
|
1642
1797
|
if (isSingleType) {
|
|
1643
1798
|
let singleDoc = null;
|
|
1644
1799
|
try {
|
|
1645
1800
|
singleDoc = await strapi2.documents(uid).findFirst({
|
|
1646
|
-
filters: Object.keys(filters2).length > 0 ? filters2 : void 0
|
|
1801
|
+
filters: Object.keys(filters2).length > 0 ? filters2 : void 0,
|
|
1802
|
+
populate: populateOption
|
|
1647
1803
|
});
|
|
1648
1804
|
} catch {
|
|
1649
1805
|
singleDoc = null;
|
|
@@ -1662,11 +1818,11 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1662
1818
|
}
|
|
1663
1819
|
};
|
|
1664
1820
|
} else {
|
|
1665
|
-
|
|
1821
|
+
let sortClause = {};
|
|
1666
1822
|
if (sortField && sortField.includes(".")) {
|
|
1667
|
-
const
|
|
1668
|
-
if (contentType.
|
|
1669
|
-
sortClause
|
|
1823
|
+
const sortParts = sortField.split(".");
|
|
1824
|
+
if (isDeepComponentField(contentType, strapi2.components, sortField)) {
|
|
1825
|
+
sortClause = buildDeepFilter(sortParts, sortOrder);
|
|
1670
1826
|
} else {
|
|
1671
1827
|
sortClause["createdAt"] = "desc";
|
|
1672
1828
|
}
|
|
@@ -1675,10 +1831,6 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1675
1831
|
} else {
|
|
1676
1832
|
sortClause["createdAt"] = "desc";
|
|
1677
1833
|
}
|
|
1678
|
-
const populateAttrs = Object.keys(contentType.attributes || {}).filter(
|
|
1679
|
-
(k) => contentType.attributes[k].type === "relation" || contentType.attributes[k].type === "component"
|
|
1680
|
-
);
|
|
1681
|
-
const populateOption = populateAttrs.length > 0 ? populateAttrs.reduce((acc, k) => ({ ...acc, [k]: true }), {}) : void 0;
|
|
1682
1834
|
const [entries, totalEntries] = await Promise.all([
|
|
1683
1835
|
strapi2.documents(uid).findMany({
|
|
1684
1836
|
start,
|
|
@@ -1800,21 +1952,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1800
1952
|
const displayName = contentType?.info?.displayName || target.uid;
|
|
1801
1953
|
try {
|
|
1802
1954
|
const isNested = attribute.includes(".");
|
|
1803
|
-
|
|
1804
|
-
let parentKey = "";
|
|
1805
|
-
let childKey = "";
|
|
1806
|
-
if (isNested) {
|
|
1807
|
-
const parts = attribute.split(".");
|
|
1808
|
-
parentKey = parts[0];
|
|
1809
|
-
childKey = parts[1];
|
|
1810
|
-
const parentAttrDef = contentType?.attributes?.[parentKey];
|
|
1811
|
-
if (parentAttrDef?.type === "component" && !parentAttrDef.repeatable && parentAttrDef.component) {
|
|
1812
|
-
const compSchema = strapi2.components[parentAttrDef.component];
|
|
1813
|
-
if (compSchema?.attributes?.[childKey]) {
|
|
1814
|
-
isComponentNested = true;
|
|
1815
|
-
}
|
|
1816
|
-
}
|
|
1817
|
-
}
|
|
1955
|
+
const isComponentNested = isNested && isDeepComponentField(contentType, strapi2.components, attribute);
|
|
1818
1956
|
const attrDef = contentType?.attributes?.[attribute];
|
|
1819
1957
|
const hasAttribute = Boolean(attrDef) || isComponentNested;
|
|
1820
1958
|
if (!hasAttribute) {
|
|
@@ -1839,7 +1977,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1839
1977
|
if (isComponentNested) {
|
|
1840
1978
|
currentEntry = await strapi2.documents(target.uid).findOne({
|
|
1841
1979
|
documentId: target.documentId,
|
|
1842
|
-
populate:
|
|
1980
|
+
populate: buildDeepPopulate$1(contentType.attributes, strapi2.components)
|
|
1843
1981
|
});
|
|
1844
1982
|
} else if (attrDef?.type === "relation") {
|
|
1845
1983
|
currentEntry = await strapi2.documents(target.uid).findOne({
|
|
@@ -1853,8 +1991,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1853
1991
|
}
|
|
1854
1992
|
const entryLabel = currentEntry?.name || currentEntry?.title || currentEntry?.email || `Entry #${target.documentId}`;
|
|
1855
1993
|
if (isComponentNested) {
|
|
1856
|
-
const
|
|
1857
|
-
const currentVal2 = existingParent?.[childKey];
|
|
1994
|
+
const currentVal2 = getDeepProperty(currentEntry, attribute);
|
|
1858
1995
|
let newValue2 = value;
|
|
1859
1996
|
if (regexObj && typeof currentVal2 === "string") {
|
|
1860
1997
|
newValue2 = currentVal2.replace(regexObj, value);
|
|
@@ -2594,7 +2731,11 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
2594
2731
|
function escapeCsvVal(val) {
|
|
2595
2732
|
if (val === null || val === void 0) return "";
|
|
2596
2733
|
let str2 = typeof val === "object" ? JSON.stringify(val) : String(val);
|
|
2597
|
-
|
|
2734
|
+
str2 = str2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
2735
|
+
if (/^[=+@-]$/.test(str2.charAt(0))) {
|
|
2736
|
+
str2 = `'${str2}`;
|
|
2737
|
+
}
|
|
2738
|
+
if (str2.includes(",") || str2.includes('"') || str2.includes("\n")) {
|
|
2598
2739
|
return `"${str2.replace(/"/g, '""')}"`;
|
|
2599
2740
|
}
|
|
2600
2741
|
return str2;
|
|
@@ -2638,7 +2779,7 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
2638
2779
|
return {
|
|
2639
2780
|
success: true,
|
|
2640
2781
|
filename,
|
|
2641
|
-
csvContent: rows.join("\n"),
|
|
2782
|
+
csvContent: "\uFEFF" + rows.join("\r\n") + "\r\n",
|
|
2642
2783
|
summary: {
|
|
2643
2784
|
batchId: batch.id,
|
|
2644
2785
|
status: batch.status,
|
|
@@ -3462,23 +3603,34 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
3462
3603
|
function escapeCsvValue(val) {
|
|
3463
3604
|
if (val === null || val === void 0) return "";
|
|
3464
3605
|
let str2 = "";
|
|
3465
|
-
if (
|
|
3606
|
+
if (val instanceof Date) {
|
|
3607
|
+
str2 = val.toISOString();
|
|
3608
|
+
} else if (typeof val === "object") {
|
|
3466
3609
|
str2 = JSON.stringify(val);
|
|
3467
3610
|
} else {
|
|
3468
3611
|
str2 = String(val);
|
|
3469
3612
|
}
|
|
3470
|
-
|
|
3613
|
+
str2 = str2.replace(/\r\n/g, "\n").replace(/\r/g, "\n");
|
|
3614
|
+
if (/^[=+@-]$/.test(str2.charAt(0))) {
|
|
3615
|
+
str2 = `'${str2}`;
|
|
3616
|
+
}
|
|
3617
|
+
if (str2.includes(",") || str2.includes('"') || str2.includes("\n")) {
|
|
3471
3618
|
return `"${str2.replace(/"/g, '""')}"`;
|
|
3472
3619
|
}
|
|
3473
3620
|
return str2;
|
|
3474
3621
|
}
|
|
3475
3622
|
function parseCsv(csvText) {
|
|
3623
|
+
if (!csvText) return [];
|
|
3624
|
+
let cleanText = csvText;
|
|
3625
|
+
if (cleanText.charCodeAt(0) === 65279) {
|
|
3626
|
+
cleanText = cleanText.slice(1);
|
|
3627
|
+
}
|
|
3476
3628
|
const lines = [];
|
|
3477
3629
|
let current = "";
|
|
3478
3630
|
let inQuotes = false;
|
|
3479
|
-
for (let i = 0; i <
|
|
3480
|
-
const char =
|
|
3481
|
-
const nextChar =
|
|
3631
|
+
for (let i = 0; i < cleanText.length; i++) {
|
|
3632
|
+
const char = cleanText[i];
|
|
3633
|
+
const nextChar = cleanText[i + 1];
|
|
3482
3634
|
if (char === '"') {
|
|
3483
3635
|
if (inQuotes && nextChar === '"') {
|
|
3484
3636
|
current += '"';
|
|
@@ -3538,6 +3690,103 @@ function parseCsv(csvText) {
|
|
|
3538
3690
|
}
|
|
3539
3691
|
return rows;
|
|
3540
3692
|
}
|
|
3693
|
+
function discoverExportFields(ct, allComponents, maxDepth = 4) {
|
|
3694
|
+
const fields2 = [{ key: "documentId", label: "documentId", type: "string" }];
|
|
3695
|
+
const excluded = ["createdBy", "updatedBy", "localizations"];
|
|
3696
|
+
function walkComponent(compUid, pathPrefix, labelPrefix, depth2) {
|
|
3697
|
+
if (depth2 >= maxDepth) return;
|
|
3698
|
+
const comp = allComponents[compUid];
|
|
3699
|
+
if (!comp?.attributes) return;
|
|
3700
|
+
for (const [subKey, subDef] of Object.entries(comp.attributes)) {
|
|
3701
|
+
if (excluded.includes(subKey)) continue;
|
|
3702
|
+
const keyPath = `${pathPrefix}.${subKey}`;
|
|
3703
|
+
const displayLabel = `${labelPrefix} > ${subKey}`;
|
|
3704
|
+
if (subDef.type === "component" && subDef.component) {
|
|
3705
|
+
if (!subDef.repeatable) {
|
|
3706
|
+
walkComponent(subDef.component, keyPath, displayLabel, depth2 + 1);
|
|
3707
|
+
} else {
|
|
3708
|
+
fields2.push({ key: keyPath, label: displayLabel, type: "component" });
|
|
3709
|
+
}
|
|
3710
|
+
} else {
|
|
3711
|
+
fields2.push({ key: keyPath, label: displayLabel, type: subDef.type });
|
|
3712
|
+
}
|
|
3713
|
+
}
|
|
3714
|
+
}
|
|
3715
|
+
for (const [attrName, attrDef] of Object.entries(ct.attributes || {})) {
|
|
3716
|
+
if (excluded.includes(attrName)) continue;
|
|
3717
|
+
if (attrDef.type === "component" && attrDef.component) {
|
|
3718
|
+
if (!attrDef.repeatable) {
|
|
3719
|
+
walkComponent(attrDef.component, attrName, attrName, 0);
|
|
3720
|
+
} else {
|
|
3721
|
+
fields2.push({ key: attrName, label: attrName, type: "component" });
|
|
3722
|
+
}
|
|
3723
|
+
} else {
|
|
3724
|
+
fields2.push({ key: attrName, label: attrName, type: attrDef.type });
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
return fields2;
|
|
3728
|
+
}
|
|
3729
|
+
function buildDeepPopulate(attributes, allComponents, depth2 = 0) {
|
|
3730
|
+
if (depth2 > 4) return true;
|
|
3731
|
+
const populate2 = {};
|
|
3732
|
+
for (const [key, def] of Object.entries(attributes || {})) {
|
|
3733
|
+
if (def.type === "component" && def.component) {
|
|
3734
|
+
const comp = allComponents[def.component];
|
|
3735
|
+
if (comp?.attributes) {
|
|
3736
|
+
populate2[key] = {
|
|
3737
|
+
populate: buildDeepPopulate(comp.attributes, allComponents, depth2 + 1)
|
|
3738
|
+
};
|
|
3739
|
+
} else {
|
|
3740
|
+
populate2[key] = true;
|
|
3741
|
+
}
|
|
3742
|
+
} else if (def.type === "dynamiczone") {
|
|
3743
|
+
populate2[key] = true;
|
|
3744
|
+
} else if (def.type === "relation" || def.type === "media") {
|
|
3745
|
+
populate2[key] = true;
|
|
3746
|
+
}
|
|
3747
|
+
}
|
|
3748
|
+
return Object.keys(populate2).length > 0 ? populate2 : true;
|
|
3749
|
+
}
|
|
3750
|
+
function getNestedExportValue(entry, path2) {
|
|
3751
|
+
if (!entry || !path2) return "";
|
|
3752
|
+
const parts = path2.split(".");
|
|
3753
|
+
let current = entry;
|
|
3754
|
+
for (let i = 0; i < parts.length; i++) {
|
|
3755
|
+
const part = parts[i];
|
|
3756
|
+
if (current === null || current === void 0) return "";
|
|
3757
|
+
if (Array.isArray(current)) {
|
|
3758
|
+
const subPath = parts.slice(i).join(".");
|
|
3759
|
+
return current.map((item) => {
|
|
3760
|
+
if (!item) return "";
|
|
3761
|
+
if (typeof item === "object") {
|
|
3762
|
+
const val = getNestedExportValue(item, subPath);
|
|
3763
|
+
return typeof val === "object" ? JSON.stringify(val) : String(val ?? "");
|
|
3764
|
+
}
|
|
3765
|
+
return String(item);
|
|
3766
|
+
}).filter(Boolean).join("; ");
|
|
3767
|
+
}
|
|
3768
|
+
current = current[part];
|
|
3769
|
+
}
|
|
3770
|
+
if (current === null || current === void 0) return "";
|
|
3771
|
+
if (typeof current === "object" && current.url) {
|
|
3772
|
+
return current.url;
|
|
3773
|
+
}
|
|
3774
|
+
if (typeof current === "object" && !Array.isArray(current)) {
|
|
3775
|
+
if (current.documentId || current.name || current.title) {
|
|
3776
|
+
return current.name || current.title || current.documentId;
|
|
3777
|
+
}
|
|
3778
|
+
}
|
|
3779
|
+
if (Array.isArray(current)) {
|
|
3780
|
+
return current.map((item) => {
|
|
3781
|
+
if (!item) return "";
|
|
3782
|
+
if (typeof item === "object") {
|
|
3783
|
+
return item.name || item.title || item.documentId || JSON.stringify(item);
|
|
3784
|
+
}
|
|
3785
|
+
return String(item);
|
|
3786
|
+
}).filter(Boolean).join("; ");
|
|
3787
|
+
}
|
|
3788
|
+
return current;
|
|
3789
|
+
}
|
|
3541
3790
|
const csvService = ({ strapi: strapi2 }) => ({
|
|
3542
3791
|
/**
|
|
3543
3792
|
* Generates a sample template CSV with schema attribute headers and sample values
|
|
@@ -3586,9 +3835,7 @@ const csvService = ({ strapi: strapi2 }) => ({
|
|
|
3586
3835
|
}
|
|
3587
3836
|
sampleRow.push(sampleVal);
|
|
3588
3837
|
}
|
|
3589
|
-
const csvContent =
|
|
3590
|
-
${sampleRow.map(escapeCsvValue).join(",")}
|
|
3591
|
-
`;
|
|
3838
|
+
const csvContent = "\uFEFF" + headers.map(escapeCsvValue).join(",") + "\r\n" + sampleRow.map(escapeCsvValue).join(",") + "\r\n";
|
|
3592
3839
|
return {
|
|
3593
3840
|
filename: `${ct.info?.singularName || "template"}_template.csv`,
|
|
3594
3841
|
csvContent,
|
|
@@ -3596,65 +3843,60 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3596
3843
|
};
|
|
3597
3844
|
},
|
|
3598
3845
|
/**
|
|
3599
|
-
* Exports entries of a collection to CSV
|
|
3846
|
+
* Exports entries of a collection to CSV with deep nested component support,
|
|
3847
|
+
* RFC-4180 CRLF line endings, and UTF-8 BOM for 100% Microsoft Excel compatibility.
|
|
3600
3848
|
*/
|
|
3601
3849
|
async exportCollectionCsv(uid, options = {}) {
|
|
3602
3850
|
const ct = strapi2.contentTypes[uid];
|
|
3603
3851
|
if (!ct) {
|
|
3604
3852
|
throw new Error(`Content type ${uid} not found`);
|
|
3605
3853
|
}
|
|
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
|
-
}
|
|
3854
|
+
const allComponents = strapi2.components || {};
|
|
3855
|
+
const fields2 = discoverExportFields(ct, allComponents);
|
|
3629
3856
|
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
|
-
|
|
3857
|
+
const { attribute, filterOperator, filterQuery, search, documentIds } = options;
|
|
3858
|
+
if (documentIds && Array.isArray(documentIds) && documentIds.length > 0) {
|
|
3859
|
+
filters2.documentId = { $in: documentIds };
|
|
3860
|
+
}
|
|
3861
|
+
if (attribute && filterOperator && filterOperator !== "none") {
|
|
3862
|
+
const isNested = attribute.includes(".");
|
|
3863
|
+
if (filterOperator === "empty") {
|
|
3864
|
+
if (isNested) {
|
|
3865
|
+
const [parent, child] = attribute.split(".");
|
|
3866
|
+
filters2[parent] = { [child]: { $null: true } };
|
|
3867
|
+
} else {
|
|
3868
|
+
filters2[attribute] = { $null: true };
|
|
3869
|
+
}
|
|
3870
|
+
} else if (filterOperator === "not_empty") {
|
|
3871
|
+
if (isNested) {
|
|
3872
|
+
const [parent, child] = attribute.split(".");
|
|
3873
|
+
filters2[parent] = { [child]: { $notNull: true } };
|
|
3874
|
+
} else {
|
|
3875
|
+
filters2[attribute] = { $notNull: true };
|
|
3876
|
+
}
|
|
3877
|
+
} else if (filterQuery !== void 0 && filterQuery !== "") {
|
|
3878
|
+
const opMap = {
|
|
3879
|
+
equals: "$eq",
|
|
3880
|
+
notEquals: "$ne",
|
|
3881
|
+
contains: "$containsi",
|
|
3882
|
+
notContains: "$notContainsi",
|
|
3883
|
+
startsWith: "$startsWith",
|
|
3884
|
+
endsWith: "$endsWith"
|
|
3885
|
+
};
|
|
3886
|
+
const strapiOp = opMap[filterOperator] || "$eq";
|
|
3887
|
+
if (isNested) {
|
|
3888
|
+
const [parent, child] = attribute.split(".");
|
|
3889
|
+
filters2[parent] = { [child]: { [strapiOp]: filterQuery } };
|
|
3890
|
+
} else {
|
|
3891
|
+
filters2[attribute] = { [strapiOp]: filterQuery };
|
|
3892
|
+
}
|
|
3651
3893
|
}
|
|
3652
3894
|
}
|
|
3653
3895
|
if (search && search.trim() !== "") {
|
|
3654
3896
|
const q = search.trim();
|
|
3655
3897
|
const searchConditions = [];
|
|
3656
3898
|
const searchFields = ["name", "title", "email", "slug", "description"].filter(
|
|
3657
|
-
(f) =>
|
|
3899
|
+
(f) => ct.attributes?.[f] && ["string", "text", "email"].includes(ct.attributes[f].type)
|
|
3658
3900
|
);
|
|
3659
3901
|
for (const field of searchFields) {
|
|
3660
3902
|
searchConditions.push({ [field]: { $containsi: q } });
|
|
@@ -3667,34 +3909,37 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3667
3909
|
}
|
|
3668
3910
|
}
|
|
3669
3911
|
}
|
|
3670
|
-
const
|
|
3671
|
-
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3912
|
+
const deepPopulate = buildDeepPopulate(ct.attributes || {}, allComponents);
|
|
3913
|
+
let entries = [];
|
|
3914
|
+
if (ct.kind === "singleType") {
|
|
3915
|
+
const doc = await strapi2.documents(uid).findFirst({
|
|
3916
|
+
populate: deepPopulate
|
|
3917
|
+
});
|
|
3918
|
+
entries = doc ? [doc] : [];
|
|
3919
|
+
} else {
|
|
3920
|
+
entries = await strapi2.documents(uid).findMany({
|
|
3921
|
+
filters: Object.keys(filters2).length > 0 ? filters2 : void 0,
|
|
3922
|
+
limit: options.limit || 5e3,
|
|
3923
|
+
sort: { createdAt: "desc" },
|
|
3924
|
+
populate: deepPopulate
|
|
3925
|
+
});
|
|
3926
|
+
}
|
|
3677
3927
|
const lines = [];
|
|
3678
|
-
lines.push(
|
|
3928
|
+
lines.push(fields2.map((f) => escapeCsvValue(f.key)).join(","));
|
|
3679
3929
|
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
|
-
}
|
|
3930
|
+
const rowValues = fields2.map((f) => {
|
|
3931
|
+
const val = getNestedExportValue(entry, f.key);
|
|
3688
3932
|
return escapeCsvValue(val);
|
|
3689
3933
|
});
|
|
3690
3934
|
lines.push(rowValues.join(","));
|
|
3691
3935
|
}
|
|
3692
|
-
const csvContent = lines.join("\n");
|
|
3936
|
+
const csvContent = "\uFEFF" + lines.join("\r\n") + "\r\n";
|
|
3693
3937
|
const filename = `${ct.info?.singularName || uid}_export_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
3694
3938
|
return {
|
|
3695
3939
|
filename,
|
|
3696
3940
|
csvContent,
|
|
3697
|
-
count: entries ? entries.length : 0
|
|
3941
|
+
count: entries ? entries.length : 0,
|
|
3942
|
+
fields: fields2.map((f) => f.key)
|
|
3698
3943
|
};
|
|
3699
3944
|
},
|
|
3700
3945
|
/**
|
|
@@ -3734,11 +3979,76 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
3734
3979
|
const filename = `violations_${safeRuleName}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
3735
3980
|
return {
|
|
3736
3981
|
filename,
|
|
3737
|
-
csvContent: lines.join("\n"),
|
|
3982
|
+
csvContent: "\uFEFF" + lines.join("\r\n") + "\r\n",
|
|
3738
3983
|
count: scanReport.violations.length,
|
|
3739
3984
|
ruleName: scanReport.rule.name
|
|
3740
3985
|
};
|
|
3741
3986
|
},
|
|
3987
|
+
/**
|
|
3988
|
+
* Bulk Export: Exports selected entries across multiple collections or a multi-collection batch.
|
|
3989
|
+
*/
|
|
3990
|
+
async exportBulkCsv(selectedEntries) {
|
|
3991
|
+
if (!selectedEntries || selectedEntries.length === 0) {
|
|
3992
|
+
throw new Error("No entries selected for bulk export");
|
|
3993
|
+
}
|
|
3994
|
+
const byUid = {};
|
|
3995
|
+
for (const item of selectedEntries) {
|
|
3996
|
+
if (!byUid[item.uid]) byUid[item.uid] = [];
|
|
3997
|
+
byUid[item.uid].push(item.documentId);
|
|
3998
|
+
}
|
|
3999
|
+
const uids = Object.keys(byUid);
|
|
4000
|
+
if (uids.length === 1) {
|
|
4001
|
+
const uid = uids[0];
|
|
4002
|
+
return await this.exportCollectionCsv(uid, { documentIds: byUid[uid] });
|
|
4003
|
+
}
|
|
4004
|
+
const rows = [];
|
|
4005
|
+
const headers = [
|
|
4006
|
+
"Collection UID",
|
|
4007
|
+
"Collection Name",
|
|
4008
|
+
"Document ID",
|
|
4009
|
+
"Record Title / Name",
|
|
4010
|
+
"Primary Identifier",
|
|
4011
|
+
"Created At",
|
|
4012
|
+
"Updated At",
|
|
4013
|
+
"Published Status"
|
|
4014
|
+
];
|
|
4015
|
+
rows.push(headers.map(escapeCsvValue).join(","));
|
|
4016
|
+
let totalExported = 0;
|
|
4017
|
+
for (const uid of uids) {
|
|
4018
|
+
const ct = strapi2.contentTypes[uid];
|
|
4019
|
+
if (!ct) continue;
|
|
4020
|
+
const displayName = ct.info?.displayName || uid;
|
|
4021
|
+
const docIds = byUid[uid];
|
|
4022
|
+
const entries = await strapi2.documents(uid).findMany({
|
|
4023
|
+
filters: { documentId: { $in: docIds } },
|
|
4024
|
+
limit: docIds.length
|
|
4025
|
+
});
|
|
4026
|
+
for (const entry of entries || []) {
|
|
4027
|
+
const titleOrName = entry.title || entry.name || entry.subject || entry.email || `Entry #${entry.documentId}`;
|
|
4028
|
+
const identifier = entry.slug || entry.email || entry.username || entry.documentId;
|
|
4029
|
+
const isPublished = Boolean(entry.publishedAt);
|
|
4030
|
+
const row = [
|
|
4031
|
+
uid,
|
|
4032
|
+
displayName,
|
|
4033
|
+
entry.documentId,
|
|
4034
|
+
titleOrName,
|
|
4035
|
+
identifier,
|
|
4036
|
+
entry.createdAt || "",
|
|
4037
|
+
entry.updatedAt || "",
|
|
4038
|
+
isPublished ? "Published" : "Draft"
|
|
4039
|
+
];
|
|
4040
|
+
rows.push(row.map(escapeCsvValue).join(","));
|
|
4041
|
+
totalExported++;
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
const filename = `bulk_export_selected_${totalExported}_entries_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
4045
|
+
return {
|
|
4046
|
+
filename,
|
|
4047
|
+
csvContent: "\uFEFF" + rows.join("\r\n") + "\r\n",
|
|
4048
|
+
count: totalExported,
|
|
4049
|
+
isMultiCollection: true
|
|
4050
|
+
};
|
|
4051
|
+
},
|
|
3742
4052
|
/**
|
|
3743
4053
|
* Imports CSV records to update existing records or insert/override records
|
|
3744
4054
|
*/
|