appwrite-utils-cli 0.0.16 → 0.0.18

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.
@@ -11,6 +11,7 @@ export declare class ImportController {
11
11
  private setupOptions;
12
12
  private documentCache;
13
13
  private batchLimit;
14
+ private postImportActionsQueue;
14
15
  constructor(config: AppwriteConfig, database: Databases, storage: Storage, appwriteFolderPath: string, importDataActions: ImportDataActions, setupOptions: SetupOptions);
15
16
  run(): Promise<void>;
16
17
  importCollections(db: ConfigDatabase): Promise<void>;
@@ -21,11 +21,7 @@ export class ImportController {
21
21
  setupOptions;
22
22
  documentCache;
23
23
  batchLimit = 25; // Define batch size limit
24
- // private postImportActionsQueue: {
25
- // context: any;
26
- // finalItem: any;
27
- // attributeMappings: AttributeMappings;
28
- // }[] = [];
24
+ postImportActionsQueue = [];
29
25
  constructor(config, database, storage, appwriteFolderPath, importDataActions, setupOptions) {
30
26
  this.config = config;
31
27
  this.database = database;
@@ -168,6 +164,10 @@ export class ImportController {
168
164
  return;
169
165
  }
170
166
  const user = await usersController.createUserAndReturn(userToCreate.data);
167
+ if (!user) {
168
+ logger.error(`Skipping user & contact creation for ${item} because of an error...`);
169
+ return;
170
+ }
171
171
  createIdToUse = user.$id;
172
172
  context.docId = createIdToUse;
173
173
  context = { ...context, ...user };
@@ -207,19 +207,23 @@ export class ImportController {
207
207
  const attributeMappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, afterImportActionContext, finalItem);
208
208
  if (attributeMappingsWithActions.some((m) => m.postImportActions)) {
209
209
  logger.info(`Pushing to post-import actions queue for ${context.docId}`);
210
- const afterImportOperationContext = ContextObject.parse({
211
- dbId: db.$id,
212
- collectionId: collection.$id,
213
- finalItem: finalItem,
214
- attributeMappings: attributeMappingsWithActions,
215
- context: afterImportActionContext,
216
- });
217
- await createOrFindAfterImportOperation(this.database, context.collId, afterImportOperationContext);
218
- // this.postImportActionsQueue.push({
219
- // context: afterImportActionContext,
210
+ // const afterImportOperationContext = ContextObject.parse({
211
+ // dbId: db.$id,
212
+ // collectionId: collection.$id,
220
213
  // finalItem: finalItem,
221
214
  // attributeMappings: attributeMappingsWithActions,
215
+ // context: afterImportActionContext,
222
216
  // });
217
+ // await createOrFindAfterImportOperation(
218
+ // this.database,
219
+ // context.collId,
220
+ // afterImportOperationContext
221
+ // );
222
+ this.postImportActionsQueue.push({
223
+ context: afterImportActionContext,
224
+ finalItem: finalItem,
225
+ attributeMappings: attributeMappingsWithActions,
226
+ });
223
227
  }
224
228
  }));
225
229
  results.forEach((result) => {
@@ -314,16 +318,29 @@ export class ImportController {
314
318
  });
315
319
  }
