appwrite-utils-cli 0.9.983 → 0.9.990
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/main.js +58 -44
- package/dist/migrations/dataLoader.js +44 -8
- package/dist/migrations/importController.js +30 -7
- package/dist/migrations/transfer.d.ts +2 -2
- package/dist/migrations/transfer.js +14 -2
- package/dist/utilsController.d.ts +2 -4
- package/dist/utilsController.js +64 -28
- package/package.json +1 -1
- package/src/main.ts +80 -55
- package/src/migrations/dataLoader.ts +65 -25
- package/src/migrations/importController.ts +46 -19
- package/src/migrations/transfer.ts +18 -8
- package/src/utilsController.ts +88 -55
- package/zlogs/album.json +0 -4
- package/zlogs/announcements.json +0 -397
- package/zlogs/announcementscomments.json +0 -36
- package/zlogs/articles.json +0 -138
- package/zlogs/articlescomments.json +0 -4
- package/zlogs/artist.json +0 -4
- package/zlogs/businesscategories.json +0 -7097
- package/zlogs/contacts.json +0 -517063
- package/zlogs/contactscouncils.json +0 -61905
- package/zlogs/contactssociallinks.json +0 -13776
- package/zlogs/councils.json +0 -5076
- package/zlogs/documents.json +0 -917
- package/zlogs/emails.json +0 -4
- package/zlogs/events.json +0 -132625
- package/zlogs/genre.json +0 -4
- package/zlogs/knowledgebase.json +0 -333
- package/zlogs/knowledgebasecomments.json +0 -4
- package/zlogs/linkcategories.json +0 -180
- package/zlogs/links.json +0 -4364
- package/zlogs/memberrequests.json +0 -83
- package/zlogs/memberrequestscomments.json +0 -65
- package/zlogs/mergedUserMap.json +0 -1
- package/zlogs/oldIdToNewIdPerCollectionMap.json +0 -1
- package/zlogs/playlist.json +0 -4
- package/zlogs/regions.json +0 -145
- package/zlogs/song.json +0 -4
- package/zlogs/testimonials.json +0 -335
- package/zlogs/useractivity.json +0 -4
- package/zlogs/userdata.json +0 -4
- package/zlogs/users.json +0 -4
@@ -1086,6 +1086,7 @@ export class DataLoader {
|
|
1086
1086
|
}
|
1087
1087
|
}
|
1088
1088
|
// Update the attribute mappings with any actions that need to be performed post-import
|
1089
|
+
// We added the basePath to get the folder from the filePath
|
1089
1090
|
const mappingsWithActions = this.getAttributeMappingsWithActions(
|
1090
1091
|
importDef.attributeMappings,
|
1091
1092
|
context,
|
@@ -1247,6 +1248,7 @@ export class DataLoader {
|
|
1247
1248
|
continue;
|
1248
1249
|
}
|
1249
1250
|
// Update the attribute mappings with any actions that need to be performed post-import
|
1251
|
+
// We added the basePath to get the folder from the filePath
|
1250
1252
|
const mappingsWithActions = this.getAttributeMappingsWithActions(
|
1251
1253
|
importDef.attributeMappings,
|
1252
1254
|
context,
|
@@ -1462,6 +1464,7 @@ export class DataLoader {
|
|
1462
1464
|
}
|
1463
1465
|
|
1464
1466
|
// Update the attribute mappings with any actions that need to be performed post-import
|
1467
|
+
// We added the basePath to get the folder from the filePath
|
1465
1468
|
const mappingsWithActions = this.getAttributeMappingsWithActions(
|
1466
1469
|
importDef.attributeMappings,
|
1467
1470
|
context,
|
@@ -1480,36 +1483,41 @@ export class DataLoader {
|
|
1480
1483
|
transformedData
|
1481
1484
|
);
|
1482
1485
|
itemDataToUpdate.context = context;
|
1483
|
-
|
1486
|
+
|
1484
1487
|
// Fix: Ensure we properly merge the attribute mappings and their actions
|
1485
|
-
const mergedAttributeMappings = newImportDef.attributeMappings.map(
|
1486
|
-
|
1487
|
-
|
1488
|
-
|
1489
|
-
|
1490
|
-
|
1491
|
-
|
1492
|
-
|
1493
|
-
...
|
1494
|
-
|
1495
|
-
|
1496
|
-
|
1497
|
-
|
1498
|
-
|
1488
|
+
const mergedAttributeMappings = newImportDef.attributeMappings.map(
|
1489
|
+
(newMapping) => {
|
1490
|
+
const existingMapping =
|
1491
|
+
itemDataToUpdate.importDef?.attributeMappings.find(
|
1492
|
+
(m) => m.targetKey === newMapping.targetKey
|
1493
|
+
);
|
1494
|
+
|
1495
|
+
return {
|
1496
|
+
...newMapping,
|
1497
|
+
postImportActions: [
|
1498
|
+
...(existingMapping?.postImportActions || []),
|
1499
|
+
...(newMapping.postImportActions || []),
|
1500
|
+
],
|
1501
|
+
};
|
1502
|
+
}
|
1503
|
+
);
|
1504
|
+
|
1499
1505
|
itemDataToUpdate.importDef = {
|
1500
1506
|
...newImportDef,
|
1501
|
-
attributeMappings: mergedAttributeMappings
|
1507
|
+
attributeMappings: mergedAttributeMappings,
|
1502
1508
|
};
|
1503
|
-
|
1509
|
+
|
1504
1510
|
// Debug logging
|
1505
|
-
if (
|
1511
|
+
if (
|
1512
|
+
mergedAttributeMappings.some((m) => m.postImportActions?.length > 0)
|
1513
|
+
) {
|
1506
1514
|
logger.info(
|
1507
1515
|
`Post-import actions for ${collection.name}: ${JSON.stringify(
|
1508
1516
|
mergedAttributeMappings
|
1509
|
-
.filter(m => m.postImportActions?.length > 0)
|
1510
|
-
.map(m => ({
|
1517
|
+
.filter((m) => m.postImportActions?.length > 0)
|
1518
|
+
.map((m) => ({
|
1511
1519
|
targetKey: m.targetKey,
|
1512
|
-
actions: m.postImportActions
|
1520
|
+
actions: m.postImportActions,
|
1513
1521
|
})),
|
1514
1522
|
null,
|
1515
1523
|
2
|
@@ -1614,10 +1622,42 @@ export class DataLoader {
|
|
1614
1622
|
);
|
1615
1623
|
// Ensure the file path is absolute if it doesn't start with "http"
|
1616
1624
|
if (!mappingFilePath.toLowerCase().startsWith("http")) {
|
1617
|
-
|
1618
|
-
|
1619
|
-
|
1620
|
-
|
1625
|
+
// First try the direct path
|
1626
|
+
let fullPath = path.resolve(this.appwriteFolderPath, mappingFilePath);
|
1627
|
+
|
1628
|
+
// If file doesn't exist, search in subdirectories
|
1629
|
+
if (!fs.existsSync(fullPath)) {
|
1630
|
+
const findFileInDir = (dir: string): string | null => {
|
1631
|
+
const files = fs.readdirSync(dir);
|
1632
|
+
|
1633
|
+
for (const file of files) {
|
1634
|
+
const filePath = path.join(dir, file);
|
1635
|
+
const stat = fs.statSync(filePath);
|
1636
|
+
|
1637
|
+
if (stat.isDirectory()) {
|
1638
|
+
// Recursively search subdirectories
|
1639
|
+
const found = findFileInDir(filePath);
|
1640
|
+
if (found) return found;
|
1641
|
+
} else if (file === path.basename(mappingFilePath)) {
|
1642
|
+
return filePath;
|
1643
|
+
}
|
1644
|
+
}
|
1645
|
+
return null;
|
1646
|
+
};
|
1647
|
+
|
1648
|
+
const foundPath = findFileInDir(this.appwriteFolderPath);
|
1649
|
+
if (foundPath) {
|
1650
|
+
mappingFilePath = foundPath;
|
1651
|
+
} else {
|
1652
|
+
logger.warn(
|
1653
|
+
`File not found in any subdirectory: ${mappingFilePath}`
|
1654
|
+
);
|
1655
|
+
// Keep the original resolved path as fallback
|
1656
|
+
mappingFilePath = fullPath;
|
1657
|
+
}
|
1658
|
+
} else {
|
1659
|
+
mappingFilePath = fullPath;
|
1660
|
+
}
|
1621
1661
|
}
|
1622
1662
|
// Define the after-import action to create a file and update the field
|
1623
1663
|
const afterImportAction = {
|
@@ -119,27 +119,54 @@ export class ImportController {
|
|
119
119
|
updatedDb: Models.Database,
|
120
120
|
targetDb: Models.Database
|
121
121
|
) {
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
const updatedDbConfig = this.config.databases.find(
|
130
|
-
(db) => db.$id === updatedDb.$id
|
131
|
-
);
|
132
|
-
const targetDbConfig = this.config.databases.find(
|
133
|
-
(db) => db.$id === targetDb.$id
|
134
|
-
);
|
122
|
+
if (this.database) {
|
123
|
+
await transferDatabaseLocalToLocal(
|
124
|
+
this.database,
|
125
|
+
updatedDb.$id,
|
126
|
+
targetDb.$id
|
127
|
+
);
|
128
|
+
}
|
135
129
|
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
130
|
+
if (this.storage) {
|
131
|
+
// Find the corresponding database configs
|
132
|
+
const updatedDbConfig = this.config.databases.find(
|
133
|
+
(db) => db.$id === updatedDb.$id
|
134
|
+
);
|
135
|
+
const targetDbConfig = this.config.databases.find(
|
136
|
+
(db) => db.$id === targetDb.$id
|
142
137
|
);
|
138
|
+
|
139
|
+
const allBuckets = await this.storage.listBuckets([Query.limit(1000)]);
|
140
|
+
const bucketsWithDbIdInThem = allBuckets.buckets.filter(bucket => bucket.name.toLowerCase().includes(updatedDb.$id.toLowerCase()));
|
141
|
+
const configuredUpdatedBucketId = `${this.config.documentBucketId}_${updatedDb.$id.toLowerCase().trim().replace(" ", "")}`;
|
142
|
+
const configuredTargetBucketId = `${this.config.documentBucketId}_${targetDb.$id.toLowerCase().trim().replace(" ", "")}`;
|
143
|
+
|
144
|
+
let sourceBucketId: string | undefined;
|
145
|
+
let targetBucketId: string | undefined;
|
146
|
+
|
147
|
+
if (bucketsWithDbIdInThem.find(bucket => bucket.$id === configuredUpdatedBucketId)) {
|
148
|
+
sourceBucketId = configuredUpdatedBucketId;
|
149
|
+
} else if (bucketsWithDbIdInThem.find(bucket => bucket.$id === configuredTargetBucketId)) {
|
150
|
+
targetBucketId = configuredTargetBucketId;
|
151
|
+
}
|
152
|
+
|
153
|
+
if (!sourceBucketId) {
|
154
|
+
sourceBucketId = updatedDbConfig?.bucket?.$id ||
|
155
|
+
bucketsWithDbIdInThem[0]?.$id;
|
156
|
+
}
|
157
|
+
|
158
|
+
if (!targetBucketId) {
|
159
|
+
targetBucketId = targetDbConfig?.bucket?.$id ||
|
160
|
+
bucketsWithDbIdInThem[0]?.$id;
|
161
|
+
}
|
162
|
+
|
163
|
+
if (sourceBucketId && targetBucketId) {
|
164
|
+
await transferStorageLocalToLocal(
|
165
|
+
this.storage,
|
166
|
+
sourceBucketId,
|
167
|
+
targetBucketId
|
168
|
+
);
|
169
|
+
}
|
143
170
|
}
|
144
171
|
}
|
145
172
|
|
@@ -13,8 +13,8 @@ import { createOrUpdateAttribute } from "../collections/attributes.js";
|
|
13
13
|
import { parseAttribute } from "appwrite-utils";
|
14
14
|
|
15
15
|
export interface TransferOptions {
|
16
|
-
fromDb: Models.Database;
|
17
|
-
targetDb: Models.Database;
|
16
|
+
fromDb: Models.Database | undefined;
|
17
|
+
targetDb: Models.Database | undefined;
|
18
18
|
isRemote: boolean;
|
19
19
|
collections?: string[];
|
20
20
|
transferEndpoint?: string;
|
@@ -64,15 +64,20 @@ export const transferStorageLocalToLocal = async (
|
|
64
64
|
file.name
|
65
65
|
);
|
66
66
|
console.log(`Creating file: ${file.name}`);
|
67
|
-
|
68
|
-
|
67
|
+
try {
|
68
|
+
await tryAwaitWithRetry(
|
69
|
+
async () =>
|
69
70
|
await storage.createFile(
|
70
71
|
toBucketId,
|
71
72
|
file.$id,
|
72
73
|
fileToCreate,
|
73
74
|
file.$permissions
|
74
75
|
)
|
75
|
-
|
76
|
+
);
|
77
|
+
} catch (error: any) {
|
78
|
+
// File already exists, so we can skip it
|
79
|
+
continue;
|
80
|
+
}
|
76
81
|
numberOfFiles++;
|
77
82
|
}
|
78
83
|
} else {
|
@@ -167,15 +172,20 @@ export const transferStorageLocalToRemote = async (
|
|
167
172
|
new Uint8Array(fileData),
|
168
173
|
file.name
|
169
174
|
);
|
170
|
-
|
171
|
-
|
175
|
+
try {
|
176
|
+
await tryAwaitWithRetry(
|
177
|
+
async () =>
|
172
178
|
await remoteStorage.createFile(
|
173
179
|
toBucketId,
|
174
180
|
file.$id,
|
175
181
|
fileToCreate,
|
176
182
|
file.$permissions
|
177
183
|
)
|
178
|
-
|
184
|
+
);
|
185
|
+
} catch (error: any) {
|
186
|
+
// File already exists, so we can skip it
|
187
|
+
continue;
|
188
|
+
}
|
179
189
|
numberOfFiles++;
|
180
190
|
}
|
181
191
|
console.log(
|
package/src/utilsController.ts
CHANGED
@@ -145,6 +145,7 @@ export class UtilsController {
|
|
145
145
|
async getDatabasesByIds(ids: string[]) {
|
146
146
|
await this.init();
|
147
147
|
if (!this.database) throw new Error("Database not initialized");
|
148
|
+
if (ids.length === 0) return [];
|
148
149
|
const dbs = await this.database.list([
|
149
150
|
Query.limit(500),
|
150
151
|
Query.equal("$id", ids),
|
@@ -178,10 +179,34 @@ export class UtilsController {
|
|
178
179
|
);
|
179
180
|
}
|
180
181
|
|
181
|
-
async wipeDatabase(database: Models.Database) {
|
182
|
+
async wipeDatabase(database: Models.Database, wipeBucket: boolean = false) {
|
182
183
|
await this.init();
|
183
184
|
if (!this.database) throw new Error("Database not initialized");
|
184
|
-
|
185
|
+
await wipeDatabase(this.database, database.$id);
|
186
|
+
if (wipeBucket) {
|
187
|
+
await this.wipeBucketFromDatabase(database);
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
async wipeBucketFromDatabase(database: Models.Database) {
|
192
|
+
// Check configured bucket in database config
|
193
|
+
const configuredBucket = this.config?.databases?.find(db => db.$id === database.$id)?.bucket;
|
194
|
+
if (configuredBucket?.$id) {
|
195
|
+
await this.wipeDocumentStorage(configuredBucket.$id);
|
196
|
+
}
|
197
|
+
|
198
|
+
// Also check for document bucket ID pattern
|
199
|
+
if (this.config?.documentBucketId) {
|
200
|
+
const documentBucketId = `${this.config.documentBucketId}_${database.$id.toLowerCase().trim().replace(/\s+/g, "")}`;
|
201
|
+
try {
|
202
|
+
await this.wipeDocumentStorage(documentBucketId);
|
203
|
+
} catch (error: any) {
|
204
|
+
// Ignore if bucket doesn't exist
|
205
|
+
if (error?.type !== 'storage_bucket_not_found') {
|
206
|
+
throw error;
|
207
|
+
}
|
208
|
+
}
|
209
|
+
}
|
185
210
|
}
|
186
211
|
|
187
212
|
async wipeCollection(database: Models.Database, collection: Models.Collection) {
|
@@ -289,16 +314,11 @@ export class UtilsController {
|
|
289
314
|
}
|
290
315
|
|
291
316
|
async transferData(options: TransferOptions): Promise<void> {
|
292
|
-
|
293
|
-
throw new Error(
|
294
|
-
"Database is not initialized, is the config file correct & created?"
|
295
|
-
);
|
296
|
-
}
|
297
|
-
|
317
|
+
// Remove database requirement check
|
298
318
|
let sourceClient = this.database;
|
299
|
-
let targetClient: Databases;
|
300
|
-
let sourceDatabases: Models.Database[];
|
301
|
-
let targetDatabases: Models.Database[];
|
319
|
+
let targetClient: Databases | undefined;
|
320
|
+
let sourceDatabases: Models.Database[] = [];
|
321
|
+
let targetDatabases: Models.Database[] = [];
|
302
322
|
|
303
323
|
if (options.isRemote) {
|
304
324
|
if (
|
@@ -314,63 +334,76 @@ export class UtilsController {
|
|
314
334
|
options.transferProject,
|
315
335
|
options.transferKey
|
316
336
|
);
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
337
|
+
|
338
|
+
if (this.database) {
|
339
|
+
targetClient = new Databases(remoteClient);
|
340
|
+
sourceDatabases = await fetchAllDatabases(sourceClient!);
|
341
|
+
targetDatabases = await fetchAllDatabases(targetClient);
|
342
|
+
}
|
343
|
+
} else if (this.database) {
|
322
344
|
targetClient = sourceClient;
|
323
|
-
sourceDatabases = targetDatabases = await fetchAllDatabases(sourceClient);
|
345
|
+
sourceDatabases = targetDatabases = await fetchAllDatabases(sourceClient!);
|
324
346
|
}
|
325
347
|
|
326
|
-
//
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
if (!fromDb || !targetDb) {
|
333
|
-
throw new Error("Source or target database not found");
|
334
|
-
}
|
335
|
-
|
336
|
-
if (options.isRemote) {
|
337
|
-
// Remote transfer
|
338
|
-
await transferDatabaseLocalToRemote(
|
339
|
-
sourceClient,
|
340
|
-
options.transferEndpoint!,
|
341
|
-
options.transferProject!,
|
342
|
-
options.transferKey!,
|
343
|
-
fromDb.$id,
|
344
|
-
targetDb.$id
|
348
|
+
// Only validate databases if they're provided in options
|
349
|
+
if (options.fromDb && options.targetDb) {
|
350
|
+
const fromDb = sourceDatabases.find((db) => db.$id === options.fromDb!.$id);
|
351
|
+
const targetDb = targetDatabases.find(
|
352
|
+
(db) => db.$id === options.targetDb!.$id
|
345
353
|
);
|
346
354
|
|
347
|
-
if (
|
348
|
-
|
349
|
-
|
355
|
+
if (!fromDb || !targetDb) {
|
356
|
+
throw new Error("Source or target database not found");
|
357
|
+
}
|
358
|
+
|
359
|
+
if (options.isRemote && targetClient) {
|
360
|
+
await transferDatabaseLocalToRemote(
|
361
|
+
sourceClient!,
|
350
362
|
options.transferEndpoint!,
|
351
363
|
options.transferProject!,
|
352
364
|
options.transferKey!,
|
353
|
-
|
354
|
-
|
365
|
+
fromDb.$id,
|
366
|
+
targetDb.$id
|
367
|
+
);
|
368
|
+
} else if (targetClient) {
|
369
|
+
await transferDatabaseLocalToLocal(
|
370
|
+
sourceClient!,
|
371
|
+
fromDb.$id,
|
372
|
+
targetDb.$id
|
355
373
|
);
|
356
374
|
}
|
357
|
-
}
|
358
|
-
// Local transfer
|
359
|
-
await transferDatabaseLocalToLocal(
|
360
|
-
sourceClient,
|
361
|
-
fromDb.$id,
|
362
|
-
targetDb.$id
|
363
|
-
);
|
375
|
+
}
|
364
376
|
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
options.
|
370
|
-
|
377
|
+
// Handle storage transfer separately
|
378
|
+
if (this.storage && (options.sourceBucket || options.fromDb)) {
|
379
|
+
const sourceBucketId = options.sourceBucket?.$id ||
|
380
|
+
(options.fromDb && this.config?.documentBucketId &&
|
381
|
+
`${this.config.documentBucketId}_${options.fromDb.$id.toLowerCase().trim().replace(" ", "")}`);
|
382
|
+
|
383
|
+
const targetBucketId = options.targetBucket?.$id ||
|
384
|
+
(options.targetDb && this.config?.documentBucketId &&
|
385
|
+
`${this.config.documentBucketId}_${options.targetDb.$id.toLowerCase().trim().replace(" ", "")}`);
|
386
|
+
|
387
|
+
if (sourceBucketId && targetBucketId) {
|
388
|
+
if (options.isRemote) {
|
389
|
+
await transferStorageLocalToRemote(
|
390
|
+
this.storage,
|
391
|
+
options.transferEndpoint!,
|
392
|
+
options.transferProject!,
|
393
|
+
options.transferKey!,
|
394
|
+
sourceBucketId,
|
395
|
+
targetBucketId
|
396
|
+
);
|
397
|
+
} else {
|
398
|
+
await transferStorageLocalToLocal(
|
399
|
+
this.storage,
|
400
|
+
sourceBucketId,
|
401
|
+
targetBucketId
|
402
|
+
);
|
403
|
+
}
|
371
404
|
}
|
372
405
|
}
|
373
406
|
|
374
|
-
console.log("
|
407
|
+
console.log("Transfer completed");
|
375
408
|
}
|
376
409
|
}
|
package/zlogs/album.json
DELETED