appwrite-utils-cli 0.0.261 → 0.0.263
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 +24 -5
- package/dist/main.js +6 -0
- package/dist/migrations/afterImportActions.d.ts +3 -1
- package/dist/migrations/afterImportActions.js +2 -2
- package/dist/migrations/appwriteToX.d.ts +109 -0
- package/dist/migrations/appwriteToX.js +88 -0
- package/dist/migrations/backup.d.ts +4 -4
- package/dist/migrations/converters.js +1 -1
- package/dist/migrations/dataLoader.d.ts +19 -18
- package/dist/migrations/dataLoader.js +65 -84
- package/dist/migrations/databases.d.ts +2 -0
- package/dist/migrations/databases.js +23 -0
- package/dist/migrations/importController.js +3 -0
- package/dist/migrations/importDataActions.d.ts +1 -1
- package/dist/migrations/importDataActions.js +1 -1
- package/dist/migrations/schema.d.ts +363 -32
- package/dist/migrations/schema.js +48 -28
- package/dist/migrations/schemaStrings.d.ts +1 -0
- package/dist/migrations/schemaStrings.js +10 -0
- package/dist/setup.js +0 -0
- package/dist/utils/helperFunctions.d.ts +3 -0
- package/dist/utils/helperFunctions.js +8 -0
- package/dist/utilsController.d.ts +1 -0
- package/dist/utilsController.js +7 -0
- package/package.json +1 -1
- package/src/main.ts +6 -0
- package/src/migrations/afterImportActions.ts +2 -2
- package/src/migrations/appwriteToX.ts +122 -0
- package/src/migrations/converters.ts +2 -2
- package/src/migrations/dataLoader.ts +87 -95
- package/src/migrations/databases.ts +25 -0
- package/src/migrations/importController.ts +3 -0
- package/src/migrations/importDataActions.ts +1 -1
- package/src/migrations/schema.ts +65 -36
- package/src/migrations/schemaStrings.ts +11 -0
- package/src/utils/helperFunctions.ts +17 -0
- package/src/utilsController.ts +9 -0
@@ -10,6 +10,7 @@ import { findOrCreateOperation, updateOperation } from "./migrationHelper.js";
|
|
10
10
|
import { AuthUserCreateSchema } from "../schemas/authUser.js";
|
11
11
|
import _ from "lodash";
|
12
12
|
import { UsersController } from "./users.js";
|
13
|
+
import { finalizeByAttributeMap } from "../utils/helperFunctions.js";
|
13
14
|
// Define a schema for the structure of collection import data using Zod for validation
|
14
15
|
export const CollectionImportDataSchema = z.object({
|
15
16
|
// Optional collection creation schema
|
@@ -69,17 +70,33 @@ export class DataLoader {
|
|
69
70
|
* @param target - The target object with values to update the source object.
|
70
71
|
* @returns The updated source object.
|
71
72
|
*/
|
72
|
-
mergeObjects(source,
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
73
|
+
mergeObjects(source, update) {
|
74
|
+
// Create a new object to hold the merged result
|
75
|
+
const result = { ...source };
|
76
|
+
Object.keys(update).forEach((key) => {
|
77
|
+
const sourceValue = source[key];
|
78
|
+
const updateValue = update[key];
|
79
|
+
// If the update value is an array, concatenate and remove duplicates
|
80
|
+
if (Array.isArray(updateValue)) {
|
81
|
+
const sourceArray = Array.isArray(sourceValue) ? sourceValue : [];
|
82
|
+
result[key] = [...new Set([...sourceArray, ...updateValue])];
|
83
|
+
}
|
84
|
+
// If the update value is an object, recursively merge
|
85
|
+
else if (updateValue !== null &&
|
86
|
+
typeof updateValue === "object" &&
|
87
|
+
!(updateValue instanceof Date)) {
|
88
|
+
result[key] = this.mergeObjects(sourceValue, updateValue);
|
89
|
+
}
|
90
|
+
// If the update value is not nullish, overwrite the source value
|
91
|
+
else if (updateValue !== null && updateValue !== undefined) {
|
92
|
+
result[key] = updateValue;
|
93
|
+
}
|
94
|
+
// If the update value is nullish, keep the original value unless it doesn't exist
|
95
|
+
else if (sourceValue === undefined) {
|
96
|
+
result[key] = updateValue;
|
80
97
|
}
|
81
98
|
});
|
82
|
-
return
|
99
|
+
return result;
|
83
100
|
}
|
84
101
|
// Method to load data from a file specified in the import definition
|
85
102
|
async loadData(importDef) {
|
@@ -255,6 +272,7 @@ export class DataLoader {
|
|
255
272
|
const collectionData = this.importMap.get(collectionKey);
|
256
273
|
if (!collectionData || !collectionData.data)
|
257
274
|
continue;
|
275
|
+
console.log(`Updating references for collection: ${collectionConfig.name}`);
|
258
276
|
// Iterate over each data item in the current collection
|
259
277
|
for (const item of collectionData.data) {
|
260
278
|
let needsUpdate = false;
|
@@ -264,91 +282,56 @@ export class DataLoader {
|
|
264
282
|
if (importDef.idMappings) {
|
265
283
|
// Iterate over each idMapping defined for the current import definition
|
266
284
|
for (const idMapping of importDef.idMappings) {
|
267
|
-
const
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
else {
|
279
|
-
// Otherwise, directly set the new ID
|
280
|
-
item.finalData[fieldToUpdate] = newIdForOldId;
|
281
|
-
}
|
282
|
-
console.log(`Updated ${oldId} to ${newIdForOldId}`);
|
283
|
-
needsUpdate = true;
|
285
|
+
const oldIds = Array.isArray(item.context[idMapping.sourceField])
|
286
|
+
? item.context[idMapping.sourceField]
|
287
|
+
: [item.context[idMapping.sourceField]];
|
288
|
+
const resolvedNewIds = [];
|
289
|
+
oldIds.forEach((oldId) => {
|
290
|
+
// Attempt to find a new ID for the old ID
|
291
|
+
let newIdForOldId = this.findNewIdForOldId(oldId, idMapping);
|
292
|
+
// Check if a new ID was found and it's not already included
|
293
|
+
if (newIdForOldId &&
|
294
|
+
!resolvedNewIds.includes(newIdForOldId)) {
|
295
|
+
resolvedNewIds.push(newIdForOldId);
|
284
296
|
}
|
297
|
+
});
|
298
|
+
if (resolvedNewIds.length) {
|
299
|
+
const targetField = idMapping.fieldToSet || idMapping.targetField;
|
300
|
+
const isArray = collectionConfig.attributes.some((attribute) => attribute.key === targetField && attribute.array);
|
301
|
+
// Set the target field based on whether it's an array or single value
|
302
|
+
if (isArray) {
|
303
|
+
item.finalData[targetField] = resolvedNewIds;
|
304
|
+
}
|
305
|
+
else {
|
306
|
+
// In case of a single value, use the first resolved ID
|
307
|
+
item.finalData[targetField] = resolvedNewIds[0];
|
308
|
+
}
|
309
|
+
needsUpdate = true;
|
285
310
|
}
|
286
311
|
}
|
287
312
|
}
|
288
313
|
}
|
289
314
|
}
|
315
|
+
// Update the importMap if changes were made to the item
|
290
316
|
if (needsUpdate) {
|
291
|
-
// Re-transform the item's finalData using its attribute mappings
|
292
|
-
const importDef = item.importDef; // Assuming importDef is available in the item
|
293
|
-
if (importDef && importDef.attributeMappings) {
|
294
|
-
item.finalData = await this.transformData(item.finalData, importDef.attributeMappings);
|
295
|
-
}
|
296
317
|
this.importMap.set(collectionKey, collectionData);
|
318
|
+
logger.info(`Updated item: ${JSON.stringify(item.finalData, undefined, 2)}`);
|
297
319
|
}
|
298
320
|
}
|
299
321
|
}
|
300
322
|
}
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
// for (const item of collectionData) {
|
314
|
-
// let needsUpdate = false;
|
315
|
-
// // Go through each idMapping defined for the collection
|
316
|
-
// for (const mapping of importDef.idMappings) {
|
317
|
-
// const oldReferenceId =
|
318
|
-
// item[mapping.targetField as keyof typeof item]; // Get the current reference ID from the item
|
319
|
-
// const referenceCollectionMap =
|
320
|
-
// this.oldIdToNewIdPerCollectionMap.get(
|
321
|
-
// this.getCollectionKey(mapping.targetCollection)
|
322
|
-
// );
|
323
|
-
// if (
|
324
|
-
// referenceCollectionMap &&
|
325
|
-
// referenceCollectionMap.has(oldReferenceId)
|
326
|
-
// ) {
|
327
|
-
// // Update the target field with the new reference ID from the mapped collection
|
328
|
-
// item[mapping.sourceField as keyof typeof item] =
|
329
|
-
// referenceCollectionMap.get(oldReferenceId);
|
330
|
-
// needsUpdate = true;
|
331
|
-
// console.log(
|
332
|
-
// `Updated item with ${mapping.sourceField} = ${JSON.stringify(
|
333
|
-
// item,
|
334
|
-
// null,
|
335
|
-
// undefined
|
336
|
-
// )}.`
|
337
|
-
// );
|
338
|
-
// }
|
339
|
-
// }
|
340
|
-
// // Save changes if any reference was updated
|
341
|
-
// if (needsUpdate) {
|
342
|
-
// await this.saveUpdatedItem(
|
343
|
-
// collection.name,
|
344
|
-
// item,
|
345
|
-
// importDef.primaryKeyField
|
346
|
-
// );
|
347
|
-
// }
|
348
|
-
// }
|
349
|
-
// }
|
350
|
-
// }
|
351
|
-
// }
|
323
|
+
findNewIdForOldId(oldId, idMapping) {
|
324
|
+
// First, check if the old ID has been merged into a new one
|
325
|
+
for (const [newUserId, oldIds] of this.mergedUserMap.entries()) {
|
326
|
+
if (oldIds.includes(oldId)) {
|
327
|
+
return newUserId;
|
328
|
+
}
|
329
|
+
}
|
330
|
+
// If not merged, look for a direct mapping from old to new ID
|
331
|
+
const targetCollectionKey = this.getCollectionKey(idMapping.targetCollection);
|
332
|
+
const targetOldIdToNewIdMap = this.oldIdToNewIdPerCollectionMap.get(targetCollectionKey);
|
333
|
+
return targetOldIdToNewIdMap?.get(oldId);
|
334
|
+
}
|
352
335
|
writeMapsToJsonFile() {
|
353
336
|
const outputDir = path.resolve(process.cwd());
|
354
337
|
const outputFile = path.join(outputDir, "dataLoaderOutput.json");
|
@@ -401,7 +384,6 @@ export class DataLoader {
|
|
401
384
|
// Check for duplicate email and add to emailToUserIdMap if not found
|
402
385
|
if (email && email.length > 0) {
|
403
386
|
if (this.emailToUserIdMap.has(email)) {
|
404
|
-
logger.error(`Duplicate email found or user exists already: ${email}`);
|
405
387
|
existingId = this.emailToUserIdMap.get(email);
|
406
388
|
}
|
407
389
|
else {
|
@@ -411,7 +393,6 @@ export class DataLoader {
|
|
411
393
|
// Check for duplicate phone and add to phoneToUserIdMap if not found
|
412
394
|
if (phone && phone.length > 0) {
|
413
395
|
if (this.phoneToUserIdMap.has(phone)) {
|
414
|
-
logger.error(`Duplicate phone found: ${phone}`);
|
415
396
|
existingId = this.phoneToUserIdMap.get(phone);
|
416
397
|
}
|
417
398
|
else {
|
@@ -0,0 +1,23 @@
|
|
1
|
+
import { Databases, Query } from "node-appwrite";
|
2
|
+
export const fetchAllDatabases = async (database) => {
|
3
|
+
const databases = await database.list([Query.limit(25)]);
|
4
|
+
const allDatabases = databases.databases;
|
5
|
+
let lastDatabaseId = allDatabases[allDatabases.length - 1].$id;
|
6
|
+
if (databases.databases.length < 25) {
|
7
|
+
return allDatabases;
|
8
|
+
}
|
9
|
+
else {
|
10
|
+
while (lastDatabaseId) {
|
11
|
+
const databases = await database.list([
|
12
|
+
Query.limit(25),
|
13
|
+
Query.cursorAfter(lastDatabaseId),
|
14
|
+
]);
|
15
|
+
allDatabases.push(...databases.databases);
|
16
|
+
if (databases.databases.length < 25) {
|
17
|
+
break;
|
18
|
+
}
|
19
|
+
lastDatabaseId = databases.databases[databases.databases.length - 1].$id;
|
20
|
+
}
|
21
|
+
}
|
22
|
+
return allDatabases;
|
23
|
+
};
|
@@ -159,6 +159,9 @@ export class ImportController {
|
|
159
159
|
if (item.finalData.hasOwnProperty("docId")) {
|
160
160
|
delete item.finalData.docId;
|
161
161
|
}
|
162
|
+
if (!item.finalData) {
|
163
|
+
return Promise.resolve();
|
164
|
+
}
|
162
165
|
return this.database
|
163
166
|
.createDocument(db.$id, collection.$id, id, item.finalData)
|
164
167
|
.then(() => {
|
@@ -12,7 +12,7 @@ export declare class ImportDataActions {
|
|
12
12
|
private validityRuleDefinitions;
|
13
13
|
private afterImportActionsDefinitions;
|
14
14
|
constructor(db: Databases, storage: Storage, config: AppwriteConfig, converterDefinitions: ConverterFunctions, validityRuleDefinitions: ValidationRules, afterImportActionsDefinitions: AfterImportActions);
|
15
|
-
runConverterFunctions(item: any, attributeMappings: AttributeMappings):
|
15
|
+
runConverterFunctions(item: any, attributeMappings: AttributeMappings): any;
|
16
16
|
/**
|
17
17
|
* Validates a single data item based on defined validation rules.
|
18
18
|
* @param item The data item to validate.
|
@@ -18,7 +18,7 @@ export class ImportDataActions {
|
|
18
18
|
this.validityRuleDefinitions = validityRuleDefinitions;
|
19
19
|
this.afterImportActionsDefinitions = afterImportActionsDefinitions;
|
20
20
|
}
|
21
|
-
|
21
|
+
runConverterFunctions(item, attributeMappings) {
|
22
22
|
const conversionSchema = attributeMappings.reduce((schema, mapping) => {
|
23
23
|
schema[mapping.targetKey] = (originalValue) => {
|
24
24
|
return mapping.converters.reduce((value, converterName) => {
|