appwrite-utils-cli 0.0.286 → 0.9.0

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.
Files changed (109) hide show
  1. package/README.md +122 -96
  2. package/dist/collections/attributes.d.ts +4 -0
  3. package/dist/collections/attributes.js +224 -0
  4. package/dist/collections/indexes.d.ts +4 -0
  5. package/dist/collections/indexes.js +27 -0
  6. package/dist/collections/methods.d.ts +16 -0
  7. package/dist/collections/methods.js +216 -0
  8. package/dist/databases/methods.d.ts +6 -0
  9. package/dist/databases/methods.js +33 -0
  10. package/dist/interactiveCLI.d.ts +19 -0
  11. package/dist/interactiveCLI.js +555 -0
  12. package/dist/main.js +227 -62
  13. package/dist/migrations/afterImportActions.js +37 -40
  14. package/dist/migrations/appwriteToX.d.ts +26 -25
  15. package/dist/migrations/appwriteToX.js +42 -6
  16. package/dist/migrations/attributes.js +21 -20
  17. package/dist/migrations/backup.d.ts +93 -87
  18. package/dist/migrations/collections.d.ts +6 -0
  19. package/dist/migrations/collections.js +149 -20
  20. package/dist/migrations/converters.d.ts +2 -18
  21. package/dist/migrations/converters.js +13 -2
  22. package/dist/migrations/dataLoader.d.ts +276 -161
  23. package/dist/migrations/dataLoader.js +535 -292
  24. package/dist/migrations/databases.js +8 -2
  25. package/dist/migrations/helper.d.ts +3 -0
  26. package/dist/migrations/helper.js +21 -0
  27. package/dist/migrations/importController.d.ts +5 -2
  28. package/dist/migrations/importController.js +125 -88
  29. package/dist/migrations/importDataActions.d.ts +9 -1
  30. package/dist/migrations/importDataActions.js +15 -3
  31. package/dist/migrations/indexes.js +3 -2
  32. package/dist/migrations/logging.js +20 -8
  33. package/dist/migrations/migrationHelper.d.ts +9 -4
  34. package/dist/migrations/migrationHelper.js +6 -5
  35. package/dist/migrations/openapi.d.ts +1 -1
  36. package/dist/migrations/openapi.js +33 -18
  37. package/dist/migrations/queue.js +3 -2
  38. package/dist/migrations/relationships.d.ts +2 -2
  39. package/dist/migrations/schemaStrings.js +53 -41
  40. package/dist/migrations/setupDatabase.d.ts +2 -4
  41. package/dist/migrations/setupDatabase.js +24 -105
  42. package/dist/migrations/storage.d.ts +3 -1
  43. package/dist/migrations/storage.js +110 -16
  44. package/dist/migrations/transfer.d.ts +30 -0
  45. package/dist/migrations/transfer.js +337 -0
  46. package/dist/migrations/users.d.ts +2 -1
  47. package/dist/migrations/users.js +78 -43
  48. package/dist/schemas/authUser.d.ts +2 -2
  49. package/dist/storage/methods.d.ts +15 -0
  50. package/dist/storage/methods.js +207 -0
  51. package/dist/storage/schemas.d.ts +687 -0
  52. package/dist/storage/schemas.js +175 -0
  53. package/dist/utils/getClientFromConfig.d.ts +4 -0
  54. package/dist/utils/getClientFromConfig.js +16 -0
  55. package/dist/utils/helperFunctions.d.ts +11 -1
  56. package/dist/utils/helperFunctions.js +38 -0
  57. package/dist/utils/retryFailedPromises.d.ts +2 -0
  58. package/dist/utils/retryFailedPromises.js +21 -0
  59. package/dist/utils/schemaStrings.d.ts +13 -0
  60. package/dist/utils/schemaStrings.js +403 -0
  61. package/dist/utils/setupFiles.js +110 -61
  62. package/dist/utilsController.d.ts +40 -22
  63. package/dist/utilsController.js +164 -84
  64. package/package.json +13 -15
  65. package/src/collections/attributes.ts +483 -0
  66. package/src/collections/indexes.ts +53 -0
  67. package/src/collections/methods.ts +331 -0
  68. package/src/databases/methods.ts +47 -0
  69. package/src/init.ts +64 -64
  70. package/src/interactiveCLI.ts +767 -0
  71. package/src/main.ts +292 -83
  72. package/src/migrations/afterImportActions.ts +553 -490
  73. package/src/migrations/appwriteToX.ts +237 -174
  74. package/src/migrations/attributes.ts +483 -422
  75. package/src/migrations/backup.ts +205 -205
  76. package/src/migrations/collections.ts +545 -300
  77. package/src/migrations/converters.ts +161 -150
  78. package/src/migrations/dataLoader.ts +1615 -1304
  79. package/src/migrations/databases.ts +44 -25
  80. package/src/migrations/dbHelpers.ts +92 -92
  81. package/src/migrations/helper.ts +40 -0
  82. package/src/migrations/importController.ts +448 -384
  83. package/src/migrations/importDataActions.ts +315 -307
  84. package/src/migrations/indexes.ts +40 -37
  85. package/src/migrations/logging.ts +29 -16
  86. package/src/migrations/migrationHelper.ts +207 -201
  87. package/src/migrations/openapi.ts +83 -70
  88. package/src/migrations/queue.ts +118 -119
  89. package/src/migrations/relationships.ts +324 -324
  90. package/src/migrations/schemaStrings.ts +472 -460
  91. package/src/migrations/setupDatabase.ts +118 -219
  92. package/src/migrations/storage.ts +538 -358
  93. package/src/migrations/transfer.ts +608 -0
  94. package/src/migrations/users.ts +362 -285
  95. package/src/migrations/validationRules.ts +63 -63
  96. package/src/schemas/authUser.ts +23 -23
  97. package/src/setup.ts +8 -8
  98. package/src/storage/methods.ts +371 -0
  99. package/src/storage/schemas.ts +205 -0
  100. package/src/types.ts +9 -9
  101. package/src/utils/getClientFromConfig.ts +17 -0
  102. package/src/utils/helperFunctions.ts +181 -127
  103. package/src/utils/index.ts +2 -2
  104. package/src/utils/loadConfigs.ts +59 -59
  105. package/src/utils/retryFailedPromises.ts +27 -0
  106. package/src/utils/schemaStrings.ts +473 -0
  107. package/src/utils/setupFiles.ts +228 -182
  108. package/src/utilsController.ts +325 -194
  109. package/tsconfig.json +37 -37
