appwrite-utils-cli 0.0.38 → 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/dist/migrations/converters.js +15 -1
- package/dist/migrations/dataLoader.d.ts +2 -2
- package/dist/migrations/dataLoader.js +207 -175
- package/dist/migrations/importDataActions.d.ts +8 -0
- package/dist/migrations/importDataActions.js +11 -0
- package/package.json +1 -1
- package/src/migrations/converters.ts +17 -1
- package/src/migrations/dataLoader.ts +280 -248
- package/src/migrations/importDataActions.ts +11 -0
|
@@ -94,7 +94,12 @@ export const convertObjectByAttributeMappings = (obj, attributeMappings) => {
|
|
|
94
94
|
const values = mapping.oldKeys
|
|
95
95
|
.map((oldKey) => resolveValue(obj, oldKey))
|
|
96
96
|
.flat(Infinity);
|
|
97
|
-
|
|
97
|
+
if (values.length > 0) {
|
|
98
|
+
result[mapping.targetKey] = values.filter((value) => value !== undefined);
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
result[mapping.targetKey] = null;
|
|
102
|
+
}
|
|
98
103
|
}
|
|
99
104
|
else if (mapping.oldKey) {
|
|
100
105
|
// Resolve single oldKey
|
|
@@ -104,6 +109,15 @@ export const convertObjectByAttributeMappings = (obj, attributeMappings) => {
|
|
|
104
109
|
? value.flat(Infinity)
|
|
105
110
|
: value;
|
|
106
111
|
}
|
|
112
|
+
else {
|
|
113
|
+
result[mapping.targetKey] = null;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// Ensure any keys in the original object that are not mapped are copied over
|
|
118
|
+
for (const key of Object.keys(obj)) {
|
|
119
|
+
if (!Object.keys(result).includes(key)) {
|
|
120
|
+
result[key] = obj[key];
|
|
107
121
|
}
|
|
108
122
|
}
|
|
109
123
|
return result;
|
|
@@ -1747,7 +1747,7 @@ export declare class DataLoader {
|
|
|
1747
1747
|
* @param collection - The collection configuration.
|
|
1748
1748
|
* @param importDef - The import definition containing the attribute mappings and other relevant info.
|
|
1749
1749
|
*/
|
|
1750
|
-
prepareUserCollectionCreateData(db: ConfigDatabase, collection: CollectionCreate, importDef: ImportDef): void
|
|
1750
|
+
prepareUserCollectionCreateData(db: ConfigDatabase, collection: CollectionCreate, importDef: ImportDef): Promise<void>;
|
|
1751
1751
|
/**
|
|
1752
1752
|
* Prepares the data for creating documents in a collection.
|
|
1753
1753
|
* This involves loading the data, transforming it, and handling ID mappings.
|
|
@@ -1756,7 +1756,7 @@ export declare class DataLoader {
|
|
|
1756
1756
|
* @param collection - The collection configuration.
|
|
1757
1757
|
* @param importDef - The import definition containing the attribute mappings and other relevant info.
|
|
1758
1758
|
*/
|
|
1759
|
-
prepareCreateData(db: ConfigDatabase, collection: CollectionCreate, importDef: ImportDef): void
|
|
1759
|
+
prepareCreateData(db: ConfigDatabase, collection: CollectionCreate, importDef: ImportDef): Promise<void>;
|
|
1760
1760
|
/**
|
|
1761
1761
|
* Prepares the data for updating documents within a collection.
|
|
1762
1762
|
* This method loads the raw data based on the import definition, transforms it according to the attribute mappings,
|
|
@@ -74,29 +74,51 @@ export class DataLoader {
|
|
|
74
74
|
mergeObjects(source, update) {
|
|
75
75
|
// Create a new object to hold the merged result
|
|
76
76
|
const result = { ...source };
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
result[key] = [...new Set([...sourceArray, ...updateValue])];
|
|
84
|
-
}
|
|
85
|
-
// If the update value is an object, recursively merge
|
|
86
|
-
else if (updateValue !== null &&
|
|
87
|
-
typeof updateValue === "object" &&
|
|
88
|
-
!(updateValue instanceof Date)) {
|
|
89
|
-
result[key] = this.mergeObjects(sourceValue, updateValue);
|
|
90
|
-
}
|
|
91
|
-
// If the update value is not nullish, overwrite the source value
|
|
92
|
-
else if (updateValue !== null && updateValue !== undefined) {
|
|
93
|
-
result[key] = updateValue;
|
|
94
|
-
}
|
|
95
|
-
// If the update value is nullish, keep the original value unless it doesn't exist
|
|
96
|
-
else if (sourceValue === undefined || sourceValue === null) {
|
|
97
|
-
result[key] = updateValue;
|
|
77
|
+
// Loop through the keys of the object we care about
|
|
78
|
+
for (const [key, value] of Object.entries(source)) {
|
|
79
|
+
// Check if the key exists in the target object
|
|
80
|
+
if (!Object.hasOwn(update, key)) {
|
|
81
|
+
// If the key doesn't exist, we can just skip it like bad cheese
|
|
82
|
+
continue;
|
|
98
83
|
}
|
|
99
|
-
|
|
84
|
+
if (update[key] === value) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
// If the value ain't here, we can just do whatever man
|
|
88
|
+
if (value === undefined || value === null || value === "") {
|
|
89
|
+
// If the update key is defined
|
|
90
|
+
if (update[key] !== undefined &&
|
|
91
|
+
update[key] !== null &&
|
|
92
|
+
update[key] !== "") {
|
|
93
|
+
// might as well use it eh?
|
|
94
|
+
result[key] = update[key];
|
|
95
|
+
}
|
|
96
|
+
// ELSE if the value is an array, because it would then not be === to those things above
|
|
97
|
+
}
|
|
98
|
+
else if (Array.isArray(value)) {
|
|
99
|
+
// Get the update value
|
|
100
|
+
const updateValue = update[key];
|
|
101
|
+
// If the update value is an array, concatenate and remove duplicates
|
|
102
|
+
// and poopy data
|
|
103
|
+
if (Array.isArray(updateValue)) {
|
|
104
|
+
result[key] = [...new Set([...value, ...updateValue])].filter((item) => item !== null && item !== undefined && item !== "");
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
// If the update value is not an array, just use it
|
|
108
|
+
result[key] = [...value, updateValue].filter((item) => item !== null && item !== undefined && item !== "");
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
else if (typeof value === "object") {
|
|
112
|
+
// If the value is an object, we need to merge it
|
|
113
|
+
if (typeof update[key] === "object") {
|
|
114
|
+
result[key] = this.mergeObjects(value, update[key]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
// Finally, the source value is defined, and not an array, so we don't care about the update value
|
|
119
|
+
continue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
100
122
|
return result;
|
|
101
123
|
}
|
|
102
124
|
// Method to load data from a file specified in the import definition
|
|
@@ -165,6 +187,9 @@ export class DataLoader {
|
|
|
165
187
|
transformData(item, attributeMappings) {
|
|
166
188
|
// Convert the item using the attribute mappings provided
|
|
167
189
|
const convertedItem = convertObjectByAttributeMappings(item, attributeMappings);
|
|
190
|
+
if (item["region"]) {
|
|
191
|
+
logger.info(`Converted item: ${JSON.stringify(convertedItem, null, 2)}`);
|
|
192
|
+
}
|
|
168
193
|
// Run additional converter functions on the converted item, if any
|
|
169
194
|
return this.importDataActions.runConverterFunctions(convertedItem, attributeMappings);
|
|
170
195
|
}
|
|
@@ -271,12 +296,12 @@ export class DataLoader {
|
|
|
271
296
|
for (const createDef of createDefs) {
|
|
272
297
|
if (!isUsersCollection) {
|
|
273
298
|
console.log(`${collection.name} is not users collection`);
|
|
274
|
-
this.prepareCreateData(db, collection, createDef);
|
|
299
|
+
await this.prepareCreateData(db, collection, createDef);
|
|
275
300
|
}
|
|
276
301
|
else {
|
|
277
302
|
// Special handling for users collection if needed
|
|
278
303
|
console.log(`${collection.name} is users collection`);
|
|
279
|
-
this.prepareUserCollectionCreateData(db, collection, createDef);
|
|
304
|
+
await this.prepareUserCollectionCreateData(db, collection, createDef);
|
|
280
305
|
}
|
|
281
306
|
}
|
|
282
307
|
for (const updateDef of updateDefs) {
|
|
@@ -285,7 +310,7 @@ export class DataLoader {
|
|
|
285
310
|
continue;
|
|
286
311
|
}
|
|
287
312
|
// Prepare the update data for the collection
|
|
288
|
-
this.prepareUpdateData(db, collection, updateDef);
|
|
313
|
+
await this.prepareUpdateData(db, collection, updateDef);
|
|
289
314
|
}
|
|
290
315
|
}
|
|
291
316
|
console.log("Running update references");
|
|
@@ -402,7 +427,9 @@ export class DataLoader {
|
|
|
402
427
|
const targetFieldKey = idMapping.targetFieldToMatch || idMapping.targetField;
|
|
403
428
|
const valueToMatch = collectionData.data[i].context[idMapping.sourceField];
|
|
404
429
|
// Skip if value to match is missing or empty
|
|
405
|
-
if (!valueToMatch ||
|
|
430
|
+
if (!valueToMatch ||
|
|
431
|
+
_.isEmpty(valueToMatch) ||
|
|
432
|
+
valueToMatch === null)
|
|
406
433
|
continue;
|
|
407
434
|
const isFieldToSetArray = collectionConfig.attributes.find((attribute) => attribute.key === fieldToSetKey)?.array;
|
|
408
435
|
const targetCollectionData = this.importMap.get(targetCollectionKey);
|
|
@@ -620,7 +647,7 @@ export class DataLoader {
|
|
|
620
647
|
* @param collection - The collection configuration.
|
|
621
648
|
* @param importDef - The import definition containing the attribute mappings and other relevant info.
|
|
622
649
|
*/
|
|
623
|
-
prepareUserCollectionCreateData(db, collection, importDef) {
|
|
650
|
+
async prepareUserCollectionCreateData(db, collection, importDef) {
|
|
624
651
|
// Load the raw data based on the import definition
|
|
625
652
|
const rawData = this.loadData(importDef);
|
|
626
653
|
const operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
|
|
@@ -635,108 +662,107 @@ export class DataLoader {
|
|
|
635
662
|
if (!operationId) {
|
|
636
663
|
throw new Error(`No import operation found for collection ${collection.name}`);
|
|
637
664
|
}
|
|
638
|
-
updateOperation(this.database, operationId, {
|
|
665
|
+
await updateOperation(this.database, operationId, {
|
|
639
666
|
status: "ready",
|
|
640
667
|
total: rawData.length,
|
|
641
|
-
})
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
668
|
+
});
|
|
669
|
+
// Retrieve the current user data and the current collection data from the import map
|
|
670
|
+
const currentUserData = this.importMap.get(this.getCollectionKey("users"));
|
|
671
|
+
const currentData = this.importMap.get(this.getCollectionKey(collection.name));
|
|
672
|
+
// Log errors if the necessary data is not found in the import map
|
|
673
|
+
if (!currentUserData) {
|
|
674
|
+
logger.error(`No data found for collection ${"users"} for createDef but it says it's supposed to have one...`);
|
|
675
|
+
return;
|
|
676
|
+
}
|
|
677
|
+
else if (!currentData) {
|
|
678
|
+
logger.error(`No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`);
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
// Iterate through each item in the raw data
|
|
682
|
+
for (const item of rawData) {
|
|
683
|
+
// Prepare user data, check for duplicates, and remove user-specific keys
|
|
684
|
+
let { transformedItem, existingId, userData } = this.prepareUserData(item, importDef.attributeMappings, importDef.primaryKeyField, this.getTrueUniqueId(this.getCollectionKey("users")));
|
|
685
|
+
logger.info(`In create user -- transformedItem: ${JSON.stringify(transformedItem, null, 2)}`);
|
|
686
|
+
// Generate a new unique ID for the item or use existing ID
|
|
687
|
+
if (!existingId) {
|
|
688
|
+
// No existing user ID, generate a new unique ID
|
|
689
|
+
existingId = this.getTrueUniqueId(this.getCollectionKey("users"));
|
|
690
|
+
transformedItem.docId = existingId; // Assign the new ID to the transformed data's docId field
|
|
653
691
|
}
|
|
654
|
-
//
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
if (importDef.primaryKeyField) {
|
|
671
|
-
const oldId = item[importDef.primaryKeyField];
|
|
672
|
-
// Check if the oldId already exists to handle potential duplicates
|
|
673
|
-
if (this.oldIdToNewIdPerCollectionMap
|
|
674
|
-
.get(this.getCollectionKey(collection.name))
|
|
675
|
-
?.has(`${oldId}`)) {
|
|
676
|
-
// Found a duplicate oldId, now decide how to merge or handle these duplicates
|
|
677
|
-
for (const data of currentData.data) {
|
|
678
|
-
if (data.finalData.docId === oldId ||
|
|
679
|
-
data.finalData.userId === oldId) {
|
|
680
|
-
transformedItem = this.mergeObjects(data.finalData, transformedItem);
|
|
681
|
-
}
|
|
692
|
+
// Create a context object for the item, including the new ID
|
|
693
|
+
let context = this.createContext(db, collection, item, existingId);
|
|
694
|
+
// Merge the transformed data into the context
|
|
695
|
+
context = { ...context, ...transformedItem, ...userData.finalData };
|
|
696
|
+
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
|
697
|
+
if (importDef.primaryKeyField) {
|
|
698
|
+
const oldId = item[importDef.primaryKeyField];
|
|
699
|
+
// Check if the oldId already exists to handle potential duplicates
|
|
700
|
+
if (this.oldIdToNewIdPerCollectionMap
|
|
701
|
+
.get(this.getCollectionKey(collection.name))
|
|
702
|
+
?.has(`${oldId}`)) {
|
|
703
|
+
// Found a duplicate oldId, now decide how to merge or handle these duplicates
|
|
704
|
+
for (const data of currentData.data) {
|
|
705
|
+
if (data.finalData.docId === oldId ||
|
|
706
|
+
data.finalData.userId === oldId) {
|
|
707
|
+
transformedItem = this.mergeObjects(data.finalData, transformedItem);
|
|
682
708
|
}
|
|
683
709
|
}
|
|
684
|
-
else {
|
|
685
|
-
// No duplicate found, simply map the oldId to the new itemId
|
|
686
|
-
collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
|
|
687
|
-
}
|
|
688
710
|
}
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
const samePhones = currentUserDataItem.finalData.phone &&
|
|
693
|
-
transformedItem.phone &&
|
|
694
|
-
currentUserDataItem.finalData.phone === transformedItem.phone;
|
|
695
|
-
const sameEmails = currentUserDataItem.finalData.email &&
|
|
696
|
-
transformedItem.email &&
|
|
697
|
-
currentUserDataItem.finalData.email === transformedItem.email;
|
|
698
|
-
if ((currentUserDataItem.finalData.docId === existingId ||
|
|
699
|
-
currentUserDataItem.finalData.userId === existingId) &&
|
|
700
|
-
(samePhones || sameEmails) &&
|
|
701
|
-
currentUserDataItem.finalData &&
|
|
702
|
-
userData.finalData) {
|
|
703
|
-
const userDataMerged = this.mergeObjects(currentUserData.data[i].finalData, userData.finalData);
|
|
704
|
-
currentUserData.data[i].finalData = userDataMerged;
|
|
705
|
-
this.importMap.set(this.getCollectionKey("users"), currentUserData);
|
|
706
|
-
}
|
|
711
|
+
else {
|
|
712
|
+
// No duplicate found, simply map the oldId to the new itemId
|
|
713
|
+
collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
|
|
707
714
|
}
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
const
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
715
|
+
}
|
|
716
|
+
// Handle merging for currentUserData
|
|
717
|
+
for (let i = 0; i < currentUserData.data.length; i++) {
|
|
718
|
+
const currentUserDataItem = currentUserData.data[i];
|
|
719
|
+
const samePhones = currentUserDataItem.finalData.phone &&
|
|
720
|
+
transformedItem.phone &&
|
|
721
|
+
currentUserDataItem.finalData.phone === transformedItem.phone;
|
|
722
|
+
const sameEmails = currentUserDataItem.finalData.email &&
|
|
723
|
+
transformedItem.email &&
|
|
724
|
+
currentUserDataItem.finalData.email === transformedItem.email;
|
|
725
|
+
if ((currentUserDataItem.finalData.docId === existingId ||
|
|
726
|
+
currentUserDataItem.finalData.userId === existingId) &&
|
|
727
|
+
(samePhones || sameEmails) &&
|
|
728
|
+
currentUserDataItem.finalData &&
|
|
729
|
+
userData.finalData) {
|
|
730
|
+
const userDataMerged = this.mergeObjects(currentUserData.data[i].finalData, userData.finalData);
|
|
731
|
+
currentUserData.data[i].finalData = userDataMerged;
|
|
732
|
+
this.importMap.set(this.getCollectionKey("users"), currentUserData);
|
|
726
733
|
}
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
734
|
+
}
|
|
735
|
+
// Update the attribute mappings with any actions that need to be performed post-import
|
|
736
|
+
const mappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, context, transformedItem);
|
|
737
|
+
// Update the import definition with the new attribute mappings
|
|
738
|
+
const newImportDef = {
|
|
739
|
+
...importDef,
|
|
740
|
+
attributeMappings: mappingsWithActions,
|
|
741
|
+
};
|
|
742
|
+
let foundData = false;
|
|
743
|
+
for (let i = 0; i < currentData.data.length; i++) {
|
|
744
|
+
if (currentData.data[i].finalData.docId === existingId ||
|
|
745
|
+
currentData.data[i].finalData.userId === existingId) {
|
|
746
|
+
currentData.data[i].finalData = this.mergeObjects(currentData.data[i].finalData, transformedItem);
|
|
747
|
+
currentData.data[i].context = context;
|
|
748
|
+
currentData.data[i].importDef = newImportDef;
|
|
735
749
|
this.importMap.set(this.getCollectionKey(collection.name), currentData);
|
|
736
750
|
this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
|
|
751
|
+
foundData = true;
|
|
737
752
|
}
|
|
738
753
|
}
|
|
739
|
-
|
|
754
|
+
if (!foundData) {
|
|
755
|
+
// Add new data to the associated collection
|
|
756
|
+
currentData.data.push({
|
|
757
|
+
rawData: item,
|
|
758
|
+
context: context,
|
|
759
|
+
importDef: newImportDef,
|
|
760
|
+
finalData: transformedItem,
|
|
761
|
+
});
|
|
762
|
+
this.importMap.set(this.getCollectionKey(collection.name), currentData);
|
|
763
|
+
this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
|
|
764
|
+
}
|
|
765
|
+
}
|
|
740
766
|
}
|
|
741
767
|
/**
|
|
742
768
|
* Prepares the data for creating documents in a collection.
|
|
@@ -746,75 +772,81 @@ export class DataLoader {
|
|
|
746
772
|
* @param collection - The collection configuration.
|
|
747
773
|
* @param importDef - The import definition containing the attribute mappings and other relevant info.
|
|
748
774
|
*/
|
|
749
|
-
prepareCreateData(db, collection, importDef) {
|
|
775
|
+
async prepareCreateData(db, collection, importDef) {
|
|
750
776
|
// Load the raw data based on the import definition
|
|
751
777
|
const rawData = this.loadData(importDef);
|
|
752
778
|
const operationId = this.collectionImportOperations.get(this.getCollectionKey(collection.name));
|
|
753
779
|
if (!operationId) {
|
|
754
780
|
throw new Error(`No import operation found for collection ${collection.name}`);
|
|
755
781
|
}
|
|
756
|
-
updateOperation(this.database, operationId, {
|
|
782
|
+
await updateOperation(this.database, operationId, {
|
|
757
783
|
status: "ready",
|
|
758
784
|
total: rawData.length,
|
|
759
|
-
})
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
const isValid = this.importDataActions.validateItem(transformedData, importDef.attributeMappings, context);
|
|
791
|
-
if (!isValid) {
|
|
792
|
-
continue;
|
|
793
|
-
}
|
|
794
|
-
// Update the attribute mappings with any actions that need to be performed post-import
|
|
795
|
-
const mappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, context, transformedData);
|
|
796
|
-
// Update the import definition with the new attribute mappings
|
|
797
|
-
const newImportDef = {
|
|
798
|
-
...importDef,
|
|
799
|
-
attributeMappings: mappingsWithActions,
|
|
800
|
-
};
|
|
801
|
-
// If the current collection data exists, add the item with its context and final data
|
|
802
|
-
if (currentData && currentData.data) {
|
|
803
|
-
currentData.data.push({
|
|
804
|
-
rawData: item,
|
|
805
|
-
context: context,
|
|
806
|
-
importDef: newImportDef,
|
|
807
|
-
finalData: transformedData,
|
|
808
|
-
});
|
|
809
|
-
this.importMap.set(this.getCollectionKey(collection.name), currentData);
|
|
810
|
-
this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
|
|
811
|
-
}
|
|
812
|
-
else {
|
|
813
|
-
logger.error(`No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`);
|
|
785
|
+
});
|
|
786
|
+
// Initialize a new map for old ID to new ID mappings
|
|
787
|
+
const oldIdToNewIdMapNew = new Map();
|
|
788
|
+
// Retrieve or initialize the collection-specific old ID to new ID map
|
|
789
|
+
const collectionOldIdToNewIdMap = this.oldIdToNewIdPerCollectionMap.get(this.getCollectionKey(collection.name)) ||
|
|
790
|
+
this.oldIdToNewIdPerCollectionMap
|
|
791
|
+
.set(this.getCollectionKey(collection.name), oldIdToNewIdMapNew)
|
|
792
|
+
.get(this.getCollectionKey(collection.name));
|
|
793
|
+
console.log(`${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`);
|
|
794
|
+
const isRegions = collection.name.toLowerCase() === "regions";
|
|
795
|
+
// Iterate through each item in the raw data
|
|
796
|
+
for (const item of rawData) {
|
|
797
|
+
// Generate a new unique ID for the item
|
|
798
|
+
const itemIdNew = this.getTrueUniqueId(this.getCollectionKey(collection.name));
|
|
799
|
+
if (isRegions) {
|
|
800
|
+
logger.info(`Creating region: ${JSON.stringify(item, null, 2)}`);
|
|
801
|
+
}
|
|
802
|
+
// Retrieve the current collection data from the import map
|
|
803
|
+
const currentData = this.importMap.get(this.getCollectionKey(collection.name));
|
|
804
|
+
// Create a context object for the item, including the new ID
|
|
805
|
+
let context = this.createContext(db, collection, item, itemIdNew);
|
|
806
|
+
// Transform the item data based on the attribute mappings
|
|
807
|
+
const transformedData = this.transformData(item, importDef.attributeMappings);
|
|
808
|
+
if (isRegions) {
|
|
809
|
+
logger.info(`Transformed region: ${JSON.stringify(transformedData, null, 2)}`);
|
|
810
|
+
}
|
|
811
|
+
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
|
812
|
+
if (importDef.primaryKeyField) {
|
|
813
|
+
const oldId = item[importDef.primaryKeyField];
|
|
814
|
+
if (collectionOldIdToNewIdMap?.has(`${oldId}`)) {
|
|
815
|
+
logger.error(`Collection ${collection.name} has multiple documents with the same primary key ${oldId}`);
|
|
814
816
|
continue;
|
|
815
817
|
}
|
|
818
|
+
collectionOldIdToNewIdMap?.set(`${oldId}`, `${itemIdNew}`);
|
|
816
819
|
}
|
|
817
|
-
|
|
820
|
+
// Merge the transformed data into the context
|
|
821
|
+
context = { ...context, ...transformedData };
|
|
822
|
+
// Validate the item before proceeding
|
|
823
|
+
const isValid = this.importDataActions.validateItem(transformedData, importDef.attributeMappings, context);
|
|
824
|
+
if (!isValid) {
|
|
825
|
+
continue;
|
|
826
|
+
}
|
|
827
|
+
// Update the attribute mappings with any actions that need to be performed post-import
|
|
828
|
+
const mappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, context, transformedData);
|
|
829
|
+
// Update the import definition with the new attribute mappings
|
|
830
|
+
const newImportDef = {
|
|
831
|
+
...importDef,
|
|
832
|
+
attributeMappings: mappingsWithActions,
|
|
833
|
+
};
|
|
834
|
+
// If the current collection data exists, add the item with its context and final data
|
|
835
|
+
if (currentData && currentData.data) {
|
|
836
|
+
currentData.data.push({
|
|
837
|
+
rawData: item,
|
|
838
|
+
context: context,
|
|
839
|
+
importDef: newImportDef,
|
|
840
|
+
finalData: transformedData,
|
|
841
|
+
});
|
|
842
|
+
this.importMap.set(this.getCollectionKey(collection.name), currentData);
|
|
843
|
+
this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
|
|
844
|
+
}
|
|
845
|
+
else {
|
|
846
|
+
logger.error(`No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`);
|
|
847
|
+
continue;
|
|
848
|
+
}
|
|
849
|
+
}
|
|
818
850
|
}
|
|
819
851
|
/**
|
|
820
852
|
* Prepares the data for updating documents within a collection.
|
|
@@ -11,10 +11,18 @@ export declare class ImportDataActions {
|
|
|
11
11
|
private validityRuleDefinitions;
|
|
12
12
|
private afterImportActionsDefinitions;
|
|
13
13
|
constructor(db: Databases, storage: Storage, config: AppwriteConfig, converterDefinitions: ConverterFunctions, validityRuleDefinitions: ValidationRules, afterImportActionsDefinitions: AfterImportActions);
|
|
14
|
+
/**
|
|
15
|
+
* Runs converter functions on the item based on the provided attribute mappings.
|
|
16
|
+
*
|
|
17
|
+
* @param item - The item to be transformed.
|
|
18
|
+
* @param attributeMappings - The mappings that define how each attribute should be transformed.
|
|
19
|
+
* @returns The transformed item.
|
|
20
|
+
*/
|
|
14
21
|
runConverterFunctions(item: any, attributeMappings: AttributeMappings): any;
|
|
15
22
|
/**
|
|
16
23
|
* Validates a single data item based on defined validation rules.
|
|
17
24
|
* @param item The data item to validate.
|
|
25
|
+
* @param attributeMap The attribute mappings for the data item.
|
|
18
26
|
* @param context The context for resolving templated parameters in validation rules.
|
|
19
27
|
* @returns A promise that resolves to true if the item is valid, false otherwise.
|
|
20
28
|
*/
|
|
@@ -21,9 +21,19 @@ export class ImportDataActions {
|
|
|
21
21
|
this.validityRuleDefinitions = validityRuleDefinitions;
|
|
22
22
|
this.afterImportActionsDefinitions = afterImportActionsDefinitions;
|
|
23
23
|
}
|
|
24
|
+
/**
|
|
25
|
+
* Runs converter functions on the item based on the provided attribute mappings.
|
|
26
|
+
*
|
|
27
|
+
* @param item - The item to be transformed.
|
|
28
|
+
* @param attributeMappings - The mappings that define how each attribute should be transformed.
|
|
29
|
+
* @returns The transformed item.
|
|
30
|
+
*/
|
|
24
31
|
runConverterFunctions(item, attributeMappings) {
|
|
25
32
|
const conversionSchema = attributeMappings.reduce((schema, mapping) => {
|
|
26
33
|
schema[mapping.targetKey] = (originalValue) => {
|
|
34
|
+
if (!mapping.converters) {
|
|
35
|
+
return originalValue;
|
|
36
|
+
}
|
|
27
37
|
return mapping.converters?.reduce((value, converterName) => {
|
|
28
38
|
let shouldProcessAsArray = false;
|
|
29
39
|
if ((converterName.includes("[Arr]") ||
|
|
@@ -66,6 +76,7 @@ export class ImportDataActions {
|
|
|
66
76
|
/**
|
|
67
77
|
* Validates a single data item based on defined validation rules.
|
|
68
78
|
* @param item The data item to validate.
|
|
79
|
+
* @param attributeMap The attribute mappings for the data item.
|
|
69
80
|
* @param context The context for resolving templated parameters in validation rules.
|
|
70
81
|
* @returns A promise that resolves to true if the item is valid, false otherwise.
|
|
71
82
|
*/
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appwrite-utils-cli",
|
|
3
3
|
"description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
|
|
4
|
-
"version": "0.0.
|
|
4
|
+
"version": "0.0.39",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -108,7 +108,13 @@ export const convertObjectByAttributeMappings = (
|
|
|
108
108
|
const values = mapping.oldKeys
|
|
109
109
|
.map((oldKey) => resolveValue(obj, oldKey))
|
|
110
110
|
.flat(Infinity);
|
|
111
|
-
|
|
111
|
+
if (values.length > 0) {
|
|
112
|
+
result[mapping.targetKey] = values.filter(
|
|
113
|
+
(value) => value !== undefined
|
|
114
|
+
);
|
|
115
|
+
} else {
|
|
116
|
+
result[mapping.targetKey] = null;
|
|
117
|
+
}
|
|
112
118
|
} else if (mapping.oldKey) {
|
|
113
119
|
// Resolve single oldKey
|
|
114
120
|
const value = resolveValue(obj, mapping.oldKey);
|
|
@@ -116,9 +122,19 @@ export const convertObjectByAttributeMappings = (
|
|
|
116
122
|
result[mapping.targetKey] = Array.isArray(value)
|
|
117
123
|
? value.flat(Infinity)
|
|
118
124
|
: value;
|
|
125
|
+
} else {
|
|
126
|
+
result[mapping.targetKey] = null;
|
|
119
127
|
}
|
|
120
128
|
}
|
|
121
129
|
}
|
|
130
|
+
|
|
131
|
+
// Ensure any keys in the original object that are not mapped are copied over
|
|
132
|
+
for (const key of Object.keys(obj)) {
|
|
133
|
+
if (!Object.keys(result).includes(key)) {
|
|
134
|
+
result[key] = obj[key];
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
122
138
|
return result;
|
|
123
139
|
};
|
|
124
140
|
|
|
@@ -103,32 +103,53 @@ export class DataLoader {
|
|
|
103
103
|
// Create a new object to hold the merged result
|
|
104
104
|
const result = { ...source };
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const sourceArray = Array.isArray(sourceValue) ? sourceValue : [];
|
|
113
|
-
result[key] = [...new Set([...sourceArray, ...updateValue])];
|
|
114
|
-
}
|
|
115
|
-
// If the update value is an object, recursively merge
|
|
116
|
-
else if (
|
|
117
|
-
updateValue !== null &&
|
|
118
|
-
typeof updateValue === "object" &&
|
|
119
|
-
!(updateValue instanceof Date)
|
|
120
|
-
) {
|
|
121
|
-
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;
|
|
122
112
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
result[key] = updateValue;
|
|
113
|
+
if (update[key] === value) {
|
|
114
|
+
continue;
|
|
126
115
|
}
|
|
127
|
-
// If the
|
|
128
|
-
|
|
129
|
-
|
|
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;
|
|
130
151
|
}
|
|
131
|
-
}
|
|
152
|
+
}
|
|
132
153
|
|
|
133
154
|
return result;
|
|
134
155
|
}
|
|
@@ -219,6 +240,9 @@ export class DataLoader {
|
|
|
219
240
|
item,
|
|
220
241
|
attributeMappings
|
|
221
242
|
);
|
|
243
|
+
if (item["region"]) {
|
|
244
|
+
logger.info(`Converted item: ${JSON.stringify(convertedItem, null, 2)}`);
|
|
245
|
+
}
|
|
222
246
|
// Run additional converter functions on the converted item, if any
|
|
223
247
|
return this.importDataActions.runConverterFunctions(
|
|
224
248
|
convertedItem,
|
|
@@ -346,11 +370,15 @@ export class DataLoader {
|
|
|
346
370
|
for (const createDef of createDefs) {
|
|
347
371
|
if (!isUsersCollection) {
|
|
348
372
|
console.log(`${collection.name} is not users collection`);
|
|
349
|
-
this.prepareCreateData(db, collection, createDef);
|
|
373
|
+
await this.prepareCreateData(db, collection, createDef);
|
|
350
374
|
} else {
|
|
351
375
|
// Special handling for users collection if needed
|
|
352
376
|
console.log(`${collection.name} is users collection`);
|
|
353
|
-
this.prepareUserCollectionCreateData(
|
|
377
|
+
await this.prepareUserCollectionCreateData(
|
|
378
|
+
db,
|
|
379
|
+
collection,
|
|
380
|
+
createDef
|
|
381
|
+
);
|
|
354
382
|
}
|
|
355
383
|
}
|
|
356
384
|
for (const updateDef of updateDefs) {
|
|
@@ -361,7 +389,7 @@ export class DataLoader {
|
|
|
361
389
|
continue;
|
|
362
390
|
}
|
|
363
391
|
// Prepare the update data for the collection
|
|
364
|
-
this.prepareUpdateData(db, collection, updateDef);
|
|
392
|
+
await this.prepareUpdateData(db, collection, updateDef);
|
|
365
393
|
}
|
|
366
394
|
}
|
|
367
395
|
console.log("Running update references");
|
|
@@ -531,7 +559,12 @@ export class DataLoader {
|
|
|
531
559
|
collectionData.data[i].context[idMapping.sourceField];
|
|
532
560
|
|
|
533
561
|
// Skip if value to match is missing or empty
|
|
534
|
-
if (
|
|
562
|
+
if (
|
|
563
|
+
!valueToMatch ||
|
|
564
|
+
_.isEmpty(valueToMatch) ||
|
|
565
|
+
valueToMatch === null
|
|
566
|
+
)
|
|
567
|
+
continue;
|
|
535
568
|
|
|
536
569
|
const isFieldToSetArray = collectionConfig.attributes.find(
|
|
537
570
|
(attribute) => attribute.key === fieldToSetKey
|
|
@@ -814,11 +847,11 @@ export class DataLoader {
|
|
|
814
847
|
* @param collection - The collection configuration.
|
|
815
848
|
* @param importDef - The import definition containing the attribute mappings and other relevant info.
|
|
816
849
|
*/
|
|
817
|
-
prepareUserCollectionCreateData(
|
|
850
|
+
async prepareUserCollectionCreateData(
|
|
818
851
|
db: ConfigDatabase,
|
|
819
852
|
collection: CollectionCreate,
|
|
820
853
|
importDef: ImportDef
|
|
821
|
-
): void {
|
|
854
|
+
): Promise<void> {
|
|
822
855
|
// Load the raw data based on the import definition
|
|
823
856
|
const rawData = this.loadData(importDef);
|
|
824
857
|
const operationId = this.collectionImportOperations.get(
|
|
@@ -842,157 +875,136 @@ export class DataLoader {
|
|
|
842
875
|
`No import operation found for collection ${collection.name}`
|
|
843
876
|
);
|
|
844
877
|
}
|
|
845
|
-
updateOperation(this.database, operationId, {
|
|
878
|
+
await updateOperation(this.database, operationId, {
|
|
846
879
|
status: "ready",
|
|
847
880
|
total: rawData.length,
|
|
848
|
-
})
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
881
|
+
});
|
|
882
|
+
// Retrieve the current user data and the current collection data from the import map
|
|
883
|
+
const currentUserData = this.importMap.get(this.getCollectionKey("users"));
|
|
884
|
+
const currentData = this.importMap.get(
|
|
885
|
+
this.getCollectionKey(collection.name)
|
|
886
|
+
);
|
|
887
|
+
// Log errors if the necessary data is not found in the import map
|
|
888
|
+
if (!currentUserData) {
|
|
889
|
+
logger.error(
|
|
890
|
+
`No data found for collection ${"users"} for createDef but it says it's supposed to have one...`
|
|
852
891
|
);
|
|
853
|
-
|
|
854
|
-
|
|
892
|
+
return;
|
|
893
|
+
} else if (!currentData) {
|
|
894
|
+
logger.error(
|
|
895
|
+
`No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`
|
|
896
|
+
);
|
|
897
|
+
return;
|
|
898
|
+
}
|
|
899
|
+
// Iterate through each item in the raw data
|
|
900
|
+
for (const item of rawData) {
|
|
901
|
+
// Prepare user data, check for duplicates, and remove user-specific keys
|
|
902
|
+
let { transformedItem, existingId, userData } = this.prepareUserData(
|
|
903
|
+
item,
|
|
904
|
+
importDef.attributeMappings,
|
|
905
|
+
importDef.primaryKeyField,
|
|
906
|
+
this.getTrueUniqueId(this.getCollectionKey("users"))
|
|
855
907
|
);
|
|
856
|
-
// Log errors if the necessary data is not found in the import map
|
|
857
|
-
if (!currentUserData) {
|
|
858
|
-
logger.error(
|
|
859
|
-
`No data found for collection ${"users"} for createDef but it says it's supposed to have one...`
|
|
860
|
-
);
|
|
861
|
-
return;
|
|
862
|
-
} else if (!currentData) {
|
|
863
|
-
logger.error(
|
|
864
|
-
`No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`
|
|
865
|
-
);
|
|
866
|
-
return;
|
|
867
|
-
}
|
|
868
|
-
// Iterate through each item in the raw data
|
|
869
|
-
for (const item of rawData) {
|
|
870
|
-
// Prepare user data, check for duplicates, and remove user-specific keys
|
|
871
|
-
let { transformedItem, existingId, userData } = this.prepareUserData(
|
|
872
|
-
item,
|
|
873
|
-
importDef.attributeMappings,
|
|
874
|
-
importDef.primaryKeyField,
|
|
875
|
-
this.getTrueUniqueId(this.getCollectionKey("users"))
|
|
876
|
-
);
|
|
877
908
|
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
909
|
+
logger.info(
|
|
910
|
+
`In create user -- transformedItem: ${JSON.stringify(
|
|
911
|
+
transformedItem,
|
|
912
|
+
null,
|
|
913
|
+
2
|
|
914
|
+
)}`
|
|
915
|
+
);
|
|
885
916
|
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
917
|
+
// Generate a new unique ID for the item or use existing ID
|
|
918
|
+
if (!existingId) {
|
|
919
|
+
// No existing user ID, generate a new unique ID
|
|
920
|
+
existingId = this.getTrueUniqueId(this.getCollectionKey("users"));
|
|
921
|
+
transformedItem.docId = existingId; // Assign the new ID to the transformed data's docId field
|
|
922
|
+
}
|
|
892
923
|
|
|
893
|
-
|
|
894
|
-
|
|
924
|
+
// Create a context object for the item, including the new ID
|
|
925
|
+
let context = this.createContext(db, collection, item, existingId);
|
|
895
926
|
|
|
896
|
-
|
|
897
|
-
|
|
927
|
+
// Merge the transformed data into the context
|
|
928
|
+
context = { ...context, ...transformedItem, ...userData.finalData };
|
|
898
929
|
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
930
|
+
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
|
931
|
+
if (importDef.primaryKeyField) {
|
|
932
|
+
const oldId = item[importDef.primaryKeyField];
|
|
902
933
|
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
}
|
|
934
|
+
// Check if the oldId already exists to handle potential duplicates
|
|
935
|
+
if (
|
|
936
|
+
this.oldIdToNewIdPerCollectionMap
|
|
937
|
+
.get(this.getCollectionKey(collection.name))
|
|
938
|
+
?.has(`${oldId}`)
|
|
939
|
+
) {
|
|
940
|
+
// Found a duplicate oldId, now decide how to merge or handle these duplicates
|
|
941
|
+
for (const data of currentData.data) {
|
|
942
|
+
if (
|
|
943
|
+
data.finalData.docId === oldId ||
|
|
944
|
+
data.finalData.userId === oldId
|
|
945
|
+
) {
|
|
946
|
+
transformedItem = this.mergeObjects(
|
|
947
|
+
data.finalData,
|
|
948
|
+
transformedItem
|
|
949
|
+
);
|
|
920
950
|
}
|
|
921
|
-
} else {
|
|
922
|
-
// No duplicate found, simply map the oldId to the new itemId
|
|
923
|
-
collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
|
|
924
951
|
}
|
|
952
|
+
} else {
|
|
953
|
+
// No duplicate found, simply map the oldId to the new itemId
|
|
954
|
+
collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
|
|
925
955
|
}
|
|
956
|
+
}
|
|
926
957
|
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
958
|
+
// Handle merging for currentUserData
|
|
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;
|
|
969
|
+
if (
|
|
970
|
+
(currentUserDataItem.finalData.docId === existingId ||
|
|
971
|
+
currentUserDataItem.finalData.userId === existingId) &&
|
|
972
|
+
(samePhones || sameEmails) &&
|
|
973
|
+
currentUserDataItem.finalData &&
|
|
974
|
+
userData.finalData
|
|
975
|
+
) {
|
|
976
|
+
const userDataMerged = this.mergeObjects(
|
|
977
|
+
currentUserData.data[i].finalData,
|
|
943
978
|
userData.finalData
|
|
944
|
-
)
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
userData.finalData
|
|
948
|
-
);
|
|
949
|
-
currentUserData.data[i].finalData = userDataMerged;
|
|
950
|
-
this.importMap.set(this.getCollectionKey("users"), currentUserData);
|
|
951
|
-
}
|
|
979
|
+
);
|
|
980
|
+
currentUserData.data[i].finalData = userDataMerged;
|
|
981
|
+
this.importMap.set(this.getCollectionKey("users"), currentUserData);
|
|
952
982
|
}
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
983
|
+
}
|
|
984
|
+
// Update the attribute mappings with any actions that need to be performed post-import
|
|
985
|
+
const mappingsWithActions = this.getAttributeMappingsWithActions(
|
|
986
|
+
importDef.attributeMappings,
|
|
987
|
+
context,
|
|
988
|
+
transformedItem
|
|
989
|
+
);
|
|
990
|
+
// Update the import definition with the new attribute mappings
|
|
991
|
+
const newImportDef = {
|
|
992
|
+
...importDef,
|
|
993
|
+
attributeMappings: mappingsWithActions,
|
|
994
|
+
};
|
|
964
995
|
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
this.importMap.set(
|
|
978
|
-
this.getCollectionKey(collection.name),
|
|
979
|
-
currentData
|
|
980
|
-
);
|
|
981
|
-
this.oldIdToNewIdPerCollectionMap.set(
|
|
982
|
-
this.getCollectionKey(collection.name),
|
|
983
|
-
collectionOldIdToNewIdMap!
|
|
984
|
-
);
|
|
985
|
-
foundData = true;
|
|
986
|
-
}
|
|
987
|
-
}
|
|
988
|
-
if (!foundData) {
|
|
989
|
-
// Add new data to the associated collection
|
|
990
|
-
currentData.data.push({
|
|
991
|
-
rawData: item,
|
|
992
|
-
context: context,
|
|
993
|
-
importDef: newImportDef,
|
|
994
|
-
finalData: transformedItem,
|
|
995
|
-
});
|
|
996
|
+
let foundData = false;
|
|
997
|
+
for (let i = 0; i < currentData.data.length; i++) {
|
|
998
|
+
if (
|
|
999
|
+
currentData.data[i].finalData.docId === existingId ||
|
|
1000
|
+
currentData.data[i].finalData.userId === existingId
|
|
1001
|
+
) {
|
|
1002
|
+
currentData.data[i].finalData = this.mergeObjects(
|
|
1003
|
+
currentData.data[i].finalData,
|
|
1004
|
+
transformedItem
|
|
1005
|
+
);
|
|
1006
|
+
currentData.data[i].context = context;
|
|
1007
|
+
currentData.data[i].importDef = newImportDef;
|
|
996
1008
|
this.importMap.set(
|
|
997
1009
|
this.getCollectionKey(collection.name),
|
|
998
1010
|
currentData
|
|
@@ -1001,9 +1013,24 @@ export class DataLoader {
|
|
|
1001
1013
|
this.getCollectionKey(collection.name),
|
|
1002
1014
|
collectionOldIdToNewIdMap!
|
|
1003
1015
|
);
|
|
1016
|
+
foundData = true;
|
|
1004
1017
|
}
|
|
1005
1018
|
}
|
|
1006
|
-
|
|
1019
|
+
if (!foundData) {
|
|
1020
|
+
// Add new data to the associated collection
|
|
1021
|
+
currentData.data.push({
|
|
1022
|
+
rawData: item,
|
|
1023
|
+
context: context,
|
|
1024
|
+
importDef: newImportDef,
|
|
1025
|
+
finalData: transformedItem,
|
|
1026
|
+
});
|
|
1027
|
+
this.importMap.set(this.getCollectionKey(collection.name), currentData);
|
|
1028
|
+
this.oldIdToNewIdPerCollectionMap.set(
|
|
1029
|
+
this.getCollectionKey(collection.name),
|
|
1030
|
+
collectionOldIdToNewIdMap!
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1007
1034
|
}
|
|
1008
1035
|
|
|
1009
1036
|
/**
|
|
@@ -1014,11 +1041,11 @@ export class DataLoader {
|
|
|
1014
1041
|
* @param collection - The collection configuration.
|
|
1015
1042
|
* @param importDef - The import definition containing the attribute mappings and other relevant info.
|
|
1016
1043
|
*/
|
|
1017
|
-
prepareCreateData(
|
|
1044
|
+
async prepareCreateData(
|
|
1018
1045
|
db: ConfigDatabase,
|
|
1019
1046
|
collection: CollectionCreate,
|
|
1020
1047
|
importDef: ImportDef
|
|
1021
|
-
): void {
|
|
1048
|
+
): Promise<void> {
|
|
1022
1049
|
// Load the raw data based on the import definition
|
|
1023
1050
|
const rawData = this.loadData(importDef);
|
|
1024
1051
|
const operationId = this.collectionImportOperations.get(
|
|
@@ -1029,97 +1056,102 @@ export class DataLoader {
|
|
|
1029
1056
|
`No import operation found for collection ${collection.name}`
|
|
1030
1057
|
);
|
|
1031
1058
|
}
|
|
1032
|
-
updateOperation(this.database, operationId, {
|
|
1059
|
+
await updateOperation(this.database, operationId, {
|
|
1033
1060
|
status: "ready",
|
|
1034
1061
|
total: rawData.length,
|
|
1035
|
-
})
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1062
|
+
});
|
|
1063
|
+
// Initialize a new map for old ID to new ID mappings
|
|
1064
|
+
const oldIdToNewIdMapNew = new Map<string, string>();
|
|
1065
|
+
// Retrieve or initialize the collection-specific old ID to new ID map
|
|
1066
|
+
const collectionOldIdToNewIdMap =
|
|
1067
|
+
this.oldIdToNewIdPerCollectionMap.get(
|
|
1068
|
+
this.getCollectionKey(collection.name)
|
|
1069
|
+
) ||
|
|
1070
|
+
this.oldIdToNewIdPerCollectionMap
|
|
1071
|
+
.set(this.getCollectionKey(collection.name), oldIdToNewIdMapNew)
|
|
1072
|
+
.get(this.getCollectionKey(collection.name));
|
|
1073
|
+
console.log(
|
|
1074
|
+
`${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`
|
|
1075
|
+
);
|
|
1076
|
+
const isRegions = collection.name.toLowerCase() === "regions";
|
|
1077
|
+
// Iterate through each item in the raw data
|
|
1078
|
+
for (const item of rawData) {
|
|
1079
|
+
// Generate a new unique ID for the item
|
|
1080
|
+
const itemIdNew = this.getTrueUniqueId(
|
|
1081
|
+
this.getCollectionKey(collection.name)
|
|
1048
1082
|
);
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
)
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
|
1067
|
-
if (importDef.primaryKeyField) {
|
|
1068
|
-
const oldId = item[importDef.primaryKeyField];
|
|
1069
|
-
if (collectionOldIdToNewIdMap?.has(`${oldId}`)) {
|
|
1070
|
-
logger.error(
|
|
1071
|
-
`Collection ${collection.name} has multiple documents with the same primary key ${oldId}`
|
|
1072
|
-
);
|
|
1073
|
-
continue;
|
|
1074
|
-
}
|
|
1075
|
-
collectionOldIdToNewIdMap?.set(`${oldId}`, `${itemIdNew}`);
|
|
1076
|
-
}
|
|
1077
|
-
// Merge the transformed data into the context
|
|
1078
|
-
context = { ...context, ...transformedData };
|
|
1079
|
-
// Validate the item before proceeding
|
|
1080
|
-
const isValid = this.importDataActions.validateItem(
|
|
1081
|
-
transformedData,
|
|
1082
|
-
importDef.attributeMappings,
|
|
1083
|
-
context
|
|
1084
|
-
);
|
|
1085
|
-
if (!isValid) {
|
|
1086
|
-
continue;
|
|
1087
|
-
}
|
|
1088
|
-
// Update the attribute mappings with any actions that need to be performed post-import
|
|
1089
|
-
const mappingsWithActions = this.getAttributeMappingsWithActions(
|
|
1090
|
-
importDef.attributeMappings,
|
|
1091
|
-
context,
|
|
1092
|
-
transformedData
|
|
1083
|
+
if (isRegions) {
|
|
1084
|
+
logger.info(`Creating region: ${JSON.stringify(item, null, 2)}`);
|
|
1085
|
+
}
|
|
1086
|
+
// Retrieve the current collection data from the import map
|
|
1087
|
+
const currentData = this.importMap.get(
|
|
1088
|
+
this.getCollectionKey(collection.name)
|
|
1089
|
+
);
|
|
1090
|
+
// Create a context object for the item, including the new ID
|
|
1091
|
+
let context = this.createContext(db, collection, item, itemIdNew);
|
|
1092
|
+
// Transform the item data based on the attribute mappings
|
|
1093
|
+
const transformedData = this.transformData(
|
|
1094
|
+
item,
|
|
1095
|
+
importDef.attributeMappings
|
|
1096
|
+
);
|
|
1097
|
+
if (isRegions) {
|
|
1098
|
+
logger.info(
|
|
1099
|
+
`Transformed region: ${JSON.stringify(transformedData, null, 2)}`
|
|
1093
1100
|
);
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
}
|
|
1099
|
-
// If the current collection data exists, add the item with its context and final data
|
|
1100
|
-
if (currentData && currentData.data) {
|
|
1101
|
-
currentData.data.push({
|
|
1102
|
-
rawData: item,
|
|
1103
|
-
context: context,
|
|
1104
|
-
importDef: newImportDef,
|
|
1105
|
-
finalData: transformedData,
|
|
1106
|
-
});
|
|
1107
|
-
this.importMap.set(
|
|
1108
|
-
this.getCollectionKey(collection.name),
|
|
1109
|
-
currentData
|
|
1110
|
-
);
|
|
1111
|
-
this.oldIdToNewIdPerCollectionMap.set(
|
|
1112
|
-
this.getCollectionKey(collection.name),
|
|
1113
|
-
collectionOldIdToNewIdMap!
|
|
1114
|
-
);
|
|
1115
|
-
} else {
|
|
1101
|
+
}
|
|
1102
|
+
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
|
1103
|
+
if (importDef.primaryKeyField) {
|
|
1104
|
+
const oldId = item[importDef.primaryKeyField];
|
|
1105
|
+
if (collectionOldIdToNewIdMap?.has(`${oldId}`)) {
|
|
1116
1106
|
logger.error(
|
|
1117
|
-
`
|
|
1107
|
+
`Collection ${collection.name} has multiple documents with the same primary key ${oldId}`
|
|
1118
1108
|
);
|
|
1119
1109
|
continue;
|
|
1120
1110
|
}
|
|
1111
|
+
collectionOldIdToNewIdMap?.set(`${oldId}`, `${itemIdNew}`);
|
|
1121
1112
|
}
|
|
1122
|
-
|
|
1113
|
+
// Merge the transformed data into the context
|
|
1114
|
+
context = { ...context, ...transformedData };
|
|
1115
|
+
// Validate the item before proceeding
|
|
1116
|
+
const isValid = this.importDataActions.validateItem(
|
|
1117
|
+
transformedData,
|
|
1118
|
+
importDef.attributeMappings,
|
|
1119
|
+
context
|
|
1120
|
+
);
|
|
1121
|
+
if (!isValid) {
|
|
1122
|
+
continue;
|
|
1123
|
+
}
|
|
1124
|
+
// Update the attribute mappings with any actions that need to be performed post-import
|
|
1125
|
+
const mappingsWithActions = this.getAttributeMappingsWithActions(
|
|
1126
|
+
importDef.attributeMappings,
|
|
1127
|
+
context,
|
|
1128
|
+
transformedData
|
|
1129
|
+
);
|
|
1130
|
+
// Update the import definition with the new attribute mappings
|
|
1131
|
+
const newImportDef = {
|
|
1132
|
+
...importDef,
|
|
1133
|
+
attributeMappings: mappingsWithActions,
|
|
1134
|
+
};
|
|
1135
|
+
// If the current collection data exists, add the item with its context and final data
|
|
1136
|
+
if (currentData && currentData.data) {
|
|
1137
|
+
currentData.data.push({
|
|
1138
|
+
rawData: item,
|
|
1139
|
+
context: context,
|
|
1140
|
+
importDef: newImportDef,
|
|
1141
|
+
finalData: transformedData,
|
|
1142
|
+
});
|
|
1143
|
+
this.importMap.set(this.getCollectionKey(collection.name), currentData);
|
|
1144
|
+
this.oldIdToNewIdPerCollectionMap.set(
|
|
1145
|
+
this.getCollectionKey(collection.name),
|
|
1146
|
+
collectionOldIdToNewIdMap!
|
|
1147
|
+
);
|
|
1148
|
+
} else {
|
|
1149
|
+
logger.error(
|
|
1150
|
+
`No data found for collection ${collection.name} for createDef but it says it's supposed to have one...`
|
|
1151
|
+
);
|
|
1152
|
+
continue;
|
|
1153
|
+
}
|
|
1154
|
+
}
|
|
1123
1155
|
}
|
|
1124
1156
|
|
|
1125
1157
|
/**
|
|
@@ -42,9 +42,19 @@ export class ImportDataActions {
|
|
|
42
42
|
this.afterImportActionsDefinitions = afterImportActionsDefinitions;
|
|
43
43
|
}
|
|
44
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
|
+
*/
|
|
45
52
|
runConverterFunctions(item: any, attributeMappings: AttributeMappings) {
|
|
46
53
|
const conversionSchema = attributeMappings.reduce((schema, mapping) => {
|
|
47
54
|
schema[mapping.targetKey] = (originalValue: any) => {
|
|
55
|
+
if (!mapping.converters) {
|
|
56
|
+
return originalValue;
|
|
57
|
+
}
|
|
48
58
|
return mapping.converters?.reduce((value, converterName) => {
|
|
49
59
|
let shouldProcessAsArray = false;
|
|
50
60
|
if (
|
|
@@ -95,6 +105,7 @@ export class ImportDataActions {
|
|
|
95
105
|
/**
|
|
96
106
|
* Validates a single data item based on defined validation rules.
|
|
97
107
|
* @param item The data item to validate.
|
|
108
|
+
* @param attributeMap The attribute mappings for the data item.
|
|
98
109
|
* @param context The context for resolving templated parameters in validation rules.
|
|
99
110
|
* @returns A promise that resolves to true if the item is valid, false otherwise.
|
|
100
111
|
*/
|