@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.
Files changed (148) hide show
  1. package/README.md +2 -0
  2. package/dist/database/index.d.ts +2 -0
  3. package/dist/database/index.js +19 -0
  4. package/dist/database/index.js.map +1 -0
  5. package/dist/database/mongodb/index.d.ts +2 -0
  6. package/dist/database/mongodb/index.js +19 -0
  7. package/dist/database/mongodb/index.js.map +1 -0
  8. package/dist/database/mongodb/mongodb.module.d.ts +2 -0
  9. package/dist/database/mongodb/mongodb.module.js +50 -0
  10. package/dist/database/mongodb/mongodb.module.js.map +1 -0
  11. package/dist/database/mongodb/mongodb.service.d.ts +29 -0
  12. package/dist/database/mongodb/mongodb.service.js +114 -0
  13. package/dist/database/mongodb/mongodb.service.js.map +1 -0
  14. package/dist/database/redis/index.d.ts +2 -0
  15. package/dist/database/redis/index.js +19 -0
  16. package/dist/database/redis/index.js.map +1 -0
  17. package/dist/database/redis/redis.module.d.ts +2 -0
  18. package/dist/database/redis/redis.module.js +52 -0
  19. package/dist/database/redis/redis.module.js.map +1 -0
  20. package/dist/database/redis/redis.service.d.ts +26 -0
  21. package/dist/database/redis/redis.service.js +126 -0
  22. package/dist/database/redis/redis.service.js.map +1 -0
  23. package/dist/exception/exception.filter.d.ts +4 -0
  24. package/dist/exception/exception.filter.js +35 -0
  25. package/dist/exception/exception.filter.js.map +1 -0
  26. package/dist/generic-repository/index.d.ts +4 -0
  27. package/dist/generic-repository/index.js +21 -0
  28. package/dist/generic-repository/index.js.map +1 -0
  29. package/dist/generic-repository/repositories/base.repository.d.ts +39 -0
  30. package/dist/generic-repository/repositories/base.repository.interface.d.ts +36 -0
  31. package/dist/generic-repository/repositories/base.repository.interface.js +3 -0
  32. package/dist/generic-repository/repositories/base.repository.interface.js.map +1 -0
  33. package/dist/generic-repository/repositories/base.repository.js +96 -0
  34. package/dist/generic-repository/repositories/base.repository.js.map +1 -0
  35. package/dist/generic-repository/services/base.service.d.ts +42 -0
  36. package/dist/generic-repository/services/base.service.interface.d.ts +36 -0
  37. package/dist/generic-repository/services/base.service.interface.js +3 -0
  38. package/dist/generic-repository/services/base.service.interface.js.map +1 -0
  39. package/dist/generic-repository/services/base.service.js +57 -0
  40. package/dist/generic-repository/services/base.service.js.map +1 -0
  41. package/dist/index.d.ts +7 -0
  42. package/dist/index.js +24 -0
  43. package/dist/index.js.map +1 -0
  44. package/dist/logger/custom.logger.d.ts +14 -0
  45. package/dist/logger/custom.logger.js +76 -0
  46. package/dist/logger/custom.logger.js.map +1 -0
  47. package/dist/logger/index.d.ts +2 -0
  48. package/dist/logger/index.js +19 -0
  49. package/dist/logger/index.js.map +1 -0
  50. package/dist/logger/logger.module.d.ts +2 -0
  51. package/dist/logger/logger.module.js +27 -0
  52. package/dist/logger/logger.module.js.map +1 -0
  53. package/dist/pipes/custom-validation.pipe.d.ts +7 -0
  54. package/dist/pipes/custom-validation.pipe.js +69 -0
  55. package/dist/pipes/custom-validation.pipe.js.map +1 -0
  56. package/dist/response/response.interceptor.d.ts +5 -0
  57. package/dist/response/response.interceptor.js +29 -0
  58. package/dist/response/response.interceptor.js.map +1 -0
  59. package/dist/schemas/base.schema.d.ts +11 -0
  60. package/dist/schemas/base.schema.js +29 -0
  61. package/dist/schemas/base.schema.js.map +1 -0
  62. package/dist/schemas/event/event.interfaces.d.ts +69 -0
  63. package/dist/schemas/event/event.interfaces.js +9 -0
  64. package/dist/schemas/event/event.interfaces.js.map +1 -0
  65. package/dist/schemas/event/event.schema.d.ts +70 -0
  66. package/dist/schemas/event/event.schema.js +242 -0
  67. package/dist/schemas/event/event.schema.js.map +1 -0
  68. package/dist/schemas/index.d.ts +14 -0
  69. package/dist/schemas/index.js +31 -0
  70. package/dist/schemas/index.js.map +1 -0
  71. package/dist/schemas/order/order.interfaces.d.ts +72 -0
  72. package/dist/schemas/order/order.interfaces.js +31 -0
  73. package/dist/schemas/order/order.interfaces.js.map +1 -0
  74. package/dist/schemas/order/order.schema.d.ts +39 -0
  75. package/dist/schemas/order/order.schema.js +162 -0
  76. package/dist/schemas/order/order.schema.js.map +1 -0
  77. package/dist/schemas/promotions/promotions.interfaces.d.ts +30 -0
  78. package/dist/schemas/promotions/promotions.interfaces.js +14 -0
  79. package/dist/schemas/promotions/promotions.interfaces.js.map +1 -0
  80. package/dist/schemas/promotions/promotions.schema.d.ts +25 -0
  81. package/dist/schemas/promotions/promotions.schema.js +92 -0
  82. package/dist/schemas/promotions/promotions.schema.js.map +1 -0
  83. package/dist/schemas/rows/rows.interfaces.d.ts +14 -0
  84. package/dist/schemas/rows/rows.interfaces.js +3 -0
  85. package/dist/schemas/rows/rows.interfaces.js.map +1 -0
  86. package/dist/schemas/rows/rows.schema.d.ts +22 -0
  87. package/dist/schemas/rows/rows.schema.js +65 -0
  88. package/dist/schemas/rows/rows.schema.js.map +1 -0
  89. package/dist/schemas/schema.helper.d.ts +4 -0
  90. package/dist/schemas/schema.helper.js +84 -0
  91. package/dist/schemas/schema.helper.js.map +1 -0
  92. package/dist/schemas/ticket-classes/ticket-classes.interfaces.d.ts +18 -0
  93. package/dist/schemas/ticket-classes/ticket-classes.interfaces.js +3 -0
  94. package/dist/schemas/ticket-classes/ticket-classes.interfaces.js.map +1 -0
  95. package/dist/schemas/ticket-classes/ticket-classes.schemas.d.ts +26 -0
  96. package/dist/schemas/ticket-classes/ticket-classes.schemas.js +75 -0
  97. package/dist/schemas/ticket-classes/ticket-classes.schemas.js.map +1 -0
  98. package/dist/schemas/ticket-summary/ticket-summary.interfaces.d.ts +19 -0
  99. package/dist/schemas/ticket-summary/ticket-summary.interfaces.js +9 -0
  100. package/dist/schemas/ticket-summary/ticket-summary.interfaces.js.map +1 -0
  101. package/dist/schemas/ticket-summary/ticket-summary.schema.d.ts +23 -0
  102. package/dist/schemas/ticket-summary/ticket-summary.schema.js +65 -0
  103. package/dist/schemas/ticket-summary/ticket-summary.schema.js.map +1 -0
  104. package/dist/schemas/user-fanpass/user-fanpass.interfaces.d.ts +26 -0
  105. package/dist/schemas/user-fanpass/user-fanpass.interfaces.js +9 -0
  106. package/dist/schemas/user-fanpass/user-fanpass.interfaces.js.map +1 -0
  107. package/dist/schemas/user-fanpass/user-fanpass.schemas.d.ts +29 -0
  108. package/dist/schemas/user-fanpass/user-fanpass.schemas.js +113 -0
  109. package/dist/schemas/user-fanpass/user-fanpass.schemas.js.map +1 -0
  110. package/dist/tsconfig.tsbuildinfo +1 -0
  111. package/package.json +40 -0
  112. package/src/database/index.ts +2 -0
  113. package/src/database/mongodb/index.ts +2 -0
  114. package/src/database/mongodb/mongodb.module.ts +42 -0
  115. package/src/database/mongodb/mongodb.service.ts +111 -0
  116. package/src/database/redis/index.ts +2 -0
  117. package/src/database/redis/redis.module.ts +45 -0
  118. package/src/database/redis/redis.service.ts +142 -0
  119. package/src/exception/exception.filter.ts +36 -0
  120. package/src/generic-repository/index.ts +7 -0
  121. package/src/generic-repository/repositories/base.repository.interface.ts +78 -0
  122. package/src/generic-repository/repositories/base.repository.ts +199 -0
  123. package/src/generic-repository/services/base.service.interface.ts +78 -0
  124. package/src/generic-repository/services/base.service.ts +165 -0
  125. package/src/index.ts +11 -0
  126. package/src/logger/custom.logger.ts +83 -0
  127. package/src/logger/index.ts +2 -0
  128. package/src/logger/logger.module.ts +14 -0
  129. package/src/pipes/custom-validation.pipe.ts +61 -0
  130. package/src/response/response.interceptor.ts +26 -0
  131. package/src/schemas/base.schema.ts +19 -0
  132. package/src/schemas/event/event.interfaces.ts +74 -0
  133. package/src/schemas/event/event.schema.ts +195 -0
  134. package/src/schemas/index.ts +16 -0
  135. package/src/schemas/order/order.interfaces.ts +78 -0
  136. package/src/schemas/order/order.schema.ts +120 -0
  137. package/src/schemas/promotions/promotions.interfaces.ts +34 -0
  138. package/src/schemas/promotions/promotions.schema.ts +71 -0
  139. package/src/schemas/rows/rows.interfaces.ts +14 -0
  140. package/src/schemas/rows/rows.schema.ts +47 -0
  141. package/src/schemas/schema.helper.ts +103 -0
  142. package/src/schemas/ticket-classes/ticket-classes.interfaces.ts +18 -0
  143. package/src/schemas/ticket-classes/ticket-classes.schemas.ts +51 -0
  144. package/src/schemas/ticket-summary/ticket-summary.interfaces.ts +20 -0
  145. package/src/schemas/ticket-summary/ticket-summary.schema.ts +46 -0
  146. package/src/schemas/user-fanpass/user-fanpass.interfaces.ts +28 -0
  147. package/src/schemas/user-fanpass/user-fanpass.schemas.ts +86 -0
  148. 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,2 @@
1
+ export * from './redis.module';
2
+ export * from './redis.service';
@@ -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,7 @@
1
+ // Export repositories
2
+ export * from './repositories/base.repository.interface';
3
+ export * from './repositories/base.repository';
4
+
5
+ // Export services
6
+ export * from './services/base.service.interface';
7
+ export * from './services/base.service';
@@ -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
+ }