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.js
CHANGED
|
@@ -91,6 +91,30 @@ const routes = {
|
|
|
91
91
|
policies: []
|
|
92
92
|
}
|
|
93
93
|
},
|
|
94
|
+
{
|
|
95
|
+
method: "GET",
|
|
96
|
+
path: "/batches/recent",
|
|
97
|
+
handler: "bulkUpdater.getRecentBatches",
|
|
98
|
+
config: {
|
|
99
|
+
policies: []
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
method: "GET",
|
|
104
|
+
path: "/batches/:batchId/status",
|
|
105
|
+
handler: "bulkUpdater.getBatchStatus",
|
|
106
|
+
config: {
|
|
107
|
+
policies: []
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
method: "GET",
|
|
112
|
+
path: "/batches/:batchId/report",
|
|
113
|
+
handler: "bulkUpdater.getBatchReport",
|
|
114
|
+
config: {
|
|
115
|
+
policies: []
|
|
116
|
+
}
|
|
117
|
+
},
|
|
94
118
|
{
|
|
95
119
|
method: "GET",
|
|
96
120
|
path: "/audit/history",
|
|
@@ -376,6 +400,39 @@ const bulkUpdaterController = ({ strapi: strapi2 }) => ({
|
|
|
376
400
|
ctx.status = 400;
|
|
377
401
|
ctx.body = { success: false, error: err.message };
|
|
378
402
|
}
|
|
403
|
+
},
|
|
404
|
+
async getBatchStatus(ctx) {
|
|
405
|
+
try {
|
|
406
|
+
const { batchId } = ctx.params;
|
|
407
|
+
const service = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
408
|
+
const result = await service.getBatchStatus(batchId);
|
|
409
|
+
ctx.body = result;
|
|
410
|
+
} catch (err) {
|
|
411
|
+
ctx.status = 404;
|
|
412
|
+
ctx.body = { success: false, error: err.message };
|
|
413
|
+
}
|
|
414
|
+
},
|
|
415
|
+
async getBatchReport(ctx) {
|
|
416
|
+
try {
|
|
417
|
+
const { batchId } = ctx.params;
|
|
418
|
+
const { format = "csv" } = ctx.query;
|
|
419
|
+
const service = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
420
|
+
const result = await service.getBatchReport(batchId, format);
|
|
421
|
+
ctx.body = result;
|
|
422
|
+
} catch (err) {
|
|
423
|
+
ctx.status = 500;
|
|
424
|
+
ctx.body = { success: false, error: err.message };
|
|
425
|
+
}
|
|
426
|
+
},
|
|
427
|
+
async getRecentBatches(ctx) {
|
|
428
|
+
try {
|
|
429
|
+
const service = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
430
|
+
const result = await service.getRecentBatches();
|
|
431
|
+
ctx.body = result;
|
|
432
|
+
} catch (err) {
|
|
433
|
+
ctx.status = 500;
|
|
434
|
+
ctx.body = { success: false, error: err.message };
|
|
435
|
+
}
|
|
379
436
|
}
|
|
380
437
|
});
|
|
381
438
|
const auditController = ({ strapi: strapi2 }) => ({
|
|
@@ -548,7 +605,7 @@ const csvController = ({ strapi: strapi2 }) => ({
|
|
|
548
605
|
}
|
|
549
606
|
const csvService2 = strapi2.plugin("bulk-updater").service("csv");
|
|
550
607
|
const result = await csvService2.importCsv(uid, csvText, {
|
|
551
|
-
mode: mode2 === "create_or_override" ? "create_or_override" : "
|
|
608
|
+
mode: mode2 === "update" ? "update" : mode2 === "create_or_override" ? "create_or_override" : "auto",
|
|
552
609
|
publish: publish !== false,
|
|
553
610
|
user: ctx.state.user
|
|
554
611
|
});
|
|
@@ -621,6 +678,7 @@ function checkBusinessHours(config, testDate) {
|
|
|
621
678
|
};
|
|
622
679
|
}
|
|
623
680
|
const pendingBatches = /* @__PURE__ */ new Map();
|
|
681
|
+
const batchJobsMap = /* @__PURE__ */ new Map();
|
|
624
682
|
let isRunnerRunning = false;
|
|
625
683
|
let runnerInterval = null;
|
|
626
684
|
function invalidateCollectionCache(uid) {
|
|
@@ -720,6 +778,31 @@ async function executeBatchInternal(strapi2, {
|
|
|
720
778
|
const results = [];
|
|
721
779
|
let updatedCount = 0;
|
|
722
780
|
let skippedCount = 0;
|
|
781
|
+
const jobState = {
|
|
782
|
+
id: batchId,
|
|
783
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
784
|
+
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
785
|
+
createdBy: currentUser ? {
|
|
786
|
+
id: currentUser.id,
|
|
787
|
+
email: currentUser.email,
|
|
788
|
+
firstname: currentUser.firstname,
|
|
789
|
+
lastname: currentUser.lastname,
|
|
790
|
+
username: currentUser.username
|
|
791
|
+
} : { email: "admin@system.local" },
|
|
792
|
+
status: "processing",
|
|
793
|
+
executionMode: "immediate",
|
|
794
|
+
attribute,
|
|
795
|
+
value,
|
|
796
|
+
totalTargets: targetsMap.size,
|
|
797
|
+
processedCount: 0,
|
|
798
|
+
updatedCount: 0,
|
|
799
|
+
skippedCount: 0,
|
|
800
|
+
failedCount: 0,
|
|
801
|
+
isLive: false,
|
|
802
|
+
publishOnApproval: Boolean(publishOnApproval),
|
|
803
|
+
results: []
|
|
804
|
+
};
|
|
805
|
+
batchJobsMap.set(batchId, jobState);
|
|
723
806
|
let regexObj = null;
|
|
724
807
|
if (isRegex && regexPattern) {
|
|
725
808
|
regexObj = new RegExp(regexPattern, "g");
|
|
@@ -954,11 +1037,24 @@ async function executeBatchInternal(strapi2, {
|
|
|
954
1037
|
else if (res.status === "skipped") skippedCount++;
|
|
955
1038
|
results.push(res);
|
|
956
1039
|
}
|
|
1040
|
+
jobState.processedCount = results.length;
|
|
1041
|
+
jobState.updatedCount = updatedCount;
|
|
1042
|
+
jobState.skippedCount = skippedCount;
|
|
1043
|
+
jobState.failedCount = results.filter((r) => r.status === "error").length;
|
|
957
1044
|
}
|
|
958
1045
|
const affectedUids = new Set(targets.map((t) => t.uid));
|
|
959
1046
|
affectedUids.forEach((uid) => {
|
|
960
1047
|
invalidateCollectionCache(uid);
|
|
961
1048
|
});
|
|
1049
|
+
const failedCount = results.filter((r) => r.status === "error").length;
|
|
1050
|
+
jobState.failedCount = failedCount;
|
|
1051
|
+
jobState.completedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
1052
|
+
jobState.isLive = updatedCount > 0;
|
|
1053
|
+
jobState.status = failedCount === 0 && updatedCount > 0 ? "successful" : failedCount > 0 && updatedCount > 0 ? "partial_failure" : failedCount > 0 ? "failed" : "successful";
|
|
1054
|
+
jobState.results = results.map((r) => ({
|
|
1055
|
+
...r,
|
|
1056
|
+
isLive: r.status === "updated"
|
|
1057
|
+
}));
|
|
962
1058
|
const batchSummary = {
|
|
963
1059
|
batchId,
|
|
964
1060
|
attribute,
|
|
@@ -980,7 +1076,10 @@ async function executeBatchInternal(strapi2, {
|
|
|
980
1076
|
value,
|
|
981
1077
|
updatedCount,
|
|
982
1078
|
skippedCount,
|
|
1079
|
+
failedCount,
|
|
983
1080
|
totalTargets: targetsMap.size,
|
|
1081
|
+
isLive: jobState.isLive,
|
|
1082
|
+
status: jobState.status,
|
|
984
1083
|
results
|
|
985
1084
|
};
|
|
986
1085
|
}
|
|
@@ -1108,69 +1207,87 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1108
1207
|
}
|
|
1109
1208
|
const collections = [];
|
|
1110
1209
|
const signatures = [];
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
populate: populateOption
|
|
1210
|
+
const BATCH_CONCURRENCY = 10;
|
|
1211
|
+
const collectionResults = [];
|
|
1212
|
+
for (let i = 0; i < staticSchemaCache.targetUids.length; i += BATCH_CONCURRENCY) {
|
|
1213
|
+
const batchUids = staticSchemaCache.targetUids.slice(i, i + BATCH_CONCURRENCY);
|
|
1214
|
+
const batchRes = await Promise.all(
|
|
1215
|
+
batchUids.map(async (uid) => {
|
|
1216
|
+
const meta = staticSchemaCache.metaByUid[uid];
|
|
1217
|
+
if (!meta) return null;
|
|
1218
|
+
const signature = await getCollectionSignature(strapi2, uid);
|
|
1219
|
+
const cached = modelsCache.get(uid);
|
|
1220
|
+
if (cached && cached.signature === signature) {
|
|
1221
|
+
return {
|
|
1222
|
+
collection: {
|
|
1223
|
+
...meta,
|
|
1224
|
+
totalEntries: cached.totalCount,
|
|
1225
|
+
entries: cached.entries
|
|
1226
|
+
},
|
|
1227
|
+
signature: `${uid}=${signature}`
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
let entries = [];
|
|
1231
|
+
let totalCount = 0;
|
|
1232
|
+
try {
|
|
1233
|
+
const ctAttrs = allContentTypes[uid]?.attributes || {};
|
|
1234
|
+
const relKeys = Object.keys(meta.relations || {});
|
|
1235
|
+
const compKeys = Object.keys(ctAttrs).filter((k) => ctAttrs[k].type === "component");
|
|
1236
|
+
const populateOptionObj = {};
|
|
1237
|
+
relKeys.forEach((k) => {
|
|
1238
|
+
populateOptionObj[k] = true;
|
|
1141
1239
|
});
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
totalCount = 1;
|
|
1145
|
-
}
|
|
1146
|
-
} else {
|
|
1147
|
-
totalCount = await strapi2.documents(uid).count();
|
|
1148
|
-
entries = await strapi2.documents(uid).findMany({
|
|
1149
|
-
limit: 10,
|
|
1150
|
-
sort: { createdAt: "desc" },
|
|
1151
|
-
populate: populateOption
|
|
1240
|
+
compKeys.forEach((k) => {
|
|
1241
|
+
populateOptionObj[k] = true;
|
|
1152
1242
|
});
|
|
1243
|
+
const populateOption = Object.keys(populateOptionObj).length > 0 ? populateOptionObj : void 0;
|
|
1244
|
+
if (meta.isSingleType) {
|
|
1245
|
+
const singleDoc = await strapi2.documents(uid).findFirst({
|
|
1246
|
+
populate: populateOption
|
|
1247
|
+
});
|
|
1248
|
+
if (singleDoc) {
|
|
1249
|
+
entries = [singleDoc];
|
|
1250
|
+
totalCount = 1;
|
|
1251
|
+
}
|
|
1252
|
+
} else {
|
|
1253
|
+
totalCount = await strapi2.documents(uid).count();
|
|
1254
|
+
entries = await strapi2.documents(uid).findMany({
|
|
1255
|
+
limit: 10,
|
|
1256
|
+
sort: { createdAt: "desc" },
|
|
1257
|
+
populate: populateOption
|
|
1258
|
+
});
|
|
1259
|
+
}
|
|
1260
|
+
} catch (err) {
|
|
1261
|
+
strapi2.log.error(`[bulk-updater] Failed to fetch entries for ${uid}:`, err);
|
|
1262
|
+
entries = [];
|
|
1263
|
+
totalCount = 0;
|
|
1153
1264
|
}
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1265
|
+
const formattedEntries = (entries || []).map((entry) => ({
|
|
1266
|
+
id: entry.id,
|
|
1267
|
+
documentId: entry.documentId,
|
|
1268
|
+
...entry
|
|
1269
|
+
}));
|
|
1270
|
+
modelsCache.set(uid, {
|
|
1271
|
+
signature,
|
|
1272
|
+
totalCount,
|
|
1273
|
+
entries: formattedEntries
|
|
1274
|
+
});
|
|
1275
|
+
return {
|
|
1276
|
+
collection: {
|
|
1277
|
+
...meta,
|
|
1278
|
+
totalEntries: totalCount,
|
|
1279
|
+
entries: formattedEntries
|
|
1280
|
+
},
|
|
1281
|
+
signature: `${uid}=${signature}`
|
|
1282
|
+
};
|
|
1283
|
+
})
|
|
1284
|
+
);
|
|
1285
|
+
collectionResults.push(...batchRes);
|
|
1286
|
+
}
|
|
1287
|
+
for (const res of collectionResults) {
|
|
1288
|
+
if (res) {
|
|
1289
|
+
collections.push(res.collection);
|
|
1290
|
+
signatures.push(res.signature);
|
|
1174
1291
|
}
|
|
1175
1292
|
}
|
|
1176
1293
|
const executionConfig = getExecutionWindowConfig(strapi2);
|
|
@@ -1489,6 +1606,24 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1489
1606
|
makerNotes
|
|
1490
1607
|
};
|
|
1491
1608
|
pendingBatches.set(batchId, batch);
|
|
1609
|
+
batchJobsMap.set(batchId, {
|
|
1610
|
+
id: batchId,
|
|
1611
|
+
createdAt: batch.createdAt,
|
|
1612
|
+
createdBy: batch.createdBy,
|
|
1613
|
+
status: "pending_approval",
|
|
1614
|
+
executionMode: "maker_checker",
|
|
1615
|
+
attribute,
|
|
1616
|
+
value,
|
|
1617
|
+
totalTargets: summary.totalTargets,
|
|
1618
|
+
processedCount: 0,
|
|
1619
|
+
updatedCount: 0,
|
|
1620
|
+
skippedCount: 0,
|
|
1621
|
+
failedCount: 0,
|
|
1622
|
+
isLive: false,
|
|
1623
|
+
publishOnApproval: Boolean(publishOnApproval),
|
|
1624
|
+
makerNotes,
|
|
1625
|
+
results: []
|
|
1626
|
+
});
|
|
1492
1627
|
return {
|
|
1493
1628
|
success: true,
|
|
1494
1629
|
pending: true,
|
|
@@ -1528,6 +1663,25 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1528
1663
|
makerNotes
|
|
1529
1664
|
};
|
|
1530
1665
|
pendingBatches.set(batchId, batch);
|
|
1666
|
+
batchJobsMap.set(batchId, {
|
|
1667
|
+
id: batchId,
|
|
1668
|
+
createdAt: batch.createdAt,
|
|
1669
|
+
createdBy: batch.createdBy,
|
|
1670
|
+
status: "queued_offpeak",
|
|
1671
|
+
executionMode: "queue_offpeak",
|
|
1672
|
+
scheduledFor: executionConfig.offPeakStartTime,
|
|
1673
|
+
attribute,
|
|
1674
|
+
value,
|
|
1675
|
+
totalTargets: summary.totalTargets,
|
|
1676
|
+
processedCount: 0,
|
|
1677
|
+
updatedCount: 0,
|
|
1678
|
+
skippedCount: 0,
|
|
1679
|
+
failedCount: 0,
|
|
1680
|
+
isLive: false,
|
|
1681
|
+
publishOnApproval: Boolean(publishOnApproval),
|
|
1682
|
+
makerNotes,
|
|
1683
|
+
results: []
|
|
1684
|
+
});
|
|
1531
1685
|
return {
|
|
1532
1686
|
success: true,
|
|
1533
1687
|
pending: true,
|
|
@@ -1568,6 +1722,26 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1568
1722
|
makerNotes: "Automatically queued due to peak business hours restriction."
|
|
1569
1723
|
};
|
|
1570
1724
|
pendingBatches.set(batchId, batch);
|
|
1725
|
+
batchJobsMap.set(batchId, {
|
|
1726
|
+
id: batchId,
|
|
1727
|
+
createdAt: batch.createdAt,
|
|
1728
|
+
createdBy: batch.createdBy,
|
|
1729
|
+
status: "queued_offpeak",
|
|
1730
|
+
executionMode: "queue_offpeak",
|
|
1731
|
+
scheduledFor: executionConfig.offPeakStartTime,
|
|
1732
|
+
attribute,
|
|
1733
|
+
value,
|
|
1734
|
+
totalTargets: summary.totalTargets,
|
|
1735
|
+
processedCount: 0,
|
|
1736
|
+
updatedCount: 0,
|
|
1737
|
+
skippedCount: 0,
|
|
1738
|
+
failedCount: 0,
|
|
1739
|
+
isLive: false,
|
|
1740
|
+
publishOnApproval: Boolean(publishOnApproval),
|
|
1741
|
+
policyMessage,
|
|
1742
|
+
makerNotes: "Automatically queued due to peak business hours restriction.",
|
|
1743
|
+
results: []
|
|
1744
|
+
});
|
|
1571
1745
|
return {
|
|
1572
1746
|
success: true,
|
|
1573
1747
|
pending: true,
|
|
@@ -1700,6 +1874,226 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1700
1874
|
result
|
|
1701
1875
|
};
|
|
1702
1876
|
},
|
|
1877
|
+
/**
|
|
1878
|
+
* Get real-time or historical processing status of a batch job
|
|
1879
|
+
*/
|
|
1880
|
+
async getBatchStatus(batchId) {
|
|
1881
|
+
if (!batchId) throw new Error("batchId is required");
|
|
1882
|
+
let job = batchJobsMap.get(batchId);
|
|
1883
|
+
if (job) {
|
|
1884
|
+
return { success: true, batch: job };
|
|
1885
|
+
}
|
|
1886
|
+
const pending = pendingBatches.get(batchId);
|
|
1887
|
+
if (pending) {
|
|
1888
|
+
const pendingJob = {
|
|
1889
|
+
id: pending.id,
|
|
1890
|
+
createdAt: pending.createdAt,
|
|
1891
|
+
createdBy: pending.createdBy,
|
|
1892
|
+
status: pending.status,
|
|
1893
|
+
executionMode: pending.executionMode,
|
|
1894
|
+
scheduledFor: pending.scheduledFor,
|
|
1895
|
+
attribute: pending.payload.attribute,
|
|
1896
|
+
value: pending.payload.value,
|
|
1897
|
+
totalTargets: pending.summary.totalTargets,
|
|
1898
|
+
processedCount: 0,
|
|
1899
|
+
updatedCount: 0,
|
|
1900
|
+
skippedCount: 0,
|
|
1901
|
+
failedCount: 0,
|
|
1902
|
+
isLive: false,
|
|
1903
|
+
publishOnApproval: Boolean(pending.payload.publishOnApproval),
|
|
1904
|
+
policyMessage: pending.makerNotes,
|
|
1905
|
+
results: []
|
|
1906
|
+
};
|
|
1907
|
+
return { success: true, batch: pendingJob };
|
|
1908
|
+
}
|
|
1909
|
+
try {
|
|
1910
|
+
const auditService2 = strapi2.plugin("bulk-updater").service("audit");
|
|
1911
|
+
const historyData = await auditService2.getHistory();
|
|
1912
|
+
const records = Array.isArray(historyData?.history) ? historyData.history : [];
|
|
1913
|
+
const found = records.find((r) => r.batchId === batchId);
|
|
1914
|
+
if (found) {
|
|
1915
|
+
const total = found.totalTargets || found.updatedCount + found.skippedCount || 1;
|
|
1916
|
+
const historicalJob = {
|
|
1917
|
+
id: found.batchId,
|
|
1918
|
+
createdAt: found.executedAt || found.createdAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
1919
|
+
startedAt: found.executedAt || found.createdAt,
|
|
1920
|
+
completedAt: found.executedAt || found.createdAt,
|
|
1921
|
+
createdBy: {
|
|
1922
|
+
email: found.userEmail || "admin@strapi.io",
|
|
1923
|
+
firstname: found.userName || "Admin"
|
|
1924
|
+
},
|
|
1925
|
+
status: found.isRolledBack ? "failed" : "successful",
|
|
1926
|
+
executionMode: "immediate",
|
|
1927
|
+
attribute: found.attribute,
|
|
1928
|
+
value: found.value,
|
|
1929
|
+
totalTargets: total,
|
|
1930
|
+
processedCount: total,
|
|
1931
|
+
updatedCount: found.updatedCount || 0,
|
|
1932
|
+
skippedCount: found.skippedCount || 0,
|
|
1933
|
+
failedCount: 0,
|
|
1934
|
+
isLive: !found.isRolledBack && found.updatedCount > 0,
|
|
1935
|
+
publishOnApproval: true,
|
|
1936
|
+
results: (found.diffs || []).map((d) => ({
|
|
1937
|
+
uid: d.uid,
|
|
1938
|
+
collectionName: d.collectionName || d.uid,
|
|
1939
|
+
documentId: d.documentId,
|
|
1940
|
+
label: d.label || `Entry #${d.documentId}`,
|
|
1941
|
+
attribute: d.attribute || found.attribute,
|
|
1942
|
+
oldValue: d.oldValue,
|
|
1943
|
+
newValue: d.newValue,
|
|
1944
|
+
status: d.status || "updated",
|
|
1945
|
+
isLive: !found.isRolledBack,
|
|
1946
|
+
message: d.message || "Updated via bulk update"
|
|
1947
|
+
}))
|
|
1948
|
+
};
|
|
1949
|
+
batchJobsMap.set(batchId, historicalJob);
|
|
1950
|
+
return { success: true, batch: historicalJob };
|
|
1951
|
+
}
|
|
1952
|
+
} catch (err) {
|
|
1953
|
+
}
|
|
1954
|
+
throw new Error(`Batch with ID "${batchId}" was not found.`);
|
|
1955
|
+
},
|
|
1956
|
+
/**
|
|
1957
|
+
* Downloadable execution report (CSV or JSON)
|
|
1958
|
+
*/
|
|
1959
|
+
async getBatchReport(batchId, format = "csv") {
|
|
1960
|
+
const statusRes = await this.getBatchStatus(batchId);
|
|
1961
|
+
const batch = statusRes.batch;
|
|
1962
|
+
function escapeCsvVal(val) {
|
|
1963
|
+
if (val === null || val === void 0) return "";
|
|
1964
|
+
let str2 = typeof val === "object" ? JSON.stringify(val) : String(val);
|
|
1965
|
+
if (str2.includes(",") || str2.includes('"') || str2.includes("\n") || str2.includes("\r")) {
|
|
1966
|
+
return `"${str2.replace(/"/g, '""')}"`;
|
|
1967
|
+
}
|
|
1968
|
+
return str2;
|
|
1969
|
+
}
|
|
1970
|
+
const headers = [
|
|
1971
|
+
"Batch ID",
|
|
1972
|
+
"Collection UID",
|
|
1973
|
+
"Collection Name",
|
|
1974
|
+
"Document ID",
|
|
1975
|
+
"Record Title / Identifier",
|
|
1976
|
+
"Attribute Name",
|
|
1977
|
+
"Previous Value",
|
|
1978
|
+
"New Live Value",
|
|
1979
|
+
"Processing Status",
|
|
1980
|
+
"Live On Server",
|
|
1981
|
+
"Details / Message",
|
|
1982
|
+
"Executed At",
|
|
1983
|
+
"Operator"
|
|
1984
|
+
];
|
|
1985
|
+
const rows = [headers.join(",")];
|
|
1986
|
+
for (const item of batch.results || []) {
|
|
1987
|
+
const row = [
|
|
1988
|
+
escapeCsvVal(batch.id),
|
|
1989
|
+
escapeCsvVal(item.uid),
|
|
1990
|
+
escapeCsvVal(item.collectionName),
|
|
1991
|
+
escapeCsvVal(item.documentId),
|
|
1992
|
+
escapeCsvVal(item.label),
|
|
1993
|
+
escapeCsvVal(item.attribute),
|
|
1994
|
+
escapeCsvVal(item.oldValue),
|
|
1995
|
+
escapeCsvVal(item.newValue),
|
|
1996
|
+
escapeCsvVal(item.status === "updated" ? "Updated" : item.status === "error" ? "Failed" : "Skipped"),
|
|
1997
|
+
escapeCsvVal(item.isLive ? "YES (Live on Site)" : "NO"),
|
|
1998
|
+
escapeCsvVal(item.message),
|
|
1999
|
+
escapeCsvVal(batch.completedAt || batch.createdAt),
|
|
2000
|
+
escapeCsvVal(batch.createdBy?.email || "admin@strapi.io")
|
|
2001
|
+
];
|
|
2002
|
+
rows.push(row.join(","));
|
|
2003
|
+
}
|
|
2004
|
+
const safeBatchId = batch.id.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
2005
|
+
const filename = `bulk_update_report_${safeBatchId}_${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}.csv`;
|
|
2006
|
+
return {
|
|
2007
|
+
success: true,
|
|
2008
|
+
filename,
|
|
2009
|
+
csvContent: rows.join("\n"),
|
|
2010
|
+
summary: {
|
|
2011
|
+
batchId: batch.id,
|
|
2012
|
+
status: batch.status,
|
|
2013
|
+
isLive: batch.isLive,
|
|
2014
|
+
totalTargets: batch.totalTargets,
|
|
2015
|
+
updatedCount: batch.updatedCount,
|
|
2016
|
+
skippedCount: batch.skippedCount,
|
|
2017
|
+
failedCount: batch.failedCount,
|
|
2018
|
+
executedAt: batch.completedAt || batch.createdAt,
|
|
2019
|
+
operator: batch.createdBy?.email || "admin@strapi.io"
|
|
2020
|
+
},
|
|
2021
|
+
batch
|
|
2022
|
+
};
|
|
2023
|
+
},
|
|
2024
|
+
/**
|
|
2025
|
+
* Get recent batch jobs for the processing dashboard
|
|
2026
|
+
*/
|
|
2027
|
+
async getRecentBatches() {
|
|
2028
|
+
const mergedMap = /* @__PURE__ */ new Map();
|
|
2029
|
+
for (const [id, job] of batchJobsMap.entries()) {
|
|
2030
|
+
mergedMap.set(id, {
|
|
2031
|
+
id: job.id,
|
|
2032
|
+
createdAt: job.createdAt,
|
|
2033
|
+
completedAt: job.completedAt,
|
|
2034
|
+
status: job.status,
|
|
2035
|
+
isLive: job.isLive,
|
|
2036
|
+
executionMode: job.executionMode,
|
|
2037
|
+
attribute: job.attribute,
|
|
2038
|
+
value: job.value,
|
|
2039
|
+
totalTargets: job.totalTargets,
|
|
2040
|
+
updatedCount: job.updatedCount,
|
|
2041
|
+
skippedCount: job.skippedCount,
|
|
2042
|
+
failedCount: job.failedCount,
|
|
2043
|
+
operator: job.createdBy?.email || "admin@strapi.io"
|
|
2044
|
+
});
|
|
2045
|
+
}
|
|
2046
|
+
for (const [id, p] of pendingBatches.entries()) {
|
|
2047
|
+
if (!mergedMap.has(id)) {
|
|
2048
|
+
mergedMap.set(id, {
|
|
2049
|
+
id: p.id,
|
|
2050
|
+
createdAt: p.createdAt,
|
|
2051
|
+
status: p.status,
|
|
2052
|
+
isLive: false,
|
|
2053
|
+
executionMode: p.executionMode,
|
|
2054
|
+
attribute: p.payload.attribute,
|
|
2055
|
+
value: p.payload.value,
|
|
2056
|
+
totalTargets: p.summary.totalTargets,
|
|
2057
|
+
updatedCount: 0,
|
|
2058
|
+
skippedCount: 0,
|
|
2059
|
+
failedCount: 0,
|
|
2060
|
+
operator: p.createdBy?.email || "admin@strapi.io"
|
|
2061
|
+
});
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
try {
|
|
2065
|
+
const auditService2 = strapi2.plugin("bulk-updater").service("audit");
|
|
2066
|
+
const historyData = await auditService2.getHistory();
|
|
2067
|
+
const records = Array.isArray(historyData?.history) ? historyData.history : [];
|
|
2068
|
+
for (const r of records) {
|
|
2069
|
+
if (!mergedMap.has(r.batchId)) {
|
|
2070
|
+
mergedMap.set(r.batchId, {
|
|
2071
|
+
id: r.batchId,
|
|
2072
|
+
createdAt: r.executedAt || r.createdAt,
|
|
2073
|
+
completedAt: r.executedAt,
|
|
2074
|
+
status: r.isRolledBack ? "failed" : "successful",
|
|
2075
|
+
isLive: !r.isRolledBack && r.updatedCount > 0,
|
|
2076
|
+
executionMode: "immediate",
|
|
2077
|
+
attribute: r.attribute,
|
|
2078
|
+
value: r.value,
|
|
2079
|
+
totalTargets: r.totalTargets || r.updatedCount + r.skippedCount || 1,
|
|
2080
|
+
updatedCount: r.updatedCount || 0,
|
|
2081
|
+
skippedCount: r.skippedCount || 0,
|
|
2082
|
+
failedCount: 0,
|
|
2083
|
+
operator: r.userEmail || "admin@strapi.io"
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
} catch {
|
|
2088
|
+
}
|
|
2089
|
+
const batches = Array.from(mergedMap.values()).sort(
|
|
2090
|
+
(a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
|
|
2091
|
+
);
|
|
2092
|
+
return {
|
|
2093
|
+
success: true,
|
|
2094
|
+
batches: batches.slice(0, 50)
|
|
2095
|
+
};
|
|
2096
|
+
},
|
|
1703
2097
|
invalidateCache(uid) {
|
|
1704
2098
|
if (uid) {
|
|
1705
2099
|
invalidateCollectionCache(uid);
|
|
@@ -1811,6 +2205,33 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1811
2205
|
isEnterpriseNativeAvailable: false
|
|
1812
2206
|
};
|
|
1813
2207
|
},
|
|
2208
|
+
/**
|
|
2209
|
+
* Checks whether audit logging is enabled.
|
|
2210
|
+
*/
|
|
2211
|
+
isAuditEnabled() {
|
|
2212
|
+
const config = this.getAuditConfig();
|
|
2213
|
+
return Boolean(config && config.enabled);
|
|
2214
|
+
},
|
|
2215
|
+
/**
|
|
2216
|
+
* Convenience alias to log an operation into the audit trail.
|
|
2217
|
+
*/
|
|
2218
|
+
async logOperation(data) {
|
|
2219
|
+
return this.recordBatch(
|
|
2220
|
+
{
|
|
2221
|
+
batchId: data.batchId,
|
|
2222
|
+
attribute: data.attribute,
|
|
2223
|
+
value: data.value,
|
|
2224
|
+
updatedCount: data.updatedCount,
|
|
2225
|
+
skippedCount: data.skippedCount,
|
|
2226
|
+
totalTargets: data.totalTargets
|
|
2227
|
+
},
|
|
2228
|
+
data.diffs || [],
|
|
2229
|
+
{
|
|
2230
|
+
email: data.userEmail,
|
|
2231
|
+
firstname: data.userName
|
|
2232
|
+
}
|
|
2233
|
+
);
|
|
2234
|
+
},
|
|
1814
2235
|
/**
|
|
1815
2236
|
* Optionally writes an audit event directly to Strapi Enterprise's native Audit Logs table.
|
|
1816
2237
|
*/
|
|
@@ -2302,56 +2723,102 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
|
|
|
2302
2723
|
const violations = [];
|
|
2303
2724
|
let totalScanned = 0;
|
|
2304
2725
|
const allContentTypes = strapi2.contentTypes || {};
|
|
2726
|
+
const allComponents = strapi2.components || {};
|
|
2727
|
+
function schemaHasField(ctSchema) {
|
|
2728
|
+
if (!ctSchema || !ctSchema.attributes) return { hasField: false, populateAttrs: {} };
|
|
2729
|
+
const rawAttrs = ctSchema.attributes;
|
|
2730
|
+
const populateAttrs = {};
|
|
2731
|
+
let hasField = Boolean(rawAttrs[targetField]);
|
|
2732
|
+
for (const [attrKey, attrDef] of Object.entries(rawAttrs)) {
|
|
2733
|
+
if (attrDef.type === "component" && attrDef.component) {
|
|
2734
|
+
const comp = allComponents[attrDef.component];
|
|
2735
|
+
if (comp?.attributes?.[targetField]) {
|
|
2736
|
+
hasField = true;
|
|
2737
|
+
populateAttrs[attrKey] = true;
|
|
2738
|
+
}
|
|
2739
|
+
} else if (attrDef.type === "dynamiczone" && Array.isArray(attrDef.components)) {
|
|
2740
|
+
const matchingComp = attrDef.components.some(
|
|
2741
|
+
(compUid) => allComponents[compUid]?.attributes?.[targetField]
|
|
2742
|
+
);
|
|
2743
|
+
if (matchingComp) {
|
|
2744
|
+
hasField = true;
|
|
2745
|
+
populateAttrs[attrKey] = true;
|
|
2746
|
+
}
|
|
2747
|
+
}
|
|
2748
|
+
}
|
|
2749
|
+
return { hasField, populateAttrs };
|
|
2750
|
+
}
|
|
2305
2751
|
const candidateUids = Object.keys(allContentTypes).filter(
|
|
2306
2752
|
(uid) => uid.startsWith("api::") && !uid.includes("bulk-updater")
|
|
2307
2753
|
);
|
|
2754
|
+
const relevantCollections = [];
|
|
2308
2755
|
for (const uid of candidateUids) {
|
|
2309
2756
|
const ct = allContentTypes[uid];
|
|
2310
2757
|
if (!ct) continue;
|
|
2311
|
-
const
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2758
|
+
const { hasField, populateAttrs } = schemaHasField(ct);
|
|
2759
|
+
if (hasField) {
|
|
2760
|
+
relevantCollections.push({
|
|
2761
|
+
uid,
|
|
2762
|
+
displayName: ct.info?.displayName || uid,
|
|
2763
|
+
populateOption: Object.keys(populateAttrs).length > 0 ? populateAttrs : void 0
|
|
2315
2764
|
});
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2765
|
+
}
|
|
2766
|
+
}
|
|
2767
|
+
const BATCH_SIZE = 250;
|
|
2768
|
+
await Promise.all(
|
|
2769
|
+
relevantCollections.map(async ({ uid, displayName, populateOption }) => {
|
|
2770
|
+
try {
|
|
2771
|
+
let page = 1;
|
|
2772
|
+
while (true) {
|
|
2773
|
+
const entries = await strapi2.documents(uid).findMany({
|
|
2774
|
+
page,
|
|
2775
|
+
pageSize: BATCH_SIZE,
|
|
2776
|
+
populate: populateOption
|
|
2777
|
+
});
|
|
2778
|
+
if (!entries || entries.length === 0) break;
|
|
2779
|
+
for (const entry of entries) {
|
|
2780
|
+
let traverse = function(obj) {
|
|
2781
|
+
if (!obj || typeof obj !== "object") return;
|
|
2782
|
+
for (const key of Object.keys(obj)) {
|
|
2783
|
+
const val = obj[key];
|
|
2784
|
+
if (key === targetField && typeof val === "string" && val) {
|
|
2785
|
+
const lower = val.toLowerCase();
|
|
2786
|
+
counts[lower] = (counts[lower] || 0) + 1;
|
|
2787
|
+
}
|
|
2788
|
+
if (typeof val === "object" && val !== null) {
|
|
2789
|
+
traverse(val);
|
|
2790
|
+
}
|
|
2791
|
+
}
|
|
2792
|
+
};
|
|
2793
|
+
totalScanned++;
|
|
2794
|
+
const counts = {};
|
|
2795
|
+
traverse(entry);
|
|
2796
|
+
for (const [optKey, max] of Object.entries(quotas)) {
|
|
2797
|
+
const lowerOpt = optKey.toLowerCase();
|
|
2798
|
+
const actualCount = counts[lowerOpt] || 0;
|
|
2799
|
+
if (actualCount > max) {
|
|
2800
|
+
const entryLabel = entry.title || entry.name || entry.email || `Entry #${entry.documentId}`;
|
|
2801
|
+
violations.push({
|
|
2802
|
+
uid,
|
|
2803
|
+
collectionDisplayName: displayName,
|
|
2804
|
+
documentId: entry.documentId,
|
|
2805
|
+
entryLabel,
|
|
2806
|
+
violatingOption: optKey.toUpperCase(),
|
|
2807
|
+
foundCount: actualCount,
|
|
2808
|
+
maxAllowed: max,
|
|
2809
|
+
editUrl: `/admin/content-manager/collection-types/${uid}/${entry.documentId}`
|
|
2810
|
+
});
|
|
2811
|
+
}
|
|
2327
2812
|
}
|
|
2328
2813
|
}
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
const counts = {};
|
|
2332
|
-
traverse(entry);
|
|
2333
|
-
for (const [optKey, max] of Object.entries(quotas)) {
|
|
2334
|
-
const lowerOpt = optKey.toLowerCase();
|
|
2335
|
-
const actualCount = counts[lowerOpt] || 0;
|
|
2336
|
-
if (actualCount > max) {
|
|
2337
|
-
const entryLabel = entry.title || entry.name || entry.email || `Entry #${entry.documentId}`;
|
|
2338
|
-
violations.push({
|
|
2339
|
-
uid,
|
|
2340
|
-
collectionDisplayName: displayName,
|
|
2341
|
-
documentId: entry.documentId,
|
|
2342
|
-
entryLabel,
|
|
2343
|
-
violatingOption: optKey.toUpperCase(),
|
|
2344
|
-
foundCount: actualCount,
|
|
2345
|
-
maxAllowed: max,
|
|
2346
|
-
editUrl: `/admin/content-manager/collection-types/${uid}/${entry.documentId}`
|
|
2347
|
-
});
|
|
2348
|
-
}
|
|
2814
|
+
if (entries.length < BATCH_SIZE) break;
|
|
2815
|
+
page++;
|
|
2349
2816
|
}
|
|
2817
|
+
} catch (err) {
|
|
2818
|
+
strapi2.log.warn(`[bulk-updater] Failed scanning ${uid} for rule ${ruleId}:`, err);
|
|
2350
2819
|
}
|
|
2351
|
-
}
|
|
2352
|
-
|
|
2353
|
-
}
|
|
2354
|
-
}
|
|
2820
|
+
})
|
|
2821
|
+
);
|
|
2355
2822
|
return {
|
|
2356
2823
|
rule,
|
|
2357
2824
|
totalScanned,
|
|
@@ -2653,12 +3120,13 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2653
3120
|
throw new Error("CSV is empty or could not be parsed.");
|
|
2654
3121
|
}
|
|
2655
3122
|
const attrs = ct.attributes || {};
|
|
2656
|
-
const { mode: mode2, publish = true, user } = options;
|
|
3123
|
+
const { mode: mode2 = "auto", publish = true, user } = options;
|
|
2657
3124
|
const stats = {
|
|
2658
3125
|
total: parsedRows.length,
|
|
2659
3126
|
created: 0,
|
|
2660
3127
|
updated: 0,
|
|
2661
3128
|
skipped: 0,
|
|
3129
|
+
newWithoutDocId: 0,
|
|
2662
3130
|
errors: [],
|
|
2663
3131
|
diffs: []
|
|
2664
3132
|
};
|
|
@@ -2730,7 +3198,7 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2730
3198
|
stats.skipped++;
|
|
2731
3199
|
stats.errors.push({
|
|
2732
3200
|
row: rowNumber,
|
|
2733
|
-
error: "Missing documentId for Update mode."
|
|
3201
|
+
error: "Missing documentId for Update mode. (Tip: Use Smart Auto-Detect mode to auto-add entries without document IDs)."
|
|
2734
3202
|
});
|
|
2735
3203
|
continue;
|
|
2736
3204
|
}
|
|
@@ -2775,7 +3243,7 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2775
3243
|
status: "updated",
|
|
2776
3244
|
message: `Updated via CSV import (row ${rowNumber})`
|
|
2777
3245
|
});
|
|
2778
|
-
} else {
|
|
3246
|
+
} else if (mode2 === "create_or_override") {
|
|
2779
3247
|
if (docId) {
|
|
2780
3248
|
const existing = await strapi2.documents(uid).findOne({
|
|
2781
3249
|
documentId: docId
|
|
@@ -2825,6 +3293,9 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2825
3293
|
}
|
|
2826
3294
|
}
|
|
2827
3295
|
stats.created++;
|
|
3296
|
+
if (!docId) {
|
|
3297
|
+
stats.newWithoutDocId++;
|
|
3298
|
+
}
|
|
2828
3299
|
stats.diffs.push({
|
|
2829
3300
|
uid,
|
|
2830
3301
|
collectionName: ct.info?.displayName || uid,
|
|
@@ -2834,7 +3305,71 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2834
3305
|
oldValue: null,
|
|
2835
3306
|
newValue: payload,
|
|
2836
3307
|
status: "updated",
|
|
2837
|
-
message: `Created via CSV import (row ${rowNumber})`
|
|
3308
|
+
message: docId ? `Created via CSV import (row ${rowNumber})` : `Created as new entry (no documentId provided in CSV - row ${rowNumber})`
|
|
3309
|
+
});
|
|
3310
|
+
} else {
|
|
3311
|
+
if (docId) {
|
|
3312
|
+
const existing = await strapi2.documents(uid).findOne({
|
|
3313
|
+
documentId: docId
|
|
3314
|
+
});
|
|
3315
|
+
if (existing) {
|
|
3316
|
+
const mergedPayload = { ...payload };
|
|
3317
|
+
for (const key of Object.keys(payload)) {
|
|
3318
|
+
if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
|
|
3319
|
+
mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
|
|
3320
|
+
}
|
|
3321
|
+
}
|
|
3322
|
+
await strapi2.documents(uid).update({
|
|
3323
|
+
documentId: docId,
|
|
3324
|
+
data: mergedPayload
|
|
3325
|
+
});
|
|
3326
|
+
if (publish && ct.options?.draftAndPublish) {
|
|
3327
|
+
try {
|
|
3328
|
+
await strapi2.documents(uid).publish({ documentId: docId });
|
|
3329
|
+
} catch (pubErr) {
|
|
3330
|
+
strapi2.log.warn(`[bulk-updater] Auto-publish failed for ${docId}:`, pubErr);
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
stats.updated++;
|
|
3334
|
+
stats.diffs.push({
|
|
3335
|
+
uid,
|
|
3336
|
+
collectionName: ct.info?.displayName || uid,
|
|
3337
|
+
documentId: docId,
|
|
3338
|
+
label: existing.title || existing.name || `Entry #${docId}`,
|
|
3339
|
+
attribute: Object.keys(payload).join(", "),
|
|
3340
|
+
oldValue: existing,
|
|
3341
|
+
newValue: payload,
|
|
3342
|
+
status: "updated",
|
|
3343
|
+
message: `Updated existing entry via smart match (row ${rowNumber})`
|
|
3344
|
+
});
|
|
3345
|
+
continue;
|
|
3346
|
+
}
|
|
3347
|
+
}
|
|
3348
|
+
const created = await strapi2.documents(uid).create({
|
|
3349
|
+
data: payload
|
|
3350
|
+
});
|
|
3351
|
+
const newDocId = created?.documentId || docId;
|
|
3352
|
+
if (publish && ct.options?.draftAndPublish && newDocId) {
|
|
3353
|
+
try {
|
|
3354
|
+
await strapi2.documents(uid).publish({ documentId: newDocId });
|
|
3355
|
+
} catch (pubErr) {
|
|
3356
|
+
strapi2.log.warn(`[bulk-updater] Auto-publish failed for ${newDocId}:`, pubErr);
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
stats.created++;
|
|
3360
|
+
if (!docId) {
|
|
3361
|
+
stats.newWithoutDocId++;
|
|
3362
|
+
}
|
|
3363
|
+
stats.diffs.push({
|
|
3364
|
+
uid,
|
|
3365
|
+
collectionName: ct.info?.displayName || uid,
|
|
3366
|
+
documentId: newDocId,
|
|
3367
|
+
label: created?.title || created?.name || `New Entry #${newDocId}`,
|
|
3368
|
+
attribute: Object.keys(payload).join(", "),
|
|
3369
|
+
oldValue: null,
|
|
3370
|
+
newValue: payload,
|
|
3371
|
+
status: "updated",
|
|
3372
|
+
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})`
|
|
2838
3373
|
});
|
|
2839
3374
|
}
|
|
2840
3375
|
} catch (err) {
|
|
@@ -2855,18 +3390,32 @@ ${sampleRow.map(escapeCsvValue).join(",")}
|
|
|
2855
3390
|
if (stats.updated > 0 || stats.created > 0) {
|
|
2856
3391
|
try {
|
|
2857
3392
|
const auditService2 = strapi2.plugin("bulk-updater").service("audit");
|
|
2858
|
-
if (auditService2
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
3393
|
+
if (auditService2) {
|
|
3394
|
+
const isEnabled = typeof auditService2.isAuditEnabled === "function" ? auditService2.isAuditEnabled() : Boolean(auditService2.getAuditConfig?.().enabled ?? true);
|
|
3395
|
+
if (isEnabled) {
|
|
3396
|
+
const batchSummary = {
|
|
3397
|
+
batchId,
|
|
3398
|
+
attribute: "CSV_IMPORT",
|
|
3399
|
+
value: `Mode: ${mode2} (${stats.created} created, ${stats.updated} updated)`,
|
|
3400
|
+
updatedCount: stats.updated + stats.created,
|
|
3401
|
+
skippedCount: stats.skipped,
|
|
3402
|
+
totalTargets: stats.total
|
|
3403
|
+
};
|
|
3404
|
+
const userObj = {
|
|
3405
|
+
email: userEmail || "admin@strapi.io",
|
|
3406
|
+
firstname: userName || "Admin"
|
|
3407
|
+
};
|
|
3408
|
+
if (typeof auditService2.recordBatch === "function") {
|
|
3409
|
+
await auditService2.recordBatch(batchSummary, stats.diffs, userObj);
|
|
3410
|
+
} else if (typeof auditService2.logOperation === "function") {
|
|
3411
|
+
await auditService2.logOperation({
|
|
3412
|
+
...batchSummary,
|
|
3413
|
+
userEmail,
|
|
3414
|
+
userName,
|
|
3415
|
+
diffs: stats.diffs
|
|
3416
|
+
});
|
|
3417
|
+
}
|
|
3418
|
+
}
|
|
2870
3419
|
}
|
|
2871
3420
|
} catch (auditErr) {
|
|
2872
3421
|
strapi2.log.warn("[bulk-updater] Failed logging CSV import audit:", auditErr);
|