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