strapi-plugin-bulk-updater 1.2.8 → 1.2.10
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-DR8Z9z95.mjs → BulkUpdater-Cg9utqzm.mjs} +507 -40
- package/dist/_chunks/BulkUpdater-Cg9utqzm.mjs.map +1 -0
- package/dist/_chunks/{BulkUpdater-2TTEWMXI.js → BulkUpdater-EI4sKaLN.js} +506 -39
- package/dist/_chunks/BulkUpdater-EI4sKaLN.js.map +1 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +264 -0
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +264 -0
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/_chunks/BulkUpdater-2TTEWMXI.js.map +0 -1
- package/dist/_chunks/BulkUpdater-DR8Z9z95.mjs.map +0 -1
package/dist/admin/index.js
CHANGED
|
@@ -10,7 +10,7 @@ const index = {
|
|
|
10
10
|
id: `${PLUGIN_ID}.plugin.name`,
|
|
11
11
|
defaultMessage: "Bulk Attribute Updater"
|
|
12
12
|
},
|
|
13
|
-
Component: () => Promise.resolve().then(() => require("../_chunks/BulkUpdater-
|
|
13
|
+
Component: () => Promise.resolve().then(() => require("../_chunks/BulkUpdater-EI4sKaLN.js")),
|
|
14
14
|
permissions: [],
|
|
15
15
|
position: 3
|
|
16
16
|
});
|
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-Cg9utqzm.mjs"),
|
|
13
13
|
permissions: [],
|
|
14
14
|
position: 3
|
|
15
15
|
});
|
package/dist/server/index.js
CHANGED
|
@@ -59,6 +59,14 @@ const routes = {
|
|
|
59
59
|
policies: []
|
|
60
60
|
}
|
|
61
61
|
},
|
|
62
|
+
{
|
|
63
|
+
method: "POST",
|
|
64
|
+
path: "/preview",
|
|
65
|
+
handler: "bulkUpdater.previewBatch",
|
|
66
|
+
config: {
|
|
67
|
+
policies: []
|
|
68
|
+
}
|
|
69
|
+
},
|
|
62
70
|
{
|
|
63
71
|
method: "GET",
|
|
64
72
|
path: "/pending-batches",
|
|
@@ -353,6 +361,16 @@ const bulkUpdaterController = ({ strapi: strapi2 }) => ({
|
|
|
353
361
|
ctx.body = { success: false, error: err.message };
|
|
354
362
|
}
|
|
355
363
|
},
|
|
364
|
+
async previewBatch(ctx) {
|
|
365
|
+
try {
|
|
366
|
+
const service = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
367
|
+
const result = await service.previewBatch(ctx.request.body || {});
|
|
368
|
+
ctx.body = result;
|
|
369
|
+
} catch (err) {
|
|
370
|
+
ctx.status = 400;
|
|
371
|
+
ctx.body = { success: false, error: err.message };
|
|
372
|
+
}
|
|
373
|
+
},
|
|
356
374
|
async getPendingBatches(ctx) {
|
|
357
375
|
try {
|
|
358
376
|
const service = strapi2.plugin("bulk-updater").service("bulkUpdater");
|
|
@@ -1557,6 +1575,252 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
|
|
|
1557
1575
|
queryCache.set(cacheKey, { signature, data: result, timestamp: Date.now() });
|
|
1558
1576
|
return result;
|
|
1559
1577
|
},
|
|
1578
|
+
/**
|
|
1579
|
+
* Preview bulk update changes before execution:
|
|
1580
|
+
* Reads current values and computes proposed new values without mutating data.
|
|
1581
|
+
*/
|
|
1582
|
+
async previewBatch({
|
|
1583
|
+
attribute,
|
|
1584
|
+
value,
|
|
1585
|
+
selectedEntries = [],
|
|
1586
|
+
selectedCollections = [],
|
|
1587
|
+
isRegex = false,
|
|
1588
|
+
regexPattern = ""
|
|
1589
|
+
}) {
|
|
1590
|
+
if (!attribute) {
|
|
1591
|
+
throw new Error("Attribute name is required.");
|
|
1592
|
+
}
|
|
1593
|
+
const targetsMap = /* @__PURE__ */ new Map();
|
|
1594
|
+
for (const colUid of selectedCollections) {
|
|
1595
|
+
if (!strapi2.contentTypes[colUid]) continue;
|
|
1596
|
+
const entries = await strapi2.documents(colUid).findMany();
|
|
1597
|
+
for (const entry of entries || []) {
|
|
1598
|
+
const key = `${colUid}@@@${entry.documentId}`;
|
|
1599
|
+
targetsMap.set(key, { uid: colUid, documentId: entry.documentId });
|
|
1600
|
+
}
|
|
1601
|
+
}
|
|
1602
|
+
for (const item of selectedEntries) {
|
|
1603
|
+
if (!item.uid || !item.documentId) continue;
|
|
1604
|
+
const key = `${item.uid}@@@${item.documentId}`;
|
|
1605
|
+
targetsMap.set(key, { uid: item.uid, documentId: item.documentId });
|
|
1606
|
+
}
|
|
1607
|
+
if (targetsMap.size === 0) {
|
|
1608
|
+
throw new Error("No collections or entries were selected for preview.");
|
|
1609
|
+
}
|
|
1610
|
+
let regexObj = null;
|
|
1611
|
+
if (isRegex && regexPattern) {
|
|
1612
|
+
try {
|
|
1613
|
+
regexObj = new RegExp(regexPattern, "g");
|
|
1614
|
+
} catch (e) {
|
|
1615
|
+
throw new Error(`Invalid regular expression: ${e.message}`);
|
|
1616
|
+
}
|
|
1617
|
+
}
|
|
1618
|
+
const targets = Array.from(targetsMap.values());
|
|
1619
|
+
const diffs = [];
|
|
1620
|
+
let willUpdateCount = 0;
|
|
1621
|
+
let unchangedCount = 0;
|
|
1622
|
+
let skippedCount = 0;
|
|
1623
|
+
for (const target of targets) {
|
|
1624
|
+
const contentType = strapi2.contentTypes[target.uid];
|
|
1625
|
+
const displayName = contentType?.info?.displayName || target.uid;
|
|
1626
|
+
try {
|
|
1627
|
+
const isNested = attribute.includes(".");
|
|
1628
|
+
let isComponentNested = false;
|
|
1629
|
+
let parentKey = "";
|
|
1630
|
+
let childKey = "";
|
|
1631
|
+
if (isNested) {
|
|
1632
|
+
const parts = attribute.split(".");
|
|
1633
|
+
parentKey = parts[0];
|
|
1634
|
+
childKey = parts[1];
|
|
1635
|
+
const parentAttrDef = contentType?.attributes?.[parentKey];
|
|
1636
|
+
if (parentAttrDef?.type === "component" && !parentAttrDef.repeatable && parentAttrDef.component) {
|
|
1637
|
+
const compSchema = strapi2.components[parentAttrDef.component];
|
|
1638
|
+
if (compSchema?.attributes?.[childKey]) {
|
|
1639
|
+
isComponentNested = true;
|
|
1640
|
+
}
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
const attrDef = contentType?.attributes?.[attribute];
|
|
1644
|
+
const hasAttribute = Boolean(attrDef) || isComponentNested;
|
|
1645
|
+
if (!hasAttribute) {
|
|
1646
|
+
skippedCount++;
|
|
1647
|
+
diffs.push({
|
|
1648
|
+
uid: target.uid,
|
|
1649
|
+
collectionName: displayName,
|
|
1650
|
+
documentId: target.documentId,
|
|
1651
|
+
label: `Entry #${target.documentId}`,
|
|
1652
|
+
attribute,
|
|
1653
|
+
oldValue: null,
|
|
1654
|
+
newValue: null,
|
|
1655
|
+
oldDisplay: "—",
|
|
1656
|
+
newDisplay: "—",
|
|
1657
|
+
hasChange: false,
|
|
1658
|
+
status: "will_skip",
|
|
1659
|
+
message: `Attribute "${attribute}" does not exist in schema for ${displayName}`
|
|
1660
|
+
});
|
|
1661
|
+
continue;
|
|
1662
|
+
}
|
|
1663
|
+
let currentEntry;
|
|
1664
|
+
if (isComponentNested) {
|
|
1665
|
+
currentEntry = await strapi2.documents(target.uid).findOne({
|
|
1666
|
+
documentId: target.documentId,
|
|
1667
|
+
populate: [parentKey]
|
|
1668
|
+
});
|
|
1669
|
+
} else if (attrDef?.type === "relation") {
|
|
1670
|
+
currentEntry = await strapi2.documents(target.uid).findOne({
|
|
1671
|
+
documentId: target.documentId,
|
|
1672
|
+
populate: [attribute]
|
|
1673
|
+
});
|
|
1674
|
+
} else {
|
|
1675
|
+
currentEntry = await strapi2.documents(target.uid).findOne({
|
|
1676
|
+
documentId: target.documentId
|
|
1677
|
+
});
|
|
1678
|
+
}
|
|
1679
|
+
const entryLabel = currentEntry?.name || currentEntry?.title || currentEntry?.email || `Entry #${target.documentId}`;
|
|
1680
|
+
if (isComponentNested) {
|
|
1681
|
+
const existingParent = currentEntry?.[parentKey] || {};
|
|
1682
|
+
const currentVal2 = existingParent?.[childKey];
|
|
1683
|
+
let newValue2 = value;
|
|
1684
|
+
if (regexObj && typeof currentVal2 === "string") {
|
|
1685
|
+
newValue2 = currentVal2.replace(regexObj, value);
|
|
1686
|
+
}
|
|
1687
|
+
const hasChange2 = String(currentVal2 ?? "") !== String(newValue2 ?? "");
|
|
1688
|
+
if (hasChange2) {
|
|
1689
|
+
willUpdateCount++;
|
|
1690
|
+
} else {
|
|
1691
|
+
unchangedCount++;
|
|
1692
|
+
}
|
|
1693
|
+
diffs.push({
|
|
1694
|
+
uid: target.uid,
|
|
1695
|
+
collectionName: displayName,
|
|
1696
|
+
documentId: target.documentId,
|
|
1697
|
+
label: entryLabel,
|
|
1698
|
+
attribute,
|
|
1699
|
+
oldValue: currentVal2 ?? null,
|
|
1700
|
+
newValue: newValue2,
|
|
1701
|
+
oldDisplay: currentVal2 !== void 0 && currentVal2 !== null ? String(currentVal2) : "(empty)",
|
|
1702
|
+
newDisplay: String(newValue2 ?? ""),
|
|
1703
|
+
hasChange: hasChange2,
|
|
1704
|
+
status: hasChange2 ? "will_update" : "unchanged",
|
|
1705
|
+
message: hasChange2 ? "Value will be updated" : "Already has target value"
|
|
1706
|
+
});
|
|
1707
|
+
continue;
|
|
1708
|
+
}
|
|
1709
|
+
if (attrDef?.type === "relation") {
|
|
1710
|
+
const isMulti = attrDef.relation === "oneToMany" || attrDef.relation === "manyToMany";
|
|
1711
|
+
const currentRel = currentEntry?.[attribute];
|
|
1712
|
+
let oldDocIds = [];
|
|
1713
|
+
let oldDisplay = "";
|
|
1714
|
+
if (isMulti && Array.isArray(currentRel)) {
|
|
1715
|
+
oldDocIds = currentRel.map((r) => r.documentId).filter(Boolean);
|
|
1716
|
+
oldDisplay = currentRel.map((r) => r.name || r.title || r.email || r.documentId).join(", ") || "(empty)";
|
|
1717
|
+
} else if (currentRel) {
|
|
1718
|
+
oldDocIds = currentRel.documentId ? [currentRel.documentId] : [];
|
|
1719
|
+
oldDisplay = currentRel.name || currentRel.title || currentRel.email || currentRel.documentId || "(empty)";
|
|
1720
|
+
} else {
|
|
1721
|
+
oldDisplay = "(unlinked / none)";
|
|
1722
|
+
}
|
|
1723
|
+
let mode2 = "set";
|
|
1724
|
+
let targetDocIds = [];
|
|
1725
|
+
let newDisplay = "";
|
|
1726
|
+
if (value === "__clear__" || value === "") {
|
|
1727
|
+
mode2 = "clear";
|
|
1728
|
+
newDisplay = "(cleared / unlinked)";
|
|
1729
|
+
} else {
|
|
1730
|
+
try {
|
|
1731
|
+
const parsed = JSON.parse(value);
|
|
1732
|
+
if (parsed && typeof parsed === "object" && parsed.mode) {
|
|
1733
|
+
mode2 = parsed.mode;
|
|
1734
|
+
targetDocIds = Array.isArray(parsed.documentIds) ? parsed.documentIds : parsed.documentId ? [parsed.documentId] : [];
|
|
1735
|
+
newDisplay = parsed.label || targetDocIds.join(", ") || `[${mode2}]`;
|
|
1736
|
+
} else if (Array.isArray(parsed)) {
|
|
1737
|
+
targetDocIds = parsed;
|
|
1738
|
+
newDisplay = targetDocIds.join(", ");
|
|
1739
|
+
} else {
|
|
1740
|
+
targetDocIds = [value];
|
|
1741
|
+
newDisplay = value;
|
|
1742
|
+
}
|
|
1743
|
+
} catch {
|
|
1744
|
+
targetDocIds = [value];
|
|
1745
|
+
newDisplay = value;
|
|
1746
|
+
}
|
|
1747
|
+
}
|
|
1748
|
+
const hasChange2 = oldDisplay !== newDisplay;
|
|
1749
|
+
if (hasChange2) {
|
|
1750
|
+
willUpdateCount++;
|
|
1751
|
+
} else {
|
|
1752
|
+
unchangedCount++;
|
|
1753
|
+
}
|
|
1754
|
+
diffs.push({
|
|
1755
|
+
uid: target.uid,
|
|
1756
|
+
collectionName: displayName,
|
|
1757
|
+
documentId: target.documentId,
|
|
1758
|
+
label: entryLabel,
|
|
1759
|
+
attribute,
|
|
1760
|
+
oldValue: { isRelation: true, documentIds: oldDocIds, display: oldDisplay },
|
|
1761
|
+
newValue: { isRelation: true, mode: mode2, documentIds: targetDocIds, display: newDisplay },
|
|
1762
|
+
oldDisplay,
|
|
1763
|
+
newDisplay,
|
|
1764
|
+
hasChange: hasChange2,
|
|
1765
|
+
status: hasChange2 ? "will_update" : "unchanged",
|
|
1766
|
+
message: hasChange2 ? `Relation will be modified (${mode2})` : "Relation already set to this target"
|
|
1767
|
+
});
|
|
1768
|
+
continue;
|
|
1769
|
+
}
|
|
1770
|
+
let newValue = value;
|
|
1771
|
+
const currentVal = currentEntry?.[attribute];
|
|
1772
|
+
if (regexObj && typeof currentVal === "string") {
|
|
1773
|
+
newValue = currentVal.replace(regexObj, value);
|
|
1774
|
+
}
|
|
1775
|
+
const hasChange = String(currentVal ?? "") !== String(newValue ?? "");
|
|
1776
|
+
if (hasChange) {
|
|
1777
|
+
willUpdateCount++;
|
|
1778
|
+
} else {
|
|
1779
|
+
unchangedCount++;
|
|
1780
|
+
}
|
|
1781
|
+
diffs.push({
|
|
1782
|
+
uid: target.uid,
|
|
1783
|
+
collectionName: displayName,
|
|
1784
|
+
documentId: target.documentId,
|
|
1785
|
+
label: entryLabel,
|
|
1786
|
+
attribute,
|
|
1787
|
+
oldValue: currentVal ?? null,
|
|
1788
|
+
newValue,
|
|
1789
|
+
oldDisplay: currentVal !== void 0 && currentVal !== null ? String(currentVal) : "(empty)",
|
|
1790
|
+
newDisplay: String(newValue ?? ""),
|
|
1791
|
+
hasChange,
|
|
1792
|
+
status: hasChange ? "will_update" : "unchanged",
|
|
1793
|
+
message: hasChange ? "Value will be updated" : "Already matches target value"
|
|
1794
|
+
});
|
|
1795
|
+
} catch (err) {
|
|
1796
|
+
skippedCount++;
|
|
1797
|
+
diffs.push({
|
|
1798
|
+
uid: target.uid,
|
|
1799
|
+
collectionName: displayName,
|
|
1800
|
+
documentId: target.documentId,
|
|
1801
|
+
label: `Entry #${target.documentId}`,
|
|
1802
|
+
attribute,
|
|
1803
|
+
oldValue: null,
|
|
1804
|
+
newValue: null,
|
|
1805
|
+
oldDisplay: "—",
|
|
1806
|
+
newDisplay: "—",
|
|
1807
|
+
hasChange: false,
|
|
1808
|
+
status: "will_skip",
|
|
1809
|
+
message: `Error inspecting entry: ${err.message}`
|
|
1810
|
+
});
|
|
1811
|
+
}
|
|
1812
|
+
}
|
|
1813
|
+
return {
|
|
1814
|
+
success: true,
|
|
1815
|
+
summary: {
|
|
1816
|
+
total: targets.length,
|
|
1817
|
+
willUpdate: willUpdateCount,
|
|
1818
|
+
unchanged: unchangedCount,
|
|
1819
|
+
skipped: skippedCount
|
|
1820
|
+
},
|
|
1821
|
+
diffs
|
|
1822
|
+
};
|
|
1823
|
+
},
|
|
1560
1824
|
/**
|
|
1561
1825
|
* Batch Update API with Maker-Checker and Off-Peak Scheduling:
|
|
1562
1826
|
*/
|