316
320
  async executePostImportActions(dbId) {
317
- const collectionActionsPromises = [];
318
- for (const collection of this.config.collections) {
319
- collectionActionsPromises.push(this.executeActionsInParallel(dbId, collection));
321
+ let actionQueue = [];
322
+ for (const action of this.postImportActionsQueue) {
323
+ actionQueue.push(this.importDataActions.executeAfterImportActions(action.finalItem, action.attributeMappings, action.context));
320
324
  }
321
- const results = await Promise.allSettled(collectionActionsPromises);
325
+ const results = await Promise.allSettled(actionQueue);
322
326
  results.forEach((result) => {
323
327
  if (result.status === "rejected") {
324
- console.error("A process batch promise was rejected:", result.reason);
328
+ console.error("An action promise was rejected:", result.reason);
329
+ logger.error(`An action promise was rejected: ${result.reason} -- ${JSON.stringify(result)}`);
325
330
  }
326
331
  });
332
+ // const collectionActionsPromises = [];
333
+ // for (const collection of this.config.collections) {
334
+ // collectionActionsPromises.push(
335
+ // this.executeActionsInParallel(dbId, collection)
336
+ // );
337
+ // }
338
+ // const results = await Promise.allSettled(collectionActionsPromises);
339
+ // results.forEach((result) => {
340
+ // if (result.status === "rejected") {
341
+ // console.error("A process batch promise was rejected:", result.reason);
342
+ // }
343
+ // });
327
344
  }
328
345
  async executeActionsInParallel(dbId, collection) {
329
346
  const collectionExists = await checkForCollection(this.database, dbId, collection);
@@ -193,7 +193,7 @@ export class ImportDataActions {
193
193
  resolvedString = resolvedString.replace(match[0], value);
194
194
  }
195
195
  else {
196
- logger.warn(`Failed to resolve ${template} in context: `, context);
196
+ logger.warn(`Failed to resolve ${template} in context: `, JSON.stringify({ ...context, ...item }, null, 2));
197
197
  }
198
198
  }
199
199
  // console.log(`Resolved string: ${resolvedString}`);
@@ -7,5 +7,5 @@ export declare class UsersController {
7
7
  static userFields: string[];
8
8
  constructor(config: AppwriteConfig, db: Databases);
9
9
  wipeUsers(): Promise<void>;
10
- createUserAndReturn(item: AuthUserCreate): Promise<Models.User<Models.Preferences>>;
10
+ createUserAndReturn(item: AuthUserCreate): Promise<Models.User<Models.Preferences> | undefined>;
11
11
  }
@@ -40,72 +40,79 @@ export class UsersController {
40
40
  }
41
41
  }
