appwrite-utils-cli 0.0.37 → 0.0.39
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 +1 -0
- package/dist/migrations/attributes.js +21 -20
- package/dist/migrations/collections.js +10 -13
- package/dist/migrations/converters.js +15 -1
- package/dist/migrations/dataLoader.d.ts +12 -21
- package/dist/migrations/dataLoader.js +168 -58
- package/dist/migrations/databases.js +2 -1
- package/dist/migrations/importController.js +6 -24
- package/dist/migrations/importDataActions.d.ts +9 -1
- package/dist/migrations/importDataActions.js +14 -2
- package/dist/migrations/indexes.js +2 -1
- package/dist/migrations/migrationHelper.js +2 -1
- package/dist/migrations/queue.js +3 -2
- package/dist/migrations/setupDatabase.js +10 -12
- package/dist/migrations/storage.js +14 -13
- package/dist/migrations/users.js +14 -36
- package/dist/utils/helperFunctions.d.ts +10 -1
- package/dist/utils/helperFunctions.js +27 -0
- package/dist/utilsController.js +2 -1
- package/package.json +2 -2
- package/src/migrations/attributes.ts +204 -143
- package/src/migrations/collections.ts +49 -33
- package/src/migrations/converters.ts +17 -1
- package/src/migrations/dataLoader.ts +232 -63
- package/src/migrations/databases.ts +4 -1
- package/src/migrations/importController.ts +13 -27
- package/src/migrations/importDataActions.ts +17 -3
- package/src/migrations/indexes.ts +4 -1
- package/src/migrations/migrationHelper.ts +9 -5
- package/src/migrations/queue.ts +5 -6
- package/src/migrations/setupDatabase.ts +35 -18
- package/src/migrations/storage.ts +50 -36
- package/src/migrations/users.ts +28 -38
- package/src/utils/helperFunctions.ts +33 -1
- package/src/utilsController.ts +3 -1
|
@@ -93,6 +93,7 @@ export class DataLoader {
|
|
|
93
93
|
* It iterates through the target object's keys and updates the source object if:
|
|
94
94
|
* - The source object has the key.
|
|
95
95
|
* - The target object's value for that key is not null, undefined, or an empty string.
|
|
96
|
+
* - If the target object has an array value, it concatenates the values and removes duplicates.
|
|
96
97
|
*
|
|
97
98
|
* @param source - The source object to be updated.
|
|
98
99
|
* @param target - The target object with values to update the source object.
|
|
@@ -102,32 +103,53 @@ export class DataLoader {
|
|
|
102
103
|
// Create a new object to hold the merged result
|
|
103
104
|
const result = { ...source };
|
|
104
105
|
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
const sourceArray = Array.isArray(sourceValue) ? sourceValue : [];
|
|
112
|
-
result[key] = [...new Set([...sourceArray, ...updateValue])];
|
|
113
|
-
}
|
|
114
|
-
// If the update value is an object, recursively merge
|
|
115
|
-
else if (
|
|
116
|
-
updateValue !== null &&
|
|
117
|
-
typeof updateValue === "object" &&
|
|
118
|
-
!(updateValue instanceof Date)
|
|
119
|
-
) {
|
|
120
|
-
result[key] = this.mergeObjects(sourceValue, updateValue);
|
|
106
|
+
// Loop through the keys of the object we care about
|
|
107
|
+
for (const [key, value] of Object.entries(source)) {
|
|
108
|
+
// Check if the key exists in the target object
|
|
109
|
+
if (!Object.hasOwn(update, key)) {
|
|
110
|
+
// If the key doesn't exist, we can just skip it like bad cheese
|
|
111
|
+
continue;
|
|
121
112
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
result[key] = updateValue;
|
|
113
|
+
if (update[key] === value) {
|
|
114
|
+
continue;
|
|
125
115
|
}
|
|
126
|
-
// If the
|
|
127
|
-
|
|
128
|
-
|
|
116
|
+
// If the value ain't here, we can just do whatever man
|
|
117
|
+
if (value === undefined || value === null || value === "") {
|
|
118
|
+
// If the update key is defined
|
|
119
|
+
if (
|
|
120
|
+
update[key] !== undefined &&
|
|
121
|
+
update[key] !== null &&
|
|
122
|
+
update[key] !== ""
|
|
123
|
+
) {
|
|
124
|
+
// might as well use it eh?
|
|
125
|
+
result[key] = update[key];
|
|
126
|
+
}
|
|
127
|
+
// ELSE if the value is an array, because it would then not be === to those things above
|
|
128
|
+
} else if (Array.isArray(value)) {
|
|
129
|
+
// Get the update value
|
|
130
|
+
const updateValue = update[key];
|
|
131
|
+
// If the update value is an array, concatenate and remove duplicates
|
|
132
|
+
// and poopy data
|
|
133
|
+
if (Array.isArray(updateValue)) {
|
|
134
|
+
result[key] = [...new Set([...value, ...updateValue])].filter(
|
|
135
|
+
(item) => item !== null && item !== undefined && item !== ""
|
|
136
|
+
);
|
|
137
|
+
} else {
|
|
138
|
+
// If the update value is not an array, just use it
|
|
139
|
+
result[key] = [...value, updateValue].filter(
|
|
140
|
+
(item) => item !== null && item !== undefined && item !== ""
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
} else if (typeof value === "object") {
|
|
144
|
+
// If the value is an object, we need to merge it
|
|
145
|
+
if (typeof update[key] === "object") {
|
|
146
|
+
result[key] = this.mergeObjects(value, update[key]);
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
// Finally, the source value is defined, and not an array, so we don't care about the update value
|
|
150
|
+
continue;
|
|
129
151
|
}
|
|
130
|
-
}
|
|
152
|
+
}
|
|
131
153
|
|
|
132
154
|
return result;
|
|
133
155
|
}
|
|
@@ -162,8 +184,26 @@ export class DataLoader {
|
|
|
162
184
|
// Method to generate a unique ID that doesn't conflict with existing IDs
|
|
163
185
|
getTrueUniqueId(collectionName: string) {
|
|
164
186
|
let newId = ID.unique();
|
|
165
|
-
|
|
187
|
+
let condition =
|
|
188
|
+
this.checkMapValuesForId(newId, collectionName) ||
|
|
189
|
+
this.userExistsMap.has(newId) ||
|
|
190
|
+
this.importMap
|
|
191
|
+
.get(this.getCollectionKey("users"))
|
|
192
|
+
?.data.some(
|
|
193
|
+
(user) =>
|
|
194
|
+
user.finalData.docId === newId || user.finalData.userId === newId
|
|
195
|
+
);
|
|
196
|
+
while (condition) {
|
|
166
197
|
newId = ID.unique();
|
|
198
|
+
condition =
|
|
199
|
+
this.checkMapValuesForId(newId, collectionName) ||
|
|
200
|
+
this.userExistsMap.has(newId) ||
|
|
201
|
+
this.importMap
|
|
202
|
+
.get(this.getCollectionKey("users"))
|
|
203
|
+
?.data.some(
|
|
204
|
+
(user) =>
|
|
205
|
+
user.finalData.docId === newId || user.finalData.userId === newId
|
|
206
|
+
);
|
|
167
207
|
}
|
|
168
208
|
return newId;
|
|
169
209
|
}
|
|
@@ -200,6 +240,9 @@ export class DataLoader {
|
|
|
200
240
|
item,
|
|
201
241
|
attributeMappings
|
|
202
242
|
);
|
|
243
|
+
if (item["region"]) {
|
|
244
|
+
logger.info(`Converted item: ${JSON.stringify(convertedItem, null, 2)}`);
|
|
245
|
+
}
|
|
203
246
|
// Run additional converter functions on the converted item, if any
|
|
204
247
|
return this.importDataActions.runConverterFunctions(
|
|
205
248
|
convertedItem,
|
|
@@ -271,6 +314,21 @@ export class DataLoader {
|
|
|
271
314
|
this.phoneToUserIdMap.set(user.phone, user.$id);
|
|
272
315
|
}
|
|
273
316
|
this.userExistsMap.set(user.$id, true);
|
|
317
|
+
let importData = this.importMap.get(this.getCollectionKey("users"));
|
|
318
|
+
if (!importData) {
|
|
319
|
+
importData = {
|
|
320
|
+
data: [],
|
|
321
|
+
};
|
|
322
|
+
}
|
|
323
|
+
importData.data.push({
|
|
324
|
+
finalData: {
|
|
325
|
+
...user,
|
|
326
|
+
userId: user.$id,
|
|
327
|
+
docId: user.$id,
|
|
328
|
+
},
|
|
329
|
+
rawData: user,
|
|
330
|
+
});
|
|
331
|
+
this.importMap.set(this.getCollectionKey("users"), importData);
|
|
274
332
|
}
|
|
275
333
|
return allUsers;
|
|
276
334
|
}
|
|
@@ -335,7 +393,7 @@ export class DataLoader {
|
|
|
335
393
|
}
|
|
336
394
|
}
|
|
337
395
|
console.log("Running update references");
|
|
338
|
-
this.dealWithMergedUsers();
|
|
396
|
+
// this.dealWithMergedUsers();
|
|
339
397
|
this.updateOldReferencesForNew();
|
|
340
398
|
console.log("Done running update references");
|
|
341
399
|
}
|
|
@@ -355,9 +413,15 @@ export class DataLoader {
|
|
|
355
413
|
this.config.usersCollectionName
|
|
356
414
|
);
|
|
357
415
|
const usersCollectionPrimaryKeyFields = new Set();
|
|
416
|
+
|
|
358
417
|
if (!this.config.collections) {
|
|
418
|
+
console.log("No collections found in configuration.");
|
|
359
419
|
return;
|
|
360
420
|
}
|
|
421
|
+
|
|
422
|
+
let needsUpdate = false;
|
|
423
|
+
let numUpdates = 0;
|
|
424
|
+
|
|
361
425
|
// Collect primary key fields from the users collection definitions
|
|
362
426
|
this.config.collections.forEach((collection) => {
|
|
363
427
|
if (this.getCollectionKey(collection.name) === usersCollectionKey) {
|
|
@@ -373,45 +437,95 @@ export class DataLoader {
|
|
|
373
437
|
}
|
|
374
438
|
});
|
|
375
439
|
|
|
440
|
+
console.log(
|
|
441
|
+
`Primary key fields collected for users collection: ${[
|
|
442
|
+
...usersCollectionPrimaryKeyFields,
|
|
443
|
+
]}`
|
|
444
|
+
);
|
|
445
|
+
|
|
376
446
|
// Iterate over all collections to update references based on merged users
|
|
377
447
|
this.config.collections.forEach((collection) => {
|
|
378
448
|
const collectionData = this.importMap.get(
|
|
379
449
|
this.getCollectionKey(collection.name)
|
|
380
450
|
);
|
|
381
|
-
|
|
451
|
+
|
|
452
|
+
if (!collectionData || !collectionData.data) {
|
|
453
|
+
console.log(`No data found for collection ${collection.name}`);
|
|
454
|
+
return;
|
|
455
|
+
}
|
|
456
|
+
|
|
382
457
|
const collectionImportDefs = collection.importDefs;
|
|
383
458
|
if (!collectionImportDefs || !collectionImportDefs.length) {
|
|
459
|
+
console.log(
|
|
460
|
+
`No import definitions found for collection ${collection.name}`
|
|
461
|
+
);
|
|
384
462
|
return;
|
|
385
463
|
}
|
|
464
|
+
|
|
386
465
|
collectionImportDefs.forEach((importDef) => {
|
|
387
466
|
importDef.idMappings?.forEach((idMapping) => {
|
|
388
467
|
if (
|
|
389
468
|
this.getCollectionKey(idMapping.targetCollection) ===
|
|
390
469
|
usersCollectionKey
|
|
391
470
|
) {
|
|
471
|
+
const fieldToSetKey = idMapping.fieldToSet || idMapping.sourceField;
|
|
392
472
|
const targetFieldKey =
|
|
393
473
|
idMapping.targetFieldToMatch || idMapping.targetField;
|
|
474
|
+
|
|
394
475
|
if (usersCollectionPrimaryKeyFields.has(targetFieldKey)) {
|
|
476
|
+
console.log(
|
|
477
|
+
`Processing collection ${collection.name} with target field ${targetFieldKey}`
|
|
478
|
+
);
|
|
479
|
+
|
|
395
480
|
// Process each item in the collection
|
|
396
481
|
collectionData.data.forEach((item) => {
|
|
397
|
-
const oldId =
|
|
482
|
+
const oldId =
|
|
483
|
+
item.finalData[idMapping.sourceField] ||
|
|
484
|
+
item.context[idMapping.sourceField];
|
|
485
|
+
|
|
486
|
+
if (oldId === undefined || oldId === null) {
|
|
487
|
+
console.log(
|
|
488
|
+
`Skipping item with undefined or null oldId in collection ${collection.name}`
|
|
489
|
+
);
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
|
|
398
493
|
const newId = this.mergedUserMap.get(`${oldId}`);
|
|
399
494
|
|
|
400
495
|
if (newId) {
|
|
496
|
+
needsUpdate = true;
|
|
497
|
+
numUpdates++;
|
|
498
|
+
console.log(
|
|
499
|
+
`Updating old ID ${oldId} to new ID ${newId} in collection ${collection.name}`
|
|
500
|
+
);
|
|
501
|
+
|
|
401
502
|
// Update context to use new user ID
|
|
402
|
-
item.finalData[
|
|
403
|
-
|
|
404
|
-
|
|
503
|
+
item.finalData[fieldToSetKey] = newId;
|
|
504
|
+
item.context[fieldToSetKey] = newId;
|
|
505
|
+
} else {
|
|
506
|
+
console.log(
|
|
507
|
+
`No new ID found for old ID ${oldId} in mergedUserMap.`
|
|
508
|
+
);
|
|
405
509
|
}
|
|
406
510
|
});
|
|
407
511
|
}
|
|
408
512
|
}
|
|
409
513
|
});
|
|
410
514
|
});
|
|
515
|
+
|
|
516
|
+
if (needsUpdate) {
|
|
517
|
+
console.log(
|
|
518
|
+
`Updated ${numUpdates} references for collection ${collection.name}`
|
|
519
|
+
);
|
|
520
|
+
this.importMap.set(
|
|
521
|
+
this.getCollectionKey(collection.name),
|
|
522
|
+
collectionData
|
|
523
|
+
);
|
|
524
|
+
}
|
|
411
525
|
});
|
|
412
526
|
}
|
|
413
527
|
|
|
414
|
-
|
|
528
|
+
updateOldReferencesForNew() {
|
|
415
529
|
if (!this.config.collections) {
|
|
416
530
|
return;
|
|
417
531
|
}
|
|
@@ -445,7 +559,12 @@ export class DataLoader {
|
|
|
445
559
|
collectionData.data[i].context[idMapping.sourceField];
|
|
446
560
|
|
|
447
561
|
// Skip if value to match is missing or empty
|
|
448
|
-
if (
|
|
562
|
+
if (
|
|
563
|
+
!valueToMatch ||
|
|
564
|
+
_.isEmpty(valueToMatch) ||
|
|
565
|
+
valueToMatch === null
|
|
566
|
+
)
|
|
567
|
+
continue;
|
|
449
568
|
|
|
450
569
|
const isFieldToSetArray = collectionConfig.attributes.find(
|
|
451
570
|
(attribute) => attribute.key === fieldToSetKey
|
|
@@ -612,12 +731,19 @@ export class DataLoader {
|
|
|
612
731
|
* @param attributeMappings - The attribute mappings for the item.
|
|
613
732
|
* @returns The transformed item with user-specific keys removed.
|
|
614
733
|
*/
|
|
615
|
-
|
|
734
|
+
prepareUserData(
|
|
616
735
|
item: any,
|
|
617
736
|
attributeMappings: AttributeMappings,
|
|
618
737
|
primaryKeyField: string,
|
|
619
738
|
newId: string
|
|
620
|
-
):
|
|
739
|
+
): {
|
|
740
|
+
transformedItem: any;
|
|
741
|
+
existingId: string | undefined;
|
|
742
|
+
userData: {
|
|
743
|
+
rawData: any;
|
|
744
|
+
finalData: z.infer<typeof AuthUserCreateSchema>;
|
|
745
|
+
};
|
|
746
|
+
} {
|
|
621
747
|
let transformedItem = this.transformData(item, attributeMappings);
|
|
622
748
|
const userData = AuthUserCreateSchema.safeParse(transformedItem);
|
|
623
749
|
if (!userData.success) {
|
|
@@ -666,15 +792,24 @@ export class DataLoader {
|
|
|
666
792
|
});
|
|
667
793
|
if (userFound) {
|
|
668
794
|
userFound.finalData.userId = existingId;
|
|
795
|
+
userFound.finalData.docId = existingId;
|
|
796
|
+
this.userExistsMap.set(existingId, true);
|
|
669
797
|
}
|
|
670
|
-
|
|
798
|
+
|
|
799
|
+
const userKeys = ["email", "phone", "name", "labels", "prefs"];
|
|
800
|
+
userKeys.forEach((key) => {
|
|
801
|
+
if (transformedItem.hasOwnProperty(key)) {
|
|
802
|
+
delete transformedItem[key];
|
|
803
|
+
}
|
|
804
|
+
});
|
|
805
|
+
return {
|
|
671
806
|
transformedItem,
|
|
672
807
|
existingId,
|
|
673
|
-
{
|
|
808
|
+
userData: {
|
|
674
809
|
rawData: userFound?.rawData,
|
|
675
810
|
finalData: userFound?.finalData,
|
|
676
811
|
},
|
|
677
|
-
|
|
812
|
+
};
|
|
678
813
|
} else {
|
|
679
814
|
existingId = newId;
|
|
680
815
|
userData.data.userId = existingId;
|
|
@@ -696,7 +831,11 @@ export class DataLoader {
|
|
|
696
831
|
data: [...(usersMap?.data || []), userDataToAdd],
|
|
697
832
|
});
|
|
698
833
|
|
|
699
|
-
return
|
|
834
|
+
return {
|
|
835
|
+
transformedItem,
|
|
836
|
+
existingId,
|
|
837
|
+
userData: userDataToAdd,
|
|
838
|
+
};
|
|
700
839
|
}
|
|
701
840
|
|
|
702
841
|
/**
|
|
@@ -760,7 +899,7 @@ export class DataLoader {
|
|
|
760
899
|
// Iterate through each item in the raw data
|
|
761
900
|
for (const item of rawData) {
|
|
762
901
|
// Prepare user data, check for duplicates, and remove user-specific keys
|
|
763
|
-
let
|
|
902
|
+
let { transformedItem, existingId, userData } = this.prepareUserData(
|
|
764
903
|
item,
|
|
765
904
|
importDef.attributeMappings,
|
|
766
905
|
importDef.primaryKeyField,
|
|
@@ -815,21 +954,30 @@ export class DataLoader {
|
|
|
815
954
|
collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
|
|
816
955
|
}
|
|
817
956
|
}
|
|
818
|
-
// Merge the final user data into the context
|
|
819
|
-
context = { ...context, ...userData.finalData };
|
|
820
957
|
|
|
821
958
|
// Handle merging for currentUserData
|
|
822
959
|
for (let i = 0; i < currentUserData.data.length; i++) {
|
|
960
|
+
const currentUserDataItem = currentUserData.data[i];
|
|
961
|
+
const samePhones =
|
|
962
|
+
currentUserDataItem.finalData.phone &&
|
|
963
|
+
transformedItem.phone &&
|
|
964
|
+
currentUserDataItem.finalData.phone === transformedItem.phone;
|
|
965
|
+
const sameEmails =
|
|
966
|
+
currentUserDataItem.finalData.email &&
|
|
967
|
+
transformedItem.email &&
|
|
968
|
+
currentUserDataItem.finalData.email === transformedItem.email;
|
|
823
969
|
if (
|
|
824
|
-
(
|
|
825
|
-
|
|
826
|
-
|
|
970
|
+
(currentUserDataItem.finalData.docId === existingId ||
|
|
971
|
+
currentUserDataItem.finalData.userId === existingId) &&
|
|
972
|
+
(samePhones || sameEmails) &&
|
|
973
|
+
currentUserDataItem.finalData &&
|
|
974
|
+
userData.finalData
|
|
827
975
|
) {
|
|
828
|
-
this.mergeObjects(
|
|
976
|
+
const userDataMerged = this.mergeObjects(
|
|
829
977
|
currentUserData.data[i].finalData,
|
|
830
978
|
userData.finalData
|
|
831
979
|
);
|
|
832
|
-
|
|
980
|
+
currentUserData.data[i].finalData = userDataMerged;
|
|
833
981
|
this.importMap.set(this.getCollectionKey("users"), currentUserData);
|
|
834
982
|
}
|
|
835
983
|
}
|
|
@@ -925,12 +1073,16 @@ export class DataLoader {
|
|
|
925
1073
|
console.log(
|
|
926
1074
|
`${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`
|
|
927
1075
|
);
|
|
1076
|
+
const isRegions = collection.name.toLowerCase() === "regions";
|
|
928
1077
|
// Iterate through each item in the raw data
|
|
929
1078
|
for (const item of rawData) {
|
|
930
1079
|
// Generate a new unique ID for the item
|
|
931
1080
|
const itemIdNew = this.getTrueUniqueId(
|
|
932
1081
|
this.getCollectionKey(collection.name)
|
|
933
1082
|
);
|
|
1083
|
+
if (isRegions) {
|
|
1084
|
+
logger.info(`Creating region: ${JSON.stringify(item, null, 2)}`);
|
|
1085
|
+
}
|
|
934
1086
|
// Retrieve the current collection data from the import map
|
|
935
1087
|
const currentData = this.importMap.get(
|
|
936
1088
|
this.getCollectionKey(collection.name)
|
|
@@ -942,6 +1094,11 @@ export class DataLoader {
|
|
|
942
1094
|
item,
|
|
943
1095
|
importDef.attributeMappings
|
|
944
1096
|
);
|
|
1097
|
+
if (isRegions) {
|
|
1098
|
+
logger.info(
|
|
1099
|
+
`Transformed region: ${JSON.stringify(transformedData, null, 2)}`
|
|
1100
|
+
);
|
|
1101
|
+
}
|
|
945
1102
|
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
|
946
1103
|
if (importDef.primaryKeyField) {
|
|
947
1104
|
const oldId = item[importDef.primaryKeyField];
|
|
@@ -956,7 +1113,7 @@ export class DataLoader {
|
|
|
956
1113
|
// Merge the transformed data into the context
|
|
957
1114
|
context = { ...context, ...transformedData };
|
|
958
1115
|
// Validate the item before proceeding
|
|
959
|
-
const isValid =
|
|
1116
|
+
const isValid = this.importDataActions.validateItem(
|
|
960
1117
|
transformedData,
|
|
961
1118
|
importDef.attributeMappings,
|
|
962
1119
|
context
|
|
@@ -996,6 +1153,7 @@ export class DataLoader {
|
|
|
996
1153
|
}
|
|
997
1154
|
}
|
|
998
1155
|
}
|
|
1156
|
+
|
|
999
1157
|
/**
|
|
1000
1158
|
* Prepares the data for updating documents within a collection.
|
|
1001
1159
|
* This method loads the raw data based on the import definition, transforms it according to the attribute mappings,
|
|
@@ -1006,7 +1164,7 @@ export class DataLoader {
|
|
|
1006
1164
|
* @param collection - The collection configuration.
|
|
1007
1165
|
* @param importDef - The import definition containing the attribute mappings and other relevant info.
|
|
1008
1166
|
*/
|
|
1009
|
-
|
|
1167
|
+
prepareUpdateData(
|
|
1010
1168
|
db: ConfigDatabase,
|
|
1011
1169
|
collection: CollectionCreate,
|
|
1012
1170
|
importDef: ImportDef
|
|
@@ -1047,11 +1205,7 @@ export class DataLoader {
|
|
|
1047
1205
|
let newId: string | undefined;
|
|
1048
1206
|
let oldId: string | undefined;
|
|
1049
1207
|
// Determine the new ID for the item based on the primary key field or update mapping
|
|
1050
|
-
|
|
1051
|
-
oldId = item[importDef.primaryKeyField];
|
|
1052
|
-
} else if (importDef.updateMapping) {
|
|
1053
|
-
oldId = item[importDef.updateMapping.originalIdField];
|
|
1054
|
-
}
|
|
1208
|
+
oldId = item[importDef.primaryKeyField];
|
|
1055
1209
|
if (oldId) {
|
|
1056
1210
|
newId = oldIdToNewIdMap?.get(`${oldId}`);
|
|
1057
1211
|
if (
|
|
@@ -1074,8 +1228,13 @@ export class DataLoader {
|
|
|
1074
1228
|
);
|
|
1075
1229
|
continue;
|
|
1076
1230
|
}
|
|
1231
|
+
const itemDataToUpdate = this.importMap
|
|
1232
|
+
.get(this.getCollectionKey(collection.name))
|
|
1233
|
+
?.data.find(
|
|
1234
|
+
(data) => `${data.context[importDef.primaryKeyField]}` === `${oldId}`
|
|
1235
|
+
);
|
|
1077
1236
|
// Log an error and continue to the next item if no new ID is found
|
|
1078
|
-
if (!newId) {
|
|
1237
|
+
if (!newId && !itemDataToUpdate) {
|
|
1079
1238
|
logger.error(
|
|
1080
1239
|
`No new id found for collection ${
|
|
1081
1240
|
collection.name
|
|
@@ -1086,15 +1245,24 @@ export class DataLoader {
|
|
|
1086
1245
|
)} but it says it's supposed to have one...`
|
|
1087
1246
|
);
|
|
1088
1247
|
continue;
|
|
1248
|
+
} else if (itemDataToUpdate) {
|
|
1249
|
+
newId = itemDataToUpdate.finalData.docId;
|
|
1250
|
+
if (!newId) {
|
|
1251
|
+
logger.error(
|
|
1252
|
+
`No new id found for collection ${
|
|
1253
|
+
collection.name
|
|
1254
|
+
} for updateDef ${JSON.stringify(
|
|
1255
|
+
item,
|
|
1256
|
+
null,
|
|
1257
|
+
2
|
|
1258
|
+
)} but it says it's supposed to have one...`
|
|
1259
|
+
);
|
|
1260
|
+
continue;
|
|
1261
|
+
}
|
|
1089
1262
|
}
|
|
1090
|
-
|
|
1091
|
-
.get(this.getCollectionKey(collection.name))
|
|
1092
|
-
?.data.find(
|
|
1093
|
-
(data) => data.rawData[importDef.primaryKeyField] === oldId
|
|
1094
|
-
);
|
|
1095
|
-
if (!itemDataToUpdate) {
|
|
1263
|
+
if (!itemDataToUpdate || !newId) {
|
|
1096
1264
|
logger.error(
|
|
1097
|
-
`No data found for collection ${
|
|
1265
|
+
`No data or ID (docId) found for collection ${
|
|
1098
1266
|
collection.name
|
|
1099
1267
|
} for updateDef ${JSON.stringify(
|
|
1100
1268
|
item,
|
|
@@ -1110,9 +1278,9 @@ export class DataLoader {
|
|
|
1110
1278
|
);
|
|
1111
1279
|
// Create a context object for the item, including the new ID and transformed data
|
|
1112
1280
|
let context = this.createContext(db, collection, item, newId);
|
|
1113
|
-
context =
|
|
1281
|
+
context = { ...context, ...transformedData };
|
|
1114
1282
|
// Validate the item before proceeding
|
|
1115
|
-
const isValid =
|
|
1283
|
+
const isValid = this.importDataActions.validateItem(
|
|
1116
1284
|
item,
|
|
1117
1285
|
importDef.attributeMappings,
|
|
1118
1286
|
context
|
|
@@ -1144,6 +1312,7 @@ export class DataLoader {
|
|
|
1144
1312
|
);
|
|
1145
1313
|
itemDataToUpdate.context = context;
|
|
1146
1314
|
itemDataToUpdate.importDef = newImportDef;
|
|
1315
|
+
currentData!.data.push(itemDataToUpdate);
|
|
1147
1316
|
} else {
|
|
1148
1317
|
// If no existing item matches, then add the new item
|
|
1149
1318
|
currentData!.data.push({
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
import { Databases, Query, type Models } from "node-appwrite";
|
|
2
|
+
import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
|
2
3
|
|
|
3
4
|
export const fetchAllDatabases = async (
|
|
4
5
|
database: Databases
|
|
5
6
|
): Promise<Models.Database[]> => {
|
|
6
|
-
const databases = await
|
|
7
|
+
const databases = await tryAwaitWithRetry(
|
|
8
|
+
async () => await database.list([Query.limit(25)])
|
|
9
|
+
);
|
|
7
10
|
const allDatabases = databases.databases;
|
|
8
11
|
let lastDatabaseId = allDatabases[allDatabases.length - 1].$id;
|
|
9
12
|
if (databases.databases.length < 25) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AppwriteException,
|
|
2
3
|
ID,
|
|
3
4
|
Query,
|
|
4
5
|
type Databases,
|
|
@@ -13,7 +14,7 @@ import type {
|
|
|
13
14
|
} from "appwrite-utils";
|
|
14
15
|
import type { ImportDataActions } from "./importDataActions.js";
|
|
15
16
|
import _ from "lodash";
|
|
16
|
-
import { areCollectionNamesSame } from "../utils/index.js";
|
|
17
|
+
import { areCollectionNamesSame, tryAwaitWithRetry } from "../utils/index.js";
|
|
17
18
|
import type { SetupOptions } from "../utilsController.js";
|
|
18
19
|
import { resolveAndUpdateRelationships } from "./relationships.js";
|
|
19
20
|
import { UsersController } from "./users.js";
|
|
@@ -119,7 +120,7 @@ export class ImportController {
|
|
|
119
120
|
dataLoader.getCollectionKey(collection.name)
|
|
120
121
|
);
|
|
121
122
|
const createBatches = (finalData: CollectionImportData["data"]) => {
|
|
122
|
-
let maxBatchLength =
|
|
123
|
+
let maxBatchLength = 25;
|
|
123
124
|
const finalBatches: CollectionImportData["data"][] = [];
|
|
124
125
|
for (let i = 0; i < finalData.length; i++) {
|
|
125
126
|
if (i % maxBatchLength === 0) {
|
|
@@ -161,7 +162,7 @@ export class ImportController {
|
|
|
161
162
|
.map((item) => {
|
|
162
163
|
return usersController.createUserAndReturn(item.finalData);
|
|
163
164
|
});
|
|
164
|
-
await Promise.
|
|
165
|
+
const promiseResults = await Promise.allSettled(userBatchPromises);
|
|
165
166
|
for (const item of batch) {
|
|
166
167
|
if (item && item.finalData) {
|
|
167
168
|
dataLoader.userExistsMap.set(
|
|
@@ -173,26 +174,8 @@ export class ImportController {
|
|
|
173
174
|
);
|
|
174
175
|
}
|
|
175
176
|
}
|
|
176
|
-
console.log("Finished importing users batch"
|
|
177
|
+
console.log("Finished importing users batch");
|
|
177
178
|
}
|
|
178
|
-
// for (let i = 0; i < usersData.length; i++) {
|
|
179
|
-
// const user = usersData[i];
|
|
180
|
-
// if (user.finalData) {
|
|
181
|
-
// const userId =
|
|
182
|
-
// user.finalData.userId ||
|
|
183
|
-
// user.context.userId ||
|
|
184
|
-
// user.context.docId;
|
|
185
|
-
// if (!dataLoader.userExistsMap.has(userId)) {
|
|
186
|
-
// if (!user.finalData.userId) {
|
|
187
|
-
// user.finalData.userId = userId;
|
|
188
|
-
// }
|
|
189
|
-
// await usersController.createUserAndReturn(user.finalData);
|
|
190
|
-
// dataLoader.userExistsMap.set(userId, true);
|
|
191
|
-
// } else {
|
|
192
|
-
// console.log("Skipped existing user: ", userId);
|
|
193
|
-
// }
|
|
194
|
-
// }
|
|
195
|
-
// }
|
|
196
179
|
console.log("Finished importing users");
|
|
197
180
|
}
|
|
198
181
|
}
|
|
@@ -238,11 +221,14 @@ export class ImportController {
|
|
|
238
221
|
if (!item.finalData) {
|
|
239
222
|
return Promise.resolve();
|
|
240
223
|
}
|
|
241
|
-
return
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
224
|
+
return tryAwaitWithRetry(
|
|
225
|
+
async () =>
|
|
226
|
+
await this.database.createDocument(
|
|
227
|
+
db.$id,
|
|
228
|
+
collection.$id,
|
|
229
|
+
id,
|
|
230
|
+
item.finalData
|
|
231
|
+
)
|
|
246
232
|
);
|
|
247
233
|
});
|
|
248
234
|
// Wait for all promises in the current batch to resolve
|
|
@@ -16,6 +16,7 @@ import { convertObjectBySchema } from "./converters.js";
|
|
|
16
16
|
import { type AfterImportActions } from "appwrite-utils";
|
|
17
17
|
import { afterImportActions } from "./afterImportActions.js";
|
|
18
18
|
import { logger } from "./logging.js";
|
|
19
|
+
import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
|
19
20
|
|
|
20
21
|
export class ImportDataActions {
|
|
21
22
|
private db: Databases;
|
|
@@ -41,9 +42,19 @@ export class ImportDataActions {
|
|
|
41
42
|
this.afterImportActionsDefinitions = afterImportActionsDefinitions;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Runs converter functions on the item based on the provided attribute mappings.
|
|
47
|
+
*
|
|
48
|
+
* @param item - The item to be transformed.
|
|
49
|
+
* @param attributeMappings - The mappings that define how each attribute should be transformed.
|
|
50
|
+
* @returns The transformed item.
|
|
51
|
+
*/
|
|
44
52
|
runConverterFunctions(item: any, attributeMappings: AttributeMappings) {
|
|
45
53
|
const conversionSchema = attributeMappings.reduce((schema, mapping) => {
|
|
46
54
|
schema[mapping.targetKey] = (originalValue: any) => {
|
|
55
|
+
if (!mapping.converters) {
|
|
56
|
+
return originalValue;
|
|
57
|
+
}
|
|
47
58
|
return mapping.converters?.reduce((value, converterName) => {
|
|
48
59
|
let shouldProcessAsArray = false;
|
|
49
60
|
if (
|
|
@@ -94,14 +105,15 @@ export class ImportDataActions {
|
|
|
94
105
|
/**
|
|
95
106
|
* Validates a single data item based on defined validation rules.
|
|
96
107
|
* @param item The data item to validate.
|
|
108
|
+
* @param attributeMap The attribute mappings for the data item.
|
|
97
109
|
* @param context The context for resolving templated parameters in validation rules.
|
|
98
110
|
* @returns A promise that resolves to true if the item is valid, false otherwise.
|
|
99
111
|
*/
|
|
100
|
-
|
|
112
|
+
validateItem(
|
|
101
113
|
item: any,
|
|
102
114
|
attributeMap: AttributeMappings,
|
|
103
115
|
context: { [key: string]: any }
|
|
104
|
-
):
|
|
116
|
+
): boolean {
|
|
105
117
|
for (const mapping of attributeMap) {
|
|
106
118
|
const { validationActions } = mapping;
|
|
107
119
|
if (
|
|
@@ -167,7 +179,9 @@ export class ImportDataActions {
|
|
|
167
179
|
}' with params ${params.join(", ")}...`
|
|
168
180
|
);
|
|
169
181
|
try {
|
|
170
|
-
await
|
|
182
|
+
await tryAwaitWithRetry(
|
|
183
|
+
async () => await this.executeAction(action, params, context, item)
|
|
184
|
+
);
|
|
171
185
|
} catch (error) {
|
|
172
186
|
logger.error(
|
|
173
187
|
`Failed to execute post-import action '${action}' for attribute '${mapping.targetKey}':`,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { indexSchema, type Index } from "appwrite-utils";
|
|
2
2
|
import { Databases, Query, type Models } from "node-appwrite";
|
|
3
|
+
import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
|
3
4
|
// import {}
|
|
4
5
|
|
|
5
6
|
export const createOrUpdateIndex = async (
|
|
@@ -32,6 +33,8 @@ export const createOrUpdateIndexes = async (
|
|
|
32
33
|
indexes: Index[]
|
|
33
34
|
) => {
|
|
34
35
|
for (const index of indexes) {
|
|
35
|
-
await
|
|
36
|
+
await tryAwaitWithRetry(
|
|
37
|
+
async () => await createOrUpdateIndex(dbId, db, collectionId, index)
|
|
38
|
+
);
|
|
36
39
|
}
|
|
37
40
|
};
|