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