strapi-plugin-bulk-updater 1.3.11 → 1.4.2
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-J21ZgJWx.mjs → BulkUpdater-D4v5fP-1.mjs} +603 -420
- package/dist/_chunks/BulkUpdater-D4v5fP-1.mjs.map +1 -0
- package/dist/_chunks/{BulkUpdater-DRHQBZZV.js → BulkUpdater-L2FdlJfh.js} +602 -419
- package/dist/_chunks/BulkUpdater-L2FdlJfh.js.map +1 -0
- package/dist/_chunks/{SettingsPage-BDObdCIY.mjs → SettingsPage-D2qYhqpF.mjs} +2 -2
- package/dist/_chunks/{SettingsPage-BDObdCIY.mjs.map → SettingsPage-D2qYhqpF.mjs.map} +1 -1
- package/dist/_chunks/{SettingsPage-M-HQ8Jtr.js → SettingsPage-Dv2DRplw.js} +2 -2
- package/dist/_chunks/{SettingsPage-M-HQ8Jtr.js.map → SettingsPage-Dv2DRplw.js.map} +1 -1
- package/dist/_chunks/{ValidationRulesTab-DuvL-K4F.js → ValidationRulesTab-BTm3QXxM.js} +11 -1
- package/dist/_chunks/ValidationRulesTab-BTm3QXxM.js.map +1 -0
- package/dist/_chunks/{ValidationRulesTab-VeRr-vxa.mjs → ValidationRulesTab-BtOCWK8E.mjs} +12 -2
- package/dist/_chunks/ValidationRulesTab-BtOCWK8E.mjs.map +1 -0
- package/dist/admin/index.js +2 -2
- package/dist/admin/index.mjs +2 -2
- package/dist/server/index.js +431 -135
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +431 -135
- package/dist/server/index.mjs.map +1 -1
- package/package.json +1 -1
- package/dist/_chunks/BulkUpdater-DRHQBZZV.js.map +0 -1
- package/dist/_chunks/BulkUpdater-J21ZgJWx.mjs.map +0 -1
- package/dist/_chunks/ValidationRulesTab-DuvL-K4F.js.map +0 -1
- package/dist/_chunks/ValidationRulesTab-VeRr-vxa.mjs.map +0 -1
package/dist/server/index.mjs
CHANGED
|
@@ -4157,6 +4157,99 @@ function parseCsv(csvText) {
|
|
|
4157
4157
|
}
|
|
4158
4158
|
return records;
|
|
4159
4159
|
}
|
|
4160
|
+
const LIFECYCLE_STATUS_WORDS = /* @__PURE__ */ new Set([
|
|
4161
|
+
"pending review",
|
|
4162
|
+
"pending_review",
|
|
4163
|
+
"pending approval",
|
|
4164
|
+
"pending_approval",
|
|
4165
|
+
"approved",
|
|
4166
|
+
"rejected",
|
|
4167
|
+
"draft",
|
|
4168
|
+
"published",
|
|
4169
|
+
"modified",
|
|
4170
|
+
"supported",
|
|
4171
|
+
"ignored"
|
|
4172
|
+
]);
|
|
4173
|
+
function isValidDocId(val) {
|
|
4174
|
+
if (!val || typeof val !== "string") return false;
|
|
4175
|
+
const trimmed = val.trim();
|
|
4176
|
+
if (!trimmed) return false;
|
|
4177
|
+
if (LIFECYCLE_STATUS_WORDS.has(trimmed.toLowerCase())) return false;
|
|
4178
|
+
if (/\s/.test(trimmed)) return false;
|
|
4179
|
+
return /^[a-zA-Z0-9_-]+$/.test(trimmed);
|
|
4180
|
+
}
|
|
4181
|
+
function extractDocId(row) {
|
|
4182
|
+
if (!row || typeof row !== "object") return "";
|
|
4183
|
+
const candidates = [
|
|
4184
|
+
row.documentId,
|
|
4185
|
+
row.document_id,
|
|
4186
|
+
row["Document ID"],
|
|
4187
|
+
row["Document Id"],
|
|
4188
|
+
row["document_id"],
|
|
4189
|
+
row.docId,
|
|
4190
|
+
row["docId"],
|
|
4191
|
+
row.id,
|
|
4192
|
+
row.ID,
|
|
4193
|
+
row.Id
|
|
4194
|
+
];
|
|
4195
|
+
for (const c of candidates) {
|
|
4196
|
+
if (c !== void 0 && c !== null) {
|
|
4197
|
+
const str = String(c).trim();
|
|
4198
|
+
if (isValidDocId(str)) {
|
|
4199
|
+
return str;
|
|
4200
|
+
}
|
|
4201
|
+
}
|
|
4202
|
+
}
|
|
4203
|
+
return "";
|
|
4204
|
+
}
|
|
4205
|
+
function extractLocale(row) {
|
|
4206
|
+
if (!row || typeof row !== "object") return void 0;
|
|
4207
|
+
const candidates = [row.locale, row.Locale, row["locale"], row["Locale"]];
|
|
4208
|
+
for (const c of candidates) {
|
|
4209
|
+
if (c !== void 0 && c !== null) {
|
|
4210
|
+
const str = String(c).trim();
|
|
4211
|
+
if (str && str.toLowerCase() !== "null" && str.toLowerCase() !== "undefined") {
|
|
4212
|
+
return str;
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
4215
|
+
}
|
|
4216
|
+
return void 0;
|
|
4217
|
+
}
|
|
4218
|
+
function extractDetailedErrorMessage(err) {
|
|
4219
|
+
if (!err) return "Error processing row";
|
|
4220
|
+
const detailsErrors = err.details?.errors || err.error?.details?.errors;
|
|
4221
|
+
if (Array.isArray(detailsErrors) && detailsErrors.length > 0) {
|
|
4222
|
+
const messages = detailsErrors.map((e) => {
|
|
4223
|
+
if (!e) return "";
|
|
4224
|
+
const path = Array.isArray(e.path) ? e.path.join(".") : e.path;
|
|
4225
|
+
const msg = e.message || (typeof e === "string" ? e : JSON.stringify(e));
|
|
4226
|
+
return path ? `${path}: ${msg}` : msg;
|
|
4227
|
+
}).filter(Boolean);
|
|
4228
|
+
if (messages.length > 0) return messages.join("; ");
|
|
4229
|
+
}
|
|
4230
|
+
if (Array.isArray(err.inner) && err.inner.length > 0) {
|
|
4231
|
+
const messages = err.inner.map((e) => {
|
|
4232
|
+
if (!e) return "";
|
|
4233
|
+
const path = e.path;
|
|
4234
|
+
const msg = e.message || (typeof e === "string" ? e : JSON.stringify(e));
|
|
4235
|
+
return path ? `${path}: ${msg}` : msg;
|
|
4236
|
+
}).filter(Boolean);
|
|
4237
|
+
if (messages.length > 0) return messages.join("; ");
|
|
4238
|
+
}
|
|
4239
|
+
if (Array.isArray(err.errors) && err.errors.length > 0) {
|
|
4240
|
+
return err.errors.join("; ");
|
|
4241
|
+
}
|
|
4242
|
+
if (typeof err.details === "string" && err.details.trim()) {
|
|
4243
|
+
return err.details;
|
|
4244
|
+
}
|
|
4245
|
+
if (err.details?.message) {
|
|
4246
|
+
return err.details.message;
|
|
4247
|
+
}
|
|
4248
|
+
if (err.message && err.message !== "[object Object]") {
|
|
4249
|
+
return err.message;
|
|
4250
|
+
}
|
|
4251
|
+
return "Error processing row";
|
|
4252
|
+
}
|
|
4160
4253
|
function discoverExportFields(ct, allComponents, maxDepth = 4) {
|
|
4161
4254
|
const fields = [{ key: "documentId", label: "documentId", type: "string" }];
|
|
4162
4255
|
const excluded = ["createdBy", "updatedBy", "localizations"];
|
|
@@ -4552,13 +4645,63 @@ const csvService = ({ strapi }) => ({
|
|
|
4552
4645
|
const batchId = `csv_${Date.now()}_${Math.random().toString(36).substring(2, 7)}`;
|
|
4553
4646
|
const userEmail = user?.email || "admin@strapi.io";
|
|
4554
4647
|
const userName = user ? `${user.firstname || ""} ${user.lastname || ""}`.trim() : "CSV Importer";
|
|
4648
|
+
const findExistingDocument = async (targetDocId, docLocale) => {
|
|
4649
|
+
if (!targetDocId) return null;
|
|
4650
|
+
try {
|
|
4651
|
+
const queryArgs = { documentId: targetDocId };
|
|
4652
|
+
if (docLocale) queryArgs.locale = docLocale;
|
|
4653
|
+
const found = await strapi.documents(uid).findOne(queryArgs);
|
|
4654
|
+
if (found) return found;
|
|
4655
|
+
} catch {
|
|
4656
|
+
}
|
|
4657
|
+
if (/^\d+$/.test(targetDocId)) {
|
|
4658
|
+
try {
|
|
4659
|
+
const queryArgs = {
|
|
4660
|
+
filters: { id: parseInt(targetDocId, 10) },
|
|
4661
|
+
limit: 1
|
|
4662
|
+
};
|
|
4663
|
+
if (docLocale) queryArgs.locale = docLocale;
|
|
4664
|
+
const list = await strapi.documents(uid).findMany(queryArgs);
|
|
4665
|
+
if (list && list.length > 0) return list[0];
|
|
4666
|
+
} catch {
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4669
|
+
return null;
|
|
4670
|
+
};
|
|
4671
|
+
const SYSTEM_FIELD_KEYS = /* @__PURE__ */ new Set([
|
|
4672
|
+
"documentid",
|
|
4673
|
+
"document_id",
|
|
4674
|
+
"docid",
|
|
4675
|
+
"id",
|
|
4676
|
+
"createdat",
|
|
4677
|
+
"updatedat",
|
|
4678
|
+
"publishedat",
|
|
4679
|
+
"createdby",
|
|
4680
|
+
"updatedby",
|
|
4681
|
+
"localizations",
|
|
4682
|
+
"locale",
|
|
4683
|
+
"status"
|
|
4684
|
+
]);
|
|
4555
4685
|
for (let i = 0; i < parsedRows.length; i++) {
|
|
4556
4686
|
const rowNumber = i + 2;
|
|
4557
4687
|
const row = parsedRows[i];
|
|
4558
|
-
|
|
4688
|
+
let docId = extractDocId(row);
|
|
4689
|
+
const rowLocale = extractLocale(row);
|
|
4690
|
+
if (ct.kind === "singleType" && !docId) {
|
|
4691
|
+
try {
|
|
4692
|
+
const findSingleArgs = {};
|
|
4693
|
+
if (rowLocale) findSingleArgs.locale = rowLocale;
|
|
4694
|
+
const singleDoc = await strapi.documents(uid).findFirst(findSingleArgs);
|
|
4695
|
+
if (singleDoc?.documentId) {
|
|
4696
|
+
docId = singleDoc.documentId;
|
|
4697
|
+
}
|
|
4698
|
+
} catch {
|
|
4699
|
+
}
|
|
4700
|
+
}
|
|
4559
4701
|
const payload = {};
|
|
4560
4702
|
for (const [key, rawVal] of Object.entries(row)) {
|
|
4561
|
-
|
|
4703
|
+
const normalizedKey = key.toLowerCase().replace(/[\s_-]/g, "");
|
|
4704
|
+
if (SYSTEM_FIELD_KEYS.has(normalizedKey)) continue;
|
|
4562
4705
|
if (key.includes(".")) {
|
|
4563
4706
|
const [parent, child] = key.split(".");
|
|
4564
4707
|
const parentDef = attrs[parent];
|
|
@@ -4571,32 +4714,54 @@ const csvService = ({ strapi }) => ({
|
|
|
4571
4714
|
if (rawVal === "" || rawVal === null || rawVal === void 0) {
|
|
4572
4715
|
val = null;
|
|
4573
4716
|
} else if (childDef.type === "integer" || childDef.type === "biginteger") {
|
|
4574
|
-
|
|
4717
|
+
const parsedInt = parseInt(rawVal, 10);
|
|
4718
|
+
val = isNaN(parsedInt) ? null : parsedInt;
|
|
4575
4719
|
} else if (childDef.type === "float" || childDef.type === "decimal") {
|
|
4576
|
-
|
|
4720
|
+
const parsedFloat = parseFloat(rawVal);
|
|
4721
|
+
val = isNaN(parsedFloat) ? null : parsedFloat;
|
|
4577
4722
|
} else if (childDef.type === "boolean") {
|
|
4578
4723
|
val = rawVal.toLowerCase() === "true" || rawVal === "1";
|
|
4724
|
+
} else if (childDef.type === "json") {
|
|
4725
|
+
try {
|
|
4726
|
+
val = JSON.parse(rawVal);
|
|
4727
|
+
} catch {
|
|
4728
|
+
val = rawVal;
|
|
4729
|
+
}
|
|
4579
4730
|
}
|
|
4580
4731
|
payload[parent][child] = val;
|
|
4581
4732
|
}
|
|
4582
4733
|
}
|
|
4583
4734
|
continue;
|
|
4584
4735
|
}
|
|
4585
|
-
|
|
4736
|
+
let attrDef = attrs[key];
|
|
4737
|
+
if (!attrDef) {
|
|
4738
|
+
const matchKey = Object.keys(attrs).find(
|
|
4739
|
+
(k) => k.toLowerCase() === key.toLowerCase() || k.toLowerCase() === key.toLowerCase().replace(/\s+/g, "")
|
|
4740
|
+
);
|
|
4741
|
+
if (matchKey) {
|
|
4742
|
+
attrDef = attrs[matchKey];
|
|
4743
|
+
}
|
|
4744
|
+
}
|
|
4586
4745
|
if (!attrDef) continue;
|
|
4587
4746
|
if (rawVal === "" || rawVal === null || rawVal === void 0) {
|
|
4588
|
-
|
|
4747
|
+
if (attrDef.type !== "dynamiczone" && !(attrDef.type === "component" && attrDef.repeatable)) {
|
|
4748
|
+
payload[key] = null;
|
|
4749
|
+
}
|
|
4589
4750
|
continue;
|
|
4590
4751
|
}
|
|
4591
4752
|
switch (attrDef.type) {
|
|
4592
4753
|
case "integer":
|
|
4593
|
-
case "biginteger":
|
|
4594
|
-
|
|
4754
|
+
case "biginteger": {
|
|
4755
|
+
const parsedInt = parseInt(rawVal, 10);
|
|
4756
|
+
payload[key] = isNaN(parsedInt) ? null : parsedInt;
|
|
4595
4757
|
break;
|
|
4758
|
+
}
|
|
4596
4759
|
case "float":
|
|
4597
|
-
case "decimal":
|
|
4598
|
-
|
|
4760
|
+
case "decimal": {
|
|
4761
|
+
const parsedFloat = parseFloat(rawVal);
|
|
4762
|
+
payload[key] = isNaN(parsedFloat) ? null : parsedFloat;
|
|
4599
4763
|
break;
|
|
4764
|
+
}
|
|
4600
4765
|
case "boolean":
|
|
4601
4766
|
payload[key] = rawVal.toLowerCase() === "true" || rawVal === "1";
|
|
4602
4767
|
break;
|
|
@@ -4607,6 +4772,69 @@ const csvService = ({ strapi }) => ({
|
|
|
4607
4772
|
payload[key] = rawVal;
|
|
4608
4773
|
}
|
|
4609
4774
|
break;
|
|
4775
|
+
case "dynamiczone": {
|
|
4776
|
+
try {
|
|
4777
|
+
const parsed = JSON.parse(rawVal);
|
|
4778
|
+
if (Array.isArray(parsed) && parsed.every((item) => item && typeof item === "object" && typeof item.__component === "string")) {
|
|
4779
|
+
payload[key] = parsed;
|
|
4780
|
+
}
|
|
4781
|
+
} catch {
|
|
4782
|
+
}
|
|
4783
|
+
break;
|
|
4784
|
+
}
|
|
4785
|
+
case "component": {
|
|
4786
|
+
if (attrDef.repeatable) {
|
|
4787
|
+
try {
|
|
4788
|
+
const parsed = JSON.parse(rawVal);
|
|
4789
|
+
if (Array.isArray(parsed) && parsed.every((item) => item && typeof item === "object")) {
|
|
4790
|
+
payload[key] = parsed;
|
|
4791
|
+
}
|
|
4792
|
+
} catch {
|
|
4793
|
+
}
|
|
4794
|
+
} else {
|
|
4795
|
+
try {
|
|
4796
|
+
const parsed = JSON.parse(rawVal);
|
|
4797
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
4798
|
+
payload[key] = parsed;
|
|
4799
|
+
}
|
|
4800
|
+
} catch {
|
|
4801
|
+
}
|
|
4802
|
+
}
|
|
4803
|
+
break;
|
|
4804
|
+
}
|
|
4805
|
+
case "relation": {
|
|
4806
|
+
try {
|
|
4807
|
+
const parsed = JSON.parse(rawVal);
|
|
4808
|
+
if (Array.isArray(parsed) || typeof parsed === "object" && parsed !== null) {
|
|
4809
|
+
payload[key] = parsed;
|
|
4810
|
+
break;
|
|
4811
|
+
}
|
|
4812
|
+
} catch {
|
|
4813
|
+
}
|
|
4814
|
+
if (isValidDocId(rawVal)) {
|
|
4815
|
+
payload[key] = rawVal;
|
|
4816
|
+
} else if (rawVal.includes(";") || rawVal.includes(",")) {
|
|
4817
|
+
const parts = rawVal.split(/[;,]/).map((s) => s.trim()).filter(Boolean);
|
|
4818
|
+
if (parts.length > 0 && parts.every((p) => isValidDocId(p))) {
|
|
4819
|
+
payload[key] = parts;
|
|
4820
|
+
}
|
|
4821
|
+
}
|
|
4822
|
+
break;
|
|
4823
|
+
}
|
|
4824
|
+
case "media": {
|
|
4825
|
+
if (/^\d+$/.test(rawVal.trim())) {
|
|
4826
|
+
payload[key] = parseInt(rawVal.trim(), 10);
|
|
4827
|
+
} else {
|
|
4828
|
+
try {
|
|
4829
|
+
const parsed = JSON.parse(rawVal);
|
|
4830
|
+
if (typeof parsed === "number" || typeof parsed === "object") {
|
|
4831
|
+
payload[key] = parsed;
|
|
4832
|
+
}
|
|
4833
|
+
} catch {
|
|
4834
|
+
}
|
|
4835
|
+
}
|
|
4836
|
+
break;
|
|
4837
|
+
}
|
|
4610
4838
|
default:
|
|
4611
4839
|
payload[key] = rawVal;
|
|
4612
4840
|
}
|
|
@@ -4617,114 +4845,146 @@ const csvService = ({ strapi }) => ({
|
|
|
4617
4845
|
stats.skipped++;
|
|
4618
4846
|
stats.errors.push({
|
|
4619
4847
|
row: rowNumber,
|
|
4620
|
-
error: "Missing documentId for Update mode. (Tip: Use Smart Auto-Detect mode to auto-add entries without document IDs)."
|
|
4848
|
+
error: "Missing or invalid documentId for Update mode. (Tip: Use Smart Auto-Detect mode to auto-add entries without document IDs)."
|
|
4621
4849
|
});
|
|
4622
4850
|
continue;
|
|
4623
4851
|
}
|
|
4624
|
-
const existing = await
|
|
4625
|
-
documentId: docId
|
|
4626
|
-
});
|
|
4852
|
+
const existing = await findExistingDocument(docId, rowLocale);
|
|
4627
4853
|
if (!existing) {
|
|
4628
4854
|
stats.skipped++;
|
|
4629
4855
|
stats.errors.push({
|
|
4630
4856
|
row: rowNumber,
|
|
4631
4857
|
documentId: docId,
|
|
4632
|
-
error: `Document with
|
|
4858
|
+
error: `Document with ID "${docId}" not found in database.`
|
|
4633
4859
|
});
|
|
4634
4860
|
continue;
|
|
4635
4861
|
}
|
|
4862
|
+
const resolvedDocId = existing.documentId || docId;
|
|
4636
4863
|
const mergedPayload = { ...payload };
|
|
4637
4864
|
for (const key of Object.keys(payload)) {
|
|
4638
4865
|
if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
|
|
4639
4866
|
mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
|
|
4640
4867
|
}
|
|
4641
4868
|
}
|
|
4642
|
-
|
|
4643
|
-
|
|
4869
|
+
if (Object.keys(mergedPayload).length === 0) {
|
|
4870
|
+
stats.skipped++;
|
|
4871
|
+
stats.diffs.push({
|
|
4872
|
+
uid,
|
|
4873
|
+
collectionName: ct.info?.displayName || uid,
|
|
4874
|
+
documentId: resolvedDocId,
|
|
4875
|
+
label: existing.title || existing.name || `Entry #${resolvedDocId}`,
|
|
4876
|
+
attribute: "",
|
|
4877
|
+
oldValue: existing,
|
|
4878
|
+
newValue: {},
|
|
4879
|
+
status: "skipped",
|
|
4880
|
+
message: `Skipped (no editable attributes in row ${rowNumber})`
|
|
4881
|
+
});
|
|
4882
|
+
continue;
|
|
4883
|
+
}
|
|
4884
|
+
const updateArgs = {
|
|
4885
|
+
documentId: resolvedDocId,
|
|
4644
4886
|
data: mergedPayload
|
|
4645
|
-
}
|
|
4887
|
+
};
|
|
4888
|
+
if (rowLocale) updateArgs.locale = rowLocale;
|
|
4889
|
+
await strapi.documents(uid).update(updateArgs);
|
|
4646
4890
|
if (publish && ct.options?.draftAndPublish) {
|
|
4647
4891
|
try {
|
|
4648
|
-
|
|
4892
|
+
const pubArgs = { documentId: resolvedDocId };
|
|
4893
|
+
if (rowLocale) pubArgs.locale = rowLocale;
|
|
4894
|
+
await strapi.documents(uid).publish(pubArgs);
|
|
4649
4895
|
} catch (pubErr) {
|
|
4650
|
-
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${
|
|
4896
|
+
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${resolvedDocId}:`, pubErr);
|
|
4651
4897
|
}
|
|
4652
4898
|
}
|
|
4653
4899
|
try {
|
|
4654
4900
|
await recordContentHistoryVersion(strapi, {
|
|
4655
4901
|
uid,
|
|
4656
|
-
documentId:
|
|
4902
|
+
documentId: resolvedDocId,
|
|
4657
4903
|
userId: user?.id,
|
|
4658
4904
|
statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
|
|
4659
4905
|
});
|
|
4660
4906
|
} catch {
|
|
4661
4907
|
}
|
|
4662
4908
|
stats.updated++;
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4909
|
+
for (const attrKey of Object.keys(payload)) {
|
|
4910
|
+
const oldVal = existing?.[attrKey] ?? null;
|
|
4911
|
+
const newVal = payload[attrKey];
|
|
4912
|
+
stats.diffs.push({
|
|
4913
|
+
uid,
|
|
4914
|
+
collectionName: ct.info?.displayName || uid,
|
|
4915
|
+
documentId: resolvedDocId,
|
|
4916
|
+
label: existing.title || existing.name || `Entry #${resolvedDocId}`,
|
|
4917
|
+
attribute: attrKey,
|
|
4918
|
+
oldValue: oldVal,
|
|
4919
|
+
newValue: newVal,
|
|
4920
|
+
status: "updated",
|
|
4921
|
+
message: `Updated "${attrKey}" via CSV import (row ${rowNumber})`
|
|
4922
|
+
});
|
|
4923
|
+
}
|
|
4674
4924
|
} else if (mode === "create_or_override") {
|
|
4925
|
+
let existing = null;
|
|
4675
4926
|
if (docId) {
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
|
|
4681
|
-
|
|
4682
|
-
|
|
4683
|
-
|
|
4684
|
-
}
|
|
4685
|
-
}
|
|
4686
|
-
await strapi.documents(uid).update({
|
|
4687
|
-
documentId: docId,
|
|
4688
|
-
data: mergedPayload
|
|
4689
|
-
});
|
|
4690
|
-
if (publish && ct.options?.draftAndPublish) {
|
|
4691
|
-
try {
|
|
4692
|
-
await strapi.documents(uid).publish({ documentId: docId });
|
|
4693
|
-
} catch (pubErr) {
|
|
4694
|
-
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${docId}:`, pubErr);
|
|
4695
|
-
}
|
|
4927
|
+
existing = await findExistingDocument(docId, rowLocale);
|
|
4928
|
+
}
|
|
4929
|
+
if (existing) {
|
|
4930
|
+
const resolvedDocId = existing.documentId || docId;
|
|
4931
|
+
const mergedPayload = { ...payload };
|
|
4932
|
+
for (const key of Object.keys(payload)) {
|
|
4933
|
+
if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
|
|
4934
|
+
mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
|
|
4696
4935
|
}
|
|
4936
|
+
}
|
|
4937
|
+
const updateArgs = {
|
|
4938
|
+
documentId: resolvedDocId,
|
|
4939
|
+
data: mergedPayload
|
|
4940
|
+
};
|
|
4941
|
+
if (rowLocale) updateArgs.locale = rowLocale;
|
|
4942
|
+
await strapi.documents(uid).update(updateArgs);
|
|
4943
|
+
if (publish && ct.options?.draftAndPublish) {
|
|
4697
4944
|
try {
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
|
|
4703
|
-
});
|
|
4704
|
-
} catch {
|
|
4945
|
+
const pubArgs = { documentId: resolvedDocId };
|
|
4946
|
+
if (rowLocale) pubArgs.locale = rowLocale;
|
|
4947
|
+
await strapi.documents(uid).publish(pubArgs);
|
|
4948
|
+
} catch (pubErr) {
|
|
4949
|
+
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${resolvedDocId}:`, pubErr);
|
|
4705
4950
|
}
|
|
4706
|
-
|
|
4951
|
+
}
|
|
4952
|
+
try {
|
|
4953
|
+
await recordContentHistoryVersion(strapi, {
|
|
4954
|
+
uid,
|
|
4955
|
+
documentId: resolvedDocId,
|
|
4956
|
+
userId: user?.id,
|
|
4957
|
+
statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
|
|
4958
|
+
});
|
|
4959
|
+
} catch {
|
|
4960
|
+
}
|
|
4961
|
+
stats.updated++;
|
|
4962
|
+
for (const attrKey of Object.keys(payload)) {
|
|
4963
|
+
const oldVal = existing?.[attrKey] ?? null;
|
|
4964
|
+
const newVal = payload[attrKey];
|
|
4707
4965
|
stats.diffs.push({
|
|
4708
4966
|
uid,
|
|
4709
4967
|
collectionName: ct.info?.displayName || uid,
|
|
4710
|
-
documentId:
|
|
4711
|
-
label: existing.title || existing.name || `Entry #${
|
|
4712
|
-
attribute:
|
|
4713
|
-
oldValue:
|
|
4714
|
-
newValue:
|
|
4968
|
+
documentId: resolvedDocId,
|
|
4969
|
+
label: existing.title || existing.name || `Entry #${resolvedDocId}`,
|
|
4970
|
+
attribute: attrKey,
|
|
4971
|
+
oldValue: oldVal,
|
|
4972
|
+
newValue: newVal,
|
|
4715
4973
|
status: "updated",
|
|
4716
|
-
message: `Overridden via CSV import (row ${rowNumber})`
|
|
4974
|
+
message: `Overridden "${attrKey}" via CSV import (row ${rowNumber})`
|
|
4717
4975
|
});
|
|
4718
|
-
continue;
|
|
4719
4976
|
}
|
|
4977
|
+
continue;
|
|
4720
4978
|
}
|
|
4721
|
-
const
|
|
4722
|
-
|
|
4723
|
-
|
|
4979
|
+
const createArgs = { data: payload };
|
|
4980
|
+
if (rowLocale) createArgs.locale = rowLocale;
|
|
4981
|
+
const created = await strapi.documents(uid).create(createArgs);
|
|
4724
4982
|
const newDocId = created?.documentId || docId;
|
|
4725
4983
|
if (publish && ct.options?.draftAndPublish && newDocId) {
|
|
4726
4984
|
try {
|
|
4727
|
-
|
|
4985
|
+
const pubArgs = { documentId: newDocId };
|
|
4986
|
+
if (rowLocale) pubArgs.locale = rowLocale;
|
|
4987
|
+
await strapi.documents(uid).publish(pubArgs);
|
|
4728
4988
|
} catch (pubErr) {
|
|
4729
4989
|
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${newDocId}:`, pubErr);
|
|
4730
4990
|
}
|
|
@@ -4742,71 +5002,83 @@ const csvService = ({ strapi }) => ({
|
|
|
4742
5002
|
if (!docId) {
|
|
4743
5003
|
stats.newWithoutDocId++;
|
|
4744
5004
|
}
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
4749
|
-
|
|
4750
|
-
|
|
4751
|
-
|
|
4752
|
-
|
|
4753
|
-
|
|
4754
|
-
|
|
4755
|
-
|
|
5005
|
+
for (const attrKey of Object.keys(payload)) {
|
|
5006
|
+
stats.diffs.push({
|
|
5007
|
+
uid,
|
|
5008
|
+
collectionName: ct.info?.displayName || uid,
|
|
5009
|
+
documentId: newDocId,
|
|
5010
|
+
label: created?.title || created?.name || `New Entry #${newDocId}`,
|
|
5011
|
+
attribute: attrKey,
|
|
5012
|
+
oldValue: null,
|
|
5013
|
+
newValue: payload[attrKey],
|
|
5014
|
+
status: "created",
|
|
5015
|
+
message: docId ? `Created "${attrKey}" via CSV import (row ${rowNumber})` : `Created "${attrKey}" as new entry (row ${rowNumber})`
|
|
5016
|
+
});
|
|
5017
|
+
}
|
|
4756
5018
|
} else {
|
|
5019
|
+
let existing = null;
|
|
4757
5020
|
if (docId) {
|
|
4758
|
-
|
|
4759
|
-
|
|
4760
|
-
|
|
4761
|
-
|
|
4762
|
-
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
}
|
|
4767
|
-
}
|
|
4768
|
-
await strapi.documents(uid).update({
|
|
4769
|
-
documentId: docId,
|
|
4770
|
-
data: mergedPayload
|
|
4771
|
-
});
|
|
4772
|
-
if (publish && ct.options?.draftAndPublish) {
|
|
4773
|
-
try {
|
|
4774
|
-
await strapi.documents(uid).publish({ documentId: docId });
|
|
4775
|
-
} catch (pubErr) {
|
|
4776
|
-
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${docId}:`, pubErr);
|
|
4777
|
-
}
|
|
5021
|
+
existing = await findExistingDocument(docId, rowLocale);
|
|
5022
|
+
}
|
|
5023
|
+
if (existing) {
|
|
5024
|
+
const resolvedDocId = existing.documentId || docId;
|
|
5025
|
+
const mergedPayload = { ...payload };
|
|
5026
|
+
for (const key of Object.keys(payload)) {
|
|
5027
|
+
if (attrs[key]?.type === "component" && typeof payload[key] === "object" && payload[key] !== null) {
|
|
5028
|
+
mergedPayload[key] = { ...existing[key] || {}, ...payload[key] };
|
|
4778
5029
|
}
|
|
5030
|
+
}
|
|
5031
|
+
const updateArgs = {
|
|
5032
|
+
documentId: resolvedDocId,
|
|
5033
|
+
data: mergedPayload
|
|
5034
|
+
};
|
|
5035
|
+
if (rowLocale) updateArgs.locale = rowLocale;
|
|
5036
|
+
await strapi.documents(uid).update(updateArgs);
|
|
5037
|
+
if (publish && ct.options?.draftAndPublish) {
|
|
4779
5038
|
try {
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
});
|
|
4786
|
-
} catch {
|
|
5039
|
+
const pubArgs = { documentId: resolvedDocId };
|
|
5040
|
+
if (rowLocale) pubArgs.locale = rowLocale;
|
|
5041
|
+
await strapi.documents(uid).publish(pubArgs);
|
|
5042
|
+
} catch (pubErr) {
|
|
5043
|
+
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${resolvedDocId}:`, pubErr);
|
|
4787
5044
|
}
|
|
4788
|
-
|
|
5045
|
+
}
|
|
5046
|
+
try {
|
|
5047
|
+
await recordContentHistoryVersion(strapi, {
|
|
5048
|
+
uid,
|
|
5049
|
+
documentId: resolvedDocId,
|
|
5050
|
+
userId: user?.id,
|
|
5051
|
+
statusOverride: publish && ct.options?.draftAndPublish ? "published" : void 0
|
|
5052
|
+
});
|
|
5053
|
+
} catch {
|
|
5054
|
+
}
|
|
5055
|
+
stats.updated++;
|
|
5056
|
+
for (const attrKey of Object.keys(payload)) {
|
|
5057
|
+
const oldVal = existing?.[attrKey] ?? null;
|
|
5058
|
+
const newVal = payload[attrKey];
|
|
4789
5059
|
stats.diffs.push({
|
|
4790
5060
|
uid,
|
|
4791
5061
|
collectionName: ct.info?.displayName || uid,
|
|
4792
|
-
documentId:
|
|
4793
|
-
label: existing.title || existing.name || `Entry #${
|
|
4794
|
-
attribute:
|
|
4795
|
-
oldValue:
|
|
4796
|
-
newValue:
|
|
5062
|
+
documentId: resolvedDocId,
|
|
5063
|
+
label: existing.title || existing.name || `Entry #${resolvedDocId}`,
|
|
5064
|
+
attribute: attrKey,
|
|
5065
|
+
oldValue: oldVal,
|
|
5066
|
+
newValue: newVal,
|
|
4797
5067
|
status: "updated",
|
|
4798
|
-
message: `Updated
|
|
5068
|
+
message: `Updated "${attrKey}" via CSV import (row ${rowNumber})`
|
|
4799
5069
|
});
|
|
4800
|
-
continue;
|
|
4801
5070
|
}
|
|
5071
|
+
continue;
|
|
4802
5072
|
}
|
|
4803
|
-
const
|
|
4804
|
-
|
|
4805
|
-
|
|
5073
|
+
const createArgs = { data: payload };
|
|
5074
|
+
if (rowLocale) createArgs.locale = rowLocale;
|
|
5075
|
+
const created = await strapi.documents(uid).create(createArgs);
|
|
4806
5076
|
const newDocId = created?.documentId || docId;
|
|
4807
5077
|
if (publish && ct.options?.draftAndPublish && newDocId) {
|
|
4808
5078
|
try {
|
|
4809
|
-
|
|
5079
|
+
const pubArgs = { documentId: newDocId };
|
|
5080
|
+
if (rowLocale) pubArgs.locale = rowLocale;
|
|
5081
|
+
await strapi.documents(uid).publish(pubArgs);
|
|
4810
5082
|
} catch (pubErr) {
|
|
4811
5083
|
strapi.log.warn(`[bulk-updater] Auto-publish failed for ${newDocId}:`, pubErr);
|
|
4812
5084
|
}
|
|
@@ -4824,23 +5096,25 @@ const csvService = ({ strapi }) => ({
|
|
|
4824
5096
|
if (!docId) {
|
|
4825
5097
|
stats.newWithoutDocId++;
|
|
4826
5098
|
}
|
|
4827
|
-
|
|
4828
|
-
|
|
4829
|
-
|
|
4830
|
-
|
|
4831
|
-
|
|
4832
|
-
|
|
4833
|
-
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
5099
|
+
for (const attrKey of Object.keys(payload)) {
|
|
5100
|
+
stats.diffs.push({
|
|
5101
|
+
uid,
|
|
5102
|
+
collectionName: ct.info?.displayName || uid,
|
|
5103
|
+
documentId: newDocId,
|
|
5104
|
+
label: created?.title || created?.name || `New Entry #${newDocId}`,
|
|
5105
|
+
attribute: attrKey,
|
|
5106
|
+
oldValue: null,
|
|
5107
|
+
newValue: payload[attrKey],
|
|
5108
|
+
status: "created",
|
|
5109
|
+
message: docId ? `Created "${attrKey}" as new entry (documentId "${docId}" not found in database - row ${rowNumber})` : `Created "${attrKey}" as new entry (no documentId provided in CSV - row ${rowNumber})`
|
|
5110
|
+
});
|
|
5111
|
+
}
|
|
4838
5112
|
}
|
|
4839
5113
|
} catch (err) {
|
|
4840
5114
|
stats.errors.push({
|
|
4841
5115
|
row: rowNumber,
|
|
4842
5116
|
documentId: docId || void 0,
|
|
4843
|
-
error: err
|
|
5117
|
+
error: extractDetailedErrorMessage(err)
|
|
4844
5118
|
});
|
|
4845
5119
|
}
|
|
4846
5120
|
}
|
|
@@ -4857,10 +5131,32 @@ const csvService = ({ strapi }) => ({
|
|
|
4857
5131
|
if (auditService2) {
|
|
4858
5132
|
const isEnabled = typeof auditService2.isAuditEnabled === "function" ? auditService2.isAuditEnabled() : Boolean(auditService2.getAuditConfig?.().enabled ?? true);
|
|
4859
5133
|
if (isEnabled) {
|
|
5134
|
+
const collectionDisplayName = ct.info?.displayName || uid;
|
|
5135
|
+
const updatedDiffs = stats.diffs.filter(
|
|
5136
|
+
(d) => d.status === "updated" || d.status === "created"
|
|
5137
|
+
);
|
|
5138
|
+
const uniqueAttributes = Array.from(
|
|
5139
|
+
new Set(updatedDiffs.map((d) => d.attribute))
|
|
5140
|
+
).filter(Boolean);
|
|
5141
|
+
const attrListStr = uniqueAttributes.length > 0 ? uniqueAttributes.join(", ") : "fields";
|
|
5142
|
+
const batchAttribute = `CSV [${collectionDisplayName}]: ${attrListStr}`;
|
|
5143
|
+
let batchValue = "";
|
|
5144
|
+
if (updatedDiffs.length === 1) {
|
|
5145
|
+
const d = updatedDiffs[0];
|
|
5146
|
+
const newStr = d.newValue !== null && d.newValue !== void 0 ? String(d.newValue) : "(empty)";
|
|
5147
|
+
batchValue = `"${d.label}": ${d.attribute} → "${newStr}"`;
|
|
5148
|
+
} else if (updatedDiffs.length > 1 && stats.updated === 1 && stats.created === 0) {
|
|
5149
|
+
const d = updatedDiffs[0];
|
|
5150
|
+
const changes = updatedDiffs.map((ud) => `${ud.attribute}: "${ud.newValue ?? ""}"`).join(", ");
|
|
5151
|
+
batchValue = `"${d.label}": ${changes}`;
|
|
5152
|
+
} else {
|
|
5153
|
+
const sampleVal = updatedDiffs[0] ? ` (e.g. ${updatedDiffs[0].attribute}: "${updatedDiffs[0].newValue ?? ""}")` : "";
|
|
5154
|
+
batchValue = `${stats.updated} updated, ${stats.created} created in ${collectionDisplayName}${sampleVal}`;
|
|
5155
|
+
}
|
|
4860
5156
|
const batchSummary = {
|
|
4861
5157
|
batchId,
|
|
4862
|
-
attribute:
|
|
4863
|
-
value:
|
|
5158
|
+
attribute: batchAttribute,
|
|
5159
|
+
value: batchValue,
|
|
4864
5160
|
updatedCount: stats.updated + stats.created,
|
|
4865
5161
|
skippedCount: stats.skipped,
|
|
4866
5162
|
totalTargets: stats.total
|