appwrite-utils-cli 0.0.64 → 0.0.66
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.
@@ -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;
|
@@ -366,7 +366,16 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
366
366
|
fieldToSet: z.ZodOptional<z.ZodString>;
|
367
367
|
targetFieldToMatch: z.ZodOptional<z.ZodString>;
|
368
368
|
targetField: z.ZodString;
|
369
|
-
targetCollection: z.ZodString;
|
369
|
+
targetCollection: z.ZodString; /**
|
370
|
+
* Prepares the data for updating documents within a collection.
|
371
|
+
* This method loads the raw data based on the import definition, transforms it according to the attribute mappings,
|
372
|
+
* finds the new ID for each item based on the primary key or update mapping, and then validates the transformed data.
|
373
|
+
* If the data is valid, it updates the import definition with any post-import actions and adds the item to the current collection data.
|
374
|
+
*
|
375
|
+
* @param db - The database configuration.
|
376
|
+
* @param collection - The collection configuration.
|
377
|
+
* @param importDef - The import definition containing the attribute mappings and other relevant info.
|
378
|
+
*/
|
370
379
|
}, "strip", z.ZodTypeAny, {
|
371
380
|
sourceField: string;
|
372
381
|
targetField: string;
|
@@ -380,6 +389,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
380
389
|
fieldToSet?: string | undefined;
|
381
390
|
targetFieldToMatch?: string | undefined;
|
382
391
|
}>, "many">>;
|
392
|
+
createUsers: z.ZodOptional<z.ZodNullable<z.ZodDefault<z.ZodBoolean>>>;
|
383
393
|
updateMapping: z.ZodOptional<z.ZodObject<{
|
384
394
|
originalIdField: z.ZodString;
|
385
395
|
targetField: z.ZodString;
|
@@ -494,6 +504,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
494
504
|
fieldToSet?: string | undefined;
|
495
505
|
targetFieldToMatch?: string | undefined;
|
496
506
|
}[] | undefined;
|
507
|
+
createUsers?: boolean | null | undefined;
|
497
508
|
updateMapping?: {
|
498
509
|
targetField: string;
|
499
510
|
originalIdField: string;
|
@@ -529,6 +540,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
529
540
|
fieldToSet?: string | undefined;
|
530
541
|
targetFieldToMatch?: string | undefined;
|
531
542
|
}[] | undefined;
|
543
|
+
createUsers?: boolean | null | undefined;
|
532
544
|
updateMapping?: {
|
533
545
|
targetField: string;
|
534
546
|
originalIdField: string;
|
@@ -623,16 +635,6 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
623
635
|
relatedCollection: string;
|
624
636
|
relationType: "oneToMany" | "manyToOne" | "oneToOne" | "manyToMany";
|
625
637
|
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
638
|
twoWayKey: string;
|
637
639
|
onDelete: "setNull" | "cascade" | "restrict";
|
638
640
|
side: "parent" | "child";
|
@@ -689,6 +691,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
689
691
|
fieldToSet?: string | undefined;
|
690
692
|
targetFieldToMatch?: string | undefined;
|
691
693
|
}[] | undefined;
|
694
|
+
createUsers?: boolean | null | undefined;
|
692
695
|
updateMapping?: {
|
693
696
|
targetField: string;
|
694
697
|
originalIdField: string;
|
@@ -845,6 +848,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
845
848
|
fieldToSet?: string | undefined;
|
846
849
|
targetFieldToMatch?: string | undefined;
|
847
850
|
}[] | undefined;
|
851
|
+
createUsers?: boolean | null | undefined;
|
848
852
|
updateMapping?: {
|
849
853
|
targetField: string;
|
850
854
|
originalIdField: string;
|
@@ -880,6 +884,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
880
884
|
fieldToSet?: string | undefined;
|
881
885
|
targetFieldToMatch?: string | undefined;
|
882
886
|
}>, "many">>;
|
887
|
+
createUsers: z.ZodOptional<z.ZodNullable<z.ZodDefault<z.ZodBoolean>>>;
|
883
888
|
updateMapping: z.ZodOptional<z.ZodObject<{
|
884
889
|
originalIdField: z.ZodString;
|
885
890
|
targetField: z.ZodString;
|
@@ -994,6 +999,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
994
999
|
fieldToSet?: string | undefined;
|
995
1000
|
targetFieldToMatch?: string | undefined;
|
996
1001
|
}[] | undefined;
|
1002
|
+
createUsers?: boolean | null | undefined;
|
997
1003
|
updateMapping?: {
|
998
1004
|
targetField: string;
|
999
1005
|
originalIdField: string;
|
@@ -1029,6 +1035,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1029
1035
|
fieldToSet?: string | undefined;
|
1030
1036
|
targetFieldToMatch?: string | undefined;
|
1031
1037
|
}[] | undefined;
|
1038
|
+
createUsers?: boolean | null | undefined;
|
1032
1039
|
updateMapping?: {
|
1033
1040
|
targetField: string;
|
1034
1041
|
originalIdField: string;
|
@@ -1069,6 +1076,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1069
1076
|
fieldToSet?: string | undefined;
|
1070
1077
|
targetFieldToMatch?: string | undefined;
|
1071
1078
|
}[] | undefined;
|
1079
|
+
createUsers?: boolean | null | undefined;
|
1072
1080
|
updateMapping?: {
|
1073
1081
|
targetField: string;
|
1074
1082
|
originalIdField: string;
|
@@ -1109,6 +1117,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1109
1117
|
fieldToSet?: string | undefined;
|
1110
1118
|
targetFieldToMatch?: string | undefined;
|
1111
1119
|
}[] | undefined;
|
1120
|
+
createUsers?: boolean | null | undefined;
|
1112
1121
|
updateMapping?: {
|
1113
1122
|
targetField: string;
|
1114
1123
|
originalIdField: string;
|
@@ -1151,6 +1160,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1151
1160
|
fieldToSet?: string | undefined;
|
1152
1161
|
targetFieldToMatch?: string | undefined;
|
1153
1162
|
}[] | undefined;
|
1163
|
+
createUsers?: boolean | null | undefined;
|
1154
1164
|
updateMapping?: {
|
1155
1165
|
targetField: string;
|
1156
1166
|
originalIdField: string;
|
@@ -1245,16 +1255,6 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1245
1255
|
relatedCollection: string;
|
1246
1256
|
relationType: "oneToMany" | "manyToOne" | "oneToOne" | "manyToMany";
|
1247
1257
|
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
1258
|
twoWayKey: string;
|
1259
1259
|
onDelete: "setNull" | "cascade" | "restrict";
|
1260
1260
|
side: "parent" | "child";
|
@@ -1311,6 +1311,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1311
1311
|
fieldToSet?: string | undefined;
|
1312
1312
|
targetFieldToMatch?: string | undefined;
|
1313
1313
|
}[] | undefined;
|
1314
|
+
createUsers?: boolean | null | undefined;
|
1314
1315
|
updateMapping?: {
|
1315
1316
|
targetField: string;
|
1316
1317
|
originalIdField: string;
|
@@ -1357,6 +1358,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1357
1358
|
fieldToSet?: string | undefined;
|
1358
1359
|
targetFieldToMatch?: string | undefined;
|
1359
1360
|
}[] | undefined;
|
1361
|
+
createUsers?: boolean | null | undefined;
|
1360
1362
|
updateMapping?: {
|
1361
1363
|
targetField: string;
|
1362
1364
|
originalIdField: string;
|
@@ -1510,6 +1512,7 @@ export declare const CollectionImportDataSchema: z.ZodObject<{
|
|
1510
1512
|
fieldToSet?: string | undefined;
|
1511
1513
|
targetFieldToMatch?: string | undefined;
|
1512
1514
|
}[] | undefined;
|
1515
|
+
createUsers?: boolean | null | undefined;
|
1513
1516
|
updateMapping?: {
|
1514
1517
|
targetField: string;
|
1515
1518
|
originalIdField: string;
|
@@ -1561,6 +1564,7 @@ export declare class DataLoader {
|
|
1561
1564
|
fieldToSet?: string | undefined;
|
1562
1565
|
targetFieldToMatch?: string | undefined;
|
1563
1566
|
}[] | undefined;
|
1567
|
+
createUsers?: boolean | null | undefined;
|
1564
1568
|
updateMapping?: {
|
1565
1569
|
targetField: string;
|
1566
1570
|
originalIdField: string;
|
@@ -1655,16 +1659,6 @@ export declare class DataLoader {
|
|
1655
1659
|
relatedCollection: string;
|
1656
1660
|
relationType: "oneToMany" | "manyToOne" | "oneToOne" | "manyToMany";
|
1657
1661
|
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
1662
|
twoWayKey: string;
|
1669
1663
|
onDelete: "setNull" | "cascade" | "restrict";
|
1670
1664
|
side: "parent" | "child";
|
@@ -1721,6 +1715,7 @@ export declare class DataLoader {
|
|
1721
1715
|
fieldToSet?: string | undefined;
|
1722
1716
|
targetFieldToMatch?: string | undefined;
|
1723
1717
|
}[] | undefined;
|
1718
|
+
createUsers?: boolean | null | undefined;
|
1724
1719
|
updateMapping?: {
|
1725
1720
|
targetField: string;
|
1726
1721
|
originalIdField: string;
|
@@ -1737,6 +1732,7 @@ export declare class DataLoader {
|
|
1737
1732
|
private mergedUserMap;
|
1738
1733
|
private emailToUserIdMap;
|
1739
1734
|
private phoneToUserIdMap;
|
1735
|
+
private userIdSet;
|
1740
1736
|
userExistsMap: Map<string, boolean>;
|
1741
1737
|
private shouldWriteFile;
|
1742
1738
|
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 {
|
@@ -581,14 +587,18 @@ export class DataLoader {
|
|
581
587
|
* @returns The transformed item with user-specific keys removed.
|
582
588
|
*/
|
583
589
|
prepareUserData(item, attributeMappings, primaryKeyField, newId) {
|
584
|
-
if (this.
|
590
|
+
if (this.userIdSet.has(newId) ||
|
591
|
+
this.userExistsMap.has(newId) ||
|
585
592
|
Array.from(this.emailToUserIdMap.values()).includes(newId) ||
|
586
593
|
Array.from(this.phoneToUserIdMap.values()).includes(newId)) {
|
587
594
|
newId = this.getTrueUniqueId(this.getCollectionKey("users"));
|
588
595
|
}
|
589
596
|
let transformedItem = this.transformData(item, attributeMappings);
|
590
|
-
|
591
|
-
if (
|
597
|
+
let userData = AuthUserCreateSchema.safeParse(transformedItem);
|
598
|
+
if (userData.data?.email) {
|
599
|
+
userData.data.email = userData.data.email.toLowerCase();
|
600
|
+
}
|
601
|
+
if (!userData.success || !(userData.data.email || userData.data.phone)) {
|
592
602
|
logger.error(`Invalid user data: ${JSON.stringify(userData.error?.errors, undefined, 2)} or missing email/phone`);
|
593
603
|
const userKeys = ["email", "phone", "name", "labels", "prefs"];
|
594
604
|
userKeys.forEach((key) => {
|
@@ -605,7 +615,7 @@ export class DataLoader {
|
|
605
615
|
},
|
606
616
|
};
|
607
617
|
}
|
608
|
-
const email = userData.data.email;
|
618
|
+
const email = userData.data.email?.toLowerCase();
|
609
619
|
const phone = userData.data.phone;
|
610
620
|
let existingId;
|
611
621
|
// Check for duplicate email and phone
|
@@ -653,7 +663,12 @@ export class DataLoader {
|
|
653
663
|
if (userFound) {
|
654
664
|
userFound.finalData.userId = existingId;
|
655
665
|
userFound.finalData.docId = existingId;
|
656
|
-
this.
|
666
|
+
this.userIdSet.add(existingId);
|
667
|
+
transformedItem = {
|
668
|
+
...transformedItem,
|
669
|
+
userId: existingId,
|
670
|
+
docId: existingId,
|
671
|
+
};
|
657
672
|
}
|
658
673
|
const userKeys = ["email", "phone", "name", "labels", "prefs"];
|
659
674
|
userKeys.forEach((key) => {
|
@@ -688,6 +703,7 @@ export class DataLoader {
|
|
688
703
|
this.importMap.set(this.getCollectionKey("users"), {
|
689
704
|
data: [...(usersMap?.data || []), userDataToAdd],
|
690
705
|
});
|
706
|
+
this.userIdSet.add(existingId);
|
691
707
|
return {
|
692
708
|
transformedItem,
|
693
709
|
existingId,
|
@@ -1033,7 +1049,10 @@ export class DataLoader {
|
|
1033
1049
|
// Update the import definition with the new attribute mappings
|
1034
1050
|
const newImportDef = {
|
1035
1051
|
...importDef,
|
1036
|
-
attributeMappings:
|
1052
|
+
attributeMappings: [
|
1053
|
+
...importDef.attributeMappings,
|
1054
|
+
...mappingsWithActions,
|
1055
|
+
],
|
1037
1056
|
};
|
1038
1057
|
if (itemDataToUpdate) {
|
1039
1058
|
itemDataToUpdate.finalData = this.mergeObjects(itemDataToUpdate.finalData, transformedData);
|
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.66",
|
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
|
@@ -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,
|
@@ -1418,7 +1434,10 @@ export class DataLoader {
|
|
1418
1434
|
// Update the import definition with the new attribute mappings
|
1419
1435
|
const newImportDef = {
|
1420
1436
|
...importDef,
|
1421
|
-
attributeMappings:
|
1437
|
+
attributeMappings: [
|
1438
|
+
...importDef.attributeMappings,
|
1439
|
+
...mappingsWithActions,
|
1440
|
+
],
|
1422
1441
|
};
|
1423
1442
|
|
1424
1443
|
if (itemDataToUpdate) {
|
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
|
}
|