42
42
  async createUserAndReturn(item) {
43
- // Attempt to find an existing user by email or phone.
44
- let foundUsers = [];
45
- if (item.email) {
46
- const foundUsersByEmail = await this.users.list([
47
- Query.equal("email", item.email),
48
- ]);
49
- foundUsers = foundUsersByEmail.users;
50
- }
51
- if (item.phone) {
52
- const foundUsersByPhone = await this.users.list([
53
- Query.equal("phone", item.phone),
54
- ]);
55
- foundUsers = foundUsers.length
56
- ? foundUsers.concat(foundUsersByPhone.users)
57
- : foundUsersByPhone.users;
58
- }
59
- let userToReturn = foundUsers[0] || undefined;
60
- if (!userToReturn) {
61
- userToReturn = await this.users.create(item.userId || ID.unique(), item.email || undefined, item.phone && item.phone.length < 15 && item.phone.startsWith("+")
62
- ? item.phone
63
- : undefined, item.password?.toLowerCase() || `changeMe${item.email}`.toLowerCase(), item.name || undefined);
64
- }
65
- else {
66
- // Update user details as necessary, ensuring email uniqueness if attempting an update.
67
- if (item.email &&
68
- item.email !== userToReturn.email &&
69
- !_.isEmpty(item.email) &&
70
- !_.isUndefined(item.email)) {
71
- const emailExists = await this.users.list([
43
+ let userToReturn = undefined;
44
+ try {
45
+ // Attempt to find an existing user by email or phone.
46
+ let foundUsers = [];
47
+ if (item.email) {
48
+ const foundUsersByEmail = await this.users.list([
72
49
  Query.equal("email", item.email),
73
50
  ]);
74
- if (emailExists.users.length === 0) {
75
- userToReturn = await this.users.updateEmail(userToReturn.$id, item.email);
51
+ foundUsers = foundUsersByEmail.users;
52
+ }
53
+ if (item.phone) {
54
+ const foundUsersByPhone = await this.users.list([
55
+ Query.equal("phone", item.phone),
56
+ ]);
57
+ foundUsers = foundUsers.length
58
+ ? foundUsers.concat(foundUsersByPhone.users)
59
+ : foundUsersByPhone.users;
60
+ }
61
+ userToReturn = foundUsers[0] || undefined;
62
+ if (!userToReturn) {
63
+ userToReturn = await this.users.create(item.userId || ID.unique(), item.email || undefined, item.phone && item.phone.length < 15 && item.phone.startsWith("+")
64
+ ? item.phone
65
+ : undefined, item.password?.toLowerCase() || `changeMe${item.email}`.toLowerCase(), item.name || undefined);
66
+ }
67
+ else {
68
+ // Update user details as necessary, ensuring email uniqueness if attempting an update.
69
+ if (item.email &&
70
+ item.email !== userToReturn.email &&
71
+ !_.isEmpty(item.email) &&
72
+ !_.isUndefined(item.email)) {
73
+ const emailExists = await this.users.list([
74
+ Query.equal("email", item.email),
75
+ ]);
76
+ if (emailExists.users.length === 0) {
77
+ userToReturn = await this.users.updateEmail(userToReturn.$id, item.email);
78
+ }
79
+ else {
80
+ console.log("Email update skipped: Email already exists.");
81
+ }
82
+ }
83
+ if (item.password) {
84
+ userToReturn = await this.users.updatePassword(userToReturn.$id, item.password.toLowerCase());
76
85
  }
77
- else {
78
- console.log("Email update skipped: Email already exists.");
86
+ if (item.name && item.name !== userToReturn.name) {
87
+ userToReturn = await this.users.updateName(userToReturn.$id, item.name);
88
+ }
89
+ if (item.phone &&
90
+ item.phone !== userToReturn.phone &&
91
+ item.phone.length < 15 &&
92
+ item.phone.startsWith("+") &&
93
+ (_.isUndefined(userToReturn.phone) || _.isEmpty(userToReturn.phone))) {
94
+ const userFoundWithPhone = await this.users.list([
95
+ Query.equal("phone", item.phone),
96
+ ]);
97
+ if (userFoundWithPhone.total === 0) {
98
+ userToReturn = await this.users.updatePhone(userToReturn.$id, item.phone);
99
+ }
79
100
  }
80
101
  }
81
- if (item.password) {
82
- userToReturn = await this.users.updatePassword(userToReturn.$id, item.password.toLowerCase());
102
+ if (item.$createdAt && item.$updatedAt) {
103
+ console.log("$createdAt and $updatedAt are not yet supported, sorry about that!");
83
104
  }
84
- if (item.name && item.name !== userToReturn.name) {
85
- userToReturn = await this.users.updateName(userToReturn.$id, item.name);
105
+ if (item.labels && item.labels.length) {
106
+ userToReturn = await this.users.updateLabels(userToReturn.$id, item.labels);
86
107
  }
87
- if (item.phone &&
88
- item.phone !== userToReturn.phone &&
89
- item.phone.length < 15 &&
90
- item.phone.startsWith("+") &&
91
- (_.isUndefined(userToReturn.phone) || _.isEmpty(userToReturn.phone))) {
92
- const userFoundWithPhone = await this.users.list([
93
- Query.equal("phone", item.phone),
94
- ]);
95
- if (userFoundWithPhone.total === 0) {
96
- userToReturn = await this.users.updatePhone(userToReturn.$id, item.phone);
97
- }
108
+ if (item.prefs && Object.keys(item.prefs).length) {
109
+ await this.users.updatePrefs(userToReturn.$id, item.prefs);
110
+ userToReturn.prefs = item.prefs;
98
111
  }
112
+ return userToReturn;
99
113
  }
100
- if (item.$createdAt && item.$updatedAt) {
101
- console.log("$createdAt and $updatedAt are not yet supported, sorry about that!");
102
- }
103
- if (item.labels && item.labels.length) {
104
- userToReturn = await this.users.updateLabels(userToReturn.$id, item.labels);
105
- }
106
- if (item.prefs && Object.keys(item.prefs).length) {
107
- userToReturn = await this.users.updatePrefs(userToReturn.$id, item.prefs);
114
+ catch (error) {
115
+ return userToReturn;
108
116
  }
109
- return userToReturn;
110
117
  }
111
118
  }
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.16",
4
+ "version": "0.0.18",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -47,11 +47,11 @@ export class ImportController {
47
47
  private setupOptions: SetupOptions;
48
48
  private documentCache: Map<string, any>;
49
49
  private batchLimit: number = 25; // Define batch size limit
50
- // private postImportActionsQueue: {
51
- // context: any;
52
- // finalItem: any;
53
- // attributeMappings: AttributeMappings;
54
- // }[] = [];
50
+ private postImportActionsQueue: {
51
+ context: any;
52
+ finalItem: any;
53
+ attributeMappings: AttributeMappings;
54
+ }[] = [];
55
55
 
56
56
  constructor(
57
57
  config: AppwriteConfig,
@@ -284,6 +284,12 @@ export class ImportController {
284
284
  const user = await usersController.createUserAndReturn(
285
285
  userToCreate.data
286
286
  );
287
+ if (!user) {
288
+ logger.error(
289
+ `Skipping user & contact creation for ${item} because of an error...`
290
+ );
291
+ return;
292
+ }
287
293
  createIdToUse = user.$id;
288
294
  context.docId = createIdToUse;
289
295
  context = { ...context, ...user };
@@ -351,23 +357,23 @@ export class ImportController {
351
357
  logger.info(
352
358
  `Pushing to post-import actions queue for ${context.docId}`
353
359
  );
354
- const afterImportOperationContext = ContextObject.parse({
355
- dbId: db.$id,
356
- collectionId: collection.$id,
357
- finalItem: finalItem,
358
- attributeMappings: attributeMappingsWithActions,
359
- context: afterImportActionContext,
360
- });
361
- await createOrFindAfterImportOperation(
362
- this.database,
363
- context.collId,
364
- afterImportOperationContext
365
- );
366
- // this.postImportActionsQueue.push({
367
- // context: afterImportActionContext,
360
+ // const afterImportOperationContext = ContextObject.parse({
361
+ // dbId: db.$id,
362
+ // collectionId: collection.$id,
368
363
  // finalItem: finalItem,
369
364
  // attributeMappings: attributeMappingsWithActions,
365
+ // context: afterImportActionContext,
370
366
  // });
367
+ // await createOrFindAfterImportOperation(
368
+ // this.database,
369
+ // context.collId,
370
+ // afterImportOperationContext
371
+ // );
372
+ this.postImportActionsQueue.push({
373
+ context: afterImportActionContext,
374
+ finalItem: finalItem,
375
+ attributeMappings: attributeMappingsWithActions,
376
+ });
371
377
  }
372
378
  })
373
379
  );
@@ -506,18 +512,39 @@ export class ImportController {
506
512
  }
507
513
 
508
514
  async executePostImportActions(dbId: string) {
509
- const collectionActionsPromises = [];
510
- for (const collection of this.config.collections) {
511
- collectionActionsPromises.push(
512
- this.executeActionsInParallel(dbId, collection)
515
+ let actionQueue: Promise<any>[] = [];
516
+ for (const action of this.postImportActionsQueue) {
517
+ actionQueue.push(
518
+ this.importDataActions.executeAfterImportActions(
519
+ action.finalItem,
520
+ action.attributeMappings,
521
+ action.context
522
+ )
513
523
  );
514
524
  }
515
- const results = await Promise.allSettled(collectionActionsPromises);
525
+ const results = await Promise.allSettled(actionQueue);
516
526
  results.forEach((result) => {
517
527
  if (result.status === "rejected") {
518
- console.error("A process batch promise was rejected:", result.reason);
528
+ console.error("An action promise was rejected:", result.reason);
529
+ logger.error(
530
+ `An action promise was rejected: ${result.reason} -- ${JSON.stringify(
531
+ result
532
+ )}`
533
+ );
519
534
  }
520
535
  });
536
+ // const collectionActionsPromises = [];
537
+ // for (const collection of this.config.collections) {
538
+ // collectionActionsPromises.push(
539
+ // this.executeActionsInParallel(dbId, collection)
540
+ // );
541
+ // }
542
+ // const results = await Promise.allSettled(collectionActionsPromises);
543
+ // results.forEach((result) => {
544
+ // if (result.status === "rejected") {
545
+ // console.error("A process batch promise was rejected:", result.reason);
546
+ // }
547
+ // });
521
548
  }
522
549
 
523
550
  async executeActionsInParallel(dbId: string, collection: ConfigCollection) {
@@ -285,7 +285,10 @@ export class ImportDataActions {
285
285
  : resolvedValue;
286
286
  resolvedString = resolvedString.replace(match[0], value);
287
287
  } else {
288
- logger.warn(`Failed to resolve ${template} in context: `, context);
288
+ logger.warn(
289
+ `Failed to resolve ${template} in context: `,
290
+ JSON.stringify({ ...context, ...item }, null, 2)
291
+ );
289
292
  }
290
293
  }
291
294
  // console.log(`Resolved string: ${resolvedString}`);
@@ -49,96 +49,105 @@ export class UsersController {
49
49
  }
50
50
 
51
51
  async createUserAndReturn(item: AuthUserCreate) {
52
- // Attempt to find an existing user by email or phone.
53
- let foundUsers: Models.User<Models.Preferences>[] = [];
54
- if (item.email) {
55
- const foundUsersByEmail = await this.users.list([
56
- Query.equal("email", item.email),
57
- ]);
58
- foundUsers = foundUsersByEmail.users;
59
- }
60
- if (item.phone) {
61
- const foundUsersByPhone = await this.users.list([
62
- Query.equal("phone", item.phone),
63
- ]);
64
- foundUsers = foundUsers.length
65
- ? foundUsers.concat(foundUsersByPhone.users)
66
- : foundUsersByPhone.users;
67
- }
68
-
69
- let userToReturn = foundUsers[0] || undefined;
70
-
71
- if (!userToReturn) {
72
- userToReturn = await this.users.create(
73
- item.userId || ID.unique(),
74
- item.email || undefined,
75
- item.phone && item.phone.length < 15 && item.phone.startsWith("+")
76
- ? item.phone
77
- : undefined,
78
- item.password?.toLowerCase() || `changeMe${item.email}`.toLowerCase(),
79
- item.name || undefined
80
- );
81
- } else {
82
- // Update user details as necessary, ensuring email uniqueness if attempting an update.
83
- if (
84
- item.email &&
85
- item.email !== userToReturn.email &&
86
- !_.isEmpty(item.email) &&
87
- !_.isUndefined(item.email)
88
- ) {
89
- const emailExists = await this.users.list([
52
+ let userToReturn: Models.User<Models.Preferences> | undefined = undefined;
53
+ try {
54
+ // Attempt to find an existing user by email or phone.
55
+ let foundUsers: Models.User<Models.Preferences>[] = [];
56
+ if (item.email) {
57
+ const foundUsersByEmail = await this.users.list([
90
58
  Query.equal("email", item.email),
91
59
  ]);
92
- if (emailExists.users.length === 0) {
93
- userToReturn = await this.users.updateEmail(
60
+ foundUsers = foundUsersByEmail.users;
61
+ }
62
+ if (item.phone) {
63
+ const foundUsersByPhone = await this.users.list([
64
+ Query.equal("phone", item.phone),
65
+ ]);
66
+ foundUsers = foundUsers.length
67
+ ? foundUsers.concat(foundUsersByPhone.users)
68
+ : foundUsersByPhone.users;
69
+ }
70
+
71
+ userToReturn = foundUsers[0] || undefined;
72
+
73
+ if (!userToReturn) {
74
+ userToReturn = await this.users.create(
75
+ item.userId || ID.unique(),
76
+ item.email || undefined,
77
+ item.phone && item.phone.length < 15 && item.phone.startsWith("+")
78
+ ? item.phone
79
+ : undefined,
80
+ item.password?.toLowerCase() || `changeMe${item.email}`.toLowerCase(),
81
+ item.name || undefined
82
+ );
83
+ } else {
84
+ // Update user details as necessary, ensuring email uniqueness if attempting an update.
85
+ if (
86
+ item.email &&
87
+ item.email !== userToReturn.email &&
88
+ !_.isEmpty(item.email) &&
89
+ !_.isUndefined(item.email)
90
+ ) {
91
+ const emailExists = await this.users.list([
92
+ Query.equal("email", item.email),
93
+ ]);
94
+ if (emailExists.users.length === 0) {
95
+ userToReturn = await this.users.updateEmail(
96
+ userToReturn.$id,
97
+ item.email
98
+ );
99
+ } else {
100
+ console.log("Email update skipped: Email already exists.");
101
+ }
102
+ }
103
+ if (item.password) {
104
+ userToReturn = await this.users.updatePassword(
94
105
  userToReturn.$id,
95
- item.email
106
+ item.password.toLowerCase()
96
107
  );
97
- } else {
98
- console.log("Email update skipped: Email already exists.");
108
+ }
109
+ if (item.name && item.name !== userToReturn.name) {
110
+ userToReturn = await this.users.updateName(
111
+ userToReturn.$id,
112
+ item.name
113
+ );
114
+ }
115
+ if (
116
+ item.phone &&
117
+ item.phone !== userToReturn.phone &&
118
+ item.phone.length < 15 &&
119
+ item.phone.startsWith("+") &&
120
+ (_.isUndefined(userToReturn.phone) || _.isEmpty(userToReturn.phone))
121
+ ) {
122
+ const userFoundWithPhone = await this.users.list([
123
+ Query.equal("phone", item.phone),
124
+ ]);
125
+ if (userFoundWithPhone.total === 0) {
126
+ userToReturn = await this.users.updatePhone(
127
+ userToReturn.$id,
128
+ item.phone
129
+ );
130
+ }
99
131
  }
100
132
  }
101
- if (item.password) {
102
- userToReturn = await this.users.updatePassword(
103
- userToReturn.$id,
104
- item.password.toLowerCase()
133
+ if (item.$createdAt && item.$updatedAt) {
134
+ console.log(
135
+ "$createdAt and $updatedAt are not yet supported, sorry about that!"
105
136
  );
106
137
  }
107
- if (item.name && item.name !== userToReturn.name) {
108
- userToReturn = await this.users.updateName(userToReturn.$id, item.name);
138
+ if (item.labels && item.labels.length) {
139
+ userToReturn = await this.users.updateLabels(
140
+ userToReturn.$id,
141
+ item.labels
142
+ );
109
143
  }
110
- if (
111
- item.phone &&
112
- item.phone !== userToReturn.phone &&
113
- item.phone.length < 15 &&
114
- item.phone.startsWith("+") &&
115
- (_.isUndefined(userToReturn.phone) || _.isEmpty(userToReturn.phone))
116
- ) {
117
- const userFoundWithPhone = await this.users.list([
118
- Query.equal("phone", item.phone),
119
- ]);
120
- if (userFoundWithPhone.total === 0) {
121
- userToReturn = await this.users.updatePhone(
122
- userToReturn.$id,
123
- item.phone
124
- );
125
- }
144
+ if (item.prefs && Object.keys(item.prefs).length) {
145
+ await this.users.updatePrefs(userToReturn.$id, item.prefs);
146
+ userToReturn.prefs = item.prefs;
126
147
  }
148
+ return userToReturn;
149
+ } catch (error) {
150
+ return userToReturn;
127
151
  }
128
- if (item.$createdAt && item.$updatedAt) {
129
- console.log(
130
- "$createdAt and $updatedAt are not yet supported, sorry about that!"
131
- );
132
- }
133
- if (item.labels && item.labels.length) {
134
- userToReturn = await this.users.updateLabels(
135
- userToReturn.$id,
136
- item.labels
137
- );
138
- }
139
- if (item.prefs && Object.keys(item.prefs).length) {
140
- userToReturn = await this.users.updatePrefs(userToReturn.$id, item.prefs);
141
- }
142
- return userToReturn;
143
152
  }
144
153
  }