appwrite-utils-cli 1.2.17 → 1.2.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.
- package/README.md +1 -0
- package/dist/migrations/transfer.js +127 -39
- package/package.json +1 -1
- package/src/migrations/transfer.ts +320 -107
package/README.md
CHANGED
@@ -637,6 +637,7 @@ npx appwrite-utils-cli appwrite-migrate --generateConstants --constantsLanguages
|
|
637
637
|
|
638
638
|
### Changelog
|
639
639
|
|
640
|
+
- 1.2.18: Fix users transfer comparison not counting email or phone validation as a reason
|
640
641
|
- 1.2.17: Fixed users transfer not keeping validation of email / phone, temporarily disable bulk transfer to see if permissions aren't being updated by it
|
641
642
|
- 1.2.15: Fixed various transfer and sync functionalities
|
642
643
|
- 1.0.5: Fixed `.` directories being ignored. Normally a good thing
|
@@ -38,7 +38,9 @@ export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucke
|
|
38
38
|
continue;
|
39
39
|
}
|
40
40
|
const fileToCreate = InputFile.fromBuffer(new Uint8Array(fileData), file.name);
|
41
|
-
MessageFormatter.progress(`Creating file: ${file.name}`, {
|
41
|
+
MessageFormatter.progress(`Creating file: ${file.name}`, {
|
42
|
+
prefix: "Transfer",
|
43
|
+
});
|
42
44
|
try {
|
43
45
|
await tryAwaitWithRetry(async () => await storage.createFile(toBucketId, file.$id, fileToCreate, file.$permissions));
|
44
46
|
}
|
@@ -114,7 +116,9 @@ export const transferStorageLocalToRemote = async (localStorage, endpoint, proje
|
|
114
116
|
}
|
115
117
|
catch (error) {
|
116
118
|
// File already exists, so we can skip it
|
117
|
-
MessageFormatter.warning(`File ${file.$id} already exists, skipping...`, {
|
119
|
+
MessageFormatter.warning(`File ${file.$id} already exists, skipping...`, {
|
120
|
+
prefix: "Transfer",
|
121
|
+
});
|
118
122
|
continue;
|
119
123
|
}
|
120
124
|
numberOfFiles++;
|
@@ -163,7 +167,7 @@ export const transferDatabaseLocalToLocal = async (localDb, fromDbId, targetDbId
|
|
163
167
|
}
|
164
168
|
// Handle attributes with enhanced status checking
|
165
169
|
console.log(chalk.blue(`Creating attributes for collection ${collection.name} with enhanced monitoring...`));
|
166
|
-
const allAttributes = collection.attributes.map(attr => parseAttribute(attr));
|
170
|
+
const allAttributes = collection.attributes.map((attr) => parseAttribute(attr));
|
167
171
|
const attributeSuccess = await createUpdateCollectionAttributesWithStatusCheck(localDb, targetDbId, targetCollection, allAttributes);
|
168
172
|
if (!attributeSuccess) {
|
169
173
|
console.log(chalk.red(`❌ Failed to create all attributes for collection ${collection.name}, skipping to next collection`));
|
@@ -223,7 +227,7 @@ export const transferDatabaseLocalToRemote = async (localDb, endpoint, projectId
|
|
223
227
|
}
|
224
228
|
// Handle attributes with enhanced status checking
|
225
229
|
console.log(chalk.blue(`Creating attributes for collection ${collection.name} with enhanced monitoring...`));
|
226
|
-
const attributesToCreate = collection.attributes.map(attr => parseAttribute(attr));
|
230
|
+
const attributesToCreate = collection.attributes.map((attr) => parseAttribute(attr));
|
227
231
|
const attributesSuccess = await createUpdateCollectionAttributesWithStatusCheck(remoteDb, toDbId, targetCollection, attributesToCreate);
|
228
232
|
if (!attributesSuccess) {
|
229
233
|
console.log(chalk.red(`Failed to create some attributes for collection ${collection.name}`));
|
@@ -269,10 +273,75 @@ export const transferUsersLocalToRemote = async (localUsers, endpoint, projectId
|
|
269
273
|
for (const user of usersList.users) {
|
270
274
|
try {
|
271
275
|
// Check if user already exists in remote
|
276
|
+
let remoteUser;
|
272
277
|
try {
|
273
|
-
await tryAwaitWithRetry(async () => remoteUsers.get(user.$id));
|
274
|
-
|
275
|
-
|
278
|
+
remoteUser = await tryAwaitWithRetry(async () => remoteUsers.get(user.$id));
|
279
|
+
// If user exists, update only the differences
|
280
|
+
if (remoteUser) {
|
281
|
+
console.log(chalk.blue(`User ${user.$id} exists, checking for updates...`));
|
282
|
+
let hasUpdates = false;
|
283
|
+
// Update name if different
|
284
|
+
if (remoteUser.name !== user.name) {
|
285
|
+
await tryAwaitWithRetry(async () => remoteUsers.updateName(user.$id, user.name));
|
286
|
+
console.log(chalk.green(`Updated name for user ${user.$id}`));
|
287
|
+
hasUpdates = true;
|
288
|
+
}
|
289
|
+
// Update email if different
|
290
|
+
if (remoteUser.email !== user.email) {
|
291
|
+
await tryAwaitWithRetry(async () => remoteUsers.updateEmail(user.$id, user.email));
|
292
|
+
console.log(chalk.green(`Updated email for user ${user.$id}`));
|
293
|
+
hasUpdates = true;
|
294
|
+
}
|
295
|
+
// Update phone if different
|
296
|
+
const normalizedLocalPhone = user.phone
|
297
|
+
? converterFunctions.convertPhoneStringToUSInternational(user.phone)
|
298
|
+
: undefined;
|
299
|
+
if (remoteUser.phone !== normalizedLocalPhone) {
|
300
|
+
if (normalizedLocalPhone) {
|
301
|
+
await tryAwaitWithRetry(async () => remoteUsers.updatePhone(user.$id, normalizedLocalPhone));
|
302
|
+
}
|
303
|
+
console.log(chalk.green(`Updated phone for user ${user.$id}`));
|
304
|
+
hasUpdates = true;
|
305
|
+
}
|
306
|
+
// Update preferences if different
|
307
|
+
if (JSON.stringify(remoteUser.prefs) !== JSON.stringify(user.prefs)) {
|
308
|
+
await tryAwaitWithRetry(async () => remoteUsers.updatePrefs(user.$id, user.prefs));
|
309
|
+
console.log(chalk.green(`Updated preferences for user ${user.$id}`));
|
310
|
+
hasUpdates = true;
|
311
|
+
}
|
312
|
+
// Update labels if different
|
313
|
+
if (JSON.stringify(remoteUser.labels) !== JSON.stringify(user.labels)) {
|
314
|
+
await tryAwaitWithRetry(async () => remoteUsers.updateLabels(user.$id, user.labels));
|
315
|
+
console.log(chalk.green(`Updated labels for user ${user.$id}`));
|
316
|
+
hasUpdates = true;
|
317
|
+
}
|
318
|
+
// Update email verification if different
|
319
|
+
if (remoteUser.emailVerification !== user.emailVerification) {
|
320
|
+
await tryAwaitWithRetry(async () => remoteUsers.updateEmailVerification(user.$id, user.emailVerification));
|
321
|
+
console.log(chalk.green(`Updated email verification for user ${user.$id}`));
|
322
|
+
hasUpdates = true;
|
323
|
+
}
|
324
|
+
// Update phone verification if different
|
325
|
+
if (remoteUser.phoneVerification !== user.phoneVerification) {
|
326
|
+
await tryAwaitWithRetry(async () => remoteUsers.updatePhoneVerification(user.$id, user.phoneVerification));
|
327
|
+
console.log(chalk.green(`Updated phone verification for user ${user.$id}`));
|
328
|
+
hasUpdates = true;
|
329
|
+
}
|
330
|
+
// Update status if different
|
331
|
+
if (remoteUser.status !== user.status) {
|
332
|
+
await tryAwaitWithRetry(async () => remoteUsers.updateStatus(user.$id, user.status));
|
333
|
+
console.log(chalk.green(`Updated status for user ${user.$id}`));
|
334
|
+
hasUpdates = true;
|
335
|
+
}
|
336
|
+
if (!hasUpdates) {
|
337
|
+
console.log(chalk.yellow(`User ${user.$id} is already up to date, skipping...`));
|
338
|
+
}
|
339
|
+
else {
|
340
|
+
totalTransferred++;
|
341
|
+
console.log(chalk.green(`Updated user ${user.$id}`));
|
342
|
+
}
|
343
|
+
continue;
|
344
|
+
}
|
276
345
|
}
|
277
346
|
catch (error) {
|
278
347
|
// User doesn't exist, proceed with creation
|
@@ -288,55 +357,74 @@ export const transferUsersLocalToRemote = async (localUsers, endpoint, projectId
|
|
288
357
|
const hashOptions = user.hashOptions || {};
|
289
358
|
try {
|
290
359
|
switch (hashType) {
|
291
|
-
case
|
360
|
+
case "argon2":
|
292
361
|
await tryAwaitWithRetry(async () => remoteUsers.createArgon2User(user.$id, user.email, hashedPassword, user.name));
|
293
362
|
break;
|
294
|
-
case
|
363
|
+
case "bcrypt":
|
295
364
|
await tryAwaitWithRetry(async () => remoteUsers.createBcryptUser(user.$id, user.email, hashedPassword, user.name));
|
296
365
|
break;
|
297
|
-
case
|
366
|
+
case "scrypt":
|
298
367
|
// Scrypt requires additional parameters from hashOptions
|
299
|
-
const salt = typeof hashOptions.salt ===
|
300
|
-
const costCpu = typeof hashOptions.costCpu ===
|
301
|
-
|
302
|
-
|
303
|
-
const
|
368
|
+
const salt = typeof hashOptions.salt === "string" ? hashOptions.salt : "";
|
369
|
+
const costCpu = typeof hashOptions.costCpu === "number"
|
370
|
+
? hashOptions.costCpu
|
371
|
+
: 32768;
|
372
|
+
const costMemory = typeof hashOptions.costMemory === "number"
|
373
|
+
? hashOptions.costMemory
|
374
|
+
: 14;
|
375
|
+
const costParallel = typeof hashOptions.costParallel === "number"
|
376
|
+
? hashOptions.costParallel
|
377
|
+
: 1;
|
378
|
+
const length = typeof hashOptions.length === "number"
|
379
|
+
? hashOptions.length
|
380
|
+
: 64;
|
304
381
|
// Warn if using default values due to missing hash options
|
305
|
-
if (!hashOptions.salt ||
|
382
|
+
if (!hashOptions.salt ||
|
383
|
+
typeof hashOptions.costCpu !== "number") {
|
306
384
|
console.log(chalk.yellow(`User ${user.$id}: Using default Scrypt parameters due to missing hashOptions`));
|
307
385
|
}
|
308
386
|
await tryAwaitWithRetry(async () => remoteUsers.createScryptUser(user.$id, user.email, hashedPassword, salt, costCpu, costMemory, costParallel, length, user.name));
|
309
387
|
break;
|
310
|
-
case
|
388
|
+
case "scryptmodified":
|
311
389
|
// Scrypt Modified (Firebase) requires salt, separator, and signer key
|
312
|
-
const modSalt = typeof hashOptions.salt ===
|
313
|
-
const saltSeparator = typeof hashOptions.saltSeparator ===
|
314
|
-
|
390
|
+
const modSalt = typeof hashOptions.salt === "string" ? hashOptions.salt : "";
|
391
|
+
const saltSeparator = typeof hashOptions.saltSeparator === "string"
|
392
|
+
? hashOptions.saltSeparator
|
393
|
+
: "";
|
394
|
+
const signerKey = typeof hashOptions.signerKey === "string"
|
395
|
+
? hashOptions.signerKey
|
396
|
+
: "";
|
315
397
|
// Warn if critical parameters are missing
|
316
|
-
if (!hashOptions.salt ||
|
398
|
+
if (!hashOptions.salt ||
|
399
|
+
!hashOptions.saltSeparator ||
|
400
|
+
!hashOptions.signerKey) {
|
317
401
|
console.log(chalk.yellow(`User ${user.$id}: Missing critical Scrypt Modified parameters in hashOptions`));
|
318
402
|
}
|
319
403
|
await tryAwaitWithRetry(async () => remoteUsers.createScryptModifiedUser(user.$id, user.email, hashedPassword, modSalt, saltSeparator, signerKey, user.name));
|
320
404
|
break;
|
321
|
-
case
|
405
|
+
case "md5":
|
322
406
|
await tryAwaitWithRetry(async () => remoteUsers.createMD5User(user.$id, user.email, hashedPassword, user.name));
|
323
407
|
break;
|
324
|
-
case
|
325
|
-
case
|
326
|
-
case
|
327
|
-
case
|
408
|
+
case "sha":
|
409
|
+
case "sha1":
|
410
|
+
case "sha256":
|
411
|
+
case "sha512":
|
328
412
|
// SHA variants - determine version from hash type
|
329
413
|
const getPasswordHashVersion = (hash) => {
|
330
414
|
switch (hash.toLowerCase()) {
|
331
|
-
case
|
332
|
-
|
333
|
-
case
|
334
|
-
|
415
|
+
case "sha1":
|
416
|
+
return "sha1";
|
417
|
+
case "sha256":
|
418
|
+
return "sha256";
|
419
|
+
case "sha512":
|
420
|
+
return "sha512";
|
421
|
+
default:
|
422
|
+
return "sha256"; // Default to SHA256
|
335
423
|
}
|
336
424
|
};
|
337
425
|
await tryAwaitWithRetry(async () => remoteUsers.createSHAUser(user.$id, user.email, hashedPassword, getPasswordHashVersion(hashType), user.name));
|
338
426
|
break;
|
339
|
-
case
|
427
|
+
case "phpass":
|
340
428
|
await tryAwaitWithRetry(async () => remoteUsers.createPHPassUser(user.$id, user.email, hashedPassword, user.name));
|
341
429
|
break;
|
342
430
|
default:
|
@@ -361,27 +449,27 @@ export const transferUsersLocalToRemote = async (localUsers, endpoint, projectId
|
|
361
449
|
console.log(chalk.yellow(`User ${user.$id} created with temporary password - password reset required`));
|
362
450
|
}
|
363
451
|
}
|
364
|
-
// Update phone, labels, and other attributes
|
452
|
+
// Update phone, labels, and other attributes for newly created users
|
365
453
|
if (phone) {
|
366
454
|
await tryAwaitWithRetry(async () => remoteUsers.updatePhone(user.$id, phone));
|
367
455
|
}
|
368
456
|
if (user.labels && user.labels.length > 0) {
|
369
457
|
await tryAwaitWithRetry(async () => remoteUsers.updateLabels(user.$id, user.labels));
|
370
458
|
}
|
371
|
-
// Update user preferences and status
|
459
|
+
// Update user preferences and status for newly created users
|
372
460
|
await tryAwaitWithRetry(async () => remoteUsers.updatePrefs(user.$id, user.prefs));
|
373
|
-
if (!user.emailVerification) {
|
374
|
-
await tryAwaitWithRetry(async () => remoteUsers.updateEmailVerification(user.$id, false));
|
375
|
-
}
|
376
|
-
if (user.status === false) {
|
377
|
-
await tryAwaitWithRetry(async () => remoteUsers.updateStatus(user.$id, false));
|
378
|
-
}
|
379
461
|
if (user.emailVerification) {
|
380
462
|
await tryAwaitWithRetry(async () => remoteUsers.updateEmailVerification(user.$id, true));
|
381
463
|
}
|
464
|
+
else {
|
465
|
+
await tryAwaitWithRetry(async () => remoteUsers.updateEmailVerification(user.$id, false));
|
466
|
+
}
|
382
467
|
if (user.phoneVerification) {
|
383
468
|
await tryAwaitWithRetry(async () => remoteUsers.updatePhoneVerification(user.$id, true));
|
384
469
|
}
|
470
|
+
if (user.status === false) {
|
471
|
+
await tryAwaitWithRetry(async () => remoteUsers.updateStatus(user.$id, false));
|
472
|
+
}
|
385
473
|
totalTransferred++;
|
386
474
|
console.log(chalk.green(`Transferred user ${user.$id}`));
|
387
475
|
}
|
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": "1.2.
|
4
|
+
"version": "1.2.18",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
@@ -45,7 +45,10 @@ export const transferStorageLocalToLocal = async (
|
|
45
45
|
fromBucketId: string,
|
46
46
|
toBucketId: string
|
47
47
|
) => {
|
48
|
-
MessageFormatter.info(
|
48
|
+
MessageFormatter.info(
|
49
|
+
`Transferring files from ${fromBucketId} to ${toBucketId}`,
|
50
|
+
{ prefix: "Transfer" }
|
51
|
+
);
|
49
52
|
let lastFileId: string | undefined;
|
50
53
|
let fromFiles = await tryAwaitWithRetry(
|
51
54
|
async () => await storage.listFiles(fromBucketId, [Query.limit(100)])
|
@@ -59,7 +62,11 @@ export const transferStorageLocalToLocal = async (
|
|
59
62
|
try {
|
60
63
|
return await storage.getFileDownload(bucketId, fileId);
|
61
64
|
} catch (error) {
|
62
|
-
MessageFormatter.error(
|
65
|
+
MessageFormatter.error(
|
66
|
+
`Error downloading file ${fileId}`,
|
67
|
+
error instanceof Error ? error : new Error(String(error)),
|
68
|
+
{ prefix: "Transfer" }
|
69
|
+
);
|
63
70
|
attempts--;
|
64
71
|
if (attempts === 0) throw error;
|
65
72
|
}
|
@@ -72,14 +79,20 @@ export const transferStorageLocalToLocal = async (
|
|
72
79
|
async () => await downloadFileWithRetry(file.bucketId, file.$id)
|
73
80
|
);
|
74
81
|
if (!fileData) {
|
75
|
-
MessageFormatter.error(
|
82
|
+
MessageFormatter.error(
|
83
|
+
`Error downloading file ${file.$id}`,
|
84
|
+
undefined,
|
85
|
+
{ prefix: "Transfer" }
|
86
|
+
);
|
76
87
|
continue;
|
77
88
|
}
|
78
89
|
const fileToCreate = InputFile.fromBuffer(
|
79
90
|
new Uint8Array(fileData),
|
80
91
|
file.name
|
81
92
|
);
|
82
|
-
MessageFormatter.progress(`Creating file: ${file.name}`, {
|
93
|
+
MessageFormatter.progress(`Creating file: ${file.name}`, {
|
94
|
+
prefix: "Transfer",
|
95
|
+
});
|
83
96
|
try {
|
84
97
|
await tryAwaitWithRetry(
|
85
98
|
async () =>
|
@@ -118,7 +131,11 @@ export const transferStorageLocalToLocal = async (
|
|
118
131
|
async () => await downloadFileWithRetry(file.bucketId, file.$id)
|
119
132
|
);
|
120
133
|
if (!fileData) {
|
121
|
-
MessageFormatter.error(
|
134
|
+
MessageFormatter.error(
|
135
|
+
`Error downloading file ${file.$id}`,
|
136
|
+
undefined,
|
137
|
+
{ prefix: "Transfer" }
|
138
|
+
);
|
122
139
|
continue;
|
123
140
|
}
|
124
141
|
const fileToCreate = InputFile.fromBuffer(
|
@@ -137,7 +154,10 @@ export const transferStorageLocalToLocal = async (
|
|
137
154
|
);
|
138
155
|
} catch (error: any) {
|
139
156
|
// File already exists, so we can skip it
|
140
|
-
MessageFormatter.warning(
|
157
|
+
MessageFormatter.warning(
|
158
|
+
`File ${file.$id} already exists, skipping...`,
|
159
|
+
{ prefix: "Transfer" }
|
160
|
+
);
|
141
161
|
continue;
|
142
162
|
}
|
143
163
|
numberOfFiles++;
|
@@ -208,7 +228,9 @@ export const transferStorageLocalToRemote = async (
|
|
208
228
|
);
|
209
229
|
} catch (error: any) {
|
210
230
|
// File already exists, so we can skip it
|
211
|
-
MessageFormatter.warning(`File ${file.$id} already exists, skipping...`, {
|
231
|
+
MessageFormatter.warning(`File ${file.$id} already exists, skipping...`, {
|
232
|
+
prefix: "Transfer",
|
233
|
+
});
|
212
234
|
continue;
|
213
235
|
}
|
214
236
|
numberOfFiles++;
|
@@ -308,22 +330,37 @@ export const transferDatabaseLocalToLocal = async (
|
|
308
330
|
}
|
309
331
|
|
310
332
|
// Handle attributes with enhanced status checking
|
311
|
-
console.log(
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
333
|
+
console.log(
|
334
|
+
chalk.blue(
|
335
|
+
`Creating attributes for collection ${collection.name} with enhanced monitoring...`
|
336
|
+
)
|
337
|
+
);
|
338
|
+
|
339
|
+
const allAttributes = collection.attributes.map((attr) =>
|
340
|
+
parseAttribute(attr as any)
|
319
341
|
);
|
320
|
-
|
342
|
+
const attributeSuccess =
|
343
|
+
await createUpdateCollectionAttributesWithStatusCheck(
|
344
|
+
localDb,
|
345
|
+
targetDbId,
|
346
|
+
targetCollection,
|
347
|
+
allAttributes
|
348
|
+
);
|
349
|
+
|
321
350
|
if (!attributeSuccess) {
|
322
|
-
console.log(
|
351
|
+
console.log(
|
352
|
+
chalk.red(
|
353
|
+
`❌ Failed to create all attributes for collection ${collection.name}, skipping to next collection`
|
354
|
+
)
|
355
|
+
);
|
323
356
|
continue;
|
324
357
|
}
|
325
|
-
|
326
|
-
console.log(
|
358
|
+
|
359
|
+
console.log(
|
360
|
+
chalk.green(
|
361
|
+
`✅ All attributes created successfully for collection ${collection.name}`
|
362
|
+
)
|
363
|
+
);
|
327
364
|
|
328
365
|
// Handle indexes
|
329
366
|
const existingIndexes = await tryAwaitWithRetry(
|
@@ -361,7 +398,9 @@ export const transferDatabaseLocalToLocal = async (
|
|
361
398
|
}
|
362
399
|
|
363
400
|
// Transfer documents
|
364
|
-
const { transferDocumentsBetweenDbsLocalToLocal } = await import(
|
401
|
+
const { transferDocumentsBetweenDbsLocalToLocal } = await import(
|
402
|
+
"../collections/methods.js"
|
403
|
+
);
|
365
404
|
await transferDocumentsBetweenDbsLocalToLocal(
|
366
405
|
localDb,
|
367
406
|
fromDbId,
|
@@ -456,27 +495,46 @@ export const transferDatabaseLocalToRemote = async (
|
|
456
495
|
}
|
457
496
|
|
458
497
|
// Handle attributes with enhanced status checking
|
459
|
-
console.log(
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
const attributesSuccess = await createUpdateCollectionAttributesWithStatusCheck(
|
464
|
-
remoteDb,
|
465
|
-
toDbId,
|
466
|
-
targetCollection,
|
467
|
-
attributesToCreate
|
498
|
+
console.log(
|
499
|
+
chalk.blue(
|
500
|
+
`Creating attributes for collection ${collection.name} with enhanced monitoring...`
|
501
|
+
)
|
468
502
|
);
|
469
|
-
|
503
|
+
|
504
|
+
const attributesToCreate = collection.attributes.map((attr) =>
|
505
|
+
parseAttribute(attr as any)
|
506
|
+
);
|
507
|
+
|
508
|
+
const attributesSuccess =
|
509
|
+
await createUpdateCollectionAttributesWithStatusCheck(
|
510
|
+
remoteDb,
|
511
|
+
toDbId,
|
512
|
+
targetCollection,
|
513
|
+
attributesToCreate
|
514
|
+
);
|
515
|
+
|
470
516
|
if (!attributesSuccess) {
|
471
|
-
console.log(
|
517
|
+
console.log(
|
518
|
+
chalk.red(
|
519
|
+
`Failed to create some attributes for collection ${collection.name}`
|
520
|
+
)
|
521
|
+
);
|
472
522
|
// Continue with the transfer even if some attributes failed
|
473
523
|
} else {
|
474
|
-
console.log(
|
524
|
+
console.log(
|
525
|
+
chalk.green(
|
526
|
+
`All attributes created successfully for collection ${collection.name}`
|
527
|
+
)
|
528
|
+
);
|
475
529
|
}
|
476
530
|
|
477
531
|
// Handle indexes with enhanced status checking
|
478
|
-
console.log(
|
479
|
-
|
532
|
+
console.log(
|
533
|
+
chalk.blue(
|
534
|
+
`Creating indexes for collection ${collection.name} with enhanced monitoring...`
|
535
|
+
)
|
536
|
+
);
|
537
|
+
|
480
538
|
const indexesSuccess = await createOrUpdateIndexesWithStatusCheck(
|
481
539
|
toDbId,
|
482
540
|
remoteDb,
|
@@ -484,16 +542,26 @@ export const transferDatabaseLocalToRemote = async (
|
|
484
542
|
targetCollection,
|
485
543
|
collection.indexes as any
|
486
544
|
);
|
487
|
-
|
545
|
+
|
488
546
|
if (!indexesSuccess) {
|
489
|
-
console.log(
|
547
|
+
console.log(
|
548
|
+
chalk.red(
|
549
|
+
`Failed to create some indexes for collection ${collection.name}`
|
550
|
+
)
|
551
|
+
);
|
490
552
|
// Continue with the transfer even if some indexes failed
|
491
553
|
} else {
|
492
|
-
console.log(
|
554
|
+
console.log(
|
555
|
+
chalk.green(
|
556
|
+
`All indexes created successfully for collection ${collection.name}`
|
557
|
+
)
|
558
|
+
);
|
493
559
|
}
|
494
560
|
|
495
561
|
// Transfer documents
|
496
|
-
const { transferDocumentsBetweenDbsLocalToRemote } = await import(
|
562
|
+
const { transferDocumentsBetweenDbsLocalToRemote } = await import(
|
563
|
+
"../collections/methods.js"
|
564
|
+
);
|
497
565
|
await transferDocumentsBetweenDbsLocalToRemote(
|
498
566
|
localDb,
|
499
567
|
endpoint,
|
@@ -544,12 +612,102 @@ export const transferUsersLocalToRemote = async (
|
|
544
612
|
for (const user of usersList.users) {
|
545
613
|
try {
|
546
614
|
// Check if user already exists in remote
|
615
|
+
let remoteUser: Models.User<Models.Preferences> | undefined;
|
547
616
|
try {
|
548
|
-
await tryAwaitWithRetry(async () =>
|
549
|
-
|
550
|
-
chalk.yellow(`User ${user.$id} already exists, skipping...`)
|
617
|
+
remoteUser = await tryAwaitWithRetry(async () =>
|
618
|
+
remoteUsers.get(user.$id)
|
551
619
|
);
|
552
|
-
|
620
|
+
|
621
|
+
// If user exists, update only the differences
|
622
|
+
if (remoteUser) {
|
623
|
+
console.log(chalk.blue(`User ${user.$id} exists, checking for updates...`));
|
624
|
+
let hasUpdates = false;
|
625
|
+
|
626
|
+
// Update name if different
|
627
|
+
if (remoteUser.name !== user.name) {
|
628
|
+
await tryAwaitWithRetry(async () =>
|
629
|
+
remoteUsers.updateName(user.$id, user.name)
|
630
|
+
);
|
631
|
+
console.log(chalk.green(`Updated name for user ${user.$id}`));
|
632
|
+
hasUpdates = true;
|
633
|
+
}
|
634
|
+
|
635
|
+
// Update email if different
|
636
|
+
if (remoteUser.email !== user.email) {
|
637
|
+
await tryAwaitWithRetry(async () =>
|
638
|
+
remoteUsers.updateEmail(user.$id, user.email)
|
639
|
+
);
|
640
|
+
console.log(chalk.green(`Updated email for user ${user.$id}`));
|
641
|
+
hasUpdates = true;
|
642
|
+
}
|
643
|
+
|
644
|
+
// Update phone if different
|
645
|
+
const normalizedLocalPhone = user.phone
|
646
|
+
? converterFunctions.convertPhoneStringToUSInternational(user.phone)
|
647
|
+
: undefined;
|
648
|
+
if (remoteUser.phone !== normalizedLocalPhone) {
|
649
|
+
if (normalizedLocalPhone) {
|
650
|
+
await tryAwaitWithRetry(async () =>
|
651
|
+
remoteUsers.updatePhone(user.$id, normalizedLocalPhone)
|
652
|
+
);
|
653
|
+
}
|
654
|
+
console.log(chalk.green(`Updated phone for user ${user.$id}`));
|
655
|
+
hasUpdates = true;
|
656
|
+
}
|
657
|
+
|
658
|
+
// Update preferences if different
|
659
|
+
if (JSON.stringify(remoteUser.prefs) !== JSON.stringify(user.prefs)) {
|
660
|
+
await tryAwaitWithRetry(async () =>
|
661
|
+
remoteUsers.updatePrefs(user.$id, user.prefs)
|
662
|
+
);
|
663
|
+
console.log(chalk.green(`Updated preferences for user ${user.$id}`));
|
664
|
+
hasUpdates = true;
|
665
|
+
}
|
666
|
+
|
667
|
+
// Update labels if different
|
668
|
+
if (JSON.stringify(remoteUser.labels) !== JSON.stringify(user.labels)) {
|
669
|
+
await tryAwaitWithRetry(async () =>
|
670
|
+
remoteUsers.updateLabels(user.$id, user.labels)
|
671
|
+
);
|
672
|
+
console.log(chalk.green(`Updated labels for user ${user.$id}`));
|
673
|
+
hasUpdates = true;
|
674
|
+
}
|
675
|
+
|
676
|
+
// Update email verification if different
|
677
|
+
if (remoteUser.emailVerification !== user.emailVerification) {
|
678
|
+
await tryAwaitWithRetry(async () =>
|
679
|
+
remoteUsers.updateEmailVerification(user.$id, user.emailVerification)
|
680
|
+
);
|
681
|
+
console.log(chalk.green(`Updated email verification for user ${user.$id}`));
|
682
|
+
hasUpdates = true;
|
683
|
+
}
|
684
|
+
|
685
|
+
// Update phone verification if different
|
686
|
+
if (remoteUser.phoneVerification !== user.phoneVerification) {
|
687
|
+
await tryAwaitWithRetry(async () =>
|
688
|
+
remoteUsers.updatePhoneVerification(user.$id, user.phoneVerification)
|
689
|
+
);
|
690
|
+
console.log(chalk.green(`Updated phone verification for user ${user.$id}`));
|
691
|
+
hasUpdates = true;
|
692
|
+
}
|
693
|
+
|
694
|
+
// Update status if different
|
695
|
+
if (remoteUser.status !== user.status) {
|
696
|
+
await tryAwaitWithRetry(async () =>
|
697
|
+
remoteUsers.updateStatus(user.$id, user.status)
|
698
|
+
);
|
699
|
+
console.log(chalk.green(`Updated status for user ${user.$id}`));
|
700
|
+
hasUpdates = true;
|
701
|
+
}
|
702
|
+
|
703
|
+
if (!hasUpdates) {
|
704
|
+
console.log(chalk.yellow(`User ${user.$id} is already up to date, skipping...`));
|
705
|
+
} else {
|
706
|
+
totalTransferred++;
|
707
|
+
console.log(chalk.green(`Updated user ${user.$id}`));
|
708
|
+
}
|
709
|
+
continue;
|
710
|
+
}
|
553
711
|
} catch (error: any) {
|
554
712
|
// User doesn't exist, proceed with creation
|
555
713
|
}
|
@@ -564,10 +722,10 @@ export const transferUsersLocalToRemote = async (
|
|
564
722
|
const hashType = user.hash.toLowerCase();
|
565
723
|
const hashedPassword = user.password; // This is already hashed
|
566
724
|
const hashOptions = (user.hashOptions as Record<string, any>) || {};
|
567
|
-
|
725
|
+
|
568
726
|
try {
|
569
727
|
switch (hashType) {
|
570
|
-
case
|
728
|
+
case "argon2":
|
571
729
|
await tryAwaitWithRetry(async () =>
|
572
730
|
remoteUsers.createArgon2User(
|
573
731
|
user.$id,
|
@@ -577,8 +735,8 @@ export const transferUsersLocalToRemote = async (
|
|
577
735
|
)
|
578
736
|
);
|
579
737
|
break;
|
580
|
-
|
581
|
-
case
|
738
|
+
|
739
|
+
case "bcrypt":
|
582
740
|
await tryAwaitWithRetry(async () =>
|
583
741
|
remoteUsers.createBcryptUser(
|
584
742
|
user.$id,
|
@@ -588,20 +746,40 @@ export const transferUsersLocalToRemote = async (
|
|
588
746
|
)
|
589
747
|
);
|
590
748
|
break;
|
591
|
-
|
592
|
-
case
|
749
|
+
|
750
|
+
case "scrypt":
|
593
751
|
// Scrypt requires additional parameters from hashOptions
|
594
|
-
const salt =
|
595
|
-
|
596
|
-
const
|
597
|
-
|
598
|
-
|
599
|
-
|
752
|
+
const salt =
|
753
|
+
typeof hashOptions.salt === "string" ? hashOptions.salt : "";
|
754
|
+
const costCpu =
|
755
|
+
typeof hashOptions.costCpu === "number"
|
756
|
+
? hashOptions.costCpu
|
757
|
+
: 32768;
|
758
|
+
const costMemory =
|
759
|
+
typeof hashOptions.costMemory === "number"
|
760
|
+
? hashOptions.costMemory
|
761
|
+
: 14;
|
762
|
+
const costParallel =
|
763
|
+
typeof hashOptions.costParallel === "number"
|
764
|
+
? hashOptions.costParallel
|
765
|
+
: 1;
|
766
|
+
const length =
|
767
|
+
typeof hashOptions.length === "number"
|
768
|
+
? hashOptions.length
|
769
|
+
: 64;
|
770
|
+
|
600
771
|
// Warn if using default values due to missing hash options
|
601
|
-
if (
|
602
|
-
|
772
|
+
if (
|
773
|
+
!hashOptions.salt ||
|
774
|
+
typeof hashOptions.costCpu !== "number"
|
775
|
+
) {
|
776
|
+
console.log(
|
777
|
+
chalk.yellow(
|
778
|
+
`User ${user.$id}: Using default Scrypt parameters due to missing hashOptions`
|
779
|
+
)
|
780
|
+
);
|
603
781
|
}
|
604
|
-
|
782
|
+
|
605
783
|
await tryAwaitWithRetry(async () =>
|
606
784
|
remoteUsers.createScryptUser(
|
607
785
|
user.$id,
|
@@ -616,18 +794,33 @@ export const transferUsersLocalToRemote = async (
|
|
616
794
|
)
|
617
795
|
);
|
618
796
|
break;
|
619
|
-
|
620
|
-
case
|
797
|
+
|
798
|
+
case "scryptmodified":
|
621
799
|
// Scrypt Modified (Firebase) requires salt, separator, and signer key
|
622
|
-
const modSalt =
|
623
|
-
|
624
|
-
const
|
625
|
-
|
800
|
+
const modSalt =
|
801
|
+
typeof hashOptions.salt === "string" ? hashOptions.salt : "";
|
802
|
+
const saltSeparator =
|
803
|
+
typeof hashOptions.saltSeparator === "string"
|
804
|
+
? hashOptions.saltSeparator
|
805
|
+
: "";
|
806
|
+
const signerKey =
|
807
|
+
typeof hashOptions.signerKey === "string"
|
808
|
+
? hashOptions.signerKey
|
809
|
+
: "";
|
810
|
+
|
626
811
|
// Warn if critical parameters are missing
|
627
|
-
if (
|
628
|
-
|
812
|
+
if (
|
813
|
+
!hashOptions.salt ||
|
814
|
+
!hashOptions.saltSeparator ||
|
815
|
+
!hashOptions.signerKey
|
816
|
+
) {
|
817
|
+
console.log(
|
818
|
+
chalk.yellow(
|
819
|
+
`User ${user.$id}: Missing critical Scrypt Modified parameters in hashOptions`
|
820
|
+
)
|
821
|
+
);
|
629
822
|
}
|
630
|
-
|
823
|
+
|
631
824
|
await tryAwaitWithRetry(async () =>
|
632
825
|
remoteUsers.createScryptModifiedUser(
|
633
826
|
user.$id,
|
@@ -640,8 +833,8 @@ export const transferUsersLocalToRemote = async (
|
|
640
833
|
)
|
641
834
|
);
|
642
835
|
break;
|
643
|
-
|
644
|
-
case
|
836
|
+
|
837
|
+
case "md5":
|
645
838
|
await tryAwaitWithRetry(async () =>
|
646
839
|
remoteUsers.createMD5User(
|
647
840
|
user.$id,
|
@@ -651,21 +844,25 @@ export const transferUsersLocalToRemote = async (
|
|
651
844
|
)
|
652
845
|
);
|
653
846
|
break;
|
654
|
-
|
655
|
-
case
|
656
|
-
case
|
657
|
-
case
|
658
|
-
case
|
847
|
+
|
848
|
+
case "sha":
|
849
|
+
case "sha1":
|
850
|
+
case "sha256":
|
851
|
+
case "sha512":
|
659
852
|
// SHA variants - determine version from hash type
|
660
853
|
const getPasswordHashVersion = (hash: string) => {
|
661
854
|
switch (hash.toLowerCase()) {
|
662
|
-
case
|
663
|
-
|
664
|
-
case
|
665
|
-
|
855
|
+
case "sha1":
|
856
|
+
return "sha1" as any;
|
857
|
+
case "sha256":
|
858
|
+
return "sha256" as any;
|
859
|
+
case "sha512":
|
860
|
+
return "sha512" as any;
|
861
|
+
default:
|
862
|
+
return "sha256" as any; // Default to SHA256
|
666
863
|
}
|
667
864
|
};
|
668
|
-
|
865
|
+
|
669
866
|
await tryAwaitWithRetry(async () =>
|
670
867
|
remoteUsers.createSHAUser(
|
671
868
|
user.$id,
|
@@ -676,8 +873,8 @@ export const transferUsersLocalToRemote = async (
|
|
676
873
|
)
|
677
874
|
);
|
678
875
|
break;
|
679
|
-
|
680
|
-
case
|
876
|
+
|
877
|
+
case "phpass":
|
681
878
|
await tryAwaitWithRetry(async () =>
|
682
879
|
remoteUsers.createPHPassUser(
|
683
880
|
user.$id,
|
@@ -687,9 +884,13 @@ export const transferUsersLocalToRemote = async (
|
|
687
884
|
)
|
688
885
|
);
|
689
886
|
break;
|
690
|
-
|
887
|
+
|
691
888
|
default:
|
692
|
-
console.log(
|
889
|
+
console.log(
|
890
|
+
chalk.yellow(
|
891
|
+
`Unknown hash type '${hashType}' for user ${user.$id}, falling back to Argon2`
|
892
|
+
)
|
893
|
+
);
|
693
894
|
await tryAwaitWithRetry(async () =>
|
694
895
|
remoteUsers.createArgon2User(
|
695
896
|
user.$id,
|
@@ -700,12 +901,19 @@ export const transferUsersLocalToRemote = async (
|
|
700
901
|
);
|
701
902
|
break;
|
702
903
|
}
|
703
|
-
|
704
|
-
console.log(
|
705
|
-
|
904
|
+
|
905
|
+
console.log(
|
906
|
+
chalk.green(
|
907
|
+
`User ${user.$id} created with preserved ${hashType} password`
|
908
|
+
)
|
909
|
+
);
|
706
910
|
} catch (error) {
|
707
|
-
console.log(
|
708
|
-
|
911
|
+
console.log(
|
912
|
+
chalk.yellow(
|
913
|
+
`Failed to create user ${user.$id} with ${hashType} hash, trying with temporary password`
|
914
|
+
)
|
915
|
+
);
|
916
|
+
|
709
917
|
// Fallback to creating user with temporary password
|
710
918
|
await tryAwaitWithRetry(async () =>
|
711
919
|
remoteUsers.create(
|
@@ -716,14 +924,17 @@ export const transferUsersLocalToRemote = async (
|
|
716
924
|
user.name
|
717
925
|
)
|
718
926
|
);
|
719
|
-
|
720
|
-
console.log(
|
927
|
+
|
928
|
+
console.log(
|
929
|
+
chalk.yellow(
|
930
|
+
`User ${user.$id} created with temporary password - password reset required`
|
931
|
+
)
|
932
|
+
);
|
721
933
|
}
|
722
|
-
|
723
934
|
} else {
|
724
935
|
// No hash or password - create with temporary password
|
725
936
|
const tempPassword = user.password || `changeMe${user.email}`;
|
726
|
-
|
937
|
+
|
727
938
|
await tryAwaitWithRetry(async () =>
|
728
939
|
remoteUsers.create(
|
729
940
|
user.$id,
|
@@ -733,51 +944,53 @@ export const transferUsersLocalToRemote = async (
|
|
733
944
|
user.name
|
734
945
|
)
|
735
946
|
);
|
736
|
-
|
947
|
+
|
737
948
|
if (!user.password) {
|
738
|
-
console.log(
|
949
|
+
console.log(
|
950
|
+
chalk.yellow(
|
951
|
+
`User ${user.$id} created with temporary password - password reset required`
|
952
|
+
)
|
953
|
+
);
|
739
954
|
}
|
740
955
|
}
|
741
|
-
|
742
|
-
// Update phone, labels, and other attributes
|
956
|
+
|
957
|
+
// Update phone, labels, and other attributes for newly created users
|
743
958
|
if (phone) {
|
744
959
|
await tryAwaitWithRetry(async () =>
|
745
960
|
remoteUsers.updatePhone(user.$id, phone)
|
746
961
|
);
|
747
962
|
}
|
748
|
-
|
963
|
+
|
749
964
|
if (user.labels && user.labels.length > 0) {
|
750
965
|
await tryAwaitWithRetry(async () =>
|
751
966
|
remoteUsers.updateLabels(user.$id, user.labels)
|
752
967
|
);
|
753
968
|
}
|
754
969
|
|
755
|
-
// Update user preferences and status
|
970
|
+
// Update user preferences and status for newly created users
|
756
971
|
await tryAwaitWithRetry(async () =>
|
757
972
|
remoteUsers.updatePrefs(user.$id, user.prefs)
|
758
973
|
);
|
759
974
|
|
760
|
-
if (
|
975
|
+
if (user.emailVerification) {
|
761
976
|
await tryAwaitWithRetry(async () =>
|
762
|
-
remoteUsers.updateEmailVerification(user.$id,
|
977
|
+
remoteUsers.updateEmailVerification(user.$id, true)
|
763
978
|
);
|
764
|
-
}
|
765
|
-
|
766
|
-
if (user.status === false) {
|
979
|
+
} else {
|
767
980
|
await tryAwaitWithRetry(async () =>
|
768
|
-
remoteUsers.
|
981
|
+
remoteUsers.updateEmailVerification(user.$id, false)
|
769
982
|
);
|
770
983
|
}
|
771
984
|
|
772
|
-
if (user.
|
985
|
+
if (user.phoneVerification) {
|
773
986
|
await tryAwaitWithRetry(async () =>
|
774
|
-
remoteUsers.
|
987
|
+
remoteUsers.updatePhoneVerification(user.$id, true)
|
775
988
|
);
|
776
989
|
}
|
777
990
|
|
778
|
-
if (user.
|
991
|
+
if (user.status === false) {
|
779
992
|
await tryAwaitWithRetry(async () =>
|
780
|
-
remoteUsers.
|
993
|
+
remoteUsers.updateStatus(user.$id, false)
|
781
994
|
);
|
782
995
|
}
|
783
996
|
|