strapi-plugin-bulk-updater 1.2.3 → 1.2.8
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-BdRJhVFh.js → BulkUpdater-2TTEWMXI.js} +1170 -163
- package/dist/_chunks/BulkUpdater-2TTEWMXI.js.map +1 -0
- package/dist/_chunks/{BulkUpdater-BYtO2SHc.mjs → BulkUpdater-DR8Z9z95.mjs} +1172 -165
- package/dist/_chunks/BulkUpdater-DR8Z9z95.mjs.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +665 -116
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +665 -116
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/_chunks/BulkUpdater-BYtO2SHc.mjs.map +0 -1
- package/dist/_chunks/BulkUpdater-BdRJhVFh.js.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -78,6 +78,30 @@ const routes = {
|
|
|
78
78
|
policies: []
|
|
79
79
|
}
|
|
80
80
|
},
|
|
81
|
+
{
|
|
82
|
+
method: "GET",
|
|
83
|
+
path: "/batches/recent",
|
|
84
|
+
handler: "bulkUpdater.getRecentBatches",
|
|
85
|
+
config: {
|
|
86
|
+
policies: []
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
{
|
|
90
|
+
method: "GET",
|
|
91
|
+
path: "/batches/:batchId/status",
|
|
92
|
+
handler: "bulkUpdater.getBatchStatus",
|
|
93
|
+
config: {
|
|
94
|
+
policies: []
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
method: "GET",
|
|
99
|
+
path: "/batches/:batchId/report",
|
|
100
|
+
handler: "bulkUpdater.getBatchReport",
|
|
101
|
+
config: {
|
|
102
|
+
policies: []
|
|
103
|
+
}
|
|
104
|
+
},
|
|
81
105
|
{
|
|
82
106
|
method: "GET",
|
|
83
107
|
path: "/audit/history",
|
|
@@ -363,6 +387,39 @@ const bulkUpdaterController = ({ strapi: strapi2 }) => ({
|
|
|
363
387
|
ctx.status = 400;
|
|
364
388
|
ctx.body = { success: false, error: err.message };
|
|
365
389
|
}
|
|
390
|
+
},
|
|
391
|
+
async getBatchStatus(ctx) {
|
|
392
|
+
try {
|
|
393
|
+
const { batchId } = ctx.params;
|
|
394
|
+
const service = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
395
|
+
const result = await service.getBatchStatus(batchId);
|
|
396
|
+
ctx.body = result;
|
|
397
|
+
} catch (err) {
|
|
398
|
+
ctx.status = 404;
|
|
399
|
+
ctx.body = { success: false, error: err.message };
|
|
400
|
+
}
|
|
401
|
+
},
|
|
402
|
+
async getBatchReport(ctx) {
|
|
403
|
+
try {
|
|
404
|
+
const { batchId } = ctx.params;
|
|
405
|
+
const { format = "csv" } = ctx.query;
|
|
406
|
+
const service = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
407
|
+
const result = await service.getBatchReport(batchId, format);
|
|
408
|
+
ctx.body = result;
|
|
409
|
+
} catch (err) {
|
|
410
|
+
ctx.status = 500;
|
|
411
|
+
ctx.body = { success: false, error: err.message };
|
|
412
|
+
}
|
|
413
|
+
},
|
|
414
|
+
async getRecentBatches(ctx) {
|
|
415
|
+
try {
|
|
416
|
+
const service = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
417
|
+
const result = await service.getRecentBatches();
|
|
418
|
+
ctx.body = result;
|
|
419
|
+
} catch (err) {
|
|
420
|
+
ctx.status = 500;
|
|
421
|
+
ctx.body = { success: false, error: err.message };
|
|
422
|
+
}
|
|
366
423
|
}
|
|
367
424
|
});
|
|
368
425
|
const auditController = ({ strapi: strapi2 }) => ({
|
|
@@ -535,7 +592,7 @@ const csvController = ({ strapi: strapi2 }) => ({
|
|
|
535
592
|
}
|
|
536
593
|
const csvService2 = strapi2.plugin("bulk-updater").service("csv");
|
|
537
594
|
const result = await csvService2.importCsv(uid, csvText, {
|
|
538
|
-
mode: mode2 === "create_or_override" ? "create_or_override" : "
|
|
595
|
+
mode: mode2 === "update" ? "update" : mode2 === "create_or_override" ? "create_or_override" : "auto",
|
|
539
596
|
publish: publish !== false,
|
|
540
597
|
user: ctx.state.user
|
|
541
598
|
});
|
|
@@ -608,6 +665,7 @@ function checkBusinessHours(config, testDate) {
|
|
|
608
665
|
};
|
|
609
666
|
}
|
|
610
667
|
const pendingBatches = /* @__PURE__ */ new Map();
|
|
668
|
+
const batchJobsMap = /* @__PURE__ */ new Map();
|
|
611
669
|
let isRunnerRunning = false;
|
|
612
670
|
let runnerInterval = null;
|
|
613
671
|
function invalidateCollectionCache(uid) {
|
|
@@ -707,6 +765,31 @@ async function executeBatchInternal(strapi2, {
|
|
|
707
765
|
const results = [];
|
|
708
766
|
let updatedCount = 0;
|
|
709
767
|
let skippedCount = 0;
|
|
768
|
+
const jobState = {
|
|
769
|
+
id: batchId,
|
|
770
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
771
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
772
|
+
createdBy: currentUser ? {
|
|
773
|
+
id: currentUser.id,
|
|
774
|
+
email: currentUser.email,
|
|
775
|
+
firstname: currentUser.firstname,
|
|
776
|
+
lastname: currentUser.lastname,
|
|
777
|
+
username: currentUser.username
|
|
778
|
+
} : { email: "admin@system.local" },
|
|
779
|
+
status: "processing",
|
|
780
|
+
executionMode: "immediate",
|
|
781
|
+
attribute,
|
|
782
|
+
value,
|
|
783
|
+
totalTargets: targetsMap.size,
|
|
784
|
+
processedCount: 0,
|
|
785
|
+
updatedCount: 0,
|
|
786
|
+
skippedCount: 0,
|
|
787
|
+
failedCount: 0,
|
|
788
|
+
isLive: false,
|
|
789
|
+
publishOnApproval: Boolean(publishOnApproval),
|
|
790
|
+
results: []
|
|
791
|
+
};
|
|
792
|
+
batchJobsMap.set(batchId, jobState);
|
|
710
793
|
let regexObj = null;
|
|
711
794
|
if (isRegex && regexPattern) {
|
|
712
795
|
regexObj = new RegExp(regexPattern, "g");
|
|
@@ -941,11 +1024,24 @@ async function executeBatchInternal(strapi2, {
|
|
|
941
1024
|
else if (res.status === "skipped") skippedCount++;
|
|
942
1025
|
results.push(res);
|
|
943
1026
|
}
|
|
1027
|
+
jobState.processedCount = results.length;
|
|
1028
|
+
jobState.updatedCount = updatedCount;
|
|
1029
|
+
jobState.skippedCount = skippedCount;
|
|
1030
|
+
jobState.failedCount = results.filter((r) => r.status === "error").length;
|
|
944
1031
|
}
|
|
945
1032
|
const affectedUids = new Set(targets.map((t) => t.uid));
|
|
946
1033
|
affectedUids.forEach((uid) => {
|
|
947
1034
|
invalidateCollectionCache(uid);
|
|
948
1035
|
});
|
|
1036
|
+
const failedCount = results.filter((r) => r.status === "error").length;
|
|
1037
|
+
jobState.failedCount = failedCount;
|
|
1038
|
+
jobState.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1039
|
+
jobState.isLive = updatedCount > 0;
|
|
1040
|
+
jobState.status = failedCount === 0 && updatedCount > 0 ? "successful" : failedCount > 0 && updatedCount > 0 ? "partial_failure" : failedCount > 0 ? "failed" : "successful";
|
|
1041
|
+
jobState.results = results.map((r) => ({
|
|
1042
|
+
...r,
|
|
1043
|
+
isLive: r.status === "updated"
|
|
1044
|
+
}));
|
|
949
1045
|
const batchSummary = {
|
|
950
1046
|
batchId,
|
|
951
1047
|
attribute,
|
|
@@ -967,7 +1063,10 @@ async function executeBatchInternal(strapi2, {
|
|
|
967
1063
|
value,
|
|
968
1064
|
updatedCount,
|
|
969
1065
|
skippedCount,
|
|
1066
|
+
failedCount,
|
|
970
1067
|
totalTargets: targetsMap.size,
|
|
1068
|
+
isLive: jobState.isLive,
|
|
1069
|
+
status: jobState.status,
|
|
971
1070
|
results
|
|
972
1071
|
};
|
|
973
1072
|
}
|
|
@@ -1095,69 +1194,87 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1095
1194
|
}
|
|
1096
1195
|
const collections = [];
|
|
1097
1196
|
const signatures = [];
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
const
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
populate: populateOption
|
|
1197
|
+
const BATCH_CONCURRENCY = 10;
|
|
1198
|
+
const collectionResults = [];
|
|
1199
|
+
for (let i = 0; i < staticSchemaCache.targetUids.length; i += BATCH_CONCURRENCY) {
|
|
1200
|
+
const batchUids = staticSchemaCache.targetUids.slice(i, i + BATCH_CONCURRENCY);
|
|
1201
|
+
const batchRes = await Promise.all(
|
|
1202
|
+
batchUids.map(async (uid) => {
|
|
1203
|
+
const meta = staticSchemaCache.metaByUid[uid];
|
|
1204
|
+
if (!meta) return null;
|
|
1205
|
+
const signature = await getCollectionSignature(strapi2, uid);
|
|
1206
|
+
const cached = modelsCache.get(uid);
|
|
1207
|
+
if (cached && cached.signature === signature) {
|
|
1208
|
+
return {
|
|
1209
|
+
collection: {
|
|
1210
|
+
...meta,
|
|
1211
|
+
totalEntries: cached.totalCount,
|
|
1212
|
+
entries: cached.entries
|
|
1213
|
+
},
|
|
1214
|
+
signature: `${uid}=${signature}`
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
let entries = [];
|
|
1218
|
+
let totalCount = 0;
|
|
1219
|
+
try {
|
|
1220
|
+
const ctAttrs = allContentTypes[uid]?.attributes || {};
|
|
1221
|
+
const relKeys = Object.keys(meta.relations || {});
|
|
1222
|
+
const compKeys = Object.keys(ctAttrs).filter((k) => ctAttrs[k].type === "component");
|
|
1223
|
+
const populateOptionObj = {};
|
|
1224
|
+
relKeys.forEach((k) => {
|
|
1225
|
+
populateOptionObj[k] = true;
|
|
1128
1226
|
});
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
totalCount = 1;
|
|
1132
|
-
}
|
|
1133
|
-
} else {
|
|
1134
|
-
totalCount = await strapi2.documents(uid).count();
|
|
1135
|
-
entries = await strapi2.documents(uid).findMany({
|
|
1136
|
-
limit: 10,
|
|
1137
|
-
sort: { createdAt: "desc" },
|
|
1138
|
-
populate: populateOption
|
|
1227
|
+
compKeys.forEach((k) => {
|
|
1228
|
+
populateOptionObj[k] = true;
|
|
1139
1229
|
});
|
|
1230
|
+
const populateOption = Object.keys(populateOptionObj).length > 0 ? populateOptionObj : void 0;
|
|
1231
|
+
if (meta.isSingleType) {
|
|
1232
|
+
const singleDoc = await strapi2.documents(uid).findFirst({
|
|
1233
|
+
populate: populateOption
|
|
1234
|
+
});
|
|
1235
|
+
if (singleDoc) {
|
|
1236
|
+
entries = [singleDoc];
|
|
1237
|
+
totalCount = 1;
|
|
1238
|
+
}
|
|
1239
|
+
} else {
|
|
1240
|
+
totalCount = await strapi2.documents(uid).count();
|
|
1241
|
+
entries = await strapi2.documents(uid).findMany({
|
|
1242
|
+
limit: 10,
|
|
1243
|
+
sort: { createdAt: "desc" },
|
|
1244
|
+
populate: populateOption
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
} catch (err) {
|
|
1248
|
+
strapi2.log.error(`[bulk-updater] Failed to fetch entries for ${uid}:`, err);
|
|
1249
|
+
entries = [];
|
|
1250
|
+
totalCount = 0;
|
|
1140
1251
|
}
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1252
|
+
const formattedEntries = (entries || []).map((entry) => ({
|
|
1253
|
+
id: entry.id,
|
|
1254
|
+
documentId: entry.documentId,
|
|
1255
|
+
...entry
|
|
1256
|
+
}));
|
|
1257
|
+
modelsCache.set(uid, {
|
|
1258
|
+
signature,
|
|
1259
|
+
totalCount,
|
|
1260
|
+
entries: formattedEntries
|
|
1261
|
+
});
|
|
1262
|
+
return {
|
|
1263
|
+
collection: {
|
|
1264
|
+
...meta,
|
|
1265
|
+
totalEntries: totalCount,
|
|
1266
|
+
entries: formattedEntries
|
|
1267
|
+
},
|
|
1268
|
+
signature: `${uid}=${signature}`
|
|
1269
|
+
};
|
|
1270
|
+
})
|
|
1271
|
+
);
|
|
1272
|
+
collectionResults.push(...batchRes);
|
|
1273
|
+
}
|
|
1274
|
+
for (const res of collectionResults) {
|
|
1275
|
+
if (res) {
|
|
1276
|
+
collections.push(res.collection);
|
|
1277
|
+
signatures.push(res.signature);
|
|
1161
1278
|
}
|
|
1162
1279
|
}
|
|
1163
1280
|
const executionConfig = getExecutionWindowConfig(strapi2);
|
|
@@ -1476,6 +1593,24 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1476
1593
|
makerNotes
|
|
1477
1594
|
};
|
|
1478
1595
|
pendingBatches.set(batchId, batch);
|
|
1596
|
+
batchJobsMap.set(batchId, {
|
|
1597
|
+
id: batchId,
|
|
1598
|
+
createdAt: batch.createdAt,
|
|
1599
|
+
createdBy: batch.createdBy,
|
|
1600
|
+
status: "pending_approval",
|
|
1601
|
+
executionMode: "maker_checker",
|
|
1602
|
+
attribute,
|
|
1603
|
+
value,
|
|
1604
|
+
totalTargets: summary.totalTargets,
|
|
1605
|
+
processedCount: 0,
|
|
1606
|
+
updatedCount: 0,
|
|
1607
|
+
skippedCount: 0,
|
|
1608
|
+
failedCount: 0,
|
|
1609
|
+
isLive: false,
|
|
1610
|
+
publishOnApproval: Boolean(publishOnApproval),
|
|
1611
|
+
makerNotes,
|
|
1612
|
+
results: []
|
|
1613
|
+
});
|
|
1479
1614
|
return {
|
|
1480
1615
|
success: true,
|
|
1481
1616
|
pending: true,
|
|
@@ -1515,6 +1650,25 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1515
1650
|
makerNotes
|
|
1516
1651
|
};
|
|
1517
1652
|
pendingBatches.set(batchId, batch);
|
|
1653
|
+
batchJobsMap.set(batchId, {
|
|
1654
|
+
id: batchId,
|
|
1655
|
+
createdAt: batch.createdAt,
|
|
1656
|
+
createdBy: batch.createdBy,
|
|
1657
|
+
status: "queued_offpeak",
|
|
1658
|
+
executionMode: "queue_offpeak",
|
|
1659
|
+
scheduledFor: executionConfig.offPeakStartTime,
|
|
1660
|
+
attribute,
|
|
1661
|
+
value,
|
|
1662
|
+
totalTargets: summary.totalTargets,
|
|
1663
|
+
processedCount: 0,
|
|
1664
|
+
updatedCount: 0,
|
|
1665
|
+
skippedCount: 0,
|
|
1666
|
+
failedCount: 0,
|
|
1667
|
+
isLive: false,
|
|
1668
|
+
publishOnApproval: Boolean(publishOnApproval),
|
|
1669
|
+
makerNotes,
|
|
1670
|
+
results: []
|
|
1671
|
+
});
|
|
1518
1672
|
return {
|
|
1519
1673
|
success: true,
|
|
1520
1674
|
pending: true,
|
|
@@ -1555,6 +1709,26 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1555
1709
|
makerNotes: "Automatically queued due to peak business hours restriction."
|
|
1556
1710
|
};
|
|
1557
1711
|
pendingBatches.set(batchId, batch);
|
|
1712
|
+
batchJobsMap.set(batchId, {
|
|
1713
|
+
id: batchId,
|
|
1714
|
+
createdAt: batch.createdAt,
|
|
1715
|
+
createdBy: batch.createdBy,
|
|
1716
|
+
status: "queued_offpeak",
|
|
1717
|
+
executionMode: "queue_offpeak",
|
|
1718
|
+
scheduledFor: executionConfig.offPeakStartTime,
|
|
1719
|
+
attribute,
|
|
1720
|
+
value,
|
|
1721
|
+
totalTargets: summary.totalTargets,
|
|
1722
|
+
processedCount: 0,
|
|
1723
|
+
updatedCount: 0,
|
|
1724
|
+
skippedCount: 0,
|
|
1725
|
+
failedCount: 0,
|
|
1726
|
+
isLive: false,
|
|
1727
|
+
publishOnApproval: Boolean(publishOnApproval),
|
|
1728
|
+
policyMessage,
|
|
1729
|
+
makerNotes: "Automatically queued due to peak business hours restriction.",
|
|
1730
|
+
results: []
|
|
1731
|
+
});
|
|
1558
1732
|
return {
|
|
1559
1733
|
success: true,
|
|
1560
1734
|
pending: true,
|
|
@@ -1687,6 +1861,226 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1687
1861
|
result
|
|
1688
1862
|
};
|
|
1689
1863
|
},
|
|
1864
|
+
/**
|
|
1865
|
+
* Get real-time or historical processing status of a batch job
|
|
1866
|
+
*/
|
|
1867
|
+
async getBatchStatus(batchId) {
|
|
1868
|
+
if (!batchId) throw new Error("batchId is required");
|
|
1869
|
+
let job = batchJobsMap.get(batchId);
|
|
1870
|
+
if (job) {
|
|
1871
|
+
return { success: true, batch: job };
|
|
1872
|
+
}
|
|
1873
|
+
const pending = pendingBatches.get(batchId);
|
|
1874
|
+
if (pending) {
|
|
1875
|
+
const pendingJob = {
|
|
1876
|
+
id: pending.id,
|
|
1877
|
+
createdAt: pending.createdAt,
|
|
1878
|
+
createdBy: pending.createdBy,
|
|
1879
|
+
status: pending.status,
|
|
1880
|
+
executionMode: pending.executionMode,
|
|
1881
|
+
scheduledFor: pending.scheduledFor,
|
|
1882
|
+
attribute: pending.payload.attribute,
|
|
1883
|
+
value: pending.payload.value,
|
|
1884
|
+
totalTargets: pending.summary.totalTargets,
|
|
1885
|
+
processedCount: 0,
|
|
1886
|
+
updatedCount: 0,
|
|
1887
|
+
skippedCount: 0,
|
|
1888
|
+
failedCount: 0,
|
|
1889
|
+
isLive: false,
|
|
1890
|
+
publishOnApproval: Boolean(pending.payload.publishOnApproval),
|
|
1891
|
+
policyMessage: pending.makerNotes,
|
|
1892
|
+
results: []
|
|
1893
|
+
};
|
|
1894
|
+
return { success: true, batch: pendingJob };
|
|
1895
|
+
}
|
|
1896
|
+
try {
|
|
1897
|
+
const auditService2 = strapi2.plugin("bulk-updater").service("audit");
|
|
1898
|
+
const historyData = await auditService2.getHistory();
|
|
1899
|
+
const records = Array.isArray(historyData?.history) ? historyData.history : [];
|
|
1900
|
+
const found = records.find((r) => r.batchId === batchId);
|
|
1901
|
+
if (found) {
|
|
1902
|
+
const total = found.totalTargets || found.updatedCount + found.skippedCount || 1;
|
|
1903
|
+
const historicalJob = {
|
|
1904
|
+
id: found.batchId,
|
|
1905
|
+
createdAt: found.executedAt || found.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1906
|
+
startedAt: found.executedAt || found.createdAt,
|
|
1907
|
+
completedAt: found.executedAt || found.createdAt,
|
|
1908
|
+
createdBy: {
|
|
1909
|
+
email: found.userEmail || "admin@strapi.io",
|
|
1910
|
+
firstname: found.userName || "Admin"
|
|
1911
|
+
},
|
|
1912
|
+
status: found.isRolledBack ? "failed" : "successful",
|
|
1913
|
+
executionMode: "immediate",
|
|
1914
|
+
attribute: found.attribute,
|
|
1915
|
+
value: found.value,
|
|
1916
|
+
totalTargets: total,
|
|
1917
|
+
processedCount: total,
|
|
1918
|
+
updatedCount: found.updatedCount || 0,
|
|
1919
|
+
skippedCount: found.skippedCount || 0,
|
|
1920
|
+
failedCount: 0,
|
|
1921
|
+
isLive: !found.isRolledBack && found.updatedCount > 0,
|
|
1922
|
+
publishOnApproval: true,
|
|
1923
|
+
results: (found.diffs || []).map((d) => ({
|
|
1924
|
+
uid: d.uid,
|
|
1925
|
+
collectionName: d.collectionName || d.uid,
|
|
1926
|
+
documentId: d.documentId,
|
|
1927
|
+
label: d.label || `Entry #${d.documentId}`,
|
|
1928
|
+
attribute: d.attribute || found.attribute,
|
|
1929
|
+
oldValue: d.oldValue,
|
|
1930
|
+
newValue: d.newValue,
|
|
1931
|
+
status: d.status || "updated",
|
|
1932
|
+
isLive: !found.isRolledBack,
|
|
1933
|
+
message: d.message || "Updated via bulk update"
|
|
1934
|
+
}))
|
|
1935
|
+
};
|
|
1936
|
+
batchJobsMap.set(batchId, historicalJob);
|
|
1937
|
+
return { success: true, batch: historicalJob };
|
|
1938
|
+
}
|
|
1939
|
+
} catch (err) {
|
|
1940
|
+
}
|
|
1941
|
+
throw new Error(`Batch with ID "${batchId}" was not found.`);
|
|
1942
|
+
},
|
|
1943
|
+
/**
|
|
1944
|
+
* Downloadable execution report (CSV or JSON)
|
|
1945
|
+
*/
|
|
1946
|
+
async getBatchReport(batchId, format = "csv") {
|
|
1947
|
+
const statusRes = await this.getBatchStatus(batchId);
|
|
1948
|
+
const batch = statusRes.batch;
|
|
1949
|
+
function escapeCsvVal(val) {
|
|
1950
|
+
if (val === null || val === void 0) return "";
|
|
1951
|
+
let str2 = typeof val === "object" ? JSON.stringify(val) : String(val);
|
|
1952
|
+
if (str2.includes(",") || str2.includes('"') || str2.includes("\n") || str2.includes("\r")) {
|
|
1953
|
+
return `"${str2.replace(/"/g, '""')}"`;
|
|
1954
|
+
}
|
|
1955
|
+
return str2;
|
|
1956
|
+
}
|
|
1957
|
+
const headers = [
|
|
1958
|
+
"Batch ID",
|
|
1959
|
+
"Collection UID",
|
|
1960
|
+
"Collection Name",
|
|
1961
|
+
"Document ID",
|
|
1962
|
+
"Record Title / Identifier",
|
|
1963
|
+
"Attribute Name",
|
|
1964
|
+
"Previous Value",
|
|
1965
|
+
"New Live Value",
|
|
1966
|
+
"Processing Status",
|
|
1967
|
+
"Live On Server",
|
|
1968
|
+
"Details / Message",
|
|
1969
|
+
"Executed At",
|
|
1970
|
+
"Operator"
|
|
1971
|
+
];
|
|
1972
|
+
const rows = [headers.join(",")];
|
|
1973
|
+
for (const item of batch.results || []) {
|
|
1974
|
+
const row = [
|
|
1975
|
+
escapeCsvVal(batch.id),
|
|
1976
|
+
escapeCsvVal(item.uid),
|
|
1977
|
+
escapeCsvVal(item.collectionName),
|
|
1978
|
+
escapeCsvVal(item.documentId),
|
|
1979
|
+
escapeCsvVal(item.label),
|
|
1980
|
+
escapeCsvVal(item.attribute),
|
|
1981
|
+
escapeCsvVal(item.oldValue),
|
|
1982
|
+
escapeCsvVal(item.newValue),
|
|
1983
|
+
escapeCsvVal(item.status === "updated" ? "Updated" : item.status === "error" ? "Failed" : "Skipped"),
|
|
1984
|
+
escapeCsvVal(item.isLive ? "YES (Live on Site)" : "NO"),
|
|
1985
|
+
escapeCsvVal(item.message),
|
|
1986
|
+
escapeCsvVal(batch.completedAt || batch.createdAt),
|
|
1987
|
+
escapeCsvVal(batch.createdBy?.email || "admin@strapi.io")
|
|
1988
|
+
];
|
|
1989
|
+
rows.push(row.join(","));
|
|
1990
|
+
}
|
|
1991
|
+
const safeBatchId = batch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
1992
|
+
const filename = `bulk_update_report_${safeBatchId}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
1993
|
+
return {
|
|
1994
|
+
success: true,
|
|
1995
|
+
filename,
|
|
1996
|
+
csvContent: rows.join("\n"),
|
|
1997
|
+
summary: {
|
|
1998
|
+
batchId: batch.id,
|
|
1999
|
+
status: batch.status,
|
|
2000
|
+
isLive: batch.isLive,
|
|
2001
|
+
totalTargets: batch.totalTargets,
|
|
2002
|
+
updatedCount: batch.updatedCount,
|
|
2003
|
+
skippedCount: batch.skippedCount,
|
|
2004
|
+
failedCount: batch.failedCount,
|
|
2005
|
+
executedAt: batch.completedAt || batch.createdAt,
|
|
2006
|
+
operator: batch.createdBy?.email || "admin@strapi.io"
|
|
2007
|
+
},
|
|
2008
|
+
batch
|
|
2009
|
+
};
|
|
2010
|
+
},
|
|
2011
|
+
/**
|
|
2012
|
+
* Get recent batch jobs for the processing dashboard
|
|
2013
|
+
*/
|
|
2014
|
+
async getRecentBatches() {
|
|
2015
|
+
const mergedMap = /* @__PURE__ */ new Map();
|
|
2016
|
+
for (const [id, job] of batchJobsMap.entries()) {
|
|
2017
|
+
mergedMap.set(id, {
|
|
2018
|
+
id: job.id,
|
|
2019
|
+
createdAt: job.createdAt,
|
|
2020
|
+
completedAt: job.completedAt,
|
|
2021
|
+
status: job.status,
|
|
2022
|
+
isLive: job.isLive,
|
|
2023
|
+
executionMode: job.executionMode,
|
|
2024
|
+
attribute: job.attribute,
|
|
2025
|
+
value: job.value,
|
|
2026
|
+
totalTargets: job.totalTargets,
|
|
2027
|
+
updatedCount: job.updatedCount,
|
|
2028
|
+
skippedCount: job.skippedCount,
|
|
2029
|
+
failedCount: job.failedCount,
|
|
2030
|
+
operator: job.createdBy?.email || "admin@strapi.io"
|
|
2031
|
+
});
|
|
2032
|
+
}
|
|
2033
|
+
for (const [id, p] of pendingBatches.entries()) {
|
|
2034
|
+
if (!mergedMap.has(id)) {
|
|
2035
|
+
mergedMap.set(id, {
|
|
2036
|
+
id: p.id,
|
|
2037
|
+
createdAt: p.createdAt,
|
|
2038
|
+
status: p.status,
|
|
2039
|
+
isLive: false,
|
|
2040
|
+
executionMode: p.executionMode,
|
|
2041
|
+
attribute: p.payload.attribute,
|
|
2042
|
+
value: p.payload.value,
|
|
2043
|
+
totalTargets: p.summary.totalTargets,
|
|
2044
|
+
updatedCount: 0,
|
|
2045
|
+
skippedCount: 0,
|
|
2046
|
+
failedCount: 0,
|
|
2047
|
+
operator: p.createdBy?.email || "admin@strapi.io"
|
|
2048
|
+
});
|
|
2049
|
+
}
|
|
2050
|
+
}
|
|
2051
|
+
try {
|
|
2052
|
+
const auditService2 = strapi2.plugin("bulk-updater").service("audit");
|
|
2053
|
+
const historyData = await auditService2.getHistory();
|
|
2054
|
+
const records = Array.isArray(historyData?.history) ? historyData.history : [];
|
|
2055
|
+
for (const r of records) {
|
|
2056
|
+
if (!mergedMap.has(r.batchId)) {
|
|
2057
|
+
mergedMap.set(r.batchId, {
|
|
2058
|
+
id: r.batchId,
|
|
2059
|
+
createdAt: r.executedAt || r.createdAt,
|
|
2060
|
+
completedAt: r.executedAt,
|
|
2061
|
+
status: r.isRolledBack ? "failed" : "successful",
|
|
2062
|
+
isLive: !r.isRolledBack && r.updatedCount > 0,
|
|
2063
|
+
executionMode: "immediate",
|
|
2064
|
+
attribute: r.attribute,
|
|
2065
|
+
value: r.value,
|
|
2066
|
+
totalTargets: r.totalTargets || r.updatedCount + r.skippedCount || 1,
|
|
2067
|
+
updatedCount: r.updatedCount || 0,
|
|
2068
|
+
skippedCount: r.skippedCount || 0,
|
|
2069
|
+
failedCount: 0,
|
|
2070
|
+
operator: r.userEmail || "admin@strapi.io"
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
} catch {
|
|
2075
|
+
}
|
|
2076
|
+
const batches = Array.from(mergedMap.values()).sort(
|
|
2077
|
+
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
2078
|
+
);
|
|
2079
|
+
return {
|
|
2080
|
+
success: true,
|
|
2081
|
+
batches: batches.slice(0, 50)
|
|
2082
|
+
};
|
|
2083
|
+
},
|
|
1690
2084
|
invalidateCache(uid) {
|
|
1691
2085
|
if (uid) {
|
|
1692
2086
|
invalidateCollectionCache(uid);
|
|
@@ -1798,6 +2192,33 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1798
2192
|
isEnterpriseNativeAvailable: false
|
|
1799
2193
|
};
|
|
1800
2194
|
},
|
|
2195
|
+
/**
|
|
2196
|
+
* Checks whether audit logging is enabled.
|
|
2197
|
+
*/
|
|
2198
|
+
isAuditEnabled() {
|
|
2199
|
+
const config = this.getAuditConfig();
|
|
2200
|
+
return Boolean(config && config.enabled);
|
|
2201
|
+
},
|
|
2202
|
+
/**
|
|
2203
|
+
* Convenience alias to log an operation into the audit trail.
|
|
2204
|
+
*/
|
|
2205
|
+
async logOperation(data) {
|
|
2206
|
+
return this.recordBatch(
|
|
2207
|
+
{
|
|
2208
|
+
batchId: data.batchId,
|
|
2209
|
+
attribute: data.attribute,
|
|
2210
|
+
value: data.value,
|
|
2211
|
+
updatedCount: data.updatedCount,
|
|
2212
|
+
skippedCount: data.skippedCount,
|
|
2213
|
+
totalTargets: data.totalTargets
|
|
2214
|
+
},
|
|
2215
|
+
data.diffs || [],
|
|
2216
|
+
{
|
|
2217
|
+
email: data.userEmail,
|
|
2218
|
+
firstname: data.userName
|
|
2219
|
+
}
|
|
2220
|
+
);
|
|
2221
|
+
},
|
|
1801
2222
|
/**
|
|
1802
2223
|
* Optionally writes an audit event directly to Strapi Enterprise's native Audit Logs table.
|
|
1803
2224
|
*/
|
|
@@ -2289,56 +2710,102 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
2289
2710
|
const violations = [];
|
|
2290
2711
|
let totalScanned = 0;
|
|
2291
2712
|
const allContentTypes = strapi2.contentTypes || {};
|
|
2713
|
+
const allComponents = strapi2.components || {};
|
|
2714
|
+
function schemaHasField(ctSchema) {
|
|
2715
|
+
if (!ctSchema || !ctSchema.attributes) return { hasField: false, populateAttrs: {} };
|
|
2716
|
+
const rawAttrs = ctSchema.attributes;
|
|
2717
|
+
const populateAttrs = {};
|
|
2718
|
+
let hasField = Boolean(rawAttrs[targetField]);
|
|
2719
|
+
for (const [attrKey, attrDef] of Object.entries(rawAttrs)) {
|
|
2720
|
+
if (attrDef.type === "component" && attrDef.component) {
|
|
2721
|
+
const comp = allComponents[attrDef.component];
|
|
2722
|
+
if (comp?.attributes?.[targetField]) {
|
|
2723
|
+
hasField = true;
|
|
2724
|
+
populateAttrs[attrKey] = true;
|
|
2725
|
+
}
|
|
2726
|
+
} else if (attrDef.type === "dynamiczone" && Array.isArray(attrDef.components)) {
|
|
2727
|
+
const matchingComp = attrDef.components.some(
|
|
2728
|
+
(compUid) => allComponents[compUid]?.attributes?.[targetField]
|
|
2729
|
+
);
|
|
2730
|
+
if (matchingComp) {
|
|
2731
|
+
hasField = true;
|
|
2732
|
+
populateAttrs[attrKey] = true;
|
|
2733
|
+
}
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
return { hasField, populateAttrs };
|
|
2737
|
+
}
|
|
2292
2738
|
const candidateUids = Object.keys(allContentTypes).filter(
|
|
2293
2739
|
(uid) => uid.startsWith("api::") && !uid.includes("bulk-updater")
|
|
2294
2740
|
);
|
|
2741
|
+
const relevantCollections = [];
|
|
2295
2742
|
for (const uid of candidateUids) {
|
|
2296
2743
|
const ct = allContentTypes[uid];
|
|
2297
2744
|
if (!ct) continue;
|
|
2298
|
-
const
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2745
|
+
const { hasField, populateAttrs } = schemaHasField(ct);
|
|
2746
|
+
if (hasField) {
|
|
2747
|
+
relevantCollections.push({
|
|
2748
|
+
uid,
|
|
2749
|
+
displayName: ct.info?.displayName || uid,
|
|
2750
|
+
populateOption: Object.keys(populateAttrs).length > 0 ? populateAttrs : void 0
|
|
2302
2751
|
});
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2752
|
+
}
|
|
2753
|
+
}
|
|
2754
|
+
const BATCH_SIZE = 250;
|
|
2755
|
+
await Promise.all(
|
|
2756
|
+
relevantCollections.map(async ({ uid, displayName, populateOption }) => {
|
|
2757
|
+
try {
|
|
2758
|
+
let page = 1;
|
|
2759
|
+
while (true) {
|
|
2760
|
+
const entries = await strapi2.documents(uid).findMany({
|
|
2761
|
+
page,
|
|
2762
|
+
pageSize: BATCH_SIZE,
|
|
2763
|
+
populate: populateOption
|
|
2764
|
+
});
|
|
2765
|
+
if (!entries || entries.length === 0) break;
|
|
2766
|
+
for (const entry of entries) {
|
|
2767
|
+
let traverse = function(obj) {
|
|
2768
|
+
if (!obj || typeof obj !== "object") return;
|
|
2769
|
+
for (const key of Object.keys(obj)) {
|
|
2770
|
+
const val = obj[key];
|
|
2771
|
+
if (key === targetField && typeof val === "string" && val) {
|
|
2772
|
+
const lower = val.toLowerCase();
|
|
2773
|
+
counts[lower] = (counts[lower] || 0) + 1;
|
|
2774
|
+
}
|
|
2775
|
+
if (typeof val === "object" && val !== null) {
|
|
2776
|
+
traverse(val);
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
};
|
|
2780
|
+
totalScanned++;
|
|
2781
|
+
const counts = {};
|
|
2782
|
+
traverse(entry);
|
|
2783
|
+
for (const [optKey, max] of Object.entries(quotas)) {
|
|
2784
|
+
const lowerOpt = optKey.toLowerCase();
|
|
2785
|
+
const actualCount = counts[lowerOpt] || 0;
|
|
2786
|
+
if (actualCount > max) {
|
|
2787
|
+
const entryLabel = entry.title || entry.name || entry.email || `Entry #${entry.documentId}`;
|
|
2788
|
+
violations.push({
|
|
2789
|
+
uid,
|
|
2790
|
+
collectionDisplayName: displayName,
|
|
2791
|
+
documentId: entry.documentId,
|
|
2792
|
+
entryLabel,
|
|
2793
|
+
violatingOption: optKey.toUpperCase(),
|
|
2794
|
+
foundCount: actualCount,
|
|
2795
|
+
maxAllowed: max,
|
|
2796
|
+
editUrl: `/admin/content-manager/collection-types/${uid}/${entry.documentId}`
|
|
2797
|
+
});
|
|
2798
|
+
}
|
|
2314
2799
|
}
|
|
2315
2800
|
}
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
const counts = {};
|
|
2319
|
-
traverse(entry);
|
|
2320
|
-
for (const [optKey, max] of Object.entries(quotas)) {
|
|
2321
|
-
const lowerOpt = optKey.toLowerCase();
|
|
2322
|
-
const actualCount = counts[lowerOpt] || 0;
|
|
2323
|
-
if (actualCount > max) {
|
|
2324
|
-
const entryLabel = entry.title || entry.name || entry.email || `Entry #${entry.documentId}`;
|
|
2325
|
-
violations.push({
|
|
2326
|
-
uid,
|
|
2327
|
-
collectionDisplayName: displayName,
|
|
2328
|
-
documentId: entry.documentId,
|
|
2329
|
-
entryLabel,
|
|
2330
|
-
violatingOption: optKey.toUpperCase(),
|
|
2331
|
-
foundCount: actualCount,
|
|
2332
|
-
maxAllowed: max,
|
|
2333
|
-
editUrl: `/admin/content-manager/collection-types/${uid}/${entry.documentId}`
|
|
2334
|
-
});
|
|
2335
|
-
}
|
|
2801
|
+
if (entries.length < BATCH_SIZE) break;
|
|
2802
|
+
page++;
|
|
2336
2803
|
}
|
|
2804
|
+
} catch (err) {
|
|
2805
|
+
strapi2.log.warn(`[bulk-updater] Failed scanning ${uid} for rule ${ruleId}:`, err);
|
|
2337
2806
|
}
|
|
2338
|
-
}
|
|
2339
|
-
|
|
2340
|
-
}
|
|
2341
|
-
}
|
|
2807
|
+
})
|
|
2808
|
+
);
|
|
2342
2809
|
return {
|
|
2343
2810
|
rule,
|
|
2344
2811
|
totalScanned,
|
|
@@ -2640,12 +3107,13 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2640
3107
|
throw new Error("CSV is empty or could not be parsed.");
|
|
2641
3108
|
}
|
|
2642
3109
|
const attrs = ct.attributes || {};
|
|
2643
|
-
const { mode: mode2, publish = true, user } = options;
|
|
3110
|
+
const { mode: mode2 = "auto", publish = true, user } = options;
|
|
2644
3111
|
const stats = {
|
|
2645
3112
|
total: parsedRows.length,
|
|
2646
3113
|
created: 0,
|
|
2647
3114
|
updated: 0,
|
|
2648
3115
|
skipped: 0,
|
|
3116
|
+
newWithoutDocId: 0,
|
|
2649
3117
|
errors: [],
|
|
2650
3118
|
diffs: []
|
|
2651
3119
|
};
|
|
@@ -2717,7 +3185,7 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2717
3185
|
stats.skipped++;
|
|
2718
3186
|
stats.errors.push({
|
|
2719
3187
|
row: rowNumber,
|
|
2720
|
-
error: "Missing documentId for Update mode."
|
|
3188
|
+
error: "Missing documentId for Update mode. (Tip: Use Smart Auto-Detect mode to auto-add entries without document IDs)."
|
|
2721
3189
|
});
|
|
2722
3190
|
continue;
|
|
2723
3191
|
}
|
|
@@ -2762,7 +3230,7 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2762
3230
|
status: "updated",
|
|
2763
3231
|
message: `Updated via CSV import (row ${rowNumber})`
|
|
2764
3232
|
});
|
|
2765
|
-
} else {
|
|
3233
|
+
} else if (mode2 === "create_or_override") {
|
|
2766
3234
|
if (docId) {
|
|
2767
3235
|
const existing = await strapi2.documents(uid).findOne({
|
|
2768
3236
|
documentId: docId
|
|
@@ -2812,6 +3280,9 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2812
3280
|
}
|
|
2813
3281
|
}
|
|
2814
3282
|
stats.created++;
|
|
3283
|
+
if (!docId) {
|
|
3284
|
+
stats.newWithoutDocId++;
|
|
3285
|
+
}
|
|
2815
3286
|
stats.diffs.push({
|
|
2816
3287
|
uid,
|
|
2817
3288
|
collectionName: ct.info?.displayName || uid,
|
|
@@ -2821,7 +3292,71 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2821
3292
|
oldValue: null,
|
|
2822
3293
|
newValue: payload,
|
|
2823
3294
|
status: "updated",
|
|
2824
|
-
message: `Created via CSV import (row ${rowNumber})`
|
|
3295
|
+
message: docId ? `Created via CSV import (row ${rowNumber})` : `Created as new entry (no documentId provided in CSV - row ${rowNumber})`
|
|
3296
|
+
});
|
|
3297
|
+
} else {
|
|
3298
|
+
if (docId) {
|
|
3299
|
+
const existing = await strapi2.documents(uid).findOne({
|
|
3300
|
+
documentId: docId
|
|
3301
|
+
});
|
|
3302
|
+
if (existing) {
|
|
3303
|
+
const mergedPayload = { ...payload };
|
|
3304
|
+
for (const key of Object.keys(payload)) {
|
|
3305
|
+
if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
|
|
3306
|
+
mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
|
|
3307
|
+
}
|
|
3308
|
+
}
|
|
3309
|
+
await strapi2.documents(uid).update({
|
|
3310
|
+
documentId: docId,
|
|
3311
|
+
data: mergedPayload
|
|
3312
|
+
});
|
|
3313
|
+
if (publish && ct.options?.draftAndPublish) {
|
|
3314
|
+
try {
|
|
3315
|
+
await strapi2.documents(uid).publish({ documentId: docId });
|
|
3316
|
+
} catch (pubErr) {
|
|
3317
|
+
strapi2.log.warn(`[bulk-updater] Auto-publish failed for ${docId}:`, pubErr);
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
stats.updated++;
|
|
3321
|
+
stats.diffs.push({
|
|
3322
|
+
uid,
|
|
3323
|
+
collectionName: ct.info?.displayName || uid,
|
|
3324
|
+
documentId: docId,
|
|
3325
|
+
label: existing.title || existing.name || `Entry #${docId}`,
|
|
3326
|
+
attribute: Object.keys(payload).join(", "),
|
|
3327
|
+
oldValue: existing,
|
|
3328
|
+
newValue: payload,
|
|
3329
|
+
status: "updated",
|
|
3330
|
+
message: `Updated existing entry via smart match (row ${rowNumber})`
|
|
3331
|
+
});
|
|
3332
|
+
continue;
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
const created = await strapi2.documents(uid).create({
|
|
3336
|
+
data: payload
|
|
3337
|
+
});
|
|
3338
|
+
const newDocId = created?.documentId || docId;
|
|
3339
|
+
if (publish && ct.options?.draftAndPublish && newDocId) {
|
|
3340
|
+
try {
|
|
3341
|
+
await strapi2.documents(uid).publish({ documentId: newDocId });
|
|
3342
|
+
} catch (pubErr) {
|
|
3343
|
+
strapi2.log.warn(`[bulk-updater] Auto-publish failed for ${newDocId}:`, pubErr);
|
|
3344
|
+
}
|
|
3345
|
+
}
|
|
3346
|
+
stats.created++;
|
|
3347
|
+
if (!docId) {
|
|
3348
|
+
stats.newWithoutDocId++;
|
|
3349
|
+
}
|
|
3350
|
+
stats.diffs.push({
|
|
3351
|
+
uid,
|
|
3352
|
+
collectionName: ct.info?.displayName || uid,
|
|
3353
|
+
documentId: newDocId,
|
|
3354
|
+
label: created?.title || created?.name || `New Entry #${newDocId}`,
|
|
3355
|
+
attribute: Object.keys(payload).join(", "),
|
|
3356
|
+
oldValue: null,
|
|
3357
|
+
newValue: payload,
|
|
3358
|
+
status: "updated",
|
|
3359
|
+
message: docId ? `Created as new entry (documentId "${docId}" not found in database - row ${rowNumber})` : `Created as new entry (no documentId provided in CSV - row ${rowNumber})`
|
|
2825
3360
|
});
|
|
2826
3361
|
}
|
|
2827
3362
|
} catch (err) {
|
|
@@ -2842,18 +3377,32 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2842
3377
|
if (stats.updated > 0 || stats.created > 0) {
|
|
2843
3378
|
try {
|
|
2844
3379
|
const auditService2 = strapi2.plugin("bulk-updater").service("audit");
|
|
2845
|
-
if (auditService2
|
|
2846
|
-
|
|
2847
|
-
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
3380
|
+
if (auditService2) {
|
|
3381
|
+
const isEnabled = typeof auditService2.isAuditEnabled === "function" ? auditService2.isAuditEnabled() : Boolean(auditService2.getAuditConfig?.().enabled ?? true);
|
|
3382
|
+
if (isEnabled) {
|
|
3383
|
+
const batchSummary = {
|
|
3384
|
+
batchId,
|
|
3385
|
+
attribute: "CSV_IMPORT",
|
|
3386
|
+
value: `Mode: ${mode2} (${stats.created} created, ${stats.updated} updated)`,
|
|
3387
|
+
updatedCount: stats.updated + stats.created,
|
|
3388
|
+
skippedCount: stats.skipped,
|
|
3389
|
+
totalTargets: stats.total
|
|
3390
|
+
};
|
|
3391
|
+
const userObj = {
|
|
3392
|
+
email: userEmail || "admin@strapi.io",
|
|
3393
|
+
firstname: userName || "Admin"
|
|
3394
|
+
};
|
|
3395
|
+
if (typeof auditService2.recordBatch === "function") {
|
|
3396
|
+
await auditService2.recordBatch(batchSummary, stats.diffs, userObj);
|
|
3397
|
+
} else if (typeof auditService2.logOperation === "function") {
|
|
3398
|
+
await auditService2.logOperation({
|
|
3399
|
+
...batchSummary,
|
|
3400
|
+
userEmail,
|
|
3401
|
+
userName,
|
|
3402
|
+
diffs: stats.diffs
|
|
3403
|
+
});
|
|
3404
|
+
}
|
|
3405
|
+
}
|
|
2857
3406
|
}
|
|
2858
3407
|
} catch (auditErr) {
|
|
2859
3408
|
strapi2.log.warn("[bulk-updater] Failed logging CSV import audit:", auditErr);
|