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.
@@ -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-BDszahRM.mjs"),
12
+ Component: () => import("../_chunks/BulkUpdater-BYtO2SHc.mjs"),
13
13
  permissions: [],
14
14
  position: 3
15
15
  });
@@ -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: false,
1736
- mode: "disabled",
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 || !config.contentType || !config.fields) {
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
- * Performs deterministic rollback of a batch
1994
+ * Helper to revert diff items back to original values
1870
1995
  */
1871
- async rollbackBatch(batchId, currentUser) {
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,