appwrite-utils-cli 0.0.286 → 0.9.0
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 +122 -96
- package/dist/collections/attributes.d.ts +4 -0
- package/dist/collections/attributes.js +224 -0
- package/dist/collections/indexes.d.ts +4 -0
- package/dist/collections/indexes.js +27 -0
- package/dist/collections/methods.d.ts +16 -0
- package/dist/collections/methods.js +216 -0
- package/dist/databases/methods.d.ts +6 -0
- package/dist/databases/methods.js +33 -0
- package/dist/interactiveCLI.d.ts +19 -0
- package/dist/interactiveCLI.js +555 -0
- package/dist/main.js +227 -62
- package/dist/migrations/afterImportActions.js +37 -40
- package/dist/migrations/appwriteToX.d.ts +26 -25
- package/dist/migrations/appwriteToX.js +42 -6
- package/dist/migrations/attributes.js +21 -20
- package/dist/migrations/backup.d.ts +93 -87
- package/dist/migrations/collections.d.ts +6 -0
- package/dist/migrations/collections.js +149 -20
- package/dist/migrations/converters.d.ts +2 -18
- package/dist/migrations/converters.js +13 -2
- package/dist/migrations/dataLoader.d.ts +276 -161
- package/dist/migrations/dataLoader.js +535 -292
- package/dist/migrations/databases.js +8 -2
- package/dist/migrations/helper.d.ts +3 -0
- package/dist/migrations/helper.js +21 -0
- package/dist/migrations/importController.d.ts +5 -2
- package/dist/migrations/importController.js +125 -88
- package/dist/migrations/importDataActions.d.ts +9 -1
- package/dist/migrations/importDataActions.js +15 -3
- package/dist/migrations/indexes.js +3 -2
- package/dist/migrations/logging.js +20 -8
- package/dist/migrations/migrationHelper.d.ts +9 -4
- package/dist/migrations/migrationHelper.js +6 -5
- package/dist/migrations/openapi.d.ts +1 -1
- package/dist/migrations/openapi.js +33 -18
- package/dist/migrations/queue.js +3 -2
- package/dist/migrations/relationships.d.ts +2 -2
- package/dist/migrations/schemaStrings.js +53 -41
- package/dist/migrations/setupDatabase.d.ts +2 -4
- package/dist/migrations/setupDatabase.js +24 -105
- package/dist/migrations/storage.d.ts +3 -1
- package/dist/migrations/storage.js +110 -16
- package/dist/migrations/transfer.d.ts +30 -0
- package/dist/migrations/transfer.js +337 -0
- package/dist/migrations/users.d.ts +2 -1
- package/dist/migrations/users.js +78 -43
- package/dist/schemas/authUser.d.ts +2 -2
- package/dist/storage/methods.d.ts +15 -0
- package/dist/storage/methods.js +207 -0
- package/dist/storage/schemas.d.ts +687 -0
- package/dist/storage/schemas.js +175 -0
- package/dist/utils/getClientFromConfig.d.ts +4 -0
- package/dist/utils/getClientFromConfig.js +16 -0
- package/dist/utils/helperFunctions.d.ts +11 -1
- package/dist/utils/helperFunctions.js +38 -0
- package/dist/utils/retryFailedPromises.d.ts +2 -0
- package/dist/utils/retryFailedPromises.js +21 -0
- package/dist/utils/schemaStrings.d.ts +13 -0
- package/dist/utils/schemaStrings.js +403 -0
- package/dist/utils/setupFiles.js +110 -61
- package/dist/utilsController.d.ts +40 -22
- package/dist/utilsController.js +164 -84
- package/package.json +13 -15
- package/src/collections/attributes.ts +483 -0
- package/src/collections/indexes.ts +53 -0
- package/src/collections/methods.ts +331 -0
- package/src/databases/methods.ts +47 -0
- package/src/init.ts +64 -64
- package/src/interactiveCLI.ts +767 -0
- package/src/main.ts +292 -83
- package/src/migrations/afterImportActions.ts +553 -490
- package/src/migrations/appwriteToX.ts +237 -174
- package/src/migrations/attributes.ts +483 -422
- package/src/migrations/backup.ts +205 -205
- package/src/migrations/collections.ts +545 -300
- package/src/migrations/converters.ts +161 -150
- package/src/migrations/dataLoader.ts +1615 -1304
- package/src/migrations/databases.ts +44 -25
- package/src/migrations/dbHelpers.ts +92 -92
- package/src/migrations/helper.ts +40 -0
- package/src/migrations/importController.ts +448 -384
- package/src/migrations/importDataActions.ts +315 -307
- package/src/migrations/indexes.ts +40 -37
- package/src/migrations/logging.ts +29 -16
- package/src/migrations/migrationHelper.ts +207 -201
- package/src/migrations/openapi.ts +83 -70
- package/src/migrations/queue.ts +118 -119
- package/src/migrations/relationships.ts +324 -324
- package/src/migrations/schemaStrings.ts +472 -460
- package/src/migrations/setupDatabase.ts +118 -219
- package/src/migrations/storage.ts +538 -358
- package/src/migrations/transfer.ts +608 -0
- package/src/migrations/users.ts +362 -285
- package/src/migrations/validationRules.ts +63 -63
- package/src/schemas/authUser.ts +23 -23
- package/src/setup.ts +8 -8
- package/src/storage/methods.ts +371 -0
- package/src/storage/schemas.ts +205 -0
- package/src/types.ts +9 -9
- package/src/utils/getClientFromConfig.ts +17 -0
- package/src/utils/helperFunctions.ts +181 -127
- package/src/utils/index.ts +2 -2
- package/src/utils/loadConfigs.ts +59 -59
- package/src/utils/retryFailedPromises.ts +27 -0
- package/src/utils/schemaStrings.ts +473 -0
- package/src/utils/setupFiles.ts +228 -182
- package/src/utilsController.ts +325 -194
- package/tsconfig.json +37 -37
@@ -10,36 +10,51 @@ export const generateOpenApi = async (config) => {
|
|
10
10
|
for (const collection of config.collections) {
|
11
11
|
// Transform and register each attribute schema
|
12
12
|
const attributeSchemas = collection.attributes.map((attribute) => {
|
13
|
-
return transformTypeToOpenApi(attributeSchema);
|
13
|
+
return transformTypeToOpenApi(attributeSchema, attribute.description);
|
14
14
|
});
|
15
15
|
// Create and register the collection schema with descriptions
|
16
16
|
const updatedCollectionSchema = CollectionSchema.extend({
|
17
17
|
// @ts-ignore
|
18
|
-
attributes:
|
18
|
+
attributes: attributeSchemas,
|
19
19
|
}).openapi(collection.description ?? "No description");
|
20
20
|
// Register the updated collection schema under the collection name
|
21
21
|
registry.register(collection.name, updatedCollectionSchema);
|
22
22
|
}
|
23
23
|
// Convert the registry to OpenAPI JSON
|
24
|
-
|
25
|
-
const openApiSpec =
|
24
|
+
const generator = new OpenApiGeneratorV31(registry.definitions);
|
25
|
+
const openApiSpec = generator.generateComponents();
|
26
26
|
// Output the OpenAPI spec to a file
|
27
27
|
writeFileSync("./appwrite/openapi/openapi.json", JSON.stringify(openApiSpec, null, 2));
|
28
28
|
};
|
29
|
-
export function transformTypeToOpenApi(schema) {
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
29
|
+
export function transformTypeToOpenApi(schema, description) {
|
30
|
+
// Check if description is an object (OpenAPI properties) or a string
|
31
|
+
let updatedSchema;
|
32
|
+
if (!description) {
|
33
|
+
return schema;
|
34
|
+
}
|
35
|
+
if (typeof description === "string") {
|
36
|
+
updatedSchema = schema.openapi(description);
|
37
|
+
}
|
38
|
+
else if (typeof description === "object") {
|
39
|
+
updatedSchema = schema.openapi(description);
|
40
|
+
}
|
41
|
+
else {
|
42
|
+
updatedSchema = schema;
|
43
|
+
}
|
44
|
+
// Check and transform attributes if they exist
|
45
|
+
if (schema._def && schema._def.shape) {
|
46
|
+
const shape = schema._def.shape();
|
47
|
+
for (const key in shape) {
|
48
|
+
const attributeDesc = shape[key].description;
|
49
|
+
if (attributeDesc) {
|
50
|
+
if (typeof attributeDesc === "string") {
|
51
|
+
shape[key] = shape[key].openapi(attributeDesc);
|
36
52
|
}
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
finalData.openapi(schema.description);
|
53
|
+
else if (typeof attributeDesc === "object") {
|
54
|
+
shape[key] = shape[key].openapi(attributeDesc);
|
55
|
+
}
|
56
|
+
}
|
42
57
|
}
|
43
|
-
|
44
|
-
|
58
|
+
}
|
59
|
+
return updatedSchema;
|
45
60
|
}
|
package/dist/migrations/queue.js
CHANGED
@@ -2,6 +2,7 @@ import { Query } from "node-appwrite";
|
|
2
2
|
import { createOrUpdateAttribute } from "./attributes.js";
|
3
3
|
import _ from "lodash";
|
4
4
|
import { fetchAndCacheCollectionByName } from "./collections.js";
|
5
|
+
import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
5
6
|
export const queuedOperations = [];
|
6
7
|
export const nameToIdMapping = new Map();
|
7
8
|
export const enqueueOperation = (operation) => {
|
@@ -24,7 +25,7 @@ export const processQueue = async (db, dbId) => {
|
|
24
25
|
if (operation.collectionId) {
|
25
26
|
console.log(`\tFetching collection by ID: ${operation.collectionId}`);
|
26
27
|
try {
|
27
|
-
collectionFound = await db.getCollection(dbId, operation.collectionId);
|
28
|
+
collectionFound = await tryAwaitWithRetry(async () => await db.getCollection(dbId, operation.collectionId));
|
28
29
|
}
|
29
30
|
catch (e) {
|
30
31
|
console.log(`\tCollection not found by ID: ${operation.collectionId}`);
|
@@ -47,7 +48,7 @@ export const processQueue = async (db, dbId) => {
|
|
47
48
|
// Handle non-relationship operations with a specified collectionId
|
48
49
|
console.log(`\tFetching collection by ID: ${operation.collectionId}`);
|
49
50
|
try {
|
50
|
-
collectionFound = await db.getCollection(dbId, operation.collectionId);
|
51
|
+
collectionFound = await tryAwaitWithRetry(async () => await db.getCollection(dbId, operation.collectionId));
|
51
52
|
}
|
52
53
|
catch (e) {
|
53
54
|
console.log(`\tCollection not found by ID: ${operation.collectionId}`);
|
@@ -12,10 +12,10 @@ export declare const findCollectionsWithRelationships: (config: AppwriteConfig)
|
|
12
12
|
twoWayKey: string;
|
13
13
|
onDelete: "setNull" | "cascade" | "restrict";
|
14
14
|
side: "parent" | "child";
|
15
|
+
description?: string | Record<string, string> | null | undefined;
|
16
|
+
required?: boolean | undefined;
|
15
17
|
array?: boolean | undefined;
|
16
18
|
error?: string | undefined;
|
17
|
-
required?: boolean | undefined;
|
18
|
-
description?: string | Record<string, string> | null | undefined;
|
19
19
|
importMapping?: {
|
20
20
|
originalIdField: string;
|
21
21
|
targetField?: string | undefined;
|
@@ -17,25 +17,24 @@ export class SchemaGenerator {
|
|
17
17
|
const collections = this.config.collections;
|
18
18
|
delete this.config.collections;
|
19
19
|
const configPath = path.join(this.appwriteFolderPath, "appwriteConfig.ts");
|
20
|
-
const configContent = `import { type AppwriteConfig } from "appwrite-utils";
|
21
|
-
|
22
|
-
const appwriteConfig: AppwriteConfig = {
|
23
|
-
appwriteEndpoint: "${this.config.appwriteEndpoint}",
|
24
|
-
appwriteProject: "${this.config.appwriteProject}",
|
25
|
-
appwriteKey: "${this.config.appwriteKey}",
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
export default appwriteConfig;
|
20
|
+
const configContent = `import { type AppwriteConfig } from "appwrite-utils";
|
21
|
+
|
22
|
+
const appwriteConfig: AppwriteConfig = {
|
23
|
+
appwriteEndpoint: "${this.config.appwriteEndpoint}",
|
24
|
+
appwriteProject: "${this.config.appwriteProject}",
|
25
|
+
appwriteKey: "${this.config.appwriteKey}",
|
26
|
+
enableBackups: ${this.config.enableBackups},
|
27
|
+
backupInterval: ${this.config.backupInterval},
|
28
|
+
backupRetention: ${this.config.backupRetention},
|
29
|
+
enableBackupCleanup: ${this.config.enableBackupCleanup},
|
30
|
+
enableMockData: ${this.config.enableMockData},
|
31
|
+
documentBucketId: "${this.config.documentBucketId}",
|
32
|
+
usersCollectionName: "${this.config.usersCollectionName}",
|
33
|
+
databases: ${JSON.stringify(this.config.databases)},
|
34
|
+
buckets: ${JSON.stringify(this.config.buckets)}
|
35
|
+
};
|
36
|
+
|
37
|
+
export default appwriteConfig;
|
39
38
|
`;
|
40
39
|
fs.writeFileSync(configPath, configContent, { encoding: "utf-8" });
|
41
40
|
const collectionsFolderPath = path.join(this.appwriteFolderPath, "collections");
|
@@ -45,19 +44,19 @@ export class SchemaGenerator {
|
|
45
44
|
collections?.forEach((collection) => {
|
46
45
|
const { databaseId, ...collectionWithoutDbId } = collection; // Destructure to exclude databaseId
|
47
46
|
const collectionFilePath = path.join(collectionsFolderPath, `${collection.name}.ts`);
|
48
|
-
const collectionContent = `import { type CollectionCreate } from "appwrite-utils";
|
49
|
-
|
50
|
-
const ${collection.name}Config: Partial<CollectionCreate> = {
|
51
|
-
name: "${collection.name}",
|
52
|
-
$id: "${collection.$id}",
|
53
|
-
enabled: ${collection.enabled},
|
54
|
-
documentSecurity: ${collection.documentSecurity},
|
55
|
-
$permissions: [
|
47
|
+
const collectionContent = `import { type CollectionCreate } from "appwrite-utils";
|
48
|
+
|
49
|
+
const ${collection.name}Config: Partial<CollectionCreate> = {
|
50
|
+
name: "${collection.name}",
|
51
|
+
$id: "${collection.$id}",
|
52
|
+
enabled: ${collection.enabled},
|
53
|
+
documentSecurity: ${collection.documentSecurity},
|
54
|
+
$permissions: [
|
56
55
|
${collection.$permissions
|
57
56
|
.map((permission) => `{ permission: "${permission.permission}", target: "${permission.target}" }`)
|
58
|
-
.join(",\n ")}
|
59
|
-
],
|
60
|
-
attributes: [
|
57
|
+
.join(",\n ")}
|
58
|
+
],
|
59
|
+
attributes: [
|
61
60
|
${collection.attributes
|
62
61
|
.map((attr) => {
|
63
62
|
return `{ ${Object.entries(attr)
|
@@ -85,23 +84,21 @@ export class SchemaGenerator {
|
|
85
84
|
})
|
86
85
|
.join(", ")} }`;
|
87
86
|
})
|
88
|
-
.join(",\n ")}
|
89
|
-
],
|
90
|
-
indexes: [
|
87
|
+
.join(",\n ")}
|
88
|
+
],
|
89
|
+
indexes: [
|
91
90
|
${(collection.indexes?.map((index) => {
|
92
91
|
// Map each attribute to ensure it is properly quoted
|
93
|
-
const formattedAttributes = index.attributes
|
94
|
-
.map((attr) => `"${attr}"`)
|
95
|
-
.join(", ");
|
92
|
+
const formattedAttributes = index.attributes.map((attr) => `"${attr}"`).join(", ") ?? "";
|
96
93
|
return `{ key: "${index.key}", type: "${index.type}", attributes: [${formattedAttributes}], orders: [${index.orders
|
97
94
|
?.filter((order) => order !== null)
|
98
95
|
.map((order) => `"${order}"`)
|
99
96
|
.join(", ") ?? ""}] }`;
|
100
|
-
}) ?? []).join(",\n ")}
|
101
|
-
]
|
102
|
-
};
|
103
|
-
|
104
|
-
export default ${collection.name}Config;
|
97
|
+
}) ?? []).join(",\n ")}
|
98
|
+
]
|
99
|
+
};
|
100
|
+
|
101
|
+
export default ${collection.name}Config;
|
105
102
|
`;
|
106
103
|
fs.writeFileSync(collectionFilePath, collectionContent, {
|
107
104
|
encoding: "utf-8",
|
@@ -182,6 +179,11 @@ export class SchemaGenerator {
|
|
182
179
|
createSchemaString = (name, attributes) => {
|
183
180
|
const pascalName = toPascalCase(name);
|
184
181
|
let imports = `import { z } from "zod";\n`;
|
182
|
+
const hasDescription = attributes.some((attr) => attr.description);
|
183
|
+
if (hasDescription) {
|
184
|
+
imports += `import { extendZodWithOpenApi } from "@asteasolutions/zod-to-openapi";\n`;
|
185
|
+
imports += `extendZodWithOpenApi(z);\n`;
|
186
|
+
}
|
185
187
|
// Use the relationshipMap to find related collections
|
186
188
|
const relationshipDetails = this.relationshipMap.get(name) || [];
|
187
189
|
const relatedCollections = relationshipDetails
|
@@ -386,6 +388,16 @@ export class SchemaGenerator {
|
|
386
388
|
if (attribute.array && !attribute.required) {
|
387
389
|
baseSchemaCode += ".nullish()";
|
388
390
|
}
|
391
|
+
if (attribute.description) {
|
392
|
+
if (typeof attribute.description === "string") {
|
393
|
+
baseSchemaCode += `.openapi({ description: "${attribute.description}" })`;
|
394
|
+
}
|
395
|
+
else {
|
396
|
+
baseSchemaCode += `.openapi(${Object.entries(attribute.description)
|
397
|
+
.map(([key, value]) => `"${key}": ${value}`)
|
398
|
+
.join(", ")})`;
|
399
|
+
}
|
400
|
+
}
|
389
401
|
return baseSchemaCode;
|
390
402
|
};
|
391
403
|
}
|
@@ -1,7 +1,5 @@
|
|
1
|
-
import { Databases,
|
1
|
+
import { Databases, type Models } from "node-appwrite";
|
2
2
|
import { type AppwriteConfig } from "appwrite-utils";
|
3
|
-
import type { SetupOptions } from "../utilsController.js";
|
4
3
|
export declare const setupMigrationDatabase: (config: AppwriteConfig) => Promise<void>;
|
5
4
|
export declare const ensureDatabasesExist: (config: AppwriteConfig) => Promise<void>;
|
6
|
-
export declare const wipeOtherDatabases: (database: Databases,
|
7
|
-
export declare const startSetup: (database: Databases, storage: Storage, config: AppwriteConfig, setupOptions: SetupOptions, appwriteFolderPath: string) => Promise<void>;
|
5
|
+
export declare const wipeOtherDatabases: (database: Databases, databasesToKeep: Models.Database[]) => Promise<void>;
|
@@ -1,49 +1,33 @@
|
|
1
|
-
import { Databases,
|
1
|
+
import { Databases, Query } from "node-appwrite";
|
2
2
|
import { createOrUpdateAttribute } from "./attributes.js";
|
3
|
-
import { createOrUpdateCollections, generateSchemas, wipeDatabase, } from "./collections.js";
|
4
3
|
import { getMigrationCollectionSchemas } from "./backup.js";
|
5
|
-
import { areCollectionNamesSame, toCamelCase } from "../utils/index.js";
|
6
|
-
import { backupDatabase, initOrGetBackupStorage, initOrGetDocumentStorage, wipeDocumentStorage, } from "./storage.js";
|
4
|
+
import { areCollectionNamesSame, toCamelCase, tryAwaitWithRetry, } from "../utils/index.js";
|
7
5
|
import {} from "appwrite-utils";
|
8
|
-
import {
|
9
|
-
import { UsersController } from "./users.js";
|
6
|
+
import { ulid } from "ulidx";
|
10
7
|
export const setupMigrationDatabase = async (config) => {
|
11
|
-
// Create the migrations database if needed
|
12
8
|
console.log("---------------------------------");
|
13
9
|
console.log("Starting Migrations Setup");
|
14
10
|
console.log("---------------------------------");
|
15
11
|
const database = new Databases(config.appwriteClient);
|
16
12
|
let db = null;
|
17
|
-
const dbCollections = [];
|
18
13
|
const migrationCollectionsSetup = getMigrationCollectionSchemas();
|
19
14
|
try {
|
20
|
-
db = await database.get("migrations");
|
15
|
+
db = await tryAwaitWithRetry(async () => await database.get("migrations"), undefined, true);
|
21
16
|
console.log("Migrations database found");
|
22
17
|
}
|
23
18
|
catch (e) {
|
24
|
-
db = await database.create("migrations", "Migrations", true);
|
19
|
+
db = await tryAwaitWithRetry(async () => await database.create("migrations", "Migrations", true));
|
25
20
|
console.log("Migrations database created");
|
26
21
|
}
|
27
|
-
if (db) {
|
28
|
-
const collectionsPulled = await database.listCollections(db.$id, [
|
29
|
-
Query.limit(25),
|
30
|
-
]);
|
31
|
-
dbCollections.push(...collectionsPulled.collections);
|
32
|
-
}
|
33
|
-
console.log(`Collections in migrations database: ${dbCollections.length}`);
|
34
|
-
// Iterate over each key in the migrationCollectionsSetup object
|
35
22
|
for (const [collectionName, { collection, attributes }] of Object.entries(migrationCollectionsSetup)) {
|
36
|
-
const collectionId = toCamelCase(collectionName);
|
23
|
+
const collectionId = toCamelCase(collectionName);
|
37
24
|
let collectionFound = null;
|
38
25
|
try {
|
39
|
-
collectionFound = await database.getCollection(db.$id, collectionId);
|
26
|
+
collectionFound = await tryAwaitWithRetry(async () => await database.getCollection(db.$id, collectionId));
|
40
27
|
}
|
41
28
|
catch (e) {
|
42
29
|
console.log(`Collection not found: ${collectionId}`);
|
43
|
-
|
44
|
-
if (!collectionFound) {
|
45
|
-
// Create the collection with the provided configuration
|
46
|
-
collectionFound = await database.createCollection(db.$id, collectionId, collectionName, undefined, collection.documentSecurity, collection.enabled);
|
30
|
+
collectionFound = await tryAwaitWithRetry(async () => await database.createCollection(db.$id, collectionId, collectionName, undefined, collection.documentSecurity, collection.enabled));
|
47
31
|
}
|
48
32
|
for (const attribute of attributes) {
|
49
33
|
await createOrUpdateAttribute(database, db.$id, collectionFound, attribute);
|
@@ -56,96 +40,31 @@ export const setupMigrationDatabase = async (config) => {
|
|
56
40
|
export const ensureDatabasesExist = async (config) => {
|
57
41
|
const database = new Databases(config.appwriteClient);
|
58
42
|
const databasesToEnsure = config.databases;
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
43
|
+
const existingDatabases = await tryAwaitWithRetry(async () => await database.list([Query.limit(500)]));
|
44
|
+
const migrationsDatabase = existingDatabases.databases.find((d) => d.name.toLowerCase().trim().replace(" ", "") === "migrations");
|
45
|
+
if (existingDatabases.databases.length !== 0 && migrationsDatabase) {
|
46
|
+
console.log("Wiping all databases except migrations");
|
47
|
+
databasesToEnsure.push(migrationsDatabase);
|
48
|
+
}
|
65
49
|
for (const db of databasesToEnsure) {
|
66
50
|
if (!existingDatabases.databases.some((d) => d.name === db.name)) {
|
67
|
-
await database.create(db.$id ||
|
51
|
+
await tryAwaitWithRetry(async () => await database.create(db.$id || ulid(), db.name, true));
|
68
52
|
console.log(`${db.name} database created`);
|
69
53
|
}
|
70
54
|
}
|
71
55
|
};
|
72
|
-
export const wipeOtherDatabases = async (database,
|
73
|
-
const databasesToKeep = config.databases.map((db) => db.name.toLowerCase().trim().replace(" ", ""));
|
74
|
-
databasesToKeep.push("migrations");
|
56
|
+
export const wipeOtherDatabases = async (database, databasesToKeep) => {
|
75
57
|
console.log(`Databases to keep: ${databasesToKeep.join(", ")}`);
|
76
|
-
const allDatabases = await database.list([Query.limit(500)]);
|
58
|
+
const allDatabases = await tryAwaitWithRetry(async () => await database.list([Query.limit(500)]));
|
59
|
+
const migrationsDatabase = allDatabases.databases.find((d) => d.name.toLowerCase().trim().replace(" ", "") === "migrations");
|
60
|
+
if (allDatabases.databases.length !== 0 && migrationsDatabase) {
|
61
|
+
console.log("Wiping all databases except migrations");
|
62
|
+
databasesToKeep.push(migrationsDatabase);
|
63
|
+
}
|
77
64
|
for (const db of allDatabases.databases) {
|
78
|
-
if (!databasesToKeep.
|
79
|
-
await database.delete(db.$id);
|
65
|
+
if (!databasesToKeep.some((d) => d.name === db.name)) {
|
66
|
+
await tryAwaitWithRetry(async () => await database.delete(db.$id));
|
80
67
|
console.log(`Deleted database: ${db.name}`);
|
81
68
|
}
|
82
69
|
}
|
83
70
|
};
|
84
|
-
export const startSetup = async (database, storage, config, setupOptions, appwriteFolderPath) => {
|
85
|
-
await setupMigrationDatabase(config);
|
86
|
-
if (config.enableBackups) {
|
87
|
-
await initOrGetBackupStorage(storage);
|
88
|
-
if (setupOptions.wipeDocumentStorage) {
|
89
|
-
if (setupOptions.runProd) {
|
90
|
-
await initOrGetDocumentStorage(storage, config, config.databases[0].name);
|
91
|
-
await wipeDocumentStorage(storage, config, config.databases[0].name);
|
92
|
-
}
|
93
|
-
if (setupOptions.runStaging) {
|
94
|
-
await initOrGetDocumentStorage(storage, config, config.databases[1].name);
|
95
|
-
await wipeDocumentStorage(storage, config, config.databases[1].name);
|
96
|
-
}
|
97
|
-
if (setupOptions.runDev) {
|
98
|
-
await initOrGetDocumentStorage(storage, config, config.databases[2].name);
|
99
|
-
await wipeDocumentStorage(storage, config, config.databases[2].name);
|
100
|
-
}
|
101
|
-
}
|
102
|
-
}
|
103
|
-
if (config.enableWipeOtherDatabases) {
|
104
|
-
await wipeOtherDatabases(database, config);
|
105
|
-
}
|
106
|
-
if (setupOptions.wipeUsers) {
|
107
|
-
const usersController = new UsersController(config, database);
|
108
|
-
console.log("Wiping users");
|
109
|
-
await usersController.wipeUsers();
|
110
|
-
console.log("Users wiped");
|
111
|
-
}
|
112
|
-
await ensureDatabasesExist(config);
|
113
|
-
const databaseNames = config.databases.map((db) => db.name);
|
114
|
-
// Move to here so it always runs if it's set to true
|
115
|
-
if (setupOptions.generateSchemas) {
|
116
|
-
await generateSchemas(config, appwriteFolderPath);
|
117
|
-
}
|
118
|
-
for (const db of config.databases) {
|
119
|
-
// Determine if the current database should be processed based on the setup options
|
120
|
-
const processDatabase = (setupOptions.runProd &&
|
121
|
-
areCollectionNamesSame(db.name, databaseNames[0])) ||
|
122
|
-
(setupOptions.runStaging &&
|
123
|
-
areCollectionNamesSame(db.name, databaseNames[1])) ||
|
124
|
-
(setupOptions.runDev &&
|
125
|
-
areCollectionNamesSame(db.name, databaseNames[2]));
|
126
|
-
if (!processDatabase) {
|
127
|
-
continue;
|
128
|
-
}
|
129
|
-
else {
|
130
|
-
await initOrGetDocumentStorage(storage, config, db.name);
|
131
|
-
}
|
132
|
-
console.log(`---------------------------------`);
|
133
|
-
console.log(`Starting setup for database: ${db.name}`);
|
134
|
-
console.log(`---------------------------------`);
|
135
|
-
let deletedCollections;
|
136
|
-
if (setupOptions.wipeDatabases && processDatabase) {
|
137
|
-
if (config.enableBackups && setupOptions.doBackup) {
|
138
|
-
await backupDatabase(database, db.$id, storage);
|
139
|
-
}
|
140
|
-
deletedCollections = await wipeDatabase(database, db.$id);
|
141
|
-
}
|
142
|
-
if (processDatabase) {
|
143
|
-
await createOrUpdateCollections(database, db.$id, config, deletedCollections);
|
144
|
-
}
|
145
|
-
deletedCollections = undefined;
|
146
|
-
nameToIdMapping.clear();
|
147
|
-
console.log(`---------------------------------`);
|
148
|
-
console.log(`Finished setup for database: ${db.name}`);
|
149
|
-
console.log(`---------------------------------`);
|
150
|
-
}
|
151
|
-
};
|
@@ -3,6 +3,8 @@ import { type OperationCreate } from "./backup.js";
|
|
3
3
|
import type { AppwriteConfig } from "appwrite-utils";
|
4
4
|
export declare const logOperation: (db: Databases, dbId: string, operationDetails: OperationCreate, operationId?: string) => Promise<Models.Document>;
|
5
5
|
export declare const initOrGetBackupStorage: (storage: Storage) => Promise<Models.Bucket>;
|
6
|
-
export declare const initOrGetDocumentStorage: (storage: Storage, config: AppwriteConfig,
|
6
|
+
export declare const initOrGetDocumentStorage: (storage: Storage, config: AppwriteConfig, dbId: string, bucketName?: string) => Promise<Models.Bucket | undefined>;
|
7
7
|
export declare const wipeDocumentStorage: (storage: Storage, config: AppwriteConfig, dbName: string) => Promise<void>;
|
8
8
|
export declare const backupDatabase: (database: Databases, databaseId: string, storage: Storage) => Promise<void>;
|
9
|
+
export declare const transferStorageLocalToLocal: (storage: Storage, fromBucketId: string, toBucketId: string) => Promise<void>;
|
10
|
+
export declare const transferStorageLocalToRemote: (localStorage: Storage, endpoint: string, projectId: string, apiKey: string, fromBucketId: string, toBucketId: string) => Promise<void>;
|
@@ -1,12 +1,14 @@
|
|
1
|
-
import { Storage, Databases, Query,
|
1
|
+
import { Storage, Databases, Query, ID, Permission, } from "node-appwrite";
|
2
|
+
import { InputFile } from "node-appwrite/file";
|
2
3
|
import {} from "./backup.js";
|
3
4
|
import { splitIntoBatches } from "./migrationHelper.js";
|
5
|
+
import { getAppwriteClient, tryAwaitWithRetry, } from "../utils/helperFunctions.js";
|
4
6
|
export const logOperation = async (db, dbId, operationDetails, operationId) => {
|
5
7
|
try {
|
6
8
|
let operation;
|
7
9
|
if (operationId) {
|
8
10
|
// Update existing operation log
|
9
|
-
operation = await db.updateDocument("migrations", "currentOperations", operationId, operationDetails);
|
11
|
+
operation = await tryAwaitWithRetry(async () => await db.updateDocument("migrations", "currentOperations", operationId, operationDetails));
|
10
12
|
}
|
11
13
|
else {
|
12
14
|
// Create new operation log
|
@@ -22,29 +24,30 @@ export const logOperation = async (db, dbId, operationDetails, operationId) => {
|
|
22
24
|
};
|
23
25
|
export const initOrGetBackupStorage = async (storage) => {
|
24
26
|
try {
|
25
|
-
const backupStorage = await storage.getBucket("backupStorage");
|
27
|
+
const backupStorage = await tryAwaitWithRetry(async () => await storage.getBucket("backupStorage"));
|
26
28
|
return backupStorage;
|
27
29
|
}
|
28
30
|
catch (e) {
|
29
31
|
// ID backupStorage
|
30
32
|
// Name Backups Storage
|
31
|
-
const backupStorage = await storage.createBucket("backupStorage", "Backups Storage");
|
33
|
+
const backupStorage = await tryAwaitWithRetry(async () => await storage.createBucket("backupStorage", "Backups Storage"));
|
32
34
|
return backupStorage;
|
33
35
|
}
|
34
36
|
};
|
35
|
-
export const initOrGetDocumentStorage = async (storage, config,
|
37
|
+
export const initOrGetDocumentStorage = async (storage, config, dbId, bucketName) => {
|
36
38
|
try {
|
37
|
-
await
|
39
|
+
await tryAwaitWithRetry(async () => await storage.getBucket(bucketName ??
|
40
|
+
`${config.documentBucketId}_${dbId.toLowerCase().replace(" ", "")}`));
|
38
41
|
}
|
39
42
|
catch (e) {
|
40
43
|
// ID documentStorage
|
41
44
|
// Name Document Storage
|
42
|
-
const documentStorage = await storage.createBucket(`${config.documentBucketId}_${
|
45
|
+
const documentStorage = await tryAwaitWithRetry(async () => await storage.createBucket(`${config.documentBucketId}_${dbId.toLowerCase().replace(" ", "")}`, `${dbId} Storage`, [
|
43
46
|
Permission.read("any"),
|
44
47
|
Permission.create("users"),
|
45
48
|
Permission.update("users"),
|
46
49
|
Permission.delete("users"),
|
47
|
-
]);
|
50
|
+
]));
|
48
51
|
return documentStorage;
|
49
52
|
}
|
50
53
|
};
|
@@ -61,7 +64,7 @@ export const wipeDocumentStorage = async (storage, config, dbName) => {
|
|
61
64
|
if (lastFileId) {
|
62
65
|
queries.push(Query.cursorAfter(lastFileId));
|
63
66
|
}
|
64
|
-
const filesPulled = await storage.listFiles(bucketId, queries);
|
67
|
+
const filesPulled = await tryAwaitWithRetry(async () => await storage.listFiles(bucketId, queries));
|
65
68
|
if (filesPulled.files.length === 0) {
|
66
69
|
console.log("No files found, done!");
|
67
70
|
moreFiles = false;
|
@@ -78,7 +81,7 @@ export const wipeDocumentStorage = async (storage, config, dbName) => {
|
|
78
81
|
}
|
79
82
|
for (const fileId of allFiles) {
|
80
83
|
console.log(`Deleting file: ${fileId}`);
|
81
|
-
await storage.deleteFile(bucketId, fileId);
|
84
|
+
await tryAwaitWithRetry(async () => await storage.deleteFile(bucketId, fileId));
|
82
85
|
}
|
83
86
|
console.log(`All files in bucket ${bucketId} have been deleted.`);
|
84
87
|
};
|
@@ -124,7 +127,7 @@ export const backupDatabase = async (database, databaseId, storage) => {
|
|
124
127
|
// Fetch and backup the database details
|
125
128
|
let db;
|
126
129
|
try {
|
127
|
-
db = await database.get(databaseId);
|
130
|
+
db = await tryAwaitWithRetry(async () => await database.get(databaseId));
|
128
131
|
}
|
129
132
|
catch (e) {
|
130
133
|
console.error(`Error fetching database: ${e}`);
|
@@ -146,25 +149,25 @@ export const backupDatabase = async (database, databaseId, storage) => {
|
|
146
149
|
let progress = 0;
|
147
150
|
let total = 0; // Initialize total to 0, will be updated dynamically
|
148
151
|
while (moreCollections) {
|
149
|
-
const collectionResponse = await database.listCollections(databaseId, [
|
152
|
+
const collectionResponse = await tryAwaitWithRetry(async () => await database.listCollections(databaseId, [
|
150
153
|
Query.limit(500), // Adjust the limit as needed
|
151
154
|
...(lastCollectionId ? [Query.cursorAfter(lastCollectionId)] : []),
|
152
|
-
]);
|
155
|
+
]));
|
153
156
|
total += collectionResponse.collections.length; // Update total with number of collections
|
154
157
|
for (const { $id: collectionId, name: collectionName, } of collectionResponse.collections) {
|
155
158
|
let collectionDocumentCount = 0; // Initialize document count for the current collection
|
156
159
|
try {
|
157
|
-
const collection = await database.getCollection(databaseId, collectionId);
|
160
|
+
const collection = await tryAwaitWithRetry(async () => await database.getCollection(databaseId, collectionId));
|
158
161
|
progress++;
|
159
162
|
data.collections.push(JSON.stringify(collection));
|
160
163
|
// Initialize pagination for documents within the current collection
|
161
164
|
let lastDocumentId = "";
|
162
165
|
let moreDocuments = true;
|
163
166
|
while (moreDocuments) {
|
164
|
-
const documentResponse = await database.listDocuments(databaseId, collectionId, [
|
167
|
+
const documentResponse = await tryAwaitWithRetry(async () => await database.listDocuments(databaseId, collectionId, [
|
165
168
|
Query.limit(500), // Adjust the limit as needed
|
166
169
|
...(lastDocumentId ? [Query.cursorAfter(lastDocumentId)] : []),
|
167
|
-
]);
|
170
|
+
]));
|
168
171
|
total += documentResponse.documents.length; // Update total with number of documents
|
169
172
|
collectionDocumentCount += documentResponse.documents.length; // Update document count for the current collection
|
170
173
|
let documentPromises = [];
|
@@ -244,3 +247,94 @@ export const backupDatabase = async (database, databaseId, storage) => {
|
|
244
247
|
console.log("Database Backup Complete");
|
245
248
|
console.log("---------------------------------");
|
246
249
|
};
|
250
|
+
export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucketId) => {
|
251
|
+
console.log(`Transferring files from ${fromBucketId} to ${toBucketId}`);
|
252
|
+
let lastFileId;
|
253
|
+
let fromFiles = await tryAwaitWithRetry(async () => await storage.listFiles(fromBucketId, [Query.limit(100)]));
|
254
|
+
const allFromFiles = fromFiles.files;
|
255
|
+
let numberOfFiles = 0;
|
256
|
+
const downloadFileWithRetry = async (bucketId, fileId) => {
|
257
|
+
let attempts = 3;
|
258
|
+
while (attempts > 0) {
|
259
|
+
try {
|
260
|
+
return await storage.getFileDownload(bucketId, fileId);
|
261
|
+
}
|
262
|
+
catch (error) {
|
263
|
+
console.error(`Error downloading file ${fileId}: ${error}`);
|
264
|
+
attempts--;
|
265
|
+
if (attempts === 0)
|
266
|
+
throw error;
|
267
|
+
}
|
268
|
+
}
|
269
|
+
};
|
270
|
+
if (fromFiles.files.length < 100) {
|
271
|
+
for (const file of allFromFiles) {
|
272
|
+
const fileData = await tryAwaitWithRetry(async () => await downloadFileWithRetry(file.bucketId, file.$id));
|
273
|
+
if (!fileData) {
|
274
|
+
console.error(`Error downloading file ${file.$id}`);
|
275
|
+
continue;
|
276
|
+
}
|
277
|
+
const fileToCreate = InputFile.fromBuffer(new Uint8Array(fileData), file.name);
|
278
|
+
console.log(`Creating file: ${file.name}`);
|
279
|
+
tryAwaitWithRetry(async () => await storage.createFile(toBucketId, file.$id, fileToCreate, file.$permissions));
|
280
|
+
numberOfFiles++;
|
281
|
+
}
|
282
|
+
}
|
283
|
+
else {
|
284
|
+
lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
|
285
|
+
while (lastFileId) {
|
286
|
+
const files = await tryAwaitWithRetry(async () => await storage.listFiles(fromBucketId, [
|
287
|
+
Query.limit(100),
|
288
|
+
Query.cursorAfter(lastFileId),
|
289
|
+
]));
|
290
|
+
allFromFiles.push(...files.files);
|
291
|
+
if (files.files.length < 100) {
|
292
|
+
lastFileId = undefined;
|
293
|
+
}
|
294
|
+
else {
|
295
|
+
lastFileId = files.files[files.files.length - 1].$id;
|
296
|
+
}
|
297
|
+
}
|
298
|
+
for (const file of allFromFiles) {
|
299
|
+
const fileData = await tryAwaitWithRetry(async () => await downloadFileWithRetry(file.bucketId, file.$id));
|
300
|
+
if (!fileData) {
|
301
|
+
console.error(`Error downloading file ${file.$id}`);
|
302
|
+
continue;
|
303
|
+
}
|
304
|
+
const fileToCreate = InputFile.fromBuffer(new Uint8Array(fileData), file.name);
|
305
|
+
await tryAwaitWithRetry(async () => await storage.createFile(toBucketId, file.$id, fileToCreate, file.$permissions));
|
306
|
+
numberOfFiles++;
|
307
|
+
}
|
308
|
+
}
|
309
|
+
console.log(`Transferred ${numberOfFiles} files from ${fromBucketId} to ${toBucketId}`);
|
310
|
+
};
|
311
|
+
export const transferStorageLocalToRemote = async (localStorage, endpoint, projectId, apiKey, fromBucketId, toBucketId) => {
|
312
|
+
console.log(`Transferring files from current storage ${fromBucketId} to ${endpoint} bucket ${toBucketId}`);
|
313
|
+
const client = getAppwriteClient(endpoint, apiKey, projectId);
|
314
|
+
const remoteStorage = new Storage(client);
|
315
|
+
let numberOfFiles = 0;
|
316
|
+
let lastFileId;
|
317
|
+
let fromFiles = await tryAwaitWithRetry(async () => await localStorage.listFiles(fromBucketId, [Query.limit(100)]));
|
318
|
+
const allFromFiles = fromFiles.files;
|
319
|
+
if (fromFiles.files.length === 100) {
|
320
|
+
lastFileId = fromFiles.files[fromFiles.files.length - 1].$id;
|
321
|
+
while (lastFileId) {
|
322
|
+
const files = await tryAwaitWithRetry(async () => await localStorage.listFiles(fromBucketId, [
|
323
|
+
Query.limit(100),
|
324
|
+
Query.cursorAfter(lastFileId),
|
325
|
+
]));
|
326
|
+
allFromFiles.push(...files.files);
|
327
|
+
if (files.files.length < 100) {
|
328
|
+
break;
|
329
|
+
}
|
330
|
+
lastFileId = files.files[files.files.length - 1].$id;
|
331
|
+
}
|
332
|
+
}
|
333
|
+
for (const file of allFromFiles) {
|
334
|
+
const fileData = await tryAwaitWithRetry(async () => await localStorage.getFileDownload(file.bucketId, file.$id));
|
335
|
+
const fileToCreate = InputFile.fromBuffer(new Uint8Array(fileData), file.name);
|
336
|
+
await tryAwaitWithRetry(async () => await remoteStorage.createFile(toBucketId, file.$id, fileToCreate, file.$permissions));
|
337
|
+
numberOfFiles++;
|
338
|
+
}
|
339
|
+
console.log(`Transferred ${numberOfFiles} files from ${fromBucketId} to ${toBucketId}`);
|
340
|
+
};
|