appwrite-utils-cli 0.0.62 → 0.0.64
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/dataLoader.d.ts +30 -0
- package/dist/migrations/dataLoader.js +56 -29
- package/dist/migrations/importController.js +1 -0
- package/dist/migrations/storage.js +6 -6
- package/package.json +1 -1
- package/src/migrations/dataLoader.ts +77 -40
- package/src/migrations/importController.ts +1 -0
- package/src/migrations/storage.ts +20 -10
@@ -623,6 +623,16 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
623
623
|
relatedCollection: string;
|
624
624
|
relationType: "oneToMany" | "manyToOne" | "oneToOne" | "manyToMany";
|
625
625
|
twoWay: boolean;
|
626
|
+
/**
|
627
|
+
* Generates attribute mappings with post-import actions based on the provided attribute mappings.
|
628
|
+
* This method checks each mapping for a fileData attribute and adds a post-import action to create a file
|
629
|
+
* and update the field with the file's ID if necessary.
|
630
|
+
*
|
631
|
+
* @param attributeMappings - The attribute mappings from the import definition.
|
632
|
+
* @param context - The context object containing information about the database, collection, and document.
|
633
|
+
* @param item - The item being imported, used for resolving template paths in fileData mappings.
|
634
|
+
* @returns The attribute mappings updated with any necessary post-import actions.
|
635
|
+
*/
|
626
636
|
twoWayKey: string;
|
627
637
|
onDelete: "setNull" | "cascade" | "restrict";
|
628
638
|
side: "parent" | "child";
|
@@ -1235,6 +1245,16 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1235
1245
|
relatedCollection: string;
|
1236
1246
|
relationType: "oneToMany" | "manyToOne" | "oneToOne" | "manyToMany";
|
1237
1247
|
twoWay: boolean;
|
1248
|
+
/**
|
1249
|
+
* Generates attribute mappings with post-import actions based on the provided attribute mappings.
|
1250
|
+
* This method checks each mapping for a fileData attribute and adds a post-import action to create a file
|
1251
|
+
* and update the field with the file's ID if necessary.
|
1252
|
+
*
|
1253
|
+
* @param attributeMappings - The attribute mappings from the import definition.
|
1254
|
+
* @param context - The context object containing information about the database, collection, and document.
|
1255
|
+
* @param item - The item being imported, used for resolving template paths in fileData mappings.
|
1256
|
+
* @returns The attribute mappings updated with any necessary post-import actions.
|
1257
|
+
*/
|
1238
1258
|
twoWayKey: string;
|
1239
1259
|
onDelete: "setNull" | "cascade" | "restrict";
|
1240
1260
|
side: "parent" | "child";
|
@@ -1635,6 +1655,16 @@ export declare class DataLoader {
|
|
1635
1655
|
relatedCollection: string;
|
1636
1656
|
relationType: "oneToMany" | "manyToOne" | "oneToOne" | "manyToMany";
|
1637
1657
|
twoWay: boolean;
|
1658
|
+
/**
|
1659
|
+
* Generates attribute mappings with post-import actions based on the provided attribute mappings.
|
1660
|
+
* This method checks each mapping for a fileData attribute and adds a post-import action to create a file
|
1661
|
+
* and update the field with the file's ID if necessary.
|
1662
|
+
*
|
1663
|
+
* @param attributeMappings - The attribute mappings from the import definition.
|
1664
|
+
* @param context - The context object containing information about the database, collection, and document.
|
1665
|
+
* @param item - The item being imported, used for resolving template paths in fileData mappings.
|
1666
|
+
* @returns The attribute mappings updated with any necessary post-import actions.
|
1667
|
+
*/
|
1638
1668
|
twoWayKey: string;
|
1639
1669
|
onDelete: "setNull" | "cascade" | "restrict";
|
1640
1670
|
side: "parent" | "child";
|
@@ -278,6 +278,11 @@ export class DataLoader {
|
|
278
278
|
userId: user.$id,
|
279
279
|
docId: user.$id,
|
280
280
|
},
|
281
|
+
context: {
|
282
|
+
...user,
|
283
|
+
userId: user.$id,
|
284
|
+
docId: user.$id,
|
285
|
+
},
|
281
286
|
rawData: user,
|
282
287
|
});
|
283
288
|
this.importMap.set(this.getCollectionKey("users"), importData);
|
@@ -291,7 +296,8 @@ export class DataLoader {
|
|
291
296
|
console.log("---------------------------------");
|
292
297
|
await this.setupMaps(dbId);
|
293
298
|
const allUsers = await this.getAllUsers();
|
294
|
-
console.log(`Fetched ${allUsers.length} users
|
299
|
+
console.log(`Fetched ${allUsers.length} users, waiting a few seconds to let the program catch up...`);
|
300
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
295
301
|
// Iterate over the configured databases to find the matching one
|
296
302
|
for (const db of this.config.databases) {
|
297
303
|
if (db.$id !== dbId) {
|
@@ -448,13 +454,6 @@ export class DataLoader {
|
|
448
454
|
targetValue !== undefined &&
|
449
455
|
targetValue !== null);
|
450
456
|
});
|
451
|
-
// Log and skip if no matching data found
|
452
|
-
if (!foundData.length) {
|
453
|
-
console.log(`No data found for collection ${collectionConfig.name}:\nTarget collection: ${targetCollectionKey}\nValue to match: ${valueToMatch}\nField to set: ${fieldToSetKey}\nTarget field to match: ${targetFieldKey}\nTarget field value: ${idMapping.targetField}`);
|
454
|
-
logger.info(`No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey} -- idMapping: ${JSON.stringify(idMapping, null, 2)}`);
|
455
|
-
continue;
|
456
|
-
}
|
457
|
-
needsUpdate = true;
|
458
457
|
const getCurrentDataFiltered = (currentData) => {
|
459
458
|
if (Array.isArray(currentData.finalData[fieldToSetKey])) {
|
460
459
|
return currentData.finalData[fieldToSetKey].filter((data) => `${data}` !== `${valueToMatch}`);
|
@@ -463,6 +462,13 @@ export class DataLoader {
|
|
463
462
|
};
|
464
463
|
// Get the current data to be updated
|
465
464
|
const currentDataFiltered = getCurrentDataFiltered(collectionData.data[i]);
|
465
|
+
// Log and skip if no matching data found
|
466
|
+
if (!foundData.length) {
|
467
|
+
console.log(`No data found for collection ${collectionConfig.name}:\nTarget collection: ${targetCollectionKey}\nValue to match: ${valueToMatch}\nField to set: ${fieldToSetKey}\nTarget field to match: ${targetFieldKey}\nTarget field value: ${idMapping.targetField}`);
|
468
|
+
logger.info(`No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey} -- idMapping: ${JSON.stringify(idMapping, null, 2)}`);
|
469
|
+
continue;
|
470
|
+
}
|
471
|
+
needsUpdate = true;
|
466
472
|
// Extract the new data to set
|
467
473
|
const newData = foundData.map((data) => {
|
468
474
|
const valueFound = this.getValueFromData(data.finalData, data.context, idMapping.targetField);
|
@@ -575,6 +581,11 @@ export class DataLoader {
|
|
575
581
|
* @returns The transformed item with user-specific keys removed.
|
576
582
|
*/
|
577
583
|
prepareUserData(item, attributeMappings, primaryKeyField, newId) {
|
584
|
+
if (this.userExistsMap.has(newId) ||
|
585
|
+
Array.from(this.emailToUserIdMap.values()).includes(newId) ||
|
586
|
+
Array.from(this.phoneToUserIdMap.values()).includes(newId)) {
|
587
|
+
newId = this.getTrueUniqueId(this.getCollectionKey("users"));
|
588
|
+
}
|
578
589
|
let transformedItem = this.transformData(item, attributeMappings);
|
579
590
|
const userData = AuthUserCreateSchema.safeParse(transformedItem);
|
580
591
|
if (!userData.success || !(userData.data.email && userData.data.phone)) {
|
@@ -728,9 +739,18 @@ export class DataLoader {
|
|
728
739
|
let { transformedItem, existingId, userData } = this.prepareUserData(item, importDef.attributeMappings, importDef.primaryKeyField, this.getTrueUniqueId(this.getCollectionKey("users")));
|
729
740
|
logger.info(`In create user -- transformedItem: ${JSON.stringify(transformedItem, null, 2)}`);
|
730
741
|
// Generate a new unique ID for the item or use existing ID
|
731
|
-
if (!existingId) {
|
742
|
+
if (!existingId && !userData.finalData?.userId) {
|
732
743
|
// No existing user ID, generate a new unique ID
|
733
|
-
existingId = this.getTrueUniqueId(this.getCollectionKey(
|
744
|
+
existingId = this.getTrueUniqueId(this.getCollectionKey(collection.name));
|
745
|
+
transformedItem = {
|
746
|
+
...transformedItem,
|
747
|
+
userId: existingId,
|
748
|
+
docId: existingId,
|
749
|
+
};
|
750
|
+
}
|
751
|
+
else if (!existingId && userData.finalData?.userId) {
|
752
|
+
// Existing user ID, use it as the new ID
|
753
|
+
existingId = userData.finalData.userId;
|
734
754
|
transformedItem = {
|
735
755
|
...transformedItem,
|
736
756
|
userId: existingId,
|
@@ -751,7 +771,9 @@ export class DataLoader {
|
|
751
771
|
// Found a duplicate oldId, now decide how to merge or handle these duplicates
|
752
772
|
for (const data of currentData.data) {
|
753
773
|
if (data.finalData.docId === oldId ||
|
754
|
-
data.finalData.userId === oldId
|
774
|
+
data.finalData.userId === oldId ||
|
775
|
+
data.context.docId === oldId ||
|
776
|
+
data.context.userId === oldId) {
|
755
777
|
transformedItem = this.mergeObjects(data.finalData, transformedItem);
|
756
778
|
}
|
757
779
|
}
|
@@ -787,30 +809,41 @@ export class DataLoader {
|
|
787
809
|
...importDef,
|
788
810
|
attributeMappings: mappingsWithActions,
|
789
811
|
};
|
812
|
+
const updatedData = this.importMap.get(this.getCollectionKey(collection.name));
|
790
813
|
let foundData = false;
|
791
|
-
for (let i = 0; i <
|
792
|
-
if (
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
814
|
+
for (let i = 0; i < updatedData.data.length; i++) {
|
815
|
+
if (updatedData.data[i].finalData.docId === existingId ||
|
816
|
+
updatedData.data[i].finalData.userId === existingId ||
|
817
|
+
updatedData.data[i].context.docId === existingId ||
|
818
|
+
updatedData.data[i].context.userId === existingId) {
|
819
|
+
updatedData.data[i].finalData = this.mergeObjects(updatedData.data[i].finalData, transformedItem);
|
820
|
+
updatedData.data[i].context = this.mergeObjects(updatedData.data[i].context, context);
|
821
|
+
const mergedImportDef = {
|
822
|
+
...updatedData.data[i].importDef,
|
823
|
+
idMappings: [
|
824
|
+
...(updatedData.data[i].importDef?.idMappings || []),
|
825
|
+
...(newImportDef.idMappings || []),
|
826
|
+
],
|
827
|
+
attributeMappings: [
|
828
|
+
...(updatedData.data[i].importDef?.attributeMappings || []),
|
829
|
+
...(newImportDef.attributeMappings || []),
|
830
|
+
],
|
798
831
|
};
|
799
|
-
|
800
|
-
this.importMap.set(this.getCollectionKey(collection.name),
|
832
|
+
updatedData.data[i].importDef = mergedImportDef;
|
833
|
+
this.importMap.set(this.getCollectionKey(collection.name), updatedData);
|
801
834
|
this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
|
802
835
|
foundData = true;
|
803
836
|
}
|
804
837
|
}
|
805
838
|
if (!foundData) {
|
806
839
|
// Add new data to the associated collection
|
807
|
-
|
840
|
+
updatedData.data.push({
|
808
841
|
rawData: item,
|
809
842
|
context: context,
|
810
843
|
importDef: newImportDef,
|
811
844
|
finalData: transformedItem,
|
812
845
|
});
|
813
|
-
this.importMap.set(this.getCollectionKey(collection.name),
|
846
|
+
this.importMap.set(this.getCollectionKey(collection.name), updatedData);
|
814
847
|
this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
|
815
848
|
}
|
816
849
|
}
|
@@ -854,13 +887,7 @@ export class DataLoader {
|
|
854
887
|
// Create a context object for the item, including the new ID
|
855
888
|
let context = this.createContext(db, collection, item, itemIdNew);
|
856
889
|
// Transform the item data based on the attribute mappings
|
857
|
-
|
858
|
-
if (collection.name.toLowerCase() === "councils") {
|
859
|
-
console.log("Transformed Council: ", transformedData);
|
860
|
-
}
|
861
|
-
if (isRegions) {
|
862
|
-
logger.info(`Transformed region: ${JSON.stringify(transformedData, null, 2)}`);
|
863
|
-
}
|
890
|
+
let transformedData = this.transformData(item, importDef.attributeMappings);
|
864
891
|
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
865
892
|
if (importDef.primaryKeyField) {
|
866
893
|
const oldId = item[importDef.primaryKeyField];
|
@@ -157,6 +157,7 @@ export class ImportController {
|
|
157
157
|
status: "in_progress",
|
158
158
|
});
|
159
159
|
const collectionData = dataLoader.importMap.get(dataLoader.getCollectionKey(collection.name));
|
160
|
+
console.log(`Processing collection: ${collection.name}...`);
|
160
161
|
if (!collectionData) {
|
161
162
|
console.log("No collection data for ", collection.name);
|
162
163
|
continue;
|
@@ -253,7 +253,7 @@ export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucke
|
|
253
253
|
let numberOfFiles = 0;
|
254
254
|
if (fromFiles.files.length < 100) {
|
255
255
|
for (const file of allFromFiles) {
|
256
|
-
const fileData = await storage.getFileDownload(file.bucketId, file.$id);
|
256
|
+
const fileData = await tryAwaitWithRetry(async () => await storage.getFileDownload(file.bucketId, file.$id));
|
257
257
|
const fileToCreate = InputFile.fromBuffer(Buffer.from(fileData), file.name);
|
258
258
|
console.log(`Creating file: ${file.name}`);
|
259
259
|
tryAwaitWithRetry(async () => await storage.createFile(toBucketId, file.$id, fileToCreate, file.$permissions));
|
@@ -263,10 +263,10 @@ export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucke
|
|
263
263
|
else {
|
264
264
|
lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
|
265
265
|
while (lastFileId) {
|
266
|
-
const files = await storage.listFiles(fromBucketId, [
|
266
|
+
const files = await tryAwaitWithRetry(async () => await storage.listFiles(fromBucketId, [
|
267
267
|
Query.limit(100),
|
268
268
|
Query.cursorAfter(lastFileId),
|
269
|
-
]);
|
269
|
+
]));
|
270
270
|
allFromFiles.push(...files.files);
|
271
271
|
if (files.files.length < 100) {
|
272
272
|
lastFileId = undefined;
|
@@ -276,7 +276,7 @@ export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucke
|
|
276
276
|
}
|
277
277
|
}
|
278
278
|
for (const file of allFromFiles) {
|
279
|
-
const fileData = await storage.getFileDownload(file.bucketId, file.$id);
|
279
|
+
const fileData = await tryAwaitWithRetry(async () => await storage.getFileDownload(file.bucketId, file.$id));
|
280
280
|
const fileToCreate = InputFile.fromBuffer(Buffer.from(fileData), file.name);
|
281
281
|
await tryAwaitWithRetry(async () => await storage.createFile(toBucketId, file.$id, fileToCreate, file.$permissions));
|
282
282
|
numberOfFiles++;
|
@@ -295,10 +295,10 @@ export const transferStorageLocalToRemote = async (localStorage, endpoint, proje
|
|
295
295
|
if (fromFiles.files.length === 100) {
|
296
296
|
lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
|
297
297
|
while (lastFileId) {
|
298
|
-
const files = await localStorage.listFiles(fromBucketId, [
|
298
|
+
const files = await tryAwaitWithRetry(async () => await localStorage.listFiles(fromBucketId, [
|
299
299
|
Query.limit(100),
|
300
300
|
Query.cursorAfter(lastFileId),
|
301
|
-
]);
|
301
|
+
]));
|
302
302
|
allFromFiles.push(...files.files);
|
303
303
|
if (files.files.length < 100) {
|
304
304
|
break;
|
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.64",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
@@ -348,6 +348,11 @@ export class DataLoader {
|
|
348
348
|
userId: user.$id,
|
349
349
|
docId: user.$id,
|
350
350
|
},
|
351
|
+
context: {
|
352
|
+
...user,
|
353
|
+
userId: user.$id,
|
354
|
+
docId: user.$id,
|
355
|
+
},
|
351
356
|
rawData: user,
|
352
357
|
});
|
353
358
|
this.importMap.set(this.getCollectionKey("users"), importData);
|
@@ -362,7 +367,10 @@ export class DataLoader {
|
|
362
367
|
console.log("---------------------------------");
|
363
368
|
await this.setupMaps(dbId);
|
364
369
|
const allUsers = await this.getAllUsers();
|
365
|
-
console.log(
|
370
|
+
console.log(
|
371
|
+
`Fetched ${allUsers.length} users, waiting a few seconds to let the program catch up...`
|
372
|
+
);
|
373
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
366
374
|
// Iterate over the configured databases to find the matching one
|
367
375
|
for (const db of this.config.databases) {
|
368
376
|
if (db.$id !== dbId) {
|
@@ -568,6 +576,20 @@ export class DataLoader {
|
|
568
576
|
);
|
569
577
|
}
|
570
578
|
);
|
579
|
+
|
580
|
+
const getCurrentDataFiltered = (currentData: any) => {
|
581
|
+
if (Array.isArray(currentData.finalData[fieldToSetKey])) {
|
582
|
+
return currentData.finalData[fieldToSetKey].filter(
|
583
|
+
(data: any) => `${data}` !== `${valueToMatch}`
|
584
|
+
);
|
585
|
+
}
|
586
|
+
return currentData.finalData[fieldToSetKey];
|
587
|
+
};
|
588
|
+
|
589
|
+
// Get the current data to be updated
|
590
|
+
const currentDataFiltered = getCurrentDataFiltered(
|
591
|
+
collectionData.data[i]
|
592
|
+
);
|
571
593
|
// Log and skip if no matching data found
|
572
594
|
if (!foundData.length) {
|
573
595
|
console.log(
|
@@ -585,20 +607,6 @@ export class DataLoader {
|
|
585
607
|
|
586
608
|
needsUpdate = true;
|
587
609
|
|
588
|
-
const getCurrentDataFiltered = (currentData: any) => {
|
589
|
-
if (Array.isArray(currentData.finalData[fieldToSetKey])) {
|
590
|
-
return currentData.finalData[fieldToSetKey].filter(
|
591
|
-
(data: any) => `${data}` !== `${valueToMatch}`
|
592
|
-
);
|
593
|
-
}
|
594
|
-
return currentData.finalData[fieldToSetKey];
|
595
|
-
};
|
596
|
-
|
597
|
-
// Get the current data to be updated
|
598
|
-
const currentDataFiltered = getCurrentDataFiltered(
|
599
|
-
collectionData.data[i]
|
600
|
-
);
|
601
|
-
|
602
610
|
// Extract the new data to set
|
603
611
|
const newData = foundData.map((data) => {
|
604
612
|
const valueFound = this.getValueFromData(
|
@@ -752,6 +760,13 @@ export class DataLoader {
|
|
752
760
|
finalData: z.infer<typeof AuthUserCreateSchema>;
|
753
761
|
};
|
754
762
|
} {
|
763
|
+
if (
|
764
|
+
this.userExistsMap.has(newId) ||
|
765
|
+
Array.from(this.emailToUserIdMap.values()).includes(newId) ||
|
766
|
+
Array.from(this.phoneToUserIdMap.values()).includes(newId)
|
767
|
+
) {
|
768
|
+
newId = this.getTrueUniqueId(this.getCollectionKey("users"));
|
769
|
+
}
|
755
770
|
let transformedItem = this.transformData(item, attributeMappings);
|
756
771
|
const userData = AuthUserCreateSchema.safeParse(transformedItem);
|
757
772
|
if (!userData.success || !(userData.data.email && userData.data.phone)) {
|
@@ -940,9 +955,19 @@ export class DataLoader {
|
|
940
955
|
);
|
941
956
|
|
942
957
|
// Generate a new unique ID for the item or use existing ID
|
943
|
-
if (!existingId) {
|
958
|
+
if (!existingId && !userData.finalData?.userId) {
|
944
959
|
// No existing user ID, generate a new unique ID
|
945
|
-
existingId = this.getTrueUniqueId(
|
960
|
+
existingId = this.getTrueUniqueId(
|
961
|
+
this.getCollectionKey(collection.name)
|
962
|
+
);
|
963
|
+
transformedItem = {
|
964
|
+
...transformedItem,
|
965
|
+
userId: existingId,
|
966
|
+
docId: existingId,
|
967
|
+
};
|
968
|
+
} else if (!existingId && userData.finalData?.userId) {
|
969
|
+
// Existing user ID, use it as the new ID
|
970
|
+
existingId = userData.finalData.userId;
|
946
971
|
transformedItem = {
|
947
972
|
...transformedItem,
|
948
973
|
userId: existingId,
|
@@ -951,7 +976,7 @@ export class DataLoader {
|
|
951
976
|
}
|
952
977
|
|
953
978
|
// Create a context object for the item, including the new ID
|
954
|
-
let context = this.createContext(db, collection, item, existingId);
|
979
|
+
let context = this.createContext(db, collection, item, existingId!);
|
955
980
|
|
956
981
|
// Merge the transformed data into the context
|
957
982
|
context = { ...context, ...transformedItem, ...userData.finalData };
|
@@ -970,7 +995,9 @@ export class DataLoader {
|
|
970
995
|
for (const data of currentData.data) {
|
971
996
|
if (
|
972
997
|
data.finalData.docId === oldId ||
|
973
|
-
data.finalData.userId === oldId
|
998
|
+
data.finalData.userId === oldId ||
|
999
|
+
data.context.docId === oldId ||
|
1000
|
+
data.context.userId === oldId
|
974
1001
|
) {
|
975
1002
|
transformedItem = this.mergeObjects(
|
976
1003
|
data.finalData,
|
@@ -1022,41 +1049,59 @@ export class DataLoader {
|
|
1022
1049
|
attributeMappings: mappingsWithActions,
|
1023
1050
|
};
|
1024
1051
|
|
1052
|
+
const updatedData = this.importMap.get(
|
1053
|
+
this.getCollectionKey(collection.name)
|
1054
|
+
)!;
|
1055
|
+
|
1025
1056
|
let foundData = false;
|
1026
|
-
for (let i = 0; i <
|
1057
|
+
for (let i = 0; i < updatedData.data.length; i++) {
|
1027
1058
|
if (
|
1028
|
-
|
1029
|
-
|
1059
|
+
updatedData.data[i].finalData.docId === existingId ||
|
1060
|
+
updatedData.data[i].finalData.userId === existingId ||
|
1061
|
+
updatedData.data[i].context.docId === existingId ||
|
1062
|
+
updatedData.data[i].context.userId === existingId
|
1030
1063
|
) {
|
1031
|
-
|
1032
|
-
|
1064
|
+
updatedData.data[i].finalData = this.mergeObjects(
|
1065
|
+
updatedData.data[i].finalData,
|
1033
1066
|
transformedItem
|
1034
1067
|
);
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1068
|
+
updatedData.data[i].context = this.mergeObjects(
|
1069
|
+
updatedData.data[i].context,
|
1070
|
+
context
|
1071
|
+
);
|
1072
|
+
const mergedImportDef = {
|
1073
|
+
...updatedData.data[i].importDef,
|
1074
|
+
idMappings: [
|
1075
|
+
...(updatedData.data[i].importDef?.idMappings || []),
|
1076
|
+
...(newImportDef.idMappings || []),
|
1077
|
+
],
|
1078
|
+
attributeMappings: [
|
1079
|
+
...(updatedData.data[i].importDef?.attributeMappings || []),
|
1080
|
+
...(newImportDef.attributeMappings || []),
|
1081
|
+
],
|
1038
1082
|
};
|
1039
|
-
|
1083
|
+
updatedData.data[i].importDef = mergedImportDef as ImportDef;
|
1040
1084
|
this.importMap.set(
|
1041
1085
|
this.getCollectionKey(collection.name),
|
1042
|
-
|
1086
|
+
updatedData
|
1043
1087
|
);
|
1044
1088
|
this.oldIdToNewIdPerCollectionMap.set(
|
1045
1089
|
this.getCollectionKey(collection.name),
|
1046
1090
|
collectionOldIdToNewIdMap!
|
1047
1091
|
);
|
1092
|
+
|
1048
1093
|
foundData = true;
|
1049
1094
|
}
|
1050
1095
|
}
|
1051
1096
|
if (!foundData) {
|
1052
1097
|
// Add new data to the associated collection
|
1053
|
-
|
1098
|
+
updatedData.data.push({
|
1054
1099
|
rawData: item,
|
1055
1100
|
context: context,
|
1056
1101
|
importDef: newImportDef,
|
1057
1102
|
finalData: transformedItem,
|
1058
1103
|
});
|
1059
|
-
this.importMap.set(this.getCollectionKey(collection.name),
|
1104
|
+
this.importMap.set(this.getCollectionKey(collection.name), updatedData);
|
1060
1105
|
this.oldIdToNewIdPerCollectionMap.set(
|
1061
1106
|
this.getCollectionKey(collection.name),
|
1062
1107
|
collectionOldIdToNewIdMap!
|
@@ -1119,18 +1164,10 @@ export class DataLoader {
|
|
1119
1164
|
// Create a context object for the item, including the new ID
|
1120
1165
|
let context = this.createContext(db, collection, item, itemIdNew);
|
1121
1166
|
// Transform the item data based on the attribute mappings
|
1122
|
-
|
1167
|
+
let transformedData = this.transformData(
|
1123
1168
|
item,
|
1124
1169
|
importDef.attributeMappings
|
1125
1170
|
);
|
1126
|
-
if (collection.name.toLowerCase() === "councils") {
|
1127
|
-
console.log("Transformed Council: ", transformedData);
|
1128
|
-
}
|
1129
|
-
if (isRegions) {
|
1130
|
-
logger.info(
|
1131
|
-
`Transformed region: ${JSON.stringify(transformedData, null, 2)}`
|
1132
|
-
);
|
1133
|
-
}
|
1134
1171
|
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
1135
1172
|
if (importDef.primaryKeyField) {
|
1136
1173
|
const oldId = item[importDef.primaryKeyField];
|
@@ -237,6 +237,7 @@ export class ImportController {
|
|
237
237
|
const collectionData = dataLoader.importMap.get(
|
238
238
|
dataLoader.getCollectionKey(collection.name)
|
239
239
|
);
|
240
|
+
console.log(`Processing collection: ${collection.name}...`);
|
240
241
|
if (!collectionData) {
|
241
242
|
console.log("No collection data for ", collection.name);
|
242
243
|
continue;
|
@@ -390,7 +390,9 @@ export const transferStorageLocalToLocal = async (
|
|
390
390
|
let numberOfFiles = 0;
|
391
391
|
if (fromFiles.files.length < 100) {
|
392
392
|
for (const file of allFromFiles) {
|
393
|
-
const fileData = await
|
393
|
+
const fileData = await tryAwaitWithRetry(
|
394
|
+
async () => await storage.getFileDownload(file.bucketId, file.$id)
|
395
|
+
);
|
394
396
|
const fileToCreate = InputFile.fromBuffer(
|
395
397
|
Buffer.from(fileData),
|
396
398
|
file.name
|
@@ -410,10 +412,13 @@ export const transferStorageLocalToLocal = async (
|
|
410
412
|
} else {
|
411
413
|
lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
|
412
414
|
while (lastFileId) {
|
413
|
-
const files = await
|
414
|
-
|
415
|
-
|
416
|
-
|
415
|
+
const files = await tryAwaitWithRetry(
|
416
|
+
async () =>
|
417
|
+
await storage.listFiles(fromBucketId, [
|
418
|
+
Query.limit(100),
|
419
|
+
Query.cursorAfter(lastFileId!),
|
420
|
+
])
|
421
|
+
);
|
417
422
|
allFromFiles.push(...files.files);
|
418
423
|
if (files.files.length < 100) {
|
419
424
|
lastFileId = undefined;
|
@@ -422,7 +427,9 @@ export const transferStorageLocalToLocal = async (
|
|
422
427
|
}
|
423
428
|
}
|
424
429
|
for (const file of allFromFiles) {
|
425
|
-
const fileData = await
|
430
|
+
const fileData = await tryAwaitWithRetry(
|
431
|
+
async () => await storage.getFileDownload(file.bucketId, file.$id)
|
432
|
+
);
|
426
433
|
const fileToCreate = InputFile.fromBuffer(
|
427
434
|
Buffer.from(fileData),
|
428
435
|
file.name
|
@@ -467,10 +474,13 @@ export const transferStorageLocalToRemote = async (
|
|
467
474
|
if (fromFiles.files.length === 100) {
|
468
475
|
lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
|
469
476
|
while (lastFileId) {
|
470
|
-
const files = await
|
471
|
-
|
472
|
-
|
473
|
-
|
477
|
+
const files = await tryAwaitWithRetry(
|
478
|
+
async () =>
|
479
|
+
await localStorage.listFiles(fromBucketId, [
|
480
|
+
Query.limit(100),
|
481
|
+
Query.cursorAfter(lastFileId!),
|
482
|
+
])
|
483
|
+
);
|
474
484
|
allFromFiles.push(...files.files);
|
475
485
|
if (files.files.length < 100) {
|
476
486
|
break;
|