strapi-plugin-bulk-updater 1.3.11 → 1.4.3
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-J21ZgJWx.mjs → BulkUpdater-CbQk_OQG.mjs} +973 -440
- package/dist/_chunks/BulkUpdater-CbQk_OQG.mjs.map +1 -0
- package/dist/_chunks/{BulkUpdater-DRHQBZZV.js → BulkUpdater-mSvmiLwc.js} +971 -438
- package/dist/_chunks/BulkUpdater-mSvmiLwc.js.map +1 -0
- package/dist/_chunks/{SettingsPage-BDObdCIY.mjs → SettingsPage-D2qYhqpF.mjs} +2 -2
- package/dist/_chunks/{SettingsPage-BDObdCIY.mjs.map → SettingsPage-D2qYhqpF.mjs.map} +1 -1
- package/dist/_chunks/{SettingsPage-M-HQ8Jtr.js → SettingsPage-Dv2DRplw.js} +2 -2
- package/dist/_chunks/{SettingsPage-M-HQ8Jtr.js.map → SettingsPage-Dv2DRplw.js.map} +1 -1
- package/dist/_chunks/{ValidationRulesTab-DuvL-K4F.js → ValidationRulesTab-BTm3QXxM.js} +11 -1
- package/dist/_chunks/ValidationRulesTab-BTm3QXxM.js.map +1 -0
- package/dist/_chunks/{ValidationRulesTab-VeRr-vxa.mjs → ValidationRulesTab-BtOCWK8E.mjs} +12 -2
- package/dist/_chunks/ValidationRulesTab-BtOCWK8E.mjs.map +1 -0
- package/dist/admin/index.js +2 -2
- package/dist/admin/index.mjs +2 -2
- package/dist/server/index.js +709 -198
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +709 -198
- package/dist/server/index.mjs.map +1 -1
- package/package.json +3 -3
- package/dist/_chunks/BulkUpdater-DRHQBZZV.js.map +0 -1
- package/dist/_chunks/BulkUpdater-J21ZgJWx.mjs.map +0 -1
- package/dist/_chunks/ValidationRulesTab-DuvL-K4F.js.map +0 -1
- package/dist/_chunks/ValidationRulesTab-VeRr-vxa.mjs.map +0 -1
package/dist/server/index.js
CHANGED
|
@@ -911,9 +911,72 @@ function discoverComponentAttributes(strapi, compUid, prefix, currentDepth, maxD
|
|
|
911
911
|
}
|
|
912
912
|
return Array.from(new Set(discoveredAttrs));
|
|
913
913
|
}
|
|
914
|
+
function parseDynamicZonePath(path) {
|
|
915
|
+
if (!path) return { cleanPath: "", rootKey: "", subPath: "" };
|
|
916
|
+
const bracketMatch = path.match(/^([^.[]+)\[([^\]]+)\]\.(.+)$/);
|
|
917
|
+
if (bracketMatch) {
|
|
918
|
+
const rootKey = bracketMatch[1];
|
|
919
|
+
const selector = bracketMatch[2];
|
|
920
|
+
const subPath = bracketMatch[3];
|
|
921
|
+
const cleanPath = `${rootKey}.${subPath}`;
|
|
922
|
+
if (/^\d+$/.test(selector)) {
|
|
923
|
+
return { cleanPath, rootKey, subPath, targetIndex: parseInt(selector, 10) };
|
|
924
|
+
}
|
|
925
|
+
return { cleanPath, rootKey, subPath, targetComponentUid: selector };
|
|
926
|
+
}
|
|
927
|
+
const parts = path.split(".");
|
|
928
|
+
return {
|
|
929
|
+
cleanPath: path,
|
|
930
|
+
rootKey: parts[0] || "",
|
|
931
|
+
subPath: parts.slice(1).join(".")
|
|
932
|
+
};
|
|
933
|
+
}
|
|
934
|
+
function formatComponentName(compUid) {
|
|
935
|
+
if (!compUid) return "Component";
|
|
936
|
+
const name = compUid.includes(".") ? compUid.split(".")[1] : compUid;
|
|
937
|
+
return name.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
|
|
938
|
+
}
|
|
939
|
+
function getAllNestedValues(obj, path) {
|
|
940
|
+
if (!obj || !path) return [];
|
|
941
|
+
const parsed = parseDynamicZonePath(path);
|
|
942
|
+
const parts = parsed.cleanPath.split(".");
|
|
943
|
+
const [head, ...tail] = parts;
|
|
944
|
+
const rootTarget = obj[head];
|
|
945
|
+
if (Array.isArray(rootTarget)) {
|
|
946
|
+
const results = [];
|
|
947
|
+
rootTarget.forEach((item, idx) => {
|
|
948
|
+
if (!item || typeof item !== "object") return;
|
|
949
|
+
if (parsed.targetIndex !== void 0 && idx !== parsed.targetIndex) return;
|
|
950
|
+
if (parsed.targetComponentUid && item.__component !== parsed.targetComponentUid) return;
|
|
951
|
+
const subVal = tail.length > 0 ? getDeepProperty(item, tail.join(".")) : item;
|
|
952
|
+
if (subVal !== void 0) {
|
|
953
|
+
results.push({
|
|
954
|
+
index: idx,
|
|
955
|
+
componentUid: item.__component,
|
|
956
|
+
componentName: formatComponentName(item.__component),
|
|
957
|
+
value: subVal,
|
|
958
|
+
path: `${head}[#${idx}${item.__component ? ` ${formatComponentName(item.__component)}` : ""}].${tail.join(".")}`
|
|
959
|
+
});
|
|
960
|
+
}
|
|
961
|
+
});
|
|
962
|
+
return results;
|
|
963
|
+
}
|
|
964
|
+
const singleVal = getDeepProperty(obj, parsed.cleanPath);
|
|
965
|
+
if (singleVal !== void 0) {
|
|
966
|
+
return [
|
|
967
|
+
{
|
|
968
|
+
index: 0,
|
|
969
|
+
value: singleVal,
|
|
970
|
+
path: parsed.cleanPath
|
|
971
|
+
}
|
|
972
|
+
];
|
|
973
|
+
}
|
|
974
|
+
return [];
|
|
975
|
+
}
|
|
914
976
|
function isDeepComponentField(contentType, allComponents, attribute) {
|
|
915
|
-
if (!attribute || !attribute.includes(".")) return false;
|
|
916
|
-
const
|
|
977
|
+
if (!attribute || !attribute.includes(".") && !attribute.includes("[")) return false;
|
|
978
|
+
const parsed = parseDynamicZonePath(attribute);
|
|
979
|
+
const parts = parsed.cleanPath.split(".");
|
|
917
980
|
function checkPath(currentSchema, pathParts) {
|
|
918
981
|
if (pathParts.length === 0) return true;
|
|
919
982
|
if (!currentSchema || !currentSchema.attributes) return false;
|
|
@@ -929,6 +992,10 @@ function isDeepComponentField(contentType, allComponents, attribute) {
|
|
|
929
992
|
}
|
|
930
993
|
if (attrDef.type === "dynamiczone") {
|
|
931
994
|
const compUids = Array.isArray(attrDef.components) ? attrDef.components : [];
|
|
995
|
+
if (parsed.targetComponentUid) {
|
|
996
|
+
const comp = allComponents[parsed.targetComponentUid];
|
|
997
|
+
return comp ? checkPath(comp, tail) : false;
|
|
998
|
+
}
|
|
932
999
|
return compUids.some((compUid) => {
|
|
933
1000
|
const comp = allComponents[compUid];
|
|
934
1001
|
return checkPath(comp, tail);
|
|
@@ -940,8 +1007,9 @@ function isDeepComponentField(contentType, allComponents, attribute) {
|
|
|
940
1007
|
}
|
|
941
1008
|
function getLeafAttributeSchema(contentType, allComponents, attribute) {
|
|
942
1009
|
if (!attribute) return null;
|
|
943
|
-
|
|
944
|
-
|
|
1010
|
+
const parsed = parseDynamicZonePath(attribute);
|
|
1011
|
+
if (!parsed.cleanPath.includes(".")) return contentType?.attributes?.[parsed.cleanPath] || null;
|
|
1012
|
+
const parts = parsed.cleanPath.split(".");
|
|
945
1013
|
function resolveLeaf(currentSchema, pathParts) {
|
|
946
1014
|
if (!currentSchema || !currentSchema.attributes) return null;
|
|
947
1015
|
const [head, ...tail] = pathParts;
|
|
@@ -955,6 +1023,9 @@ function getLeafAttributeSchema(contentType, allComponents, attribute) {
|
|
|
955
1023
|
}
|
|
956
1024
|
if (attrDef.type === "dynamiczone") {
|
|
957
1025
|
const compUids = Array.isArray(attrDef.components) ? attrDef.components : [];
|
|
1026
|
+
if (parsed.targetComponentUid) {
|
|
1027
|
+
return resolveLeaf(allComponents[parsed.targetComponentUid], tail);
|
|
1028
|
+
}
|
|
958
1029
|
for (const compUid of compUids) {
|
|
959
1030
|
const found = resolveLeaf(allComponents[compUid], tail);
|
|
960
1031
|
if (found) return found;
|
|
@@ -964,22 +1035,28 @@ function getLeafAttributeSchema(contentType, allComponents, attribute) {
|
|
|
964
1035
|
}
|
|
965
1036
|
return resolveLeaf(contentType, parts);
|
|
966
1037
|
}
|
|
967
|
-
function setDeepProperty(obj, pathParts, value) {
|
|
1038
|
+
function setDeepProperty(obj, pathParts, value, options) {
|
|
968
1039
|
if (pathParts.length === 0) return value;
|
|
969
1040
|
const [head, ...tail] = pathParts;
|
|
970
1041
|
if (Array.isArray(obj)) {
|
|
971
1042
|
if (/^\d+$/.test(head)) {
|
|
972
1043
|
const idx = parseInt(head, 10);
|
|
973
1044
|
const copy = [...obj];
|
|
974
|
-
copy[idx] = setDeepProperty(copy[idx] || {}, tail, value);
|
|
1045
|
+
copy[idx] = setDeepProperty(copy[idx] || {}, tail, value, options);
|
|
975
1046
|
return copy;
|
|
976
1047
|
}
|
|
977
|
-
return obj.map((item) => {
|
|
1048
|
+
return obj.map((item, idx) => {
|
|
978
1049
|
if (!item || typeof item !== "object") return item;
|
|
1050
|
+
if (options?.targetIndexes && options.targetIndexes.length > 0) {
|
|
1051
|
+
if (!options.targetIndexes.includes(idx)) return item;
|
|
1052
|
+
}
|
|
1053
|
+
if (options?.targetComponentUid) {
|
|
1054
|
+
if (item.__component !== options.targetComponentUid) return item;
|
|
1055
|
+
}
|
|
979
1056
|
if (head in item || tail.length > 0 && typeof item[head] === "object") {
|
|
980
1057
|
return {
|
|
981
1058
|
...item,
|
|
982
|
-
[head]: setDeepProperty(item[head], tail, value)
|
|
1059
|
+
[head]: setDeepProperty(item[head], tail, value, options)
|
|
983
1060
|
};
|
|
984
1061
|
}
|
|
985
1062
|
return item;
|
|
@@ -991,16 +1068,17 @@ function setDeepProperty(obj, pathParts, value) {
|
|
|
991
1068
|
} else {
|
|
992
1069
|
const subTarget = current[head];
|
|
993
1070
|
if (Array.isArray(subTarget)) {
|
|
994
|
-
current[head] = setDeepProperty(subTarget, tail, value);
|
|
1071
|
+
current[head] = setDeepProperty(subTarget, tail, value, options);
|
|
995
1072
|
} else {
|
|
996
|
-
current[head] = setDeepProperty(subTarget || {}, tail, value);
|
|
1073
|
+
current[head] = setDeepProperty(subTarget || {}, tail, value, options);
|
|
997
1074
|
}
|
|
998
1075
|
}
|
|
999
1076
|
return current;
|
|
1000
1077
|
}
|
|
1001
1078
|
function getDeepProperty(obj, path) {
|
|
1002
1079
|
if (!obj || !path) return void 0;
|
|
1003
|
-
const
|
|
1080
|
+
const parsed = parseDynamicZonePath(path);
|
|
1081
|
+
const parts = parsed.cleanPath.split(".");
|
|
1004
1082
|
function extract(current, remainingParts) {
|
|
1005
1083
|
if (current === void 0 || current === null) return void 0;
|
|
1006
1084
|
if (remainingParts.length === 0) return current;
|
|
@@ -1009,7 +1087,10 @@ function getDeepProperty(obj, path) {
|
|
|
1009
1087
|
if (/^\d+$/.test(head)) {
|
|
1010
1088
|
return extract(current[parseInt(head, 10)], tail);
|
|
1011
1089
|
}
|
|
1012
|
-
for (
|
|
1090
|
+
for (let idx = 0; idx < current.length; idx++) {
|
|
1091
|
+
const item = current[idx];
|
|
1092
|
+
if (parsed.targetIndex !== void 0 && idx !== parsed.targetIndex) continue;
|
|
1093
|
+
if (parsed.targetComponentUid && item?.__component !== parsed.targetComponentUid) continue;
|
|
1013
1094
|
const found = extract(item?.[head], tail);
|
|
1014
1095
|
if (found !== void 0 && found !== null) return found;
|
|
1015
1096
|
}
|
|
@@ -1174,7 +1255,12 @@ async function executeBatchInternal(strapi, {
|
|
|
1174
1255
|
for (const item of selectedEntries) {
|
|
1175
1256
|
if (!item.uid || !item.documentId) continue;
|
|
1176
1257
|
const key = `${item.uid}@@@${item.documentId}`;
|
|
1177
|
-
targetsMap.set(key, {
|
|
1258
|
+
targetsMap.set(key, {
|
|
1259
|
+
uid: item.uid,
|
|
1260
|
+
documentId: item.documentId,
|
|
1261
|
+
dynamicZoneIndexes: item.dynamicZoneIndexes,
|
|
1262
|
+
targetComponentUid: item.targetComponentUid
|
|
1263
|
+
});
|
|
1178
1264
|
}
|
|
1179
1265
|
if (targetsMap.size === 0) {
|
|
1180
1266
|
throw new Error("No collections or entries were selected for update.");
|
|
@@ -1233,10 +1319,13 @@ async function executeBatchInternal(strapi, {
|
|
|
1233
1319
|
try {
|
|
1234
1320
|
const currentEntry = await getDocumentDeduplicated(target.uid, target.documentId);
|
|
1235
1321
|
const entryLabel = currentEntry?.name || currentEntry?.title || currentEntry?.email || `Entry #${target.documentId}`;
|
|
1236
|
-
const
|
|
1322
|
+
const parsed = parseDynamicZonePath(attribute);
|
|
1323
|
+
const isNested = attribute.includes(".") || attribute.includes("[");
|
|
1237
1324
|
const isComponentNested = isNested && isDeepComponentField(contentType, strapi.components, attribute);
|
|
1238
|
-
const parts = isNested ?
|
|
1239
|
-
const rootKey =
|
|
1325
|
+
const parts = isNested ? parsed.cleanPath.split(".") : [];
|
|
1326
|
+
const rootKey = parsed.rootKey;
|
|
1327
|
+
const targetComponentUid = target.targetComponentUid || parsed.targetComponentUid;
|
|
1328
|
+
const targetIndexes = target.dynamicZoneIndexes;
|
|
1240
1329
|
const attrDef = contentType?.attributes?.[attribute];
|
|
1241
1330
|
const hasAttribute = Boolean(attrDef) || isComponentNested;
|
|
1242
1331
|
if (hasAttribute) {
|
|
@@ -1249,12 +1338,25 @@ async function executeBatchInternal(strapi, {
|
|
|
1249
1338
|
const currentEntryWithPopulate = await strapi.documents(target.uid).findOne(findOneArgs);
|
|
1250
1339
|
const isRootDz = contentType?.attributes?.[rootKey]?.type === "dynamiczone";
|
|
1251
1340
|
const existingRoot = currentEntryWithPopulate?.[rootKey] || (isRootDz ? [] : {});
|
|
1252
|
-
|
|
1341
|
+
let affectedMatches = [];
|
|
1342
|
+
if (isRootDz && Array.isArray(existingRoot)) {
|
|
1343
|
+
affectedMatches = getAllNestedValues({ [rootKey]: existingRoot }, attribute);
|
|
1344
|
+
if (targetIndexes && targetIndexes.length > 0) {
|
|
1345
|
+
affectedMatches = affectedMatches.filter((m) => targetIndexes.includes(m.index));
|
|
1346
|
+
}
|
|
1347
|
+
if (targetComponentUid) {
|
|
1348
|
+
affectedMatches = affectedMatches.filter((m) => m.componentUid === targetComponentUid);
|
|
1349
|
+
}
|
|
1350
|
+
}
|
|
1351
|
+
const currentVal2 = isRootDz ? affectedMatches.length > 0 ? affectedMatches.map((m) => m.value).join(", ") : getDeepProperty(existingRoot, parts.slice(1).join(".")) : getDeepProperty(existingRoot, parts.slice(1).join("."));
|
|
1253
1352
|
let newValue2 = value;
|
|
1254
1353
|
if (regexObj && typeof currentVal2 === "string") {
|
|
1255
1354
|
newValue2 = currentVal2.replace(regexObj, value);
|
|
1256
1355
|
}
|
|
1257
|
-
const updatedRoot = setDeepProperty(existingRoot, parts.slice(1), newValue2
|
|
1356
|
+
const updatedRoot = setDeepProperty(existingRoot, parts.slice(1), newValue2, {
|
|
1357
|
+
targetIndexes,
|
|
1358
|
+
targetComponentUid
|
|
1359
|
+
});
|
|
1258
1360
|
await strapi.documents(target.uid).update({
|
|
1259
1361
|
documentId: target.documentId,
|
|
1260
1362
|
data: {
|
|
@@ -1278,6 +1380,14 @@ async function executeBatchInternal(strapi, {
|
|
|
1278
1380
|
userId: currentUser?.id,
|
|
1279
1381
|
statusOverride: publishOnApproval && isDraftAndPublishComp ? "published" : void 0
|
|
1280
1382
|
});
|
|
1383
|
+
const dzDiffs = isRootDz && affectedMatches.length > 0 ? affectedMatches.map((m) => ({
|
|
1384
|
+
index: m.index,
|
|
1385
|
+
componentUid: m.componentUid,
|
|
1386
|
+
componentName: m.componentName,
|
|
1387
|
+
attribute: `${rootKey}[#${m.index} ${m.componentName}].${parts.slice(1).join(".")}`,
|
|
1388
|
+
oldValue: m.value,
|
|
1389
|
+
newValue: regexObj && typeof m.value === "string" ? m.value.replace(regexObj, value) : value
|
|
1390
|
+
})) : [];
|
|
1281
1391
|
return {
|
|
1282
1392
|
uid: target.uid,
|
|
1283
1393
|
collectionName: displayName,
|
|
@@ -1287,7 +1397,8 @@ async function executeBatchInternal(strapi, {
|
|
|
1287
1397
|
oldValue: currentVal2 ?? null,
|
|
1288
1398
|
newValue: newValue2,
|
|
1289
1399
|
status: "updated",
|
|
1290
|
-
message: `Successfully updated nested field ${attribute}
|
|
1400
|
+
message: isRootDz ? `Successfully updated ${affectedMatches.length} dynamic zone item(s) in ${attribute}` : `Successfully updated nested field ${attribute}`,
|
|
1401
|
+
dynamicZoneDiffs: dzDiffs
|
|
1291
1402
|
};
|
|
1292
1403
|
}
|
|
1293
1404
|
if (attrDef.type === "relation") {
|
|
@@ -1316,13 +1427,13 @@ async function executeBatchInternal(strapi, {
|
|
|
1316
1427
|
newDisplay = "(cleared / unlinked)";
|
|
1317
1428
|
} else {
|
|
1318
1429
|
try {
|
|
1319
|
-
const
|
|
1320
|
-
if (
|
|
1321
|
-
mode =
|
|
1322
|
-
targetDocIds = Array.isArray(
|
|
1323
|
-
newDisplay =
|
|
1324
|
-
} else if (Array.isArray(
|
|
1325
|
-
targetDocIds =
|
|
1430
|
+
const parsed2 = JSON.parse(value);
|
|
1431
|
+
if (parsed2 && typeof parsed2 === "object" && parsed2.mode) {
|
|
1432
|
+
mode = parsed2.mode;
|
|
1433
|
+
targetDocIds = Array.isArray(parsed2.documentIds) ? parsed2.documentIds : parsed2.documentId ? [parsed2.documentId] : [];
|
|
1434
|
+
newDisplay = parsed2.label || targetDocIds.join(", ") || `[${mode}]`;
|
|
1435
|
+
} else if (Array.isArray(parsed2)) {
|
|
1436
|
+
targetDocIds = parsed2;
|
|
1326
1437
|
newDisplay = targetDocIds.join(", ");
|
|
1327
1438
|
} else {
|
|
1328
1439
|
targetDocIds = [value];
|
|
@@ -1466,6 +1577,26 @@ async function executeBatchInternal(strapi, {
|
|
|
1466
1577
|
...r,
|
|
1467
1578
|
isLive: r.status === "updated"
|
|
1468
1579
|
}));
|
|
1580
|
+
const flattenedDiffs = [];
|
|
1581
|
+
for (const r of results) {
|
|
1582
|
+
if (r.dynamicZoneDiffs && r.dynamicZoneDiffs.length > 0) {
|
|
1583
|
+
for (const dz of r.dynamicZoneDiffs) {
|
|
1584
|
+
flattenedDiffs.push({
|
|
1585
|
+
uid: r.uid,
|
|
1586
|
+
collectionName: r.collectionName,
|
|
1587
|
+
documentId: r.documentId,
|
|
1588
|
+
label: `${r.label} [Block #${dz.index} (${dz.componentName})]`,
|
|
1589
|
+
attribute: dz.attribute,
|
|
1590
|
+
oldValue: dz.oldValue,
|
|
1591
|
+
newValue: dz.newValue,
|
|
1592
|
+
status: "updated",
|
|
1593
|
+
message: `Updated ${dz.attribute}`
|
|
1594
|
+
});
|
|
1595
|
+
}
|
|
1596
|
+
} else {
|
|
1597
|
+
flattenedDiffs.push(r);
|
|
1598
|
+
}
|
|
1599
|
+
}
|
|
1469
1600
|
const batchSummary = {
|
|
1470
1601
|
batchId,
|
|
1471
1602
|
attribute,
|
|
@@ -1477,7 +1608,7 @@ async function executeBatchInternal(strapi, {
|
|
|
1477
1608
|
};
|
|
1478
1609
|
await strapi.plugin("bulk-updater").service("audit").recordBatch(
|
|
1479
1610
|
batchSummary,
|
|
1480
|
-
|
|
1611
|
+
flattenedDiffs,
|
|
1481
1612
|
currentUser
|
|
1482
1613
|
);
|
|
1483
1614
|
return {
|
|
@@ -1745,6 +1876,19 @@ const bulkUpdaterService = ({ strapi }) => {
|
|
|
1745
1876
|
colAttributesMeta
|
|
1746
1877
|
);
|
|
1747
1878
|
nestedComponentAttrs.push(...compAttrs);
|
|
1879
|
+
const scopedAttrs = discoverComponentAttributes(
|
|
1880
|
+
strapi,
|
|
1881
|
+
compUid,
|
|
1882
|
+
`${attrKey}[${compUid}]`,
|
|
1883
|
+
1,
|
|
1884
|
+
5,
|
|
1885
|
+
systemFields,
|
|
1886
|
+
allContentTypes,
|
|
1887
|
+
relationsByAttr,
|
|
1888
|
+
allRelationsMeta,
|
|
1889
|
+
colAttributesMeta
|
|
1890
|
+
);
|
|
1891
|
+
nestedComponentAttrs.push(...scopedAttrs);
|
|
1748
1892
|
}
|
|
1749
1893
|
return false;
|
|
1750
1894
|
}
|
|
@@ -1959,13 +2103,20 @@ const bulkUpdaterService = ({ strapi }) => {
|
|
|
1959
2103
|
const attrs = contentType.attributes || {};
|
|
1960
2104
|
let colHasAttribute = false;
|
|
1961
2105
|
let isNestedAttr = false;
|
|
2106
|
+
let isRootDz = false;
|
|
1962
2107
|
let parts = [];
|
|
1963
2108
|
if (targetFilterAttr) {
|
|
1964
|
-
|
|
2109
|
+
const parsed = parseDynamicZonePath(targetFilterAttr);
|
|
2110
|
+
if (parsed.rootKey && contentType.attributes?.[parsed.rootKey]?.type === "dynamiczone") {
|
|
2111
|
+
isRootDz = true;
|
|
2112
|
+
colHasAttribute = true;
|
|
2113
|
+
isNestedAttr = true;
|
|
2114
|
+
parts = parsed.cleanPath.split(".");
|
|
2115
|
+
} else if (targetFilterAttr.includes(".") || targetFilterAttr.includes("[")) {
|
|
1965
2116
|
isNestedAttr = isDeepComponentField(contentType, strapi.components, targetFilterAttr);
|
|
1966
2117
|
if (isNestedAttr) {
|
|
1967
2118
|
colHasAttribute = true;
|
|
1968
|
-
parts =
|
|
2119
|
+
parts = parsed.cleanPath.split(".");
|
|
1969
2120
|
}
|
|
1970
2121
|
} else if (attrs[targetFilterAttr]) {
|
|
1971
2122
|
colHasAttribute = true;
|
|
@@ -2022,8 +2173,11 @@ const bulkUpdaterService = ({ strapi }) => {
|
|
|
2022
2173
|
}
|
|
2023
2174
|
if (condition !== void 0) {
|
|
2024
2175
|
if (isNestedAttr) {
|
|
2025
|
-
|
|
2026
|
-
|
|
2176
|
+
if (isRootDz) ;
|
|
2177
|
+
else {
|
|
2178
|
+
const deepFilter = buildDeepFilter(parts, condition);
|
|
2179
|
+
Object.assign(filters, deepFilter);
|
|
2180
|
+
}
|
|
2027
2181
|
} else {
|
|
2028
2182
|
if (normOp === "empty" && isStringLike) {
|
|
2029
2183
|
filters.$or = [
|
|
@@ -2097,39 +2251,100 @@ const bulkUpdaterService = ({ strapi }) => {
|
|
|
2097
2251
|
} else {
|
|
2098
2252
|
sortClause["createdAt"] = "desc";
|
|
2099
2253
|
}
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
}
|
|
2109
|
-
const [entries, totalEntries] = await Promise.all([
|
|
2110
|
-
strapi.documents(uid).findMany(findManyArgs),
|
|
2111
|
-
strapi.documents(uid).count({
|
|
2112
|
-
filters: Object.keys(filters).length > 0 ? filters : void 0
|
|
2113
|
-
})
|
|
2114
|
-
]);
|
|
2115
|
-
const totalPages = Math.ceil(totalEntries / limit);
|
|
2116
|
-
result = {
|
|
2117
|
-
uid,
|
|
2118
|
-
displayName: contentType.info?.displayName || uid,
|
|
2119
|
-
isSingleType: false,
|
|
2120
|
-
draftAndPublish: Boolean(contentType.options?.draftAndPublish),
|
|
2121
|
-
entries: (entries || []).map((entry) => ({
|
|
2122
|
-
id: entry.id,
|
|
2123
|
-
documentId: entry.documentId,
|
|
2124
|
-
...entry
|
|
2125
|
-
})),
|
|
2126
|
-
pagination: {
|
|
2127
|
-
page: currentPage,
|
|
2128
|
-
pageSize: limit,
|
|
2129
|
-
total: totalEntries,
|
|
2130
|
-
pageCount: totalPages
|
|
2254
|
+
if (isRootDz && normOp && normOp !== "none") {
|
|
2255
|
+
const findManyArgs = {
|
|
2256
|
+
limit: 500,
|
|
2257
|
+
filters: Object.keys(filters).length > 0 ? filters : void 0,
|
|
2258
|
+
sort: sortClause
|
|
2259
|
+
};
|
|
2260
|
+
if (populateOption) {
|
|
2261
|
+
findManyArgs.populate = populateOption;
|
|
2131
2262
|
}
|
|
2132
|
-
|
|
2263
|
+
const rawEntries = await strapi.documents(uid).findMany(findManyArgs);
|
|
2264
|
+
const qVal = (filterQuery || "").trim().toLowerCase();
|
|
2265
|
+
const passesDzFilter = (entry) => {
|
|
2266
|
+
const matches = getAllNestedValues(entry, targetFilterAttr);
|
|
2267
|
+
if (normOp === "empty") {
|
|
2268
|
+
return matches.length === 0 || matches.every((m) => m.value === void 0 || m.value === null || String(m.value).trim() === "");
|
|
2269
|
+
}
|
|
2270
|
+
if (normOp === "notempty") {
|
|
2271
|
+
return matches.some((m) => m.value !== void 0 && m.value !== null && String(m.value).trim() !== "");
|
|
2272
|
+
}
|
|
2273
|
+
return matches.some((m) => {
|
|
2274
|
+
if (m.value === void 0 || m.value === null) return false;
|
|
2275
|
+
const valStr = String(m.value).toLowerCase();
|
|
2276
|
+
switch (normOp) {
|
|
2277
|
+
case "equals":
|
|
2278
|
+
return valStr === qVal;
|
|
2279
|
+
case "notequals":
|
|
2280
|
+
return valStr !== qVal;
|
|
2281
|
+
case "contains":
|
|
2282
|
+
return valStr.includes(qVal);
|
|
2283
|
+
case "notcontains":
|
|
2284
|
+
return !valStr.includes(qVal);
|
|
2285
|
+
case "startswith":
|
|
2286
|
+
return valStr.startsWith(qVal);
|
|
2287
|
+
case "endswith":
|
|
2288
|
+
return valStr.endsWith(qVal);
|
|
2289
|
+
default:
|
|
2290
|
+
return true;
|
|
2291
|
+
}
|
|
2292
|
+
});
|
|
2293
|
+
};
|
|
2294
|
+
const matchedEntries = (rawEntries || []).filter(passesDzFilter);
|
|
2295
|
+
const totalEntries = matchedEntries.length;
|
|
2296
|
+
const paginatedEntries = matchedEntries.slice(start, start + limit);
|
|
2297
|
+
result = {
|
|
2298
|
+
uid,
|
|
2299
|
+
displayName: contentType.info?.displayName || uid,
|
|
2300
|
+
isSingleType: false,
|
|
2301
|
+
draftAndPublish: Boolean(contentType.options?.draftAndPublish),
|
|
2302
|
+
entries: paginatedEntries.map((entry) => ({
|
|
2303
|
+
id: entry.id,
|
|
2304
|
+
documentId: entry.documentId,
|
|
2305
|
+
...entry
|
|
2306
|
+
})),
|
|
2307
|
+
pagination: {
|
|
2308
|
+
page: currentPage,
|
|
2309
|
+
pageSize: limit,
|
|
2310
|
+
total: totalEntries,
|
|
2311
|
+
pageCount: Math.ceil(totalEntries / limit) || 1
|
|
2312
|
+
}
|
|
2313
|
+
};
|
|
2314
|
+
} else {
|
|
2315
|
+
const findManyArgs = {
|
|
2316
|
+
start,
|
|
2317
|
+
limit,
|
|
2318
|
+
filters: Object.keys(filters).length > 0 ? filters : void 0,
|
|
2319
|
+
sort: sortClause
|
|
2320
|
+
};
|
|
2321
|
+
if (populateOption) {
|
|
2322
|
+
findManyArgs.populate = populateOption;
|
|
2323
|
+
}
|
|
2324
|
+
const [entries, totalEntries] = await Promise.all([
|
|
2325
|
+
strapi.documents(uid).findMany(findManyArgs),
|
|
2326
|
+
strapi.documents(uid).count({
|
|
2327
|
+
filters: Object.keys(filters).length > 0 ? filters : void 0
|
|
2328
|
+
})
|
|
2329
|
+
]);
|
|
2330
|
+
result = {
|
|
2331
|
+
uid,
|
|
2332
|
+
displayName: contentType.info?.displayName || uid,
|
|
2333
|
+
isSingleType: false,
|
|
2334
|
+
draftAndPublish: Boolean(contentType.options?.draftAndPublish),
|
|
2335
|
+
entries: (entries || []).map((entry) => ({
|
|
2336
|
+
id: entry.id,
|
|
2337
|
+
documentId: entry.documentId,
|
|
2338
|
+
...entry
|
|
2339
|
+
})),
|
|
2340
|
+
pagination: {
|
|
2341
|
+
page: currentPage,
|
|
2342
|
+
pageSize: limit,
|
|
2343
|
+
total: totalEntries,
|
|
2344
|
+
pageCount: Math.ceil(totalEntries / limit) || 1
|
|
2345
|
+
}
|
|
2346
|
+
};
|
|
2347
|
+
}
|
|
2133
2348
|
}
|
|
2134
2349
|
if (queryCache.size >= MAX_QUERY_CACHE_SIZE) {
|
|
2135
2350
|
const toDelete = Array.from(queryCache.keys()).slice(0, 100);
|
|
@@ -4160,6 +4375,99 @@ function parseCsv(csvText) {
|
|
|
4160
4375
|
}
|
|
4161
4376
|
return records;
|
|
4162
4377
|
}
|
|
4378
|
+
const LIFECYCLE_STATUS_WORDS = /* @__PURE__ */ new Set([
|
|
4379
|
+
"pending review",
|
|
4380
|
+
"pending_review",
|
|
4381
|
+
"pending approval",
|
|
4382
|
+
"pending_approval",
|
|
4383
|
+
"approved",
|
|
4384
|
+
"rejected",
|
|
4385
|
+
"draft",
|
|
4386
|
+
"published",
|
|
4387
|
+
"modified",
|
|
4388
|
+
"supported",
|
|
4389
|
+
"ignored"
|
|
4390
|
+
]);
|
|
4391
|
+
function isValidDocId(val) {
|
|
4392
|
+
if (!val || typeof val !== "string") return false;
|
|
4393
|
+
const trimmed = val.trim();
|
|
4394
|
+
if (!trimmed) return false;
|
|
4395
|
+
if (LIFECYCLE_STATUS_WORDS.has(trimmed.toLowerCase())) return false;
|
|
4396
|
+
if (/\s/.test(trimmed)) return false;
|
|
4397
|
+
return /^[a-zA-Z0-9_-]+$/.test(trimmed);
|
|
4398
|
+
}
|
|
4399
|
+
function extractDocId(row) {
|
|
4400
|
+
if (!row || typeof row !== "object") return "";
|
|
4401
|
+
const candidates = [
|
|
4402
|
+
row.documentId,
|
|
4403
|
+
row.document_id,
|
|
4404
|
+
row["Document ID"],
|
|
4405
|
+
row["Document Id"],
|
|
4406
|
+
row["document_id"],
|
|
4407
|
+
row.docId,
|
|
4408
|
+
row["docId"],
|
|
4409
|
+
row.id,
|
|
4410
|
+
row.ID,
|
|
4411
|
+
row.Id
|
|
4412
|
+
];
|
|
4413
|
+
for (const c of candidates) {
|
|
4414
|
+
if (c !== void 0 && c !== null) {
|
|
4415
|
+
const str = String(c).trim();
|
|
4416
|
+
if (isValidDocId(str)) {
|
|
4417
|
+
return str;
|
|
4418
|
+
}
|
|
4419
|
+
}
|
|
4420
|
+
}
|
|
4421
|
+
return "";
|
|
4422
|
+
}
|
|
4423
|
+
function extractLocale(row) {
|
|
4424
|
+
if (!row || typeof row !== "object") return void 0;
|
|
4425
|
+
const candidates = [row.locale, row.Locale, row["locale"], row["Locale"]];
|
|
4426
|
+
for (const c of candidates) {
|
|
4427
|
+
if (c !== void 0 && c !== null) {
|
|
4428
|
+
const str = String(c).trim();
|
|
4429
|
+
if (str && str.toLowerCase() !== "null" && str.toLowerCase() !== "undefined") {
|
|
4430
|
+
return str;
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
}
|
|
4434
|
+
return void 0;
|
|
4435
|
+
}
|
|
4436
|
+
function extractDetailedErrorMessage(err) {
|
|
4437
|
+
if (!err) return "Error processing row";
|
|
4438
|
+
const detailsErrors = err.details?.errors || err.error?.details?.errors;
|
|
4439
|
+
if (Array.isArray(detailsErrors) && detailsErrors.length > 0) {
|
|
4440
|
+
const messages = detailsErrors.map((e) => {
|
|
4441
|
+
if (!e) return "";
|
|
4442
|
+
const path = Array.isArray(e.path) ? e.path.join(".") : e.path;
|
|
4443
|
+
const msg = e.message || (typeof e === "string" ? e : JSON.stringify(e));
|
|
4444
|
+
return path ? `${path}: ${msg}` : msg;
|
|
4445
|
+
}).filter(Boolean);
|
|
4446
|
+
if (messages.length > 0) return messages.join("; ");
|
|
4447
|
+
}
|
|
4448
|
+
if (Array.isArray(err.inner) && err.inner.length > 0) {
|
|
4449
|
+
const messages = err.inner.map((e) => {
|
|
4450
|
+
if (!e) return "";
|
|
4451
|
+
const path = e.path;
|
|
4452
|
+
const msg = e.message || (typeof e === "string" ? e : JSON.stringify(e));
|
|
4453
|
+
return path ? `${path}: ${msg}` : msg;
|
|
4454
|
+
}).filter(Boolean);
|
|
4455
|
+
if (messages.length > 0) return messages.join("; ");
|
|
4456
|
+
}
|
|
4457
|
+
if (Array.isArray(err.errors) && err.errors.length > 0) {
|
|
4458
|
+
return err.errors.join("; ");
|
|
4459
|
+
}
|
|
4460
|
+
if (typeof err.details === "string" && err.details.trim()) {
|
|
4461
|
+
return err.details;
|
|
4462
|
+
}
|
|
4463
|
+
if (err.details?.message) {
|
|
4464
|
+
return err.details.message;
|
|
4465
|
+
}
|
|
4466
|
+
if (err.message && err.message !== "[object Object]") {
|
|
4467
|
+
return err.message;
|
|
4468
|
+
}
|
|
4469
|
+
return "Error processing row";
|
|
4470
|
+
}
|
|
4163
4471
|
function discoverExportFields(ct, allComponents, maxDepth = 4) {
|
|
4164
4472
|
const fields = [{ key: "documentId", label: "documentId", type: "string" }];
|
|
4165
4473
|
const excluded = ["createdBy", "updatedBy", "localizations"];
|
|
@@ -4555,13 +4863,63 @@ const csvService = ({ strapi }) => ({
|
|
|
4555
4863
|
const batchId = `csv_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
4556
4864
|
const userEmail = user?.email || "admin@strapi.io";
|
|
4557
4865
|
const userName = user ? `${user.firstname || ""} ${user.lastname || ""}`.trim() : "CSV Importer";
|
|
4866
|
+
const findExistingDocument = async (targetDocId, docLocale) => {
|
|
4867
|
+
if (!targetDocId) return null;
|
|
4868
|
+
try {
|
|
4869
|
+
const queryArgs = { documentId: targetDocId };
|
|
4870
|
+
if (docLocale) queryArgs.locale = docLocale;
|
|
4871
|
+
const found = await strapi.documents(uid).findOne(queryArgs);
|
|
4872
|
+
if (found) return found;
|
|
4873
|
+
} catch {
|
|
4874
|
+
}
|
|
4875
|
+
if (/^\d+$/.test(targetDocId)) {
|
|
4876
|
+
try {
|
|
4877
|
+
const queryArgs = {
|
|
4878
|
+
filters: { id: parseInt(targetDocId, 10) },
|
|
4879
|
+
limit: 1
|
|
4880
|
+
};
|
|
4881
|
+
if (docLocale) queryArgs.locale = docLocale;
|
|
4882
|
+
const list = await strapi.documents(uid).findMany(queryArgs);
|
|
4883
|
+
if (list && list.length > 0) return list[0];
|
|
4884
|
+
} catch {
|
|
4885
|
+
}
|
|
4886
|
+
}
|
|
4887
|
+
return null;
|
|
4888
|
+
};
|
|
4889
|
+
const SYSTEM_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
4890
|
+
"documentid",
|
|
4891
|
+
"document_id",
|
|
4892
|
+
"docid",
|
|
4893
|
+
"id",
|
|
4894
|
+
"createdat",
|
|
4895
|
+
"updatedat",
|
|
4896
|
+
"publishedat",
|
|
4897
|
+
"createdby",
|
|
4898
|
+
"updatedby",
|
|
4899
|
+
"localizations",
|
|
4900
|
+
"locale",
|
|
4901
|
+
"status"
|
|
4902
|
+
]);
|
|
4558
4903
|
for (let i = 0; i < parsedRows.length; i++) {
|
|
4559
4904
|
const rowNumber = i + 2;
|
|
4560
4905
|
const row = parsedRows[i];
|
|
4561
|
-
|
|
4906
|
+
let docId = extractDocId(row);
|
|
4907
|
+
const rowLocale = extractLocale(row);
|
|
4908
|
+
if (ct.kind === "singleType" && !docId) {
|
|
4909
|
+
try {
|
|
4910
|
+
const findSingleArgs = {};
|
|
4911
|
+
if (rowLocale) findSingleArgs.locale = rowLocale;
|
|
4912
|
+
const singleDoc = await strapi.documents(uid).findFirst(findSingleArgs);
|
|
4913
|
+
if (singleDoc?.documentId) {
|
|
4914
|
+
docId = singleDoc.documentId;
|
|
4915
|
+
}
|
|
4916
|
+
} catch {
|
|
4917
|
+
}
|
|
4918
|
+
}
|
|
4562
4919
|
const payload = {};
|
|
4563
4920
|
for (const [key, rawVal] of Object.entries(row)) {
|
|
4564
|
-
|
|
4921
|
+
const normalizedKey = key.toLowerCase().replace(/[\s_-]/g, "");
|
|
4922
|
+
if (SYSTEM_FIELD_KEYS.has(normalizedKey)) continue;
|
|
4565
4923
|
if (key.includes(".")) {
|
|
4566
4924
|
const [parent, child] = key.split(".");
|
|
4567
4925
|
const parentDef = attrs[parent];
|
|
@@ -4574,32 +4932,54 @@ const csvService = ({ strapi }) => ({
|
|
|
4574
4932
|
if (rawVal === "" || rawVal === null || rawVal === void 0) {
|
|
4575
4933
|
val = null;
|
|
4576
4934
|
} else if (childDef.type === "integer" || childDef.type === "biginteger") {
|
|
4577
|
-
|
|
4935
|
+
const parsedInt = parseInt(rawVal, 10);
|
|
4936
|
+
val = isNaN(parsedInt) ? null : parsedInt;
|
|
4578
4937
|
} else if (childDef.type === "float" || childDef.type === "decimal") {
|
|
4579
|
-
|
|
4938
|
+
const parsedFloat = parseFloat(rawVal);
|
|
4939
|
+
val = isNaN(parsedFloat) ? null : parsedFloat;
|
|
4580
4940
|
} else if (childDef.type === "boolean") {
|
|
4581
4941
|
val = rawVal.toLowerCase() === "true" || rawVal === "1";
|
|
4942
|
+
} else if (childDef.type === "json") {
|
|
4943
|
+
try {
|
|
4944
|
+
val = JSON.parse(rawVal);
|
|
4945
|
+
} catch {
|
|
4946
|
+
val = rawVal;
|
|
4947
|
+
}
|
|
4582
4948
|
}
|
|
4583
4949
|
payload[parent][child] = val;
|
|
4584
4950
|
}
|
|
4585
4951
|
}
|
|
4586
4952
|
continue;
|
|
4587
4953
|
}
|
|
4588
|
-
|
|
4954
|
+
let attrDef = attrs[key];
|
|
4955
|
+
if (!attrDef) {
|
|
4956
|
+
const matchKey = Object.keys(attrs).find(
|
|
4957
|
+
(k) => k.toLowerCase() === key.toLowerCase() || k.toLowerCase() === key.toLowerCase().replace(/\s+/g, "")
|
|
4958
|
+
);
|
|
4959
|
+
if (matchKey) {
|
|
4960
|
+
attrDef = attrs[matchKey];
|
|
4961
|
+
}
|
|
4962
|
+
}
|
|
4589
4963
|
if (!attrDef) continue;
|
|
4590
4964
|
if (rawVal === "" || rawVal === null || rawVal === void 0) {
|
|
4591
|
-
|
|
4965
|
+
if (attrDef.type !== "dynamiczone" && !(attrDef.type === "component" && attrDef.repeatable)) {
|
|
4966
|
+
payload[key] = null;
|
|
4967
|
+
}
|
|
4592
4968
|
continue;
|
|
4593
4969
|
}
|
|
4594
4970
|
switch (attrDef.type) {
|
|
4595
4971
|
case "integer":
|
|
4596
|
-
case "biginteger":
|
|
4597
|
-
|
|
4972
|
+
case "biginteger": {
|
|
4973
|
+
const parsedInt = parseInt(rawVal, 10);
|
|
4974
|
+
payload[key] = isNaN(parsedInt) ? null : parsedInt;
|
|
4598
4975
|
break;
|
|
4976
|
+
}
|
|
4599
4977
|
case "float":
|
|
4600
|
-
case "decimal":
|
|
4601
|
-
|
|
4978
|
+
case "decimal": {
|
|
4979
|
+
const parsedFloat = parseFloat(rawVal);
|
|
4980
|
+
payload[key] = isNaN(parsedFloat) ? null : parsedFloat;
|
|
4602
4981
|
break;
|
|
4982
|
+
}
|
|
4603
4983
|
case "boolean":
|
|
4604
4984
|
payload[key] = rawVal.toLowerCase() === "true" || rawVal === "1";
|
|
4605
4985
|
break;
|
|
@@ -4610,6 +4990,69 @@ const csvService = ({ strapi }) => ({
|
|
|
4610
4990
|
payload[key] = rawVal;
|
|
4611
4991
|
}
|
|
4612
4992
|
break;
|
|
4993
|
+
case "dynamiczone": {
|
|
4994
|
+
try {
|
|
4995
|
+
const parsed = JSON.parse(rawVal);
|
|
4996
|
+
if (Array.isArray(parsed) && parsed.every((item) => item && typeof item === "object" && typeof item.__component === "string")) {
|
|
4997
|
+
payload[key] = parsed;
|
|
4998
|
+
}
|
|
4999
|
+
} catch {
|
|
5000
|
+
}
|
|
5001
|
+
break;
|
|
5002
|
+
}
|
|
5003
|
+
case "component": {
|
|
5004
|
+
if (attrDef.repeatable) {
|
|
5005
|
+
try {
|
|
5006
|
+
const parsed = JSON.parse(rawVal);
|
|
5007
|
+
if (Array.isArray(parsed) && parsed.every((item) => item && typeof item === "object")) {
|
|
5008
|
+
payload[key] = parsed;
|
|
5009
|
+
}
|
|
5010
|
+
} catch {
|
|
5011
|
+
}
|
|
5012
|
+
} else {
|
|
5013
|
+
try {
|
|
5014
|
+
const parsed = JSON.parse(rawVal);
|
|
5015
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
5016
|
+
payload[key] = parsed;
|
|
5017
|
+
}
|
|
5018
|
+
} catch {
|
|
5019
|
+
}
|
|
5020
|
+
}
|
|
5021
|
+
break;
|
|
5022
|
+
}
|
|
5023
|
+
case "relation": {
|
|
5024
|
+
try {
|
|
5025
|
+
const parsed = JSON.parse(rawVal);
|
|
5026
|
+
if (Array.isArray(parsed) || typeof parsed === "object" && parsed !== null) {
|
|
5027
|
+
payload[key] = parsed;
|
|
5028
|
+
break;
|
|
5029
|
+
}
|
|
5030
|
+
} catch {
|
|
5031
|
+
}
|
|
5032
|
+
if (isValidDocId(rawVal)) {
|
|
5033
|
+
payload[key] = rawVal;
|
|
5034
|
+
} else if (rawVal.includes(";") || rawVal.includes(",")) {
|
|
5035
|
+
const parts = rawVal.split(/[;,]/).map((s) => s.trim()).filter(Boolean);
|
|
5036
|
+
if (parts.length > 0 && parts.every((p) => isValidDocId(p))) {
|
|
5037
|
+
payload[key] = parts;
|
|
5038
|
+
}
|
|
5039
|
+
}
|
|
5040
|
+
break;
|
|
5041
|
+
}
|
|
5042
|
+
case "media": {
|
|
5043
|
+
if (/^\d+$/.test(rawVal.trim())) {
|
|
5044
|
+
payload[key] = parseInt(rawVal.trim(), 10);
|
|
5045
|
+
} else {
|
|
5046
|
+
try {
|
|
5047
|
+
const parsed = JSON.parse(rawVal);
|
|
5048
|
+
if (typeof parsed === "number" || typeof parsed === "object") {
|
|
5049
|
+
payload[key] = parsed;
|
|
5050
|
+
}
|
|
5051
|
+
} catch {
|
|
5052
|
+
}
|
|
5053
|
+
}
|
|
5054
|
+
break;
|
|
5055
|
+
}
|
|
4613
5056
|
default:
|
|
4614
5057
|
payload[key] = rawVal;
|
|
4615
5058
|
}
|
|
@@ -4620,114 +5063,146 @@ const csvService = ({ strapi }) => ({
|
|
|
4620
5063
|
stats.skipped++;
|
|
4621
5064
|
stats.errors.push({
|
|
4622
5065
|
row: rowNumber,
|
|
4623
|
-
error: "Missing documentId for Update mode. (Tip: Use Smart Auto-Detect mode to auto-add entries without document IDs)."
|
|
5066
|
+
error: "Missing or invalid documentId for Update mode. (Tip: Use Smart Auto-Detect mode to auto-add entries without document IDs)."
|
|
4624
5067
|
});
|
|
4625
5068
|
continue;
|
|
4626
5069
|
}
|
|
4627
|
-
const existing = await
|
|
4628
|
-
documentId: docId
|
|
4629
|
-
});
|
|
5070
|
+
const existing = await findExistingDocument(docId, rowLocale);
|
|
4630
5071
|
if (!existing) {
|
|
4631
5072
|
stats.skipped++;
|
|
4632
5073
|
stats.errors.push({
|
|
4633
5074
|
row: rowNumber,
|
|
4634
5075
|
documentId: docId,
|
|
4635
|
-
error: `Document with
|
|
5076
|
+
error: `Document with ID "${docId}" not found in database.`
|
|
4636
5077
|
});
|
|
4637
5078
|
continue;
|
|
4638
5079
|
}
|
|
5080
|
+
const resolvedDocId = existing.documentId || docId;
|
|
4639
5081
|
const mergedPayload = { ...payload };
|
|
4640
5082
|
for (const key of Object.keys(payload)) {
|
|
4641
5083
|
if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
|
|
4642
5084
|
mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
|
|
4643
5085
|
}
|
|
4644
5086
|
}
|
|
4645
|
-
|
|
4646
|
-
|
|
5087
|
+
if (Object.keys(mergedPayload).length === 0) {
|
|
5088
|
+
stats.skipped++;
|
|
5089
|
+
stats.diffs.push({
|
|
5090
|
+
uid,
|
|
5091
|
+
collectionName: ct.info?.displayName || uid,
|
|
5092
|
+
documentId: resolvedDocId,
|
|
5093
|
+
label: existing.title || existing.name || `Entry #${resolvedDocId}`,
|
|
5094
|
+
attribute: "",
|
|
5095
|
+
oldValue: existing,
|
|
5096
|
+
newValue: {},
|
|
5097
|
+
status: "skipped",
|
|
5098
|
+
message: `Skipped (no editable attributes in row ${rowNumber})`
|
|
5099
|
+
});
|
|
5100
|
+
continue;
|
|
5101
|
+
}
|
|
5102
|
+
const updateArgs = {
|
|
5103
|
+
documentId: resolvedDocId,
|
|
4647
5104
|
data: mergedPayload
|
|
4648
|
-
}
|
|
5105
|
+
};
|
|
5106
|
+
if (rowLocale) updateArgs.locale = rowLocale;
|
|
5107
|
+
await strapi.documents(uid).update(updateArgs);
|
|
4649
5108
|
if (publish && ct.options?.draftAndPublish) {
|
|
4650
5109
|
try {
|
|
4651
|
-
|
|
5110
|
+
const pubArgs = { documentId: resolvedDocId };
|
|
5111
|
+
if (rowLocale) pubArgs.locale = rowLocale;
|
|
5112
|
+
await strapi.documents(uid).publish(pubArgs);
|
|
4652
5113
|
} catch (pubErr) {
|
|
4653
|
-
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${
|
|
5114
|
+
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${resolvedDocId}:`, pubErr);
|
|
4654
5115
|
}
|
|
4655
5116
|
}
|
|
4656
5117
|
try {
|
|
4657
5118
|
await recordContentHistoryVersion(strapi, {
|
|
4658
5119
|
uid,
|
|
4659
|
-
documentId:
|
|
5120
|
+
documentId: resolvedDocId,
|
|
4660
5121
|
userId: user?.id,
|
|
4661
5122
|
statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
|
|
4662
5123
|
});
|
|
4663
5124
|
} catch {
|
|
4664
5125
|
}
|
|
4665
5126
|
stats.updated++;
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
5127
|
+
for (const attrKey of Object.keys(payload)) {
|
|
5128
|
+
const oldVal = existing?.[attrKey] ?? null;
|
|
5129
|
+
const newVal = payload[attrKey];
|
|
5130
|
+
stats.diffs.push({
|
|
5131
|
+
uid,
|
|
5132
|
+
collectionName: ct.info?.displayName || uid,
|
|
5133
|
+
documentId: resolvedDocId,
|
|
5134
|
+
label: existing.title || existing.name || `Entry #${resolvedDocId}`,
|
|
5135
|
+
attribute: attrKey,
|
|
5136
|
+
oldValue: oldVal,
|
|
5137
|
+
newValue: newVal,
|
|
5138
|
+
status: "updated",
|
|
5139
|
+
message: `Updated "${attrKey}" via CSV import (row ${rowNumber})`
|
|
5140
|
+
});
|
|
5141
|
+
}
|
|
4677
5142
|
} else if (mode === "create_or_override") {
|
|
5143
|
+
let existing = null;
|
|
4678
5144
|
if (docId) {
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
|
|
4685
|
-
|
|
4686
|
-
|
|
4687
|
-
}
|
|
4688
|
-
}
|
|
4689
|
-
await strapi.documents(uid).update({
|
|
4690
|
-
documentId: docId,
|
|
4691
|
-
data: mergedPayload
|
|
4692
|
-
});
|
|
4693
|
-
if (publish && ct.options?.draftAndPublish) {
|
|
4694
|
-
try {
|
|
4695
|
-
await strapi.documents(uid).publish({ documentId: docId });
|
|
4696
|
-
} catch (pubErr) {
|
|
4697
|
-
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${docId}:`, pubErr);
|
|
4698
|
-
}
|
|
5145
|
+
existing = await findExistingDocument(docId, rowLocale);
|
|
5146
|
+
}
|
|
5147
|
+
if (existing) {
|
|
5148
|
+
const resolvedDocId = existing.documentId || docId;
|
|
5149
|
+
const mergedPayload = { ...payload };
|
|
5150
|
+
for (const key of Object.keys(payload)) {
|
|
5151
|
+
if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
|
|
5152
|
+
mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
|
|
4699
5153
|
}
|
|
5154
|
+
}
|
|
5155
|
+
const updateArgs = {
|
|
5156
|
+
documentId: resolvedDocId,
|
|
5157
|
+
data: mergedPayload
|
|
5158
|
+
};
|
|
5159
|
+
if (rowLocale) updateArgs.locale = rowLocale;
|
|
5160
|
+
await strapi.documents(uid).update(updateArgs);
|
|
5161
|
+
if (publish && ct.options?.draftAndPublish) {
|
|
4700
5162
|
try {
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
});
|
|
4707
|
-
} catch {
|
|
5163
|
+
const pubArgs = { documentId: resolvedDocId };
|
|
5164
|
+
if (rowLocale) pubArgs.locale = rowLocale;
|
|
5165
|
+
await strapi.documents(uid).publish(pubArgs);
|
|
5166
|
+
} catch (pubErr) {
|
|
5167
|
+
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${resolvedDocId}:`, pubErr);
|
|
4708
5168
|
}
|
|
4709
|
-
|
|
5169
|
+
}
|
|
5170
|
+
try {
|
|
5171
|
+
await recordContentHistoryVersion(strapi, {
|
|
5172
|
+
uid,
|
|
5173
|
+
documentId: resolvedDocId,
|
|
5174
|
+
userId: user?.id,
|
|
5175
|
+
statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
|
|
5176
|
+
});
|
|
5177
|
+
} catch {
|
|
5178
|
+
}
|
|
5179
|
+
stats.updated++;
|
|
5180
|
+
for (const attrKey of Object.keys(payload)) {
|
|
5181
|
+
const oldVal = existing?.[attrKey] ?? null;
|
|
5182
|
+
const newVal = payload[attrKey];
|
|
4710
5183
|
stats.diffs.push({
|
|
4711
5184
|
uid,
|
|
4712
5185
|
collectionName: ct.info?.displayName || uid,
|
|
4713
|
-
documentId:
|
|
4714
|
-
label: existing.title || existing.name || `Entry #${
|
|
4715
|
-
attribute:
|
|
4716
|
-
oldValue:
|
|
4717
|
-
newValue:
|
|
5186
|
+
documentId: resolvedDocId,
|
|
5187
|
+
label: existing.title || existing.name || `Entry #${resolvedDocId}`,
|
|
5188
|
+
attribute: attrKey,
|
|
5189
|
+
oldValue: oldVal,
|
|
5190
|
+
newValue: newVal,
|
|
4718
5191
|
status: "updated",
|
|
4719
|
-
message: `Overridden via CSV import (row ${rowNumber})`
|
|
5192
|
+
message: `Overridden "${attrKey}" via CSV import (row ${rowNumber})`
|
|
4720
5193
|
});
|
|
4721
|
-
continue;
|
|
4722
5194
|
}
|
|
5195
|
+
continue;
|
|
4723
5196
|
}
|
|
4724
|
-
const
|
|
4725
|
-
|
|
4726
|
-
|
|
5197
|
+
const createArgs = { data: payload };
|
|
5198
|
+
if (rowLocale) createArgs.locale = rowLocale;
|
|
5199
|
+
const created = await strapi.documents(uid).create(createArgs);
|
|
4727
5200
|
const newDocId = created?.documentId || docId;
|
|
4728
5201
|
if (publish && ct.options?.draftAndPublish && newDocId) {
|
|
4729
5202
|
try {
|
|
4730
|
-
|
|
5203
|
+
const pubArgs = { documentId: newDocId };
|
|
5204
|
+
if (rowLocale) pubArgs.locale = rowLocale;
|
|
5205
|
+
await strapi.documents(uid).publish(pubArgs);
|
|
4731
5206
|
} catch (pubErr) {
|
|
4732
5207
|
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${newDocId}:`, pubErr);
|
|
4733
5208
|
}
|
|
@@ -4745,71 +5220,83 @@ const csvService = ({ strapi }) => ({
|
|
|
4745
5220
|
if (!docId) {
|
|
4746
5221
|
stats.newWithoutDocId++;
|
|
4747
5222
|
}
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
4756
|
-
|
|
4757
|
-
|
|
4758
|
-
|
|
5223
|
+
for (const attrKey of Object.keys(payload)) {
|
|
5224
|
+
stats.diffs.push({
|
|
5225
|
+
uid,
|
|
5226
|
+
collectionName: ct.info?.displayName || uid,
|
|
5227
|
+
documentId: newDocId,
|
|
5228
|
+
label: created?.title || created?.name || `New Entry #${newDocId}`,
|
|
5229
|
+
attribute: attrKey,
|
|
5230
|
+
oldValue: null,
|
|
5231
|
+
newValue: payload[attrKey],
|
|
5232
|
+
status: "created",
|
|
5233
|
+
message: docId ? `Created "${attrKey}" via CSV import (row ${rowNumber})` : `Created "${attrKey}" as new entry (row ${rowNumber})`
|
|
5234
|
+
});
|
|
5235
|
+
}
|
|
4759
5236
|
} else {
|
|
5237
|
+
let existing = null;
|
|
4760
5238
|
if (docId) {
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
}
|
|
4770
|
-
}
|
|
4771
|
-
await strapi.documents(uid).update({
|
|
4772
|
-
documentId: docId,
|
|
4773
|
-
data: mergedPayload
|
|
4774
|
-
});
|
|
4775
|
-
if (publish && ct.options?.draftAndPublish) {
|
|
4776
|
-
try {
|
|
4777
|
-
await strapi.documents(uid).publish({ documentId: docId });
|
|
4778
|
-
} catch (pubErr) {
|
|
4779
|
-
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${docId}:`, pubErr);
|
|
4780
|
-
}
|
|
5239
|
+
existing = await findExistingDocument(docId, rowLocale);
|
|
5240
|
+
}
|
|
5241
|
+
if (existing) {
|
|
5242
|
+
const resolvedDocId = existing.documentId || docId;
|
|
5243
|
+
const mergedPayload = { ...payload };
|
|
5244
|
+
for (const key of Object.keys(payload)) {
|
|
5245
|
+
if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
|
|
5246
|
+
mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
|
|
4781
5247
|
}
|
|
5248
|
+
}
|
|
5249
|
+
const updateArgs = {
|
|
5250
|
+
documentId: resolvedDocId,
|
|
5251
|
+
data: mergedPayload
|
|
5252
|
+
};
|
|
5253
|
+
if (rowLocale) updateArgs.locale = rowLocale;
|
|
5254
|
+
await strapi.documents(uid).update(updateArgs);
|
|
5255
|
+
if (publish && ct.options?.draftAndPublish) {
|
|
4782
5256
|
try {
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
});
|
|
4789
|
-
} catch {
|
|
5257
|
+
const pubArgs = { documentId: resolvedDocId };
|
|
5258
|
+
if (rowLocale) pubArgs.locale = rowLocale;
|
|
5259
|
+
await strapi.documents(uid).publish(pubArgs);
|
|
5260
|
+
} catch (pubErr) {
|
|
5261
|
+
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${resolvedDocId}:`, pubErr);
|
|
4790
5262
|
}
|
|
4791
|
-
|
|
5263
|
+
}
|
|
5264
|
+
try {
|
|
5265
|
+
await recordContentHistoryVersion(strapi, {
|
|
5266
|
+
uid,
|
|
5267
|
+
documentId: resolvedDocId,
|
|
5268
|
+
userId: user?.id,
|
|
5269
|
+
statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
|
|
5270
|
+
});
|
|
5271
|
+
} catch {
|
|
5272
|
+
}
|
|
5273
|
+
stats.updated++;
|
|
5274
|
+
for (const attrKey of Object.keys(payload)) {
|
|
5275
|
+
const oldVal = existing?.[attrKey] ?? null;
|
|
5276
|
+
const newVal = payload[attrKey];
|
|
4792
5277
|
stats.diffs.push({
|
|
4793
5278
|
uid,
|
|
4794
5279
|
collectionName: ct.info?.displayName || uid,
|
|
4795
|
-
documentId:
|
|
4796
|
-
label: existing.title || existing.name || `Entry #${
|
|
4797
|
-
attribute:
|
|
4798
|
-
oldValue:
|
|
4799
|
-
newValue:
|
|
5280
|
+
documentId: resolvedDocId,
|
|
5281
|
+
label: existing.title || existing.name || `Entry #${resolvedDocId}`,
|
|
5282
|
+
attribute: attrKey,
|
|
5283
|
+
oldValue: oldVal,
|
|
5284
|
+
newValue: newVal,
|
|
4800
5285
|
status: "updated",
|
|
4801
|
-
message: `Updated
|
|
5286
|
+
message: `Updated "${attrKey}" via CSV import (row ${rowNumber})`
|
|
4802
5287
|
});
|
|
4803
|
-
continue;
|
|
4804
5288
|
}
|
|
5289
|
+
continue;
|
|
4805
5290
|
}
|
|
4806
|
-
const
|
|
4807
|
-
|
|
4808
|
-
|
|
5291
|
+
const createArgs = { data: payload };
|
|
5292
|
+
if (rowLocale) createArgs.locale = rowLocale;
|
|
5293
|
+
const created = await strapi.documents(uid).create(createArgs);
|
|
4809
5294
|
const newDocId = created?.documentId || docId;
|
|
4810
5295
|
if (publish && ct.options?.draftAndPublish && newDocId) {
|
|
4811
5296
|
try {
|
|
4812
|
-
|
|
5297
|
+
const pubArgs = { documentId: newDocId };
|
|
5298
|
+
if (rowLocale) pubArgs.locale = rowLocale;
|
|
5299
|
+
await strapi.documents(uid).publish(pubArgs);
|
|
4813
5300
|
} catch (pubErr) {
|
|
4814
5301
|
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${newDocId}:`, pubErr);
|
|
4815
5302
|
}
|
|
@@ -4827,23 +5314,25 @@ const csvService = ({ strapi }) => ({
|
|
|
4827
5314
|
if (!docId) {
|
|
4828
5315
|
stats.newWithoutDocId++;
|
|
4829
5316
|
}
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
5317
|
+
for (const attrKey of Object.keys(payload)) {
|
|
5318
|
+
stats.diffs.push({
|
|
5319
|
+
uid,
|
|
5320
|
+
collectionName: ct.info?.displayName || uid,
|
|
5321
|
+
documentId: newDocId,
|
|
5322
|
+
label: created?.title || created?.name || `New Entry #${newDocId}`,
|
|
5323
|
+
attribute: attrKey,
|
|
5324
|
+
oldValue: null,
|
|
5325
|
+
newValue: payload[attrKey],
|
|
5326
|
+
status: "created",
|
|
5327
|
+
message: docId ? `Created "${attrKey}" as new entry (documentId "${docId}" not found in database - row ${rowNumber})` : `Created "${attrKey}" as new entry (no documentId provided in CSV - row ${rowNumber})`
|
|
5328
|
+
});
|
|
5329
|
+
}
|
|
4841
5330
|
}
|
|
4842
5331
|
} catch (err) {
|
|
4843
5332
|
stats.errors.push({
|
|
4844
5333
|
row: rowNumber,
|
|
4845
5334
|
documentId: docId || void 0,
|
|
4846
|
-
error: err
|
|
5335
|
+
error: extractDetailedErrorMessage(err)
|
|
4847
5336
|
});
|
|
4848
5337
|
}
|
|
4849
5338
|
}
|
|
@@ -4860,10 +5349,32 @@ const csvService = ({ strapi }) => ({
|
|
|
4860
5349
|
if (auditService2) {
|
|
4861
5350
|
const isEnabled = typeof auditService2.isAuditEnabled === "function" ? auditService2.isAuditEnabled() : Boolean(auditService2.getAuditConfig?.().enabled ?? true);
|
|
4862
5351
|
if (isEnabled) {
|
|
5352
|
+
const collectionDisplayName = ct.info?.displayName || uid;
|
|
5353
|
+
const updatedDiffs = stats.diffs.filter(
|
|
5354
|
+
(d) => d.status === "updated" || d.status === "created"
|
|
5355
|
+
);
|
|
5356
|
+
const uniqueAttributes = Array.from(
|
|
5357
|
+
new Set(updatedDiffs.map((d) => d.attribute))
|
|
5358
|
+
).filter(Boolean);
|
|
5359
|
+
const attrListStr = uniqueAttributes.length > 0 ? uniqueAttributes.join(", ") : "fields";
|
|
5360
|
+
const batchAttribute = `CSV [${collectionDisplayName}]: ${attrListStr}`;
|
|
5361
|
+
let batchValue = "";
|
|
5362
|
+
if (updatedDiffs.length === 1) {
|
|
5363
|
+
const d = updatedDiffs[0];
|
|
5364
|
+
const newStr = d.newValue !== null && d.newValue !== void 0 ? String(d.newValue) : "(empty)";
|
|
5365
|
+
batchValue = `"${d.label}": ${d.attribute} → "${newStr}"`;
|
|
5366
|
+
} else if (updatedDiffs.length > 1 && stats.updated === 1 && stats.created === 0) {
|
|
5367
|
+
const d = updatedDiffs[0];
|
|
5368
|
+
const changes = updatedDiffs.map((ud) => `${ud.attribute}: "${ud.newValue ?? ""}"`).join(", ");
|
|
5369
|
+
batchValue = `"${d.label}": ${changes}`;
|
|
5370
|
+
} else {
|
|
5371
|
+
const sampleVal = updatedDiffs[0] ? ` (e.g. ${updatedDiffs[0].attribute}: "${updatedDiffs[0].newValue ?? ""}")` : "";
|
|
5372
|
+
batchValue = `${stats.updated} updated, ${stats.created} created in ${collectionDisplayName}${sampleVal}`;
|
|
5373
|
+
}
|
|
4863
5374
|
const batchSummary = {
|
|
4864
5375
|
batchId,
|
|
4865
|
-
attribute:
|
|
4866
|
-
value:
|
|
5376
|
+
attribute: batchAttribute,
|
|
5377
|
+
value: batchValue,
|
|
4867
5378
|
updatedCount: stats.updated + stats.created,
|
|
4868
5379
|
skippedCount: stats.skipped,
|
|
4869
5380
|
totalTargets: stats.total
|