strapi-plugin-bulk-updater 1.2.0 → 1.2.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/README.md +8 -5
- package/dist/_chunks/{BulkUpdater-BDszahRM.mjs → BulkUpdater-BYtO2SHc.mjs} +55 -41
- package/dist/_chunks/BulkUpdater-BYtO2SHc.mjs.map +1 -0
- package/dist/_chunks/{BulkUpdater-DCrCPKSe.js → BulkUpdater-BdRJhVFh.js} +54 -40
- package/dist/_chunks/BulkUpdater-BdRJhVFh.js.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +205 -25
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +205 -25
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/_chunks/BulkUpdater-BDszahRM.mjs.map +0 -1
- package/dist/_chunks/BulkUpdater-DCrCPKSe.js.map +0 -1
package/dist/admin/index.mjs
CHANGED
|
@@ -9,7 +9,7 @@ const index = {
|
|
|
9
9
|
id: `${PLUGIN_ID}.plugin.name`,
|
|
10
10
|
defaultMessage: "Bulk Attribute Updater"
|
|
11
11
|
},
|
|
12
|
-
Component: () => import("../_chunks/BulkUpdater-
|
|
12
|
+
Component: () => import("../_chunks/BulkUpdater-BYtO2SHc.mjs"),
|
|
13
13
|
permissions: [],
|
|
14
14
|
position: 3
|
|
15
15
|
});
|
package/dist/server/index.js
CHANGED
|
@@ -182,6 +182,78 @@ const routes = {
|
|
|
182
182
|
]
|
|
183
183
|
}
|
|
184
184
|
};
|
|
185
|
+
const auditLog = {
|
|
186
|
+
collectionName: "bulk_updater_audit_logs",
|
|
187
|
+
info: {
|
|
188
|
+
singularName: "audit-log",
|
|
189
|
+
pluralName: "audit-logs",
|
|
190
|
+
displayName: "Bulk Updater Audit Log",
|
|
191
|
+
description: "Internal audit trail for bulk attribute and relation updates"
|
|
192
|
+
},
|
|
193
|
+
options: {
|
|
194
|
+
draftAndPublish: false
|
|
195
|
+
},
|
|
196
|
+
pluginOptions: {
|
|
197
|
+
"content-manager": {
|
|
198
|
+
visible: true
|
|
199
|
+
},
|
|
200
|
+
"content-type-builder": {
|
|
201
|
+
visible: false
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
attributes: {
|
|
205
|
+
batchId: {
|
|
206
|
+
type: "string",
|
|
207
|
+
required: true
|
|
208
|
+
},
|
|
209
|
+
attribute: {
|
|
210
|
+
type: "string",
|
|
211
|
+
required: true
|
|
212
|
+
},
|
|
213
|
+
value: {
|
|
214
|
+
type: "text"
|
|
215
|
+
},
|
|
216
|
+
userEmail: {
|
|
217
|
+
type: "string"
|
|
218
|
+
},
|
|
219
|
+
userName: {
|
|
220
|
+
type: "string"
|
|
221
|
+
},
|
|
222
|
+
updatedCount: {
|
|
223
|
+
type: "integer",
|
|
224
|
+
default: 0
|
|
225
|
+
},
|
|
226
|
+
skippedCount: {
|
|
227
|
+
type: "integer",
|
|
228
|
+
default: 0
|
|
229
|
+
},
|
|
230
|
+
totalTargets: {
|
|
231
|
+
type: "integer",
|
|
232
|
+
default: 0
|
|
233
|
+
},
|
|
234
|
+
diffs: {
|
|
235
|
+
type: "json"
|
|
236
|
+
},
|
|
237
|
+
isRolledBack: {
|
|
238
|
+
type: "boolean",
|
|
239
|
+
default: false
|
|
240
|
+
},
|
|
241
|
+
rolledBackAt: {
|
|
242
|
+
type: "datetime"
|
|
243
|
+
},
|
|
244
|
+
rolledBackBy: {
|
|
245
|
+
type: "string"
|
|
246
|
+
},
|
|
247
|
+
executedAt: {
|
|
248
|
+
type: "datetime"
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
};
|
|
252
|
+
const contentTypes = {
|
|
253
|
+
"audit-log": {
|
|
254
|
+
schema: auditLog
|
|
255
|
+
}
|
|
256
|
+
};
|
|
185
257
|
const bulkUpdaterController = ({ strapi: strapi2 }) => ({
|
|
186
258
|
async getModels(ctx) {
|
|
187
259
|
try {
|
|
@@ -1658,6 +1730,7 @@ const DEFAULT_FIELDS = {
|
|
|
1658
1730
|
executedAt: "executedAt"
|
|
1659
1731
|
};
|
|
1660
1732
|
const DEFAULT_CANDIDATE_TYPES = [
|
|
1733
|
+
"plugin::bulk-updater.audit-log",
|
|
1661
1734
|
"api::audit-log.audit-log",
|
|
1662
1735
|
"api::audit.audit",
|
|
1663
1736
|
"api::bulk-audit-log.bulk-audit-log"
|
|
@@ -1679,6 +1752,7 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1679
1752
|
* 1. Explicit consumer custom schema (full declarative control)
|
|
1680
1753
|
* 2. Zero-config convention auto-detection (api::audit-log.audit-log)
|
|
1681
1754
|
* 3. Strapi Enterprise native audit log bridge
|
|
1755
|
+
* 4. Zero-config plugin core store (works out of the box in 100% of Strapi installations)
|
|
1682
1756
|
*/
|
|
1683
1757
|
getAuditConfig() {
|
|
1684
1758
|
const rawConfig = strapi2.config.get("plugin::bulk-updater.audit");
|
|
@@ -1732,10 +1806,9 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1732
1806
|
};
|
|
1733
1807
|
}
|
|
1734
1808
|
return {
|
|
1735
|
-
enabled:
|
|
1736
|
-
mode: "
|
|
1737
|
-
isEnterpriseNativeAvailable: false
|
|
1738
|
-
error: "No audit collection configured or detected. To enable rollback, provide a collection or use Strapi Enterprise."
|
|
1809
|
+
enabled: true,
|
|
1810
|
+
mode: "store",
|
|
1811
|
+
isEnterpriseNativeAvailable: false
|
|
1739
1812
|
};
|
|
1740
1813
|
},
|
|
1741
1814
|
/**
|
|
@@ -1780,7 +1853,40 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1780
1853
|
},
|
|
1781
1854
|
user
|
|
1782
1855
|
);
|
|
1783
|
-
if (!config.enabled
|
|
1856
|
+
if (!config.enabled) {
|
|
1857
|
+
return null;
|
|
1858
|
+
}
|
|
1859
|
+
if (config.mode === "store") {
|
|
1860
|
+
try {
|
|
1861
|
+
const store = strapi2.store({ type: "plugin", name: "bulk-updater", key: "audit-history" });
|
|
1862
|
+
const stored = await store.get({}) || { history: [] };
|
|
1863
|
+
const history = Array.isArray(stored.history) ? stored.history : [];
|
|
1864
|
+
const newRecord = {
|
|
1865
|
+
id: batchSummary.batchId,
|
|
1866
|
+
documentId: batchSummary.batchId,
|
|
1867
|
+
batchId: batchSummary.batchId,
|
|
1868
|
+
attribute: batchSummary.attribute,
|
|
1869
|
+
value: batchSummary.value,
|
|
1870
|
+
userEmail: user?.email || "admin@strapi.io",
|
|
1871
|
+
userName: user?.firstname ? `${user.firstname} ${user.lastname || ""}`.trim() : user?.email || "Admin",
|
|
1872
|
+
updatedCount: batchSummary.updatedCount,
|
|
1873
|
+
skippedCount: batchSummary.skippedCount,
|
|
1874
|
+
totalTargets: batchSummary.totalTargets,
|
|
1875
|
+
diffs: results,
|
|
1876
|
+
isRolledBack: false,
|
|
1877
|
+
rolledBackAt: null,
|
|
1878
|
+
rolledBackBy: null,
|
|
1879
|
+
executedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1880
|
+
};
|
|
1881
|
+
const updatedHistory = [newRecord, ...history].slice(0, 200);
|
|
1882
|
+
await store.set({ value: { history: updatedHistory } });
|
|
1883
|
+
return newRecord;
|
|
1884
|
+
} catch (err) {
|
|
1885
|
+
strapi2.log.error(`[bulk-updater] Failed to record audit log in core store: ${err.message}`);
|
|
1886
|
+
return null;
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
if (!config.contentType || !config.fields) {
|
|
1784
1890
|
return null;
|
|
1785
1891
|
}
|
|
1786
1892
|
const f = config.fields;
|
|
@@ -1821,6 +1927,25 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1821
1927
|
history: []
|
|
1822
1928
|
};
|
|
1823
1929
|
}
|
|
1930
|
+
if (config.mode === "store") {
|
|
1931
|
+
try {
|
|
1932
|
+
const store = strapi2.store({ type: "plugin", name: "bulk-updater", key: "audit-history" });
|
|
1933
|
+
const stored = await store.get({}) || { history: [] };
|
|
1934
|
+
const history = Array.isArray(stored.history) ? stored.history : [];
|
|
1935
|
+
return {
|
|
1936
|
+
enabled: true,
|
|
1937
|
+
mode: "store",
|
|
1938
|
+
isEnterpriseNativeAvailable: config.isEnterpriseNativeAvailable,
|
|
1939
|
+
history
|
|
1940
|
+
};
|
|
1941
|
+
} catch (err) {
|
|
1942
|
+
return {
|
|
1943
|
+
enabled: false,
|
|
1944
|
+
message: `Failed to read audit history from core store: ${err.message}`,
|
|
1945
|
+
history: []
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1824
1949
|
if (!config.enabled || !config.contentType || !config.fields) {
|
|
1825
1950
|
return {
|
|
1826
1951
|
enabled: false,
|
|
@@ -1866,27 +1991,9 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1866
1991
|
}
|
|
1867
1992
|
},
|
|
1868
1993
|
/**
|
|
1869
|
-
*
|
|
1994
|
+
* Helper to revert diff items back to original values
|
|
1870
1995
|
*/
|
|
1871
|
-
async
|
|
1872
|
-
const config = this.getAuditConfig();
|
|
1873
|
-
if (!config.enabled || !config.contentType || !config.fields) {
|
|
1874
|
-
throw new Error("Audit trail is not configured or disabled.");
|
|
1875
|
-
}
|
|
1876
|
-
const f = config.fields;
|
|
1877
|
-
const records = await strapi2.documents(config.contentType).findMany({
|
|
1878
|
-
filters: {
|
|
1879
|
-
[f.batchId]: { $eq: batchId }
|
|
1880
|
-
}
|
|
1881
|
-
});
|
|
1882
|
-
const record = records?.[0];
|
|
1883
|
-
if (!record) {
|
|
1884
|
-
throw new Error(`Batch record with ID "${batchId}" was not found in audit trail.`);
|
|
1885
|
-
}
|
|
1886
|
-
if (record[f.isRolledBack]) {
|
|
1887
|
-
throw new Error(`Batch "${batchId}" has already been rolled back.`);
|
|
1888
|
-
}
|
|
1889
|
-
const diffs = typeof record[f.diffs] === "string" ? JSON.parse(record[f.diffs]) : record[f.diffs] || [];
|
|
1996
|
+
async revertDiffItems(diffs) {
|
|
1890
1997
|
let revertedCount = 0;
|
|
1891
1998
|
const rollbackDetails = [];
|
|
1892
1999
|
for (const item of diffs) {
|
|
@@ -1971,6 +2078,78 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1971
2078
|
}
|
|
1972
2079
|
}
|
|
1973
2080
|
}
|
|
2081
|
+
return { revertedCount, rollbackDetails };
|
|
2082
|
+
},
|
|
2083
|
+
/**
|
|
2084
|
+
* Performs deterministic rollback of a batch
|
|
2085
|
+
*/
|
|
2086
|
+
async rollbackBatch(batchId, currentUser) {
|
|
2087
|
+
const config = this.getAuditConfig();
|
|
2088
|
+
if (!config.enabled) {
|
|
2089
|
+
throw new Error("Audit trail is not configured or disabled.");
|
|
2090
|
+
}
|
|
2091
|
+
if (config.mode === "store") {
|
|
2092
|
+
const store = strapi2.store({ type: "plugin", name: "bulk-updater", key: "audit-history" });
|
|
2093
|
+
const stored = await store.get({}) || { history: [] };
|
|
2094
|
+
const history = Array.isArray(stored.history) ? stored.history : [];
|
|
2095
|
+
const recordIndex = history.findIndex((r) => r.batchId === batchId);
|
|
2096
|
+
if (recordIndex === -1) {
|
|
2097
|
+
throw new Error(`Batch record with ID "${batchId}" was not found in audit trail.`);
|
|
2098
|
+
}
|
|
2099
|
+
const record2 = history[recordIndex];
|
|
2100
|
+
if (record2.isRolledBack) {
|
|
2101
|
+
throw new Error(`Batch "${batchId}" has already been rolled back.`);
|
|
2102
|
+
}
|
|
2103
|
+
const diffs2 = Array.isArray(record2.diffs) ? record2.diffs : [];
|
|
2104
|
+
const { revertedCount: revertedCount2, rollbackDetails: rollbackDetails2 } = await this.revertDiffItems(diffs2);
|
|
2105
|
+
record2.isRolledBack = true;
|
|
2106
|
+
record2.rolledBackAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2107
|
+
record2.rolledBackBy = currentUser?.email || "Admin";
|
|
2108
|
+
history[recordIndex] = record2;
|
|
2109
|
+
await store.set({ value: { history } });
|
|
2110
|
+
try {
|
|
2111
|
+
const affectedUids = new Set(rollbackDetails2.map((d) => d.uid));
|
|
2112
|
+
const bulkService = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
2113
|
+
affectedUids.forEach((uid) => {
|
|
2114
|
+
if (uid && bulkService?.invalidateCache) {
|
|
2115
|
+
bulkService.invalidateCache(uid);
|
|
2116
|
+
}
|
|
2117
|
+
});
|
|
2118
|
+
} catch {
|
|
2119
|
+
}
|
|
2120
|
+
await this.recordToEnterpriseNative(
|
|
2121
|
+
"bulk-updater.rollback",
|
|
2122
|
+
{
|
|
2123
|
+
batchId,
|
|
2124
|
+
revertedCount: revertedCount2
|
|
2125
|
+
},
|
|
2126
|
+
currentUser
|
|
2127
|
+
);
|
|
2128
|
+
return {
|
|
2129
|
+
success: true,
|
|
2130
|
+
batchId,
|
|
2131
|
+
revertedCount: revertedCount2,
|
|
2132
|
+
details: rollbackDetails2
|
|
2133
|
+
};
|
|
2134
|
+
}
|
|
2135
|
+
if (!config.contentType || !config.fields) {
|
|
2136
|
+
throw new Error("Audit trail is not configured or disabled.");
|
|
2137
|
+
}
|
|
2138
|
+
const f = config.fields;
|
|
2139
|
+
const records = await strapi2.documents(config.contentType).findMany({
|
|
2140
|
+
filters: {
|
|
2141
|
+
[f.batchId]: { $eq: batchId }
|
|
2142
|
+
}
|
|
2143
|
+
});
|
|
2144
|
+
const record = records?.[0];
|
|
2145
|
+
if (!record) {
|
|
2146
|
+
throw new Error(`Batch record with ID "${batchId}" was not found in audit trail.`);
|
|
2147
|
+
}
|
|
2148
|
+
if (record[f.isRolledBack]) {
|
|
2149
|
+
throw new Error(`Batch "${batchId}" has already been rolled back.`);
|
|
2150
|
+
}
|
|
2151
|
+
const diffs = typeof record[f.diffs] === "string" ? JSON.parse(record[f.diffs]) : record[f.diffs] || [];
|
|
2152
|
+
const { revertedCount, rollbackDetails } = await this.revertDiffItems(diffs);
|
|
1974
2153
|
const updateData = {};
|
|
1975
2154
|
if (f.isRolledBack) updateData[f.isRolledBack] = true;
|
|
1976
2155
|
if (f.rolledBackAt) updateData[f.rolledBackAt] = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -32213,6 +32392,7 @@ const index = {
|
|
|
32213
32392
|
});
|
|
32214
32393
|
},
|
|
32215
32394
|
routes,
|
|
32395
|
+
contentTypes,
|
|
32216
32396
|
controllers: {
|
|
32217
32397
|
bulkUpdater: bulkUpdaterController,
|
|
32218
32398
|
audit: auditController,
|