appwrite-utils-cli 0.0.47 → 0.0.49
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 +54 -6
- package/dist/main.js +23 -0
- package/dist/migrations/collections.d.ts +6 -0
- package/dist/migrations/collections.js +133 -2
- package/dist/migrations/dataLoader.d.ts +11 -1
- package/dist/migrations/dataLoader.js +49 -32
- package/dist/migrations/databases.d.ts +10 -0
- package/dist/migrations/databases.js +114 -2
- package/dist/migrations/importController.d.ts +1 -0
- package/dist/migrations/importController.js +23 -5
- package/dist/migrations/storage.d.ts +2 -0
- package/dist/migrations/storage.js +68 -1
- package/dist/migrations/users.d.ts +1 -0
- package/dist/migrations/users.js +46 -1
- package/dist/utils/helperFunctions.d.ts +2 -1
- package/dist/utils/helperFunctions.js +7 -1
- package/dist/utilsController.d.ts +11 -0
- package/dist/utilsController.js +54 -0
- package/package.json +2 -2
- package/src/main.ts +53 -0
- package/src/migrations/collections.ts +222 -2
- package/src/migrations/dataLoader.ts +82 -35
- package/src/migrations/databases.ts +221 -2
- package/src/migrations/importController.ts +40 -11
- package/src/migrations/storage.ts +124 -1
- package/src/migrations/users.ts +66 -1
- package/src/utils/helperFunctions.ts +17 -1
- package/src/utilsController.ts +140 -0
|
@@ -393,7 +393,7 @@ export class DataLoader {
|
|
|
393
393
|
}
|
|
394
394
|
}
|
|
395
395
|
console.log("Running update references");
|
|
396
|
-
|
|
396
|
+
this.dealWithMergedUsers();
|
|
397
397
|
this.updateOldReferencesForNew();
|
|
398
398
|
console.log("Done running update references");
|
|
399
399
|
}
|
|
@@ -1164,19 +1164,18 @@ export class DataLoader {
|
|
|
1164
1164
|
* @param collection - The collection configuration.
|
|
1165
1165
|
* @param importDef - The import definition containing the attribute mappings and other relevant info.
|
|
1166
1166
|
*/
|
|
1167
|
-
prepareUpdateData(
|
|
1167
|
+
async prepareUpdateData(
|
|
1168
1168
|
db: ConfigDatabase,
|
|
1169
1169
|
collection: CollectionCreate,
|
|
1170
1170
|
importDef: ImportDef
|
|
1171
1171
|
) {
|
|
1172
|
-
// Retrieve the current collection data and old-to-new ID map from the import map
|
|
1173
1172
|
const currentData = this.importMap.get(
|
|
1174
1173
|
this.getCollectionKey(collection.name)
|
|
1175
1174
|
);
|
|
1176
1175
|
const oldIdToNewIdMap = this.oldIdToNewIdPerCollectionMap.get(
|
|
1177
1176
|
this.getCollectionKey(collection.name)
|
|
1178
1177
|
);
|
|
1179
|
-
|
|
1178
|
+
|
|
1180
1179
|
if (
|
|
1181
1180
|
!(currentData?.data && currentData?.data.length > 0) &&
|
|
1182
1181
|
!oldIdToNewIdMap
|
|
@@ -1186,7 +1185,7 @@ export class DataLoader {
|
|
|
1186
1185
|
);
|
|
1187
1186
|
return;
|
|
1188
1187
|
}
|
|
1189
|
-
|
|
1188
|
+
|
|
1190
1189
|
const rawData = this.loadData(importDef);
|
|
1191
1190
|
const operationId = this.collectionImportOperations.get(
|
|
1192
1191
|
this.getCollectionKey(collection.name)
|
|
@@ -1196,31 +1195,75 @@ export class DataLoader {
|
|
|
1196
1195
|
`No import operation found for collection ${collection.name}`
|
|
1197
1196
|
);
|
|
1198
1197
|
}
|
|
1198
|
+
|
|
1199
1199
|
for (const item of rawData) {
|
|
1200
|
-
// Transform the item data based on the attribute mappings
|
|
1201
1200
|
let transformedData = this.transformData(
|
|
1202
1201
|
item,
|
|
1203
1202
|
importDef.attributeMappings
|
|
1204
1203
|
);
|
|
1205
1204
|
let newId: string | undefined;
|
|
1206
1205
|
let oldId: string | undefined;
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
) {
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1206
|
+
let itemDataToUpdate: CollectionImportData["data"][number] | undefined;
|
|
1207
|
+
|
|
1208
|
+
// Try to find itemDataToUpdate using updateMapping
|
|
1209
|
+
if (importDef.updateMapping) {
|
|
1210
|
+
console.log(importDef.updateMapping);
|
|
1211
|
+
oldId =
|
|
1212
|
+
item[importDef.updateMapping.originalIdField] ||
|
|
1213
|
+
transformedData[importDef.updateMapping.originalIdField];
|
|
1214
|
+
if (oldId) {
|
|
1215
|
+
itemDataToUpdate = currentData?.data.find(
|
|
1216
|
+
({ context, rawData, finalData }) => {
|
|
1217
|
+
const targetField =
|
|
1218
|
+
importDef.updateMapping!.targetField ??
|
|
1219
|
+
importDef.updateMapping!.originalIdField;
|
|
1220
|
+
|
|
1221
|
+
return (
|
|
1222
|
+
`${context[targetField]}` === `${oldId}` ||
|
|
1223
|
+
`${rawData[targetField]}` === `${oldId}` ||
|
|
1224
|
+
`${finalData[targetField]}` === `${oldId}`
|
|
1225
|
+
);
|
|
1226
|
+
}
|
|
1227
|
+
);
|
|
1228
|
+
|
|
1229
|
+
if (itemDataToUpdate) {
|
|
1230
|
+
newId =
|
|
1231
|
+
itemDataToUpdate.finalData.docId ||
|
|
1232
|
+
itemDataToUpdate.context.docId;
|
|
1233
|
+
}
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// If updateMapping is not defined or did not find the item, use primaryKeyField
|
|
1238
|
+
if (!itemDataToUpdate && importDef.primaryKeyField) {
|
|
1239
|
+
oldId =
|
|
1240
|
+
item[importDef.primaryKeyField] ||
|
|
1241
|
+
transformedData[importDef.primaryKeyField];
|
|
1242
|
+
if (oldId) {
|
|
1243
|
+
newId = oldIdToNewIdMap?.get(`${oldId}`);
|
|
1244
|
+
if (
|
|
1245
|
+
!newId &&
|
|
1246
|
+
this.getCollectionKey(this.config.usersCollectionName) ===
|
|
1247
|
+
this.getCollectionKey(collection.name)
|
|
1248
|
+
) {
|
|
1249
|
+
for (const [key, value] of this.mergedUserMap.entries()) {
|
|
1250
|
+
if (value.includes(`${oldId}`)) {
|
|
1251
|
+
newId = key;
|
|
1252
|
+
break;
|
|
1253
|
+
}
|
|
1220
1254
|
}
|
|
1221
1255
|
}
|
|
1222
1256
|
}
|
|
1223
|
-
|
|
1257
|
+
|
|
1258
|
+
if (oldId && !itemDataToUpdate) {
|
|
1259
|
+
itemDataToUpdate = currentData?.data.find(
|
|
1260
|
+
(data) =>
|
|
1261
|
+
`${data.context[importDef.primaryKeyField]}` === `${oldId}`
|
|
1262
|
+
);
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
if (!oldId) {
|
|
1224
1267
|
logger.error(
|
|
1225
1268
|
`No old ID found (to update another document with) in prepareUpdateData for ${
|
|
1226
1269
|
collection.name
|
|
@@ -1228,15 +1271,10 @@ export class DataLoader {
|
|
|
1228
1271
|
);
|
|
1229
1272
|
continue;
|
|
1230
1273
|
}
|
|
1231
|
-
|
|
1232
|
-
.get(this.getCollectionKey(collection.name))
|
|
1233
|
-
?.data.find(
|
|
1234
|
-
(data) => `${data.context[importDef.primaryKeyField]}` === `${oldId}`
|
|
1235
|
-
);
|
|
1236
|
-
// Log an error and continue to the next item if no new ID is found
|
|
1274
|
+
|
|
1237
1275
|
if (!newId && !itemDataToUpdate) {
|
|
1238
1276
|
logger.error(
|
|
1239
|
-
`No new id found for collection ${
|
|
1277
|
+
`No new id && no data found for collection ${
|
|
1240
1278
|
collection.name
|
|
1241
1279
|
} for updateDef ${JSON.stringify(
|
|
1242
1280
|
item,
|
|
@@ -1246,7 +1284,8 @@ export class DataLoader {
|
|
|
1246
1284
|
);
|
|
1247
1285
|
continue;
|
|
1248
1286
|
} else if (itemDataToUpdate) {
|
|
1249
|
-
newId =
|
|
1287
|
+
newId =
|
|
1288
|
+
itemDataToUpdate.finalData.docId || itemDataToUpdate.context.docId;
|
|
1250
1289
|
if (!newId) {
|
|
1251
1290
|
logger.error(
|
|
1252
1291
|
`No new id found for collection ${
|
|
@@ -1255,11 +1294,16 @@ export class DataLoader {
|
|
|
1255
1294
|
item,
|
|
1256
1295
|
null,
|
|
1257
1296
|
2
|
|
1297
|
+
)} but has itemDataToUpdate ${JSON.stringify(
|
|
1298
|
+
itemDataToUpdate,
|
|
1299
|
+
null,
|
|
1300
|
+
2
|
|
1258
1301
|
)} but it says it's supposed to have one...`
|
|
1259
1302
|
);
|
|
1260
1303
|
continue;
|
|
1261
1304
|
}
|
|
1262
1305
|
}
|
|
1306
|
+
|
|
1263
1307
|
if (!itemDataToUpdate || !newId) {
|
|
1264
1308
|
logger.error(
|
|
1265
1309
|
`No data or ID (docId) found for collection ${
|
|
@@ -1272,49 +1316,51 @@ export class DataLoader {
|
|
|
1272
1316
|
);
|
|
1273
1317
|
continue;
|
|
1274
1318
|
}
|
|
1319
|
+
|
|
1275
1320
|
transformedData = this.mergeObjects(
|
|
1276
1321
|
itemDataToUpdate.finalData,
|
|
1277
1322
|
transformedData
|
|
1278
1323
|
);
|
|
1324
|
+
|
|
1279
1325
|
// Create a context object for the item, including the new ID and transformed data
|
|
1280
1326
|
let context = this.createContext(db, collection, item, newId);
|
|
1281
|
-
context =
|
|
1327
|
+
context = this.mergeObjects(context, transformedData);
|
|
1328
|
+
|
|
1282
1329
|
// Validate the item before proceeding
|
|
1283
|
-
const isValid = this.importDataActions.validateItem(
|
|
1330
|
+
const isValid = await this.importDataActions.validateItem(
|
|
1284
1331
|
item,
|
|
1285
1332
|
importDef.attributeMappings,
|
|
1286
1333
|
context
|
|
1287
1334
|
);
|
|
1288
|
-
|
|
1335
|
+
|
|
1289
1336
|
if (!isValid) {
|
|
1290
1337
|
logger.info(
|
|
1291
1338
|
`Skipping item: ${JSON.stringify(item, null, 2)} because it's invalid`
|
|
1292
1339
|
);
|
|
1293
1340
|
continue;
|
|
1294
1341
|
}
|
|
1342
|
+
|
|
1295
1343
|
// Update the attribute mappings with any actions that need to be performed post-import
|
|
1296
1344
|
const mappingsWithActions = this.getAttributeMappingsWithActions(
|
|
1297
1345
|
importDef.attributeMappings,
|
|
1298
1346
|
context,
|
|
1299
1347
|
transformedData
|
|
1300
1348
|
);
|
|
1349
|
+
|
|
1301
1350
|
// Update the import definition with the new attribute mappings
|
|
1302
1351
|
const newImportDef = {
|
|
1303
1352
|
...importDef,
|
|
1304
1353
|
attributeMappings: mappingsWithActions,
|
|
1305
1354
|
};
|
|
1306
|
-
|
|
1355
|
+
|
|
1307
1356
|
if (itemDataToUpdate) {
|
|
1308
|
-
// Update the existing item's finalData and context in place
|
|
1309
1357
|
itemDataToUpdate.finalData = this.mergeObjects(
|
|
1310
1358
|
itemDataToUpdate.finalData,
|
|
1311
1359
|
transformedData
|
|
1312
1360
|
);
|
|
1313
1361
|
itemDataToUpdate.context = context;
|
|
1314
1362
|
itemDataToUpdate.importDef = newImportDef;
|
|
1315
|
-
currentData!.data.push(itemDataToUpdate);
|
|
1316
1363
|
} else {
|
|
1317
|
-
// If no existing item matches, then add the new item
|
|
1318
1364
|
currentData!.data.push({
|
|
1319
1365
|
rawData: item,
|
|
1320
1366
|
context: context,
|
|
@@ -1322,6 +1368,7 @@ export class DataLoader {
|
|
|
1322
1368
|
finalData: transformedData,
|
|
1323
1369
|
});
|
|
1324
1370
|
}
|
|
1371
|
+
|
|
1325
1372
|
// Since we're modifying currentData in place, we ensure no duplicates are added
|
|
1326
1373
|
this.importMap.set(this.getCollectionKey(collection.name), currentData!);
|
|
1327
1374
|
}
|
|
@@ -1,5 +1,14 @@
|
|
|
1
|
-
import { Databases, Query, type Models } from "node-appwrite";
|
|
2
|
-
import {
|
|
1
|
+
import { Client, Databases, Query, type Models } from "node-appwrite";
|
|
2
|
+
import {
|
|
3
|
+
getAppwriteClient,
|
|
4
|
+
tryAwaitWithRetry,
|
|
5
|
+
} from "../utils/helperFunctions.js";
|
|
6
|
+
import {
|
|
7
|
+
transferDocumentsBetweenDbsLocalToLocal,
|
|
8
|
+
transferDocumentsBetweenDbsLocalToRemote,
|
|
9
|
+
} from "./collections.js";
|
|
10
|
+
import { createOrUpdateAttribute } from "./attributes.js";
|
|
11
|
+
import { parseAttribute } from "appwrite-utils";
|
|
3
12
|
|
|
4
13
|
export const fetchAllDatabases = async (
|
|
5
14
|
database: Databases
|
|
@@ -26,3 +35,213 @@ export const fetchAllDatabases = async (
|
|
|
26
35
|
}
|
|
27
36
|
return allDatabases;
|
|
28
37
|
};
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Transfers all collections and documents from one local database to another local database.
|
|
41
|
+
*
|
|
42
|
+
* @param {Databases} localDb - The local database instance.
|
|
43
|
+
* @param {string} fromDbId - The ID of the source database.
|
|
44
|
+
* @param {string} targetDbId - The ID of the target database.
|
|
45
|
+
* @return {Promise<void>} A promise that resolves when the transfer is complete.
|
|
46
|
+
*/
|
|
47
|
+
export const transferDatabaseLocalToLocal = async (
|
|
48
|
+
localDb: Databases,
|
|
49
|
+
fromDbId: string,
|
|
50
|
+
targetDbId: string
|
|
51
|
+
) => {
|
|
52
|
+
let lastCollectionId: string | undefined;
|
|
53
|
+
let fromCollections = await tryAwaitWithRetry(
|
|
54
|
+
async () => await localDb.listCollections(fromDbId, [Query.limit(50)])
|
|
55
|
+
);
|
|
56
|
+
const allFromCollections = fromCollections.collections;
|
|
57
|
+
if (fromCollections.collections.length < 50) {
|
|
58
|
+
lastCollectionId = undefined;
|
|
59
|
+
} else {
|
|
60
|
+
lastCollectionId =
|
|
61
|
+
fromCollections.collections[fromCollections.collections.length - 1].$id;
|
|
62
|
+
while (lastCollectionId) {
|
|
63
|
+
const collections = await localDb.listCollections(fromDbId, [
|
|
64
|
+
Query.limit(50),
|
|
65
|
+
Query.cursorAfter(lastCollectionId),
|
|
66
|
+
]);
|
|
67
|
+
allFromCollections.push(...collections.collections);
|
|
68
|
+
if (collections.collections.length < 50) {
|
|
69
|
+
break;
|
|
70
|
+
}
|
|
71
|
+
lastCollectionId =
|
|
72
|
+
collections.collections[collections.collections.length - 1].$id;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
lastCollectionId = undefined;
|
|
76
|
+
let toCollections = await tryAwaitWithRetry(
|
|
77
|
+
async () => await localDb.listCollections(targetDbId, [Query.limit(50)])
|
|
78
|
+
);
|
|
79
|
+
const allToCollections = toCollections.collections;
|
|
80
|
+
if (toCollections.collections.length < 50) {
|
|
81
|
+
} else {
|
|
82
|
+
lastCollectionId =
|
|
83
|
+
toCollections.collections[toCollections.collections.length - 1].$id;
|
|
84
|
+
while (lastCollectionId) {
|
|
85
|
+
const collections = await localDb.listCollections(targetDbId, [
|
|
86
|
+
Query.limit(50),
|
|
87
|
+
Query.cursorAfter(lastCollectionId),
|
|
88
|
+
]);
|
|
89
|
+
allToCollections.push(...collections.collections);
|
|
90
|
+
if (collections.collections.length < 50) {
|
|
91
|
+
lastCollectionId = undefined;
|
|
92
|
+
} else {
|
|
93
|
+
lastCollectionId =
|
|
94
|
+
collections.collections[collections.collections.length - 1].$id;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
for (const collection of allFromCollections) {
|
|
99
|
+
const toCollection = allToCollections.find((c) => c.$id === collection.$id);
|
|
100
|
+
if (toCollection) {
|
|
101
|
+
await transferDocumentsBetweenDbsLocalToLocal(
|
|
102
|
+
localDb,
|
|
103
|
+
fromDbId,
|
|
104
|
+
targetDbId,
|
|
105
|
+
collection.$id,
|
|
106
|
+
toCollection.$id
|
|
107
|
+
);
|
|
108
|
+
} else {
|
|
109
|
+
console.log(
|
|
110
|
+
`Collection ${collection.name} not found in destination database, creating...`
|
|
111
|
+
);
|
|
112
|
+
const newCollection = await tryAwaitWithRetry(
|
|
113
|
+
async () =>
|
|
114
|
+
await localDb.createCollection(
|
|
115
|
+
targetDbId,
|
|
116
|
+
collection.$id,
|
|
117
|
+
collection.name,
|
|
118
|
+
collection.$permissions,
|
|
119
|
+
collection.documentSecurity,
|
|
120
|
+
collection.enabled
|
|
121
|
+
)
|
|
122
|
+
);
|
|
123
|
+
console.log(`Collection ${newCollection.name} created`);
|
|
124
|
+
for (const attribute of collection.attributes) {
|
|
125
|
+
await tryAwaitWithRetry(
|
|
126
|
+
async () =>
|
|
127
|
+
await createOrUpdateAttribute(
|
|
128
|
+
localDb,
|
|
129
|
+
targetDbId,
|
|
130
|
+
newCollection,
|
|
131
|
+
parseAttribute(attribute as any)
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
}
|
|
135
|
+
for (const index of collection.indexes) {
|
|
136
|
+
await tryAwaitWithRetry(
|
|
137
|
+
async () =>
|
|
138
|
+
await localDb.createIndex(
|
|
139
|
+
targetDbId,
|
|
140
|
+
newCollection.$id,
|
|
141
|
+
index.key,
|
|
142
|
+
index.type,
|
|
143
|
+
index.attributes,
|
|
144
|
+
index.orders
|
|
145
|
+
)
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
await transferDocumentsBetweenDbsLocalToLocal(
|
|
149
|
+
localDb,
|
|
150
|
+
fromDbId,
|
|
151
|
+
targetDbId,
|
|
152
|
+
collection.$id,
|
|
153
|
+
newCollection.$id
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
export const transferDatabaseLocalToRemote = async (
|
|
160
|
+
localDb: Databases,
|
|
161
|
+
endpoint: string,
|
|
162
|
+
projectId: string,
|
|
163
|
+
apiKey: string,
|
|
164
|
+
fromDbId: string,
|
|
165
|
+
toDbId: string
|
|
166
|
+
) => {
|
|
167
|
+
const client = getAppwriteClient(endpoint, projectId, apiKey);
|
|
168
|
+
const remoteDb = new Databases(client);
|
|
169
|
+
|
|
170
|
+
let lastCollectionId: string | undefined;
|
|
171
|
+
let fromCollections = await tryAwaitWithRetry(
|
|
172
|
+
async () => await localDb.listCollections(fromDbId, [Query.limit(50)])
|
|
173
|
+
);
|
|
174
|
+
const allFromCollections = fromCollections.collections;
|
|
175
|
+
if (fromCollections.collections.length < 50) {
|
|
176
|
+
} else {
|
|
177
|
+
lastCollectionId =
|
|
178
|
+
fromCollections.collections[fromCollections.collections.length - 1].$id;
|
|
179
|
+
while (lastCollectionId) {
|
|
180
|
+
const collections = await tryAwaitWithRetry(
|
|
181
|
+
async () =>
|
|
182
|
+
await localDb.listCollections(fromDbId, [
|
|
183
|
+
Query.limit(50),
|
|
184
|
+
Query.cursorAfter(lastCollectionId!),
|
|
185
|
+
])
|
|
186
|
+
);
|
|
187
|
+
allFromCollections.push(...collections.collections);
|
|
188
|
+
if (collections.collections.length < 50) {
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
lastCollectionId =
|
|
192
|
+
collections.collections[collections.collections.length - 1].$id;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
for (const collection of allFromCollections) {
|
|
197
|
+
const toCollection = await tryAwaitWithRetry(
|
|
198
|
+
async () =>
|
|
199
|
+
await remoteDb.createCollection(
|
|
200
|
+
toDbId,
|
|
201
|
+
collection.$id,
|
|
202
|
+
collection.name,
|
|
203
|
+
collection.$permissions,
|
|
204
|
+
collection.documentSecurity,
|
|
205
|
+
collection.enabled
|
|
206
|
+
)
|
|
207
|
+
);
|
|
208
|
+
console.log(`Collection ${toCollection.name} created`);
|
|
209
|
+
|
|
210
|
+
for (const attribute of collection.attributes) {
|
|
211
|
+
await tryAwaitWithRetry(
|
|
212
|
+
async () =>
|
|
213
|
+
await createOrUpdateAttribute(
|
|
214
|
+
remoteDb,
|
|
215
|
+
toDbId,
|
|
216
|
+
toCollection,
|
|
217
|
+
parseAttribute(attribute as any)
|
|
218
|
+
)
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
for (const index of collection.indexes) {
|
|
223
|
+
await tryAwaitWithRetry(
|
|
224
|
+
async () =>
|
|
225
|
+
await remoteDb.createIndex(
|
|
226
|
+
toDbId,
|
|
227
|
+
toCollection.$id,
|
|
228
|
+
index.key,
|
|
229
|
+
index.type,
|
|
230
|
+
index.attributes,
|
|
231
|
+
index.orders
|
|
232
|
+
)
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
await transferDocumentsBetweenDbsLocalToRemote(
|
|
237
|
+
localDb,
|
|
238
|
+
endpoint,
|
|
239
|
+
projectId,
|
|
240
|
+
apiKey,
|
|
241
|
+
fromDbId,
|
|
242
|
+
toDbId,
|
|
243
|
+
collection.$id,
|
|
244
|
+
toCollection.$id
|
|
245
|
+
);
|
|
246
|
+
}
|
|
247
|
+
};
|
|
@@ -26,6 +26,9 @@ import {
|
|
|
26
26
|
OperationSchema,
|
|
27
27
|
} from "./backup.js";
|
|
28
28
|
import { DataLoader, type CollectionImportData } from "./dataLoader.js";
|
|
29
|
+
import { transferDocumentsBetweenDbsLocalToLocal } from "./collections.js";
|
|
30
|
+
import { transferDatabaseLocalToLocal } from "./databases.js";
|
|
31
|
+
import { transferStorageLocalToLocal } from "./storage.js";
|
|
29
32
|
|
|
30
33
|
export class ImportController {
|
|
31
34
|
private config: AppwriteConfig;
|
|
@@ -73,6 +76,7 @@ export class ImportController {
|
|
|
73
76
|
)
|
|
74
77
|
.map((db) => db.name);
|
|
75
78
|
let dataLoader: DataLoader | undefined;
|
|
79
|
+
let databaseRan: ConfigDatabase | undefined;
|
|
76
80
|
for (let db of this.config.databases) {
|
|
77
81
|
if (
|
|
78
82
|
db.name.toLowerCase().trim().replace(" ", "") === "migrations" ||
|
|
@@ -92,23 +96,48 @@ export class ImportController {
|
|
|
92
96
|
console.log(`Starting import data for database: ${db.name}`);
|
|
93
97
|
console.log(`---------------------------------`);
|
|
94
98
|
// await this.importCollections(db);
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
99
|
+
if (!databaseRan) {
|
|
100
|
+
databaseRan = db;
|
|
101
|
+
dataLoader = new DataLoader(
|
|
102
|
+
this.appwriteFolderPath,
|
|
103
|
+
this.importDataActions,
|
|
104
|
+
this.database,
|
|
105
|
+
this.config,
|
|
106
|
+
this.setupOptions.shouldWriteFile
|
|
107
|
+
);
|
|
108
|
+
await dataLoader.start(db.$id);
|
|
109
|
+
await this.importCollections(db, dataLoader);
|
|
110
|
+
await resolveAndUpdateRelationships(db.$id, this.database, this.config);
|
|
111
|
+
await this.executePostImportActions(db.$id, dataLoader);
|
|
112
|
+
} else if (databaseRan.$id !== db.$id) {
|
|
113
|
+
await this.updateOthersToFinalData(databaseRan, db);
|
|
114
|
+
}
|
|
106
115
|
console.log(`---------------------------------`);
|
|
107
116
|
console.log(`Finished import data for database: ${db.name}`);
|
|
108
117
|
console.log(`---------------------------------`);
|
|
109
118
|
}
|
|
110
119
|
}
|
|
111
120
|
|
|
121
|
+
async updateOthersToFinalData(
|
|
122
|
+
updatedDb: ConfigDatabase,
|
|
123
|
+
targetDb: ConfigDatabase
|
|
124
|
+
) {
|
|
125
|
+
await transferDatabaseLocalToLocal(
|
|
126
|
+
this.database,
|
|
127
|
+
updatedDb.$id,
|
|
128
|
+
targetDb.$id
|
|
129
|
+
);
|
|
130
|
+
await transferStorageLocalToLocal(
|
|
131
|
+
this.storage,
|
|
132
|
+
`${this.config.documentBucketId}_${updatedDb.name
|
|
133
|
+
.toLowerCase()
|
|
134
|
+
.replace(" ", "")}`,
|
|
135
|
+
`${this.config.documentBucketId}_${targetDb.name
|
|
136
|
+
.toLowerCase()
|
|
137
|
+
.replace(" ", "")}`
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
|
|
112
141
|
async importCollections(db: ConfigDatabase, dataLoader: DataLoader) {
|
|
113
142
|
if (!this.config.collections) {
|
|
114
143
|
return;
|
|
@@ -10,7 +10,10 @@ import {
|
|
|
10
10
|
import { type OperationCreate, type BackupCreate } from "./backup.js";
|
|
11
11
|
import { splitIntoBatches } from "./migrationHelper.js";
|
|
12
12
|
import type { AppwriteConfig } from "appwrite-utils";
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
getAppwriteClient,
|
|
15
|
+
tryAwaitWithRetry,
|
|
16
|
+
} from "../utils/helperFunctions.js";
|
|
14
17
|
|
|
15
18
|
export const logOperation = async (
|
|
16
19
|
db: Databases,
|
|
@@ -372,3 +375,123 @@ export const backupDatabase = async (
|
|
|
372
375
|
console.log("Database Backup Complete");
|
|
373
376
|
console.log("---------------------------------");
|
|
374
377
|
};
|
|
378
|
+
|
|
379
|
+
export const transferStorageLocalToLocal = async (
|
|
380
|
+
storage: Storage,
|
|
381
|
+
fromBucketId: string,
|
|
382
|
+
toBucketId: string
|
|
383
|
+
) => {
|
|
384
|
+
console.log(`Transferring files from ${fromBucketId} to ${toBucketId}`);
|
|
385
|
+
let lastFileId: string | undefined;
|
|
386
|
+
let fromFiles = await tryAwaitWithRetry(
|
|
387
|
+
async () => await storage.listFiles(fromBucketId, [Query.limit(100)])
|
|
388
|
+
);
|
|
389
|
+
const allFromFiles = fromFiles.files;
|
|
390
|
+
let numberOfFiles = 0;
|
|
391
|
+
if (fromFiles.files.length < 100) {
|
|
392
|
+
for (const file of allFromFiles) {
|
|
393
|
+
const fileData = await storage.getFileDownload(file.bucketId, file.$id);
|
|
394
|
+
const fileToCreate = InputFile.fromBuffer(
|
|
395
|
+
Buffer.from(fileData),
|
|
396
|
+
file.name
|
|
397
|
+
);
|
|
398
|
+
console.log(`Creating file: ${file.name}`);
|
|
399
|
+
tryAwaitWithRetry(
|
|
400
|
+
async () =>
|
|
401
|
+
await storage.createFile(
|
|
402
|
+
toBucketId,
|
|
403
|
+
file.$id,
|
|
404
|
+
fileToCreate,
|
|
405
|
+
file.$permissions
|
|
406
|
+
)
|
|
407
|
+
);
|
|
408
|
+
numberOfFiles++;
|
|
409
|
+
}
|
|
410
|
+
} else {
|
|
411
|
+
lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
|
|
412
|
+
while (lastFileId) {
|
|
413
|
+
const files = await storage.listFiles(fromBucketId, [
|
|
414
|
+
Query.limit(100),
|
|
415
|
+
Query.cursorAfter(lastFileId),
|
|
416
|
+
]);
|
|
417
|
+
allFromFiles.push(...files.files);
|
|
418
|
+
if (files.files.length < 100) {
|
|
419
|
+
lastFileId = undefined;
|
|
420
|
+
} else {
|
|
421
|
+
lastFileId = files.files[files.files.length - 1].$id;
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
for (const file of allFromFiles) {
|
|
425
|
+
const fileData = await storage.getFileDownload(file.bucketId, file.$id);
|
|
426
|
+
const fileToCreate = InputFile.fromBuffer(
|
|
427
|
+
Buffer.from(fileData),
|
|
428
|
+
file.name
|
|
429
|
+
);
|
|
430
|
+
await tryAwaitWithRetry(
|
|
431
|
+
async () =>
|
|
432
|
+
await storage.createFile(
|
|
433
|
+
toBucketId,
|
|
434
|
+
file.$id,
|
|
435
|
+
fileToCreate,
|
|
436
|
+
file.$permissions
|
|
437
|
+
)
|
|
438
|
+
);
|
|
439
|
+
numberOfFiles++;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
console.log(
|
|
444
|
+
`Transferred ${numberOfFiles} files from ${fromBucketId} to ${toBucketId}`
|
|
445
|
+
);
|
|
446
|
+
};
|
|
447
|
+
|
|
448
|
+
export const transferStorageLocalToRemote = async (
|
|
449
|
+
localStorage: Storage,
|
|
450
|
+
endpoint: string,
|
|
451
|
+
projectId: string,
|
|
452
|
+
apiKey: string,
|
|
453
|
+
fromBucketId: string,
|
|
454
|
+
toBucketId: string
|
|
455
|
+
) => {
|
|
456
|
+
console.log(
|
|
457
|
+
`Transferring files from current storage ${fromBucketId} to ${endpoint} bucket ${toBucketId}`
|
|
458
|
+
);
|
|
459
|
+
const client = getAppwriteClient(endpoint, apiKey, projectId);
|
|
460
|
+
const remoteStorage = new Storage(client);
|
|
461
|
+
let numberOfFiles = 0;
|
|
462
|
+
let lastFileId: string | undefined;
|
|
463
|
+
let fromFiles = await tryAwaitWithRetry(
|
|
464
|
+
async () => await localStorage.listFiles(fromBucketId, [Query.limit(100)])
|
|
465
|
+
);
|
|
466
|
+
const allFromFiles = fromFiles.files;
|
|
467
|
+
if (fromFiles.files.length === 100) {
|
|
468
|
+
lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
|
|
469
|
+
while (lastFileId) {
|
|
470
|
+
const files = await localStorage.listFiles(fromBucketId, [
|
|
471
|
+
Query.limit(100),
|
|
472
|
+
Query.cursorAfter(lastFileId),
|
|
473
|
+
]);
|
|
474
|
+
allFromFiles.push(...files.files);
|
|
475
|
+
if (files.files.length < 100) {
|
|
476
|
+
break;
|
|
477
|
+
}
|
|
478
|
+
lastFileId = files.files[files.files.length - 1].$id;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
for (const file of allFromFiles) {
|
|
483
|
+
await tryAwaitWithRetry(
|
|
484
|
+
async () =>
|
|
485
|
+
await remoteStorage.createFile(
|
|
486
|
+
toBucketId,
|
|
487
|
+
file.$id,
|
|
488
|
+
file,
|
|
489
|
+
file.$permissions
|
|
490
|
+
)
|
|
491
|
+
);
|
|
492
|
+
numberOfFiles++;
|
|
493
|
+
}
|
|
494
|
+
console.log(
|
|
495
|
+
`Transferred ${numberOfFiles} files from ${fromBucketId} to ${toBucketId}`
|
|
496
|
+
);
|
|
497
|
+
};
|