zync-nest-data-module 1.1.38 → 1.1.39
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/dist/redis/redis.module.js +2 -3
- package/dist/redis/redis.module.js.map +1 -1
- package/dist/tsconfig.lib.tsbuildinfo +1 -1
- package/package.json +4 -5
- package/dist/index.js.map +0 -1
- package/libs/src/app.controller.ts +0 -91
- package/libs/src/app.module.ts +0 -34
- package/libs/src/backup/backup.config.ts +0 -45
- package/libs/src/backup/backup.interface.ts +0 -21
- package/libs/src/backup/backup.module.ts +0 -11
- package/libs/src/backup/backup.service.ts +0 -274
- package/libs/src/backup/backup.worker.ts +0 -35
- package/libs/src/backup/index.ts +0 -4
- package/libs/src/base/dto.ts +0 -46
- package/libs/src/base/index.ts +0 -2
- package/libs/src/base/resolver.ts +0 -20
- package/libs/src/database/database.module.ts +0 -28
- package/libs/src/database/database.repository.ts +0 -355
- package/libs/src/database/database.scheme.ts +0 -128
- package/libs/src/database/database.service.ts +0 -75
- package/libs/src/database/database.sync.ts +0 -61
- package/libs/src/database/database.transaction.ts +0 -99
- package/libs/src/database/database.uniqueId.ts +0 -90
- package/libs/src/database/database.utils.ts +0 -99
- package/libs/src/database/index.ts +0 -8
- package/libs/src/index.ts +0 -5
- package/libs/src/main.ts +0 -52
- package/libs/src/redis/index.ts +0 -2
- package/libs/src/redis/redis.interface.ts +0 -34
- package/libs/src/redis/redis.module.ts +0 -30
- package/libs/src/redis/redis.service.ts +0 -137
- package/libs/src/service/index.ts +0 -1
- package/libs/src/service/service.ts +0 -181
- package/libs/src/test/test.controller.ts +0 -59
- package/libs/src/test/test.dto.ts +0 -41
- package/libs/src/test/test.module.ts +0 -24
- package/libs/src/test/test.redis.ts +0 -19
- package/libs/src/test/test.repository.ts +0 -44
- package/libs/src/test/test.resolver.ts +0 -35
- package/libs/src/test/test.schema.ts +0 -21
- package/libs/src/test/test.service.ts +0 -19
- package/libs/tsconfig.lib.json +0 -19
- package/tsconfig.json +0 -29
- package/update-links.js +0 -159
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zync-nest-data-module",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.39",
|
|
4
4
|
"description": "NestJS database module with database backup and file upload utilities",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nestjs",
|
|
@@ -23,7 +23,7 @@
|
|
|
23
23
|
"build:watch": "tsc -p libs/tsconfig.lib.json --watch",
|
|
24
24
|
"clean": "rimraf dist",
|
|
25
25
|
"prepublishOnly": "npm run build",
|
|
26
|
-
"publish:
|
|
26
|
+
"publish:npm": "npm publish",
|
|
27
27
|
"start": "npm run build && node dist/main.js",
|
|
28
28
|
"start:dev": "npm run build:watch & nodemon dist/main.js",
|
|
29
29
|
"test": "echo \"Error: no test specified\" && exit 1",
|
|
@@ -70,7 +70,7 @@
|
|
|
70
70
|
"slugify": "^1.6.6",
|
|
71
71
|
"uuid": "^11.1.0",
|
|
72
72
|
"xlsx": "^0.18.5",
|
|
73
|
-
"zync-nest-library": "^1.0.
|
|
73
|
+
"zync-nest-library": "^1.0.38"
|
|
74
74
|
},
|
|
75
75
|
"devDependencies": {
|
|
76
76
|
"@nestjs/cli": "^11.0.10",
|
|
@@ -103,6 +103,5 @@
|
|
|
103
103
|
"packageManager": "pnpm@10.14.0",
|
|
104
104
|
"publishConfig": {
|
|
105
105
|
"access": "public"
|
|
106
|
-
}
|
|
107
|
-
"registry": "https://registry.asynctechs.com"
|
|
106
|
+
}
|
|
108
107
|
}
|
package/dist/index.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../libs/src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;AAAA,2CAAyB;AACzB,6CAA2B;AAC3B,4CAA0B;AAC1B,yCAAuB;AACvB,0CAAwB"}
|
|
@@ -1,91 +0,0 @@
|
|
|
1
|
-
import { Body, Controller, Get, Param, Post, Query } from "@nestjs/common";
|
|
2
|
-
import { ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from "@nestjs/swagger";
|
|
3
|
-
|
|
4
|
-
@ApiTags("Test")
|
|
5
|
-
@Controller("test")
|
|
6
|
-
export class AppController {
|
|
7
|
-
@Get()
|
|
8
|
-
@ApiOperation({ summary: "Get test message" })
|
|
9
|
-
@ApiResponse({ status: 200, description: "Returns a test message" })
|
|
10
|
-
getTest(): { message: string; timestamp: string } {
|
|
11
|
-
return {
|
|
12
|
-
message: "Zync Nest Library is working!",
|
|
13
|
-
timestamp: new Date().toISOString()
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
@Get("health")
|
|
18
|
-
@ApiOperation({ summary: "Health check endpoint" })
|
|
19
|
-
@ApiResponse({ status: 200, description: "Returns health status" })
|
|
20
|
-
getHealth(): { status: string; uptime: number } {
|
|
21
|
-
return {
|
|
22
|
-
status: "healthy",
|
|
23
|
-
uptime: process.uptime()
|
|
24
|
-
};
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
@Post("echo")
|
|
28
|
-
@ApiOperation({ summary: "Echo back the request body" })
|
|
29
|
-
@ApiResponse({ status: 201, description: "Returns the echoed data" })
|
|
30
|
-
echoData(@Body() data: any): { echo: any; receivedAt: string } {
|
|
31
|
-
return {
|
|
32
|
-
echo: data,
|
|
33
|
-
receivedAt: new Date().toISOString()
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
@Get("params/:id")
|
|
38
|
-
@ApiOperation({ summary: "Test path parameters" })
|
|
39
|
-
@ApiParam({ name: "id", description: "Test ID" })
|
|
40
|
-
@ApiQuery({ name: "query", required: false, description: "Optional query parameter" })
|
|
41
|
-
@ApiResponse({ status: 200, description: "Returns parameter data" })
|
|
42
|
-
testParams(
|
|
43
|
-
@Param("id") id: string,
|
|
44
|
-
@Query("query") query?: string
|
|
45
|
-
): { id: string; query?: string; timestamp: string } {
|
|
46
|
-
return {
|
|
47
|
-
id,
|
|
48
|
-
query,
|
|
49
|
-
timestamp: new Date().toISOString()
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
@Get("library-info")
|
|
54
|
-
@ApiOperation({ summary: "Get library information" })
|
|
55
|
-
@ApiResponse({ status: 200, description: "Returns library information" })
|
|
56
|
-
getLibraryInfo(): {
|
|
57
|
-
name: string;
|
|
58
|
-
version: string;
|
|
59
|
-
description: string;
|
|
60
|
-
modules: string[];
|
|
61
|
-
} {
|
|
62
|
-
return {
|
|
63
|
-
name: "zync-nest-library",
|
|
64
|
-
version: "1.0.23",
|
|
65
|
-
description: "NestJS library with database backup and file upload utilities",
|
|
66
|
-
modules: [
|
|
67
|
-
"billplz",
|
|
68
|
-
"dbbackup",
|
|
69
|
-
"exabytes",
|
|
70
|
-
"firebase",
|
|
71
|
-
"googleapi",
|
|
72
|
-
"htmlConverter",
|
|
73
|
-
"mailer",
|
|
74
|
-
"message",
|
|
75
|
-
"razorpay",
|
|
76
|
-
"senangpay",
|
|
77
|
-
"ultramsg",
|
|
78
|
-
"upload",
|
|
79
|
-
"utils",
|
|
80
|
-
"xlsx"
|
|
81
|
-
]
|
|
82
|
-
};
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
@Get("redis-keys")
|
|
86
|
-
@ApiOperation({ summary: "Get all Redis keys" })
|
|
87
|
-
@ApiResponse({ status: 200, description: "Returns all Redis keys" })
|
|
88
|
-
getRedisKeys(): Promise<string[]> {
|
|
89
|
-
return Promise.resolve([]);
|
|
90
|
-
}
|
|
91
|
-
}
|
package/libs/src/app.module.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
import { ApolloDriver, ApolloDriverConfig } from "@nestjs/apollo";
|
|
2
|
-
import { Module, OnModuleInit } from "@nestjs/common";
|
|
3
|
-
import { GraphQLModule } from "@nestjs/graphql";
|
|
4
|
-
import { join } from "path";
|
|
5
|
-
import { AppController } from "./app.controller";
|
|
6
|
-
import { DbBackupModule, DbBackupService } from "./backup";
|
|
7
|
-
import { RedisModule } from "./redis";
|
|
8
|
-
import { TestModule } from "./test/test.module";
|
|
9
|
-
import { ApScalarModule } from "zync-nest-library";
|
|
10
|
-
import { MongooseModule } from "@nestjs/mongoose";
|
|
11
|
-
|
|
12
|
-
@Module({
|
|
13
|
-
imports: [
|
|
14
|
-
RedisModule,
|
|
15
|
-
TestModule,
|
|
16
|
-
ApScalarModule,
|
|
17
|
-
MongooseModule.forRoot(process.env.mongodb_url),
|
|
18
|
-
GraphQLModule.forRoot<ApolloDriverConfig>({
|
|
19
|
-
driver: ApolloDriver,
|
|
20
|
-
autoSchemaFile: join(process.cwd(), "src/schema.gql"),
|
|
21
|
-
subscriptions: {
|
|
22
|
-
"graphql-ws": true,
|
|
23
|
-
"subscriptions-transport-ws": true
|
|
24
|
-
},
|
|
25
|
-
playground: true,
|
|
26
|
-
plugins: [],
|
|
27
|
-
context: ({ req, res }) => ({ req, res })
|
|
28
|
-
}),
|
|
29
|
-
DbBackupModule
|
|
30
|
-
],
|
|
31
|
-
controllers: [AppController],
|
|
32
|
-
exports: [DbBackupModule, ApScalarModule]
|
|
33
|
-
})
|
|
34
|
-
export class ZyncNestDataModule {}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import { Injectable } from "@nestjs/common";
|
|
2
|
-
|
|
3
|
-
import * as dotenv from "dotenv";
|
|
4
|
-
import { IDbBackupConfig } from "./backup.interface";
|
|
5
|
-
import * as Path from "path";
|
|
6
|
-
|
|
7
|
-
dotenv.config();
|
|
8
|
-
|
|
9
|
-
@Injectable()
|
|
10
|
-
export class DbBackupConfigService {
|
|
11
|
-
public getBackupConfig(): IDbBackupConfig {
|
|
12
|
-
const isDocker =
|
|
13
|
-
process.env.NODE_ENV === "production" || process.env.is_docker === "true";
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
env: process.env.app_env || "",
|
|
17
|
-
mongodb: {
|
|
18
|
-
connectionStrings: process.env.mongodb_backup_connection_strings
|
|
19
|
-
? process.env.mongodb_backup_connection_strings.split(",")
|
|
20
|
-
: [process.env.mongodb_url || ""],
|
|
21
|
-
},
|
|
22
|
-
backup: {
|
|
23
|
-
enabled: process.env.db_backup_enabled === "true",
|
|
24
|
-
dir: isDocker
|
|
25
|
-
? process.env.db_backup_dir || "/app/backups"
|
|
26
|
-
: process.env.db_backup_dir
|
|
27
|
-
? Path.join(__dirname, "..", "..", "..", "..", process.env.db_backup_dir)
|
|
28
|
-
: Path.join(__dirname, "..", "..", "..", "..", "db_backups"),
|
|
29
|
-
maxBackups: parseInt(process.env.db_backup_max || "10", 10),
|
|
30
|
-
isDownload: false, // download to false if upload to space is true
|
|
31
|
-
uploadToSpace: true, // upload to space to true if download is false
|
|
32
|
-
},
|
|
33
|
-
spaces: {
|
|
34
|
-
name: process.env.aws_bucket || "",
|
|
35
|
-
region: process.env.aws_s3_region || "nyc3",
|
|
36
|
-
key: process.env.aws_access_key_id || "",
|
|
37
|
-
secret: process.env.aws_secret_access_key || "",
|
|
38
|
-
dir: process.env.s3_spaces_dir || "db-backups",
|
|
39
|
-
endpoint:
|
|
40
|
-
process.env.s3_spaces_endpoint ||
|
|
41
|
-
"https://sgp1.digitaloceanspaces.com",
|
|
42
|
-
},
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export interface IDbBackupConfig {
|
|
2
|
-
env: string;
|
|
3
|
-
mongodb: {
|
|
4
|
-
connectionStrings: string | string[];
|
|
5
|
-
};
|
|
6
|
-
backup: {
|
|
7
|
-
dir: string;
|
|
8
|
-
enabled: boolean;
|
|
9
|
-
maxBackups: number;
|
|
10
|
-
isDownload: boolean;
|
|
11
|
-
uploadToSpace: boolean;
|
|
12
|
-
};
|
|
13
|
-
spaces: {
|
|
14
|
-
name: string;
|
|
15
|
-
region: string;
|
|
16
|
-
key: string;
|
|
17
|
-
secret: string;
|
|
18
|
-
endpoint: string;
|
|
19
|
-
dir?: string;
|
|
20
|
-
};
|
|
21
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import { Module } from "@nestjs/common";
|
|
2
|
-
import { DbBackupService } from "./backup.service";
|
|
3
|
-
import { DbBackupConfigService } from "./backup.config";
|
|
4
|
-
import { ApUploadModule } from "zync-nest-library";
|
|
5
|
-
|
|
6
|
-
@Module({
|
|
7
|
-
imports: [ApUploadModule],
|
|
8
|
-
providers: [DbBackupService, DbBackupConfigService],
|
|
9
|
-
exports: [DbBackupService, DbBackupConfigService]
|
|
10
|
-
})
|
|
11
|
-
export class DbBackupModule {}
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
import { Injectable, Logger } from "@nestjs/common";
|
|
2
|
-
import { exec } from "child_process";
|
|
3
|
-
import { promises as fs } from "fs";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
import { promisify } from "util";
|
|
6
|
-
import { UploadService } from "zync-nest-library";
|
|
7
|
-
import { DbBackupConfigService } from "./backup.config";
|
|
8
|
-
import { IDbBackupConfig } from "./backup.interface";
|
|
9
|
-
import { Cron, CronExpression } from "@nestjs/schedule";
|
|
10
|
-
import { join } from "path";
|
|
11
|
-
import { Worker } from "worker_threads";
|
|
12
|
-
|
|
13
|
-
const execAsync = promisify(exec);
|
|
14
|
-
|
|
15
|
-
@Injectable()
|
|
16
|
-
export class DbBackupService {
|
|
17
|
-
private readonly logger = new Logger(DbBackupService.name);
|
|
18
|
-
|
|
19
|
-
constructor(private readonly dbConfigService: DbBackupConfigService, private readonly uploadService: UploadService) {}
|
|
20
|
-
|
|
21
|
-
public async runBackup(): Promise<any> {
|
|
22
|
-
|
|
23
|
-
console.log("[DB Backup] Log 1 Starting backup...");
|
|
24
|
-
|
|
25
|
-
const config = this.dbConfigService.getBackupConfig();
|
|
26
|
-
|
|
27
|
-
if (!config?.backup?.enabled) {
|
|
28
|
-
return;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
console.log("[DB Backup] Log 2 Config:", config);
|
|
32
|
-
|
|
33
|
-
const backup = await this.createBackup(this.dbConfigService.getBackupConfig());
|
|
34
|
-
|
|
35
|
-
console.log("[DB Backup] Log 3 Backup:", backup);
|
|
36
|
-
|
|
37
|
-
return backup;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
@Cron(CronExpression.EVERY_30_MINUTES)
|
|
41
|
-
public async runBackupWorker(): Promise<any> {
|
|
42
|
-
|
|
43
|
-
console.log("[DB Backup] Log 4 Running backup worker...");
|
|
44
|
-
|
|
45
|
-
return new Promise((resolve, reject) => {
|
|
46
|
-
// 👇 Path to the compiled worker file (dist folder after build)
|
|
47
|
-
const workerPath = join(__dirname, "backup.worker.js");
|
|
48
|
-
|
|
49
|
-
console.log("Worker path:", workerPath);
|
|
50
|
-
|
|
51
|
-
const worker = new Worker(workerPath);
|
|
52
|
-
|
|
53
|
-
worker.on("message", (msg) => {
|
|
54
|
-
this.logger.log(`Worker finished: ${JSON.stringify(msg)}`);
|
|
55
|
-
resolve(void 0);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
worker.on("error", (err) => {
|
|
59
|
-
this.logger.error(`Worker error: ${err.message}`);
|
|
60
|
-
reject(err);
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
worker.on("exit", (code) => {
|
|
64
|
-
if (code !== 0) this.logger.warn(`Worker exited with code ${code}`);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
public async createBackup(options: IDbBackupConfig): Promise<any> {
|
|
70
|
-
try {
|
|
71
|
-
// Validate configuration before starting backup
|
|
72
|
-
this.validateBackupOptions(options);
|
|
73
|
-
|
|
74
|
-
// Create backup directory if it doesn't exist
|
|
75
|
-
await this.ensureDirectoryExists(options.backup.dir);
|
|
76
|
-
|
|
77
|
-
console.log("Starting backup process...", options.mongodb);
|
|
78
|
-
|
|
79
|
-
for (const connectionString of options.mongodb.connectionStrings) {
|
|
80
|
-
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
81
|
-
|
|
82
|
-
const dbName = this.parseConnectionStringDatabase(connectionString);
|
|
83
|
-
|
|
84
|
-
if (!dbName) {
|
|
85
|
-
this.logger.warn(`No valid database name found in connection string: ${connectionString}`);
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
this.logger.log(`Creating backup for database: ${dbName}`);
|
|
90
|
-
|
|
91
|
-
const backupFileName = `${process.env.app_name || "dev"}_${dbName}_backup_${timestamp}.tar.gz`;
|
|
92
|
-
|
|
93
|
-
const localBackupPath = path.join(
|
|
94
|
-
options.backup.dir,
|
|
95
|
-
`${process.env.app_name || "dev"}_${dbName}_backup_${timestamp}`
|
|
96
|
-
);
|
|
97
|
-
const localBackupFile = path.join(options.backup.dir, backupFileName);
|
|
98
|
-
// Ensure the local backup directory exists
|
|
99
|
-
await this.ensureDirectoryExists(localBackupPath);
|
|
100
|
-
// Create MongoDB dump
|
|
101
|
-
await this.createMongoDump(connectionString, localBackupPath);
|
|
102
|
-
// Compress the backup
|
|
103
|
-
await this.compressBackup(localBackupPath, localBackupFile);
|
|
104
|
-
|
|
105
|
-
if (options?.backup?.isDownload) {
|
|
106
|
-
return await fs.readFile(localBackupFile);
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (options?.backup?.uploadToSpace) {
|
|
110
|
-
await this.uploadToSpaces(options.spaces, backupFileName, localBackupFile);
|
|
111
|
-
}
|
|
112
|
-
// Upload to DigitalOcean Spaces
|
|
113
|
-
// Clean up
|
|
114
|
-
await this.cleanup(localBackupPath, options.backup.dir, options.backup.maxBackups);
|
|
115
|
-
|
|
116
|
-
this.logger.log(`Backup for ${dbName} completed: ${backupFileName}`);
|
|
117
|
-
}
|
|
118
|
-
} catch (error) {
|
|
119
|
-
this.logger.error(`Backup failed: ${error.message}`);
|
|
120
|
-
throw error;
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
private validateBackupOptions(options: IDbBackupConfig): void {
|
|
125
|
-
if (!options.mongodb?.connectionStrings) {
|
|
126
|
-
throw new Error("MongoDB connection strings are required");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
const connectionStrings = Array.isArray(options.mongodb.connectionStrings)
|
|
130
|
-
? options.mongodb.connectionStrings
|
|
131
|
-
: [options.mongodb.connectionStrings];
|
|
132
|
-
|
|
133
|
-
if (connectionStrings.length === 0 || connectionStrings.some((cs) => !cs || cs.trim() === "")) {
|
|
134
|
-
throw new Error("At least one valid MongoDB connection string is required");
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
if (options?.backup?.uploadToSpace) {
|
|
138
|
-
if (!options.spaces) {
|
|
139
|
-
throw new Error("DigitalOcean Spaces configuration is required");
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
if (!options.spaces.name || !options.spaces.key || !options.spaces.secret || !options.spaces.endpoint) {
|
|
143
|
-
throw new Error(
|
|
144
|
-
"DigitalOcean Spaces configuration is incomplete (name, key, secret, endpoint are all required)"
|
|
145
|
-
);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
if (!options.backup?.dir) {
|
|
150
|
-
throw new Error("Backup directory is required");
|
|
151
|
-
}
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
private async ensureDirectoryExists(dir: string): Promise<void> {
|
|
155
|
-
try {
|
|
156
|
-
await fs.mkdir(dir, { recursive: true });
|
|
157
|
-
} catch (error) {
|
|
158
|
-
if (error.code !== "EEXIST") {
|
|
159
|
-
throw error;
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
private async createMongoDump(connectionString: string, outputPath: string): Promise<void> {
|
|
165
|
-
this.logger.log("Creating MongoDB dump...");
|
|
166
|
-
|
|
167
|
-
if (!connectionString) {
|
|
168
|
-
throw new Error("Connection string is required for MongoDB backup");
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
await execAsync(`mongodump --uri "${connectionString}" --out ${outputPath}`);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
private async compressBackup(sourcePath: string, destinationFile: string): Promise<void> {
|
|
175
|
-
this.logger.log("Compressing backup...");
|
|
176
|
-
await execAsync(`tar -czvf ${destinationFile} -C ${sourcePath} .`);
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
private async uploadToSpaces(
|
|
180
|
-
spacesConfig: IDbBackupConfig["spaces"],
|
|
181
|
-
fileName: string,
|
|
182
|
-
filePath: string
|
|
183
|
-
): Promise<void> {
|
|
184
|
-
this.logger.log("Uploading to DigitalOcean Spaces...");
|
|
185
|
-
|
|
186
|
-
// Validate required spaces configuration
|
|
187
|
-
if (!spacesConfig.name) {
|
|
188
|
-
throw new Error("DigitalOcean Spaces bucket name is required");
|
|
189
|
-
}
|
|
190
|
-
if (!spacesConfig.key) {
|
|
191
|
-
throw new Error("DigitalOcean Spaces access key is required");
|
|
192
|
-
}
|
|
193
|
-
if (!spacesConfig.secret) {
|
|
194
|
-
throw new Error("DigitalOcean Spaces secret key is required");
|
|
195
|
-
}
|
|
196
|
-
if (!spacesConfig.endpoint) {
|
|
197
|
-
throw new Error("DigitalOcean Spaces endpoint is required");
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// Ensure endpoint is a proper URL
|
|
201
|
-
let endpoint = spacesConfig.endpoint;
|
|
202
|
-
if (endpoint && !endpoint.startsWith("http://") && !endpoint.startsWith("https://")) {
|
|
203
|
-
endpoint = `https://${endpoint}`;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
const fileContent = await fs.readFile(filePath);
|
|
207
|
-
|
|
208
|
-
await this.uploadService.upload.uploadBuffer({
|
|
209
|
-
file: fileContent,
|
|
210
|
-
filename: fileName,
|
|
211
|
-
disableTransformName: true,
|
|
212
|
-
filetype: "application/gzip",
|
|
213
|
-
dir: spacesConfig.dir || "db-backups"
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
private async cleanup(backupPath: string, backupDir: string, maxBackups: number): Promise<void> {
|
|
218
|
-
this.logger.log("Cleaning up...");
|
|
219
|
-
|
|
220
|
-
// Remove the uncompressed backup
|
|
221
|
-
await fs.rm(backupPath, { recursive: true, force: true });
|
|
222
|
-
|
|
223
|
-
// Remove old backups
|
|
224
|
-
this.cleanupBackups();
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
public async cleanupBackups(): Promise<void> {
|
|
228
|
-
this.logger.log("Cleaning up...");
|
|
229
|
-
|
|
230
|
-
const { backup } = this.dbConfigService.getBackupConfig();
|
|
231
|
-
|
|
232
|
-
// Remove old backups
|
|
233
|
-
const files = await fs.readdir(backup.dir);
|
|
234
|
-
|
|
235
|
-
const backupFiles = files
|
|
236
|
-
.filter((file) => file.endsWith(".tar.gz"))
|
|
237
|
-
.sort()
|
|
238
|
-
.reverse();
|
|
239
|
-
|
|
240
|
-
if (backupFiles.length > backup.maxBackups) {
|
|
241
|
-
const toDelete = backupFiles.slice(backup.maxBackups);
|
|
242
|
-
for (const file of toDelete) {
|
|
243
|
-
console.log(file, "files to delete");
|
|
244
|
-
console.log(file?.replace(".tar.gz", ""), "files to delete");
|
|
245
|
-
|
|
246
|
-
await fs.unlink(path.join(backup.dir, file));
|
|
247
|
-
await fs.rm(path.join(backup.dir, file?.replace(".tar.gz", "")), {
|
|
248
|
-
recursive: true,
|
|
249
|
-
force: true
|
|
250
|
-
});
|
|
251
|
-
}
|
|
252
|
-
this.logger.log(`Removed ${toDelete.length} old backup(s)`);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
private parseConnectionStringDatabase(connectionString: string): string | null {
|
|
257
|
-
try {
|
|
258
|
-
// Remove any authentication info and query parameters for parsing
|
|
259
|
-
const cleanUri = connectionString.replace(/^mongodb(\+srv)?:\/\//, "");
|
|
260
|
-
|
|
261
|
-
// Find the database name after the last slash and before any query parameters
|
|
262
|
-
const dbMatch = cleanUri.match(/\/([^/?&]+)(\?|$)/);
|
|
263
|
-
|
|
264
|
-
if (dbMatch && dbMatch[1] && dbMatch[1] !== "admin" && dbMatch[1] !== "test") {
|
|
265
|
-
return dbMatch[1];
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
return null;
|
|
269
|
-
} catch (error) {
|
|
270
|
-
this.logger.warn(`Failed to parse database from connection string: ${error.message}`);
|
|
271
|
-
return null;
|
|
272
|
-
}
|
|
273
|
-
}
|
|
274
|
-
}
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
import { Module } from "@nestjs/common";
|
|
2
|
-
import { NestFactory } from "@nestjs/core";
|
|
3
|
-
import { MongooseModule } from "@nestjs/mongoose";
|
|
4
|
-
import { WinstonModule } from "nest-winston";
|
|
5
|
-
import { isMainThread, parentPort } from "worker_threads";
|
|
6
|
-
import { DbBackupModule } from "./backup.module";
|
|
7
|
-
import { DbBackupService } from "./backup.service";
|
|
8
|
-
|
|
9
|
-
@Module({
|
|
10
|
-
imports: [MongooseModule.forRoot(process.env.mongodb_url), DbBackupModule, WinstonModule.forRoot({})],
|
|
11
|
-
providers: [],
|
|
12
|
-
exports: []
|
|
13
|
-
})
|
|
14
|
-
class BackupWorkerModule {}
|
|
15
|
-
|
|
16
|
-
async function bootstrap() {
|
|
17
|
-
console.log("[Sales Worker] Bootstrap started");
|
|
18
|
-
|
|
19
|
-
try {
|
|
20
|
-
const app = await NestFactory.createApplicationContext(BackupWorkerModule, {});
|
|
21
|
-
const backupService = app.get(DbBackupService);
|
|
22
|
-
|
|
23
|
-
await backupService.runBackup();
|
|
24
|
-
|
|
25
|
-
parentPort?.postMessage({ status: "completed" });
|
|
26
|
-
await app.close();
|
|
27
|
-
process.exit(0);
|
|
28
|
-
} catch (error) {
|
|
29
|
-
console.error("[Sales Worker] Error:", error);
|
|
30
|
-
parentPort?.postMessage({ status: "error", message: error?.message, stack: error?.stack });
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!isMainThread) bootstrap();
|
package/libs/src/backup/index.ts
DELETED
package/libs/src/base/dto.ts
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { Field, ID, InputType, ObjectType } from "@nestjs/graphql";
|
|
2
|
-
|
|
3
|
-
@ObjectType()
|
|
4
|
-
export class BaseDto {
|
|
5
|
-
@Field(() => ID)
|
|
6
|
-
_id: string;
|
|
7
|
-
|
|
8
|
-
@Field({ nullable: true })
|
|
9
|
-
key?: string;
|
|
10
|
-
|
|
11
|
-
@Field({ nullable: true })
|
|
12
|
-
ref: string;
|
|
13
|
-
@Field(() => String, { nullable: true })
|
|
14
|
-
refId: string;
|
|
15
|
-
@Field({ nullable: true })
|
|
16
|
-
createdAt: number;
|
|
17
|
-
@Field({ nullable: true })
|
|
18
|
-
createdBy: string;
|
|
19
|
-
@Field({ nullable: true })
|
|
20
|
-
updatedAt: number;
|
|
21
|
-
@Field({ nullable: true })
|
|
22
|
-
updatedBy: string;
|
|
23
|
-
|
|
24
|
-
@Field({ nullable: true })
|
|
25
|
-
canDelete: boolean;
|
|
26
|
-
|
|
27
|
-
@Field({ nullable: true })
|
|
28
|
-
canUpdate: boolean;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
@InputType()
|
|
33
|
-
export class BasePageInput {
|
|
34
|
-
@Field((type) => Number, { nullable: false })
|
|
35
|
-
skip: number;
|
|
36
|
-
@Field((type) => Number, { nullable: false })
|
|
37
|
-
take: number;
|
|
38
|
-
@Field((type) => String, { nullable: true })
|
|
39
|
-
keyword: string;
|
|
40
|
-
|
|
41
|
-
@Field((type) => Number, { nullable: true })
|
|
42
|
-
fromDate: number;
|
|
43
|
-
|
|
44
|
-
@Field((type) => Number, { nullable: true })
|
|
45
|
-
toDate: number;
|
|
46
|
-
}
|
package/libs/src/base/index.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { Parent, ResolveField, Resolver } from "@nestjs/graphql";
|
|
2
|
-
import { BaseDto } from "./dto";
|
|
3
|
-
|
|
4
|
-
@Resolver((of) => BaseDto)
|
|
5
|
-
export class ApBaseResolver<T extends BaseDto> {
|
|
6
|
-
@ResolveField((of) => String, { name: "key" })
|
|
7
|
-
public async key(@Parent() parent: BaseDto) {
|
|
8
|
-
return parent?.key || parent?._id;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
@ResolveField((of) => Boolean, { name: "canUpdate" })
|
|
12
|
-
public async canUpdate(@Parent() parent: BaseDto) {
|
|
13
|
-
return parent.canUpdate == undefined ? true : parent.canUpdate;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
@ResolveField((of) => Boolean, { name: "canDelete" })
|
|
17
|
-
public async canDelete(@Parent() parent: BaseDto) {
|
|
18
|
-
return parent.canDelete == undefined ? true : parent.canDelete;
|
|
19
|
-
}
|
|
20
|
-
}
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
import { DynamicModule, Module } from "@nestjs/common";
|
|
2
|
-
import { ModelDefinition, MongooseModule } from "@nestjs/mongoose";
|
|
3
|
-
import { DatabaseService } from "./database.service";
|
|
4
|
-
import { TransactionManager } from "./database.transaction";
|
|
5
|
-
import { ApUniqueIdGenerator, UniqueId, UniqueIdSchema } from "./database.uniqueId";
|
|
6
|
-
|
|
7
|
-
export const CONNECTION_NAME = "ZYNC_DB_CONNECTION_NAME";
|
|
8
|
-
|
|
9
|
-
@Module({})
|
|
10
|
-
export class DatabaseModule {
|
|
11
|
-
static forFeature(models: ModelDefinition[], connectionName?: string): DynamicModule {
|
|
12
|
-
const MongooseModuleForFeature = connectionName
|
|
13
|
-
? MongooseModule.forFeature([...models, { name: UniqueId.name, schema: UniqueIdSchema }], connectionName)
|
|
14
|
-
: MongooseModule.forFeature([...models, { name: UniqueId.name, schema: UniqueIdSchema }]);
|
|
15
|
-
|
|
16
|
-
return {
|
|
17
|
-
module: DatabaseModule,
|
|
18
|
-
imports: [MongooseModuleForFeature],
|
|
19
|
-
providers: [
|
|
20
|
-
{ provide: CONNECTION_NAME, useValue: connectionName },
|
|
21
|
-
TransactionManager,
|
|
22
|
-
ApUniqueIdGenerator,
|
|
23
|
-
DatabaseService
|
|
24
|
-
],
|
|
25
|
-
exports: [MongooseModule, TransactionManager, ApUniqueIdGenerator, DatabaseService]
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
}
|