appwrite-utils-cli 0.0.65 → 0.0.67
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/migrations/backup.d.ts +2 -0
- package/dist/migrations/dataLoader.d.ts +17 -30
- package/dist/migrations/dataLoader.js +45 -16
- package/dist/migrations/users.js +3 -3
- package/package.json +55 -54
- package/src/migrations/dataLoader.ts +54 -17
- package/src/migrations/users.ts +3 -3
package/README.md
CHANGED
@@ -132,6 +132,7 @@ This setup ensures that developers have robust tools at their fingertips to mana
|
|
132
132
|
|
133
133
|
### Changelog
|
134
134
|
|
135
|
+
- 0.0.67: Fixed `updates` in `importDef`'s update mappings overwriting postImportActions from the original
|
135
136
|
- 0.0.57: Fixed `dataLoader`'s `idMapping`'s giving me issues
|
136
137
|
- 0.0.55: Added `documentExists` check to batch creation functionality to try to prevent duplicates
|
137
138
|
- 0.0.54: Various fixes in here
|
@@ -319,6 +319,7 @@ export declare const getMigrationCollectionSchemas: () => {
|
|
319
319
|
fieldToSet?: string | undefined;
|
320
320
|
targetFieldToMatch?: string | undefined;
|
321
321
|
}[] | undefined;
|
322
|
+
createUsers?: boolean | null | undefined;
|
322
323
|
updateMapping?: {
|
323
324
|
targetField: string;
|
324
325
|
originalIdField: string;
|
@@ -573,6 +574,7 @@ export declare const getMigrationCollectionSchemas: () => {
|
|
573
574
|
fieldToSet?: string | undefined;
|
574
575
|
targetFieldToMatch?: string | undefined;
|
575
576
|
}[] | undefined;
|
577
|
+
createUsers?: boolean | null | undefined;
|
576
578
|
updateMapping?: {
|
577
579
|
targetField: string;
|
578
580
|
originalIdField: string;
|
@@ -380,6 +380,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
380
380
|
fieldToSet?: string | undefined;
|
381
381
|
targetFieldToMatch?: string | undefined;
|
382
382
|
}>, "many">>;
|
383
|
+
createUsers: z.ZodOptional<z.ZodNullable<z.ZodDefault<z.ZodBoolean>>>;
|
383
384
|
updateMapping: z.ZodOptional<z.ZodObject<{
|
384
385
|
originalIdField: z.ZodString;
|
385
386
|
targetField: z.ZodString;
|
@@ -494,6 +495,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
494
495
|
fieldToSet?: string | undefined;
|
495
496
|
targetFieldToMatch?: string | undefined;
|
496
497
|
}[] | undefined;
|
498
|
+
createUsers?: boolean | null | undefined;
|
497
499
|
updateMapping?: {
|
498
500
|
targetField: string;
|
499
501
|
originalIdField: string;
|
@@ -529,6 +531,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
529
531
|
fieldToSet?: string | undefined;
|
530
532
|
targetFieldToMatch?: string | undefined;
|
531
533
|
}[] | undefined;
|
534
|
+
createUsers?: boolean | null | undefined;
|
532
535
|
updateMapping?: {
|
533
536
|
targetField: string;
|
534
537
|
originalIdField: string;
|
@@ -623,16 +626,6 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
623
626
|
relatedCollection: string;
|
624
627
|
relationType: "oneToMany" | "manyToOne" | "oneToOne" | "manyToMany";
|
625
628
|
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
|
-
*/
|
636
629
|
twoWayKey: string;
|
637
630
|
onDelete: "setNull" | "cascade" | "restrict";
|
638
631
|
side: "parent" | "child";
|
@@ -689,6 +682,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
689
682
|
fieldToSet?: string | undefined;
|
690
683
|
targetFieldToMatch?: string | undefined;
|
691
684
|
}[] | undefined;
|
685
|
+
createUsers?: boolean | null | undefined;
|
692
686
|
updateMapping?: {
|
693
687
|
targetField: string;
|
694
688
|
originalIdField: string;
|
@@ -845,6 +839,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
845
839
|
fieldToSet?: string | undefined;
|
846
840
|
targetFieldToMatch?: string | undefined;
|
847
841
|
}[] | undefined;
|
842
|
+
createUsers?: boolean | null | undefined;
|
848
843
|
updateMapping?: {
|
849
844
|
targetField: string;
|
850
845
|
originalIdField: string;
|
@@ -880,6 +875,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
880
875
|
fieldToSet?: string | undefined;
|
881
876
|
targetFieldToMatch?: string | undefined;
|
882
877
|
}>, "many">>;
|
878
|
+
createUsers: z.ZodOptional<z.ZodNullable<z.ZodDefault<z.ZodBoolean>>>;
|
883
879
|
updateMapping: z.ZodOptional<z.ZodObject<{
|
884
880
|
originalIdField: z.ZodString;
|
885
881
|
targetField: z.ZodString;
|
@@ -994,6 +990,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
994
990
|
fieldToSet?: string | undefined;
|
995
991
|
targetFieldToMatch?: string | undefined;
|
996
992
|
}[] | undefined;
|
993
|
+
createUsers?: boolean | null | undefined;
|
997
994
|
updateMapping?: {
|
998
995
|
targetField: string;
|
999
996
|
originalIdField: string;
|
@@ -1029,6 +1026,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1029
1026
|
fieldToSet?: string | undefined;
|
1030
1027
|
targetFieldToMatch?: string | undefined;
|
1031
1028
|
}[] | undefined;
|
1029
|
+
createUsers?: boolean | null | undefined;
|
1032
1030
|
updateMapping?: {
|
1033
1031
|
targetField: string;
|
1034
1032
|
originalIdField: string;
|
@@ -1069,6 +1067,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1069
1067
|
fieldToSet?: string | undefined;
|
1070
1068
|
targetFieldToMatch?: string | undefined;
|
1071
1069
|
}[] | undefined;
|
1070
|
+
createUsers?: boolean | null | undefined;
|
1072
1071
|
updateMapping?: {
|
1073
1072
|
targetField: string;
|
1074
1073
|
originalIdField: string;
|
@@ -1109,6 +1108,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1109
1108
|
fieldToSet?: string | undefined;
|
1110
1109
|
targetFieldToMatch?: string | undefined;
|
1111
1110
|
}[] | undefined;
|
1111
|
+
createUsers?: boolean | null | undefined;
|
1112
1112
|
updateMapping?: {
|
1113
1113
|
targetField: string;
|
1114
1114
|
originalIdField: string;
|
@@ -1151,6 +1151,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1151
1151
|
fieldToSet?: string | undefined;
|
1152
1152
|
targetFieldToMatch?: string | undefined;
|
1153
1153
|
}[] | undefined;
|
1154
|
+
createUsers?: boolean | null | undefined;
|
1154
1155
|
updateMapping?: {
|
1155
1156
|
targetField: string;
|
1156
1157
|
originalIdField: string;
|
@@ -1245,16 +1246,6 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1245
1246
|
relatedCollection: string;
|
1246
1247
|
relationType: "oneToMany" | "manyToOne" | "oneToOne" | "manyToMany";
|
1247
1248
|
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
|
-
*/
|
1258
1249
|
twoWayKey: string;
|
1259
1250
|
onDelete: "setNull" | "cascade" | "restrict";
|
1260
1251
|
side: "parent" | "child";
|
@@ -1311,6 +1302,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1311
1302
|
fieldToSet?: string | undefined;
|
1312
1303
|
targetFieldToMatch?: string | undefined;
|
1313
1304
|
}[] | undefined;
|
1305
|
+
createUsers?: boolean | null | undefined;
|
1314
1306
|
updateMapping?: {
|
1315
1307
|
targetField: string;
|
1316
1308
|
originalIdField: string;
|
@@ -1357,6 +1349,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1357
1349
|
fieldToSet?: string | undefined;
|
1358
1350
|
targetFieldToMatch?: string | undefined;
|
1359
1351
|
}[] | undefined;
|
1352
|
+
createUsers?: boolean | null | undefined;
|
1360
1353
|
updateMapping?: {
|
1361
1354
|
targetField: string;
|
1362
1355
|
originalIdField: string;
|
@@ -1510,6 +1503,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1510
1503
|
fieldToSet?: string | undefined;
|
1511
1504
|
targetFieldToMatch?: string | undefined;
|
1512
1505
|
}[] | undefined;
|
1506
|
+
createUsers?: boolean | null | undefined;
|
1513
1507
|
updateMapping?: {
|
1514
1508
|
targetField: string;
|
1515
1509
|
originalIdField: string;
|
@@ -1561,6 +1555,7 @@ export declare class DataLoader {
|
|
1561
1555
|
fieldToSet?: string | undefined;
|
1562
1556
|
targetFieldToMatch?: string | undefined;
|
1563
1557
|
}[] | undefined;
|
1558
|
+
createUsers?: boolean | null | undefined;
|
1564
1559
|
updateMapping?: {
|
1565
1560
|
targetField: string;
|
1566
1561
|
originalIdField: string;
|
@@ -1655,16 +1650,6 @@ export declare class DataLoader {
|
|
1655
1650
|
relatedCollection: string;
|
1656
1651
|
relationType: "oneToMany" | "manyToOne" | "oneToOne" | "manyToMany";
|
1657
1652
|
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
|
-
*/
|
1668
1653
|
twoWayKey: string;
|
1669
1654
|
onDelete: "setNull" | "cascade" | "restrict";
|
1670
1655
|
side: "parent" | "child";
|
@@ -1721,6 +1706,7 @@ export declare class DataLoader {
|
|
1721
1706
|
fieldToSet?: string | undefined;
|
1722
1707
|
targetFieldToMatch?: string | undefined;
|
1723
1708
|
}[] | undefined;
|
1709
|
+
createUsers?: boolean | null | undefined;
|
1724
1710
|
updateMapping?: {
|
1725
1711
|
targetField: string;
|
1726
1712
|
originalIdField: string;
|
@@ -1737,6 +1723,7 @@ export declare class DataLoader {
|
|
1737
1723
|
private mergedUserMap;
|
1738
1724
|
private emailToUserIdMap;
|
1739
1725
|
private phoneToUserIdMap;
|
1726
|
+
private userIdSet;
|
1740
1727
|
userExistsMap: Map<string, boolean>;
|
1741
1728
|
private shouldWriteFile;
|
1742
1729
|
constructor(appwriteFolderPath: string, importDataActions: ImportDataActions, database: Databases, config: AppwriteConfig, shouldWriteFile?: boolean);
|
@@ -45,6 +45,7 @@ export class DataLoader {
|
|
45
45
|
// Maps to hold email and phone to user ID mappings for unique-ness in User Accounts
|
46
46
|
emailToUserIdMap = new Map();
|
47
47
|
phoneToUserIdMap = new Map();
|
48
|
+
userIdSet = new Set();
|
48
49
|
userExistsMap = new Map();
|
49
50
|
shouldWriteFile = false;
|
50
51
|
// Constructor to initialize the DataLoader with necessary configurations
|
@@ -173,6 +174,7 @@ export class DataLoader {
|
|
173
174
|
let newId = ID.unique();
|
174
175
|
let condition = this.checkMapValuesForId(newId, collectionName) ||
|
175
176
|
this.userExistsMap.has(newId) ||
|
177
|
+
this.userIdSet.has(newId) ||
|
176
178
|
this.importMap
|
177
179
|
.get(this.getCollectionKey("users"))
|
178
180
|
?.data.some((user) => user.finalData.docId === newId || user.finalData.userId === newId);
|
@@ -181,6 +183,7 @@ export class DataLoader {
|
|
181
183
|
condition =
|
182
184
|
this.checkMapValuesForId(newId, collectionName) ||
|
183
185
|
this.userExistsMap.has(newId) ||
|
186
|
+
this.userIdSet.has(newId) ||
|
184
187
|
this.importMap
|
185
188
|
.get(this.getCollectionKey("users"))
|
186
189
|
?.data.some((user) => user.finalData.docId === newId || user.finalData.userId === newId);
|
@@ -260,12 +263,13 @@ export class DataLoader {
|
|
260
263
|
// Iterate over the users and setup our maps ahead of time for email and phone
|
261
264
|
for (const user of allUsers) {
|
262
265
|
if (user.email) {
|
263
|
-
this.emailToUserIdMap.set(user.email, user.$id);
|
266
|
+
this.emailToUserIdMap.set(user.email.toLowerCase(), user.$id);
|
264
267
|
}
|
265
268
|
if (user.phone) {
|
266
269
|
this.phoneToUserIdMap.set(user.phone, user.$id);
|
267
270
|
}
|
268
271
|
this.userExistsMap.set(user.$id, true);
|
272
|
+
this.userIdSet.add(user.$id);
|
269
273
|
let importData = this.importMap.get(this.getCollectionKey("users"));
|
270
274
|
if (!importData) {
|
271
275
|
importData = {
|
@@ -275,11 +279,13 @@ export class DataLoader {
|
|
275
279
|
importData.data.push({
|
276
280
|
finalData: {
|
277
281
|
...user,
|
282
|
+
email: user.email?.toLowerCase(),
|
278
283
|
userId: user.$id,
|
279
284
|
docId: user.$id,
|
280
285
|
},
|
281
286
|
context: {
|
282
287
|
...user,
|
288
|
+
email: user.email?.toLowerCase(),
|
283
289
|
userId: user.$id,
|
284
290
|
docId: user.$id,
|
285
291
|
},
|
@@ -320,7 +326,7 @@ export class DataLoader {
|
|
320
326
|
const createDefs = collection.importDefs.filter((def) => def.type === "create" || !def.type);
|
321
327
|
const updateDefs = collection.importDefs.filter((def) => def.type === "update");
|
322
328
|
for (const createDef of createDefs) {
|
323
|
-
if (!isUsersCollection) {
|
329
|
+
if (!isUsersCollection || !createDef.createUsers) {
|
324
330
|
await this.prepareCreateData(db, collection, createDef);
|
325
331
|
}
|
326
332
|
else {
|
@@ -464,7 +470,9 @@ export class DataLoader {
|
|
464
470
|
const currentDataFiltered = getCurrentDataFiltered(collectionData.data[i]);
|
465
471
|
// Log and skip if no matching data found
|
466
472
|
if (!foundData.length) {
|
467
|
-
console.log(
|
473
|
+
// console.log(
|
474
|
+
// `No data found for collection ${collectionConfig.name}: - Target collection: ${targetCollectionKey} - Value to match: ${valueToMatch} - Field to set: ${fieldToSetKey} - Target field to match: ${targetFieldKey} - Target field value: ${idMapping.targetField}`
|
475
|
+
// );
|
468
476
|
logger.info(`No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey} -- idMapping: ${JSON.stringify(idMapping, null, 2)}`);
|
469
477
|
continue;
|
470
478
|
}
|
@@ -581,14 +589,18 @@ export class DataLoader {
|
|
581
589
|
* @returns The transformed item with user-specific keys removed.
|
582
590
|
*/
|
583
591
|
prepareUserData(item, attributeMappings, primaryKeyField, newId) {
|
584
|
-
if (this.
|
592
|
+
if (this.userIdSet.has(newId) ||
|
593
|
+
this.userExistsMap.has(newId) ||
|
585
594
|
Array.from(this.emailToUserIdMap.values()).includes(newId) ||
|
586
595
|
Array.from(this.phoneToUserIdMap.values()).includes(newId)) {
|
587
596
|
newId = this.getTrueUniqueId(this.getCollectionKey("users"));
|
588
597
|
}
|
589
598
|
let transformedItem = this.transformData(item, attributeMappings);
|
590
|
-
|
591
|
-
if (
|
599
|
+
let userData = AuthUserCreateSchema.safeParse(transformedItem);
|
600
|
+
if (userData.data?.email) {
|
601
|
+
userData.data.email = userData.data.email.toLowerCase();
|
602
|
+
}
|
603
|
+
if (!userData.success || !(userData.data.email || userData.data.phone)) {
|
592
604
|
logger.error(`Invalid user data: ${JSON.stringify(userData.error?.errors, undefined, 2)} or missing email/phone`);
|
593
605
|
const userKeys = ["email", "phone", "name", "labels", "prefs"];
|
594
606
|
userKeys.forEach((key) => {
|
@@ -605,7 +617,7 @@ export class DataLoader {
|
|
605
617
|
},
|
606
618
|
};
|
607
619
|
}
|
608
|
-
const email = userData.data.email;
|
620
|
+
const email = userData.data.email?.toLowerCase();
|
609
621
|
const phone = userData.data.phone;
|
610
622
|
let existingId;
|
611
623
|
// Check for duplicate email and phone
|
@@ -653,7 +665,12 @@ export class DataLoader {
|
|
653
665
|
if (userFound) {
|
654
666
|
userFound.finalData.userId = existingId;
|
655
667
|
userFound.finalData.docId = existingId;
|
656
|
-
this.
|
668
|
+
this.userIdSet.add(existingId);
|
669
|
+
transformedItem = {
|
670
|
+
...transformedItem,
|
671
|
+
userId: existingId,
|
672
|
+
docId: existingId,
|
673
|
+
};
|
657
674
|
}
|
658
675
|
const userKeys = ["email", "phone", "name", "labels", "prefs"];
|
659
676
|
userKeys.forEach((key) => {
|
@@ -688,6 +705,7 @@ export class DataLoader {
|
|
688
705
|
this.importMap.set(this.getCollectionKey("users"), {
|
689
706
|
data: [...(usersMap?.data || []), userDataToAdd],
|
690
707
|
});
|
708
|
+
this.userIdSet.add(existingId);
|
691
709
|
return {
|
692
710
|
transformedItem,
|
693
711
|
existingId,
|
@@ -962,17 +980,14 @@ export class DataLoader {
|
|
962
980
|
item[importDef.updateMapping.originalIdField] ||
|
963
981
|
transformedData[importDef.updateMapping.originalIdField];
|
964
982
|
if (oldId) {
|
965
|
-
itemDataToUpdate = currentData?.data.find(({ context,
|
983
|
+
itemDataToUpdate = currentData?.data.find(({ context, finalData }) => {
|
966
984
|
const targetField = importDef.updateMapping.targetField ??
|
967
985
|
importDef.updateMapping.originalIdField;
|
968
986
|
return (`${context[targetField]}` === `${oldId}` ||
|
969
|
-
`${rawData[targetField]}` === `${oldId}` ||
|
970
987
|
`${finalData[targetField]}` === `${oldId}`);
|
971
988
|
});
|
972
989
|
if (itemDataToUpdate) {
|
973
|
-
newId =
|
974
|
-
itemDataToUpdate.finalData.docId ||
|
975
|
-
itemDataToUpdate.context.docId;
|
990
|
+
newId = itemDataToUpdate.context.docId;
|
976
991
|
}
|
977
992
|
}
|
978
993
|
}
|
@@ -1020,10 +1035,10 @@ export class DataLoader {
|
|
1020
1035
|
}
|
1021
1036
|
transformedData = this.mergeObjects(itemDataToUpdate.finalData, transformedData);
|
1022
1037
|
// Create a context object for the item, including the new ID and transformed data
|
1023
|
-
let context =
|
1038
|
+
let context = itemDataToUpdate.context;
|
1024
1039
|
context = this.mergeObjects(context, transformedData);
|
1025
1040
|
// Validate the item before proceeding
|
1026
|
-
const isValid =
|
1041
|
+
const isValid = this.importDataActions.validateItem(item, importDef.attributeMappings, context);
|
1027
1042
|
if (!isValid) {
|
1028
1043
|
logger.info(`Skipping item: ${JSON.stringify(item, null, 2)} because it's invalid`);
|
1029
1044
|
continue;
|
@@ -1038,7 +1053,21 @@ export class DataLoader {
|
|
1038
1053
|
if (itemDataToUpdate) {
|
1039
1054
|
itemDataToUpdate.finalData = this.mergeObjects(itemDataToUpdate.finalData, transformedData);
|
1040
1055
|
itemDataToUpdate.context = context;
|
1041
|
-
|
1056
|
+
// Merge existing importDef with new importDef, focusing only on postImportActions
|
1057
|
+
itemDataToUpdate.importDef = {
|
1058
|
+
...itemDataToUpdate.importDef,
|
1059
|
+
attributeMappings: itemDataToUpdate.importDef?.attributeMappings.map((attrMapping, index) => ({
|
1060
|
+
...attrMapping,
|
1061
|
+
postImportActions: [
|
1062
|
+
...(attrMapping.postImportActions || []),
|
1063
|
+
...(newImportDef.attributeMappings[index]
|
1064
|
+
?.postImportActions || []),
|
1065
|
+
],
|
1066
|
+
})) || [],
|
1067
|
+
};
|
1068
|
+
if (collection.name.toLowerCase() === "councils") {
|
1069
|
+
console.log(`Mappings in update councils: ${JSON.stringify(itemDataToUpdate.importDef.attributeMappings, null, 2)}`);
|
1070
|
+
}
|
1042
1071
|
}
|
1043
1072
|
else {
|
1044
1073
|
currentData.data.push({
|
package/dist/migrations/users.js
CHANGED
@@ -58,10 +58,10 @@ export class UsersController {
|
|
58
58
|
Query.cursorAfter(lastDocumentId),
|
59
59
|
]));
|
60
60
|
allUsers.push(...moreUsers.users);
|
61
|
-
lastDocumentId = moreUsers.users[moreUsers.users.length - 1].$id;
|
62
61
|
if (moreUsers.users.length < 200) {
|
63
62
|
break;
|
64
63
|
}
|
64
|
+
lastDocumentId = moreUsers.users[moreUsers.users.length - 1].$id;
|
65
65
|
}
|
66
66
|
}
|
67
67
|
else {
|
@@ -99,9 +99,9 @@ export class UsersController {
|
|
99
99
|
}
|
100
100
|
}
|
101
101
|
if (e instanceof Error) {
|
102
|
-
logger.error("FAILED CREATING USER: ", e.message);
|
102
|
+
logger.error("FAILED CREATING USER: ", e.message, item);
|
103
103
|
}
|
104
|
-
console.log("FAILED CREATING USER: ", e);
|
104
|
+
console.log("FAILED CREATING USER: ", e, item);
|
105
105
|
throw e;
|
106
106
|
}
|
107
107
|
}
|
package/package.json
CHANGED
@@ -1,54 +1,55 @@
|
|
1
|
-
{
|
2
|
-
"name": "appwrite-utils-cli",
|
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.
|
5
|
-
"main": "src/main.ts",
|
6
|
-
"type": "module",
|
7
|
-
"repository": {
|
8
|
-
"type": "git",
|
9
|
-
"url": "https://github.com/zachhandley/AppwriteUtils"
|
10
|
-
},
|
11
|
-
"author": "Zach Handley <zach@blackleafdigital.com> (https://zachhandley.com)",
|
12
|
-
"keywords": [
|
13
|
-
"appwrite",
|
14
|
-
"cli",
|
15
|
-
"utils",
|
16
|
-
"migrations",
|
17
|
-
"data",
|
18
|
-
"database",
|
19
|
-
"import",
|
20
|
-
"migration",
|
21
|
-
"utility"
|
22
|
-
],
|
23
|
-
"bin": {
|
24
|
-
"appwrite-init": "./dist/init.js",
|
25
|
-
"appwrite-migrate": "./dist/main.js"
|
26
|
-
},
|
27
|
-
"scripts": {
|
28
|
-
"build": "bun run tsc",
|
29
|
-
"init": "tsx --no-cache src/init.ts",
|
30
|
-
"migrate": "tsx --no-cache src/main.ts",
|
31
|
-
"deploy": "bun run build && npm publish --access public",
|
32
|
-
"postinstall": "echo 'This package is intended for CLI use only and should not be added as a dependency in other projects.'"
|
33
|
-
},
|
34
|
-
"dependencies": {
|
35
|
-
"@types/inquirer": "^9.0.7",
|
36
|
-
"appwrite-utils": "^0.3.
|
37
|
-
"commander": "^12.0.0",
|
38
|
-
"inquirer": "^9.2.20",
|
39
|
-
"js-yaml": "^4.1.0",
|
40
|
-
"lodash": "^4.17.21",
|
41
|
-
"luxon": "^3.4.4",
|
42
|
-
"nanostores": "^0.10.3",
|
43
|
-
"node-appwrite": "^12.0.1",
|
44
|
-
"tsx": "^4.9.3",
|
45
|
-
"
|
46
|
-
"
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
"@types/
|
51
|
-
"@types/
|
52
|
-
"
|
53
|
-
|
54
|
-
}
|
1
|
+
{
|
2
|
+
"name": "appwrite-utils-cli",
|
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.67",
|
5
|
+
"main": "src/main.ts",
|
6
|
+
"type": "module",
|
7
|
+
"repository": {
|
8
|
+
"type": "git",
|
9
|
+
"url": "https://github.com/zachhandley/AppwriteUtils"
|
10
|
+
},
|
11
|
+
"author": "Zach Handley <zach@blackleafdigital.com> (https://zachhandley.com)",
|
12
|
+
"keywords": [
|
13
|
+
"appwrite",
|
14
|
+
"cli",
|
15
|
+
"utils",
|
16
|
+
"migrations",
|
17
|
+
"data",
|
18
|
+
"database",
|
19
|
+
"import",
|
20
|
+
"migration",
|
21
|
+
"utility"
|
22
|
+
],
|
23
|
+
"bin": {
|
24
|
+
"appwrite-init": "./dist/init.js",
|
25
|
+
"appwrite-migrate": "./dist/main.js"
|
26
|
+
},
|
27
|
+
"scripts": {
|
28
|
+
"build": "bun run tsc",
|
29
|
+
"init": "tsx --no-cache src/init.ts",
|
30
|
+
"migrate": "tsx --no-cache src/main.ts",
|
31
|
+
"deploy": "bun run build && npm publish --access public",
|
32
|
+
"postinstall": "echo 'This package is intended for CLI use only and should not be added as a dependency in other projects.'"
|
33
|
+
},
|
34
|
+
"dependencies": {
|
35
|
+
"@types/inquirer": "^9.0.7",
|
36
|
+
"appwrite-utils": "^0.3.3",
|
37
|
+
"commander": "^12.0.0",
|
38
|
+
"inquirer": "^9.2.20",
|
39
|
+
"js-yaml": "^4.1.0",
|
40
|
+
"lodash": "^4.17.21",
|
41
|
+
"luxon": "^3.4.4",
|
42
|
+
"nanostores": "^0.10.3",
|
43
|
+
"node-appwrite": "^12.0.1",
|
44
|
+
"tsx": "^4.9.3",
|
45
|
+
"ulid": "^2.3.0",
|
46
|
+
"winston": "^3.13.0",
|
47
|
+
"zod": "^3.22.4"
|
48
|
+
},
|
49
|
+
"devDependencies": {
|
50
|
+
"@types/js-yaml": "^4.0.9",
|
51
|
+
"@types/lodash": "^4.17.0",
|
52
|
+
"@types/luxon": "^3.4.2",
|
53
|
+
"typescript": "^5.0.0"
|
54
|
+
}
|
55
|
+
}
|
@@ -64,6 +64,7 @@ export class DataLoader {
|
|
64
64
|
// Maps to hold email and phone to user ID mappings for unique-ness in User Accounts
|
65
65
|
private emailToUserIdMap = new Map<string, string>();
|
66
66
|
private phoneToUserIdMap = new Map<string, string>();
|
67
|
+
private userIdSet = new Set<string>();
|
67
68
|
userExistsMap = new Map<string, boolean>();
|
68
69
|
private shouldWriteFile = false;
|
69
70
|
|
@@ -212,6 +213,7 @@ export class DataLoader {
|
|
212
213
|
let condition =
|
213
214
|
this.checkMapValuesForId(newId, collectionName) ||
|
214
215
|
this.userExistsMap.has(newId) ||
|
216
|
+
this.userIdSet.has(newId) ||
|
215
217
|
this.importMap
|
216
218
|
.get(this.getCollectionKey("users"))
|
217
219
|
?.data.some(
|
@@ -223,6 +225,7 @@ export class DataLoader {
|
|
223
225
|
condition =
|
224
226
|
this.checkMapValuesForId(newId, collectionName) ||
|
225
227
|
this.userExistsMap.has(newId) ||
|
228
|
+
this.userIdSet.has(newId) ||
|
226
229
|
this.importMap
|
227
230
|
.get(this.getCollectionKey("users"))
|
228
231
|
?.data.some(
|
@@ -330,12 +333,13 @@ export class DataLoader {
|
|
330
333
|
// Iterate over the users and setup our maps ahead of time for email and phone
|
331
334
|
for (const user of allUsers) {
|
332
335
|
if (user.email) {
|
333
|
-
this.emailToUserIdMap.set(user.email, user.$id);
|
336
|
+
this.emailToUserIdMap.set(user.email.toLowerCase(), user.$id);
|
334
337
|
}
|
335
338
|
if (user.phone) {
|
336
339
|
this.phoneToUserIdMap.set(user.phone, user.$id);
|
337
340
|
}
|
338
341
|
this.userExistsMap.set(user.$id, true);
|
342
|
+
this.userIdSet.add(user.$id);
|
339
343
|
let importData = this.importMap.get(this.getCollectionKey("users"));
|
340
344
|
if (!importData) {
|
341
345
|
importData = {
|
@@ -345,11 +349,13 @@ export class DataLoader {
|
|
345
349
|
importData.data.push({
|
346
350
|
finalData: {
|
347
351
|
...user,
|
352
|
+
email: user.email?.toLowerCase(),
|
348
353
|
userId: user.$id,
|
349
354
|
docId: user.$id,
|
350
355
|
},
|
351
356
|
context: {
|
352
357
|
...user,
|
358
|
+
email: user.email?.toLowerCase(),
|
353
359
|
userId: user.$id,
|
354
360
|
docId: user.$id,
|
355
361
|
},
|
@@ -398,7 +404,7 @@ export class DataLoader {
|
|
398
404
|
(def: ImportDef) => def.type === "update"
|
399
405
|
);
|
400
406
|
for (const createDef of createDefs) {
|
401
|
-
if (!isUsersCollection) {
|
407
|
+
if (!isUsersCollection || !createDef.createUsers) {
|
402
408
|
await this.prepareCreateData(db, collection, createDef);
|
403
409
|
} else {
|
404
410
|
// Special handling for users collection if needed
|
@@ -592,9 +598,9 @@ export class DataLoader {
|
|
592
598
|
);
|
593
599
|
// Log and skip if no matching data found
|
594
600
|
if (!foundData.length) {
|
595
|
-
console.log(
|
596
|
-
|
597
|
-
);
|
601
|
+
// console.log(
|
602
|
+
// `No data found for collection ${collectionConfig.name}: - Target collection: ${targetCollectionKey} - Value to match: ${valueToMatch} - Field to set: ${fieldToSetKey} - Target field to match: ${targetFieldKey} - Target field value: ${idMapping.targetField}`
|
603
|
+
// );
|
598
604
|
logger.info(
|
599
605
|
`No data found for collection: ${targetCollectionKey} with value: ${valueToMatch} for field: ${fieldToSetKey} -- idMapping: ${JSON.stringify(
|
600
606
|
idMapping,
|
@@ -761,6 +767,7 @@ export class DataLoader {
|
|
761
767
|
};
|
762
768
|
} {
|
763
769
|
if (
|
770
|
+
this.userIdSet.has(newId) ||
|
764
771
|
this.userExistsMap.has(newId) ||
|
765
772
|
Array.from(this.emailToUserIdMap.values()).includes(newId) ||
|
766
773
|
Array.from(this.phoneToUserIdMap.values()).includes(newId)
|
@@ -768,8 +775,11 @@ export class DataLoader {
|
|
768
775
|
newId = this.getTrueUniqueId(this.getCollectionKey("users"));
|
769
776
|
}
|
770
777
|
let transformedItem = this.transformData(item, attributeMappings);
|
771
|
-
|
772
|
-
if (
|
778
|
+
let userData = AuthUserCreateSchema.safeParse(transformedItem);
|
779
|
+
if (userData.data?.email) {
|
780
|
+
userData.data.email = userData.data.email.toLowerCase();
|
781
|
+
}
|
782
|
+
if (!userData.success || !(userData.data.email || userData.data.phone)) {
|
773
783
|
logger.error(
|
774
784
|
`Invalid user data: ${JSON.stringify(
|
775
785
|
userData.error?.errors,
|
@@ -793,7 +803,7 @@ export class DataLoader {
|
|
793
803
|
},
|
794
804
|
};
|
795
805
|
}
|
796
|
-
const email = userData.data.email;
|
806
|
+
const email = userData.data.email?.toLowerCase();
|
797
807
|
const phone = userData.data.phone;
|
798
808
|
let existingId: string | undefined;
|
799
809
|
|
@@ -836,7 +846,12 @@ export class DataLoader {
|
|
836
846
|
if (userFound) {
|
837
847
|
userFound.finalData.userId = existingId;
|
838
848
|
userFound.finalData.docId = existingId;
|
839
|
-
this.
|
849
|
+
this.userIdSet.add(existingId);
|
850
|
+
transformedItem = {
|
851
|
+
...transformedItem,
|
852
|
+
userId: existingId,
|
853
|
+
docId: existingId,
|
854
|
+
};
|
840
855
|
}
|
841
856
|
|
842
857
|
const userKeys = ["email", "phone", "name", "labels", "prefs"];
|
@@ -873,6 +888,7 @@ export class DataLoader {
|
|
873
888
|
this.importMap.set(this.getCollectionKey("users"), {
|
874
889
|
data: [...(usersMap?.data || []), userDataToAdd],
|
875
890
|
});
|
891
|
+
this.userIdSet.add(existingId);
|
876
892
|
|
877
893
|
return {
|
878
894
|
transformedItem,
|
@@ -1281,23 +1297,20 @@ export class DataLoader {
|
|
1281
1297
|
transformedData[importDef.updateMapping.originalIdField];
|
1282
1298
|
if (oldId) {
|
1283
1299
|
itemDataToUpdate = currentData?.data.find(
|
1284
|
-
({ context,
|
1300
|
+
({ context, finalData }) => {
|
1285
1301
|
const targetField =
|
1286
1302
|
importDef.updateMapping!.targetField ??
|
1287
1303
|
importDef.updateMapping!.originalIdField;
|
1288
1304
|
|
1289
1305
|
return (
|
1290
1306
|
`${context[targetField]}` === `${oldId}` ||
|
1291
|
-
`${rawData[targetField]}` === `${oldId}` ||
|
1292
1307
|
`${finalData[targetField]}` === `${oldId}`
|
1293
1308
|
);
|
1294
1309
|
}
|
1295
1310
|
);
|
1296
1311
|
|
1297
1312
|
if (itemDataToUpdate) {
|
1298
|
-
newId =
|
1299
|
-
itemDataToUpdate.finalData.docId ||
|
1300
|
-
itemDataToUpdate.context.docId;
|
1313
|
+
newId = itemDataToUpdate.context.docId;
|
1301
1314
|
}
|
1302
1315
|
}
|
1303
1316
|
}
|
@@ -1391,11 +1404,11 @@ export class DataLoader {
|
|
1391
1404
|
);
|
1392
1405
|
|
1393
1406
|
// Create a context object for the item, including the new ID and transformed data
|
1394
|
-
let context =
|
1407
|
+
let context = itemDataToUpdate.context;
|
1395
1408
|
context = this.mergeObjects(context, transformedData);
|
1396
1409
|
|
1397
1410
|
// Validate the item before proceeding
|
1398
|
-
const isValid =
|
1411
|
+
const isValid = this.importDataActions.validateItem(
|
1399
1412
|
item,
|
1400
1413
|
importDef.attributeMappings,
|
1401
1414
|
context
|
@@ -1427,7 +1440,31 @@ export class DataLoader {
|
|
1427
1440
|
transformedData
|
1428
1441
|
);
|
1429
1442
|
itemDataToUpdate.context = context;
|
1430
|
-
|
1443
|
+
// Merge existing importDef with new importDef, focusing only on postImportActions
|
1444
|
+
itemDataToUpdate.importDef = {
|
1445
|
+
...itemDataToUpdate.importDef,
|
1446
|
+
attributeMappings:
|
1447
|
+
itemDataToUpdate.importDef?.attributeMappings.map(
|
1448
|
+
(attrMapping, index) => ({
|
1449
|
+
...attrMapping,
|
1450
|
+
postImportActions: [
|
1451
|
+
...(attrMapping.postImportActions || []),
|
1452
|
+
...(newImportDef.attributeMappings[index]
|
1453
|
+
?.postImportActions || []),
|
1454
|
+
],
|
1455
|
+
})
|
1456
|
+
) || [],
|
1457
|
+
} as ImportDef;
|
1458
|
+
|
1459
|
+
if (collection.name.toLowerCase() === "councils") {
|
1460
|
+
console.log(
|
1461
|
+
`Mappings in update councils: ${JSON.stringify(
|
1462
|
+
itemDataToUpdate.importDef.attributeMappings,
|
1463
|
+
null,
|
1464
|
+
2
|
1465
|
+
)}`
|
1466
|
+
);
|
1467
|
+
}
|
1431
1468
|
} else {
|
1432
1469
|
currentData!.data.push({
|
1433
1470
|
rawData: item,
|
package/src/migrations/users.ts
CHANGED
@@ -89,10 +89,10 @@ export class UsersController {
|
|
89
89
|
])
|
90
90
|
);
|
91
91
|
allUsers.push(...moreUsers.users);
|
92
|
-
lastDocumentId = moreUsers.users[moreUsers.users.length - 1].$id;
|
93
92
|
if (moreUsers.users.length < 200) {
|
94
93
|
break;
|
95
94
|
}
|
95
|
+
lastDocumentId = moreUsers.users[moreUsers.users.length - 1].$id;
|
96
96
|
}
|
97
97
|
} else {
|
98
98
|
allUsers.push(...users.users);
|
@@ -141,9 +141,9 @@ export class UsersController {
|
|
141
141
|
}
|
142
142
|
}
|
143
143
|
if (e instanceof Error) {
|
144
|
-
logger.error("FAILED CREATING USER: ", e.message);
|
144
|
+
logger.error("FAILED CREATING USER: ", e.message, item);
|
145
145
|
}
|
146
|
-
console.log("FAILED CREATING USER: ", e);
|
146
|
+
console.log("FAILED CREATING USER: ", e, item);
|
147
147
|
throw e;
|
148
148
|
}
|
149
149
|
}
|