appwrite-utils-cli 0.0.252 → 0.0.254
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/main.js +6 -0
- package/dist/migrations/dataLoader.d.ts +3 -2
- package/dist/migrations/dataLoader.js +58 -74
- package/dist/migrations/importController.js +4 -2
- package/dist/utilsController.d.ts +1 -0
- package/package.json +1 -1
- package/src/main.ts +6 -0
- package/src/migrations/dataLoader.ts +73 -95
- package/src/migrations/importController.ts +5 -2
- package/src/utilsController.ts +1 -0
package/README.md
CHANGED
@@ -81,6 +81,7 @@ This setup ensures that developers have robust tools at their fingertips to mana
|
|
81
81
|
|
82
82
|
### Changelog
|
83
83
|
|
84
|
+
- 0.0.253: Added `--writeData` (or `--write-data`) to command to write the output of the import data to a file called dataLoaderOutput in your root dir
|
84
85
|
- 0.0.23: Added batching to user deletion
|
85
86
|
- 0.0.22: Converted all import processes except `postImportActions` and Relationship Resolution to the local data import, so it should be much faster.
|
86
87
|
- 0.0.6: Added `setTargetFieldFromOtherCollectionDocumentsByMatchingField` for the below, but setting a different field than the field you matched. The names are long, but at least you know what's going on lmao.
|
package/dist/main.js
CHANGED
@@ -13,6 +13,7 @@ async function main() {
|
|
13
13
|
let generateSchemas = false;
|
14
14
|
let importData = false;
|
15
15
|
let wipeDocuments = false;
|
16
|
+
let shouldWriteFile = false;
|
16
17
|
if (args.includes("--prod")) {
|
17
18
|
runProd = true;
|
18
19
|
}
|
@@ -40,6 +41,9 @@ async function main() {
|
|
40
41
|
if (args.includes("--wipe-users") || args.includes("--wipeUsers")) {
|
41
42
|
wipeUsers = true;
|
42
43
|
}
|
44
|
+
if (args.includes("--write-data") || args.includes("--writeData")) {
|
45
|
+
shouldWriteFile = true;
|
46
|
+
}
|
43
47
|
if (args.includes("--init")) {
|
44
48
|
await controller.run({
|
45
49
|
runProd: runProd,
|
@@ -53,6 +57,7 @@ async function main() {
|
|
53
57
|
generateMockData: false,
|
54
58
|
importData: false,
|
55
59
|
checkDuplicates: false,
|
60
|
+
shouldWriteFile: shouldWriteFile,
|
56
61
|
});
|
57
62
|
}
|
58
63
|
else {
|
@@ -68,6 +73,7 @@ async function main() {
|
|
68
73
|
wipeUsers: wipeUsers,
|
69
74
|
importData: importData,
|
70
75
|
checkDuplicates: false,
|
76
|
+
shouldWriteFile: shouldWriteFile,
|
71
77
|
});
|
72
78
|
}
|
73
79
|
}
|
@@ -1559,7 +1559,8 @@ export declare class DataLoader {
|
|
1559
1559
|
private emailToUserIdMap;
|
1560
1560
|
private phoneToUserIdMap;
|
1561
1561
|
userExistsMap: Map<string, boolean>;
|
1562
|
-
|
1562
|
+
private shouldWriteFile;
|
1563
|
+
constructor(appwriteFolderPath: string, importDataActions: ImportDataActions, database: Databases, config: AppwriteConfig, shouldWriteFile?: boolean);
|
1563
1564
|
getCollectionKey(name: string): string;
|
1564
1565
|
loadData(importDef: ImportDef): Promise<any[]>;
|
1565
1566
|
checkMapValuesForId(newId: string, collectionName: string): string | false;
|
@@ -1578,7 +1579,7 @@ export declare class DataLoader {
|
|
1578
1579
|
getAllUsers(): Promise<import("node-appwrite").Models.User<import("node-appwrite").Models.Preferences>[]>;
|
1579
1580
|
start(dbId: string): Promise<void>;
|
1580
1581
|
updateReferencesInRelatedCollections(): Promise<void>;
|
1581
|
-
|
1582
|
+
private writeMapsToJsonFile;
|
1582
1583
|
/**
|
1583
1584
|
* Prepares user data by checking for duplicates based on email or phone, adding to a duplicate map if found,
|
1584
1585
|
* and then returning the transformed item without user-specific keys.
|
@@ -45,13 +45,15 @@ export class DataLoader {
|
|
45
45
|
emailToUserIdMap = new Map();
|
46
46
|
phoneToUserIdMap = new Map();
|
47
47
|
userExistsMap = new Map();
|
48
|
+
shouldWriteFile = false;
|
48
49
|
// Constructor to initialize the DataLoader with necessary configurations
|
49
|
-
constructor(appwriteFolderPath, importDataActions, database, config) {
|
50
|
+
constructor(appwriteFolderPath, importDataActions, database, config, shouldWriteFile) {
|
50
51
|
this.appwriteFolderPath = appwriteFolderPath;
|
51
52
|
this.importDataActions = importDataActions;
|
52
53
|
this.database = database;
|
53
54
|
this.usersController = new UsersController(config, database);
|
54
55
|
this.config = config;
|
56
|
+
this.shouldWriteFile = shouldWriteFile || false;
|
55
57
|
}
|
56
58
|
// Helper method to generate a consistent key for collections
|
57
59
|
getCollectionKey(name) {
|
@@ -156,7 +158,14 @@ export class DataLoader {
|
|
156
158
|
async getAllUsers() {
|
157
159
|
const users = new UsersController(this.config, this.database);
|
158
160
|
const allUsers = await users.getAllUsers();
|
161
|
+
// Iterate over the users and setup our maps ahead of time for email and phone
|
159
162
|
for (const user of allUsers) {
|
163
|
+
if (user.email) {
|
164
|
+
this.emailToUserIdMap.set(user.email, user.$id);
|
165
|
+
}
|
166
|
+
if (user.phone) {
|
167
|
+
this.phoneToUserIdMap.set(user.phone, user.$id);
|
168
|
+
}
|
160
169
|
this.userExistsMap.set(user.$id, true);
|
161
170
|
}
|
162
171
|
return allUsers;
|
@@ -169,15 +178,6 @@ export class DataLoader {
|
|
169
178
|
await this.setupMaps(dbId);
|
170
179
|
const allUsers = await this.getAllUsers();
|
171
180
|
console.log(`Fetched ${allUsers.length} users`);
|
172
|
-
// Iterate over the users and setup our maps ahead of time for email and phone
|
173
|
-
for (const user of allUsers) {
|
174
|
-
if (user.email) {
|
175
|
-
this.emailToUserIdMap.set(user.email, user.$id);
|
176
|
-
}
|
177
|
-
if (user.phone) {
|
178
|
-
this.phoneToUserIdMap.set(user.phone, user.$id);
|
179
|
-
}
|
180
|
-
}
|
181
181
|
// Iterate over the configured databases to find the matching one
|
182
182
|
for (const db of this.config.databases) {
|
183
183
|
if (db.$id !== dbId) {
|
@@ -222,7 +222,9 @@ export class DataLoader {
|
|
222
222
|
console.log("---------------------------------");
|
223
223
|
console.log(`Data setup for database: ${dbId} completed`);
|
224
224
|
console.log("---------------------------------");
|
225
|
-
|
225
|
+
if (this.shouldWriteFile) {
|
226
|
+
this.writeMapsToJsonFile();
|
227
|
+
}
|
226
228
|
}
|
227
229
|
async updateReferencesInRelatedCollections() {
|
228
230
|
// Iterate over each collection configuration
|
@@ -299,24 +301,6 @@ export class DataLoader {
|
|
299
301
|
}
|
300
302
|
}
|
301
303
|
}
|
302
|
-
updateObjectWithNonNullishValues(sourceObject, inputObject) {
|
303
|
-
// Iterate through the keys of the inputObject
|
304
|
-
for (const key of Object.keys(inputObject)) {
|
305
|
-
const inputValue = inputObject[key];
|
306
|
-
const sourceValue = sourceObject[key];
|
307
|
-
// Check if the inputObject's value for the current key is non-nullish
|
308
|
-
// and either the key doesn't exist in the sourceObject or its value is nullish
|
309
|
-
if (inputValue !== null &&
|
310
|
-
inputValue !== undefined &&
|
311
|
-
inputValue !== "" &&
|
312
|
-
(sourceValue === null ||
|
313
|
-
sourceValue === undefined ||
|
314
|
-
sourceValue === "")) {
|
315
|
-
// Update the sourceObject with the inputObject's value for the current key
|
316
|
-
sourceObject[key] = inputValue;
|
317
|
-
}
|
318
|
-
}
|
319
|
-
}
|
320
304
|
// async updateReferencesInRelatedCollections() {
|
321
305
|
// // Process each collection defined in the config
|
322
306
|
// for (const collection of this.config.collections) {
|
@@ -368,43 +352,36 @@ export class DataLoader {
|
|
368
352
|
// }
|
369
353
|
// }
|
370
354
|
// }
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
// console.error("Error writing data to JSON file:", err);
|
402
|
-
// return;
|
403
|
-
// }
|
404
|
-
// console.log(`Data successfully written to ${outputFile}`);
|
405
|
-
// }
|
406
|
-
// );
|
407
|
-
// }
|
355
|
+
writeMapsToJsonFile() {
|
356
|
+
const outputDir = path.resolve(process.cwd());
|
357
|
+
const outputFile = path.join(outputDir, "dataLoaderOutput.json");
|
358
|
+
const dataToWrite = {
|
359
|
+
dataFromCollections: Array.from(this.importMap.entries()).map(([key, value]) => {
|
360
|
+
return {
|
361
|
+
collection: key,
|
362
|
+
data: value.data.map((item) => item.finalData),
|
363
|
+
};
|
364
|
+
}),
|
365
|
+
// Convert Maps to arrays of entries for serialization
|
366
|
+
mergedUserMap: Array.from(this.mergedUserMap.entries()),
|
367
|
+
// emailToUserIdMap: Array.from(this.emailToUserIdMap.entries()),
|
368
|
+
// phoneToUserIdMap: Array.from(this.phoneToUserIdMap.entries()),
|
369
|
+
};
|
370
|
+
// Use JSON.stringify with a replacer function to handle Maps
|
371
|
+
const replacer = (key, value) => {
|
372
|
+
if (value instanceof Map) {
|
373
|
+
return Array.from(value.entries());
|
374
|
+
}
|
375
|
+
return value;
|
376
|
+
};
|
377
|
+
fs.writeFile(outputFile, JSON.stringify(dataToWrite, replacer, 2), "utf8", (err) => {
|
378
|
+
if (err) {
|
379
|
+
console.error("Error writing data to JSON file:", err);
|
380
|
+
return;
|
381
|
+
}
|
382
|
+
console.log(`Data successfully written to ${outputFile}`);
|
383
|
+
});
|
384
|
+
}
|
408
385
|
/**
|
409
386
|
* Prepares user data by checking for duplicates based on email or phone, adding to a duplicate map if found,
|
410
387
|
* and then returning the transformed item without user-specific keys.
|
@@ -499,6 +476,7 @@ export class DataLoader {
|
|
499
476
|
this.oldIdToNewIdPerCollectionMap
|
500
477
|
.set(this.getCollectionKey(collection.name), oldIdToNewIdMap)
|
501
478
|
.get(this.getCollectionKey(collection.name));
|
479
|
+
console.log(`${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`);
|
502
480
|
if (!operationId) {
|
503
481
|
throw new Error(`No import operation found for collection ${collection.name}`);
|
504
482
|
}
|
@@ -528,7 +506,6 @@ export class DataLoader {
|
|
528
506
|
// No existing user ID, generate a new unique ID
|
529
507
|
existingId = this.getTrueUniqueId(this.getCollectionKey("users"));
|
530
508
|
transformedItem.userId = existingId; // Assign the new ID to the transformed data's userId field
|
531
|
-
transformedItem.docId = existingId;
|
532
509
|
}
|
533
510
|
// Create a context object for the item, including the new ID
|
534
511
|
let context = this.createContext(db, collection, item, existingId);
|
@@ -542,7 +519,12 @@ export class DataLoader {
|
|
542
519
|
.get(this.getCollectionKey(collection.name))
|
543
520
|
?.has(`${oldId}`)) {
|
544
521
|
// Found a duplicate oldId, now decide how to merge or handle these duplicates
|
545
|
-
|
522
|
+
for (const data of currentData.data) {
|
523
|
+
if (data.finalData.docId === oldId ||
|
524
|
+
data.finalData.userId === oldId) {
|
525
|
+
Object.assign(data.finalData, transformedItem);
|
526
|
+
}
|
527
|
+
}
|
546
528
|
}
|
547
529
|
else {
|
548
530
|
// No duplicate found, simply map the oldId to the new itemId
|
@@ -556,7 +538,8 @@ export class DataLoader {
|
|
556
538
|
if ((currentUserData.data[i].finalData.docId === existingId ||
|
557
539
|
currentUserData.data[i].finalData.userId === existingId) &&
|
558
540
|
!_.isEqual(currentUserData.data[i], userData)) {
|
559
|
-
|
541
|
+
Object.assign(currentUserData.data[i].finalData, transformedItem);
|
542
|
+
Object.assign(currentUserData.data[i].rawData, item);
|
560
543
|
console.log("Merging user data", currentUserData.data[i].finalData);
|
561
544
|
this.importMap.set(this.getCollectionKey("users"), currentUserData);
|
562
545
|
}
|
@@ -572,7 +555,10 @@ export class DataLoader {
|
|
572
555
|
for (let i = 0; i < currentData.data.length; i++) {
|
573
556
|
if (currentData.data[i].finalData.docId === existingId ||
|
574
557
|
currentData.data[i].finalData.userId === existingId) {
|
575
|
-
|
558
|
+
currentData.data[i].finalData = {
|
559
|
+
...currentData.data[i].finalData,
|
560
|
+
...transformedItem,
|
561
|
+
};
|
576
562
|
currentData.data[i].importDef = newImportDef;
|
577
563
|
this.importMap.set(this.getCollectionKey(collection.name), currentData);
|
578
564
|
this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
|
@@ -661,7 +647,6 @@ export class DataLoader {
|
|
661
647
|
finalData: transformedData,
|
662
648
|
});
|
663
649
|
this.importMap.set(this.getCollectionKey(collection.name), currentData);
|
664
|
-
console.log(`Set import map for ${collection.name}, length is ${currentData.data.length}`);
|
665
650
|
this.oldIdToNewIdPerCollectionMap.set(this.getCollectionKey(collection.name), collectionOldIdToNewIdMap);
|
666
651
|
}
|
667
652
|
else {
|
@@ -722,9 +707,8 @@ export class DataLoader {
|
|
722
707
|
}
|
723
708
|
}
|
724
709
|
else {
|
725
|
-
|
726
|
-
|
727
|
-
newId = foundItem ? oldIdToNewIdMap?.get(`${oldId}`) : undefined;
|
710
|
+
logger.error(`No old ID found (to update another document with) in prepareUpdateData for ${collection.name}, ${JSON.stringify(item, null, 2)}`);
|
711
|
+
continue;
|
728
712
|
}
|
729
713
|
// Log an error and continue to the next item if no new ID is found
|
730
714
|
if (!newId) {
|
@@ -58,7 +58,7 @@ export class ImportController {
|
|
58
58
|
console.log(`Starting import data for database: ${db.name}`);
|
59
59
|
console.log(`---------------------------------`);
|
60
60
|
// await this.importCollections(db);
|
61
|
-
const dataLoader = new DataLoader(this.appwriteFolderPath, this.importDataActions, this.database, this.config);
|
61
|
+
const dataLoader = new DataLoader(this.appwriteFolderPath, this.importDataActions, this.database, this.config, this.setupOptions.shouldWriteFile);
|
62
62
|
await dataLoader.start(db.$id);
|
63
63
|
await this.importCollections(db, dataLoader);
|
64
64
|
await resolveAndUpdateRelationships(db.$id, this.database, this.config);
|
@@ -100,7 +100,8 @@ export class ImportController {
|
|
100
100
|
if (userBatch.finalData && userBatch.finalData.length > 0) {
|
101
101
|
const userId = userBatch.finalData.userId;
|
102
102
|
if (dataLoader.userExistsMap.has(userId)) {
|
103
|
-
|
103
|
+
// We only are storing the existing user ID's as true, so we need to check for that
|
104
|
+
if (!(dataLoader.userExistsMap.get(userId) === true)) {
|
104
105
|
return usersController
|
105
106
|
.createUserAndReturn(userBatch.finalData)
|
106
107
|
.then(() => console.log("Created user"))
|
@@ -110,6 +111,7 @@ export class ImportController {
|
|
110
111
|
});
|
111
112
|
}
|
112
113
|
else {
|
114
|
+
console.log("Skipped existing user: ", userId);
|
113
115
|
return Promise.resolve();
|
114
116
|
}
|
115
117
|
}
|
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.254",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
package/src/main.ts
CHANGED
@@ -16,6 +16,7 @@ async function main() {
|
|
16
16
|
let generateSchemas = false;
|
17
17
|
let importData = false;
|
18
18
|
let wipeDocuments = false;
|
19
|
+
let shouldWriteFile = false;
|
19
20
|
if (args.includes("--prod")) {
|
20
21
|
runProd = true;
|
21
22
|
}
|
@@ -43,6 +44,9 @@ async function main() {
|
|
43
44
|
if (args.includes("--wipe-users") || args.includes("--wipeUsers")) {
|
44
45
|
wipeUsers = true;
|
45
46
|
}
|
47
|
+
if (args.includes("--write-data") || args.includes("--writeData")) {
|
48
|
+
shouldWriteFile = true;
|
49
|
+
}
|
46
50
|
if (args.includes("--init")) {
|
47
51
|
await controller.run({
|
48
52
|
runProd: runProd,
|
@@ -56,6 +60,7 @@ async function main() {
|
|
56
60
|
generateMockData: false,
|
57
61
|
importData: false,
|
58
62
|
checkDuplicates: false,
|
63
|
+
shouldWriteFile: shouldWriteFile,
|
59
64
|
});
|
60
65
|
} else {
|
61
66
|
await controller.run({
|
@@ -70,6 +75,7 @@ async function main() {
|
|
70
75
|
wipeUsers: wipeUsers,
|
71
76
|
importData: importData,
|
72
77
|
checkDuplicates: false,
|
78
|
+
shouldWriteFile: shouldWriteFile,
|
73
79
|
});
|
74
80
|
}
|
75
81
|
}
|
@@ -63,19 +63,22 @@ export class DataLoader {
|
|
63
63
|
private emailToUserIdMap = new Map<string, string>();
|
64
64
|
private phoneToUserIdMap = new Map<string, string>();
|
65
65
|
userExistsMap = new Map<string, boolean>();
|
66
|
+
private shouldWriteFile = false;
|
66
67
|
|
67
68
|
// Constructor to initialize the DataLoader with necessary configurations
|
68
69
|
constructor(
|
69
70
|
appwriteFolderPath: string,
|
70
71
|
importDataActions: ImportDataActions,
|
71
72
|
database: Databases,
|
72
|
-
config: AppwriteConfig
|
73
|
+
config: AppwriteConfig,
|
74
|
+
shouldWriteFile?: boolean
|
73
75
|
) {
|
74
76
|
this.appwriteFolderPath = appwriteFolderPath;
|
75
77
|
this.importDataActions = importDataActions;
|
76
78
|
this.database = database;
|
77
79
|
this.usersController = new UsersController(config, database);
|
78
80
|
this.config = config;
|
81
|
+
this.shouldWriteFile = shouldWriteFile || false;
|
79
82
|
}
|
80
83
|
|
81
84
|
// Helper method to generate a consistent key for collections
|
@@ -213,7 +216,14 @@ export class DataLoader {
|
|
213
216
|
async getAllUsers() {
|
214
217
|
const users = new UsersController(this.config, this.database);
|
215
218
|
const allUsers = await users.getAllUsers();
|
219
|
+
// Iterate over the users and setup our maps ahead of time for email and phone
|
216
220
|
for (const user of allUsers) {
|
221
|
+
if (user.email) {
|
222
|
+
this.emailToUserIdMap.set(user.email, user.$id);
|
223
|
+
}
|
224
|
+
if (user.phone) {
|
225
|
+
this.phoneToUserIdMap.set(user.phone, user.$id);
|
226
|
+
}
|
217
227
|
this.userExistsMap.set(user.$id, true);
|
218
228
|
}
|
219
229
|
return allUsers;
|
@@ -227,15 +237,6 @@ export class DataLoader {
|
|
227
237
|
await this.setupMaps(dbId);
|
228
238
|
const allUsers = await this.getAllUsers();
|
229
239
|
console.log(`Fetched ${allUsers.length} users`);
|
230
|
-
// Iterate over the users and setup our maps ahead of time for email and phone
|
231
|
-
for (const user of allUsers) {
|
232
|
-
if (user.email) {
|
233
|
-
this.emailToUserIdMap.set(user.email, user.$id);
|
234
|
-
}
|
235
|
-
if (user.phone) {
|
236
|
-
this.phoneToUserIdMap.set(user.phone, user.$id);
|
237
|
-
}
|
238
|
-
}
|
239
240
|
// Iterate over the configured databases to find the matching one
|
240
241
|
for (const db of this.config.databases) {
|
241
242
|
if (db.$id !== dbId) {
|
@@ -290,7 +291,9 @@ export class DataLoader {
|
|
290
291
|
console.log("---------------------------------");
|
291
292
|
console.log(`Data setup for database: ${dbId} completed`);
|
292
293
|
console.log("---------------------------------");
|
293
|
-
|
294
|
+
if (this.shouldWriteFile) {
|
295
|
+
this.writeMapsToJsonFile();
|
296
|
+
}
|
294
297
|
}
|
295
298
|
|
296
299
|
async updateReferencesInRelatedCollections() {
|
@@ -389,31 +392,6 @@ export class DataLoader {
|
|
389
392
|
}
|
390
393
|
}
|
391
394
|
|
392
|
-
updateObjectWithNonNullishValues(
|
393
|
-
sourceObject: Record<string, any>,
|
394
|
-
inputObject: Record<string, any>
|
395
|
-
): void {
|
396
|
-
// Iterate through the keys of the inputObject
|
397
|
-
for (const key of Object.keys(inputObject)) {
|
398
|
-
const inputValue = inputObject[key];
|
399
|
-
const sourceValue = sourceObject[key];
|
400
|
-
|
401
|
-
// Check if the inputObject's value for the current key is non-nullish
|
402
|
-
// and either the key doesn't exist in the sourceObject or its value is nullish
|
403
|
-
if (
|
404
|
-
inputValue !== null &&
|
405
|
-
inputValue !== undefined &&
|
406
|
-
inputValue !== "" &&
|
407
|
-
(sourceValue === null ||
|
408
|
-
sourceValue === undefined ||
|
409
|
-
sourceValue === "")
|
410
|
-
) {
|
411
|
-
// Update the sourceObject with the inputObject's value for the current key
|
412
|
-
sourceObject[key] = inputValue;
|
413
|
-
}
|
414
|
-
}
|
415
|
-
}
|
416
|
-
|
417
395
|
// async updateReferencesInRelatedCollections() {
|
418
396
|
// // Process each collection defined in the config
|
419
397
|
// for (const collection of this.config.collections) {
|
@@ -471,46 +449,46 @@ export class DataLoader {
|
|
471
449
|
// }
|
472
450
|
// }
|
473
451
|
|
474
|
-
|
475
|
-
|
476
|
-
|
452
|
+
private writeMapsToJsonFile() {
|
453
|
+
const outputDir = path.resolve(process.cwd());
|
454
|
+
const outputFile = path.join(outputDir, "dataLoaderOutput.json");
|
477
455
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
|
456
|
+
const dataToWrite = {
|
457
|
+
dataFromCollections: Array.from(this.importMap.entries()).map(
|
458
|
+
([key, value]) => {
|
459
|
+
return {
|
460
|
+
collection: key,
|
461
|
+
data: value.data.map((item: any) => item.finalData),
|
462
|
+
};
|
463
|
+
}
|
464
|
+
),
|
465
|
+
// Convert Maps to arrays of entries for serialization
|
466
|
+
mergedUserMap: Array.from(this.mergedUserMap.entries()),
|
467
|
+
// emailToUserIdMap: Array.from(this.emailToUserIdMap.entries()),
|
468
|
+
// phoneToUserIdMap: Array.from(this.phoneToUserIdMap.entries()),
|
469
|
+
};
|
492
470
|
|
493
|
-
|
494
|
-
|
495
|
-
|
496
|
-
|
497
|
-
|
498
|
-
|
499
|
-
|
471
|
+
// Use JSON.stringify with a replacer function to handle Maps
|
472
|
+
const replacer = (key: any, value: any) => {
|
473
|
+
if (value instanceof Map) {
|
474
|
+
return Array.from(value.entries());
|
475
|
+
}
|
476
|
+
return value;
|
477
|
+
};
|
500
478
|
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
479
|
+
fs.writeFile(
|
480
|
+
outputFile,
|
481
|
+
JSON.stringify(dataToWrite, replacer, 2),
|
482
|
+
"utf8",
|
483
|
+
(err) => {
|
484
|
+
if (err) {
|
485
|
+
console.error("Error writing data to JSON file:", err);
|
486
|
+
return;
|
487
|
+
}
|
488
|
+
console.log(`Data successfully written to ${outputFile}`);
|
489
|
+
}
|
490
|
+
);
|
491
|
+
}
|
514
492
|
|
515
493
|
/**
|
516
494
|
* Prepares user data by checking for duplicates based on email or phone, adding to a duplicate map if found,
|
@@ -630,6 +608,9 @@ export class DataLoader {
|
|
630
608
|
this.oldIdToNewIdPerCollectionMap
|
631
609
|
.set(this.getCollectionKey(collection.name), oldIdToNewIdMap)
|
632
610
|
.get(this.getCollectionKey(collection.name));
|
611
|
+
console.log(
|
612
|
+
`${collection.name} -- collectionOldIdToNewIdMap: ${collectionOldIdToNewIdMap}`
|
613
|
+
);
|
633
614
|
if (!operationId) {
|
634
615
|
throw new Error(
|
635
616
|
`No import operation found for collection ${collection.name}`
|
@@ -679,7 +660,6 @@ export class DataLoader {
|
|
679
660
|
// No existing user ID, generate a new unique ID
|
680
661
|
existingId = this.getTrueUniqueId(this.getCollectionKey("users"));
|
681
662
|
transformedItem.userId = existingId; // Assign the new ID to the transformed data's userId field
|
682
|
-
transformedItem.docId = existingId;
|
683
663
|
}
|
684
664
|
|
685
665
|
// Create a context object for the item, including the new ID
|
@@ -699,10 +679,14 @@ export class DataLoader {
|
|
699
679
|
?.has(`${oldId}`)
|
700
680
|
) {
|
701
681
|
// Found a duplicate oldId, now decide how to merge or handle these duplicates
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
682
|
+
for (const data of currentData.data) {
|
683
|
+
if (
|
684
|
+
data.finalData.docId === oldId ||
|
685
|
+
data.finalData.userId === oldId
|
686
|
+
) {
|
687
|
+
Object.assign(data.finalData, transformedItem);
|
688
|
+
}
|
689
|
+
}
|
706
690
|
} else {
|
707
691
|
// No duplicate found, simply map the oldId to the new itemId
|
708
692
|
collectionOldIdToNewIdMap?.set(`${oldId}`, `${existingId}`);
|
@@ -718,10 +702,8 @@ export class DataLoader {
|
|
718
702
|
currentUserData.data[i].finalData.userId === existingId) &&
|
719
703
|
!_.isEqual(currentUserData.data[i], userData)
|
720
704
|
) {
|
721
|
-
|
722
|
-
|
723
|
-
userData.finalData
|
724
|
-
);
|
705
|
+
Object.assign(currentUserData.data[i].finalData, transformedItem);
|
706
|
+
Object.assign(currentUserData.data[i].rawData, item);
|
725
707
|
console.log("Merging user data", currentUserData.data[i].finalData);
|
726
708
|
this.importMap.set(this.getCollectionKey("users"), currentUserData);
|
727
709
|
}
|
@@ -744,10 +726,10 @@ export class DataLoader {
|
|
744
726
|
currentData.data[i].finalData.docId === existingId ||
|
745
727
|
currentData.data[i].finalData.userId === existingId
|
746
728
|
) {
|
747
|
-
|
748
|
-
currentData.data[i].finalData,
|
749
|
-
transformedItem
|
750
|
-
|
729
|
+
currentData.data[i].finalData = {
|
730
|
+
...currentData.data[i].finalData,
|
731
|
+
...transformedItem,
|
732
|
+
};
|
751
733
|
currentData.data[i].importDef = newImportDef;
|
752
734
|
this.importMap.set(
|
753
735
|
this.getCollectionKey(collection.name),
|
@@ -876,9 +858,6 @@ export class DataLoader {
|
|
876
858
|
finalData: transformedData,
|
877
859
|
});
|
878
860
|
this.importMap.set(this.getCollectionKey(collection.name), currentData);
|
879
|
-
console.log(
|
880
|
-
`Set import map for ${collection.name}, length is ${currentData.data.length}`
|
881
|
-
);
|
882
861
|
this.oldIdToNewIdPerCollectionMap.set(
|
883
862
|
this.getCollectionKey(collection.name),
|
884
863
|
collectionOldIdToNewIdMap!
|
@@ -962,13 +941,12 @@ export class DataLoader {
|
|
962
941
|
}
|
963
942
|
}
|
964
943
|
} else {
|
965
|
-
|
966
|
-
|
967
|
-
|
968
|
-
|
969
|
-
(dataItem) => dataItem.rawData[importDef.primaryKeyField] === oldId
|
944
|
+
logger.error(
|
945
|
+
`No old ID found (to update another document with) in prepareUpdateData for ${
|
946
|
+
collection.name
|
947
|
+
}, ${JSON.stringify(item, null, 2)}`
|
970
948
|
);
|
971
|
-
|
949
|
+
continue;
|
972
950
|
}
|
973
951
|
// Log an error and continue to the next item if no new ID is found
|
974
952
|
if (!newId) {
|
@@ -107,7 +107,8 @@ export class ImportController {
|
|
107
107
|
this.appwriteFolderPath,
|
108
108
|
this.importDataActions,
|
109
109
|
this.database,
|
110
|
-
this.config
|
110
|
+
this.config,
|
111
|
+
this.setupOptions.shouldWriteFile
|
111
112
|
);
|
112
113
|
await dataLoader.start(db.$id);
|
113
114
|
await this.importCollections(db, dataLoader);
|
@@ -159,7 +160,8 @@ export class ImportController {
|
|
159
160
|
if (userBatch.finalData && userBatch.finalData.length > 0) {
|
160
161
|
const userId = userBatch.finalData.userId;
|
161
162
|
if (dataLoader.userExistsMap.has(userId)) {
|
162
|
-
|
163
|
+
// We only are storing the existing user ID's as true, so we need to check for that
|
164
|
+
if (!(dataLoader.userExistsMap.get(userId) === true)) {
|
163
165
|
return usersController
|
164
166
|
.createUserAndReturn(userBatch.finalData)
|
165
167
|
.then(() => console.log("Created user"))
|
@@ -173,6 +175,7 @@ export class ImportController {
|
|
173
175
|
throw error;
|
174
176
|
});
|
175
177
|
} else {
|
178
|
+
console.log("Skipped existing user: ", userId);
|
176
179
|
return Promise.resolve();
|
177
180
|
}
|
178
181
|
}
|