@@ -45,6 +45,7 @@ export class DataLoader {
45
45
  // Maps to hold email and phone to user ID mappings for unique-ness in User Accounts
46
46
  emailToUserIdMap = new Map();
47
47
  phoneToUserIdMap = new Map();
48
+ userIdSet = new Set();
48
49
  userExistsMap = new Map();
49
50
  shouldWriteFile = false;
50
51
  // Constructor to initialize the DataLoader with necessary configurations
@@ -65,6 +66,7 @@ export class DataLoader {
65
66
  * It iterates through the target object's keys and updates the source object if:
66
67
  * - The source object has the key.
67
68
  * - The target object's value for that key is not null, undefined, or an empty string.
69
+ * - If the target object has an array value, it concatenates the values and removes duplicates.
68
70
  *
69
71
  * @param source - The source object to be updated.
70
72
  * @param target - The target object with values to update the source object.
@@ -73,29 +75,74 @@ export class DataLoader {
73
75
  mergeObjects(source, update) {
74
76
  // Create a new object to hold the merged result
75
77
  const result = { ...source };
76
- Object.keys(update).forEach((key) => {
77
- const sourceValue = source[key];
78
- const updateValue = update[key];
79
- // If the update value is an array, concatenate and remove duplicates
80
- if (Array.isArray(updateValue)) {
81
- const sourceArray = Array.isArray(sourceValue) ? sourceValue : [];
82
- result[key] = [...new Set([...sourceArray, ...updateValue])];
83
- }
84
- // If the update value is an object, recursively merge
85
- else if (updateValue !== null &&
86
- typeof updateValue === "object" &&
87
- !(updateValue instanceof Date)) {
88
- result[key] = this.mergeObjects(sourceValue, updateValue);
89
- }
90
- // If the update value is not nullish, overwrite the source value
91
- else if (updateValue !== null && updateValue !== undefined) {
92
- result[key] = updateValue;
93
- }
94
- // If the update value is nullish, keep the original value unless it doesn't exist
95
- else if (sourceValue === undefined) {
96
- result[key] = updateValue;
78
+ // Loop through the keys of the object we care about
79
+ for (const [key, value] of Object.entries(source)) {
80
+ // Check if the key exists in the target object
81
+ if (!Object.hasOwn(update, key)) {
82
+ // If the key doesn't exist, we can just skip it like bad cheese
83
+ continue;
97
84
  }
98
- });
85
+ if (update[key] === value) {
86
+ continue;
87
+ }
88
+ // If the value ain't here, we can just do whatever man
89
+ if (value === undefined || value === null || value === "") {
90
+ // If the update key is defined
91
+ if (update[key] !== undefined &&
92
+ update[key] !== null &&
93
+ update[key] !== "") {
94
+ // might as well use it eh?
95
+ result[key] = update[key];
96
+ }
97
+ // ELSE if the value is an array, because it would then not be === to those things above
98
+ }
99
+ else if (Array.isArray(value)) {
100
+ // Get the update value
101
+ const updateValue = update[key];
102
+ // If the update value is an array, concatenate and remove duplicates
103
+ // and poopy data
104
+ if (Array.isArray(updateValue)) {
105
+ result[key] = [...new Set([...value, ...updateValue])].filter((item) => item !== null && item !== undefined && item !== "");
106
+ }
107
+ else {
108
+ // If the update value is not an array, just use it
109
+ result[key] = [...value, updateValue].filter((item) => item !== null && item !== undefined && item !== "");
110
+ }
111
+ }
112
+ else if (typeof value === "object" && !Array.isArray(value)) {
113
+ // If the value is an object, we need to merge it
114
+ if (typeof update[key] === "object" && !Array.isArray(update[key])) {
115
+ result[key] = this.mergeObjects(value, update[key]);
116
+ }
117
+ }
118
+ else {
119
+ // Finally, the source value is defined, and not an array, so we don't care about the update value
120
+ continue;
121
+ }
122
+ }
123
+ // Because the objects should technically always be validated FIRST, we can assume the update keys are also defined on the source object
124
+ for (const [key, value] of Object.entries(update)) {
125
+ if (value === undefined || value === null || value === "") {
126
+ continue;
127
+ }
128
+ else if (!Object.hasOwn(source, key)) {
129
+ result[key] = value;
130
+ }
131
+ else if (typeof source[key] === "object" &&
132
+ typeof value === "object" &&
133
+ !Array.isArray(source[key]) &&
134
+ !Array.isArray(value)) {
135
+ result[key] = this.mergeObjects(source[key], value);
136
+ }
137
+ else if (Array.isArray(source[key]) && Array.isArray(value)) {
138
+ result[key] = [...new Set([...source[key], ...value])].filter((item) => item !== null && item !== undefined && item !== "");
139
+ }
140
+ else if (source[key] === undefined ||
141
+ source[key] === null ||
142
+ source[key] === "") {
143
+ result[key] = value;
144
+ }
145
+ }
99
146
  return result;
100
147
  }
101
148
  // Method to load data from a file specified in the import definition
@@ -125,8 +172,21 @@ export class DataLoader {
125
172
  // Method to generate a unique ID that doesn't conflict with existing IDs
126
173
  getTrueUniqueId(collectionName) {
127
174
  let newId = ID.unique();
128
- while (this.checkMapValuesForId(newId, collectionName)) {
175
+ let condition = this.checkMapValuesForId(newId, collectionName) ||
176
+ this.userExistsMap.has(newId) ||
177
+ this.userIdSet.has(newId) ||
178
+ this.importMap
179
+ .get(this.getCollectionKey("users"))
180
+ ?.data.some((user) => user.finalData.docId === newId || user.finalData.userId === newId);
181
+ while (condition) {
129
182
  newId = ID.unique();
183
+ condition =
184
+ this.checkMapValuesForId(newId, collectionName) ||
185
+ this.userExistsMap.has(newId) ||
186
+ this.userIdSet.has(newId) ||
187
+ this.importMap
188
+ .get(this.getCollectionKey("users"))
189
+ ?.data.some((user) => user.finalData.docId === newId || user.finalData.userId === newId);
130
190
  }
131
191
  return newId;
132
192
  }
@@ -203,12 +263,35 @@ export class DataLoader {
203
263
  // Iterate over the users and setup our maps ahead of time for email and phone
204
264
  for (const user of allUsers) {
205
265
  if (user.email) {
206
- this.emailToUserIdMap.set(user.email, user.$id);
266
+ this.emailToUserIdMap.set(user.email.toLowerCase(), user.$id);
207
267
  }
208
268
  if (user.phone) {
209
269
  this.phoneToUserIdMap.set(user.phone, user.$id);
210
270
  }
211
271
  this.userExistsMap.set(user.$id, true);
272
+ this.userIdSet.add(user.$id);
273
+ let importData = this.importMap.get(this.getCollectionKey("users"));
274
+ if (!importData) {
275
+ importData = {
276
+ data: [],
277
+ };
278
+ }
279
+ importData.data.push({
280
+ finalData: {
281
+ ...user,
282
+ email: user.email?.toLowerCase(),
283
+ userId: user.$id,
284
+ docId: user.$id,
285
+ },
286
+ context: {
287
+ ...user,
288
+ email: user.email?.toLowerCase(),
289
+ userId: user.$id,
290
+ docId: user.$id,
291
+ },
292
+ rawData: user,
293
+ });
294
+ this.importMap.set(this.getCollectionKey("users"), importData);
212
295
  }
213
296
  return allUsers;
214
297
  }
@@ -219,7 +302,8 @@ export class DataLoader {
219
302
  console.log("---------------------------------");
220
303
  await this.setupMaps(dbId);
221
304
  const allUsers = await this.getAllUsers();
222
- console.log(`Fetched ${allUsers.length} users`);
305
+ console.log(`Fetched ${allUsers.length} users, waiting a few seconds to let the program catch up...`);
306
+ await new Promise((resolve) => setTimeout(resolve, 5000));
223
307
  // Iterate over the configured databases to find the matching one
224
308
  for (const db of this.config.databases) {
225
309
  if (db.$id !== dbId) {
@@ -234,17 +318,19 @@ export class DataLoader {
234
318
  // Determine if this is the users collection
235
319
  let isUsersCollection = this.getCollectionKey(this.config.usersCollectionName) ===
236
320
  this.getCollectionKey(collection.name);
321
+ const collectionDefs = collection.importDefs;
322
+ if (!collectionDefs || !collectionDefs.length) {
323
+ continue;
324
+ }
237
325
  // Process create and update definitions for the collection
238
326
  const createDefs = collection.importDefs.filter((def) => def.type === "create" || !def.type);
239
327
  const updateDefs = collection.importDefs.filter((def) => def.type === "update");
240
328
  for (const createDef of createDefs) {
241
- if (!isUsersCollection) {
242
- console.log(`${collection.name} is not users collection`);
329
+ if (!isUsersCollection || !createDef.createUsers) {
243
330
  await this.prepareCreateData(db, collection, createDef);
244
331
  }
245
332
  else {
246
333
  // Special handling for users collection if needed
247
- console.log(`${collection.name} is users collection`);
248
334
  await this.prepareUserCollectionCreateData(db, collection, createDef);
249
335
  }
250
336
  }
@@ -254,12 +340,12 @@ export class DataLoader {
254
340
  continue;
255
341
  }
256
342
  // Prepare the update data for the collection
257
- await this.prepareUpdateData(db, collection, updateDef);
343
+ this.prepareUpdateData(db, collection, updateDef);
258
344
  }
259
345
  }
260
346
  console.log("Running update references");
261
- await this.dealWithMergedUsers();
262
- await this.updateOldReferencesForNew();
347
+ // this.dealWithMergedUsers();
348
+ this.updateOldReferencesForNew();
263
349
  console.log("Done running update references");
264
350
  }
265
351
  // for (const collection of this.config.collections) {
@@ -272,49 +358,70 @@ export class DataLoader {
272
358
  this.writeMapsToJsonFile();
273
359
  }
274
360
  }
275
- async dealWithMergedUsers() {
276
- const usersCollectionKey = this.getCollectionKey(this.config.usersCollectionName);
277
- const usersCollectionPrimaryKeyFields = new Set();
278
- if (!this.config.collections) {
279
- return;
361
+ /**
362
+ * Deals with merged users by iterating through all collections in the configuration.
363
+ * We have merged users if there are duplicate emails or phones in the import data.
364
+ * This function will iterate through all collections that are the same name as the
365
+ * users collection and pull out their primaryKeyField's. It will then loop through
366
+ * each collection and find any documents that have a
367
+ *
368
+ * @return {void} This function does not return anything.
369
+ */
370
+ // dealWithMergedUsers() {
371
+ // const usersCollectionKey = this.getCollectionKey(
372
+ // this.config.usersCollectionName
373
+ // );
374
+ // const usersCollectionData = this.importMap.get(usersCollectionKey);
375
+ // if (!this.config.collections) {
376
+ // console.log("No collections found in configuration.");
377
+ // return;
378
+ // }
379
+ // let needsUpdate = false;
380
+ // let numUpdates = 0;
381
+ // for (const collectionConfig of this.config.collections) {
382
+ // const collectionKey = this.getCollectionKey(collectionConfig.name);
383
+ // const collectionData = this.importMap.get(collectionKey);
384
+ // const collectionImportDefs = collectionConfig.importDefs;
385
+ // const collectionIdMappings = collectionImportDefs
386
+ // .map((importDef) => importDef.idMappings)
387
+ // .flat()
388
+ // .filter((idMapping) => idMapping !== undefined && idMapping !== null);
389
+ // if (!collectionData || !collectionData.data) continue;
390
+ // for (const dataItem of collectionData.data) {
391
+ // for (const idMapping of collectionIdMappings) {
392
+ // // We know it's the users collection here
393
+ // if (this.getCollectionKey(idMapping.targetCollection) === usersCollectionKey) {
394
+ // const targetFieldKey = idMapping.targetFieldToMatch || idMapping.targetField;
395
+ // if (targetFieldKey === )
396
+ // const targetValue = dataItem.finalData[targetFieldKey];
397
+ // const targetCollectionData = this.importMap.get(this.getCollectionKey(idMapping.targetCollection));
398
+ // if (!targetCollectionData || !targetCollectionData.data) continue;
399
+ // const foundData = targetCollectionData.data.filter(({ context }) => {
400
+ // const targetValue = context[targetFieldKey];
401
+ // const isMatch = `${targetValue}` === `${valueToMatch}`;
402
+ // return isMatch && targetValue !== undefined && targetValue !== null;
403
+ // });
404
+ // }
405
+ // }
406
+ // }
407
+ // }
408
+ // }
409
+ /**
410
+ * Gets the value to match for a given key in the final data or context.
411
+ * @param finalData - The final data object.
412
+ * @param context - The context object.
413
+ * @param key - The key to get the value for.
414
+ * @returns The value to match for from finalData or Context
415
+ */
416
+ getValueFromData(finalData, context, key) {
417
+ if (context[key] !== undefined &&
418
+ context[key] !== null &&
419
+ context[key] !== "") {
420
+ return context[key];
280
421
  }
281
- // Collect primary key fields from the users collection definitions
282
- this.config.collections.forEach((collection) => {
283
- if (this.getCollectionKey(collection.name) === usersCollectionKey) {
284
- collection.importDefs.forEach((importDef) => {
285
- if (importDef.primaryKeyField) {
286
- usersCollectionPrimaryKeyFields.add(importDef.primaryKeyField);
287
- }
288
- });
289
- }
290
- });
291
- // Iterate over all collections to update references based on merged users
292
- this.config.collections.forEach((collection) => {
293
- const collectionData = this.importMap.get(this.getCollectionKey(collection.name));
294
- if (!collectionData || !collectionData.data)
295
- return;
296
- collection.importDefs.forEach((importDef) => {
297
- importDef.idMappings?.forEach((idMapping) => {
298
- if (this.getCollectionKey(idMapping.targetCollection) ===
299
- usersCollectionKey) {
300
- if (usersCollectionPrimaryKeyFields.has(idMapping.targetField)) {
301
- // Process each item in the collection
302
- collectionData.data.forEach((item) => {
303
- const oldId = item.context[idMapping.sourceField];
304
- const newId = this.mergedUserMap.get(oldId);
305
- if (newId) {
306
- // Update context to use new user ID
307
- item.context[idMapping.fieldToSet || idMapping.targetField] =
308
- newId;
309
- }
310
- });
311
- }
312
- }
313
- });
314
- });
315
- });
422
+ return finalData[key];
316
423
  }
317
- async updateOldReferencesForNew() {
424
+ updateOldReferencesForNew() {
318
425
  if (!this.config.collections) {
319
426
  return;
320
427
  }
@@ -333,158 +440,159 @@ export class DataLoader {
333
440
  for (const idMapping of importDef.idMappings) {
334
441
  const targetCollectionKey = this.getCollectionKey(idMapping.targetCollection);
335
442
  const fieldToSetKey = idMapping.fieldToSet || idMapping.sourceField;
336
- const valueToMatch = collectionData.data[i].context[idMapping.sourceField];
337
- if (!valueToMatch || _.isEmpty(valueToMatch))
443
+ const targetFieldKey = idMapping.targetFieldToMatch || idMapping.targetField;
444
+ const sourceValue = this.getValueFromData(collectionData.data[i].finalData, collectionData.data[i].context, idMapping.sourceField);
445
+ // Skip if value to match is missing or empty
446
+ if (!sourceValue ||
447
+ _.isEmpty(sourceValue) ||
448
+ sourceValue === null)
338
449
  continue;
450
+ const isFieldToSetArray = collectionConfig.attributes.find((attribute) => attribute.key === fieldToSetKey)?.array;
339
451
  const targetCollectionData = this.importMap.get(targetCollectionKey);
340
452
  if (!targetCollectionData || !targetCollectionData.data)
341
453
  continue;
342
- const foundData = targetCollectionData.data.filter((data) => {
343
- const targetValue = data.context[idMapping.targetField];
344
- const isMatch = `${targetValue}` === `${valueToMatch}`;
345
- // Debugging output to understand what's being compared
346
- logger.warn(`Comparing target: ${targetValue} with match: ${valueToMatch} - Result: ${isMatch}`);
347
- return isMatch;
348
- });
349
- if (!foundData.length) {
350
- console.log(`No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey}`);
351
- logger.error(`No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey} -- idMapping: ${JSON.stringify(idMapping, null, 2)}`);
454
+ // Handle cases where sourceValue is an array
455
+ const sourceValues = Array.isArray(sourceValue)
456
+ ? sourceValue.map((sourceValue) => `${sourceValue}`)
457
+ : [`${sourceValue}`];
458
+ let newData = [];
459
+ for (const valueToMatch of sourceValues) {
460
+ // Find matching data in the target collection
461
+ const foundData = targetCollectionData.data.filter(({ context, finalData }) => {
462
+ const targetValue = this.getValueFromData(finalData, context, targetFieldKey);
463
+ const isMatch = `${targetValue}` === `${valueToMatch}`;
464
+ // Ensure the targetValue is defined and not null
465
+ return (isMatch &&
466
+ targetValue !== undefined &&
467
+ targetValue !== null);
468
+ });
469
+ if (foundData.length) {
470
+ newData.push(...foundData.map((data) => {
471
+ const newValue = this.getValueFromData(data.finalData, data.context, idMapping.targetField);
472
+ return newValue;
473
+ }));
474
+ }
475
+ else {
476
+ logger.info(`No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey} -- idMapping: ${JSON.stringify(idMapping, null, 2)}`);
477
+ }
352
478
  continue;
353
479
  }
354
- needsUpdate = true;
355
- // Properly handle arrays and non-arrays
356
- if (Array.isArray(collectionData.data[i].finalData[fieldToSetKey])) {
357
- collectionData.data[i].finalData[fieldToSetKey] =
358
- foundData.map((data) => data.finalData);
359
- }
360
- else {
361
- collectionData.data[i].finalData[fieldToSetKey] =
362
- foundData[0].finalData;
363
- }
364
- }
365
- }
366
- }
367
- }
368
- }
369
- if (needsUpdate) {
370
- this.importMap.set(collectionKey, collectionData);
371
- }
372
- }
373
- }
374
- async updateReferencesInRelatedCollections() {
375
- if (!this.config.collections) {
376
- return;
377
- }
378
- // Iterate over each collection configuration
379
- for (const collectionConfig of this.config.collections) {
380
- const collectionKey = this.getCollectionKey(collectionConfig.name);
381
- const collectionData = this.importMap.get(collectionKey);
382
- if (!collectionData || !collectionData.data)
383
- continue;
384
- console.log(`Updating references for collection: ${collectionConfig.name}`);
385
- // Iterate over each data item in the current collection
386
- for (const item of collectionData.data) {
387
- let needsUpdate = false;
388
- // Check if the current collection has import definitions with idMappings
389
- if (collectionConfig.importDefs) {
390
- for (const importDef of collectionConfig.importDefs) {
391
- if (importDef.idMappings) {
392
- // Iterate over each idMapping defined for the current import definition
393
- for (const idMapping of importDef.idMappings) {
394
- const oldIds = Array.isArray(item.context[idMapping.sourceField])
395
- ? item.context[idMapping.sourceField]
396
- : [item.context[idMapping.sourceField]];
397
- const resolvedNewIds = [];
398
- oldIds.forEach((oldId) => {
399
- // Attempt to find a new ID for the old ID
400
- let newIdForOldId = this.findNewIdForOldId(oldId, idMapping, importDef);
401
- if (newIdForOldId &&
402
- !resolvedNewIds.includes(newIdForOldId)) {
403
- resolvedNewIds.push(newIdForOldId);
480
+ const getCurrentDataFiltered = (currentData) => {
481
+ if (Array.isArray(currentData.finalData[fieldToSetKey])) {
482
+ return currentData.finalData[fieldToSetKey].filter((data) => !sourceValues.includes(`${data}`));
483
+ }
484
+ return currentData.finalData[fieldToSetKey];
485
+ };
486
+ // Get the current data to be updated
487
+ const currentDataFiltered = getCurrentDataFiltered(collectionData.data[i]);
488
+ if (newData.length) {
489
+ needsUpdate = true;
490
+ // Handle cases where current data is an array
491
+ if (isFieldToSetArray) {
492
+ if (!currentDataFiltered) {
493
+ // Set new data if current data is undefined
494
+ collectionData.data[i].finalData[fieldToSetKey] =
495
+ Array.isArray(newData) ? newData : [newData];
496
+ }
497
+ else {
498
+ if (Array.isArray(currentDataFiltered)) {
499
+ // Convert current data to array and merge if new data is non-empty array
500
+ collectionData.data[i].finalData[fieldToSetKey] = [
501
+ ...new Set([...currentDataFiltered, ...newData].filter((value) => value !== null &&
502
+ value !== undefined &&
503
+ value !== "")),
504
+ ];
505
+ }
506
+ else {
507
+ // Merge arrays if new data is non-empty array and filter for uniqueness
508
+ collectionData.data[i].finalData[fieldToSetKey] = [
509
+ ...new Set([
510
+ ...(Array.isArray(currentDataFiltered)
511
+ ? currentDataFiltered
512
+ : [currentDataFiltered]),
513
+ ...newData,
514
+ ].filter((value) => value !== null &&
515
+ value !== undefined &&
516
+ value !== "" &&
517
+ !sourceValues.includes(`${value}`))),
518
+ ];
519
+ }
520
+ }
404
521
  }
405
522
  else {
406
- logger.error(`No new ID found for old ID ${oldId} in collection ${collectionConfig.name}`);
523
+ if (!currentDataFiltered) {
524
+ // Set new data if current data is undefined
525
+ collectionData.data[i].finalData[fieldToSetKey] =
526
+ Array.isArray(newData) ? newData[0] : newData;
527
+ }
528
+ else if (Array.isArray(newData) && newData.length > 0) {
529
+ // Convert current data to array and merge if new data is non-empty array, then filter for uniqueness
530
+ // and take the first value, because it's an array and the attribute is not an array
531
+ collectionData.data[i].finalData[fieldToSetKey] = [
532
+ ...new Set([currentDataFiltered, ...newData].filter((value) => value !== null &&
533
+ value !== undefined &&
534
+ value !== "" &&
535
+ !sourceValues.includes(`${value}`))),
536
+ ].slice(0, 1)[0];
537
+ }
538
+ else if (!Array.isArray(newData) &&
539
+ newData !== undefined) {
540
+ // Simply update the field if new data is not an array and defined
541
+ collectionData.data[i].finalData[fieldToSetKey] = newData;
542
+ }
407
543
  }
408
- });
409
- if (resolvedNewIds.length) {
410
- const targetField = idMapping.fieldToSet || idMapping.targetField;
411
- const isArray = collectionConfig.attributes.some((attribute) => attribute.key === targetField && attribute.array);
412
- // Set the target field based on whether it's an array or single value
413
- item.finalData[targetField] = isArray
414
- ? resolvedNewIds
415
- : resolvedNewIds[0];
416
- needsUpdate = true;
417
544
  }
418
545
  }
419
546
  }
420
547
  }
421
548
  }
422
- // Update the importMap if changes were made to the item
423
- if (needsUpdate) {
424
- this.importMap.set(collectionKey, collectionData);
425
- logger.info(`Updated item: ${JSON.stringify(item.finalData, undefined, 2)}`);
426
- }
427
- }
428
- }
429
- }
430
- findNewIdForOldId(oldId, idMapping, importDef) {
431
- // First, check if this ID mapping is related to the users collection.
432
- const targetCollectionKey = this.getCollectionKey(idMapping.targetCollection);
433
- const isUsersCollection = targetCollectionKey ===
434
- this.getCollectionKey(this.config.usersCollectionName);
435
- // If handling users, check the mergedUserMap for any existing new ID.
436
- if (isUsersCollection) {
437
- for (const [newUserId, oldIds] of this.mergedUserMap.entries()) {
438
- if (oldIds.includes(oldId)) {
439
- return newUserId;
440
- }
441
549
  }
442
- }
443
- // If not a user or no merged ID found, check the regular ID mapping from old to new.
444
- const targetCollectionData = this.importMap.get(targetCollectionKey);
445
- if (targetCollectionData) {
446
- const foundEntry = targetCollectionData.data.find((entry) => entry.context[importDef.primaryKeyField] === oldId);
447
- if (foundEntry) {
448
- return foundEntry.context.docId; // Assuming `docId` stores the new ID after import
550
+ // Update the import map if any changes were made
551
+ if (needsUpdate) {
552
+ this.importMap.set(collectionKey, collectionData);
449
553
  }
450
554
  }
451
- logger.error(`No corresponding new ID found for ${oldId} in ${targetCollectionKey}`);
452
- return null; // Return null if no new ID is found
453
555
  }
454
556
  writeMapsToJsonFile() {
455
- const outputDir = path.resolve(process.cwd());
456
- const outputFile = path.join(outputDir, "dataLoaderOutput.json");
457
- const dataToWrite = {
458
- // Convert Maps to arrays of entries for serialization
459
- oldIdToNewIdPerCollectionMap: Array.from(this.oldIdToNewIdPerCollectionMap.entries()).map(([key, value]) => {
460
- return {
461
- collection: key,
462
- data: Array.from(value.entries()),
463
- };
464
- }),
465
- mergedUserMap: Array.from(this.mergedUserMap.entries()),
466
- dataFromCollections: Array.from(this.importMap.entries()).map(([key, value]) => {
467
- return {
468
- collection: key,
469
- data: value.data.map((item) => item.finalData),
470
- };
471
- }),
472
- // emailToUserIdMap: Array.from(this.emailToUserIdMap.entries()),
473
- // phoneToUserIdMap: Array.from(this.phoneToUserIdMap.entries()),
474
- };
475
- // Use JSON.stringify with a replacer function to handle Maps
476
- const replacer = (key, value) => {
477
- if (value instanceof Map) {
478
- return Array.from(value.entries());
479
- }
480
- return value;
557
+ const outputDir = path.resolve(process.cwd(), "zlogs");
558
+ // Ensure the logs directory exists
559
+ if (!fs.existsSync(outputDir)) {
560
+ fs.mkdirSync(outputDir);
561
+ }
562
+ // Helper function to write data to a file
563
+ const writeToFile = (fileName, data) => {
564
+ const outputFile = path.join(outputDir, fileName);
565
+ fs.writeFile(outputFile, JSON.stringify(data, null, 2), "utf8", (err) => {
566
+ if (err) {
567
+ console.error(`Error writing data to ${fileName}:`, err);
568
+ return;
569
+ }
570
+ console.log(`Data successfully written to ${fileName}`);
571
+ });
481
572
  };
482
- fs.writeFile(outputFile, JSON.stringify(dataToWrite, replacer, 2), "utf8", (err) => {
483
- if (err) {
484
- console.error("Error writing data to JSON file:", err);
485
- return;
486
- }
487
- console.log(`Data successfully written to ${outputFile}`);
573
+ // Convert Maps to arrays of entries for serialization
574
+ const oldIdToNewIdPerCollectionMap = Array.from(this.oldIdToNewIdPerCollectionMap.entries()).map(([key, value]) => {
575
+ return {
576
+ collection: key,
577
+ data: Array.from(value.entries()),
578
+ };
579
+ });
580
+ const mergedUserMap = Array.from(this.mergedUserMap.entries());
581
+ // Write each part to a separate file
582
+ writeToFile("oldIdToNewIdPerCollectionMap.json", oldIdToNewIdPerCollectionMap);
583
+ writeToFile("mergedUserMap.json", mergedUserMap);
584
+ // Write each collection's data to a separate file
585
+ this.importMap.forEach((value, key) => {
586
+ const data = {
587
+ collection: key,
588
+ data: value.data.map((item) => {
589
+ return {
590
+ finalData: item.finalData,
591
+ context: item.context,
592
+ };
593
+ }),
594
+ };
595
+ writeToFile(`${key}.json`, data);
488
596
  });
489
597
  }
490
598
  /**
@@ -495,46 +603,109 @@ export class DataLoader {
495
603
  * @param attributeMappings - The attribute mappings for the item.
496
604
  * @returns The transformed item with user-specific keys removed.
497
605
  */
498
- async prepareUserData(item, attributeMappings, primaryKeyField, newId) {
499
- // Transform the item data based on the attribute mappings
606
+ prepareUserData(item, attributeMappings, primaryKeyField, newId) {
607
+ if (this.userIdSet.has(newId) ||
608
+ this.userExistsMap.has(newId) ||
609
+ Array.from(this.emailToUserIdMap.values()).includes(newId) ||
610
+ Array.from(this.phoneToUserIdMap.values()).includes(newId)) {
611
+ newId = this.getTrueUniqueId(this.getCollectionKey("users"));
612
+ }
500
613
  let transformedItem = this.transformData(item, attributeMappings);
501
- const userData = AuthUserCreateSchema.safeParse(transformedItem);
502
- if (!userData.success) {
503
- logger.error(`Invalid user data: ${JSON.stringify(userData.error.errors, undefined, 2)}`);
504
- return transformedItem;
614
+ let userData = AuthUserCreateSchema.safeParse(transformedItem);
615
+ if (userData.data?.email) {
616
+ userData.data.email = userData.data.email.toLowerCase();
505
617
  }
506
- const email = userData.data.email;
618
+ if (!userData.success || !(userData.data.email || userData.data.phone)) {
619
+ logger.error(`Invalid user data: ${JSON.stringify(userData.error?.errors, undefined, 2)} or missing email/phone`);
620
+ const userKeys = ["email", "phone", "name", "labels", "prefs"];
621
+ userKeys.forEach((key) => {
622
+ if (transformedItem.hasOwnProperty(key)) {
623
+ delete transformedItem[key];
624
+ }
625
+ });
626
+ return {
627
+ transformedItem,
628
+ existingId: undefined,
629
+ userData: {
630
+ rawData: item,
631
+ finalData: transformedItem,
632
+ },
633
+ };
634
+ }
635
+ const email = userData.data.email?.toLowerCase();
507
636
  const phone = userData.data.phone;
508
637
  let existingId;
509
- // Check for duplicate email and add to emailToUserIdMap if not found
510
- if (email && email.length > 0) {
511
- if (this.emailToUserIdMap.has(email)) {
512
- existingId = this.emailToUserIdMap.get(email);
638
+ // Check for duplicate email and phone
639
+ if (email && this.emailToUserIdMap.has(email)) {
640
+ existingId = this.emailToUserIdMap.get(email);
641
+ if (phone && !this.phoneToUserIdMap.has(phone)) {
642
+ this.phoneToUserIdMap.set(phone, newId);
513
643
  }
514
- else {
644
+ }
645
+ else if (phone && this.phoneToUserIdMap.has(phone)) {
646
+ existingId = this.phoneToUserIdMap.get(phone);
647
+ if (email && !this.emailToUserIdMap.has(email)) {
515
648
  this.emailToUserIdMap.set(email, newId);
516
649
  }
517
650
  }
518
- // Check for duplicate phone and add to phoneToUserIdMap if not found
519
- if (phone && phone.length > 0) {
520
- if (this.phoneToUserIdMap.has(phone)) {
521
- existingId = this.phoneToUserIdMap.get(phone);
522
- }
523
- else {
651
+ else {
652
+ if (email)
653
+ this.emailToUserIdMap.set(email, newId);
654
+ if (phone)
524
655
  this.phoneToUserIdMap.set(phone, newId);
525
- }
526
- }
527
- if (!existingId) {
528
- existingId = newId;
529
656
  }
530
- // If existingId is found, add to mergedUserMap
531
657
  if (existingId) {
532
658
  userData.data.userId = existingId;
533
659
  const mergedUsers = this.mergedUserMap.get(existingId) || [];
534
660
  mergedUsers.push(`${item[primaryKeyField]}`);
535
661
  this.mergedUserMap.set(existingId, mergedUsers);
662
+ const userFound = this.importMap
663
+ .get(this.getCollectionKey("users"))
664
+ ?.data.find((userDataExisting) => {
665
+ let userIdToMatch;
666
+ if (userDataExisting?.finalData?.userId) {
667
+ userIdToMatch = userDataExisting?.finalData?.userId;
668
+ }
669
+ else if (userDataExisting?.finalData?.docId) {
670
+ userIdToMatch = userDataExisting?.finalData?.docId;
671
+ }
672
+ else if (userDataExisting?.context?.userId) {
673
+ userIdToMatch = userDataExisting.context.userId;
674
+ }
675
+ else if (userDataExisting?.context?.docId) {
676
+ userIdToMatch = userDataExisting.context.docId;
677
+ }
678
+ return userIdToMatch === existingId;
679
+ });
680
+ if (userFound) {
681
+ userFound.finalData.userId = existingId;
682
+ userFound.finalData.docId = existingId;
683
+ this.userIdSet.add(existingId);
684
+ transformedItem = {
685
+ ...transformedItem,
686
+ userId: existingId,
687
+ docId: existingId,
688
+ };
689
+ }
690
+ const userKeys = ["email", "phone", "name", "labels", "prefs"];
691
+ userKeys.forEach((key) => {
692
+ if (transformedItem.hasOwnProperty(key)) {
693
+ delete transformedItem[key];
694
+ }
695
+ });
696
+ return {
697
+ transformedItem,
698
+ existingId,
699
+ userData: {
700
+ rawData: userFound?.rawData,
701
+ finalData: userFound?.finalData,
702
+ },
703
+ };
704
+ }
705
+ else {
706
+ existingId = newId;
707
+ userData.data.userId = existingId;
536
708
  }
537
- // Remove user-specific keys from the transformed item
538
709
  const userKeys = ["email", "phone", "name", "labels", "prefs"];
539
710
  userKeys.forEach((key) => {
540
711
  if (transformedItem.hasOwnProperty(key)) {
@@ -546,11 +717,15 @@ export class DataLoader {
546
717
  rawData: item,
547
718
  finalData: userData.data,
548
719
  };
549
- // Directly update the importMap with the new user data, without pushing to usersMap.data first
550
720
  this.importMap.set(this.getCollectionKey("users"), {
551
721
  data: [...(usersMap?.data || []), userDataToAdd],
552
722
  });
553
- return [transformedItem, existingId, userDataToAdd];
723
+ this.userIdSet.add(existingId);
724
+ return {
725
+ transformedItem,
726
+ existingId,
727
+ userData: userDataToAdd,
728
+ };
554
729
  }
555
730
  /**
556
731
  * Prepares the data for creating user collection documents.
@@ -572,7 +747,6 @@ export class DataLoader {
572
747
  this.oldIdToNewIdPerCollectionMap
573
748
  .set(this.getCollectionKey(collection.name), oldIdToNewIdMap)
574
749
  .get(this.getCollectionKey(collection.name));
575
- console.log(`${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`);
576
750
  if (!operationId) {
577
751
  throw new Error(`No import operation found for collection ${collection.name}`);
578
752
  }
@@ -595,18 +769,31 @@ export class DataLoader {
595
769
  // Iterate through each item in the raw data
596
770
  for (const item of rawData) {
597
771
  // Prepare user data, check for duplicates, and remove user-specific keys
598
- let [transformedItem, existingId, userData] = await this.prepareUserData(item, importDef.attributeMappings, importDef.primaryKeyField, this.getTrueUniqueId(this.getCollectionKey("users")));
772
+ let { transformedItem, existingId, userData } = this.prepareUserData(item, importDef.attributeMappings, importDef.primaryKeyField, this.getTrueUniqueId(this.getCollectionKey("users")));
599
773
  logger.info(`In create user -- transformedItem: ${JSON.stringify(transformedItem, null, 2)}`);
600
774
  // Generate a new unique ID for the item or use existing ID
601
- if (!existingId) {
775
+ if (!existingId && !userData.finalData?.userId) {
602
776
  // No existing user ID, generate a new unique ID
603
- existingId = this.getTrueUniqueId(this.getCollectionKey("users"));
604
- transformedItem.userId = existingId; // Assign the new ID to the transformed data's userId field
777
+ existingId = this.getTrueUniqueId(this.getCollectionKey(collection.name));
778
+ transformedItem = {
779
+ ...transformedItem,
780
+ userId: existingId,
781
+ docId: existingId,
782
+ };
783
+ }
784
+ else if (!existingId && userData.finalData?.userId) {
785
+ // Existing user ID, use it as the new ID
786
+ existingId = userData.finalData.userId;
787
+ transformedItem = {
788
+ ...transformedItem,
789
+ userId: existingId,
790
+ docId: existingId,
791
+ };
605
792
  }
606
793
  // Create a context object for the item, including the new ID
607
794
  let context = this.createContext(db, collection, item, existingId);
608
795
  // Merge the transformed data into the context
609
- context = { ...context, ...transformedItem };
796
+ context = { ...context, ...transformedItem, ...userData.finalData };
610
797
  // If a primary key field is defined, handle the ID mapping and check for duplicates
611
798
  if (importDef.primaryKeyField) {
612
799
  const oldId = item[importDef.primaryKeyField];
@@ -617,7 +804,9 @@ export class DataLoader {
617
804
  // Found a duplicate oldId, now decide how to merge or handle these duplicates
618
805
  for (const data of currentData.data) {
619
806
  if (data.finalData.docId === oldId ||
620
- data.finalData.userId === oldId) {
807
+ data.finalData.userId === oldId ||
808
+ data.context.docId === oldId ||
809
+ data.context.userId === oldId) {
621
810
  transformedItem = this.mergeObjects(data.finalData, transformedItem);
622
811
  }
623
812
  }
@@ -627,15 +816,22 @@ export class DataLoader {
627
816
  collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
628
817
  }
629
818
  }
630
- // Merge the final user data into the context
631
- context = { ...context, ...userData.finalData };
632
819
  // Handle merging for currentUserData
633
820
  for (let i = 0; i < currentUserData.data.length; i++) {
634
- if ((currentUserData.data[i].finalData.docId === existingId ||
635
- currentUserData.data[i].finalData.userId === existingId) &&
636
- !_.isEqual(currentUserData.data[i], userData)) {
637
- this.mergeObjects(currentUserData.data[i].finalData, userData.finalData);
638
- console.log("Merging user data", currentUserData.data[i].finalData);
821
+ const currentUserDataItem = currentUserData.data[i];
822
+ const samePhones = currentUserDataItem.finalData.phone &&
823
+ transformedItem.phone &&
824
+ currentUserDataItem.finalData.phone === transformedItem.phone;
825
+ const sameEmails = currentUserDataItem.finalData.email &&
826
+ transformedItem.email &&
827
+ currentUserDataItem.finalData.email === transformedItem.email;
828
+ if ((currentUserDataItem.finalData.docId === existingId ||
829
+ currentUserDataItem.finalData.userId === existingId) &&
830
+ (samePhones || sameEmails) &&
831
+ currentUserDataItem.finalData &&
832
+ userData.finalData) {
833
+ const userDataMerged = this.mergeObjects(currentUserData.data[i].finalData, userData.finalData);
834
+ currentUserData.data[i].finalData = userDataMerged;
639
835
  this.importMap.set(this.getCollectionKey("users"), currentUserData);
640
836
  }
641
837
  }
@@ -646,27 +842,41 @@ export class DataLoader {
646
842
  ...importDef,
647
843
  attributeMappings: mappingsWithActions,
648
844
  };
845
+ const updatedData = this.importMap.get(this.getCollectionKey(collection.name));
649
846
  let foundData = false;
650
- for (let i = 0; i < currentData.data.length; i++) {
651
- if (currentData.data[i].finalData.docId === existingId ||
652
- currentData.data[i].finalData.userId === existingId) {
653
- currentData.data[i].finalData = this.mergeObjects(currentData.data[i].finalData, transformedItem);
654
- currentData.data[i].context = context;
655
- currentData.data[i].importDef = newImportDef;
656
- this.importMap.set(this.getCollectionKey(collection.name), currentData);
847
+ for (let i = 0; i < updatedData.data.length; i++) {
848
+ if (updatedData.data[i].finalData.docId === existingId ||
849
+ updatedData.data[i].finalData.userId === existingId ||
850
+ updatedData.data[i].context.docId === existingId ||
851
+ updatedData.data[i].context.userId === existingId) {
852
+ updatedData.data[i].finalData = this.mergeObjects(updatedData.data[i].finalData, transformedItem);
853
+ updatedData.data[i].context = this.mergeObjects(updatedData.data[i].context, context);
854
+ const mergedImportDef = {
855
+ ...updatedData.data[i].importDef,
856
+ idMappings: [
857
+ ...(updatedData.data[i].importDef?.idMappings || []),
858
+ ...(newImportDef.idMappings || []),
859
+ ],
860
+ attributeMappings: [
861
+ ...(updatedData.data[i].importDef?.attributeMappings || []),
862
+ ...(newImportDef.attributeMappings || []),
863
+ ],
864
+ };
865
+ updatedData.data[i].importDef = mergedImportDef;
866
+ this.importMap.set(this.getCollectionKey(collection.name), updatedData);
657
867
  this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
658
868
  foundData = true;
659
869
  }
660
870
  }
661
871
  if (!foundData) {
662
872
  // Add new data to the associated collection
663
- currentData.data.push({
873
+ updatedData.data.push({
664
874
  rawData: item,
665
875
  context: context,
666
876
  importDef: newImportDef,
667
877
  finalData: transformedItem,
668
878
  });
669
- this.importMap.set(this.getCollectionKey(collection.name), currentData);
879
+ this.importMap.set(this.getCollectionKey(collection.name), updatedData);
670
880
  this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
671
881
  }
672
882
  }
@@ -697,17 +907,20 @@ export class DataLoader {
697
907
  this.oldIdToNewIdPerCollectionMap
698
908
  .set(this.getCollectionKey(collection.name), oldIdToNewIdMapNew)
699
909
  .get(this.getCollectionKey(collection.name));
700
- console.log(`${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`);
910
+ const isRegions = collection.name.toLowerCase() === "regions";
701
911
  // Iterate through each item in the raw data
702
912
  for (const item of rawData) {
703
913
  // Generate a new unique ID for the item
704
914
  const itemIdNew = this.getTrueUniqueId(this.getCollectionKey(collection.name));
915
+ if (isRegions) {
916
+ logger.info(`Creating region: ${JSON.stringify(item, null, 2)}`);
917
+ }
705
918
  // Retrieve the current collection data from the import map
706
919
  const currentData = this.importMap.get(this.getCollectionKey(collection.name));
707
920
  // Create a context object for the item, including the new ID
708
921
  let context = this.createContext(db, collection, item, itemIdNew);
709
922
  // Transform the item data based on the attribute mappings
710
- const transformedData = this.transformData(item, importDef.attributeMappings);
923
+ let transformedData = this.transformData(item, importDef.attributeMappings);
711
924
  // If a primary key field is defined, handle the ID mapping and check for duplicates
712
925
  if (importDef.primaryKeyField) {
713
926
  const oldId = item[importDef.primaryKeyField];
@@ -720,7 +933,7 @@ export class DataLoader {
720
933
  // Merge the transformed data into the context
721
934
  context = { ...context, ...transformedData };
722
935
  // Validate the item before proceeding
723
- const isValid = await this.importDataActions.validateItem(transformedData, importDef.attributeMappings, context);
936
+ const isValid = this.importDataActions.validateItem(transformedData, importDef.attributeMappings, context);
724
937
  if (!isValid) {
725
938
  continue;
726
939
  }
@@ -759,69 +972,88 @@ export class DataLoader {
759
972
  * @param importDef - The import definition containing the attribute mappings and other relevant info.
760
973
  */
761
974
  async prepareUpdateData(db, collection, importDef) {
762
- // Retrieve the current collection data and old-to-new ID map from the import map
763
975
  const currentData = this.importMap.get(this.getCollectionKey(collection.name));
764
976
  const oldIdToNewIdMap = this.oldIdToNewIdPerCollectionMap.get(this.getCollectionKey(collection.name));
765
- // Log an error and return if no current data is found for the collection
766
977
  if (!(currentData?.data && currentData?.data.length > 0) &&
767
978
  !oldIdToNewIdMap) {
768
979
  logger.error(`No data found for collection ${collection.name} for updateDef but it says it's supposed to have one...`);
769
980
  return;
770
981
  }
771
- // Load the raw data based on the import definition
772
982
  const rawData = this.loadData(importDef);
773
983
  const operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
774
984
  if (!operationId) {
775
985
  throw new Error(`No import operation found for collection ${collection.name}`);
776
986
  }
777
987
  for (const item of rawData) {
778
- // Transform the item data based on the attribute mappings
779
988
  let transformedData = this.transformData(item, importDef.attributeMappings);
780
989
  let newId;
781
990
  let oldId;
782
- // Determine the new ID for the item based on the primary key field or update mapping
783
- if (importDef.primaryKeyField) {
784
- oldId = item[importDef.primaryKeyField];
785
- }
786
- else if (importDef.updateMapping) {
787
- oldId = item[importDef.updateMapping.originalIdField];
788
- }
789
- if (oldId) {
790
- newId = oldIdToNewIdMap?.get(`${oldId}`);
791
- if (!newId &&
792
- this.getCollectionKey(this.config.usersCollectionName) ===
793
- this.getCollectionKey(collection.name)) {
794
- for (const [key, value] of this.mergedUserMap.entries()) {
795
- if (value.includes(`${oldId}`)) {
796
- newId = key;
797
- break;
991
+ let itemDataToUpdate;
992
+ // Try to find itemDataToUpdate using updateMapping
993
+ if (importDef.updateMapping) {
994
+ oldId =
995
+ item[importDef.updateMapping.originalIdField] ||
996
+ transformedData[importDef.updateMapping.originalIdField];
997
+ if (oldId) {
998
+ itemDataToUpdate = currentData?.data.find(({ context, finalData }) => {
999
+ const targetField = importDef.updateMapping.targetField ??
1000
+ importDef.updateMapping.originalIdField;
1001
+ return (`${context[targetField]}` === `${oldId}` ||
1002
+ `${finalData[targetField]}` === `${oldId}`);
1003
+ });
1004
+ if (itemDataToUpdate) {
1005
+ newId = itemDataToUpdate.context.docId;
1006
+ }
1007
+ }
1008
+ }
1009
+ // If updateMapping is not defined or did not find the item, use primaryKeyField
1010
+ if (!itemDataToUpdate && importDef.primaryKeyField) {
1011
+ oldId =
1012
+ item[importDef.primaryKeyField] ||
1013
+ transformedData[importDef.primaryKeyField];
1014
+ if (oldId && oldId.length > 0) {
1015
+ newId = oldIdToNewIdMap?.get(`${oldId}`);
1016
+ if (!newId &&
1017
+ this.getCollectionKey(this.config.usersCollectionName) ===
1018
+ this.getCollectionKey(collection.name)) {
1019
+ for (const [key, value] of this.mergedUserMap.entries()) {
1020
+ if (value.includes(`${oldId}`)) {
1021
+ newId = key;
1022
+ break;
1023
+ }
798
1024
  }
799
1025
  }
800
1026
  }
1027
+ if (oldId && !itemDataToUpdate) {
1028
+ itemDataToUpdate = currentData?.data.find((data) => `${data.context[importDef.primaryKeyField]}` === `${oldId}`);
1029
+ }
801
1030
  }
802
- else {
1031
+ if (!oldId) {
803
1032
  logger.error(`No old ID found (to update another document with) in prepareUpdateData for ${collection.name}, ${JSON.stringify(item, null, 2)}`);
804
1033
  continue;
805
1034
  }
806
- // Log an error and continue to the next item if no new ID is found
807
- if (!newId) {
808
- logger.error(`No new id found for collection ${collection.name} for updateDef ${JSON.stringify(item, null, 2)} but it says it's supposed to have one...`);
1035
+ if (!newId && !itemDataToUpdate) {
1036
+ logger.error(`No new id && no data found for collection ${collection.name} for updateDef ${JSON.stringify(item, null, 2)} but it says it's supposed to have one...`);
809
1037
  continue;
810
1038
  }
811
- const itemDataToUpdate = this.importMap
812
- .get(this.getCollectionKey(collection.name))
813
- ?.data.find((data) => data.rawData[importDef.primaryKeyField] === oldId);
814
- if (!itemDataToUpdate) {
815
- logger.error(`No data found for collection ${collection.name} for updateDef ${JSON.stringify(item, null, 2)} but it says it's supposed to have one...`);
1039
+ else if (itemDataToUpdate) {
1040
+ newId =
1041
+ itemDataToUpdate.finalData.docId || itemDataToUpdate.context.docId;
1042
+ if (!newId) {
1043
+ logger.error(`No new id found for collection ${collection.name} for updateDef ${JSON.stringify(item, null, 2)} but has itemDataToUpdate ${JSON.stringify(itemDataToUpdate, null, 2)} but it says it's supposed to have one...`);
1044
+ continue;
1045
+ }
1046
+ }
1047
+ if (!itemDataToUpdate || !newId) {
1048
+ logger.error(`No data or ID (docId) found for collection ${collection.name} for updateDef ${JSON.stringify(item, null, 2)} but it says it's supposed to have one...`);
816
1049
  continue;
817
1050
  }
818
1051
  transformedData = this.mergeObjects(itemDataToUpdate.finalData, transformedData);
819
1052
  // Create a context object for the item, including the new ID and transformed data
820
- let context = this.createContext(db, collection, item, newId);
1053
+ let context = itemDataToUpdate.context;
821
1054
  context = this.mergeObjects(context, transformedData);
822
1055
  // Validate the item before proceeding
823
- const isValid = await this.importDataActions.validateItem(item, importDef.attributeMappings, context);
824
- // Log info and continue to the next item if it's invalid
1056
+ const isValid = this.importDataActions.validateItem(item, importDef.attributeMappings, context);
825
1057
  if (!isValid) {
826
1058
  logger.info(`Skipping item: ${JSON.stringify(item, null, 2)} because it's invalid`);
827
1059
  continue;
@@ -833,15 +1065,26 @@ export class DataLoader {
833
1065
  ...importDef,
834
1066
  attributeMappings: mappingsWithActions,
835
1067
  };
836
- // Add the item with its context and final data to the current collection data
837
1068
  if (itemDataToUpdate) {
838
- // Update the existing item's finalData and context in place
839
1069
  itemDataToUpdate.finalData = this.mergeObjects(itemDataToUpdate.finalData, transformedData);
840
1070
  itemDataToUpdate.context = context;
841
- itemDataToUpdate.importDef = newImportDef;
1071
+ // Merge existing importDef with new importDef, focusing only on postImportActions
1072
+ itemDataToUpdate.importDef = {
1073
+ ...itemDataToUpdate.importDef,
1074
+ attributeMappings: itemDataToUpdate.importDef?.attributeMappings.map((attrMapping, index) => ({
1075
+ ...attrMapping,
1076
+ postImportActions: [
1077
+ ...(attrMapping.postImportActions || []),
1078
+ ...(newImportDef.attributeMappings[index]
1079
+ ?.postImportActions || []),
1080
+ ],
1081
+ })) || [],
1082
+ };
1083
+ if (collection.name.toLowerCase() === "councils") {
1084
+ console.log(`Mappings in update councils: ${JSON.stringify(itemDataToUpdate.importDef.attributeMappings, null, 2)}`);
1085
+ }
842
1086
  }
843
1087
  else {
844
- // If no existing item matches, then add the new item
845
1088
  currentData.data.push({
846
1089
  rawData: item,
847
1090
  context: context,