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/server/index.mjs
CHANGED
|
@@ -169,6 +169,78 @@ const routes = {
|
|
|
169
169
|
]
|
|
170
170
|
}
|
|
171
171
|
};
|
|
172
|
+
const auditLog = {
|
|
173
|
+
collectionName: "bulk_updater_audit_logs",
|
|
174
|
+
info: {
|
|
175
|
+
singularName: "audit-log",
|
|
176
|
+
pluralName: "audit-logs",
|
|
177
|
+
displayName: "Bulk Updater Audit Log",
|
|
178
|
+
description: "Internal audit trail for bulk attribute and relation updates"
|
|
179
|
+
},
|
|
180
|
+
options: {
|
|
181
|
+
draftAndPublish: false
|
|
182
|
+
},
|
|
183
|
+
pluginOptions: {
|
|
184
|
+
"content-manager": {
|
|
185
|
+
visible: true
|
|
186
|
+
},
|
|
187
|
+
"content-type-builder": {
|
|
188
|
+
visible: false
|
|
189
|
+
}
|
|
190
|
+
},
|
|
191
|
+
attributes: {
|
|
192
|
+
batchId: {
|
|
193
|
+
type: "string",
|
|
194
|
+
required: true
|
|
195
|
+
},
|
|
196
|
+
attribute: {
|
|
197
|
+
type: "string",
|
|
198
|
+
required: true
|
|
199
|
+
},
|
|
200
|
+
value: {
|
|
201
|
+
type: "text"
|
|
202
|
+
},
|
|
203
|
+
userEmail: {
|
|
204
|
+
type: "string"
|
|
205
|
+
},
|
|
206
|
+
userName: {
|
|
207
|
+
type: "string"
|
|
208
|
+
},
|
|
209
|
+
updatedCount: {
|
|
210
|
+
type: "integer",
|
|
211
|
+
default: 0
|
|
212
|
+
},
|
|
213
|
+
skippedCount: {
|
|
214
|
+
type: "integer",
|
|
215
|
+
default: 0
|
|
216
|
+
},
|
|
217
|
+
totalTargets: {
|
|
218
|
+
type: "integer",
|
|
219
|
+
default: 0
|
|
220
|
+
},
|
|
221
|
+
diffs: {
|
|
222
|
+
type: "json"
|
|
223
|
+
},
|
|
224
|
+
isRolledBack: {
|
|
225
|
+
type: "boolean",
|
|
226
|
+
default: false
|
|
227
|
+
},
|
|
228
|
+
rolledBackAt: {
|
|
229
|
+
type: "datetime"
|
|
230
|
+
},
|
|
231
|
+
rolledBackBy: {
|
|
232
|
+
type: "string"
|
|
233
|
+
},
|
|
234
|
+
executedAt: {
|
|
235
|
+
type: "datetime"
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
const contentTypes = {
|
|
240
|
+
"audit-log": {
|
|
241
|
+
schema: auditLog
|
|
242
|
+
}
|
|
243
|
+
};
|
|
172
244
|
const bulkUpdaterController = ({ strapi: strapi2 }) => ({
|
|
173
245
|
async getModels(ctx) {
|
|
174
246
|
try {
|
|
@@ -1645,6 +1717,7 @@ const DEFAULT_FIELDS = {
|
|
|
1645
1717
|
executedAt: "executedAt"
|
|
1646
1718
|
};
|
|
1647
1719
|
const DEFAULT_CANDIDATE_TYPES = [
|
|
1720
|
+
"plugin::bulk-updater.audit-log",
|
|
1648
1721
|
"api::audit-log.audit-log",
|
|
1649
1722
|
"api::audit.audit",
|
|
1650
1723
|
"api::bulk-audit-log.bulk-audit-log"
|
|
@@ -1666,6 +1739,7 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1666
1739
|
* 1. Explicit consumer custom schema (full declarative control)
|
|
1667
1740
|
* 2. Zero-config convention auto-detection (api::audit-log.audit-log)
|
|
1668
1741
|
* 3. Strapi Enterprise native audit log bridge
|
|
1742
|
+
* 4. Zero-config plugin core store (works out of the box in 100% of Strapi installations)
|
|
1669
1743
|
*/
|
|
1670
1744
|
getAuditConfig() {
|
|
1671
1745
|
const rawConfig = strapi2.config.get("plugin::bulk-updater.audit");
|
|
@@ -1719,10 +1793,9 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1719
1793
|
};
|
|
1720
1794
|
}
|
|
1721
1795
|
return {
|
|
1722
|
-
enabled:
|
|
1723
|
-
mode: "
|
|
1724
|
-
isEnterpriseNativeAvailable: false
|
|
1725
|
-
error: "No audit collection configured or detected. To enable rollback, provide a collection or use Strapi Enterprise."
|
|
1796
|
+
enabled: true,
|
|
1797
|
+
mode: "store",
|
|
1798
|
+
isEnterpriseNativeAvailable: false
|
|
1726
1799
|
};
|
|
1727
1800
|
},
|
|
1728
1801
|
/**
|
|
@@ -1767,7 +1840,40 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1767
1840
|
},
|
|
1768
1841
|
user
|
|
1769
1842
|
);
|
|
1770
|
-
if (!config.enabled
|
|
1843
|
+
if (!config.enabled) {
|
|
1844
|
+
return null;
|
|
1845
|
+
}
|
|
1846
|
+
if (config.mode === "store") {
|
|
1847
|
+
try {
|
|
1848
|
+
const store = strapi2.store({ type: "plugin", name: "bulk-updater", key: "audit-history" });
|
|
1849
|
+
const stored = await store.get({}) || { history: [] };
|
|
1850
|
+
const history = Array.isArray(stored.history) ? stored.history : [];
|
|
1851
|
+
const newRecord = {
|
|
1852
|
+
id: batchSummary.batchId,
|
|
1853
|
+
documentId: batchSummary.batchId,
|
|
1854
|
+
batchId: batchSummary.batchId,
|
|
1855
|
+
attribute: batchSummary.attribute,
|
|
1856
|
+
value: batchSummary.value,
|
|
1857
|
+
userEmail: user?.email || "admin@strapi.io",
|
|
1858
|
+
userName: user?.firstname ? `${user.firstname} ${user.lastname || ""}`.trim() : user?.email || "Admin",
|
|
1859
|
+
updatedCount: batchSummary.updatedCount,
|
|
1860
|
+
skippedCount: batchSummary.skippedCount,
|
|
1861
|
+
totalTargets: batchSummary.totalTargets,
|
|
1862
|
+
diffs: results,
|
|
1863
|
+
isRolledBack: false,
|
|
1864
|
+
rolledBackAt: null,
|
|
1865
|
+
rolledBackBy: null,
|
|
1866
|
+
executedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1867
|
+
};
|
|
1868
|
+
const updatedHistory = [newRecord, ...history].slice(0, 200);
|
|
1869
|
+
await store.set({ value: { history: updatedHistory } });
|
|
1870
|
+
return newRecord;
|
|
1871
|
+
} catch (err) {
|
|
1872
|
+
strapi2.log.error(`[bulk-updater] Failed to record audit log in core store: ${err.message}`);
|
|
1873
|
+
return null;
|
|
1874
|
+
}
|
|
1875
|
+
}
|
|
1876
|
+
if (!config.contentType || !config.fields) {
|
|
1771
1877
|
return null;
|
|
1772
1878
|
}
|
|
1773
1879
|
const f = config.fields;
|
|
@@ -1808,6 +1914,25 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1808
1914
|
history: []
|
|
1809
1915
|
};
|
|
1810
1916
|
}
|
|
1917
|
+
if (config.mode === "store") {
|
|
1918
|
+
try {
|
|
1919
|
+
const store = strapi2.store({ type: "plugin", name: "bulk-updater", key: "audit-history" });
|
|
1920
|
+
const stored = await store.get({}) || { history: [] };
|
|
1921
|
+
const history = Array.isArray(stored.history) ? stored.history : [];
|
|
1922
|
+
return {
|
|
1923
|
+
enabled: true,
|
|
1924
|
+
mode: "store",
|
|
1925
|
+
isEnterpriseNativeAvailable: config.isEnterpriseNativeAvailable,
|
|
1926
|
+
history
|
|
1927
|
+
};
|
|
1928
|
+
} catch (err) {
|
|
1929
|
+
return {
|
|
1930
|
+
enabled: false,
|
|
1931
|
+
message: `Failed to read audit history from core store: ${err.message}`,
|
|
1932
|
+
history: []
|
|
1933
|
+
};
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1811
1936
|
if (!config.enabled || !config.contentType || !config.fields) {
|
|
1812
1937
|
return {
|
|
1813
1938
|
enabled: false,
|
|
@@ -1853,27 +1978,9 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1853
1978
|
}
|
|
1854
1979
|
},
|
|
1855
1980
|
/**
|
|
1856
|
-
*
|
|
1981
|
+
* Helper to revert diff items back to original values
|
|
1857
1982
|
*/
|
|
1858
|
-
async
|
|
1859
|
-
const config = this.getAuditConfig();
|
|
1860
|
-
if (!config.enabled || !config.contentType || !config.fields) {
|
|
1861
|
-
throw new Error("Audit trail is not configured or disabled.");
|
|
1862
|
-
}
|
|
1863
|
-
const f = config.fields;
|
|
1864
|
-
const records = await strapi2.documents(config.contentType).findMany({
|
|
1865
|
-
filters: {
|
|
1866
|
-
[f.batchId]: { $eq: batchId }
|
|
1867
|
-
}
|
|
1868
|
-
});
|
|
1869
|
-
const record = records?.[0];
|
|
1870
|
-
if (!record) {
|
|
1871
|
-
throw new Error(`Batch record with ID "${batchId}" was not found in audit trail.`);
|
|
1872
|
-
}
|
|
1873
|
-
if (record[f.isRolledBack]) {
|
|
1874
|
-
throw new Error(`Batch "${batchId}" has already been rolled back.`);
|
|
1875
|
-
}
|
|
1876
|
-
const diffs = typeof record[f.diffs] === "string" ? JSON.parse(record[f.diffs]) : record[f.diffs] || [];
|
|
1983
|
+
async revertDiffItems(diffs) {
|
|
1877
1984
|
let revertedCount = 0;
|
|
1878
1985
|
const rollbackDetails = [];
|
|
1879
1986
|
for (const item of diffs) {
|
|
@@ -1958,6 +2065,78 @@ const auditService = ({ strapi: strapi2 }) => ({
|
|
|
1958
2065
|
}
|
|
1959
2066
|
}
|
|
1960
2067
|
}
|
|
2068
|
+
return { revertedCount, rollbackDetails };
|
|
2069
|
+
},
|
|
2070
|
+
/**
|
|
2071
|
+
* Performs deterministic rollback of a batch
|
|
2072
|
+
*/
|
|
2073
|
+
async rollbackBatch(batchId, currentUser) {
|
|
2074
|
+
const config = this.getAuditConfig();
|
|
2075
|
+
if (!config.enabled) {
|
|
2076
|
+
throw new Error("Audit trail is not configured or disabled.");
|
|
2077
|
+
}
|
|
2078
|
+
if (config.mode === "store") {
|
|
2079
|
+
const store = strapi2.store({ type: "plugin", name: "bulk-updater", key: "audit-history" });
|
|
2080
|
+
const stored = await store.get({}) || { history: [] };
|
|
2081
|
+
const history = Array.isArray(stored.history) ? stored.history : [];
|
|
2082
|
+
const recordIndex = history.findIndex((r) => r.batchId === batchId);
|
|
2083
|
+
if (recordIndex === -1) {
|
|
2084
|
+
throw new Error(`Batch record with ID "${batchId}" was not found in audit trail.`);
|
|
2085
|
+
}
|
|
2086
|
+
const record2 = history[recordIndex];
|
|
2087
|
+
if (record2.isRolledBack) {
|
|
2088
|
+
throw new Error(`Batch "${batchId}" has already been rolled back.`);
|
|
2089
|
+
}
|
|
2090
|
+
const diffs2 = Array.isArray(record2.diffs) ? record2.diffs : [];
|
|
2091
|
+
const { revertedCount: revertedCount2, rollbackDetails: rollbackDetails2 } = await this.revertDiffItems(diffs2);
|
|
2092
|
+
record2.isRolledBack = true;
|
|
2093
|
+
record2.rolledBackAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
2094
|
+
record2.rolledBackBy = currentUser?.email || "Admin";
|
|
2095
|
+
history[recordIndex] = record2;
|
|
2096
|
+
await store.set({ value: { history } });
|
|
2097
|
+
try {
|
|
2098
|
+
const affectedUids = new Set(rollbackDetails2.map((d) => d.uid));
|
|
2099
|
+
const bulkService = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
2100
|
+
affectedUids.forEach((uid) => {
|
|
2101
|
+
if (uid && bulkService?.invalidateCache) {
|
|
2102
|
+
bulkService.invalidateCache(uid);
|
|
2103
|
+
}
|
|
2104
|
+
});
|
|
2105
|
+
} catch {
|
|
2106
|
+
}
|
|
2107
|
+
await this.recordToEnterpriseNative(
|
|
2108
|
+
"bulk-updater.rollback",
|
|
2109
|
+
{
|
|
2110
|
+
batchId,
|
|
2111
|
+
revertedCount: revertedCount2
|
|
2112
|
+
},
|
|
2113
|
+
currentUser
|
|
2114
|
+
);
|
|
2115
|
+
return {
|
|
2116
|
+
success: true,
|
|
2117
|
+
batchId,
|
|
2118
|
+
revertedCount: revertedCount2,
|
|
2119
|
+
details: rollbackDetails2
|
|
2120
|
+
};
|
|
2121
|
+
}
|
|
2122
|
+
if (!config.contentType || !config.fields) {
|
|
2123
|
+
throw new Error("Audit trail is not configured or disabled.");
|
|
2124
|
+
}
|
|
2125
|
+
const f = config.fields;
|
|
2126
|
+
const records = await strapi2.documents(config.contentType).findMany({
|
|
2127
|
+
filters: {
|
|
2128
|
+
[f.batchId]: { $eq: batchId }
|
|
2129
|
+
}
|
|
2130
|
+
});
|
|
2131
|
+
const record = records?.[0];
|
|
2132
|
+
if (!record) {
|
|
2133
|
+
throw new Error(`Batch record with ID "${batchId}" was not found in audit trail.`);
|
|
2134
|
+
}
|
|
2135
|
+
if (record[f.isRolledBack]) {
|
|
2136
|
+
throw new Error(`Batch "${batchId}" has already been rolled back.`);
|
|
2137
|
+
}
|
|
2138
|
+
const diffs = typeof record[f.diffs] === "string" ? JSON.parse(record[f.diffs]) : record[f.diffs] || [];
|
|
2139
|
+
const { revertedCount, rollbackDetails } = await this.revertDiffItems(diffs);
|
|
1961
2140
|
const updateData = {};
|
|
1962
2141
|
if (f.isRolledBack) updateData[f.isRolledBack] = true;
|
|
1963
2142
|
if (f.rolledBackAt) updateData[f.rolledBackAt] = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -32200,6 +32379,7 @@ const index = {
|
|
|
32200
32379
|
});
|
|
32201
32380
|
},
|
|
32202
32381
|
routes,
|
|
32382
|
+
contentTypes,
|
|
32203
32383
|
controllers: {
|
|
32204
32384
|
bulkUpdater: bulkUpdaterController,
|
|
32205
32385
|
audit: auditController,
|