appwrite-utils-cli 0.0.61 → 0.0.63
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/afterImportActions.js +23 -25
- package/dist/migrations/dataLoader.js +29 -23
- package/dist/migrations/importController.js +1 -0
- package/dist/migrations/storage.js +6 -6
- package/package.json +1 -1
- package/src/migrations/afterImportActions.ts +104 -59
- package/src/migrations/dataLoader.ts +44 -27
- package/src/migrations/importController.ts +1 -0
- package/src/migrations/storage.ts +20 -10
@@ -26,7 +26,7 @@ export const afterImportActions = {
|
|
26
26
|
updateCreatedDocument: async (config, dbId, collId, docId, data) => {
|
27
27
|
try {
|
28
28
|
const db = getDatabaseFromConfig(config);
|
29
|
-
await db.updateDocument(dbId, collId, docId, data);
|
29
|
+
await tryAwaitWithRetry(async () => await db.updateDocument(dbId, collId, docId, data));
|
30
30
|
}
|
31
31
|
catch (error) {
|
32
32
|
console.error("Error updating document: ", error);
|
@@ -35,11 +35,11 @@ export const afterImportActions = {
|
|
35
35
|
checkAndUpdateFieldInDocument: async (config, dbId, collId, docId, fieldName, oldFieldValue, newFieldValue) => {
|
36
36
|
try {
|
37
37
|
const db = getDatabaseFromConfig(config);
|
38
|
-
const doc = await db.getDocument(dbId, collId, docId);
|
38
|
+
const doc = await tryAwaitWithRetry(async () => await db.getDocument(dbId, collId, docId));
|
39
39
|
if (doc[fieldName] == oldFieldValue) {
|
40
|
-
await db.updateDocument(dbId, collId, docId, {
|
40
|
+
await tryAwaitWithRetry(async () => await db.updateDocument(dbId, collId, docId, {
|
41
41
|
[fieldName]: newFieldValue,
|
42
|
-
});
|
42
|
+
}));
|
43
43
|
}
|
44
44
|
}
|
45
45
|
catch (error) {
|
@@ -50,10 +50,10 @@ export const afterImportActions = {
|
|
50
50
|
const db = getDatabaseFromConfig(config);
|
51
51
|
// Helper function to find a collection ID by name or return the ID if given
|
52
52
|
const findCollectionId = async (collectionIdentifier) => {
|
53
|
-
const collectionsPulled = await db.listCollections(dbId, [
|
53
|
+
const collectionsPulled = await tryAwaitWithRetry(async () => await db.listCollections(dbId, [
|
54
54
|
Query.limit(25),
|
55
55
|
Query.equal("name", collectionIdentifier),
|
56
|
-
]);
|
56
|
+
]));
|
57
57
|
if (collectionsPulled.total > 0) {
|
58
58
|
return collectionsPulled.collections[0].$id;
|
59
59
|
}
|
@@ -71,9 +71,9 @@ export const afterImportActions = {
|
|
71
71
|
const valueToSet = otherDoc[otherFieldName];
|
72
72
|
if (valueToSet) {
|
73
73
|
// Update the target document
|
74
|
-
await db.updateDocument(dbId, targetCollectionId, docId, {
|
74
|
+
await tryAwaitWithRetry(async () => await db.updateDocument(dbId, targetCollectionId, docId, {
|
75
75
|
[fieldName]: valueToSet,
|
76
|
-
});
|
76
|
+
}));
|
77
77
|
}
|
78
78
|
console.log(`Field ${fieldName} updated successfully in document ${docId}.`);
|
79
79
|
}
|
@@ -89,17 +89,17 @@ export const afterImportActions = {
|
|
89
89
|
const db = getDatabaseFromConfig(config);
|
90
90
|
// Helper function to find a collection ID by name or return the ID if given
|
91
91
|
const findCollectionId = async (collectionIdentifier) => {
|
92
|
-
const collections = await db.listCollections(dbId, [
|
92
|
+
const collections = await tryAwaitWithRetry(async () => await db.listCollections(dbId, [
|
93
93
|
Query.equal("name", collectionIdentifier),
|
94
94
|
Query.limit(1),
|
95
|
-
]);
|
95
|
+
]));
|
96
96
|
return collections.total > 0
|
97
97
|
? collections.collections[0].$id
|
98
98
|
: collectionIdentifier;
|
99
99
|
};
|
100
100
|
// Function to check if the target field is an array
|
101
101
|
const isTargetFieldArray = async (collectionId, fieldName) => {
|
102
|
-
const collection = await db.getCollection(dbId, collectionId);
|
102
|
+
const collection = await tryAwaitWithRetry(async () => await db.getCollection(dbId, collectionId));
|
103
103
|
const attribute = collection.attributes.find((attr) => attr.key === fieldName);
|
104
104
|
// @ts-ignore
|
105
105
|
return attribute?.array === true;
|
@@ -119,7 +119,7 @@ export const afterImportActions = {
|
|
119
119
|
queries.push(Query.cursorAfter(cursor));
|
120
120
|
}
|
121
121
|
queries.push(Query.limit(docLimit));
|
122
|
-
const response = await db.listDocuments(dbId, otherCollectionId, queries);
|
122
|
+
const response = await tryAwaitWithRetry(async () => await db.listDocuments(dbId, otherCollectionId, queries));
|
123
123
|
const documents = response.documents;
|
124
124
|
if (documents.length === 0 || documents.length < docLimit) {
|
125
125
|
return documents;
|
@@ -134,7 +134,7 @@ export const afterImportActions = {
|
|
134
134
|
const updatePayload = targetFieldIsArray
|
135
135
|
? { [fieldName]: documentIds }
|
136
136
|
: { [fieldName]: documentIds[0] };
|
137
|
-
await db.updateDocument(dbId, targetCollectionId, docId, updatePayload);
|
137
|
+
await tryAwaitWithRetry(async () => await db.updateDocument(dbId, targetCollectionId, docId, updatePayload));
|
138
138
|
console.log(`Field ${fieldName} updated successfully in document ${docId} with ${documentIds.length} document IDs.`);
|
139
139
|
}
|
140
140
|
}
|
@@ -145,10 +145,10 @@ export const afterImportActions = {
|
|
145
145
|
setTargetFieldFromOtherCollectionDocumentsByMatchingField: async (config, dbId, collIdOrName, docId, fieldName, otherCollIdOrName, matchingFieldName, matchingFieldValue, targetField) => {
|
146
146
|
const db = getDatabaseFromConfig(config);
|
147
147
|
const findCollectionId = async (collectionIdentifier) => {
|
148
|
-
const collections = await db.listCollections(dbId, [
|
148
|
+
const collections = await tryAwaitWithRetry(async () => await db.listCollections(dbId, [
|
149
149
|
Query.equal("name", collectionIdentifier),
|
150
150
|
Query.limit(1),
|
151
|
-
]);
|
151
|
+
]));
|
152
152
|
return collections.total > 0
|
153
153
|
? collections.collections[0].$id
|
154
154
|
: collectionIdentifier;
|
@@ -172,7 +172,7 @@ export const afterImportActions = {
|
|
172
172
|
if (cursor) {
|
173
173
|
queries.push(Query.cursorAfter(cursor));
|
174
174
|
}
|
175
|
-
const response = await db.listDocuments(dbId, otherCollectionId, queries);
|
175
|
+
const response = await tryAwaitWithRetry(async () => await db.listDocuments(dbId, otherCollectionId, queries));
|
176
176
|
const documents = response.documents;
|
177
177
|
if (documents.length === 0 || documents.length < docLimit) {
|
178
178
|
return documents;
|
@@ -188,7 +188,7 @@ export const afterImportActions = {
|
|
188
188
|
const updatePayload = targetFieldIsArray
|
189
189
|
? { [fieldName]: targetFieldValues }
|
190
190
|
: { [fieldName]: targetFieldValues[0] };
|
191
|
-
await db.updateDocument(dbId, targetCollectionId, docId, updatePayload);
|
191
|
+
await tryAwaitWithRetry(async () => await db.updateDocument(dbId, targetCollectionId, docId, updatePayload));
|
192
192
|
console.log(`Field ${fieldName} updated successfully in document ${docId} with values from field ${targetField}.`);
|
193
193
|
}
|
194
194
|
}
|
@@ -199,22 +199,20 @@ export const afterImportActions = {
|
|
199
199
|
createOrGetBucket: async (config, bucketName, bucketId, permissions, fileSecurity, enabled, maxFileSize, allowedExtensions, compression, encryption, antivirus) => {
|
200
200
|
try {
|
201
201
|
const storage = getStorageFromConfig(config);
|
202
|
-
const bucket = await storage.listBuckets([
|
203
|
-
Query.equal("name", bucketName),
|
204
|
-
]);
|
202
|
+
const bucket = await tryAwaitWithRetry(async () => await storage.listBuckets([Query.equal("name", bucketName)]));
|
205
203
|
if (bucket.buckets.length > 0) {
|
206
204
|
return bucket.buckets[0];
|
207
205
|
}
|
208
206
|
else if (bucketId) {
|
209
207
|
try {
|
210
|
-
return await storage.getBucket(bucketId);
|
208
|
+
return await tryAwaitWithRetry(async () => await storage.getBucket(bucketId));
|
211
209
|
}
|
212
210
|
catch (error) {
|
213
|
-
return await storage.createBucket(bucketId, bucketName, permissions, fileSecurity, enabled, maxFileSize, allowedExtensions, compression, encryption, antivirus);
|
211
|
+
return await tryAwaitWithRetry(async () => await storage.createBucket(bucketId, bucketName, permissions, fileSecurity, enabled, maxFileSize, allowedExtensions, compression, encryption, antivirus));
|
214
212
|
}
|
215
213
|
}
|
216
214
|
else {
|
217
|
-
return await storage.createBucket(bucketId || ID.unique(), bucketName, permissions, fileSecurity, enabled, maxFileSize, allowedExtensions, compression, encryption, antivirus);
|
215
|
+
return await tryAwaitWithRetry(async () => await storage.createBucket(bucketId || ID.unique(), bucketName, permissions, fileSecurity, enabled, maxFileSize, allowedExtensions, compression, encryption, antivirus));
|
218
216
|
}
|
219
217
|
}
|
220
218
|
catch (error) {
|
@@ -225,7 +223,7 @@ export const afterImportActions = {
|
|
225
223
|
try {
|
226
224
|
const db = getDatabaseFromConfig(config);
|
227
225
|
const storage = getStorageFromConfig(config);
|
228
|
-
const collection = await db.getCollection(dbId, collId);
|
226
|
+
const collection = await tryAwaitWithRetry(async () => await db.getCollection(dbId, collId));
|
229
227
|
const attributes = collection.attributes;
|
230
228
|
const attribute = attributes.find((a) => a.key === fieldName);
|
231
229
|
// console.log(
|
@@ -255,7 +253,7 @@ export const afterImportActions = {
|
|
255
253
|
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "appwrite_tmp"));
|
256
254
|
const tempFilePath = path.join(tempDir, fileName);
|
257
255
|
// Download the file using fetch
|
258
|
-
const response = await fetch(filePath);
|
256
|
+
const response = await tryAwaitWithRetry(async () => await fetch(filePath));
|
259
257
|
if (!response.ok)
|
260
258
|
console.error(`Failed to fetch ${filePath}: ${response.statusText} for document ${docId} with field ${fieldName}`);
|
261
259
|
// Use arrayBuffer if buffer is not available
|
@@ -291,7 +291,8 @@ export class DataLoader {
|
|
291
291
|
console.log("---------------------------------");
|
292
292
|
await this.setupMaps(dbId);
|
293
293
|
const allUsers = await this.getAllUsers();
|
294
|
-
console.log(`Fetched ${allUsers.length} users
|
294
|
+
console.log(`Fetched ${allUsers.length} users, waiting a few seconds to let the program catch up...`);
|
295
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
295
296
|
// Iterate over the configured databases to find the matching one
|
296
297
|
for (const db of this.config.databases) {
|
297
298
|
if (db.$id !== dbId) {
|
@@ -728,9 +729,18 @@ export class DataLoader {
|
|
728
729
|
let { transformedItem, existingId, userData } = this.prepareUserData(item, importDef.attributeMappings, importDef.primaryKeyField, this.getTrueUniqueId(this.getCollectionKey("users")));
|
729
730
|
logger.info(`In create user -- transformedItem: ${JSON.stringify(transformedItem, null, 2)}`);
|
730
731
|
// Generate a new unique ID for the item or use existing ID
|
731
|
-
if (!existingId) {
|
732
|
+
if (!existingId && !userData.finalData?.userId) {
|
732
733
|
// No existing user ID, generate a new unique ID
|
733
|
-
existingId = this.getTrueUniqueId(this.getCollectionKey(
|
734
|
+
existingId = this.getTrueUniqueId(this.getCollectionKey(collection.name));
|
735
|
+
transformedItem = {
|
736
|
+
...transformedItem,
|
737
|
+
userId: existingId,
|
738
|
+
docId: existingId,
|
739
|
+
};
|
740
|
+
}
|
741
|
+
else if (!existingId && userData.finalData?.userId) {
|
742
|
+
// Existing user ID, use it as the new ID
|
743
|
+
existingId = userData.finalData.userId;
|
734
744
|
transformedItem = {
|
735
745
|
...transformedItem,
|
736
746
|
userId: existingId,
|
@@ -751,7 +761,9 @@ export class DataLoader {
|
|
751
761
|
// Found a duplicate oldId, now decide how to merge or handle these duplicates
|
752
762
|
for (const data of currentData.data) {
|
753
763
|
if (data.finalData.docId === oldId ||
|
754
|
-
data.finalData.userId === oldId
|
764
|
+
data.finalData.userId === oldId ||
|
765
|
+
data.context.docId === oldId ||
|
766
|
+
data.context.userId === oldId) {
|
755
767
|
transformedItem = this.mergeObjects(data.finalData, transformedItem);
|
756
768
|
}
|
757
769
|
}
|
@@ -787,30 +799,30 @@ export class DataLoader {
|
|
787
799
|
...importDef,
|
788
800
|
attributeMappings: mappingsWithActions,
|
789
801
|
};
|
802
|
+
const updatedData = this.importMap.get(this.getCollectionKey(collection.name));
|
790
803
|
let foundData = false;
|
791
|
-
for (let i = 0; i <
|
792
|
-
if (
|
793
|
-
|
794
|
-
|
795
|
-
|
796
|
-
|
797
|
-
|
798
|
-
|
799
|
-
|
800
|
-
this.importMap.set(this.getCollectionKey(collection.name), currentData);
|
804
|
+
for (let i = 0; i < updatedData.data.length; i++) {
|
805
|
+
if (updatedData.data[i].finalData.docId === existingId ||
|
806
|
+
updatedData.data[i].finalData.userId === existingId ||
|
807
|
+
updatedData.data[i].context.docId === existingId ||
|
808
|
+
updatedData.data[i].context.userId === existingId) {
|
809
|
+
updatedData.data[i].finalData = this.mergeObjects(updatedData.data[i].finalData, transformedItem);
|
810
|
+
updatedData.data[i].context = this.mergeObjects(updatedData.data[i].context, context);
|
811
|
+
updatedData.data[i].importDef = this.mergeObjects(updatedData.data[i].importDef, newImportDef);
|
812
|
+
this.importMap.set(this.getCollectionKey(collection.name), updatedData);
|
801
813
|
this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
|
802
814
|
foundData = true;
|
803
815
|
}
|
804
816
|
}
|
805
817
|
if (!foundData) {
|
806
818
|
// Add new data to the associated collection
|
807
|
-
|
819
|
+
updatedData.data.push({
|
808
820
|
rawData: item,
|
809
821
|
context: context,
|
810
822
|
importDef: newImportDef,
|
811
823
|
finalData: transformedItem,
|
812
824
|
});
|
813
|
-
this.importMap.set(this.getCollectionKey(collection.name),
|
825
|
+
this.importMap.set(this.getCollectionKey(collection.name), updatedData);
|
814
826
|
this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
|
815
827
|
}
|
816
828
|
}
|
@@ -854,13 +866,7 @@ export class DataLoader {
|
|
854
866
|
// Create a context object for the item, including the new ID
|
855
867
|
let context = this.createContext(db, collection, item, itemIdNew);
|
856
868
|
// 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
|
-
}
|
869
|
+
let transformedData = this.transformData(item, importDef.attributeMappings);
|
864
870
|
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
865
871
|
if (importDef.primaryKeyField) {
|
866
872
|
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.63",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
@@ -47,7 +47,9 @@ export const afterImportActions = {
|
|
47
47
|
) => {
|
48
48
|
try {
|
49
49
|
const db = getDatabaseFromConfig(config);
|
50
|
-
await
|
50
|
+
await tryAwaitWithRetry(
|
51
|
+
async () => await db.updateDocument(dbId, collId, docId, data)
|
52
|
+
);
|
51
53
|
} catch (error) {
|
52
54
|
console.error("Error updating document: ", error);
|
53
55
|
}
|
@@ -63,11 +65,16 @@ export const afterImportActions = {
|
|
63
65
|
) => {
|
64
66
|
try {
|
65
67
|
const db = getDatabaseFromConfig(config);
|
66
|
-
const doc = await
|
68
|
+
const doc = await tryAwaitWithRetry(
|
69
|
+
async () => await db.getDocument(dbId, collId, docId)
|
70
|
+
);
|
67
71
|
if (doc[fieldName as keyof typeof doc] == oldFieldValue) {
|
68
|
-
await
|
69
|
-
|
70
|
-
|
72
|
+
await tryAwaitWithRetry(
|
73
|
+
async () =>
|
74
|
+
await db.updateDocument(dbId, collId, docId, {
|
75
|
+
[fieldName]: newFieldValue,
|
76
|
+
})
|
77
|
+
);
|
71
78
|
}
|
72
79
|
} catch (error) {
|
73
80
|
console.error("Error updating document: ", error);
|
@@ -87,10 +94,13 @@ export const afterImportActions = {
|
|
87
94
|
|
88
95
|
// Helper function to find a collection ID by name or return the ID if given
|
89
96
|
const findCollectionId = async (collectionIdentifier: string) => {
|
90
|
-
const collectionsPulled = await
|
91
|
-
|
92
|
-
|
93
|
-
|
97
|
+
const collectionsPulled = await tryAwaitWithRetry(
|
98
|
+
async () =>
|
99
|
+
await db.listCollections(dbId, [
|
100
|
+
Query.limit(25),
|
101
|
+
Query.equal("name", collectionIdentifier),
|
102
|
+
])
|
103
|
+
);
|
94
104
|
if (collectionsPulled.total > 0) {
|
95
105
|
return collectionsPulled.collections[0].$id;
|
96
106
|
} else {
|
@@ -114,9 +124,12 @@ export const afterImportActions = {
|
|
114
124
|
|
115
125
|
if (valueToSet) {
|
116
126
|
// Update the target document
|
117
|
-
await
|
118
|
-
|
119
|
-
|
127
|
+
await tryAwaitWithRetry(
|
128
|
+
async () =>
|
129
|
+
await db.updateDocument(dbId, targetCollectionId, docId, {
|
130
|
+
[fieldName]: valueToSet,
|
131
|
+
})
|
132
|
+
);
|
120
133
|
}
|
121
134
|
|
122
135
|
console.log(
|
@@ -148,10 +161,13 @@ export const afterImportActions = {
|
|
148
161
|
|
149
162
|
// Helper function to find a collection ID by name or return the ID if given
|
150
163
|
const findCollectionId = async (collectionIdentifier: string) => {
|
151
|
-
const collections = await
|
152
|
-
|
153
|
-
|
154
|
-
|
164
|
+
const collections = await tryAwaitWithRetry(
|
165
|
+
async () =>
|
166
|
+
await db.listCollections(dbId, [
|
167
|
+
Query.equal("name", collectionIdentifier),
|
168
|
+
Query.limit(1),
|
169
|
+
])
|
170
|
+
);
|
155
171
|
return collections.total > 0
|
156
172
|
? collections.collections[0].$id
|
157
173
|
: collectionIdentifier;
|
@@ -162,7 +178,9 @@ export const afterImportActions = {
|
|
162
178
|
collectionId: string,
|
163
179
|
fieldName: string
|
164
180
|
) => {
|
165
|
-
const collection = await
|
181
|
+
const collection = await tryAwaitWithRetry(
|
182
|
+
async () => await db.getCollection(dbId, collectionId)
|
183
|
+
);
|
166
184
|
const attribute = collection.attributes.find(
|
167
185
|
(attr: any) => attr.key === fieldName
|
168
186
|
);
|
@@ -191,10 +209,8 @@ export const afterImportActions = {
|
|
191
209
|
queries.push(Query.cursorAfter(cursor));
|
192
210
|
}
|
193
211
|
queries.push(Query.limit(docLimit));
|
194
|
-
const response = await
|
195
|
-
dbId,
|
196
|
-
otherCollectionId,
|
197
|
-
queries
|
212
|
+
const response = await tryAwaitWithRetry(
|
213
|
+
async () => await db.listDocuments(dbId, otherCollectionId, queries)
|
198
214
|
);
|
199
215
|
const documents = response.documents;
|
200
216
|
if (documents.length === 0 || documents.length < docLimit) {
|
@@ -212,7 +228,15 @@ export const afterImportActions = {
|
|
212
228
|
const updatePayload = targetFieldIsArray
|
213
229
|
? { [fieldName]: documentIds }
|
214
230
|
: { [fieldName]: documentIds[0] };
|
215
|
-
await
|
231
|
+
await tryAwaitWithRetry(
|
232
|
+
async () =>
|
233
|
+
await db.updateDocument(
|
234
|
+
dbId,
|
235
|
+
targetCollectionId,
|
236
|
+
docId,
|
237
|
+
updatePayload
|
238
|
+
)
|
239
|
+
);
|
216
240
|
|
217
241
|
console.log(
|
218
242
|
`Field ${fieldName} updated successfully in document ${docId} with ${documentIds.length} document IDs.`
|
@@ -239,10 +263,13 @@ export const afterImportActions = {
|
|
239
263
|
const db = getDatabaseFromConfig(config);
|
240
264
|
|
241
265
|
const findCollectionId = async (collectionIdentifier: string) => {
|
242
|
-
const collections = await
|
243
|
-
|
244
|
-
|
245
|
-
|
266
|
+
const collections = await tryAwaitWithRetry(
|
267
|
+
async () =>
|
268
|
+
await db.listCollections(dbId, [
|
269
|
+
Query.equal("name", collectionIdentifier),
|
270
|
+
Query.limit(1),
|
271
|
+
])
|
272
|
+
);
|
246
273
|
return collections.total > 0
|
247
274
|
? collections.collections[0].$id
|
248
275
|
: collectionIdentifier;
|
@@ -279,10 +306,8 @@ export const afterImportActions = {
|
|
279
306
|
if (cursor) {
|
280
307
|
queries.push(Query.cursorAfter(cursor));
|
281
308
|
}
|
282
|
-
const response = await
|
283
|
-
dbId,
|
284
|
-
otherCollectionId,
|
285
|
-
queries
|
309
|
+
const response = await tryAwaitWithRetry(
|
310
|
+
async () => await db.listDocuments(dbId, otherCollectionId, queries)
|
286
311
|
);
|
287
312
|
const documents = response.documents;
|
288
313
|
if (documents.length === 0 || documents.length < docLimit) {
|
@@ -303,7 +328,15 @@ export const afterImportActions = {
|
|
303
328
|
const updatePayload = targetFieldIsArray
|
304
329
|
? { [fieldName]: targetFieldValues }
|
305
330
|
: { [fieldName]: targetFieldValues[0] };
|
306
|
-
await
|
331
|
+
await tryAwaitWithRetry(
|
332
|
+
async () =>
|
333
|
+
await db.updateDocument(
|
334
|
+
dbId,
|
335
|
+
targetCollectionId,
|
336
|
+
docId,
|
337
|
+
updatePayload
|
338
|
+
)
|
339
|
+
);
|
307
340
|
|
308
341
|
console.log(
|
309
342
|
`Field ${fieldName} updated successfully in document ${docId} with values from field ${targetField}.`
|
@@ -331,40 +364,48 @@ export const afterImportActions = {
|
|
331
364
|
) => {
|
332
365
|
try {
|
333
366
|
const storage = getStorageFromConfig(config);
|
334
|
-
const bucket = await
|
335
|
-
Query.equal("name", bucketName)
|
336
|
-
|
367
|
+
const bucket = await tryAwaitWithRetry(
|
368
|
+
async () => await storage.listBuckets([Query.equal("name", bucketName)])
|
369
|
+
);
|
337
370
|
if (bucket.buckets.length > 0) {
|
338
371
|
return bucket.buckets[0];
|
339
372
|
} else if (bucketId) {
|
340
373
|
try {
|
341
|
-
return await
|
374
|
+
return await tryAwaitWithRetry(
|
375
|
+
async () => await storage.getBucket(bucketId)
|
376
|
+
);
|
342
377
|
} catch (error) {
|
343
|
-
return await
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
378
|
+
return await tryAwaitWithRetry(
|
379
|
+
async () =>
|
380
|
+
await storage.createBucket(
|
381
|
+
bucketId,
|
382
|
+
bucketName,
|
383
|
+
permissions,
|
384
|
+
fileSecurity,
|
385
|
+
enabled,
|
386
|
+
maxFileSize,
|
387
|
+
allowedExtensions,
|
388
|
+
compression,
|
389
|
+
encryption,
|
390
|
+
antivirus
|
391
|
+
)
|
354
392
|
);
|
355
393
|
}
|
356
394
|
} else {
|
357
|
-
return await
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
395
|
+
return await tryAwaitWithRetry(
|
396
|
+
async () =>
|
397
|
+
await storage.createBucket(
|
398
|
+
bucketId || ID.unique(),
|
399
|
+
bucketName,
|
400
|
+
permissions,
|
401
|
+
fileSecurity,
|
402
|
+
enabled,
|
403
|
+
maxFileSize,
|
404
|
+
allowedExtensions,
|
405
|
+
compression,
|
406
|
+
encryption,
|
407
|
+
antivirus
|
408
|
+
)
|
368
409
|
);
|
369
410
|
}
|
370
411
|
} catch (error) {
|
@@ -384,7 +425,9 @@ export const afterImportActions = {
|
|
384
425
|
try {
|
385
426
|
const db = getDatabaseFromConfig(config);
|
386
427
|
const storage = getStorageFromConfig(config);
|
387
|
-
const collection = await
|
428
|
+
const collection = await tryAwaitWithRetry(
|
429
|
+
async () => await db.getCollection(dbId, collId)
|
430
|
+
);
|
388
431
|
const attributes = collection.attributes as any[];
|
389
432
|
const attribute = attributes.find((a) => a.key === fieldName);
|
390
433
|
// console.log(
|
@@ -423,7 +466,9 @@ export const afterImportActions = {
|
|
423
466
|
const tempFilePath = path.join(tempDir, fileName);
|
424
467
|
|
425
468
|
// Download the file using fetch
|
426
|
-
const response = await
|
469
|
+
const response = await tryAwaitWithRetry(
|
470
|
+
async () => await fetch(filePath)
|
471
|
+
);
|
427
472
|
if (!response.ok)
|
428
473
|
console.error(
|
429
474
|
`Failed to fetch ${filePath}: ${response.statusText} for document ${docId} with field ${fieldName}`
|
@@ -362,7 +362,10 @@ export class DataLoader {
|
|
362
362
|
console.log("---------------------------------");
|
363
363
|
await this.setupMaps(dbId);
|
364
364
|
const allUsers = await this.getAllUsers();
|
365
|
-
console.log(
|
365
|
+
console.log(
|
366
|
+
`Fetched ${allUsers.length} users, waiting a few seconds to let the program catch up...`
|
367
|
+
);
|
368
|
+
await new Promise((resolve) => setTimeout(resolve, 5000));
|
366
369
|
// Iterate over the configured databases to find the matching one
|
367
370
|
for (const db of this.config.databases) {
|
368
371
|
if (db.$id !== dbId) {
|
@@ -940,9 +943,19 @@ export class DataLoader {
|
|
940
943
|
);
|
941
944
|
|
942
945
|
// Generate a new unique ID for the item or use existing ID
|
943
|
-
if (!existingId) {
|
946
|
+
if (!existingId && !userData.finalData?.userId) {
|
944
947
|
// No existing user ID, generate a new unique ID
|
945
|
-
existingId = this.getTrueUniqueId(
|
948
|
+
existingId = this.getTrueUniqueId(
|
949
|
+
this.getCollectionKey(collection.name)
|
950
|
+
);
|
951
|
+
transformedItem = {
|
952
|
+
...transformedItem,
|
953
|
+
userId: existingId,
|
954
|
+
docId: existingId,
|
955
|
+
};
|
956
|
+
} else if (!existingId && userData.finalData?.userId) {
|
957
|
+
// Existing user ID, use it as the new ID
|
958
|
+
existingId = userData.finalData.userId;
|
946
959
|
transformedItem = {
|
947
960
|
...transformedItem,
|
948
961
|
userId: existingId,
|
@@ -951,7 +964,7 @@ export class DataLoader {
|
|
951
964
|
}
|
952
965
|
|
953
966
|
// Create a context object for the item, including the new ID
|
954
|
-
let context = this.createContext(db, collection, item, existingId);
|
967
|
+
let context = this.createContext(db, collection, item, existingId!);
|
955
968
|
|
956
969
|
// Merge the transformed data into the context
|
957
970
|
context = { ...context, ...transformedItem, ...userData.finalData };
|
@@ -970,7 +983,9 @@ export class DataLoader {
|
|
970
983
|
for (const data of currentData.data) {
|
971
984
|
if (
|
972
985
|
data.finalData.docId === oldId ||
|
973
|
-
data.finalData.userId === oldId
|
986
|
+
data.finalData.userId === oldId ||
|
987
|
+
data.context.docId === oldId ||
|
988
|
+
data.context.userId === oldId
|
974
989
|
) {
|
975
990
|
transformedItem = this.mergeObjects(
|
976
991
|
data.finalData,
|
@@ -1022,41 +1037,51 @@ export class DataLoader {
|
|
1022
1037
|
attributeMappings: mappingsWithActions,
|
1023
1038
|
};
|
1024
1039
|
|
1040
|
+
const updatedData = this.importMap.get(
|
1041
|
+
this.getCollectionKey(collection.name)
|
1042
|
+
)!;
|
1043
|
+
|
1025
1044
|
let foundData = false;
|
1026
|
-
for (let i = 0; i <
|
1045
|
+
for (let i = 0; i < updatedData.data.length; i++) {
|
1027
1046
|
if (
|
1028
|
-
|
1029
|
-
|
1047
|
+
updatedData.data[i].finalData.docId === existingId ||
|
1048
|
+
updatedData.data[i].finalData.userId === existingId ||
|
1049
|
+
updatedData.data[i].context.docId === existingId ||
|
1050
|
+
updatedData.data[i].context.userId === existingId
|
1030
1051
|
) {
|
1031
|
-
|
1032
|
-
|
1052
|
+
updatedData.data[i].finalData = this.mergeObjects(
|
1053
|
+
updatedData.data[i].finalData,
|
1033
1054
|
transformedItem
|
1034
1055
|
);
|
1035
|
-
|
1036
|
-
|
1037
|
-
|
1038
|
-
|
1039
|
-
|
1056
|
+
updatedData.data[i].context = this.mergeObjects(
|
1057
|
+
updatedData.data[i].context,
|
1058
|
+
context
|
1059
|
+
);
|
1060
|
+
updatedData.data[i].importDef = this.mergeObjects(
|
1061
|
+
updatedData.data[i].importDef,
|
1062
|
+
newImportDef
|
1063
|
+
);
|
1040
1064
|
this.importMap.set(
|
1041
1065
|
this.getCollectionKey(collection.name),
|
1042
|
-
|
1066
|
+
updatedData
|
1043
1067
|
);
|
1044
1068
|
this.oldIdToNewIdPerCollectionMap.set(
|
1045
1069
|
this.getCollectionKey(collection.name),
|
1046
1070
|
collectionOldIdToNewIdMap!
|
1047
1071
|
);
|
1072
|
+
|
1048
1073
|
foundData = true;
|
1049
1074
|
}
|
1050
1075
|
}
|
1051
1076
|
if (!foundData) {
|
1052
1077
|
// Add new data to the associated collection
|
1053
|
-
|
1078
|
+
updatedData.data.push({
|
1054
1079
|
rawData: item,
|
1055
1080
|
context: context,
|
1056
1081
|
importDef: newImportDef,
|
1057
1082
|
finalData: transformedItem,
|
1058
1083
|
});
|
1059
|
-
this.importMap.set(this.getCollectionKey(collection.name),
|
1084
|
+
this.importMap.set(this.getCollectionKey(collection.name), updatedData);
|
1060
1085
|
this.oldIdToNewIdPerCollectionMap.set(
|
1061
1086
|
this.getCollectionKey(collection.name),
|
1062
1087
|
collectionOldIdToNewIdMap!
|
@@ -1119,18 +1144,10 @@ export class DataLoader {
|
|
1119
1144
|
// Create a context object for the item, including the new ID
|
1120
1145
|
let context = this.createContext(db, collection, item, itemIdNew);
|
1121
1146
|
// Transform the item data based on the attribute mappings
|
1122
|
-
|
1147
|
+
let transformedData = this.transformData(
|
1123
1148
|
item,
|
1124
1149
|
importDef.attributeMappings
|
1125
1150
|
);
|
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
1151
|
// If a primary key field is defined, handle the ID mapping and check for duplicates
|
1135
1152
|
if (importDef.primaryKeyField) {
|
1136
1153
|
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;
|