appwrite-utils-cli 0.0.33 → 0.0.34
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 +8 -3
- package/dist/migrations/dataLoader.d.ts +18 -0
- package/dist/migrations/dataLoader.js +49 -27
- package/dist/migrations/importController.js +54 -50
- package/dist/migrations/users.js +1 -0
- package/package.json +2 -2
- package/src/migrations/dataLoader.ts +46 -28
- package/src/migrations/importController.ts +62 -63
- package/src/migrations/users.ts +1 -0
package/README.md
CHANGED
|
@@ -55,17 +55,21 @@ Replace `--args` with the appropriate options:
|
|
|
55
55
|
- `--staging`: Run tasks in the staging environment.
|
|
56
56
|
- `--dev`: Run tasks in the development environment.
|
|
57
57
|
- `--wipe`: Wipe all databases.
|
|
58
|
-
- `--wipe-docs
|
|
58
|
+
- `--wipe-docs`: Wipe all documents in the databases.
|
|
59
59
|
- `--generate`: Generate TypeScript schemas from database schemas.
|
|
60
60
|
- `--import`: Import data into your databases.
|
|
61
61
|
- `--backup`: Perform a backup of your databases.
|
|
62
|
-
- `--wipe-users
|
|
63
|
-
- `--write-data
|
|
62
|
+
- `--wipe-users`: Wipe all user data.
|
|
63
|
+
- `--write-data`: Write converted imported data to file
|
|
64
64
|
- `--sync`: Synchronize your project's config and generate schema for your database
|
|
65
65
|
- `--endpoint`: Set a different endpoint for the migration target
|
|
66
66
|
- `--project`: Set a different project ID for the migration target
|
|
67
67
|
- `--key`: Set a different API key for the migration target
|
|
68
68
|
|
|
69
|
+
## If you run out of RAM
|
|
70
|
+
|
|
71
|
+
This happens because Node only allocates 4 GB, it'll only happen if you're importing a ton of data, but you can use `export NODE_OPTIONS="--max-old-space-size=16384"` or the relevant command for your system if that doesn't work, and it'll set the env var to whatever. That's 16 GB, I would recommend `8192` for most.
|
|
72
|
+
|
|
69
73
|
### OpenAPI Generation (almost done, in progress)
|
|
70
74
|
|
|
71
75
|
Recently, I have also added an optional OpenAPI generation for each attribute in the schema. This is because I needed it and because I felt it would be nice to have. This is done using [this package](https://github.com/asteasolutions/zod-to-openapi), many thanks to them.
|
|
@@ -82,6 +86,7 @@ This setup ensures that developers have robust tools at their fingertips to mana
|
|
|
82
86
|
|
|
83
87
|
### Changelog
|
|
84
88
|
|
|
89
|
+
- 0.0.34: Fixed the `bin` section of the package.json, apparently you can't use `node` to run it
|
|
85
90
|
- 0.0.33: Fixed `idMappings`, if you are importing data and use the `idMappings` functionality, you can set a `fieldToSet` based on the value of a `sourceField` in the current imported items data (whether it's in the final data or the original), in order to match another field in another collection. So if you had a store, and it had items and the items have a Region ID for instance. You can then, in your regionId of the items, setup an `idMapping` that will allow you to map the value of the `targetField` based on the value of the `targetFieldToMatch` in the `targetCollection`. Sounds complex, but it's very useful. Like psuedo-relationship resolution, without the relationships.
|
|
86
91
|
- 0.0.29: If you use the `description` variable in an attribute and collection, it'll add that description to the generated schemas. This assumes you have `zod-to-openpi`
|
|
87
92
|
- 0.0.275: THINGS ARE NOW IN TYPESCRIPT WOOHOO. No but for reaal, super happy to report that everything has been converted to TypeScript, just way too many changes, I hope you enjoy it!
|
|
@@ -61,6 +61,14 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
|
61
61
|
required: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
62
62
|
array: z.ZodOptional<z.ZodDefault<z.ZodBoolean>>;
|
|
63
63
|
min: z.ZodOptional<z.ZodNumber>;
|
|
64
|
+
/**
|
|
65
|
+
* Prepares the data for creating documents in a collection.
|
|
66
|
+
* This involves loading the data, transforming it, and handling ID mappings.
|
|
67
|
+
*
|
|
68
|
+
* @param db - The database configuration.
|
|
69
|
+
* @param collection - The collection configuration.
|
|
70
|
+
* @param importDef - The import definition containing the attribute mappings and other relevant info.
|
|
71
|
+
*/
|
|
64
72
|
max: z.ZodOptional<z.ZodNumber>;
|
|
65
73
|
xdefault: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
66
74
|
description: z.ZodOptional<z.ZodNullable<z.ZodUnion<[z.ZodString, z.ZodRecord<z.ZodString, z.ZodString>]>>>;
|
|
@@ -359,6 +367,16 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
|
359
367
|
type: z.ZodOptional<z.ZodDefault<z.ZodEnum<["create", "update"]>>>;
|
|
360
368
|
filePath: z.ZodString;
|
|
361
369
|
basePath: z.ZodOptional<z.ZodString>;
|
|
370
|
+
/**
|
|
371
|
+
* Generates attribute mappings with post-import actions based on the provided attribute mappings.
|
|
372
|
+
* This method checks each mapping for a fileData attribute and adds a post-import action to create a file
|
|
373
|
+
* and update the field with the file's ID if necessary.
|
|
374
|
+
*
|
|
375
|
+
* @param attributeMappings - The attribute mappings from the import definition.
|
|
376
|
+
* @param context - The context object containing information about the database, collection, and document.
|
|
377
|
+
* @param item - The item being imported, used for resolving template paths in fileData mappings.
|
|
378
|
+
* @returns The attribute mappings updated with any necessary post-import actions.
|
|
379
|
+
*/
|
|
362
380
|
primaryKeyField: z.ZodDefault<z.ZodString>;
|
|
363
381
|
idMappings: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
364
382
|
sourceField: z.ZodString;
|
|
@@ -92,7 +92,7 @@ export class DataLoader {
|
|
|
92
92
|
result[key] = updateValue;
|
|
93
93
|
}
|
|
94
94
|
// If the update value is nullish, keep the original value unless it doesn't exist
|
|
95
|
-
else if (sourceValue === undefined) {
|
|
95
|
+
else if (sourceValue === undefined || sourceValue === null) {
|
|
96
96
|
result[key] = updateValue;
|
|
97
97
|
}
|
|
98
98
|
});
|
|
@@ -314,10 +314,10 @@ export class DataLoader {
|
|
|
314
314
|
// Process each item in the collection
|
|
315
315
|
collectionData.data.forEach((item) => {
|
|
316
316
|
const oldId = item.context[idMapping.sourceField];
|
|
317
|
-
const newId = this.mergedUserMap.get(oldId);
|
|
317
|
+
const newId = this.mergedUserMap.get(`${oldId}`);
|
|
318
318
|
if (newId) {
|
|
319
319
|
// Update context to use new user ID
|
|
320
|
-
item.
|
|
320
|
+
item.finalData[idMapping.fieldToSet || idMapping.sourceField] = newId;
|
|
321
321
|
}
|
|
322
322
|
});
|
|
323
323
|
}
|
|
@@ -398,13 +398,14 @@ export class DataLoader {
|
|
|
398
398
|
if (!currentDataFiltered) {
|
|
399
399
|
// Set new data if current data is undefined
|
|
400
400
|
collectionData.data[i].finalData[fieldToSetKey] =
|
|
401
|
-
Array.isArray(newData) ? newData :
|
|
401
|
+
Array.isArray(newData) ? newData[0] : newData;
|
|
402
402
|
}
|
|
403
403
|
else if (Array.isArray(newData) && newData.length > 0) {
|
|
404
404
|
// Convert current data to array and merge if new data is non-empty array, then filter for uniqueness
|
|
405
|
+
// and take the first value, because it's an array and the attribute is not an array
|
|
405
406
|
collectionData.data[i].finalData[fieldToSetKey] = [
|
|
406
407
|
...new Set([currentDataFiltered, ...newData].filter((value) => `${value}` !== `${valueToMatch}`)),
|
|
407
|
-
];
|
|
408
|
+
].slice(0, 1)[0];
|
|
408
409
|
}
|
|
409
410
|
else if (!Array.isArray(newData) && newData !== undefined) {
|
|
410
411
|
// Simply update the field if new data is not an array and defined
|
|
@@ -467,7 +468,6 @@ export class DataLoader {
|
|
|
467
468
|
* @returns The transformed item with user-specific keys removed.
|
|
468
469
|
*/
|
|
469
470
|
async prepareUserData(item, attributeMappings, primaryKeyField, newId) {
|
|
470
|
-
// Transform the item data based on the attribute mappings
|
|
471
471
|
let transformedItem = this.transformData(item, attributeMappings);
|
|
472
472
|
const userData = AuthUserCreateSchema.safeParse(transformedItem);
|
|
473
473
|
if (!userData.success) {
|
|
@@ -477,35 +477,58 @@ export class DataLoader {
|
|
|
477
477
|
const email = userData.data.email;
|
|
478
478
|
const phone = userData.data.phone;
|
|
479
479
|
let existingId;
|
|
480
|
-
// Check for duplicate email and
|
|
481
|
-
if (email && email
|
|
482
|
-
|
|
483
|
-
existingId = this.emailToUserIdMap.get(email);
|
|
484
|
-
}
|
|
485
|
-
else {
|
|
486
|
-
this.emailToUserIdMap.set(email, newId);
|
|
487
|
-
}
|
|
480
|
+
// Check for duplicate email and phone
|
|
481
|
+
if (email && this.emailToUserIdMap.has(email)) {
|
|
482
|
+
existingId = this.emailToUserIdMap.get(email);
|
|
488
483
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
if (this.phoneToUserIdMap.has(phone)) {
|
|
492
|
-
existingId = this.phoneToUserIdMap.get(phone);
|
|
493
|
-
}
|
|
494
|
-
else {
|
|
495
|
-
this.phoneToUserIdMap.set(phone, newId);
|
|
496
|
-
}
|
|
484
|
+
else if (phone && this.phoneToUserIdMap.has(phone)) {
|
|
485
|
+
existingId = this.phoneToUserIdMap.get(phone);
|
|
497
486
|
}
|
|
498
|
-
|
|
499
|
-
|
|
487
|
+
else {
|
|
488
|
+
if (email)
|
|
489
|
+
this.emailToUserIdMap.set(email, newId);
|
|
490
|
+
if (phone)
|
|
491
|
+
this.phoneToUserIdMap.set(phone, newId);
|
|
500
492
|
}
|
|
501
|
-
// If existingId is found, add to mergedUserMap
|
|
502
493
|
if (existingId) {
|
|
503
494
|
userData.data.userId = existingId;
|
|
504
495
|
const mergedUsers = this.mergedUserMap.get(existingId) || [];
|
|
505
496
|
mergedUsers.push(`${item[primaryKeyField]}`);
|
|
506
497
|
this.mergedUserMap.set(existingId, mergedUsers);
|
|
498
|
+
const userFound = this.importMap
|
|
499
|
+
.get(this.getCollectionKey("users"))
|
|
500
|
+
?.data.find((userDataExisting) => {
|
|
501
|
+
let userIdToMatch;
|
|
502
|
+
if (userDataExisting?.finalData?.userId) {
|
|
503
|
+
userIdToMatch = userDataExisting?.finalData?.userId;
|
|
504
|
+
}
|
|
505
|
+
else if (userDataExisting?.finalData?.docId) {
|
|
506
|
+
userIdToMatch = userDataExisting?.finalData?.docId;
|
|
507
|
+
}
|
|
508
|
+
else if (userDataExisting?.context?.userId) {
|
|
509
|
+
userIdToMatch = userDataExisting.context.userId;
|
|
510
|
+
}
|
|
511
|
+
else if (userDataExisting?.context?.docId) {
|
|
512
|
+
userIdToMatch = userDataExisting.context.docId;
|
|
513
|
+
}
|
|
514
|
+
return userIdToMatch === existingId;
|
|
515
|
+
});
|
|
516
|
+
if (userFound) {
|
|
517
|
+
userFound.finalData.userId = existingId;
|
|
518
|
+
}
|
|
519
|
+
return [
|
|
520
|
+
transformedItem,
|
|
521
|
+
existingId,
|
|
522
|
+
{
|
|
523
|
+
rawData: userFound?.rawData,
|
|
524
|
+
finalData: userFound?.finalData,
|
|
525
|
+
},
|
|
526
|
+
];
|
|
527
|
+
}
|
|
528
|
+
else {
|
|
529
|
+
existingId = newId;
|
|
530
|
+
userData.data.userId = existingId;
|
|
507
531
|
}
|
|
508
|
-
// Remove user-specific keys from the transformed item
|
|
509
532
|
const userKeys = ["email", "phone", "name", "labels", "prefs"];
|
|
510
533
|
userKeys.forEach((key) => {
|
|
511
534
|
if (transformedItem.hasOwnProperty(key)) {
|
|
@@ -517,7 +540,6 @@ export class DataLoader {
|
|
|
517
540
|
rawData: item,
|
|
518
541
|
finalData: userData.data,
|
|
519
542
|
};
|
|
520
|
-
// Directly update the importMap with the new user data, without pushing to usersMap.data first
|
|
521
543
|
this.importMap.set(this.getCollectionKey("users"), {
|
|
522
544
|
data: [...(usersMap?.data || []), userDataToAdd],
|
|
523
545
|
});
|
|
@@ -86,44 +86,58 @@ export class ImportController {
|
|
|
86
86
|
const usersData = usersDataMap?.data;
|
|
87
87
|
const usersController = new UsersController(this.config, this.database);
|
|
88
88
|
if (usersData) {
|
|
89
|
-
console.log("Found users data");
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
.
|
|
97
|
-
|
|
98
|
-
const userId = userBatch.finalData.userId;
|
|
99
|
-
if (dataLoader.userExistsMap.has(userId)) {
|
|
100
|
-
// We only are storing the existing user ID's as true, so we need to check for that
|
|
101
|
-
if (!(dataLoader.userExistsMap.get(userId) === true)) {
|
|
102
|
-
const userId = userBatch.finalData.userId ||
|
|
103
|
-
userBatch.context.userId ||
|
|
104
|
-
userBatch.context.docId;
|
|
105
|
-
if (!userBatch.finalData.userId) {
|
|
106
|
-
userBatch.finalData.userId = userId;
|
|
107
|
-
}
|
|
108
|
-
return usersController
|
|
109
|
-
.createUserAndReturn(userBatch.finalData)
|
|
110
|
-
.then(() => console.log("Created user"))
|
|
111
|
-
.catch((error) => {
|
|
112
|
-
logger.error("Error creating user:", error, "\nUser data is ", userBatch.finalData);
|
|
113
|
-
});
|
|
114
|
-
}
|
|
115
|
-
else {
|
|
116
|
-
console.log("Skipped existing user: ", userId);
|
|
117
|
-
return Promise.resolve();
|
|
118
|
-
}
|
|
119
|
-
}
|
|
89
|
+
console.log("Found users data", usersData.length);
|
|
90
|
+
const userDataBatches = createBatches(usersData);
|
|
91
|
+
for (const batch of userDataBatches) {
|
|
92
|
+
console.log("Importing users batch", batch.length);
|
|
93
|
+
const userBatchPromises = batch
|
|
94
|
+
.filter((item) => {
|
|
95
|
+
let itemId;
|
|
96
|
+
if (item.finalData.userId) {
|
|
97
|
+
itemId = item.finalData.userId;
|
|
120
98
|
}
|
|
99
|
+
else if (item.finalData.docId) {
|
|
100
|
+
itemId = item.finalData.docId;
|
|
101
|
+
}
|
|
102
|
+
if (!itemId) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
return (item &&
|
|
106
|
+
item.finalData &&
|
|
107
|
+
!dataLoader.userExistsMap.has(itemId));
|
|
121
108
|
})
|
|
122
|
-
.
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
109
|
+
.map((item) => {
|
|
110
|
+
return usersController.createUserAndReturn(item.finalData);
|
|
111
|
+
});
|
|
112
|
+
await Promise.all(userBatchPromises);
|
|
113
|
+
for (const item of batch) {
|
|
114
|
+
if (item && item.finalData) {
|
|
115
|
+
dataLoader.userExistsMap.set(item.finalData.userId ||
|
|
116
|
+
item.finalData.docId ||
|
|
117
|
+
item.context.userId ||
|
|
118
|
+
item.context.docId, true);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
console.log("Finished importing users batch", batch.length);
|
|
126
122
|
}
|
|
123
|
+
// for (let i = 0; i < usersData.length; i++) {
|
|
124
|
+
// const user = usersData[i];
|
|
125
|
+
// if (user.finalData) {
|
|
126
|
+
// const userId =
|
|
127
|
+
// user.finalData.userId ||
|
|
128
|
+
// user.context.userId ||
|
|
129
|
+
// user.context.docId;
|
|
130
|
+
// if (!dataLoader.userExistsMap.has(userId)) {
|
|
131
|
+
// if (!user.finalData.userId) {
|
|
132
|
+
// user.finalData.userId = userId;
|
|
133
|
+
// }
|
|
134
|
+
// await usersController.createUserAndReturn(user.finalData);
|
|
135
|
+
// dataLoader.userExistsMap.set(userId, true);
|
|
136
|
+
// } else {
|
|
137
|
+
// console.log("Skipped existing user: ", userId);
|
|
138
|
+
// }
|
|
139
|
+
// }
|
|
140
|
+
// }
|
|
127
141
|
console.log("Finished importing users");
|
|
128
142
|
}
|
|
129
143
|
}
|
|
@@ -146,10 +160,10 @@ export class ImportController {
|
|
|
146
160
|
const batches = dataSplit[i];
|
|
147
161
|
console.log(`Processing batch ${i + 1} of ${dataSplit.length}`);
|
|
148
162
|
const batchPromises = batches.map((item) => {
|
|
149
|
-
const id = item.
|
|
150
|
-
item.
|
|
151
|
-
item.
|
|
152
|
-
item.
|
|
163
|
+
const id = item.finalData.docId ||
|
|
164
|
+
item.finalData.userId ||
|
|
165
|
+
item.context.docId ||
|
|
166
|
+
item.context.userId;
|
|
153
167
|
if (item.finalData.hasOwnProperty("userId")) {
|
|
154
168
|
delete item.finalData.userId;
|
|
155
169
|
}
|
|
@@ -159,17 +173,7 @@ export class ImportController {
|
|
|
159
173
|
if (!item.finalData) {
|
|
160
174
|
return Promise.resolve();
|
|
161
175
|
}
|
|
162
|
-
return this.database
|
|
163
|
-
.createDocument(db.$id, collection.$id, id, item.finalData)
|
|
164
|
-
.then(() => {
|
|
165
|
-
processedItems++;
|
|
166
|
-
console.log("Created item");
|
|
167
|
-
})
|
|
168
|
-
.catch((error) => {
|
|
169
|
-
console.error(`Error creating item in ${collection.name}:`, error, "\nItem data is ", item.finalData);
|
|
170
|
-
throw error;
|
|
171
|
-
// Optionally, log the failed item for retry or review
|
|
172
|
-
});
|
|
176
|
+
return this.database.createDocument(db.$id, collection.$id, id, item.finalData);
|
|
173
177
|
});
|
|
174
178
|
// Wait for all promises in the current batch to resolve
|
|
175
179
|
await Promise.allSettled(batchPromises);
|
package/dist/migrations/users.js
CHANGED
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.34",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
],
|
|
23
23
|
"bin": {
|
|
24
24
|
"appwrite-init": "./dist/init.js",
|
|
25
|
-
"appwrite-migrate": "
|
|
25
|
+
"appwrite-migrate": "./dist/main.js"
|
|
26
26
|
},
|
|
27
27
|
"scripts": {
|
|
28
28
|
"build": "bun run tsc",
|
|
@@ -124,7 +124,7 @@ export class DataLoader {
|
|
|
124
124
|
result[key] = updateValue;
|
|
125
125
|
}
|
|
126
126
|
// If the update value is nullish, keep the original value unless it doesn't exist
|
|
127
|
-
else if (sourceValue === undefined) {
|
|
127
|
+
else if (sourceValue === undefined || sourceValue === null) {
|
|
128
128
|
result[key] = updateValue;
|
|
129
129
|
}
|
|
130
130
|
});
|
|
@@ -395,11 +395,13 @@ export class DataLoader {
|
|
|
395
395
|
// Process each item in the collection
|
|
396
396
|
collectionData.data.forEach((item) => {
|
|
397
397
|
const oldId = item.context[idMapping.sourceField];
|
|
398
|
-
const newId = this.mergedUserMap.get(oldId);
|
|
398
|
+
const newId = this.mergedUserMap.get(`${oldId}`);
|
|
399
399
|
|
|
400
400
|
if (newId) {
|
|
401
401
|
// Update context to use new user ID
|
|
402
|
-
item.
|
|
402
|
+
item.finalData[
|
|
403
|
+
idMapping.fieldToSet || idMapping.sourceField
|
|
404
|
+
] = newId;
|
|
403
405
|
}
|
|
404
406
|
});
|
|
405
407
|
}
|
|
@@ -524,16 +526,17 @@ export class DataLoader {
|
|
|
524
526
|
if (!currentDataFiltered) {
|
|
525
527
|
// Set new data if current data is undefined
|
|
526
528
|
collectionData.data[i].finalData[fieldToSetKey] =
|
|
527
|
-
Array.isArray(newData) ? newData :
|
|
529
|
+
Array.isArray(newData) ? newData[0] : newData;
|
|
528
530
|
} else if (Array.isArray(newData) && newData.length > 0) {
|
|
529
531
|
// Convert current data to array and merge if new data is non-empty array, then filter for uniqueness
|
|
532
|
+
// and take the first value, because it's an array and the attribute is not an array
|
|
530
533
|
collectionData.data[i].finalData[fieldToSetKey] = [
|
|
531
534
|
...new Set(
|
|
532
535
|
[currentDataFiltered, ...newData].filter(
|
|
533
536
|
(value: any) => `${value}` !== `${valueToMatch}`
|
|
534
537
|
)
|
|
535
538
|
),
|
|
536
|
-
];
|
|
539
|
+
].slice(0, 1)[0];
|
|
537
540
|
} else if (!Array.isArray(newData) && newData !== undefined) {
|
|
538
541
|
// Simply update the field if new data is not an array and defined
|
|
539
542
|
collectionData.data[i].finalData[fieldToSetKey] = newData;
|
|
@@ -615,7 +618,6 @@ export class DataLoader {
|
|
|
615
618
|
primaryKeyField: string,
|
|
616
619
|
newId: string
|
|
617
620
|
): Promise<any> {
|
|
618
|
-
// Transform the item data based on the attribute mappings
|
|
619
621
|
let transformedItem = this.transformData(item, attributeMappings);
|
|
620
622
|
const userData = AuthUserCreateSchema.safeParse(transformedItem);
|
|
621
623
|
if (!userData.success) {
|
|
@@ -632,48 +634,64 @@ export class DataLoader {
|
|
|
632
634
|
const phone = userData.data.phone;
|
|
633
635
|
let existingId: string | undefined;
|
|
634
636
|
|
|
635
|
-
// Check for duplicate email and
|
|
636
|
-
if (email && email
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
// Check for duplicate phone and add to phoneToUserIdMap if not found
|
|
645
|
-
if (phone && phone.length > 0) {
|
|
646
|
-
if (this.phoneToUserIdMap.has(phone)) {
|
|
647
|
-
existingId = this.phoneToUserIdMap.get(phone);
|
|
648
|
-
} else {
|
|
649
|
-
this.phoneToUserIdMap.set(phone, newId);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
if (!existingId) {
|
|
653
|
-
existingId = newId;
|
|
637
|
+
// Check for duplicate email and phone
|
|
638
|
+
if (email && this.emailToUserIdMap.has(email)) {
|
|
639
|
+
existingId = this.emailToUserIdMap.get(email);
|
|
640
|
+
} else if (phone && this.phoneToUserIdMap.has(phone)) {
|
|
641
|
+
existingId = this.phoneToUserIdMap.get(phone);
|
|
642
|
+
} else {
|
|
643
|
+
if (email) this.emailToUserIdMap.set(email, newId);
|
|
644
|
+
if (phone) this.phoneToUserIdMap.set(phone, newId);
|
|
654
645
|
}
|
|
655
646
|
|
|
656
|
-
// If existingId is found, add to mergedUserMap
|
|
657
647
|
if (existingId) {
|
|
658
648
|
userData.data.userId = existingId;
|
|
659
649
|
const mergedUsers = this.mergedUserMap.get(existingId) || [];
|
|
660
650
|
mergedUsers.push(`${item[primaryKeyField]}`);
|
|
661
651
|
this.mergedUserMap.set(existingId, mergedUsers);
|
|
652
|
+
const userFound = this.importMap
|
|
653
|
+
.get(this.getCollectionKey("users"))
|
|
654
|
+
?.data.find((userDataExisting) => {
|
|
655
|
+
let userIdToMatch: string | undefined;
|
|
656
|
+
if (userDataExisting?.finalData?.userId) {
|
|
657
|
+
userIdToMatch = userDataExisting?.finalData?.userId;
|
|
658
|
+
} else if (userDataExisting?.finalData?.docId) {
|
|
659
|
+
userIdToMatch = userDataExisting?.finalData?.docId;
|
|
660
|
+
} else if (userDataExisting?.context?.userId) {
|
|
661
|
+
userIdToMatch = userDataExisting.context.userId;
|
|
662
|
+
} else if (userDataExisting?.context?.docId) {
|
|
663
|
+
userIdToMatch = userDataExisting.context.docId;
|
|
664
|
+
}
|
|
665
|
+
return userIdToMatch === existingId;
|
|
666
|
+
});
|
|
667
|
+
if (userFound) {
|
|
668
|
+
userFound.finalData.userId = existingId;
|
|
669
|
+
}
|
|
670
|
+
return [
|
|
671
|
+
transformedItem,
|
|
672
|
+
existingId,
|
|
673
|
+
{
|
|
674
|
+
rawData: userFound?.rawData,
|
|
675
|
+
finalData: userFound?.finalData,
|
|
676
|
+
},
|
|
677
|
+
];
|
|
678
|
+
} else {
|
|
679
|
+
existingId = newId;
|
|
680
|
+
userData.data.userId = existingId;
|
|
662
681
|
}
|
|
663
682
|
|
|
664
|
-
// Remove user-specific keys from the transformed item
|
|
665
683
|
const userKeys = ["email", "phone", "name", "labels", "prefs"];
|
|
666
684
|
userKeys.forEach((key) => {
|
|
667
685
|
if (transformedItem.hasOwnProperty(key)) {
|
|
668
686
|
delete transformedItem[key];
|
|
669
687
|
}
|
|
670
688
|
});
|
|
689
|
+
|
|
671
690
|
const usersMap = this.importMap.get(this.getCollectionKey("users"));
|
|
672
691
|
const userDataToAdd = {
|
|
673
692
|
rawData: item,
|
|
674
693
|
finalData: userData.data,
|
|
675
694
|
};
|
|
676
|
-
// Directly update the importMap with the new user data, without pushing to usersMap.data first
|
|
677
695
|
this.importMap.set(this.getCollectionKey("users"), {
|
|
678
696
|
data: [...(usersMap?.data || []), userDataToAdd],
|
|
679
697
|
});
|
|
@@ -137,53 +137,62 @@ export class ImportController {
|
|
|
137
137
|
const usersData = usersDataMap?.data;
|
|
138
138
|
const usersController = new UsersController(this.config, this.database);
|
|
139
139
|
if (usersData) {
|
|
140
|
-
console.log("Found users data");
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
if (!(dataLoader.userExistsMap.get(userId) === true)) {
|
|
155
|
-
const userId =
|
|
156
|
-
userBatch.finalData.userId ||
|
|
157
|
-
userBatch.context.userId ||
|
|
158
|
-
userBatch.context.docId;
|
|
159
|
-
if (!userBatch.finalData.userId) {
|
|
160
|
-
userBatch.finalData.userId = userId;
|
|
161
|
-
}
|
|
162
|
-
return usersController
|
|
163
|
-
.createUserAndReturn(userBatch.finalData)
|
|
164
|
-
.then(() => console.log("Created user"))
|
|
165
|
-
.catch((error) => {
|
|
166
|
-
logger.error(
|
|
167
|
-
"Error creating user:",
|
|
168
|
-
error,
|
|
169
|
-
"\nUser data is ",
|
|
170
|
-
userBatch.finalData
|
|
171
|
-
);
|
|
172
|
-
});
|
|
173
|
-
} else {
|
|
174
|
-
console.log("Skipped existing user: ", userId);
|
|
175
|
-
return Promise.resolve();
|
|
176
|
-
}
|
|
177
|
-
}
|
|
140
|
+
console.log("Found users data", usersData.length);
|
|
141
|
+
const userDataBatches = createBatches(usersData);
|
|
142
|
+
for (const batch of userDataBatches) {
|
|
143
|
+
console.log("Importing users batch", batch.length);
|
|
144
|
+
const userBatchPromises = batch
|
|
145
|
+
.filter((item) => {
|
|
146
|
+
let itemId: string | undefined;
|
|
147
|
+
if (item.finalData.userId) {
|
|
148
|
+
itemId = item.finalData.userId;
|
|
149
|
+
} else if (item.finalData.docId) {
|
|
150
|
+
itemId = item.finalData.docId;
|
|
151
|
+
}
|
|
152
|
+
if (!itemId) {
|
|
153
|
+
return false;
|
|
178
154
|
}
|
|
155
|
+
return (
|
|
156
|
+
item &&
|
|
157
|
+
item.finalData &&
|
|
158
|
+
!dataLoader.userExistsMap.has(itemId)
|
|
159
|
+
);
|
|
179
160
|
})
|
|
180
|
-
.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
161
|
+
.map((item) => {
|
|
162
|
+
return usersController.createUserAndReturn(item.finalData);
|
|
163
|
+
});
|
|
164
|
+
await Promise.all(userBatchPromises);
|
|
165
|
+
for (const item of batch) {
|
|
166
|
+
if (item && item.finalData) {
|
|
167
|
+
dataLoader.userExistsMap.set(
|
|
168
|
+
item.finalData.userId ||
|
|
169
|
+
item.finalData.docId ||
|
|
170
|
+
item.context.userId ||
|
|
171
|
+
item.context.docId,
|
|
172
|
+
true
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
console.log("Finished importing users batch", batch.length);
|
|
186
177
|
}
|
|
178
|
+
// for (let i = 0; i < usersData.length; i++) {
|
|
179
|
+
// const user = usersData[i];
|
|
180
|
+
// if (user.finalData) {
|
|
181
|
+
// const userId =
|
|
182
|
+
// user.finalData.userId ||
|
|
183
|
+
// user.context.userId ||
|
|
184
|
+
// user.context.docId;
|
|
185
|
+
// if (!dataLoader.userExistsMap.has(userId)) {
|
|
186
|
+
// if (!user.finalData.userId) {
|
|
187
|
+
// user.finalData.userId = userId;
|
|
188
|
+
// }
|
|
189
|
+
// await usersController.createUserAndReturn(user.finalData);
|
|
190
|
+
// dataLoader.userExistsMap.set(userId, true);
|
|
191
|
+
// } else {
|
|
192
|
+
// console.log("Skipped existing user: ", userId);
|
|
193
|
+
// }
|
|
194
|
+
// }
|
|
195
|
+
// }
|
|
187
196
|
console.log("Finished importing users");
|
|
188
197
|
}
|
|
189
198
|
}
|
|
@@ -216,10 +225,10 @@ export class ImportController {
|
|
|
216
225
|
console.log(`Processing batch ${i + 1} of ${dataSplit.length}`);
|
|
217
226
|
const batchPromises = batches.map((item) => {
|
|
218
227
|
const id =
|
|
219
|
-
item.context.docId ||
|
|
220
|
-
item.context.userId ||
|
|
221
228
|
item.finalData.docId ||
|
|
222
|
-
item.finalData.userId
|
|
229
|
+
item.finalData.userId ||
|
|
230
|
+
item.context.docId ||
|
|
231
|
+
item.context.userId;
|
|
223
232
|
if (item.finalData.hasOwnProperty("userId")) {
|
|
224
233
|
delete item.finalData.userId;
|
|
225
234
|
}
|
|
@@ -229,22 +238,12 @@ export class ImportController {
|
|
|
229
238
|
if (!item.finalData) {
|
|
230
239
|
return Promise.resolve();
|
|
231
240
|
}
|
|
232
|
-
return this.database
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
.catch((error) => {
|
|
239
|
-
console.error(
|
|
240
|
-
`Error creating item in ${collection.name}:`,
|
|
241
|
-
error,
|
|
242
|
-
"\nItem data is ",
|
|
243
|
-
item.finalData
|
|
244
|
-
);
|
|
245
|
-
throw error;
|
|
246
|
-
// Optionally, log the failed item for retry or review
|
|
247
|
-
});
|
|
241
|
+
return this.database.createDocument(
|
|
242
|
+
db.$id,
|
|
243
|
+
collection.$id,
|
|
244
|
+
id,
|
|
245
|
+
item.finalData
|
|
246
|
+
);
|
|
248
247
|
});
|
|
249
248
|
// Wait for all promises in the current batch to resolve
|
|
250
249
|
await Promise.allSettled(batchPromises);
|