@eventista/ticketing-common 1.0.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 +2 -0
- package/dist/database/index.d.ts +2 -0
- package/dist/database/index.js +19 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/mongodb/index.d.ts +2 -0
- package/dist/database/mongodb/index.js +19 -0
- package/dist/database/mongodb/index.js.map +1 -0
- package/dist/database/mongodb/mongodb.module.d.ts +2 -0
- package/dist/database/mongodb/mongodb.module.js +50 -0
- package/dist/database/mongodb/mongodb.module.js.map +1 -0
- package/dist/database/mongodb/mongodb.service.d.ts +29 -0
- package/dist/database/mongodb/mongodb.service.js +114 -0
- package/dist/database/mongodb/mongodb.service.js.map +1 -0
- package/dist/database/redis/index.d.ts +2 -0
- package/dist/database/redis/index.js +19 -0
- package/dist/database/redis/index.js.map +1 -0
- package/dist/database/redis/redis.module.d.ts +2 -0
- package/dist/database/redis/redis.module.js +52 -0
- package/dist/database/redis/redis.module.js.map +1 -0
- package/dist/database/redis/redis.service.d.ts +26 -0
- package/dist/database/redis/redis.service.js +126 -0
- package/dist/database/redis/redis.service.js.map +1 -0
- package/dist/exception/exception.filter.d.ts +4 -0
- package/dist/exception/exception.filter.js +35 -0
- package/dist/exception/exception.filter.js.map +1 -0
- package/dist/generic-repository/index.d.ts +4 -0
- package/dist/generic-repository/index.js +21 -0
- package/dist/generic-repository/index.js.map +1 -0
- package/dist/generic-repository/repositories/base.repository.d.ts +39 -0
- package/dist/generic-repository/repositories/base.repository.interface.d.ts +36 -0
- package/dist/generic-repository/repositories/base.repository.interface.js +3 -0
- package/dist/generic-repository/repositories/base.repository.interface.js.map +1 -0
- package/dist/generic-repository/repositories/base.repository.js +96 -0
- package/dist/generic-repository/repositories/base.repository.js.map +1 -0
- package/dist/generic-repository/services/base.service.d.ts +42 -0
- package/dist/generic-repository/services/base.service.interface.d.ts +36 -0
- package/dist/generic-repository/services/base.service.interface.js +3 -0
- package/dist/generic-repository/services/base.service.interface.js.map +1 -0
- package/dist/generic-repository/services/base.service.js +57 -0
- package/dist/generic-repository/services/base.service.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/logger/custom.logger.d.ts +14 -0
- package/dist/logger/custom.logger.js +76 -0
- package/dist/logger/custom.logger.js.map +1 -0
- package/dist/logger/index.d.ts +2 -0
- package/dist/logger/index.js +19 -0
- package/dist/logger/index.js.map +1 -0
- package/dist/logger/logger.module.d.ts +2 -0
- package/dist/logger/logger.module.js +27 -0
- package/dist/logger/logger.module.js.map +1 -0
- package/dist/pipes/custom-validation.pipe.d.ts +7 -0
- package/dist/pipes/custom-validation.pipe.js +69 -0
- package/dist/pipes/custom-validation.pipe.js.map +1 -0
- package/dist/response/response.interceptor.d.ts +5 -0
- package/dist/response/response.interceptor.js +29 -0
- package/dist/response/response.interceptor.js.map +1 -0
- package/dist/schemas/base.schema.d.ts +11 -0
- package/dist/schemas/base.schema.js +29 -0
- package/dist/schemas/base.schema.js.map +1 -0
- package/dist/schemas/event/event.interfaces.d.ts +69 -0
- package/dist/schemas/event/event.interfaces.js +9 -0
- package/dist/schemas/event/event.interfaces.js.map +1 -0
- package/dist/schemas/event/event.schema.d.ts +70 -0
- package/dist/schemas/event/event.schema.js +242 -0
- package/dist/schemas/event/event.schema.js.map +1 -0
- package/dist/schemas/index.d.ts +14 -0
- package/dist/schemas/index.js +31 -0
- package/dist/schemas/index.js.map +1 -0
- package/dist/schemas/order/order.interfaces.d.ts +72 -0
- package/dist/schemas/order/order.interfaces.js +31 -0
- package/dist/schemas/order/order.interfaces.js.map +1 -0
- package/dist/schemas/order/order.schema.d.ts +39 -0
- package/dist/schemas/order/order.schema.js +162 -0
- package/dist/schemas/order/order.schema.js.map +1 -0
- package/dist/schemas/promotions/promotions.interfaces.d.ts +30 -0
- package/dist/schemas/promotions/promotions.interfaces.js +14 -0
- package/dist/schemas/promotions/promotions.interfaces.js.map +1 -0
- package/dist/schemas/promotions/promotions.schema.d.ts +25 -0
- package/dist/schemas/promotions/promotions.schema.js +92 -0
- package/dist/schemas/promotions/promotions.schema.js.map +1 -0
- package/dist/schemas/rows/rows.interfaces.d.ts +14 -0
- package/dist/schemas/rows/rows.interfaces.js +3 -0
- package/dist/schemas/rows/rows.interfaces.js.map +1 -0
- package/dist/schemas/rows/rows.schema.d.ts +22 -0
- package/dist/schemas/rows/rows.schema.js +65 -0
- package/dist/schemas/rows/rows.schema.js.map +1 -0
- package/dist/schemas/schema.helper.d.ts +4 -0
- package/dist/schemas/schema.helper.js +84 -0
- package/dist/schemas/schema.helper.js.map +1 -0
- package/dist/schemas/ticket-classes/ticket-classes.interfaces.d.ts +18 -0
- package/dist/schemas/ticket-classes/ticket-classes.interfaces.js +3 -0
- package/dist/schemas/ticket-classes/ticket-classes.interfaces.js.map +1 -0
- package/dist/schemas/ticket-classes/ticket-classes.schemas.d.ts +26 -0
- package/dist/schemas/ticket-classes/ticket-classes.schemas.js +75 -0
- package/dist/schemas/ticket-classes/ticket-classes.schemas.js.map +1 -0
- package/dist/schemas/ticket-summary/ticket-summary.interfaces.d.ts +19 -0
- package/dist/schemas/ticket-summary/ticket-summary.interfaces.js +9 -0
- package/dist/schemas/ticket-summary/ticket-summary.interfaces.js.map +1 -0
- package/dist/schemas/ticket-summary/ticket-summary.schema.d.ts +23 -0
- package/dist/schemas/ticket-summary/ticket-summary.schema.js +65 -0
- package/dist/schemas/ticket-summary/ticket-summary.schema.js.map +1 -0
- package/dist/schemas/user-fanpass/user-fanpass.interfaces.d.ts +26 -0
- package/dist/schemas/user-fanpass/user-fanpass.interfaces.js +9 -0
- package/dist/schemas/user-fanpass/user-fanpass.interfaces.js.map +1 -0
- package/dist/schemas/user-fanpass/user-fanpass.schemas.d.ts +29 -0
- package/dist/schemas/user-fanpass/user-fanpass.schemas.js +113 -0
- package/dist/schemas/user-fanpass/user-fanpass.schemas.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +40 -0
- package/src/database/index.ts +2 -0
- package/src/database/mongodb/index.ts +2 -0
- package/src/database/mongodb/mongodb.module.ts +42 -0
- package/src/database/mongodb/mongodb.service.ts +111 -0
- package/src/database/redis/index.ts +2 -0
- package/src/database/redis/redis.module.ts +45 -0
- package/src/database/redis/redis.service.ts +142 -0
- package/src/exception/exception.filter.ts +36 -0
- package/src/generic-repository/index.ts +7 -0
- package/src/generic-repository/repositories/base.repository.interface.ts +78 -0
- package/src/generic-repository/repositories/base.repository.ts +199 -0
- package/src/generic-repository/services/base.service.interface.ts +78 -0
- package/src/generic-repository/services/base.service.ts +165 -0
- package/src/index.ts +11 -0
- package/src/logger/custom.logger.ts +83 -0
- package/src/logger/index.ts +2 -0
- package/src/logger/logger.module.ts +14 -0
- package/src/pipes/custom-validation.pipe.ts +61 -0
- package/src/response/response.interceptor.ts +26 -0
- package/src/schemas/base.schema.ts +19 -0
- package/src/schemas/event/event.interfaces.ts +74 -0
- package/src/schemas/event/event.schema.ts +195 -0
- package/src/schemas/index.ts +16 -0
- package/src/schemas/order/order.interfaces.ts +78 -0
- package/src/schemas/order/order.schema.ts +120 -0
- package/src/schemas/promotions/promotions.interfaces.ts +34 -0
- package/src/schemas/promotions/promotions.schema.ts +71 -0
- package/src/schemas/rows/rows.interfaces.ts +14 -0
- package/src/schemas/rows/rows.schema.ts +47 -0
- package/src/schemas/schema.helper.ts +103 -0
- package/src/schemas/ticket-classes/ticket-classes.interfaces.ts +18 -0
- package/src/schemas/ticket-classes/ticket-classes.schemas.ts +51 -0
- package/src/schemas/ticket-summary/ticket-summary.interfaces.ts +20 -0
- package/src/schemas/ticket-summary/ticket-summary.schema.ts +46 -0
- package/src/schemas/user-fanpass/user-fanpass.interfaces.ts +28 -0
- package/src/schemas/user-fanpass/user-fanpass.schemas.ts +86 -0
- package/tsconfig.json +17 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
|
|
2
|
+
import { InjectConnection } from '@nestjs/mongoose';
|
|
3
|
+
import { Connection } from 'mongoose';
|
|
4
|
+
|
|
5
|
+
@Injectable()
|
|
6
|
+
export class MongodbService implements OnModuleInit {
|
|
7
|
+
private readonly logger = new Logger(MongodbService.name);
|
|
8
|
+
|
|
9
|
+
constructor(
|
|
10
|
+
@InjectConnection()
|
|
11
|
+
private readonly connection: Connection,
|
|
12
|
+
) { }
|
|
13
|
+
|
|
14
|
+
async onModuleInit() {
|
|
15
|
+
try {
|
|
16
|
+
// Kiểm tra kết nối khi khởi động
|
|
17
|
+
if (this.connection.readyState === 1) {
|
|
18
|
+
this.logger.log('MongoDB connected successfully');
|
|
19
|
+
|
|
20
|
+
// Log thông tin về connection pool
|
|
21
|
+
const poolInfo = this.getConnectionPoolInfo();
|
|
22
|
+
this.logger.log(`MongoDB connection pool initialized: ${JSON.stringify(poolInfo)}`);
|
|
23
|
+
} else {
|
|
24
|
+
this.logger.warn(`MongoDB connection state: ${this.getReadyStateText(this.connection.readyState)}`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Thiết lập event listeners
|
|
28
|
+
this.setupConnectionListeners();
|
|
29
|
+
} catch (error) {
|
|
30
|
+
this.logger.error('Failed to initialize MongoDB connection', error.stack);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
private setupConnectionListeners() {
|
|
35
|
+
this.connection.on('connected', () => {
|
|
36
|
+
this.logger.log('MongoDB connected');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
this.connection.on('disconnected', () => {
|
|
40
|
+
this.logger.warn('MongoDB disconnected');
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
this.connection.on('reconnected', () => {
|
|
44
|
+
this.logger.log('MongoDB reconnected');
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
this.connection.on('error', (error) => {
|
|
48
|
+
this.logger.error('MongoDB connection error', error.stack);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
getConnection(): Connection {
|
|
53
|
+
return this.connection;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
getConnectionStatus(): string {
|
|
57
|
+
return this.getReadyStateText(this.connection.readyState);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async ping(): Promise<boolean> {
|
|
61
|
+
try {
|
|
62
|
+
await this.connection.db.admin().ping();
|
|
63
|
+
return true;
|
|
64
|
+
} catch (error) {
|
|
65
|
+
this.logger.error('MongoDB ping failed', error.stack);
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
getConnectionPoolInfo() {
|
|
71
|
+
// Sửa lại cách truy cập thông tin connection pool
|
|
72
|
+
try {
|
|
73
|
+
// Cách 1: Truy cập thông qua mongoose driver
|
|
74
|
+
if (this.connection.getClient) {
|
|
75
|
+
const client = this.connection.getClient();
|
|
76
|
+
return {
|
|
77
|
+
maxPoolSize: client.options?.maxPoolSize || 'default',
|
|
78
|
+
minPoolSize: client.options?.minPoolSize || 'default',
|
|
79
|
+
readyState: this.getReadyStateText(this.connection.readyState),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Cách 2: Nếu không có getClient, sử dụng cách khác
|
|
84
|
+
return {
|
|
85
|
+
readyState: this.getReadyStateText(this.connection.readyState),
|
|
86
|
+
// Thêm các thông tin khác nếu có thể truy cập
|
|
87
|
+
};
|
|
88
|
+
} catch (error) {
|
|
89
|
+
this.logger.warn('Could not retrieve connection pool info', error.message);
|
|
90
|
+
return {
|
|
91
|
+
readyState: this.getReadyStateText(this.connection.readyState),
|
|
92
|
+
error: 'Could not retrieve pool information',
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
private getReadyStateText(state: number): string {
|
|
98
|
+
switch (state) {
|
|
99
|
+
case 0:
|
|
100
|
+
return 'disconnected';
|
|
101
|
+
case 1:
|
|
102
|
+
return 'connected';
|
|
103
|
+
case 2:
|
|
104
|
+
return 'connecting';
|
|
105
|
+
case 3:
|
|
106
|
+
return 'disconnecting';
|
|
107
|
+
default:
|
|
108
|
+
return 'unknown';
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { Module, Global } from '@nestjs/common';
|
|
2
|
+
import { ConfigModule, ConfigService } from '@nestjs/config';
|
|
3
|
+
import { RedisService } from './redis.service';
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
@Global()
|
|
7
|
+
@Module({
|
|
8
|
+
imports: [ConfigModule],
|
|
9
|
+
providers: [
|
|
10
|
+
{
|
|
11
|
+
provide: 'REDIS_CLIENT',
|
|
12
|
+
useFactory: (configService: ConfigService) => {
|
|
13
|
+
const Redis = require('ioredis');
|
|
14
|
+
return new Redis({
|
|
15
|
+
host: configService.get('REDIS_HOST', 'localhost'),
|
|
16
|
+
port: configService.get('REDIS_PORT', 6379),
|
|
17
|
+
password: configService.get('REDIS_PASSWORD', 'StrongRedisPassword123'),
|
|
18
|
+
db: configService.get('REDIS_DB', 0),
|
|
19
|
+
// Tối ưu connection pool
|
|
20
|
+
maxRetriesPerRequest: 3,
|
|
21
|
+
connectTimeout: 10000,
|
|
22
|
+
// Tăng số lượng kết nối trong pool
|
|
23
|
+
connectionName: 'ticket-service',
|
|
24
|
+
// Tối ưu hóa retry strategy
|
|
25
|
+
retryStrategy: (times) => {
|
|
26
|
+
if (times > 3) {
|
|
27
|
+
return null; // stop retrying
|
|
28
|
+
}
|
|
29
|
+
return Math.min(times * 50, 1000);
|
|
30
|
+
},
|
|
31
|
+
// Tối ưu hóa các tùy chọn khác
|
|
32
|
+
enableReadyCheck: false,
|
|
33
|
+
enableOfflineQueue: true,
|
|
34
|
+
// Tối ưu hóa TCP keepalive
|
|
35
|
+
keepAlive: 10000,
|
|
36
|
+
noDelay: true,
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
inject: [ConfigService],
|
|
40
|
+
},
|
|
41
|
+
RedisService,
|
|
42
|
+
],
|
|
43
|
+
exports: [RedisService],
|
|
44
|
+
})
|
|
45
|
+
export class RedisModule {}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { Injectable, Inject, OnModuleDestroy } from '@nestjs/common';
|
|
2
|
+
import { Redis } from 'ioredis';
|
|
3
|
+
|
|
4
|
+
@Injectable()
|
|
5
|
+
export class RedisService implements OnModuleDestroy {
|
|
6
|
+
constructor(
|
|
7
|
+
@Inject('REDIS_CLIENT')
|
|
8
|
+
private readonly redis: Redis,
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
getClient(): Redis {
|
|
12
|
+
return this.redis;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async get(key: string): Promise<string | null> {
|
|
16
|
+
return this.redis.get(key);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async set(key: string, value: string, ttl?: number): Promise<'OK'> {
|
|
20
|
+
if (ttl) {
|
|
21
|
+
return this.redis.set(key, value, 'EX', ttl);
|
|
22
|
+
}
|
|
23
|
+
return this.redis.set(key, value);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async del(key: string): Promise<number> {
|
|
27
|
+
return this.redis.del(key);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
async incr(key: string): Promise<number> {
|
|
31
|
+
return this.redis.incr(key);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async decr(key: string): Promise<number> {
|
|
35
|
+
return this.redis.decr(key);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async incrby(key: string, increment: number): Promise<number> {
|
|
39
|
+
return this.redis.incrby(key, increment);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async decrby(key: string, decrement: number): Promise<number> {
|
|
43
|
+
return this.redis.decrby(key, decrement);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Thêm phương thức để thực hiện nhiều lệnh Redis trong một pipeline
|
|
47
|
+
async pipeline(commands: Array<[string, ...any[]]>): Promise<any[]> {
|
|
48
|
+
const pipeline = this.redis.pipeline();
|
|
49
|
+
|
|
50
|
+
for (const [command, ...args] of commands) {
|
|
51
|
+
pipeline[command](...args);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return pipeline.exec();
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Tối ưu phương thức executeScript
|
|
58
|
+
async executeScript(script: string, keys: string[], args: string[]): Promise<any> {
|
|
59
|
+
// Sử dụng defineCommand để tối ưu hiệu suất cho các script được sử dụng nhiều lần
|
|
60
|
+
const scriptHash = require('crypto').createHash('sha1').update(script).digest('hex');
|
|
61
|
+
|
|
62
|
+
// Kiểm tra xem script đã được cache chưa
|
|
63
|
+
try {
|
|
64
|
+
return await this.redis.evalsha(scriptHash, keys.length, ...keys, ...args);
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err.message.includes('NOSCRIPT')) {
|
|
67
|
+
// Script chưa được cache, thực thi và cache
|
|
68
|
+
return this.redis.eval(script, keys.length, ...keys, ...args);
|
|
69
|
+
}
|
|
70
|
+
throw err;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async releaseLock(lockKey: string, value: string): Promise<boolean> {
|
|
75
|
+
const script = `
|
|
76
|
+
if redis.call("get", KEYS[1]) == ARGV[1] then
|
|
77
|
+
return redis.call("del", KEYS[1])
|
|
78
|
+
else
|
|
79
|
+
return 0
|
|
80
|
+
end
|
|
81
|
+
`;
|
|
82
|
+
const result = await this.redis.eval(script, 1, lockKey, value);
|
|
83
|
+
return result === 1;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Thêm phương thức zadd để hỗ trợ sorted sets
|
|
87
|
+
async zadd(key: string, ...args: string[]): Promise<number> {
|
|
88
|
+
return this.redis.zadd(key, ...args);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Thêm các phương thức khác cho sorted sets
|
|
92
|
+
async zrevrange(key: string, start: number, stop: number, withScores?: boolean): Promise<string[]> {
|
|
93
|
+
if (withScores) {
|
|
94
|
+
return this.redis.zrevrange(key, start, stop, 'WITHSCORES');
|
|
95
|
+
}
|
|
96
|
+
return this.redis.zrevrange(key, start, stop);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
async zrange(key: string, start: number, stop: number, withScores?: boolean): Promise<string[]> {
|
|
100
|
+
if (withScores) {
|
|
101
|
+
return this.redis.zrange(key, start, stop, 'WITHSCORES');
|
|
102
|
+
}
|
|
103
|
+
return this.redis.zrange(key, start, stop);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async zrem(key: string, ...members: string[]): Promise<number> {
|
|
107
|
+
return this.redis.zrem(key, ...members);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async zscore(key: string, member: string): Promise<string | null> {
|
|
111
|
+
return this.redis.zscore(key, member);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Thêm phương thức hỗ trợ hash maps
|
|
115
|
+
async hmset(key: string, hash: Record<string, string>): Promise<'OK'> {
|
|
116
|
+
return this.redis.hmset(key, hash);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async hgetall(key: string): Promise<Record<string, string>> {
|
|
120
|
+
return this.redis.hgetall(key);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Lấy nhiều giá trị từ Redis
|
|
125
|
+
* @param keys Danh sách các key cần lấy
|
|
126
|
+
* @returns Danh sách các giá trị tương ứng
|
|
127
|
+
*/
|
|
128
|
+
async mget(...keys: string[]): Promise<(string | null)[]> {
|
|
129
|
+
try {
|
|
130
|
+
const client = this.getClient();
|
|
131
|
+
const values = await client.mget(keys);
|
|
132
|
+
return values;
|
|
133
|
+
} catch (error) {
|
|
134
|
+
// Trả về mảng null với độ dài bằng số lượng key
|
|
135
|
+
return Array(keys.length).fill(null);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
onModuleDestroy() {
|
|
140
|
+
this.redis.disconnect();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ArgumentsHost,
|
|
3
|
+
Catch,
|
|
4
|
+
ExceptionFilter,
|
|
5
|
+
HttpException,
|
|
6
|
+
HttpStatus,
|
|
7
|
+
} from '@nestjs/common';
|
|
8
|
+
|
|
9
|
+
@Catch()
|
|
10
|
+
export class GlobalExceptionFilter implements ExceptionFilter {
|
|
11
|
+
catch(exception: any, host: ArgumentsHost) {
|
|
12
|
+
const ctx = host.switchToHttp();
|
|
13
|
+
const response = ctx.getResponse();
|
|
14
|
+
const request = ctx.getRequest();
|
|
15
|
+
// Lấy mã trạng thái
|
|
16
|
+
const status =
|
|
17
|
+
exception instanceof HttpException
|
|
18
|
+
? exception.getStatus()
|
|
19
|
+
: HttpStatus.INTERNAL_SERVER_ERROR;
|
|
20
|
+
|
|
21
|
+
const errorResponse = {
|
|
22
|
+
errorCode:
|
|
23
|
+
exception instanceof HttpException
|
|
24
|
+
? exception.getResponse()['code']
|
|
25
|
+
: status,
|
|
26
|
+
message: exception.message || 'Internal Server Error',
|
|
27
|
+
data: exception instanceof HttpException ? exception.getResponse()['data'] : null,
|
|
28
|
+
|
|
29
|
+
path: request.url,
|
|
30
|
+
timestamp: new Date().toISOString(),
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Sử dụng phương thức Fastify để trả về phản hồi
|
|
34
|
+
response.status(status).send(errorResponse); // Fastify sử dụng `send` để trả về JSON
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Document, FilterQuery, UpdateQuery, QueryOptions, PipelineStage } from 'mongoose';
|
|
2
|
+
|
|
3
|
+
export interface IBaseRepository<T extends Document> {
|
|
4
|
+
findOne(
|
|
5
|
+
filterQuery: FilterQuery<T>,
|
|
6
|
+
projection?: Record<string, unknown>,
|
|
7
|
+
options?: QueryOptions,
|
|
8
|
+
): Promise<T | null>;
|
|
9
|
+
|
|
10
|
+
findOneOrFail(
|
|
11
|
+
filterQuery: FilterQuery<T>,
|
|
12
|
+
projection?: Record<string, unknown>,
|
|
13
|
+
options?: QueryOptions,
|
|
14
|
+
): Promise<T>;
|
|
15
|
+
|
|
16
|
+
find(
|
|
17
|
+
filterQuery: FilterQuery<T>,
|
|
18
|
+
projection?: Record<string, unknown>,
|
|
19
|
+
options?: QueryOptions,
|
|
20
|
+
): Promise<T[]>;
|
|
21
|
+
|
|
22
|
+
create(createDto: Partial<T>): Promise<T>;
|
|
23
|
+
|
|
24
|
+
createMany(createDtos: Partial<T>[]): Promise<T[]>;
|
|
25
|
+
|
|
26
|
+
findOneAndUpdate(
|
|
27
|
+
filterQuery: FilterQuery<T>,
|
|
28
|
+
updateQuery: UpdateQuery<T>,
|
|
29
|
+
options?: QueryOptions,
|
|
30
|
+
): Promise<T | null>;
|
|
31
|
+
|
|
32
|
+
findOneAndUpdateOrFail(
|
|
33
|
+
filterQuery: FilterQuery<T>,
|
|
34
|
+
updateQuery: UpdateQuery<T>,
|
|
35
|
+
options?: QueryOptions,
|
|
36
|
+
): Promise<T>;
|
|
37
|
+
|
|
38
|
+
updateMany(
|
|
39
|
+
filterQuery: FilterQuery<T>,
|
|
40
|
+
updateQuery: UpdateQuery<T>,
|
|
41
|
+
): Promise<{ matchedCount: number; modifiedCount: number }>;
|
|
42
|
+
|
|
43
|
+
findOneAndDelete(
|
|
44
|
+
filterQuery: FilterQuery<T>,
|
|
45
|
+
options?: QueryOptions,
|
|
46
|
+
): Promise<T | null>;
|
|
47
|
+
|
|
48
|
+
findOneAndDeleteOrFail(
|
|
49
|
+
filterQuery: FilterQuery<T>,
|
|
50
|
+
options?: QueryOptions,
|
|
51
|
+
): Promise<T>;
|
|
52
|
+
|
|
53
|
+
deleteMany(filterQuery: FilterQuery<T>): Promise<{ deletedCount: number }>;
|
|
54
|
+
|
|
55
|
+
count(filterQuery: FilterQuery<T>): Promise<number>;
|
|
56
|
+
|
|
57
|
+
exists(filterQuery: FilterQuery<T>): Promise<boolean>;
|
|
58
|
+
|
|
59
|
+
aggregate(pipeline: PipelineStage[]): Promise<any[]>;
|
|
60
|
+
|
|
61
|
+
paginate(
|
|
62
|
+
filterQuery: FilterQuery<T>,
|
|
63
|
+
options?: {
|
|
64
|
+
page?: number;
|
|
65
|
+
limit?: number;
|
|
66
|
+
sort?: Record<string, 1 | -1>;
|
|
67
|
+
projection?: Record<string, unknown>;
|
|
68
|
+
},
|
|
69
|
+
): Promise<{
|
|
70
|
+
data: T[];
|
|
71
|
+
pagination: {
|
|
72
|
+
total: number;
|
|
73
|
+
page: number;
|
|
74
|
+
limit: number;
|
|
75
|
+
pages: number;
|
|
76
|
+
};
|
|
77
|
+
}>;
|
|
78
|
+
}
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { Document, FilterQuery, Model, UpdateQuery, QueryOptions, PipelineStage } from 'mongoose';
|
|
2
|
+
import { NotFoundException } from '@nestjs/common';
|
|
3
|
+
import { IBaseRepository } from './base.repository.interface';
|
|
4
|
+
|
|
5
|
+
export class BaseRepository<T extends Document> implements IBaseRepository<T> {
|
|
6
|
+
constructor(protected readonly model: Model<T>) {}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Tìm một document theo điều kiện
|
|
10
|
+
*/
|
|
11
|
+
async findOne(
|
|
12
|
+
filterQuery: FilterQuery<T>,
|
|
13
|
+
projection?: Record<string, unknown>,
|
|
14
|
+
options?: QueryOptions,
|
|
15
|
+
): Promise<T | null> {
|
|
16
|
+
return this.model.findOne(filterQuery, projection, options).exec();
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Tìm một document theo điều kiện hoặc throw exception nếu không tìm thấy
|
|
21
|
+
*/
|
|
22
|
+
async findOneOrFail(
|
|
23
|
+
filterQuery: FilterQuery<T>,
|
|
24
|
+
projection?: Record<string, unknown>,
|
|
25
|
+
options?: QueryOptions,
|
|
26
|
+
): Promise<T> {
|
|
27
|
+
const document = await this.findOne(filterQuery, projection, options);
|
|
28
|
+
if (!document) {
|
|
29
|
+
throw new NotFoundException(`${this.model.modelName} not found`);
|
|
30
|
+
}
|
|
31
|
+
return document;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Tìm nhiều documents theo điều kiện
|
|
36
|
+
*/
|
|
37
|
+
async find(
|
|
38
|
+
filterQuery: FilterQuery<T>,
|
|
39
|
+
projection?: Record<string, unknown>,
|
|
40
|
+
options?: QueryOptions,
|
|
41
|
+
): Promise<T[]> {
|
|
42
|
+
return this.model.find(filterQuery, projection, options).exec();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Tạo một document mới
|
|
47
|
+
*/
|
|
48
|
+
async create(createDto: Partial<T>): Promise<T> {
|
|
49
|
+
const newDocument = new this.model(createDto);
|
|
50
|
+
return newDocument.save();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Tạo nhiều documents
|
|
55
|
+
*/
|
|
56
|
+
async createMany(createDtos: Partial<T>[]): Promise<T[]> {
|
|
57
|
+
const result = await this.model.insertMany(createDtos);
|
|
58
|
+
return result as unknown as T[];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Cập nhật một document theo điều kiện
|
|
63
|
+
*/
|
|
64
|
+
async findOneAndUpdate(
|
|
65
|
+
filterQuery: FilterQuery<T>,
|
|
66
|
+
updateQuery: UpdateQuery<T>,
|
|
67
|
+
options: QueryOptions = { new: true },
|
|
68
|
+
): Promise<T | null> {
|
|
69
|
+
return this.model.findOneAndUpdate(filterQuery, updateQuery, options).exec();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Cập nhật một document theo điều kiện hoặc throw exception nếu không tìm thấy
|
|
74
|
+
*/
|
|
75
|
+
async findOneAndUpdateOrFail(
|
|
76
|
+
filterQuery: FilterQuery<T>,
|
|
77
|
+
updateQuery: UpdateQuery<T>,
|
|
78
|
+
options: QueryOptions = { new: true },
|
|
79
|
+
): Promise<T> {
|
|
80
|
+
const document = await this.findOneAndUpdate(filterQuery, updateQuery, options);
|
|
81
|
+
if (!document) {
|
|
82
|
+
throw new NotFoundException(`${this.model.modelName} not found`);
|
|
83
|
+
}
|
|
84
|
+
return document;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Cập nhật nhiều documents theo điều kiện
|
|
89
|
+
*/
|
|
90
|
+
async updateMany(
|
|
91
|
+
filterQuery: FilterQuery<T>,
|
|
92
|
+
updateQuery: UpdateQuery<T>,
|
|
93
|
+
): Promise<{ matchedCount: number; modifiedCount: number }> {
|
|
94
|
+
const result = await this.model.updateMany(filterQuery, updateQuery).exec();
|
|
95
|
+
return {
|
|
96
|
+
matchedCount: result.matchedCount,
|
|
97
|
+
modifiedCount: result.modifiedCount,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Xóa một document theo điều kiện
|
|
103
|
+
*/
|
|
104
|
+
async findOneAndDelete(
|
|
105
|
+
filterQuery: FilterQuery<T>,
|
|
106
|
+
options?: QueryOptions,
|
|
107
|
+
): Promise<T | null> {
|
|
108
|
+
return this.model.findOneAndDelete(filterQuery, options).exec();
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Xóa một document theo điều kiện hoặc throw exception nếu không tìm thấy
|
|
113
|
+
*/
|
|
114
|
+
async findOneAndDeleteOrFail(
|
|
115
|
+
filterQuery: FilterQuery<T>,
|
|
116
|
+
options?: QueryOptions,
|
|
117
|
+
): Promise<T> {
|
|
118
|
+
const document = await this.findOneAndDelete(filterQuery, options);
|
|
119
|
+
if (!document) {
|
|
120
|
+
throw new NotFoundException(`${this.model.modelName} not found`);
|
|
121
|
+
}
|
|
122
|
+
return document;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Xóa nhiều documents theo điều kiện
|
|
127
|
+
*/
|
|
128
|
+
async deleteMany(filterQuery: FilterQuery<T>): Promise<{ deletedCount: number }> {
|
|
129
|
+
const result = await this.model.deleteMany(filterQuery).exec();
|
|
130
|
+
return { deletedCount: result.deletedCount || 0 };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Đếm số lượng documents theo điều kiện
|
|
135
|
+
*/
|
|
136
|
+
async count(filterQuery: FilterQuery<T>): Promise<number> {
|
|
137
|
+
return this.model.countDocuments(filterQuery).exec();
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Kiểm tra document có tồn tại không
|
|
142
|
+
*/
|
|
143
|
+
async exists(filterQuery: FilterQuery<T>): Promise<boolean> {
|
|
144
|
+
const count = await this.count(filterQuery);
|
|
145
|
+
return count > 0;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Thực hiện aggregation pipeline
|
|
150
|
+
*/
|
|
151
|
+
async aggregate(pipeline: PipelineStage[]): Promise<any[]> {
|
|
152
|
+
return this.model.aggregate(pipeline).exec();
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Phân trang
|
|
157
|
+
*/
|
|
158
|
+
async paginate(
|
|
159
|
+
filterQuery: FilterQuery<T>,
|
|
160
|
+
options: {
|
|
161
|
+
page?: number;
|
|
162
|
+
limit?: number;
|
|
163
|
+
sort?: Record<string, 1 | -1>;
|
|
164
|
+
projection?: Record<string, unknown>;
|
|
165
|
+
} = {},
|
|
166
|
+
): Promise<{
|
|
167
|
+
data: T[];
|
|
168
|
+
pagination: {
|
|
169
|
+
total: number;
|
|
170
|
+
page: number;
|
|
171
|
+
limit: number;
|
|
172
|
+
pages: number;
|
|
173
|
+
};
|
|
174
|
+
}> {
|
|
175
|
+
const page = options.page || 1;
|
|
176
|
+
const limit = options.limit || 10;
|
|
177
|
+
const skip = (page - 1) * limit;
|
|
178
|
+
|
|
179
|
+
const [data, total] = await Promise.all([
|
|
180
|
+
this.model
|
|
181
|
+
.find(filterQuery, options.projection)
|
|
182
|
+
.sort(options.sort)
|
|
183
|
+
.skip(skip)
|
|
184
|
+
.limit(limit)
|
|
185
|
+
.exec(),
|
|
186
|
+
this.count(filterQuery),
|
|
187
|
+
]);
|
|
188
|
+
|
|
189
|
+
return {
|
|
190
|
+
data,
|
|
191
|
+
pagination: {
|
|
192
|
+
total,
|
|
193
|
+
page,
|
|
194
|
+
limit,
|
|
195
|
+
pages: Math.ceil(total / limit),
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { Document, FilterQuery, UpdateQuery, QueryOptions, PipelineStage } from 'mongoose';
|
|
2
|
+
|
|
3
|
+
export interface IBaseService<T extends Document> {
|
|
4
|
+
findOne(
|
|
5
|
+
filterQuery: FilterQuery<T>,
|
|
6
|
+
projection?: Record<string, unknown>,
|
|
7
|
+
options?: QueryOptions,
|
|
8
|
+
): Promise<T | null>;
|
|
9
|
+
|
|
10
|
+
findOneOrFail(
|
|
11
|
+
filterQuery: FilterQuery<T>,
|
|
12
|
+
projection?: Record<string, unknown>,
|
|
13
|
+
options?: QueryOptions,
|
|
14
|
+
): Promise<T>;
|
|
15
|
+
|
|
16
|
+
find(
|
|
17
|
+
filterQuery: FilterQuery<T>,
|
|
18
|
+
projection?: Record<string, unknown>,
|
|
19
|
+
options?: QueryOptions,
|
|
20
|
+
): Promise<T[]>;
|
|
21
|
+
|
|
22
|
+
create(createDto: Partial<T>): Promise<T>;
|
|
23
|
+
|
|
24
|
+
createMany(createDtos: Partial<T>[]): Promise<T[]>;
|
|
25
|
+
|
|
26
|
+
update(
|
|
27
|
+
filterQuery: FilterQuery<T>,
|
|
28
|
+
updateQuery: UpdateQuery<T>,
|
|
29
|
+
options?: QueryOptions,
|
|
30
|
+
): Promise<T | null>;
|
|
31
|
+
|
|
32
|
+
updateOrFail(
|
|
33
|
+
filterQuery: FilterQuery<T>,
|
|
34
|
+
updateQuery: UpdateQuery<T>,
|
|
35
|
+
options?: QueryOptions,
|
|
36
|
+
): Promise<T>;
|
|
37
|
+
|
|
38
|
+
updateMany(
|
|
39
|
+
filterQuery: FilterQuery<T>,
|
|
40
|
+
updateQuery: UpdateQuery<T>,
|
|
41
|
+
): Promise<{ matchedCount: number; modifiedCount: number }>;
|
|
42
|
+
|
|
43
|
+
delete(
|
|
44
|
+
filterQuery: FilterQuery<T>,
|
|
45
|
+
options?: QueryOptions,
|
|
46
|
+
): Promise<T | null>;
|
|
47
|
+
|
|
48
|
+
deleteOrFail(
|
|
49
|
+
filterQuery: FilterQuery<T>,
|
|
50
|
+
options?: QueryOptions,
|
|
51
|
+
): Promise<T>;
|
|
52
|
+
|
|
53
|
+
deleteMany(filterQuery: FilterQuery<T>): Promise<{ deletedCount: number }>;
|
|
54
|
+
|
|
55
|
+
count(filterQuery: FilterQuery<T>): Promise<number>;
|
|
56
|
+
|
|
57
|
+
exists(filterQuery: FilterQuery<T>): Promise<boolean>;
|
|
58
|
+
|
|
59
|
+
aggregate(pipeline: PipelineStage[]): Promise<any[]>;
|
|
60
|
+
|
|
61
|
+
paginate(
|
|
62
|
+
filterQuery: FilterQuery<T>,
|
|
63
|
+
options?: {
|
|
64
|
+
page?: number;
|
|
65
|
+
limit?: number;
|
|
66
|
+
sort?: Record<string, 1 | -1>;
|
|
67
|
+
projection?: Record<string, unknown>;
|
|
68
|
+
},
|
|
69
|
+
): Promise<{
|
|
70
|
+
data: T[];
|
|
71
|
+
pagination: {
|
|
72
|
+
total: number;
|
|
73
|
+
page: number;
|
|
74
|
+
limit: number;
|
|
75
|
+
pages: number;
|
|
76
|
+
};
|
|
77
|
+
}>;
|
|
78
|
+
}
|