appwrite-utils-cli 0.0.46 → 0.0.48
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 +53 -5
- package/dist/main.js +23 -0
- package/dist/migrations/collections.d.ts +6 -0
- package/dist/migrations/collections.js +138 -7
- package/dist/migrations/dataLoader.d.ts +11 -1
- package/dist/migrations/dataLoader.js +40 -29
- 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 +19 -6
- 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 +226 -6
- package/src/migrations/dataLoader.ts +69 -33
- package/src/migrations/databases.ts +221 -2
- package/src/migrations/importController.ts +31 -6
- 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,69 @@ 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
|
+
oldId = item[importDef.updateMapping.originalIdField];
|
|
1211
|
+
if (oldId) {
|
|
1212
|
+
itemDataToUpdate = currentData?.data.find(
|
|
1213
|
+
({ context, rawData, finalData }) => {
|
|
1214
|
+
const targetField =
|
|
1215
|
+
importDef.updateMapping!.targetField ??
|
|
1216
|
+
importDef.updateMapping!.originalIdField;
|
|
1217
|
+
return (
|
|
1218
|
+
`${context[targetField]}` === `${oldId}` ||
|
|
1219
|
+
`${rawData[targetField]}` === `${oldId}` ||
|
|
1220
|
+
`${finalData[targetField]}` === `${oldId}`
|
|
1221
|
+
);
|
|
1220
1222
|
}
|
|
1223
|
+
);
|
|
1224
|
+
|
|
1225
|
+
if (itemDataToUpdate) {
|
|
1226
|
+
newId =
|
|
1227
|
+
itemDataToUpdate.finalData.docId ||
|
|
1228
|
+
itemDataToUpdate.context.docId;
|
|
1221
1229
|
}
|
|
1222
1230
|
}
|
|
1223
|
-
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
// If updateMapping is not defined or did not find the item, use primaryKeyField
|
|
1234
|
+
if (!itemDataToUpdate && importDef.primaryKeyField) {
|
|
1235
|
+
oldId = item[importDef.primaryKeyField];
|
|
1236
|
+
if (oldId) {
|
|
1237
|
+
newId = oldIdToNewIdMap?.get(`${oldId}`);
|
|
1238
|
+
if (
|
|
1239
|
+
!newId &&
|
|
1240
|
+
this.getCollectionKey(this.config.usersCollectionName) ===
|
|
1241
|
+
this.getCollectionKey(collection.name)
|
|
1242
|
+
) {
|
|
1243
|
+
for (const [key, value] of this.mergedUserMap.entries()) {
|
|
1244
|
+
if (value.includes(`${oldId}`)) {
|
|
1245
|
+
newId = key;
|
|
1246
|
+
break;
|
|
1247
|
+
}
|
|
1248
|
+
}
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
if (oldId && !itemDataToUpdate) {
|
|
1253
|
+
itemDataToUpdate = currentData?.data.find(
|
|
1254
|
+
(data) =>
|
|
1255
|
+
`${data.context[importDef.primaryKeyField]}` === `${oldId}`
|
|
1256
|
+
);
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
|
|
1260
|
+
if (!oldId) {
|
|
1224
1261
|
logger.error(
|
|
1225
1262
|
`No old ID found (to update another document with) in prepareUpdateData for ${
|
|
1226
1263
|
collection.name
|
|
@@ -1228,12 +1265,7 @@ export class DataLoader {
|
|
|
1228
1265
|
);
|
|
1229
1266
|
continue;
|
|
1230
1267
|
}
|
|
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
|
|
1268
|
+
|
|
1237
1269
|
if (!newId && !itemDataToUpdate) {
|
|
1238
1270
|
logger.error(
|
|
1239
1271
|
`No new id found for collection ${
|
|
@@ -1260,6 +1292,7 @@ export class DataLoader {
|
|
|
1260
1292
|
continue;
|
|
1261
1293
|
}
|
|
1262
1294
|
}
|
|
1295
|
+
|
|
1263
1296
|
if (!itemDataToUpdate || !newId) {
|
|
1264
1297
|
logger.error(
|
|
1265
1298
|
`No data or ID (docId) found for collection ${
|
|
@@ -1272,49 +1305,51 @@ export class DataLoader {
|
|
|
1272
1305
|
);
|
|
1273
1306
|
continue;
|
|
1274
1307
|
}
|
|
1308
|
+
|
|
1275
1309
|
transformedData = this.mergeObjects(
|
|
1276
1310
|
itemDataToUpdate.finalData,
|
|
1277
1311
|
transformedData
|
|
1278
1312
|
);
|
|
1313
|
+
|
|
1279
1314
|
// Create a context object for the item, including the new ID and transformed data
|
|
1280
1315
|
let context = this.createContext(db, collection, item, newId);
|
|
1281
|
-
context =
|
|
1316
|
+
context = this.mergeObjects(context, transformedData);
|
|
1317
|
+
|
|
1282
1318
|
// Validate the item before proceeding
|
|
1283
|
-
const isValid = this.importDataActions.validateItem(
|
|
1319
|
+
const isValid = await this.importDataActions.validateItem(
|
|
1284
1320
|
item,
|
|
1285
1321
|
importDef.attributeMappings,
|
|
1286
1322
|
context
|
|
1287
1323
|
);
|
|
1288
|
-
|
|
1324
|
+
|
|
1289
1325
|
if (!isValid) {
|
|
1290
1326
|
logger.info(
|
|
1291
1327
|
`Skipping item: ${JSON.stringify(item, null, 2)} because it's invalid`
|
|
1292
1328
|
);
|
|
1293
1329
|
continue;
|
|
1294
1330
|
}
|
|
1331
|
+
|
|
1295
1332
|
// Update the attribute mappings with any actions that need to be performed post-import
|
|
1296
1333
|
const mappingsWithActions = this.getAttributeMappingsWithActions(
|
|
1297
1334
|
importDef.attributeMappings,
|
|
1298
1335
|
context,
|
|
1299
1336
|
transformedData
|
|
1300
1337
|
);
|
|
1338
|
+
|
|
1301
1339
|
// Update the import definition with the new attribute mappings
|
|
1302
1340
|
const newImportDef = {
|
|
1303
1341
|
...importDef,
|
|
1304
1342
|
attributeMappings: mappingsWithActions,
|
|
1305
1343
|
};
|
|
1306
|
-
|
|
1344
|
+
|
|
1307
1345
|
if (itemDataToUpdate) {
|
|
1308
|
-
// Update the existing item's finalData and context in place
|
|
1309
1346
|
itemDataToUpdate.finalData = this.mergeObjects(
|
|
1310
1347
|
itemDataToUpdate.finalData,
|
|
1311
1348
|
transformedData
|
|
1312
1349
|
);
|
|
1313
1350
|
itemDataToUpdate.context = context;
|
|
1314
1351
|
itemDataToUpdate.importDef = newImportDef;
|
|
1315
|
-
currentData!.data.push(itemDataToUpdate);
|
|
1316
1352
|
} else {
|
|
1317
|
-
// If no existing item matches, then add the new item
|
|
1318
1353
|
currentData!.data.push({
|
|
1319
1354
|
rawData: item,
|
|
1320
1355
|
context: context,
|
|
@@ -1322,6 +1357,7 @@ export class DataLoader {
|
|
|
1322
1357
|
finalData: transformedData,
|
|
1323
1358
|
});
|
|
1324
1359
|
}
|
|
1360
|
+
|
|
1325
1361
|
// Since we're modifying currentData in place, we ensure no duplicates are added
|
|
1326
1362
|
this.importMap.set(this.getCollectionKey(collection.name), currentData!);
|
|
1327
1363
|
}
|
|
@@ -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,7 +96,8 @@ 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
|
-
if (!
|
|
99
|
+
if (!databaseRan) {
|
|
100
|
+
databaseRan = db;
|
|
96
101
|
dataLoader = new DataLoader(
|
|
97
102
|
this.appwriteFolderPath,
|
|
98
103
|
this.importDataActions,
|
|
@@ -101,18 +106,38 @@ export class ImportController {
|
|
|
101
106
|
this.setupOptions.shouldWriteFile
|
|
102
107
|
);
|
|
103
108
|
await dataLoader.start(db.$id);
|
|
104
|
-
|
|
105
|
-
|
|
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);
|
|
106
114
|
}
|
|
107
|
-
await this.importCollections(db, dataLoader);
|
|
108
|
-
await resolveAndUpdateRelationships(db.$id, this.database, this.config);
|
|
109
|
-
await this.executePostImportActions(db.$id, dataLoader);
|
|
110
115
|
console.log(`---------------------------------`);
|
|
111
116
|
console.log(`Finished import data for database: ${db.name}`);
|
|
112
117
|
console.log(`---------------------------------`);
|
|
113
118
|
}
|
|
114
119
|
}
|
|
115
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
|
+
|
|
116
141
|
async importCollections(db: ConfigDatabase, dataLoader: DataLoader) {
|
|
117
142
|
if (!this.config.collections) {
|
|
118
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
|
+
};
|