appwrite-utils-cli 1.2.15 → 1.2.16
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.
@@ -1,4 +1,9 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
converterFunctions,
|
3
|
+
tryAwaitWithRetry,
|
4
|
+
parseAttribute,
|
5
|
+
objectNeedsUpdate,
|
6
|
+
} from "appwrite-utils";
|
2
7
|
import {
|
3
8
|
Client,
|
4
9
|
Databases,
|
@@ -8,22 +13,24 @@ import {
|
|
8
13
|
Teams,
|
9
14
|
type Models,
|
10
15
|
Query,
|
16
|
+
AppwriteException,
|
11
17
|
} from "node-appwrite";
|
12
18
|
import { InputFile } from "node-appwrite/file";
|
13
19
|
import { MessageFormatter } from "../shared/messageFormatter.js";
|
14
20
|
import { ProgressManager } from "../shared/progressManager.js";
|
15
21
|
import { getClient } from "../utils/getClientFromConfig.js";
|
16
|
-
import {
|
17
|
-
transferDatabaseLocalToLocal,
|
22
|
+
import {
|
23
|
+
transferDatabaseLocalToLocal,
|
18
24
|
transferDatabaseLocalToRemote,
|
19
25
|
transferStorageLocalToLocal,
|
20
26
|
transferStorageLocalToRemote,
|
21
|
-
transferUsersLocalToRemote
|
27
|
+
transferUsersLocalToRemote,
|
22
28
|
} from "./transfer.js";
|
23
|
-
import {
|
24
|
-
|
25
|
-
|
26
|
-
|
29
|
+
import { deployLocalFunction } from "../functions/deployments.js";
|
30
|
+
import {
|
31
|
+
listFunctions,
|
32
|
+
downloadLatestFunctionDeployment,
|
33
|
+
} from "../functions/methods.js";
|
27
34
|
import pLimit from "p-limit";
|
28
35
|
import chalk from "chalk";
|
29
36
|
import { join } from "node:path";
|
@@ -100,7 +107,7 @@ export class ComprehensiveTransfer {
|
|
100
107
|
|
101
108
|
const baseLimit = options.concurrencyLimit || 10;
|
102
109
|
this.limit = pLimit(baseLimit);
|
103
|
-
|
110
|
+
|
104
111
|
// Different rate limits for different operations to prevent API throttling
|
105
112
|
// Users: Half speed (more sensitive operations)
|
106
113
|
// Files: Quarter speed (most bandwidth intensive)
|
@@ -120,18 +127,25 @@ export class ComprehensiveTransfer {
|
|
120
127
|
|
121
128
|
async execute(): Promise<TransferResults> {
|
122
129
|
try {
|
123
|
-
MessageFormatter.info("Starting comprehensive transfer", {
|
124
|
-
|
130
|
+
MessageFormatter.info("Starting comprehensive transfer", {
|
131
|
+
prefix: "Transfer",
|
132
|
+
});
|
133
|
+
|
125
134
|
if (this.options.dryRun) {
|
126
|
-
MessageFormatter.info("DRY RUN MODE - No actual changes will be made", {
|
135
|
+
MessageFormatter.info("DRY RUN MODE - No actual changes will be made", {
|
136
|
+
prefix: "Transfer",
|
137
|
+
});
|
127
138
|
}
|
128
139
|
|
129
140
|
// Show rate limiting configuration
|
130
141
|
const baseLimit = this.options.concurrencyLimit || 10;
|
131
142
|
const userLimit = Math.max(1, Math.floor(baseLimit / 2));
|
132
143
|
const fileLimit = Math.max(1, Math.floor(baseLimit / 4));
|
133
|
-
|
134
|
-
MessageFormatter.info(
|
144
|
+
|
145
|
+
MessageFormatter.info(
|
146
|
+
`Rate limits: General=${baseLimit}, Users=${userLimit}, Files=${fileLimit}`,
|
147
|
+
{ prefix: "Transfer" }
|
148
|
+
);
|
135
149
|
|
136
150
|
// Ensure temp directory exists
|
137
151
|
if (!fs.existsSync(this.tempDir)) {
|
@@ -164,7 +178,11 @@ export class ComprehensiveTransfer {
|
|
164
178
|
|
165
179
|
return this.results;
|
166
180
|
} catch (error) {
|
167
|
-
MessageFormatter.error(
|
181
|
+
MessageFormatter.error(
|
182
|
+
"Comprehensive transfer failed",
|
183
|
+
error instanceof Error ? error : new Error(String(error)),
|
184
|
+
{ prefix: "Transfer" }
|
185
|
+
);
|
168
186
|
throw error;
|
169
187
|
} finally {
|
170
188
|
// Clean up temp directory
|
@@ -175,11 +193,16 @@ export class ComprehensiveTransfer {
|
|
175
193
|
}
|
176
194
|
|
177
195
|
private async transferAllUsers(): Promise<void> {
|
178
|
-
MessageFormatter.info("Starting user transfer phase", {
|
196
|
+
MessageFormatter.info("Starting user transfer phase", {
|
197
|
+
prefix: "Transfer",
|
198
|
+
});
|
179
199
|
|
180
200
|
if (this.options.dryRun) {
|
181
201
|
const usersList = await this.sourceUsers.list([Query.limit(1)]);
|
182
|
-
MessageFormatter.info(
|
202
|
+
MessageFormatter.info(
|
203
|
+
`DRY RUN: Would transfer ${usersList.total} users`,
|
204
|
+
{ prefix: "Transfer" }
|
205
|
+
);
|
183
206
|
return;
|
184
207
|
}
|
185
208
|
|
@@ -193,20 +216,28 @@ export class ComprehensiveTransfer {
|
|
193
216
|
this.options.targetProject,
|
194
217
|
this.options.targetKey
|
195
218
|
);
|
196
|
-
|
219
|
+
|
197
220
|
// Get actual count for results
|
198
221
|
const usersList = await this.sourceUsers.list([Query.limit(1)]);
|
199
222
|
this.results.users.transferred = usersList.total;
|
200
|
-
|
201
|
-
MessageFormatter.success(`User transfer completed`, {
|
223
|
+
|
224
|
+
MessageFormatter.success(`User transfer completed`, {
|
225
|
+
prefix: "Transfer",
|
226
|
+
});
|
202
227
|
} catch (error) {
|
203
|
-
MessageFormatter.error(
|
228
|
+
MessageFormatter.error(
|
229
|
+
"User transfer failed",
|
230
|
+
error instanceof Error ? error : new Error(String(error)),
|
231
|
+
{ prefix: "Transfer" }
|
232
|
+
);
|
204
233
|
this.results.users.failed = 1;
|
205
234
|
}
|
206
235
|
}
|
207
236
|
|
208
237
|
private async transferAllTeams(): Promise<void> {
|
209
|
-
MessageFormatter.info("Starting team transfer phase", {
|
238
|
+
MessageFormatter.info("Starting team transfer phase", {
|
239
|
+
prefix: "Transfer",
|
240
|
+
});
|
210
241
|
|
211
242
|
try {
|
212
243
|
// Fetch all teams from source with pagination
|
@@ -216,47 +247,68 @@ export class ComprehensiveTransfer {
|
|
216
247
|
if (this.options.dryRun) {
|
217
248
|
let totalMemberships = 0;
|
218
249
|
for (const team of allSourceTeams) {
|
219
|
-
const memberships = await this.sourceTeams.listMemberships(team.$id, [
|
250
|
+
const memberships = await this.sourceTeams.listMemberships(team.$id, [
|
251
|
+
Query.limit(1),
|
252
|
+
]);
|
220
253
|
totalMemberships += memberships.total;
|
221
254
|
}
|
222
|
-
MessageFormatter.info(
|
255
|
+
MessageFormatter.info(
|
256
|
+
`DRY RUN: Would transfer ${allSourceTeams.length} teams with ${totalMemberships} memberships`,
|
257
|
+
{ prefix: "Transfer" }
|
258
|
+
);
|
223
259
|
return;
|
224
260
|
}
|
225
261
|
|
226
|
-
const transferTasks = allSourceTeams.map(team =>
|
262
|
+
const transferTasks = allSourceTeams.map((team) =>
|
227
263
|
this.limit(async () => {
|
228
264
|
try {
|
229
265
|
// Check if team exists in target
|
230
|
-
const existingTeam = allTargetTeams.find(
|
231
|
-
|
266
|
+
const existingTeam = allTargetTeams.find(
|
267
|
+
(tt) => tt.$id === team.$id
|
268
|
+
);
|
269
|
+
|
232
270
|
if (!existingTeam) {
|
233
271
|
// Fetch all memberships to extract unique roles before creating team
|
234
|
-
MessageFormatter.info(
|
272
|
+
MessageFormatter.info(
|
273
|
+
`Fetching memberships for team ${team.name} to extract roles`,
|
274
|
+
{ prefix: "Transfer" }
|
275
|
+
);
|
235
276
|
const memberships = await this.fetchAllMemberships(team.$id);
|
236
|
-
|
277
|
+
|
237
278
|
// Extract unique roles from all memberships
|
238
279
|
const allRoles = new Set<string>();
|
239
|
-
memberships.forEach(membership => {
|
240
|
-
membership.roles.forEach(role => allRoles.add(role));
|
280
|
+
memberships.forEach((membership) => {
|
281
|
+
membership.roles.forEach((role) => allRoles.add(role));
|
241
282
|
});
|
242
283
|
const uniqueRoles = Array.from(allRoles);
|
243
|
-
|
244
|
-
MessageFormatter.info(
|
245
|
-
|
284
|
+
|
285
|
+
MessageFormatter.info(
|
286
|
+
`Found ${uniqueRoles.length} unique roles for team ${
|
287
|
+
team.name
|
288
|
+
}: ${uniqueRoles.join(", ")}`,
|
289
|
+
{ prefix: "Transfer" }
|
290
|
+
);
|
291
|
+
|
246
292
|
// Create team in target with the collected roles
|
247
|
-
await this.targetTeams.create(
|
248
|
-
|
249
|
-
team.name
|
250
|
-
|
293
|
+
await this.targetTeams.create(team.$id, team.name, uniqueRoles);
|
294
|
+
MessageFormatter.success(
|
295
|
+
`Created team: ${team.name} with roles: ${uniqueRoles.join(
|
296
|
+
", "
|
297
|
+
)}`,
|
298
|
+
{ prefix: "Transfer" }
|
251
299
|
);
|
252
|
-
MessageFormatter.success(`Created team: ${team.name} with roles: ${uniqueRoles.join(', ')}`, { prefix: "Transfer" });
|
253
300
|
} else {
|
254
|
-
MessageFormatter.info(
|
255
|
-
|
301
|
+
MessageFormatter.info(
|
302
|
+
`Team ${team.name} already exists, updating if needed`,
|
303
|
+
{ prefix: "Transfer" }
|
304
|
+
);
|
305
|
+
|
256
306
|
// Update team if needed
|
257
307
|
if (existingTeam.name !== team.name) {
|
258
308
|
await this.targetTeams.updateName(team.$id, team.name);
|
259
|
-
MessageFormatter.success(`Updated team name: ${team.name}`, {
|
309
|
+
MessageFormatter.success(`Updated team name: ${team.name}`, {
|
310
|
+
prefix: "Transfer",
|
311
|
+
});
|
260
312
|
}
|
261
313
|
}
|
262
314
|
|
@@ -264,54 +316,85 @@ export class ComprehensiveTransfer {
|
|
264
316
|
await this.transferTeamMemberships(team.$id);
|
265
317
|
|
266
318
|
this.results.teams.transferred++;
|
267
|
-
MessageFormatter.success(
|
319
|
+
MessageFormatter.success(
|
320
|
+
`Team ${team.name} transferred successfully`,
|
321
|
+
{ prefix: "Transfer" }
|
322
|
+
);
|
268
323
|
} catch (error) {
|
269
|
-
MessageFormatter.error(
|
324
|
+
MessageFormatter.error(
|
325
|
+
`Team ${team.name} transfer failed`,
|
326
|
+
error instanceof Error ? error : new Error(String(error)),
|
327
|
+
{ prefix: "Transfer" }
|
328
|
+
);
|
270
329
|
this.results.teams.failed++;
|
271
330
|
}
|
272
331
|
})
|
273
332
|
);
|
274
333
|
|
275
334
|
await Promise.all(transferTasks);
|
276
|
-
MessageFormatter.success("Team transfer phase completed", {
|
335
|
+
MessageFormatter.success("Team transfer phase completed", {
|
336
|
+
prefix: "Transfer",
|
337
|
+
});
|
277
338
|
} catch (error) {
|
278
|
-
MessageFormatter.error(
|
339
|
+
MessageFormatter.error(
|
340
|
+
"Team transfer phase failed",
|
341
|
+
error instanceof Error ? error : new Error(String(error)),
|
342
|
+
{ prefix: "Transfer" }
|
343
|
+
);
|
279
344
|
}
|
280
345
|
}
|
281
346
|
|
282
347
|
private async transferAllDatabases(): Promise<void> {
|
283
|
-
MessageFormatter.info("Starting database transfer phase", {
|
348
|
+
MessageFormatter.info("Starting database transfer phase", {
|
349
|
+
prefix: "Transfer",
|
350
|
+
});
|
284
351
|
|
285
352
|
try {
|
286
353
|
const sourceDatabases = await this.sourceDatabases.list();
|
287
354
|
const targetDatabases = await this.targetDatabases.list();
|
288
355
|
|
289
356
|
if (this.options.dryRun) {
|
290
|
-
MessageFormatter.info(
|
357
|
+
MessageFormatter.info(
|
358
|
+
`DRY RUN: Would transfer ${sourceDatabases.databases.length} databases`,
|
359
|
+
{ prefix: "Transfer" }
|
360
|
+
);
|
291
361
|
return;
|
292
362
|
}
|
293
363
|
|
294
364
|
// Phase 1: Create all databases and collections (structure only)
|
295
|
-
MessageFormatter.info(
|
296
|
-
|
297
|
-
|
365
|
+
MessageFormatter.info(
|
366
|
+
"Phase 1: Creating database structures (databases, collections, attributes, indexes)",
|
367
|
+
{ prefix: "Transfer" }
|
368
|
+
);
|
369
|
+
|
370
|
+
const structureCreationTasks = sourceDatabases.databases.map((db) =>
|
298
371
|
this.limit(async () => {
|
299
372
|
try {
|
300
373
|
// Check if database exists in target
|
301
|
-
const existingDb = targetDatabases.databases.find(
|
302
|
-
|
374
|
+
const existingDb = targetDatabases.databases.find(
|
375
|
+
(tdb) => tdb.$id === db.$id
|
376
|
+
);
|
377
|
+
|
303
378
|
if (!existingDb) {
|
304
379
|
// Create database in target
|
305
380
|
await this.targetDatabases.create(db.$id, db.name, db.enabled);
|
306
|
-
MessageFormatter.success(`Created database: ${db.name}`, {
|
381
|
+
MessageFormatter.success(`Created database: ${db.name}`, {
|
382
|
+
prefix: "Transfer",
|
383
|
+
});
|
307
384
|
}
|
308
385
|
|
309
386
|
// Create collections, attributes, and indexes WITHOUT transferring documents
|
310
387
|
await this.createDatabaseStructure(db.$id);
|
311
388
|
|
312
|
-
MessageFormatter.success(`Database structure created: ${db.name}`, {
|
389
|
+
MessageFormatter.success(`Database structure created: ${db.name}`, {
|
390
|
+
prefix: "Transfer",
|
391
|
+
});
|
313
392
|
} catch (error) {
|
314
|
-
MessageFormatter.error(
|
393
|
+
MessageFormatter.error(
|
394
|
+
`Database structure creation failed for ${db.name}`,
|
395
|
+
error instanceof Error ? error : new Error(String(error)),
|
396
|
+
{ prefix: "Transfer" }
|
397
|
+
);
|
315
398
|
this.results.databases.failed++;
|
316
399
|
}
|
317
400
|
})
|
@@ -320,27 +403,43 @@ export class ComprehensiveTransfer {
|
|
320
403
|
await Promise.all(structureCreationTasks);
|
321
404
|
|
322
405
|
// Phase 2: Transfer all documents after all structures are created
|
323
|
-
MessageFormatter.info(
|
324
|
-
|
325
|
-
|
406
|
+
MessageFormatter.info(
|
407
|
+
"Phase 2: Transferring documents to all collections",
|
408
|
+
{ prefix: "Transfer" }
|
409
|
+
);
|
410
|
+
|
411
|
+
const documentTransferTasks = sourceDatabases.databases.map((db) =>
|
326
412
|
this.limit(async () => {
|
327
413
|
try {
|
328
414
|
// Transfer documents for this database
|
329
415
|
await this.transferDatabaseDocuments(db.$id);
|
330
416
|
|
331
417
|
this.results.databases.transferred++;
|
332
|
-
MessageFormatter.success(
|
418
|
+
MessageFormatter.success(
|
419
|
+
`Database documents transferred: ${db.name}`,
|
420
|
+
{ prefix: "Transfer" }
|
421
|
+
);
|
333
422
|
} catch (error) {
|
334
|
-
MessageFormatter.error(
|
423
|
+
MessageFormatter.error(
|
424
|
+
`Document transfer failed for ${db.name}`,
|
425
|
+
error instanceof Error ? error : new Error(String(error)),
|
426
|
+
{ prefix: "Transfer" }
|
427
|
+
);
|
335
428
|
this.results.databases.failed++;
|
336
429
|
}
|
337
430
|
})
|
338
431
|
);
|
339
432
|
|
340
433
|
await Promise.all(documentTransferTasks);
|
341
|
-
MessageFormatter.success("Database transfer phase completed", {
|
434
|
+
MessageFormatter.success("Database transfer phase completed", {
|
435
|
+
prefix: "Transfer",
|
436
|
+
});
|
342
437
|
} catch (error) {
|
343
|
-
MessageFormatter.error(
|
438
|
+
MessageFormatter.error(
|
439
|
+
"Database transfer phase failed",
|
440
|
+
error instanceof Error ? error : new Error(String(error)),
|
441
|
+
{ prefix: "Transfer" }
|
442
|
+
);
|
344
443
|
}
|
345
444
|
}
|
346
445
|
|
@@ -348,33 +447,51 @@ export class ComprehensiveTransfer {
|
|
348
447
|
* Phase 1: Create database structure (collections, attributes, indexes) without transferring documents
|
349
448
|
*/
|
350
449
|
private async createDatabaseStructure(dbId: string): Promise<void> {
|
351
|
-
MessageFormatter.info(`Creating database structure for ${dbId}`, {
|
450
|
+
MessageFormatter.info(`Creating database structure for ${dbId}`, {
|
451
|
+
prefix: "Transfer",
|
452
|
+
});
|
352
453
|
|
353
454
|
try {
|
354
455
|
// Get all collections from source database
|
355
|
-
const sourceCollections = await this.fetchAllCollections(
|
356
|
-
|
456
|
+
const sourceCollections = await this.fetchAllCollections(
|
457
|
+
dbId,
|
458
|
+
this.sourceDatabases
|
459
|
+
);
|
460
|
+
MessageFormatter.info(
|
461
|
+
`Found ${sourceCollections.length} collections in source database ${dbId}`,
|
462
|
+
{ prefix: "Transfer" }
|
463
|
+
);
|
357
464
|
|
358
465
|
// Process each collection
|
359
466
|
for (const collection of sourceCollections) {
|
360
|
-
MessageFormatter.info(
|
467
|
+
MessageFormatter.info(
|
468
|
+
`Processing collection: ${collection.name} (${collection.$id})`,
|
469
|
+
{ prefix: "Transfer" }
|
470
|
+
);
|
361
471
|
|
362
472
|
try {
|
363
473
|
// Create or update collection in target
|
364
474
|
let targetCollection: Models.Collection;
|
365
475
|
const existingCollection = await tryAwaitWithRetry(async () =>
|
366
|
-
this.targetDatabases.listCollections(dbId, [
|
476
|
+
this.targetDatabases.listCollections(dbId, [
|
477
|
+
Query.equal("$id", collection.$id),
|
478
|
+
])
|
367
479
|
);
|
368
480
|
|
369
481
|
if (existingCollection.collections.length > 0) {
|
370
482
|
targetCollection = existingCollection.collections[0];
|
371
|
-
MessageFormatter.info(
|
483
|
+
MessageFormatter.info(
|
484
|
+
`Collection ${collection.name} exists in target database`,
|
485
|
+
{ prefix: "Transfer" }
|
486
|
+
);
|
372
487
|
|
373
488
|
// Update collection if needed
|
374
489
|
if (
|
375
490
|
targetCollection.name !== collection.name ||
|
376
|
-
JSON.stringify(targetCollection.$permissions) !==
|
377
|
-
|
491
|
+
JSON.stringify(targetCollection.$permissions) !==
|
492
|
+
JSON.stringify(collection.$permissions) ||
|
493
|
+
targetCollection.documentSecurity !==
|
494
|
+
collection.documentSecurity ||
|
378
495
|
targetCollection.enabled !== collection.enabled
|
379
496
|
) {
|
380
497
|
targetCollection = await tryAwaitWithRetry(async () =>
|
@@ -387,10 +504,16 @@ export class ComprehensiveTransfer {
|
|
387
504
|
collection.enabled
|
388
505
|
)
|
389
506
|
);
|
390
|
-
MessageFormatter.success(
|
507
|
+
MessageFormatter.success(
|
508
|
+
`Collection ${collection.name} updated`,
|
509
|
+
{ prefix: "Transfer" }
|
510
|
+
);
|
391
511
|
}
|
392
512
|
} else {
|
393
|
-
MessageFormatter.info(
|
513
|
+
MessageFormatter.info(
|
514
|
+
`Creating collection ${collection.name} in target database...`,
|
515
|
+
{ prefix: "Transfer" }
|
516
|
+
);
|
394
517
|
targetCollection = await tryAwaitWithRetry(async () =>
|
395
518
|
this.targetDatabases.createCollection(
|
396
519
|
dbId,
|
@@ -401,55 +524,99 @@ export class ComprehensiveTransfer {
|
|
401
524
|
collection.enabled
|
402
525
|
)
|
403
526
|
);
|
404
|
-
MessageFormatter.success(`Collection ${collection.name} created`, {
|
527
|
+
MessageFormatter.success(`Collection ${collection.name} created`, {
|
528
|
+
prefix: "Transfer",
|
529
|
+
});
|
405
530
|
}
|
406
531
|
|
407
532
|
// Handle attributes with enhanced status checking
|
408
|
-
MessageFormatter.info(
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
targetCollection,
|
416
|
-
attributesToCreate
|
533
|
+
MessageFormatter.info(
|
534
|
+
`Creating attributes for collection ${collection.name} with enhanced monitoring...`,
|
535
|
+
{ prefix: "Transfer" }
|
536
|
+
);
|
537
|
+
|
538
|
+
const attributesToCreate = collection.attributes.map((attr) =>
|
539
|
+
parseAttribute(attr as any)
|
417
540
|
);
|
418
|
-
|
541
|
+
|
542
|
+
const attributesSuccess =
|
543
|
+
await this.createCollectionAttributesWithStatusCheck(
|
544
|
+
this.targetDatabases,
|
545
|
+
dbId,
|
546
|
+
targetCollection,
|
547
|
+
attributesToCreate
|
548
|
+
);
|
549
|
+
|
419
550
|
if (!attributesSuccess) {
|
420
|
-
MessageFormatter.error(
|
421
|
-
|
551
|
+
MessageFormatter.error(
|
552
|
+
`Failed to create some attributes for collection ${collection.name}`,
|
553
|
+
undefined,
|
554
|
+
{ prefix: "Transfer" }
|
555
|
+
);
|
556
|
+
MessageFormatter.error(
|
557
|
+
`Skipping index creation and document transfer for collection ${collection.name} due to attribute failures`,
|
558
|
+
undefined,
|
559
|
+
{ prefix: "Transfer" }
|
560
|
+
);
|
422
561
|
// Skip indexes and document transfer if attributes failed
|
423
562
|
continue;
|
424
563
|
} else {
|
425
|
-
MessageFormatter.success(
|
564
|
+
MessageFormatter.success(
|
565
|
+
`All attributes created successfully for collection ${collection.name}`,
|
566
|
+
{ prefix: "Transfer" }
|
567
|
+
);
|
426
568
|
}
|
427
569
|
|
428
570
|
// Handle indexes with enhanced status checking
|
429
|
-
MessageFormatter.info(
|
430
|
-
|
431
|
-
|
432
|
-
dbId,
|
433
|
-
this.targetDatabases,
|
434
|
-
targetCollection.$id,
|
435
|
-
targetCollection,
|
436
|
-
collection.indexes as any
|
571
|
+
MessageFormatter.info(
|
572
|
+
`Creating indexes for collection ${collection.name} with enhanced monitoring...`,
|
573
|
+
{ prefix: "Transfer" }
|
437
574
|
);
|
438
|
-
|
575
|
+
|
576
|
+
const indexesSuccess =
|
577
|
+
await this.createCollectionIndexesWithStatusCheck(
|
578
|
+
dbId,
|
579
|
+
this.targetDatabases,
|
580
|
+
targetCollection.$id,
|
581
|
+
targetCollection,
|
582
|
+
collection.indexes as any
|
583
|
+
);
|
584
|
+
|
439
585
|
if (!indexesSuccess) {
|
440
|
-
MessageFormatter.error(
|
441
|
-
|
586
|
+
MessageFormatter.error(
|
587
|
+
`Failed to create some indexes for collection ${collection.name}`,
|
588
|
+
undefined,
|
589
|
+
{ prefix: "Transfer" }
|
590
|
+
);
|
591
|
+
MessageFormatter.warning(
|
592
|
+
`Proceeding with document transfer despite index failures for collection ${collection.name}`,
|
593
|
+
{ prefix: "Transfer" }
|
594
|
+
);
|
442
595
|
} else {
|
443
|
-
MessageFormatter.success(
|
596
|
+
MessageFormatter.success(
|
597
|
+
`All indexes created successfully for collection ${collection.name}`,
|
598
|
+
{ prefix: "Transfer" }
|
599
|
+
);
|
444
600
|
}
|
445
601
|
|
446
|
-
MessageFormatter.success(
|
602
|
+
MessageFormatter.success(
|
603
|
+
`Structure complete for collection ${collection.name}`,
|
604
|
+
{ prefix: "Transfer" }
|
605
|
+
);
|
447
606
|
} catch (error) {
|
448
|
-
MessageFormatter.error(
|
607
|
+
MessageFormatter.error(
|
608
|
+
`Error processing collection ${collection.name}`,
|
609
|
+
error instanceof Error ? error : new Error(String(error)),
|
610
|
+
{ prefix: "Transfer" }
|
611
|
+
);
|
449
612
|
}
|
450
613
|
}
|
451
614
|
} catch (error) {
|
452
|
-
MessageFormatter.error(
|
615
|
+
MessageFormatter.error(
|
616
|
+
`Failed to create database structure for ${dbId}`,
|
617
|
+
error instanceof Error ? error : new Error(String(error)),
|
618
|
+
{ prefix: "Transfer" }
|
619
|
+
);
|
453
620
|
throw error;
|
454
621
|
}
|
455
622
|
}
|
@@ -458,16 +625,27 @@ export class ComprehensiveTransfer {
|
|
458
625
|
* Phase 2: Transfer documents to all collections in the database
|
459
626
|
*/
|
460
627
|
private async transferDatabaseDocuments(dbId: string): Promise<void> {
|
461
|
-
MessageFormatter.info(`Transferring documents for database ${dbId}`, {
|
628
|
+
MessageFormatter.info(`Transferring documents for database ${dbId}`, {
|
629
|
+
prefix: "Transfer",
|
630
|
+
});
|
462
631
|
|
463
632
|
try {
|
464
633
|
// Get all collections from source database
|
465
|
-
const sourceCollections = await this.fetchAllCollections(
|
466
|
-
|
634
|
+
const sourceCollections = await this.fetchAllCollections(
|
635
|
+
dbId,
|
636
|
+
this.sourceDatabases
|
637
|
+
);
|
638
|
+
MessageFormatter.info(
|
639
|
+
`Transferring documents for ${sourceCollections.length} collections in database ${dbId}`,
|
640
|
+
{ prefix: "Transfer" }
|
641
|
+
);
|
467
642
|
|
468
643
|
// Process each collection
|
469
644
|
for (const collection of sourceCollections) {
|
470
|
-
MessageFormatter.info(
|
645
|
+
MessageFormatter.info(
|
646
|
+
`Transferring documents for collection: ${collection.name} (${collection.$id})`,
|
647
|
+
{ prefix: "Transfer" }
|
648
|
+
);
|
471
649
|
|
472
650
|
try {
|
473
651
|
// Transfer documents
|
@@ -479,20 +657,33 @@ export class ComprehensiveTransfer {
|
|
479
657
|
collection.$id,
|
480
658
|
collection.$id
|
481
659
|
);
|
482
|
-
|
483
|
-
MessageFormatter.success(
|
660
|
+
|
661
|
+
MessageFormatter.success(
|
662
|
+
`Documents transferred for collection ${collection.name}`,
|
663
|
+
{ prefix: "Transfer" }
|
664
|
+
);
|
484
665
|
} catch (error) {
|
485
|
-
MessageFormatter.error(
|
666
|
+
MessageFormatter.error(
|
667
|
+
`Error transferring documents for collection ${collection.name}`,
|
668
|
+
error instanceof Error ? error : new Error(String(error)),
|
669
|
+
{ prefix: "Transfer" }
|
670
|
+
);
|
486
671
|
}
|
487
672
|
}
|
488
673
|
} catch (error) {
|
489
|
-
MessageFormatter.error(
|
674
|
+
MessageFormatter.error(
|
675
|
+
`Failed to transfer documents for database ${dbId}`,
|
676
|
+
error instanceof Error ? error : new Error(String(error)),
|
677
|
+
{ prefix: "Transfer" }
|
678
|
+
);
|
490
679
|
throw error;
|
491
680
|
}
|
492
681
|
}
|
493
682
|
|
494
683
|
private async transferAllBuckets(): Promise<void> {
|
495
|
-
MessageFormatter.info("Starting bucket transfer phase", {
|
684
|
+
MessageFormatter.info("Starting bucket transfer phase", {
|
685
|
+
prefix: "Transfer",
|
686
|
+
});
|
496
687
|
|
497
688
|
try {
|
498
689
|
// Get all buckets from source with pagination
|
@@ -502,38 +693,52 @@ export class ComprehensiveTransfer {
|
|
502
693
|
if (this.options.dryRun) {
|
503
694
|
let totalFiles = 0;
|
504
695
|
for (const bucket of allSourceBuckets) {
|
505
|
-
const files = await this.sourceStorage.listFiles(bucket.$id, [
|
696
|
+
const files = await this.sourceStorage.listFiles(bucket.$id, [
|
697
|
+
Query.limit(1),
|
698
|
+
]);
|
506
699
|
totalFiles += files.total;
|
507
700
|
}
|
508
|
-
MessageFormatter.info(
|
701
|
+
MessageFormatter.info(
|
702
|
+
`DRY RUN: Would transfer ${allSourceBuckets.length} buckets with ${totalFiles} files`,
|
703
|
+
{ prefix: "Transfer" }
|
704
|
+
);
|
509
705
|
return;
|
510
706
|
}
|
511
707
|
|
512
|
-
const transferTasks = allSourceBuckets.map(bucket =>
|
708
|
+
const transferTasks = allSourceBuckets.map((bucket) =>
|
513
709
|
this.limit(async () => {
|
514
710
|
try {
|
515
711
|
// Check if bucket exists in target
|
516
|
-
const existingBucket = allTargetBuckets.find(
|
517
|
-
|
712
|
+
const existingBucket = allTargetBuckets.find(
|
713
|
+
(tb) => tb.$id === bucket.$id
|
714
|
+
);
|
715
|
+
|
518
716
|
if (!existingBucket) {
|
519
717
|
// Create bucket with fallback strategy for maximumFileSize
|
520
718
|
await this.createBucketWithFallback(bucket);
|
521
|
-
MessageFormatter.success(`Created bucket: ${bucket.name}`, {
|
719
|
+
MessageFormatter.success(`Created bucket: ${bucket.name}`, {
|
720
|
+
prefix: "Transfer",
|
721
|
+
});
|
522
722
|
} else {
|
523
723
|
// Compare bucket permissions and update if needed
|
524
|
-
const sourcePermissions = JSON.stringify(
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
724
|
+
const sourcePermissions = JSON.stringify(
|
725
|
+
bucket.$permissions?.sort() || []
|
726
|
+
);
|
727
|
+
const targetPermissions = JSON.stringify(
|
728
|
+
existingBucket.$permissions?.sort() || []
|
729
|
+
);
|
730
|
+
|
731
|
+
if (
|
732
|
+
sourcePermissions !== targetPermissions ||
|
733
|
+
existingBucket.name !== bucket.name ||
|
734
|
+
existingBucket.fileSecurity !== bucket.fileSecurity ||
|
735
|
+
existingBucket.enabled !== bucket.enabled
|
736
|
+
) {
|
532
737
|
MessageFormatter.warning(
|
533
|
-
`Bucket ${bucket.name} exists but has different settings. Updating to match source.`,
|
738
|
+
`Bucket ${bucket.name} exists but has different settings. Updating to match source.`,
|
534
739
|
{ prefix: "Transfer" }
|
535
740
|
);
|
536
|
-
|
741
|
+
|
537
742
|
try {
|
538
743
|
await this.targetStorage.updateBucket(
|
539
744
|
bucket.$id,
|
@@ -547,16 +752,24 @@ export class ComprehensiveTransfer {
|
|
547
752
|
bucket.encryption,
|
548
753
|
bucket.antivirus
|
549
754
|
);
|
550
|
-
MessageFormatter.success(
|
755
|
+
MessageFormatter.success(
|
756
|
+
`Updated bucket ${bucket.name} to match source`,
|
757
|
+
{ prefix: "Transfer" }
|
758
|
+
);
|
551
759
|
} catch (updateError) {
|
552
760
|
MessageFormatter.error(
|
553
|
-
`Failed to update bucket ${bucket.name}`,
|
554
|
-
updateError instanceof Error
|
761
|
+
`Failed to update bucket ${bucket.name}`,
|
762
|
+
updateError instanceof Error
|
763
|
+
? updateError
|
764
|
+
: new Error(String(updateError)),
|
555
765
|
{ prefix: "Transfer" }
|
556
766
|
);
|
557
767
|
}
|
558
768
|
} else {
|
559
|
-
MessageFormatter.info(
|
769
|
+
MessageFormatter.info(
|
770
|
+
`Bucket ${bucket.name} already exists with matching settings`,
|
771
|
+
{ prefix: "Transfer" }
|
772
|
+
);
|
560
773
|
}
|
561
774
|
}
|
562
775
|
|
@@ -564,31 +777,46 @@ export class ComprehensiveTransfer {
|
|
564
777
|
await this.transferBucketFiles(bucket.$id, bucket.$id);
|
565
778
|
|
566
779
|
this.results.buckets.transferred++;
|
567
|
-
MessageFormatter.success(
|
780
|
+
MessageFormatter.success(
|
781
|
+
`Bucket ${bucket.name} transferred successfully`,
|
782
|
+
{ prefix: "Transfer" }
|
783
|
+
);
|
568
784
|
} catch (error) {
|
569
|
-
MessageFormatter.error(
|
785
|
+
MessageFormatter.error(
|
786
|
+
`Bucket ${bucket.name} transfer failed`,
|
787
|
+
error instanceof Error ? error : new Error(String(error)),
|
788
|
+
{ prefix: "Transfer" }
|
789
|
+
);
|
570
790
|
this.results.buckets.failed++;
|
571
791
|
}
|
572
792
|
})
|
573
793
|
);
|
574
794
|
|
575
795
|
await Promise.all(transferTasks);
|
576
|
-
MessageFormatter.success("Bucket transfer phase completed", {
|
796
|
+
MessageFormatter.success("Bucket transfer phase completed", {
|
797
|
+
prefix: "Transfer",
|
798
|
+
});
|
577
799
|
} catch (error) {
|
578
|
-
MessageFormatter.error(
|
800
|
+
MessageFormatter.error(
|
801
|
+
"Bucket transfer phase failed",
|
802
|
+
error instanceof Error ? error : new Error(String(error)),
|
803
|
+
{ prefix: "Transfer" }
|
804
|
+
);
|
579
805
|
}
|
580
806
|
}
|
581
807
|
|
582
808
|
private async createBucketWithFallback(bucket: Models.Bucket): Promise<void> {
|
583
809
|
// Determine the optimal size to try first
|
584
810
|
let sizeToTry: number;
|
585
|
-
|
811
|
+
|
586
812
|
if (this.cachedMaxFileSize) {
|
587
813
|
// Use cached size if it's smaller than or equal to the bucket's original size
|
588
814
|
if (bucket.maximumFileSize >= this.cachedMaxFileSize) {
|
589
815
|
sizeToTry = this.cachedMaxFileSize;
|
590
816
|
MessageFormatter.info(
|
591
|
-
`Bucket ${bucket.name}: Using cached maximumFileSize ${sizeToTry} (${(
|
817
|
+
`Bucket ${bucket.name}: Using cached maximumFileSize ${sizeToTry} (${(
|
818
|
+
sizeToTry / 1_000_000_000
|
819
|
+
).toFixed(1)}GB)`,
|
592
820
|
{ prefix: "Transfer" }
|
593
821
|
);
|
594
822
|
} else {
|
@@ -614,30 +842,41 @@ export class ComprehensiveTransfer {
|
|
614
842
|
bucket.encryption,
|
615
843
|
bucket.antivirus
|
616
844
|
);
|
617
|
-
|
845
|
+
|
618
846
|
// Success - cache this size if it's not already cached or is smaller than cached
|
619
847
|
if (!this.cachedMaxFileSize || sizeToTry < this.cachedMaxFileSize) {
|
620
848
|
this.cachedMaxFileSize = sizeToTry;
|
621
849
|
MessageFormatter.info(
|
622
|
-
`Bucket ${
|
850
|
+
`Bucket ${
|
851
|
+
bucket.name
|
852
|
+
}: Cached successful maximumFileSize ${sizeToTry} (${(
|
853
|
+
sizeToTry / 1_000_000_000
|
854
|
+
).toFixed(1)}GB)`,
|
623
855
|
{ prefix: "Transfer" }
|
624
856
|
);
|
625
857
|
}
|
626
|
-
|
858
|
+
|
627
859
|
// Log if we used a different size than original
|
628
860
|
if (sizeToTry !== bucket.maximumFileSize) {
|
629
861
|
MessageFormatter.warning(
|
630
|
-
`Bucket ${
|
862
|
+
`Bucket ${
|
863
|
+
bucket.name
|
864
|
+
}: maximumFileSize used ${sizeToTry} instead of original ${
|
865
|
+
bucket.maximumFileSize
|
866
|
+
} (${(sizeToTry / 1_000_000_000).toFixed(1)}GB)`,
|
631
867
|
{ prefix: "Transfer" }
|
632
868
|
);
|
633
869
|
}
|
634
|
-
|
870
|
+
|
635
871
|
return; // Success, exit the function
|
636
872
|
} catch (error) {
|
637
873
|
const err = error instanceof Error ? error : new Error(String(error));
|
638
|
-
|
874
|
+
|
639
875
|
// Check if the error is related to maximumFileSize validation
|
640
|
-
if (
|
876
|
+
if (
|
877
|
+
err.message.includes("maximumFileSize") ||
|
878
|
+
err.message.includes("valid range")
|
879
|
+
) {
|
641
880
|
MessageFormatter.warning(
|
642
881
|
`Bucket ${bucket.name}: Failed with maximumFileSize ${sizeToTry}, falling back to smaller sizes...`,
|
643
882
|
{ prefix: "Transfer" }
|
@@ -655,17 +894,17 @@ export class ComprehensiveTransfer {
|
|
655
894
|
2_500_000_000, // 2.5GB
|
656
895
|
2_000_000_000, // 2GB
|
657
896
|
1_000_000_000, // 1GB
|
658
|
-
500_000_000,
|
659
|
-
100_000_000
|
897
|
+
500_000_000, // 500MB
|
898
|
+
100_000_000, // 100MB
|
660
899
|
];
|
661
900
|
|
662
901
|
// Remove sizes that are larger than or equal to the already-tried size
|
663
902
|
const validSizes = fallbackSizes
|
664
|
-
.filter(size => size < sizeToTry)
|
903
|
+
.filter((size) => size < sizeToTry)
|
665
904
|
.sort((a, b) => b - a); // Sort descending
|
666
905
|
|
667
906
|
let lastError: Error | null = null;
|
668
|
-
|
907
|
+
|
669
908
|
for (const fileSize of validSizes) {
|
670
909
|
try {
|
671
910
|
await this.targetStorage.createBucket(
|
@@ -680,30 +919,39 @@ export class ComprehensiveTransfer {
|
|
680
919
|
bucket.encryption,
|
681
920
|
bucket.antivirus
|
682
921
|
);
|
683
|
-
|
922
|
+
|
684
923
|
// Success - cache this size if it's not already cached or is smaller than cached
|
685
924
|
if (!this.cachedMaxFileSize || fileSize < this.cachedMaxFileSize) {
|
686
925
|
this.cachedMaxFileSize = fileSize;
|
687
926
|
MessageFormatter.info(
|
688
|
-
`Bucket ${
|
927
|
+
`Bucket ${
|
928
|
+
bucket.name
|
929
|
+
}: Cached successful maximumFileSize ${fileSize} (${(
|
930
|
+
fileSize / 1_000_000_000
|
931
|
+
).toFixed(1)}GB)`,
|
689
932
|
{ prefix: "Transfer" }
|
690
933
|
);
|
691
934
|
}
|
692
|
-
|
935
|
+
|
693
936
|
// Log if we had to reduce the file size
|
694
937
|
if (fileSize !== bucket.maximumFileSize) {
|
695
938
|
MessageFormatter.warning(
|
696
|
-
`Bucket ${bucket.name}: maximumFileSize reduced from ${
|
939
|
+
`Bucket ${bucket.name}: maximumFileSize reduced from ${
|
940
|
+
bucket.maximumFileSize
|
941
|
+
} to ${fileSize} (${(fileSize / 1_000_000_000).toFixed(1)}GB)`,
|
697
942
|
{ prefix: "Transfer" }
|
698
943
|
);
|
699
944
|
}
|
700
|
-
|
945
|
+
|
701
946
|
return; // Success, exit the function
|
702
947
|
} catch (error) {
|
703
948
|
lastError = error instanceof Error ? error : new Error(String(error));
|
704
|
-
|
949
|
+
|
705
950
|
// Check if the error is related to maximumFileSize validation
|
706
|
-
if (
|
951
|
+
if (
|
952
|
+
lastError.message.includes("maximumFileSize") ||
|
953
|
+
lastError.message.includes("valid range")
|
954
|
+
) {
|
707
955
|
MessageFormatter.warning(
|
708
956
|
`Bucket ${bucket.name}: Failed with maximumFileSize ${fileSize}, trying smaller size...`,
|
709
957
|
{ prefix: "Transfer" }
|
@@ -715,17 +963,20 @@ export class ComprehensiveTransfer {
|
|
715
963
|
}
|
716
964
|
}
|
717
965
|
}
|
718
|
-
|
966
|
+
|
719
967
|
// If we get here, all fallback sizes failed
|
720
968
|
MessageFormatter.error(
|
721
969
|
`Bucket ${bucket.name}: All fallback file sizes failed. Last error: ${lastError?.message}`,
|
722
970
|
lastError || undefined,
|
723
971
|
{ prefix: "Transfer" }
|
724
972
|
);
|
725
|
-
throw lastError || new Error(
|
973
|
+
throw lastError || new Error("All fallback file sizes failed");
|
726
974
|
}
|
727
975
|
|
728
|
-
private async transferBucketFiles(
|
976
|
+
private async transferBucketFiles(
|
977
|
+
sourceBucketId: string,
|
978
|
+
targetBucketId: string
|
979
|
+
): Promise<void> {
|
729
980
|
let lastFileId: string | undefined;
|
730
981
|
let transferredFiles = 0;
|
731
982
|
|
@@ -739,24 +990,31 @@ export class ComprehensiveTransfer {
|
|
739
990
|
if (files.files.length === 0) break;
|
740
991
|
|
741
992
|
// Process files with rate limiting
|
742
|
-
const fileTasks = files.files.map(file =>
|
993
|
+
const fileTasks = files.files.map((file) =>
|
743
994
|
this.fileLimit(async () => {
|
744
995
|
try {
|
745
996
|
// Check if file already exists and compare permissions
|
746
997
|
let existingFile: Models.File | null = null;
|
747
998
|
try {
|
748
|
-
existingFile = await this.targetStorage.getFile(
|
749
|
-
|
999
|
+
existingFile = await this.targetStorage.getFile(
|
1000
|
+
targetBucketId,
|
1001
|
+
file.$id
|
1002
|
+
);
|
1003
|
+
|
750
1004
|
// Compare permissions between source and target file
|
751
|
-
const sourcePermissions = JSON.stringify(
|
752
|
-
|
753
|
-
|
1005
|
+
const sourcePermissions = JSON.stringify(
|
1006
|
+
file.$permissions?.sort() || []
|
1007
|
+
);
|
1008
|
+
const targetPermissions = JSON.stringify(
|
1009
|
+
existingFile.$permissions?.sort() || []
|
1010
|
+
);
|
1011
|
+
|
754
1012
|
if (sourcePermissions !== targetPermissions) {
|
755
1013
|
MessageFormatter.warning(
|
756
|
-
`File ${file.name} (${file.$id}) exists but has different permissions. Source: ${sourcePermissions}, Target: ${targetPermissions}`,
|
1014
|
+
`File ${file.name} (${file.$id}) exists but has different permissions. Source: ${sourcePermissions}, Target: ${targetPermissions}`,
|
757
1015
|
{ prefix: "Transfer" }
|
758
1016
|
);
|
759
|
-
|
1017
|
+
|
760
1018
|
// Update file permissions to match source
|
761
1019
|
try {
|
762
1020
|
await this.targetStorage.updateFile(
|
@@ -765,16 +1023,24 @@ export class ComprehensiveTransfer {
|
|
765
1023
|
file.name,
|
766
1024
|
file.$permissions
|
767
1025
|
);
|
768
|
-
MessageFormatter.success(
|
1026
|
+
MessageFormatter.success(
|
1027
|
+
`Updated file ${file.name} permissions to match source`,
|
1028
|
+
{ prefix: "Transfer" }
|
1029
|
+
);
|
769
1030
|
} catch (updateError) {
|
770
1031
|
MessageFormatter.error(
|
771
|
-
`Failed to update permissions for file ${file.name}`,
|
772
|
-
updateError instanceof Error
|
1032
|
+
`Failed to update permissions for file ${file.name}`,
|
1033
|
+
updateError instanceof Error
|
1034
|
+
? updateError
|
1035
|
+
: new Error(String(updateError)),
|
773
1036
|
{ prefix: "Transfer" }
|
774
1037
|
);
|
775
1038
|
}
|
776
1039
|
} else {
|
777
|
-
MessageFormatter.info(
|
1040
|
+
MessageFormatter.info(
|
1041
|
+
`File ${file.name} already exists with matching permissions, skipping`,
|
1042
|
+
{ prefix: "Transfer" }
|
1043
|
+
);
|
778
1044
|
}
|
779
1045
|
return;
|
780
1046
|
} catch (error) {
|
@@ -782,9 +1048,15 @@ export class ComprehensiveTransfer {
|
|
782
1048
|
}
|
783
1049
|
|
784
1050
|
// Download file with validation
|
785
|
-
const fileData = await this.validateAndDownloadFile(
|
1051
|
+
const fileData = await this.validateAndDownloadFile(
|
1052
|
+
sourceBucketId,
|
1053
|
+
file.$id
|
1054
|
+
);
|
786
1055
|
if (!fileData) {
|
787
|
-
MessageFormatter.warning(
|
1056
|
+
MessageFormatter.warning(
|
1057
|
+
`File ${file.name} failed validation, skipping`,
|
1058
|
+
{ prefix: "Transfer" }
|
1059
|
+
);
|
788
1060
|
return;
|
789
1061
|
}
|
790
1062
|
|
@@ -802,9 +1074,15 @@ export class ComprehensiveTransfer {
|
|
802
1074
|
);
|
803
1075
|
|
804
1076
|
transferredFiles++;
|
805
|
-
MessageFormatter.success(`Transferred file: ${file.name}`, {
|
1077
|
+
MessageFormatter.success(`Transferred file: ${file.name}`, {
|
1078
|
+
prefix: "Transfer",
|
1079
|
+
});
|
806
1080
|
} catch (error) {
|
807
|
-
MessageFormatter.error(
|
1081
|
+
MessageFormatter.error(
|
1082
|
+
`Failed to transfer file ${file.name}`,
|
1083
|
+
error instanceof Error ? error : new Error(String(error)),
|
1084
|
+
{ prefix: "Transfer" }
|
1085
|
+
);
|
808
1086
|
}
|
809
1087
|
})
|
810
1088
|
);
|
@@ -815,61 +1093,99 @@ export class ComprehensiveTransfer {
|
|
815
1093
|
lastFileId = files.files[files.files.length - 1].$id;
|
816
1094
|
}
|
817
1095
|
|
818
|
-
MessageFormatter.info(
|
1096
|
+
MessageFormatter.info(
|
1097
|
+
`Transferred ${transferredFiles} files from bucket ${sourceBucketId}`,
|
1098
|
+
{ prefix: "Transfer" }
|
1099
|
+
);
|
819
1100
|
}
|
820
1101
|
|
821
|
-
private async validateAndDownloadFile(
|
1102
|
+
private async validateAndDownloadFile(
|
1103
|
+
bucketId: string,
|
1104
|
+
fileId: string
|
1105
|
+
): Promise<ArrayBuffer | null> {
|
822
1106
|
let attempts = 3;
|
823
1107
|
while (attempts > 0) {
|
824
1108
|
try {
|
825
|
-
const fileData = await this.sourceStorage.getFileDownload(
|
826
|
-
|
1109
|
+
const fileData = await this.sourceStorage.getFileDownload(
|
1110
|
+
bucketId,
|
1111
|
+
fileId
|
1112
|
+
);
|
1113
|
+
|
827
1114
|
// Basic validation - ensure file is not empty and not too large
|
828
1115
|
if (fileData.byteLength === 0) {
|
829
|
-
MessageFormatter.warning(`File ${fileId} is empty`, {
|
1116
|
+
MessageFormatter.warning(`File ${fileId} is empty`, {
|
1117
|
+
prefix: "Transfer",
|
1118
|
+
});
|
830
1119
|
return null;
|
831
1120
|
}
|
832
1121
|
|
833
|
-
if (fileData.byteLength > 50 * 1024 * 1024) {
|
834
|
-
|
1122
|
+
if (fileData.byteLength > 50 * 1024 * 1024) {
|
1123
|
+
// 50MB limit
|
1124
|
+
MessageFormatter.warning(
|
1125
|
+
`File ${fileId} is too large (${fileData.byteLength} bytes)`,
|
1126
|
+
{ prefix: "Transfer" }
|
1127
|
+
);
|
835
1128
|
return null;
|
836
1129
|
}
|
837
1130
|
|
838
1131
|
return fileData;
|
839
1132
|
} catch (error) {
|
840
1133
|
attempts--;
|
841
|
-
MessageFormatter.warning(
|
1134
|
+
MessageFormatter.warning(
|
1135
|
+
`Error downloading file ${fileId}, attempts left: ${attempts}`,
|
1136
|
+
{ prefix: "Transfer" }
|
1137
|
+
);
|
842
1138
|
if (attempts === 0) {
|
843
|
-
MessageFormatter.error(
|
1139
|
+
MessageFormatter.error(
|
1140
|
+
`Failed to download file ${fileId} after all attempts`,
|
1141
|
+
error instanceof Error ? error : new Error(String(error)),
|
1142
|
+
{ prefix: "Transfer" }
|
1143
|
+
);
|
844
1144
|
return null;
|
845
1145
|
}
|
846
1146
|
// Wait before retry
|
847
|
-
await new Promise(resolve =>
|
1147
|
+
await new Promise((resolve) =>
|
1148
|
+
setTimeout(resolve, 1000 * (4 - attempts))
|
1149
|
+
);
|
848
1150
|
}
|
849
1151
|
}
|
850
1152
|
return null;
|
851
1153
|
}
|
852
1154
|
|
853
1155
|
private async transferAllFunctions(): Promise<void> {
|
854
|
-
MessageFormatter.info("Starting function transfer phase", {
|
1156
|
+
MessageFormatter.info("Starting function transfer phase", {
|
1157
|
+
prefix: "Transfer",
|
1158
|
+
});
|
855
1159
|
|
856
1160
|
try {
|
857
|
-
const sourceFunctions = await listFunctions(this.sourceClient, [
|
858
|
-
|
1161
|
+
const sourceFunctions = await listFunctions(this.sourceClient, [
|
1162
|
+
Query.limit(1000),
|
1163
|
+
]);
|
1164
|
+
const targetFunctions = await listFunctions(this.targetClient, [
|
1165
|
+
Query.limit(1000),
|
1166
|
+
]);
|
859
1167
|
|
860
1168
|
if (this.options.dryRun) {
|
861
|
-
MessageFormatter.info(
|
1169
|
+
MessageFormatter.info(
|
1170
|
+
`DRY RUN: Would transfer ${sourceFunctions.functions.length} functions`,
|
1171
|
+
{ prefix: "Transfer" }
|
1172
|
+
);
|
862
1173
|
return;
|
863
1174
|
}
|
864
1175
|
|
865
|
-
const transferTasks = sourceFunctions.functions.map(func =>
|
1176
|
+
const transferTasks = sourceFunctions.functions.map((func) =>
|
866
1177
|
this.limit(async () => {
|
867
1178
|
try {
|
868
1179
|
// Check if function exists in target
|
869
|
-
const existingFunc = targetFunctions.functions.find(
|
870
|
-
|
1180
|
+
const existingFunc = targetFunctions.functions.find(
|
1181
|
+
(tf) => tf.$id === func.$id
|
1182
|
+
);
|
1183
|
+
|
871
1184
|
if (existingFunc) {
|
872
|
-
MessageFormatter.info(
|
1185
|
+
MessageFormatter.info(
|
1186
|
+
`Function ${func.name} already exists, skipping creation`,
|
1187
|
+
{ prefix: "Transfer" }
|
1188
|
+
);
|
873
1189
|
this.results.functions.skipped++;
|
874
1190
|
return;
|
875
1191
|
}
|
@@ -877,7 +1193,11 @@ export class ComprehensiveTransfer {
|
|
877
1193
|
// Download function from source
|
878
1194
|
const functionPath = await this.downloadFunction(func);
|
879
1195
|
if (!functionPath) {
|
880
|
-
MessageFormatter.error(
|
1196
|
+
MessageFormatter.error(
|
1197
|
+
`Failed to download function ${func.name}`,
|
1198
|
+
undefined,
|
1199
|
+
{ prefix: "Transfer" }
|
1200
|
+
);
|
881
1201
|
this.results.functions.failed++;
|
882
1202
|
return;
|
883
1203
|
}
|
@@ -904,26 +1224,45 @@ export class ComprehensiveTransfer {
|
|
904
1224
|
specification: func.specification as any,
|
905
1225
|
dirPath: functionPath,
|
906
1226
|
};
|
907
|
-
|
908
|
-
await deployLocalFunction(
|
1227
|
+
|
1228
|
+
await deployLocalFunction(
|
1229
|
+
this.targetClient,
|
1230
|
+
func.name,
|
1231
|
+
functionConfig
|
1232
|
+
);
|
909
1233
|
|
910
1234
|
this.results.functions.transferred++;
|
911
|
-
MessageFormatter.success(
|
1235
|
+
MessageFormatter.success(
|
1236
|
+
`Function ${func.name} transferred successfully`,
|
1237
|
+
{ prefix: "Transfer" }
|
1238
|
+
);
|
912
1239
|
} catch (error) {
|
913
|
-
MessageFormatter.error(
|
1240
|
+
MessageFormatter.error(
|
1241
|
+
`Function ${func.name} transfer failed`,
|
1242
|
+
error instanceof Error ? error : new Error(String(error)),
|
1243
|
+
{ prefix: "Transfer" }
|
1244
|
+
);
|
914
1245
|
this.results.functions.failed++;
|
915
1246
|
}
|
916
1247
|
})
|
917
1248
|
);
|
918
1249
|
|
919
1250
|
await Promise.all(transferTasks);
|
920
|
-
MessageFormatter.success("Function transfer phase completed", {
|
1251
|
+
MessageFormatter.success("Function transfer phase completed", {
|
1252
|
+
prefix: "Transfer",
|
1253
|
+
});
|
921
1254
|
} catch (error) {
|
922
|
-
MessageFormatter.error(
|
1255
|
+
MessageFormatter.error(
|
1256
|
+
"Function transfer phase failed",
|
1257
|
+
error instanceof Error ? error : new Error(String(error)),
|
1258
|
+
{ prefix: "Transfer" }
|
1259
|
+
);
|
923
1260
|
}
|
924
1261
|
}
|
925
1262
|
|
926
|
-
private async downloadFunction(
|
1263
|
+
private async downloadFunction(
|
1264
|
+
func: Models.Function
|
1265
|
+
): Promise<string | null> {
|
927
1266
|
try {
|
928
1267
|
const { path } = await downloadLatestFunctionDeployment(
|
929
1268
|
this.sourceClient,
|
@@ -932,7 +1271,11 @@ export class ComprehensiveTransfer {
|
|
932
1271
|
);
|
933
1272
|
return path;
|
934
1273
|
} catch (error) {
|
935
|
-
MessageFormatter.error(
|
1274
|
+
MessageFormatter.error(
|
1275
|
+
`Failed to download function ${func.name}`,
|
1276
|
+
error instanceof Error ? error : new Error(String(error)),
|
1277
|
+
{ prefix: "Transfer" }
|
1278
|
+
);
|
936
1279
|
return null;
|
937
1280
|
}
|
938
1281
|
}
|
@@ -940,7 +1283,10 @@ export class ComprehensiveTransfer {
|
|
940
1283
|
/**
|
941
1284
|
* Helper method to fetch all collections from a database
|
942
1285
|
*/
|
943
|
-
private async fetchAllCollections(
|
1286
|
+
private async fetchAllCollections(
|
1287
|
+
dbId: string,
|
1288
|
+
databases: Databases
|
1289
|
+
): Promise<Models.Collection[]> {
|
944
1290
|
const collections: Models.Collection[] = [];
|
945
1291
|
let lastId: string | undefined;
|
946
1292
|
|
@@ -950,18 +1296,20 @@ export class ComprehensiveTransfer {
|
|
950
1296
|
queries.push(Query.cursorAfter(lastId));
|
951
1297
|
}
|
952
1298
|
|
953
|
-
const result = await tryAwaitWithRetry(async () =>
|
954
|
-
|
1299
|
+
const result = await tryAwaitWithRetry(async () =>
|
1300
|
+
databases.listCollections(dbId, queries)
|
1301
|
+
);
|
1302
|
+
|
955
1303
|
if (result.collections.length === 0) {
|
956
1304
|
break;
|
957
1305
|
}
|
958
1306
|
|
959
1307
|
collections.push(...result.collections);
|
960
|
-
|
1308
|
+
|
961
1309
|
if (result.collections.length < 100) {
|
962
1310
|
break;
|
963
1311
|
}
|
964
|
-
|
1312
|
+
|
965
1313
|
lastId = result.collections[result.collections.length - 1].$id;
|
966
1314
|
}
|
967
1315
|
|
@@ -981,18 +1329,20 @@ export class ComprehensiveTransfer {
|
|
981
1329
|
queries.push(Query.cursorAfter(lastId));
|
982
1330
|
}
|
983
1331
|
|
984
|
-
const result = await tryAwaitWithRetry(async () =>
|
985
|
-
|
1332
|
+
const result = await tryAwaitWithRetry(async () =>
|
1333
|
+
storage.listBuckets(queries)
|
1334
|
+
);
|
1335
|
+
|
986
1336
|
if (result.buckets.length === 0) {
|
987
1337
|
break;
|
988
1338
|
}
|
989
1339
|
|
990
1340
|
buckets.push(...result.buckets);
|
991
|
-
|
1341
|
+
|
992
1342
|
if (result.buckets.length < 100) {
|
993
1343
|
break;
|
994
1344
|
}
|
995
|
-
|
1345
|
+
|
996
1346
|
lastId = result.buckets[result.buckets.length - 1].$id;
|
997
1347
|
}
|
998
1348
|
|
@@ -1020,7 +1370,7 @@ export class ComprehensiveTransfer {
|
|
1020
1370
|
twoWay: attr.twoWay,
|
1021
1371
|
twoWayKey: attr.twoWayKey,
|
1022
1372
|
onDelete: attr.onDelete,
|
1023
|
-
side: attr.side
|
1373
|
+
side: attr.side,
|
1024
1374
|
};
|
1025
1375
|
}
|
1026
1376
|
|
@@ -1034,8 +1384,10 @@ export class ComprehensiveTransfer {
|
|
1034
1384
|
attributes: any[]
|
1035
1385
|
): Promise<boolean> {
|
1036
1386
|
// Import the enhanced attribute creation function
|
1037
|
-
const { createUpdateCollectionAttributesWithStatusCheck } = await import(
|
1038
|
-
|
1387
|
+
const { createUpdateCollectionAttributesWithStatusCheck } = await import(
|
1388
|
+
"../collections/attributes.js"
|
1389
|
+
);
|
1390
|
+
|
1039
1391
|
return await createUpdateCollectionAttributesWithStatusCheck(
|
1040
1392
|
databases,
|
1041
1393
|
dbId,
|
@@ -1055,8 +1407,10 @@ export class ComprehensiveTransfer {
|
|
1055
1407
|
indexes: any[]
|
1056
1408
|
): Promise<boolean> {
|
1057
1409
|
// Import the enhanced index creation function
|
1058
|
-
const { createOrUpdateIndexesWithStatusCheck } = await import(
|
1059
|
-
|
1410
|
+
const { createOrUpdateIndexesWithStatusCheck } = await import(
|
1411
|
+
"../collections/indexes.js"
|
1412
|
+
);
|
1413
|
+
|
1060
1414
|
return await createOrUpdateIndexesWithStatusCheck(
|
1061
1415
|
dbId,
|
1062
1416
|
databases,
|
@@ -1077,7 +1431,10 @@ export class ComprehensiveTransfer {
|
|
1077
1431
|
sourceCollectionId: string,
|
1078
1432
|
targetCollectionId: string
|
1079
1433
|
): Promise<void> {
|
1080
|
-
MessageFormatter.info(
|
1434
|
+
MessageFormatter.info(
|
1435
|
+
`Transferring documents from ${sourceCollectionId} to ${targetCollectionId} with bulk operations, content comparison, and permission filtering`,
|
1436
|
+
{ prefix: "Transfer" }
|
1437
|
+
);
|
1081
1438
|
|
1082
1439
|
let lastId: string | undefined;
|
1083
1440
|
let totalTransferred = 0;
|
@@ -1085,11 +1442,14 @@ export class ComprehensiveTransfer {
|
|
1085
1442
|
let totalUpdated = 0;
|
1086
1443
|
|
1087
1444
|
// Check if bulk operations are supported
|
1088
|
-
const supportsBulk =
|
1089
|
-
|
1090
|
-
|
1445
|
+
const supportsBulk =
|
1446
|
+
this.options.sourceEndpoint.includes("cloud.appwrite.io") ||
|
1447
|
+
this.options.targetEndpoint.includes("cloud.appwrite.io");
|
1448
|
+
|
1091
1449
|
if (supportsBulk) {
|
1092
|
-
MessageFormatter.info(`Using bulk operations for enhanced performance`, {
|
1450
|
+
MessageFormatter.info(`Using bulk operations for enhanced performance`, {
|
1451
|
+
prefix: "Transfer",
|
1452
|
+
});
|
1093
1453
|
}
|
1094
1454
|
|
1095
1455
|
while (true) {
|
@@ -1099,7 +1459,7 @@ export class ComprehensiveTransfer {
|
|
1099
1459
|
queries.push(Query.cursorAfter(lastId));
|
1100
1460
|
}
|
1101
1461
|
|
1102
|
-
const sourceDocuments = await tryAwaitWithRetry(async () =>
|
1462
|
+
const sourceDocuments = await tryAwaitWithRetry(async () =>
|
1103
1463
|
sourceDb.listDocuments(sourceDbId, sourceCollectionId, queries)
|
1104
1464
|
);
|
1105
1465
|
|
@@ -1107,87 +1467,90 @@ export class ComprehensiveTransfer {
|
|
1107
1467
|
break;
|
1108
1468
|
}
|
1109
1469
|
|
1110
|
-
MessageFormatter.info(
|
1470
|
+
MessageFormatter.info(
|
1471
|
+
`Processing batch of ${sourceDocuments.documents.length} source documents`,
|
1472
|
+
{ prefix: "Transfer" }
|
1473
|
+
);
|
1111
1474
|
|
1112
1475
|
// Extract document IDs from the current batch
|
1113
|
-
const sourceDocIds = sourceDocuments.documents.map(doc => doc.$id);
|
1114
|
-
|
1476
|
+
const sourceDocIds = sourceDocuments.documents.map((doc) => doc.$id);
|
1477
|
+
|
1115
1478
|
// Fetch existing documents from target in a single query
|
1116
1479
|
const existingTargetDocs = await this.fetchTargetDocumentsBatch(
|
1117
|
-
targetDb,
|
1118
|
-
targetDbId,
|
1119
|
-
targetCollectionId,
|
1480
|
+
targetDb,
|
1481
|
+
targetDbId,
|
1482
|
+
targetCollectionId,
|
1120
1483
|
sourceDocIds
|
1121
1484
|
);
|
1122
1485
|
|
1123
1486
|
// Create a map for quick lookup of existing documents
|
1124
1487
|
const existingDocsMap = new Map<string, Models.Document>();
|
1125
|
-
existingTargetDocs.forEach(doc => {
|
1488
|
+
existingTargetDocs.forEach((doc) => {
|
1126
1489
|
existingDocsMap.set(doc.$id, doc);
|
1127
1490
|
});
|
1128
1491
|
|
1129
1492
|
// Filter documents based on existence, content comparison, and permission comparison
|
1130
1493
|
const documentsToTransfer: Models.Document[] = [];
|
1131
|
-
const documentsToUpdate: {
|
1494
|
+
const documentsToUpdate: {
|
1495
|
+
doc: Models.Document;
|
1496
|
+
targetDoc: Models.Document;
|
1497
|
+
reason: string;
|
1498
|
+
}[] = [];
|
1132
1499
|
|
1133
1500
|
for (const sourceDoc of sourceDocuments.documents) {
|
1134
1501
|
const existingTargetDoc = existingDocsMap.get(sourceDoc.$id);
|
1135
|
-
|
1502
|
+
|
1136
1503
|
if (!existingTargetDoc) {
|
1137
1504
|
// Document doesn't exist in target, needs to be transferred
|
1138
1505
|
documentsToTransfer.push(sourceDoc);
|
1139
1506
|
} else {
|
1140
1507
|
// Document exists, compare both content and permissions
|
1141
|
-
const sourcePermissions =
|
1142
|
-
|
1143
|
-
|
1144
|
-
|
1508
|
+
const sourcePermissions = Array.from(
|
1509
|
+
new Set(sourceDoc.$permissions || [])
|
1510
|
+
).sort();
|
1511
|
+
const targetPermissions = Array.from(
|
1512
|
+
new Set(existingTargetDoc.$permissions || [])
|
1513
|
+
).sort();
|
1514
|
+
const permissionsDiffer =
|
1515
|
+
sourcePermissions.join(",") !== targetPermissions.join(",") ||
|
1516
|
+
sourcePermissions.length !== targetPermissions.length;
|
1517
|
+
|
1145
1518
|
// Use objectNeedsUpdate to compare document content (excluding system fields)
|
1146
|
-
const contentDiffers = objectNeedsUpdate(
|
1147
|
-
|
1519
|
+
const contentDiffers = objectNeedsUpdate(
|
1520
|
+
existingTargetDoc,
|
1521
|
+
sourceDoc
|
1522
|
+
);
|
1523
|
+
|
1148
1524
|
if (contentDiffers && permissionsDiffer) {
|
1149
1525
|
// Both content and permissions differ
|
1150
|
-
documentsToUpdate.push({
|
1151
|
-
doc: sourceDoc,
|
1152
|
-
targetDoc: existingTargetDoc,
|
1153
|
-
reason: "content and permissions differ"
|
1526
|
+
documentsToUpdate.push({
|
1527
|
+
doc: sourceDoc,
|
1528
|
+
targetDoc: existingTargetDoc,
|
1529
|
+
reason: "content and permissions differ",
|
1154
1530
|
});
|
1155
|
-
MessageFormatter.info(
|
1156
|
-
`Document ${sourceDoc.$id} exists but content and permissions differ - will update`,
|
1157
|
-
{ prefix: "Transfer" }
|
1158
|
-
);
|
1159
1531
|
} else if (contentDiffers) {
|
1160
1532
|
// Only content differs
|
1161
|
-
documentsToUpdate.push({
|
1162
|
-
doc: sourceDoc,
|
1163
|
-
targetDoc: existingTargetDoc,
|
1164
|
-
reason: "content differs"
|
1533
|
+
documentsToUpdate.push({
|
1534
|
+
doc: sourceDoc,
|
1535
|
+
targetDoc: existingTargetDoc,
|
1536
|
+
reason: "content differs",
|
1165
1537
|
});
|
1166
|
-
MessageFormatter.info(
|
1167
|
-
`Document ${sourceDoc.$id} exists but content differs - will update`,
|
1168
|
-
{ prefix: "Transfer" }
|
1169
|
-
);
|
1170
1538
|
} else if (permissionsDiffer) {
|
1171
1539
|
// Only permissions differ
|
1172
|
-
documentsToUpdate.push({
|
1173
|
-
doc: sourceDoc,
|
1174
|
-
targetDoc: existingTargetDoc,
|
1175
|
-
reason: "permissions differ"
|
1540
|
+
documentsToUpdate.push({
|
1541
|
+
doc: sourceDoc,
|
1542
|
+
targetDoc: existingTargetDoc,
|
1543
|
+
reason: "permissions differ",
|
1176
1544
|
});
|
1177
|
-
MessageFormatter.info(
|
1178
|
-
`Document ${sourceDoc.$id} exists but permissions differ - will update`,
|
1179
|
-
{ prefix: "Transfer" }
|
1180
|
-
);
|
1181
1545
|
} else {
|
1182
1546
|
// Document exists with identical content AND permissions, skip
|
1183
1547
|
totalSkipped++;
|
1184
|
-
MessageFormatter.info(`Document ${sourceDoc.$id} exists with matching content and permissions - skipping`, { prefix: "Transfer" });
|
1185
1548
|
}
|
1186
1549
|
}
|
1187
1550
|
}
|
1188
1551
|
|
1189
1552
|
MessageFormatter.info(
|
1190
|
-
`Batch analysis: ${documentsToTransfer.length} to create, ${documentsToUpdate.length} to update, ${totalSkipped} skipped so far`,
|
1553
|
+
`Batch analysis: ${documentsToTransfer.length} to create, ${documentsToUpdate.length} to update, ${totalSkipped} skipped so far`,
|
1191
1554
|
{ prefix: "Transfer" }
|
1192
1555
|
);
|
1193
1556
|
|
@@ -1202,7 +1565,6 @@ export class ComprehensiveTransfer {
|
|
1202
1565
|
documentsToTransfer
|
1203
1566
|
);
|
1204
1567
|
totalTransferred += documentsToTransfer.length;
|
1205
|
-
MessageFormatter.success(`Bulk transferred ${documentsToTransfer.length} new documents`, { prefix: "Transfer" });
|
1206
1568
|
} else {
|
1207
1569
|
// Use individual transfers for smaller batches or non-bulk endpoints
|
1208
1570
|
const transferCount = await this.transferDocumentsIndividual(
|
@@ -1230,11 +1592,12 @@ export class ComprehensiveTransfer {
|
|
1230
1592
|
break;
|
1231
1593
|
}
|
1232
1594
|
|
1233
|
-
lastId =
|
1595
|
+
lastId =
|
1596
|
+
sourceDocuments.documents[sourceDocuments.documents.length - 1].$id;
|
1234
1597
|
}
|
1235
1598
|
|
1236
1599
|
MessageFormatter.info(
|
1237
|
-
`Transfer complete: ${totalTransferred} new, ${totalUpdated} updated, ${totalSkipped} skipped from ${sourceCollectionId} to ${targetCollectionId}`,
|
1600
|
+
`Transfer complete: ${totalTransferred} new, ${totalUpdated} updated, ${totalSkipped} skipped from ${sourceCollectionId} to ${targetCollectionId}`,
|
1238
1601
|
{ prefix: "Transfer" }
|
1239
1602
|
);
|
1240
1603
|
}
|
@@ -1249,29 +1612,33 @@ export class ComprehensiveTransfer {
|
|
1249
1612
|
docIds: string[]
|
1250
1613
|
): Promise<Models.Document[]> {
|
1251
1614
|
const documents: Models.Document[] = [];
|
1252
|
-
|
1615
|
+
|
1253
1616
|
// Split IDs into chunks of 100 for Query.equal limitations
|
1254
1617
|
const idChunks = this.chunkArray(docIds, 100);
|
1255
|
-
|
1618
|
+
|
1256
1619
|
for (const chunk of idChunks) {
|
1257
1620
|
try {
|
1258
|
-
const result = await tryAwaitWithRetry(async () =>
|
1621
|
+
const result = await tryAwaitWithRetry(async () =>
|
1259
1622
|
targetDb.listDocuments(targetDbId, targetCollectionId, [
|
1260
|
-
Query.equal(
|
1261
|
-
Query.limit(100)
|
1623
|
+
Query.equal("$id", chunk),
|
1624
|
+
Query.limit(100),
|
1262
1625
|
])
|
1263
1626
|
);
|
1264
1627
|
documents.push(...result.documents);
|
1265
1628
|
} catch (error) {
|
1266
1629
|
// If query fails, fall back to individual gets (less efficient but more reliable)
|
1267
1630
|
MessageFormatter.warning(
|
1268
|
-
`Batch query failed for ${chunk.length} documents, falling back to individual checks`,
|
1631
|
+
`Batch query failed for ${chunk.length} documents, falling back to individual checks`,
|
1269
1632
|
{ prefix: "Transfer" }
|
1270
1633
|
);
|
1271
|
-
|
1634
|
+
|
1272
1635
|
for (const docId of chunk) {
|
1273
1636
|
try {
|
1274
|
-
const doc = await targetDb.getDocument(
|
1637
|
+
const doc = await targetDb.getDocument(
|
1638
|
+
targetDbId,
|
1639
|
+
targetCollectionId,
|
1640
|
+
docId
|
1641
|
+
);
|
1275
1642
|
documents.push(doc);
|
1276
1643
|
} catch (getError) {
|
1277
1644
|
// Document doesn't exist, which is fine
|
@@ -1279,7 +1646,7 @@ export class ComprehensiveTransfer {
|
|
1279
1646
|
}
|
1280
1647
|
}
|
1281
1648
|
}
|
1282
|
-
|
1649
|
+
|
1283
1650
|
return documents;
|
1284
1651
|
}
|
1285
1652
|
|
@@ -1293,12 +1660,20 @@ export class ComprehensiveTransfer {
|
|
1293
1660
|
documents: Models.Document[]
|
1294
1661
|
): Promise<void> {
|
1295
1662
|
// Prepare documents for bulk upsert
|
1296
|
-
const preparedDocs = documents.map(doc => {
|
1297
|
-
const {
|
1298
|
-
return {
|
1663
|
+
const preparedDocs = documents.map((doc) => {
|
1664
|
+
const {
|
1299
1665
|
$id,
|
1666
|
+
$createdAt,
|
1667
|
+
$updatedAt,
|
1300
1668
|
$permissions,
|
1669
|
+
$databaseId,
|
1670
|
+
$collectionId,
|
1301
1671
|
...docData
|
1672
|
+
} = doc;
|
1673
|
+
return {
|
1674
|
+
$id,
|
1675
|
+
$permissions,
|
1676
|
+
...docData,
|
1302
1677
|
};
|
1303
1678
|
});
|
1304
1679
|
|
@@ -1308,31 +1683,27 @@ export class ComprehensiveTransfer {
|
|
1308
1683
|
|
1309
1684
|
for (const maxBatchSize of batchSizes) {
|
1310
1685
|
const documentBatches = this.chunkArray(preparedDocs, maxBatchSize);
|
1311
|
-
|
1686
|
+
|
1312
1687
|
try {
|
1313
1688
|
for (const batch of documentBatches) {
|
1314
|
-
MessageFormatter.info(`Bulk upserting ${batch.length} documents...`, { prefix: "Transfer" });
|
1315
|
-
|
1316
1689
|
await this.bulkUpsertDocuments(
|
1317
1690
|
this.targetClient,
|
1318
1691
|
targetDbId,
|
1319
1692
|
targetCollectionId,
|
1320
1693
|
batch
|
1321
1694
|
);
|
1322
|
-
|
1323
|
-
MessageFormatter.success(
|
1324
|
-
|
1325
|
-
|
1326
|
-
|
1327
|
-
await new Promise(resolve => setTimeout(resolve, 200));
|
1328
|
-
}
|
1695
|
+
|
1696
|
+
MessageFormatter.success(
|
1697
|
+
`✅ Bulk upserted ${batch.length} documents`,
|
1698
|
+
{ prefix: "Transfer" }
|
1699
|
+
);
|
1329
1700
|
}
|
1330
|
-
|
1701
|
+
|
1331
1702
|
processed = true;
|
1332
1703
|
break; // Success, exit batch size loop
|
1333
1704
|
} catch (error) {
|
1334
1705
|
MessageFormatter.warning(
|
1335
|
-
`Bulk upsert with batch size ${maxBatchSize} failed, trying smaller size...`,
|
1706
|
+
`Bulk upsert with batch size ${maxBatchSize} failed, trying smaller size...`,
|
1336
1707
|
{ prefix: "Transfer" }
|
1337
1708
|
);
|
1338
1709
|
continue; // Try next smaller batch size
|
@@ -1341,12 +1712,17 @@ export class ComprehensiveTransfer {
|
|
1341
1712
|
|
1342
1713
|
if (!processed) {
|
1343
1714
|
MessageFormatter.warning(
|
1344
|
-
`All bulk operations failed, falling back to individual transfers`,
|
1715
|
+
`All bulk operations failed, falling back to individual transfers`,
|
1345
1716
|
{ prefix: "Transfer" }
|
1346
1717
|
);
|
1347
|
-
|
1718
|
+
|
1348
1719
|
// Fall back to individual transfers
|
1349
|
-
await this.transferDocumentsIndividual(
|
1720
|
+
await this.transferDocumentsIndividual(
|
1721
|
+
targetDb,
|
1722
|
+
targetDbId,
|
1723
|
+
targetCollectionId,
|
1724
|
+
documents
|
1725
|
+
);
|
1350
1726
|
}
|
1351
1727
|
}
|
1352
1728
|
|
@@ -1361,24 +1737,30 @@ export class ComprehensiveTransfer {
|
|
1361
1737
|
): Promise<any> {
|
1362
1738
|
const apiPath = `/databases/${dbId}/collections/${collectionId}/documents`;
|
1363
1739
|
const url = new URL(client.config.endpoint + apiPath);
|
1364
|
-
|
1740
|
+
|
1365
1741
|
const headers = {
|
1366
|
-
|
1367
|
-
|
1368
|
-
|
1742
|
+
"Content-Type": "application/json",
|
1743
|
+
"X-Appwrite-Project": client.config.project,
|
1744
|
+
"X-Appwrite-Key": client.config.key,
|
1369
1745
|
};
|
1370
|
-
|
1746
|
+
|
1371
1747
|
const response = await fetch(url.toString(), {
|
1372
|
-
method:
|
1748
|
+
method: "PUT",
|
1373
1749
|
headers,
|
1374
|
-
body: JSON.stringify({ documents })
|
1750
|
+
body: JSON.stringify({ documents }),
|
1375
1751
|
});
|
1376
|
-
|
1752
|
+
|
1377
1753
|
if (!response.ok) {
|
1378
|
-
const errorData: any = await response
|
1379
|
-
|
1754
|
+
const errorData: any = await response
|
1755
|
+
.json()
|
1756
|
+
.catch(() => ({ message: "Unknown error" }));
|
1757
|
+
throw new Error(
|
1758
|
+
`Bulk upsert failed: ${response.status} - ${
|
1759
|
+
errorData.message || "Unknown error"
|
1760
|
+
}`
|
1761
|
+
);
|
1380
1762
|
}
|
1381
|
-
|
1763
|
+
|
1382
1764
|
return await response.json();
|
1383
1765
|
}
|
1384
1766
|
|
@@ -1393,11 +1775,19 @@ export class ComprehensiveTransfer {
|
|
1393
1775
|
): Promise<number> {
|
1394
1776
|
let successCount = 0;
|
1395
1777
|
|
1396
|
-
const transferTasks = documents.map(doc =>
|
1778
|
+
const transferTasks = documents.map((doc) =>
|
1397
1779
|
this.limit(async () => {
|
1398
1780
|
try {
|
1399
|
-
const {
|
1400
|
-
|
1781
|
+
const {
|
1782
|
+
$id,
|
1783
|
+
$createdAt,
|
1784
|
+
$updatedAt,
|
1785
|
+
$permissions,
|
1786
|
+
$databaseId,
|
1787
|
+
$collectionId,
|
1788
|
+
...docData
|
1789
|
+
} = doc;
|
1790
|
+
|
1401
1791
|
await tryAwaitWithRetry(async () =>
|
1402
1792
|
targetDb.createDocument(
|
1403
1793
|
targetDbId,
|
@@ -1409,11 +1799,47 @@ export class ComprehensiveTransfer {
|
|
1409
1799
|
);
|
1410
1800
|
|
1411
1801
|
successCount++;
|
1412
|
-
MessageFormatter.success(`Transferred document ${doc.$id}`, { prefix: "Transfer" });
|
1413
1802
|
} catch (error) {
|
1803
|
+
if (
|
1804
|
+
error instanceof AppwriteException &&
|
1805
|
+
error.message.includes("already exists")
|
1806
|
+
) {
|
1807
|
+
try {
|
1808
|
+
// Update it! It's here because it needs an update or a create
|
1809
|
+
const {
|
1810
|
+
$id,
|
1811
|
+
$createdAt,
|
1812
|
+
$updatedAt,
|
1813
|
+
$permissions,
|
1814
|
+
$databaseId,
|
1815
|
+
$collectionId,
|
1816
|
+
...docData
|
1817
|
+
} = doc;
|
1818
|
+
await tryAwaitWithRetry(async () =>
|
1819
|
+
targetDb.updateDocument(
|
1820
|
+
targetDbId,
|
1821
|
+
targetCollectionId,
|
1822
|
+
doc.$id,
|
1823
|
+
docData,
|
1824
|
+
doc.$permissions
|
1825
|
+
)
|
1826
|
+
);
|
1827
|
+
successCount++;
|
1828
|
+
} catch (updateError) {
|
1829
|
+
// just send the error to the formatter
|
1830
|
+
MessageFormatter.error(
|
1831
|
+
`Failed to transfer document ${doc.$id}`,
|
1832
|
+
updateError instanceof Error
|
1833
|
+
? updateError
|
1834
|
+
: new Error(String(updateError)),
|
1835
|
+
{ prefix: "Transfer" }
|
1836
|
+
);
|
1837
|
+
}
|
1838
|
+
}
|
1839
|
+
|
1414
1840
|
MessageFormatter.error(
|
1415
|
-
`Failed to transfer document ${doc.$id}`,
|
1416
|
-
error instanceof Error ? error : new Error(String(error)),
|
1841
|
+
`Failed to transfer document ${doc.$id}`,
|
1842
|
+
error instanceof Error ? error : new Error(String(error)),
|
1417
1843
|
{ prefix: "Transfer" }
|
1418
1844
|
);
|
1419
1845
|
}
|
@@ -1431,15 +1857,27 @@ export class ComprehensiveTransfer {
|
|
1431
1857
|
targetDb: Databases,
|
1432
1858
|
targetDbId: string,
|
1433
1859
|
targetCollectionId: string,
|
1434
|
-
documentPairs: {
|
1860
|
+
documentPairs: {
|
1861
|
+
doc: Models.Document;
|
1862
|
+
targetDoc: Models.Document;
|
1863
|
+
reason: string;
|
1864
|
+
}[]
|
1435
1865
|
): Promise<number> {
|
1436
1866
|
let successCount = 0;
|
1437
1867
|
|
1438
|
-
const updateTasks = documentPairs.map(({ doc, targetDoc, reason }) =>
|
1868
|
+
const updateTasks = documentPairs.map(({ doc, targetDoc, reason }) =>
|
1439
1869
|
this.limit(async () => {
|
1440
1870
|
try {
|
1441
|
-
const {
|
1442
|
-
|
1871
|
+
const {
|
1872
|
+
$id,
|
1873
|
+
$createdAt,
|
1874
|
+
$updatedAt,
|
1875
|
+
$permissions,
|
1876
|
+
$databaseId,
|
1877
|
+
$collectionId,
|
1878
|
+
...docData
|
1879
|
+
} = doc;
|
1880
|
+
|
1443
1881
|
await tryAwaitWithRetry(async () =>
|
1444
1882
|
targetDb.updateDocument(
|
1445
1883
|
targetDbId,
|
@@ -1451,14 +1889,10 @@ export class ComprehensiveTransfer {
|
|
1451
1889
|
);
|
1452
1890
|
|
1453
1891
|
successCount++;
|
1454
|
-
MessageFormatter.success(
|
1455
|
-
`Updated document ${doc.$id} (${reason}) - permissions: [${targetDoc.$permissions?.join(', ')}] → [${doc.$permissions?.join(', ')}]`,
|
1456
|
-
{ prefix: "Transfer" }
|
1457
|
-
);
|
1458
1892
|
} catch (error) {
|
1459
1893
|
MessageFormatter.error(
|
1460
|
-
`Failed to update document ${doc.$id} (${reason})`,
|
1461
|
-
error instanceof Error ? error : new Error(String(error)),
|
1894
|
+
`Failed to update document ${doc.$id} (${reason})`,
|
1895
|
+
error instanceof Error ? error : new Error(String(error)),
|
1462
1896
|
{ prefix: "Transfer" }
|
1463
1897
|
);
|
1464
1898
|
}
|
@@ -1483,7 +1917,9 @@ export class ComprehensiveTransfer {
|
|
1483
1917
|
/**
|
1484
1918
|
* Helper method to fetch all teams with pagination
|
1485
1919
|
*/
|
1486
|
-
private async fetchAllTeams(
|
1920
|
+
private async fetchAllTeams(
|
1921
|
+
teams: Teams
|
1922
|
+
): Promise<Models.Team<Models.Preferences>[]> {
|
1487
1923
|
const teamsList: Models.Team<Models.Preferences>[] = [];
|
1488
1924
|
let lastId: string | undefined;
|
1489
1925
|
|
@@ -1494,17 +1930,17 @@ export class ComprehensiveTransfer {
|
|
1494
1930
|
}
|
1495
1931
|
|
1496
1932
|
const result = await tryAwaitWithRetry(async () => teams.list(queries));
|
1497
|
-
|
1933
|
+
|
1498
1934
|
if (result.teams.length === 0) {
|
1499
1935
|
break;
|
1500
1936
|
}
|
1501
1937
|
|
1502
1938
|
teamsList.push(...result.teams);
|
1503
|
-
|
1939
|
+
|
1504
1940
|
if (result.teams.length < 100) {
|
1505
1941
|
break;
|
1506
1942
|
}
|
1507
|
-
|
1943
|
+
|
1508
1944
|
lastId = result.teams[result.teams.length - 1].$id;
|
1509
1945
|
}
|
1510
1946
|
|
@@ -1514,7 +1950,9 @@ export class ComprehensiveTransfer {
|
|
1514
1950
|
/**
|
1515
1951
|
* Helper method to fetch all memberships for a team with pagination
|
1516
1952
|
*/
|
1517
|
-
private async fetchAllMemberships(
|
1953
|
+
private async fetchAllMemberships(
|
1954
|
+
teamId: string
|
1955
|
+
): Promise<Models.Membership[]> {
|
1518
1956
|
const membershipsList: Models.Membership[] = [];
|
1519
1957
|
let lastId: string | undefined;
|
1520
1958
|
|
@@ -1524,20 +1962,20 @@ export class ComprehensiveTransfer {
|
|
1524
1962
|
queries.push(Query.cursorAfter(lastId));
|
1525
1963
|
}
|
1526
1964
|
|
1527
|
-
const result = await tryAwaitWithRetry(async () =>
|
1965
|
+
const result = await tryAwaitWithRetry(async () =>
|
1528
1966
|
this.sourceTeams.listMemberships(teamId, queries)
|
1529
1967
|
);
|
1530
|
-
|
1968
|
+
|
1531
1969
|
if (result.memberships.length === 0) {
|
1532
1970
|
break;
|
1533
1971
|
}
|
1534
1972
|
|
1535
1973
|
membershipsList.push(...result.memberships);
|
1536
|
-
|
1974
|
+
|
1537
1975
|
if (result.memberships.length < 100) {
|
1538
1976
|
break;
|
1539
1977
|
}
|
1540
|
-
|
1978
|
+
|
1541
1979
|
lastId = result.memberships[result.memberships.length - 1].$id;
|
1542
1980
|
}
|
1543
1981
|
|
@@ -1548,40 +1986,55 @@ export class ComprehensiveTransfer {
|
|
1548
1986
|
* Helper method to transfer team memberships
|
1549
1987
|
*/
|
1550
1988
|
private async transferTeamMemberships(teamId: string): Promise<void> {
|
1551
|
-
MessageFormatter.info(`Transferring memberships for team ${teamId}`, {
|
1989
|
+
MessageFormatter.info(`Transferring memberships for team ${teamId}`, {
|
1990
|
+
prefix: "Transfer",
|
1991
|
+
});
|
1552
1992
|
|
1553
1993
|
try {
|
1554
1994
|
// Fetch all memberships for this team
|
1555
1995
|
const memberships = await this.fetchAllMemberships(teamId);
|
1556
|
-
|
1996
|
+
|
1557
1997
|
if (memberships.length === 0) {
|
1558
|
-
MessageFormatter.info(`No memberships found for team ${teamId}`, {
|
1998
|
+
MessageFormatter.info(`No memberships found for team ${teamId}`, {
|
1999
|
+
prefix: "Transfer",
|
2000
|
+
});
|
1559
2001
|
return;
|
1560
2002
|
}
|
1561
2003
|
|
1562
|
-
MessageFormatter.info(
|
2004
|
+
MessageFormatter.info(
|
2005
|
+
`Found ${memberships.length} memberships for team ${teamId}`,
|
2006
|
+
{ prefix: "Transfer" }
|
2007
|
+
);
|
1563
2008
|
|
1564
2009
|
let totalTransferred = 0;
|
1565
2010
|
|
1566
2011
|
// Transfer memberships with rate limiting
|
1567
|
-
const transferTasks = memberships.map(membership =>
|
1568
|
-
this.userLimit(async () => {
|
2012
|
+
const transferTasks = memberships.map((membership) =>
|
2013
|
+
this.userLimit(async () => {
|
2014
|
+
// Use userLimit for team operations (more sensitive)
|
1569
2015
|
try {
|
1570
2016
|
// Check if membership already exists and compare roles
|
1571
2017
|
let existingMembership: Models.Membership | null = null;
|
1572
2018
|
try {
|
1573
|
-
existingMembership = await this.targetTeams.getMembership(
|
1574
|
-
|
2019
|
+
existingMembership = await this.targetTeams.getMembership(
|
2020
|
+
teamId,
|
2021
|
+
membership.$id
|
2022
|
+
);
|
2023
|
+
|
1575
2024
|
// Compare roles between source and target membership
|
1576
|
-
const sourceRoles = JSON.stringify(
|
1577
|
-
|
1578
|
-
|
2025
|
+
const sourceRoles = JSON.stringify(
|
2026
|
+
membership.roles?.sort() || []
|
2027
|
+
);
|
2028
|
+
const targetRoles = JSON.stringify(
|
2029
|
+
existingMembership.roles?.sort() || []
|
2030
|
+
);
|
2031
|
+
|
1579
2032
|
if (sourceRoles !== targetRoles) {
|
1580
2033
|
MessageFormatter.warning(
|
1581
|
-
`Membership ${membership.$id} exists but has different roles. Source: ${sourceRoles}, Target: ${targetRoles}`,
|
2034
|
+
`Membership ${membership.$id} exists but has different roles. Source: ${sourceRoles}, Target: ${targetRoles}`,
|
1582
2035
|
{ prefix: "Transfer" }
|
1583
2036
|
);
|
1584
|
-
|
2037
|
+
|
1585
2038
|
// Update membership roles to match source
|
1586
2039
|
try {
|
1587
2040
|
await this.targetTeams.updateMembership(
|
@@ -1589,16 +2042,24 @@ export class ComprehensiveTransfer {
|
|
1589
2042
|
membership.$id,
|
1590
2043
|
membership.roles
|
1591
2044
|
);
|
1592
|
-
MessageFormatter.success(
|
2045
|
+
MessageFormatter.success(
|
2046
|
+
`Updated membership ${membership.$id} roles to match source`,
|
2047
|
+
{ prefix: "Transfer" }
|
2048
|
+
);
|
1593
2049
|
} catch (updateError) {
|
1594
2050
|
MessageFormatter.error(
|
1595
|
-
`Failed to update roles for membership ${membership.$id}`,
|
1596
|
-
updateError instanceof Error
|
2051
|
+
`Failed to update roles for membership ${membership.$id}`,
|
2052
|
+
updateError instanceof Error
|
2053
|
+
? updateError
|
2054
|
+
: new Error(String(updateError)),
|
1597
2055
|
{ prefix: "Transfer" }
|
1598
2056
|
);
|
1599
2057
|
}
|
1600
2058
|
} else {
|
1601
|
-
MessageFormatter.info(
|
2059
|
+
MessageFormatter.info(
|
2060
|
+
`Membership ${membership.$id} already exists with matching roles, skipping`,
|
2061
|
+
{ prefix: "Transfer" }
|
2062
|
+
);
|
1602
2063
|
}
|
1603
2064
|
return;
|
1604
2065
|
} catch (error) {
|
@@ -1610,7 +2071,10 @@ export class ComprehensiveTransfer {
|
|
1610
2071
|
try {
|
1611
2072
|
userData = await this.targetUsers.get(membership.userId);
|
1612
2073
|
} catch (error) {
|
1613
|
-
MessageFormatter.warning(
|
2074
|
+
MessageFormatter.warning(
|
2075
|
+
`User ${membership.userId} not found in target, membership ${membership.$id} may fail`,
|
2076
|
+
{ prefix: "Transfer" }
|
2077
|
+
);
|
1614
2078
|
}
|
1615
2079
|
|
1616
2080
|
// Create membership using the comprehensive user data
|
@@ -1627,38 +2091,87 @@ export class ComprehensiveTransfer {
|
|
1627
2091
|
);
|
1628
2092
|
|
1629
2093
|
totalTransferred++;
|
1630
|
-
MessageFormatter.success(
|
2094
|
+
MessageFormatter.success(
|
2095
|
+
`Transferred membership ${membership.$id} for user ${
|
2096
|
+
userData?.name || membership.userName
|
2097
|
+
}`,
|
2098
|
+
{ prefix: "Transfer" }
|
2099
|
+
);
|
1631
2100
|
} catch (error) {
|
1632
|
-
MessageFormatter.error(
|
2101
|
+
MessageFormatter.error(
|
2102
|
+
`Failed to transfer membership ${membership.$id}`,
|
2103
|
+
error instanceof Error ? error : new Error(String(error)),
|
2104
|
+
{ prefix: "Transfer" }
|
2105
|
+
);
|
1633
2106
|
}
|
1634
2107
|
})
|
1635
2108
|
);
|
1636
2109
|
|
1637
2110
|
await Promise.all(transferTasks);
|
1638
|
-
MessageFormatter.info(
|
2111
|
+
MessageFormatter.info(
|
2112
|
+
`Transferred ${totalTransferred} memberships for team ${teamId}`,
|
2113
|
+
{ prefix: "Transfer" }
|
2114
|
+
);
|
1639
2115
|
} catch (error) {
|
1640
|
-
MessageFormatter.error(
|
2116
|
+
MessageFormatter.error(
|
2117
|
+
`Failed to transfer memberships for team ${teamId}`,
|
2118
|
+
error instanceof Error ? error : new Error(String(error)),
|
2119
|
+
{ prefix: "Transfer" }
|
2120
|
+
);
|
1641
2121
|
}
|
1642
2122
|
}
|
1643
2123
|
|
1644
2124
|
private printSummary(): void {
|
1645
2125
|
const duration = Math.round((Date.now() - this.startTime) / 1000);
|
1646
|
-
|
1647
|
-
MessageFormatter.info("=== COMPREHENSIVE TRANSFER SUMMARY ===", {
|
2126
|
+
|
2127
|
+
MessageFormatter.info("=== COMPREHENSIVE TRANSFER SUMMARY ===", {
|
2128
|
+
prefix: "Transfer",
|
2129
|
+
});
|
1648
2130
|
MessageFormatter.info(`Total Time: ${duration}s`, { prefix: "Transfer" });
|
1649
|
-
MessageFormatter.info(
|
1650
|
-
|
1651
|
-
|
1652
|
-
|
1653
|
-
MessageFormatter.info(
|
1654
|
-
|
1655
|
-
|
1656
|
-
|
1657
|
-
|
2131
|
+
MessageFormatter.info(
|
2132
|
+
`Users: ${this.results.users.transferred} transferred, ${this.results.users.skipped} skipped, ${this.results.users.failed} failed`,
|
2133
|
+
{ prefix: "Transfer" }
|
2134
|
+
);
|
2135
|
+
MessageFormatter.info(
|
2136
|
+
`Teams: ${this.results.teams.transferred} transferred, ${this.results.teams.skipped} skipped, ${this.results.teams.failed} failed`,
|
2137
|
+
{ prefix: "Transfer" }
|
2138
|
+
);
|
2139
|
+
MessageFormatter.info(
|
2140
|
+
`Databases: ${this.results.databases.transferred} transferred, ${this.results.databases.skipped} skipped, ${this.results.databases.failed} failed`,
|
2141
|
+
{ prefix: "Transfer" }
|
2142
|
+
);
|
2143
|
+
MessageFormatter.info(
|
2144
|
+
`Buckets: ${this.results.buckets.transferred} transferred, ${this.results.buckets.skipped} skipped, ${this.results.buckets.failed} failed`,
|
2145
|
+
{ prefix: "Transfer" }
|
2146
|
+
);
|
2147
|
+
MessageFormatter.info(
|
2148
|
+
`Functions: ${this.results.functions.transferred} transferred, ${this.results.functions.skipped} skipped, ${this.results.functions.failed} failed`,
|
2149
|
+
{ prefix: "Transfer" }
|
2150
|
+
);
|
2151
|
+
|
2152
|
+
const totalTransferred =
|
2153
|
+
this.results.users.transferred +
|
2154
|
+
this.results.teams.transferred +
|
2155
|
+
this.results.databases.transferred +
|
2156
|
+
this.results.buckets.transferred +
|
2157
|
+
this.results.functions.transferred;
|
2158
|
+
const totalFailed =
|
2159
|
+
this.results.users.failed +
|
2160
|
+
this.results.teams.failed +
|
2161
|
+
this.results.databases.failed +
|
2162
|
+
this.results.buckets.failed +
|
2163
|
+
this.results.functions.failed;
|
2164
|
+
|
1658
2165
|
if (totalFailed === 0) {
|
1659
|
-
MessageFormatter.success(
|
2166
|
+
MessageFormatter.success(
|
2167
|
+
`All ${totalTransferred} items transferred successfully!`,
|
2168
|
+
{ prefix: "Transfer" }
|
2169
|
+
);
|
1660
2170
|
} else {
|
1661
|
-
MessageFormatter.warning(
|
2171
|
+
MessageFormatter.warning(
|
2172
|
+
`${totalTransferred} items transferred, ${totalFailed} failed`,
|
2173
|
+
{ prefix: "Transfer" }
|
2174
|
+
);
|
1662
2175
|
}
|
1663
2176
|
}
|
1664
|
-
}
|
2177
|
+
}
|