appwrite-utils-cli 0.0.285 → 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.
- package/README.md +122 -96
- package/dist/collections/attributes.d.ts +4 -0
- package/dist/collections/attributes.js +224 -0
- package/dist/collections/indexes.d.ts +4 -0
- package/dist/collections/indexes.js +27 -0
- package/dist/collections/methods.d.ts +16 -0
- package/dist/collections/methods.js +216 -0
- package/dist/databases/methods.d.ts +6 -0
- package/dist/databases/methods.js +33 -0
- package/dist/interactiveCLI.d.ts +19 -0
- package/dist/interactiveCLI.js +555 -0
- package/dist/main.js +227 -62
- package/dist/migrations/afterImportActions.js +37 -40
- package/dist/migrations/appwriteToX.d.ts +26 -25
- package/dist/migrations/appwriteToX.js +42 -6
- package/dist/migrations/attributes.js +21 -20
- package/dist/migrations/backup.d.ts +93 -87
- package/dist/migrations/collections.d.ts +6 -0
- package/dist/migrations/collections.js +149 -20
- package/dist/migrations/converters.d.ts +2 -18
- package/dist/migrations/converters.js +13 -2
- package/dist/migrations/dataLoader.d.ts +276 -161
- package/dist/migrations/dataLoader.js +535 -292
- package/dist/migrations/databases.js +8 -2
- package/dist/migrations/helper.d.ts +3 -0
- package/dist/migrations/helper.js +21 -0
- package/dist/migrations/importController.d.ts +5 -2
- package/dist/migrations/importController.js +125 -88
- package/dist/migrations/importDataActions.d.ts +9 -1
- package/dist/migrations/importDataActions.js +15 -3
- package/dist/migrations/indexes.js +3 -2
- package/dist/migrations/logging.js +20 -8
- package/dist/migrations/migrationHelper.d.ts +9 -4
- package/dist/migrations/migrationHelper.js +6 -5
- package/dist/migrations/openapi.d.ts +1 -1
- package/dist/migrations/openapi.js +33 -18
- package/dist/migrations/queue.js +3 -2
- package/dist/migrations/relationships.d.ts +2 -2
- package/dist/migrations/schemaStrings.js +53 -41
- package/dist/migrations/setupDatabase.d.ts +2 -4
- package/dist/migrations/setupDatabase.js +24 -105
- package/dist/migrations/storage.d.ts +3 -1
- package/dist/migrations/storage.js +110 -16
- package/dist/migrations/transfer.d.ts +30 -0
- package/dist/migrations/transfer.js +337 -0
- package/dist/migrations/users.d.ts +2 -1
- package/dist/migrations/users.js +78 -43
- package/dist/schemas/authUser.d.ts +2 -2
- package/dist/storage/methods.d.ts +15 -0
- package/dist/storage/methods.js +207 -0
- package/dist/storage/schemas.d.ts +687 -0
- package/dist/storage/schemas.js +175 -0
- package/dist/utils/getClientFromConfig.d.ts +4 -0
- package/dist/utils/getClientFromConfig.js +16 -0
- package/dist/utils/helperFunctions.d.ts +11 -1
- package/dist/utils/helperFunctions.js +38 -0
- package/dist/utils/retryFailedPromises.d.ts +2 -0
- package/dist/utils/retryFailedPromises.js +21 -0
- package/dist/utils/schemaStrings.d.ts +13 -0
- package/dist/utils/schemaStrings.js +403 -0
- package/dist/utils/setupFiles.js +110 -61
- package/dist/utilsController.d.ts +40 -22
- package/dist/utilsController.js +164 -84
- package/package.json +13 -15
- package/src/collections/attributes.ts +483 -0
- package/src/collections/indexes.ts +53 -0
- package/src/collections/methods.ts +331 -0
- package/src/databases/methods.ts +47 -0
- package/src/init.ts +64 -64
- package/src/interactiveCLI.ts +767 -0
- package/src/main.ts +292 -83
- package/src/migrations/afterImportActions.ts +553 -490
- package/src/migrations/appwriteToX.ts +237 -174
- package/src/migrations/attributes.ts +483 -422
- package/src/migrations/backup.ts +205 -205
- package/src/migrations/collections.ts +545 -300
- package/src/migrations/converters.ts +161 -150
- package/src/migrations/dataLoader.ts +1615 -1304
- package/src/migrations/databases.ts +44 -25
- package/src/migrations/dbHelpers.ts +92 -92
- package/src/migrations/helper.ts +40 -0
- package/src/migrations/importController.ts +448 -384
- package/src/migrations/importDataActions.ts +315 -307
- package/src/migrations/indexes.ts +40 -37
- package/src/migrations/logging.ts +29 -16
- package/src/migrations/migrationHelper.ts +207 -201
- package/src/migrations/openapi.ts +83 -70
- package/src/migrations/queue.ts +118 -119
- package/src/migrations/relationships.ts +324 -324
- package/src/migrations/schemaStrings.ts +472 -460
- package/src/migrations/setupDatabase.ts +118 -219
- package/src/migrations/storage.ts +538 -358
- package/src/migrations/transfer.ts +608 -0
- package/src/migrations/users.ts +362 -285
- package/src/migrations/validationRules.ts +63 -63
- package/src/schemas/authUser.ts +23 -23
- package/src/setup.ts +8 -8
- package/src/storage/methods.ts +371 -0
- package/src/storage/schemas.ts +205 -0
- package/src/types.ts +9 -9
- package/src/utils/getClientFromConfig.ts +17 -0
- package/src/utils/helperFunctions.ts +181 -127
- package/src/utils/index.ts +2 -2
- package/src/utils/loadConfigs.ts +59 -59
- package/src/utils/retryFailedPromises.ts +27 -0
- package/src/utils/schemaStrings.ts +473 -0
- package/src/utils/setupFiles.ts +228 -182
- package/src/utilsController.ts +325 -194
- 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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
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
|
-
|
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
|
-
|
343
|
+
this.prepareUpdateData(db, collection, updateDef);
|
258
344
|
}
|
259
345
|
}
|
260
346
|
console.log("Running update references");
|
261
|
-
|
262
|
-
|
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
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
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
|
-
|
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
|
-
|
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
|
337
|
-
|
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
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
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
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
}
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
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
|
-
|
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
|
-
|
444
|
-
|
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
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
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
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
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
|
-
|
499
|
-
|
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
|
-
|
502
|
-
if (
|
503
|
-
|
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
|
-
|
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
|
510
|
-
if (email && email
|
511
|
-
|
512
|
-
|
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
|
-
|
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
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
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
|
-
|
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
|
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(
|
604
|
-
transformedItem
|
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
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
638
|
-
|
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 <
|
651
|
-
if (
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
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
|
-
|
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),
|
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
|
-
|
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
|
-
|
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 =
|
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
|
-
|
783
|
-
|
784
|
-
|
785
|
-
|
786
|
-
|
787
|
-
|
788
|
-
|
789
|
-
|
790
|
-
|
791
|
-
|
792
|
-
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
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
|
-
|
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
|
-
|
807
|
-
|
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
|
-
|
812
|
-
|
813
|
-
|
814
|
-
|
815
|
-
|
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 =
|
1053
|
+
let context = itemDataToUpdate.context;
|
821
1054
|
context = this.mergeObjects(context, transformedData);
|
822
1055
|
// Validate the item before proceeding
|
823
|
-
const isValid =
|
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
|
-
|
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,
|