@flusys/nestjs-shared 1.1.0-beta → 1.2.0-rc.1

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 (198) hide show
  1. package/README.md +146 -1330
  2. package/cjs/classes/api-controller.class.js +325 -49
  3. package/cjs/classes/api-service.class.js +271 -149
  4. package/cjs/classes/handle-custome-db.js +37 -0
  5. package/cjs/classes/index.js +2 -2
  6. package/cjs/classes/winston.logger.class.js +103 -70
  7. package/cjs/constants/index.js +15 -4
  8. package/cjs/constants/message-keys.js +68 -0
  9. package/cjs/constants/permissions.js +287 -0
  10. package/cjs/decorators/api-response.decorator.js +2 -3
  11. package/cjs/decorators/index.js +1 -0
  12. package/cjs/decorators/log-action.decorator.js +149 -0
  13. package/cjs/decorators/require-permission.decorator.js +5 -5
  14. package/cjs/dtos/delete.dto.js +10 -0
  15. package/cjs/dtos/filter-and-pagination.dto.js +24 -34
  16. package/cjs/dtos/get-by-ids.dto.js +65 -0
  17. package/cjs/dtos/identity-response.dto.js +4 -8
  18. package/cjs/dtos/index.js +1 -0
  19. package/cjs/dtos/pagination.dto.js +4 -8
  20. package/cjs/dtos/response-payload.dto.js +68 -112
  21. package/cjs/entities/identity.js +12 -6
  22. package/cjs/entities/index.js +1 -0
  23. package/cjs/entities/raw-type.js +4 -0
  24. package/cjs/entities/user-root.js +15 -15
  25. package/cjs/enums/index.js +20 -0
  26. package/cjs/enums/notification-type.enum.js +17 -0
  27. package/cjs/enums/participant-status.enum.js +17 -0
  28. package/cjs/enums/recurrence-type.enum.js +18 -0
  29. package/cjs/exceptions/base-app.exception.js +151 -0
  30. package/cjs/exceptions/index.js +1 -0
  31. package/cjs/exceptions/permission.exception.js +13 -9
  32. package/cjs/filters/global-exception.filter.js +279 -0
  33. package/cjs/filters/index.js +18 -0
  34. package/cjs/guards/jwt-auth.guard.js +4 -1
  35. package/cjs/guards/permission.guard.js +88 -148
  36. package/cjs/index.js +2 -0
  37. package/cjs/interceptors/idempotency.interceptor.js +1 -1
  38. package/cjs/interceptors/index.js +1 -4
  39. package/cjs/interceptors/set-user-field-on-body.interceptor.js +60 -0
  40. package/cjs/interceptors/slug.interceptor.js +30 -9
  41. package/cjs/interfaces/datasource.interface.js +4 -0
  42. package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
  43. package/cjs/interfaces/index.js +4 -2
  44. package/cjs/interfaces/module-config.interface.js +4 -0
  45. package/cjs/interfaces/notification-adapter.interface.js +11 -0
  46. package/cjs/interfaces/permission.interface.js +1 -1
  47. package/cjs/middlewares/logger.middleware.js +133 -115
  48. package/cjs/modules/cache/cache.module.js +3 -3
  49. package/cjs/modules/datasource/index.js +0 -1
  50. package/cjs/modules/datasource/multi-tenant-datasource.service.js +60 -183
  51. package/cjs/modules/utils/utils.service.js +27 -206
  52. package/cjs/utils/date-time.util.js +94 -0
  53. package/cjs/utils/html-sanitizer.util.js +64 -0
  54. package/cjs/utils/index.js +5 -1
  55. package/cjs/utils/query-helpers.util.js +60 -0
  56. package/cjs/utils/request.util.js +96 -0
  57. package/cjs/utils/string.util.js +63 -0
  58. package/classes/api-controller.class.d.ts +16 -8
  59. package/classes/api-service.class.d.ts +30 -25
  60. package/classes/handle-custome-db.d.ts +3 -0
  61. package/classes/index.d.ts +2 -2
  62. package/classes/winston.logger.class.d.ts +1 -0
  63. package/constants/index.d.ts +2 -1
  64. package/constants/message-keys.d.ts +45 -0
  65. package/constants/permissions.d.ts +306 -0
  66. package/decorators/api-response.decorator.d.ts +1 -1
  67. package/decorators/current-user.decorator.d.ts +1 -1
  68. package/decorators/index.d.ts +1 -0
  69. package/decorators/log-action.decorator.d.ts +8 -0
  70. package/decorators/require-permission.decorator.d.ts +2 -2
  71. package/dtos/delete.dto.d.ts +1 -0
  72. package/dtos/filter-and-pagination.dto.d.ts +0 -2
  73. package/dtos/get-by-ids.dto.d.ts +4 -0
  74. package/dtos/index.d.ts +1 -0
  75. package/dtos/response-payload.dto.d.ts +8 -20
  76. package/entities/index.d.ts +1 -0
  77. package/entities/raw-type.d.ts +1 -0
  78. package/enums/index.d.ts +3 -0
  79. package/enums/notification-type.enum.d.ts +6 -0
  80. package/enums/participant-status.enum.d.ts +6 -0
  81. package/enums/recurrence-type.enum.d.ts +7 -0
  82. package/exceptions/base-app.exception.d.ts +41 -0
  83. package/exceptions/index.d.ts +1 -0
  84. package/exceptions/permission.exception.d.ts +2 -2
  85. package/fesm/classes/api-controller.class.js +326 -50
  86. package/fesm/classes/api-service.class.js +274 -152
  87. package/fesm/classes/handle-custome-db.js +19 -0
  88. package/fesm/classes/index.js +3 -2
  89. package/fesm/classes/winston.logger.class.js +100 -68
  90. package/fesm/constants/index.js +4 -1
  91. package/fesm/constants/message-keys.js +49 -0
  92. package/fesm/constants/permissions.js +200 -0
  93. package/fesm/decorators/api-response.decorator.js +3 -21
  94. package/fesm/decorators/index.js +1 -0
  95. package/fesm/decorators/log-action.decorator.js +139 -0
  96. package/fesm/decorators/require-permission.decorator.js +17 -65
  97. package/fesm/dtos/delete.dto.js +12 -2
  98. package/fesm/dtos/filter-and-pagination.dto.js +26 -47
  99. package/fesm/dtos/get-by-ids.dto.js +55 -0
  100. package/fesm/dtos/identity-response.dto.js +4 -8
  101. package/fesm/dtos/index.js +1 -0
  102. package/fesm/dtos/pagination.dto.js +4 -8
  103. package/fesm/dtos/response-payload.dto.js +68 -103
  104. package/fesm/entities/identity.js +12 -6
  105. package/fesm/entities/index.js +1 -0
  106. package/fesm/entities/raw-type.js +1 -0
  107. package/fesm/entities/user-root.js +15 -15
  108. package/fesm/enums/index.js +3 -0
  109. package/fesm/enums/notification-type.enum.js +7 -0
  110. package/fesm/enums/participant-status.enum.js +7 -0
  111. package/fesm/enums/recurrence-type.enum.js +8 -0
  112. package/fesm/exceptions/base-app.exception.js +115 -0
  113. package/fesm/exceptions/index.js +1 -0
  114. package/fesm/exceptions/permission.exception.js +16 -18
  115. package/fesm/filters/global-exception.filter.js +269 -0
  116. package/fesm/filters/index.js +1 -0
  117. package/fesm/guards/jwt-auth.guard.js +5 -2
  118. package/fesm/guards/permission.guard.js +90 -150
  119. package/fesm/index.js +2 -0
  120. package/fesm/interceptors/idempotency.interceptor.js +2 -2
  121. package/fesm/interceptors/index.js +1 -4
  122. package/fesm/interceptors/set-user-field-on-body.interceptor.js +39 -0
  123. package/fesm/interceptors/slug.interceptor.js +31 -10
  124. package/fesm/interfaces/datasource.interface.js +1 -0
  125. package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
  126. package/fesm/interfaces/index.js +4 -2
  127. package/fesm/interfaces/module-config.interface.js +5 -0
  128. package/fesm/interfaces/notification-adapter.interface.js +1 -0
  129. package/fesm/interfaces/permission.interface.js +1 -3
  130. package/fesm/middlewares/logger.middleware.js +133 -109
  131. package/fesm/modules/cache/cache.module.js +2 -2
  132. package/fesm/modules/datasource/index.js +0 -1
  133. package/fesm/modules/datasource/multi-tenant-datasource.service.js +61 -184
  134. package/fesm/modules/utils/utils.service.js +27 -206
  135. package/fesm/utils/date-time.util.js +70 -0
  136. package/fesm/utils/html-sanitizer.util.js +69 -0
  137. package/fesm/utils/index.js +5 -1
  138. package/fesm/utils/query-helpers.util.js +85 -0
  139. package/fesm/utils/request.util.js +81 -0
  140. package/fesm/utils/string.util.js +71 -0
  141. package/filters/global-exception.filter.d.ts +9 -0
  142. package/filters/index.d.ts +1 -0
  143. package/guards/permission.guard.d.ts +4 -6
  144. package/index.d.ts +2 -0
  145. package/interceptors/idempotency.interceptor.d.ts +1 -1
  146. package/interceptors/index.d.ts +1 -4
  147. package/interceptors/set-user-field-on-body.interceptor.d.ts +5 -0
  148. package/interceptors/slug.interceptor.d.ts +2 -1
  149. package/interfaces/api.interface.d.ts +4 -2
  150. package/interfaces/datasource.interface.d.ts +5 -0
  151. package/interfaces/event-manager-adapter.interface.d.ts +43 -0
  152. package/interfaces/identity.interface.d.ts +4 -4
  153. package/interfaces/index.d.ts +4 -2
  154. package/interfaces/logged-user-info.interface.d.ts +0 -2
  155. package/interfaces/module-config.interface.d.ts +6 -0
  156. package/interfaces/notification-adapter.interface.d.ts +21 -0
  157. package/interfaces/permission.interface.d.ts +13 -9
  158. package/middlewares/logger.middleware.d.ts +2 -2
  159. package/modules/datasource/index.d.ts +0 -1
  160. package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -16
  161. package/modules/utils/utils.service.d.ts +3 -14
  162. package/package.json +21 -14
  163. package/utils/date-time.util.d.ts +8 -0
  164. package/utils/html-sanitizer.util.d.ts +2 -0
  165. package/utils/index.d.ts +5 -1
  166. package/utils/query-helpers.util.d.ts +16 -0
  167. package/utils/request.util.d.ts +6 -0
  168. package/utils/string.util.d.ts +2 -0
  169. package/cjs/classes/request-scoped-api.service.js +0 -61
  170. package/cjs/classes/winston-logger-adapter.class.js +0 -121
  171. package/cjs/interceptors/query-performance.interceptor.js +0 -66
  172. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -40
  173. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -40
  174. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -40
  175. package/cjs/interfaces/base-query.interface.js +0 -6
  176. package/cjs/interfaces/logger.interface.js +0 -7
  177. package/cjs/modules/datasource/datasource.module.js +0 -120
  178. package/cjs/utils/error-handler.util.js +0 -67
  179. package/classes/request-scoped-api.service.d.ts +0 -13
  180. package/classes/winston-logger-adapter.class.d.ts +0 -20
  181. package/fesm/classes/request-scoped-api.service.js +0 -51
  182. package/fesm/classes/winston-logger-adapter.class.js +0 -124
  183. package/fesm/interceptors/query-performance.interceptor.js +0 -56
  184. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -30
  185. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -30
  186. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -30
  187. package/fesm/interfaces/base-query.interface.js +0 -3
  188. package/fesm/interfaces/logger.interface.js +0 -4
  189. package/fesm/modules/datasource/datasource.module.js +0 -110
  190. package/fesm/utils/error-handler.util.js +0 -59
  191. package/interceptors/query-performance.interceptor.d.ts +0 -8
  192. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -5
  193. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -5
  194. package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -5
  195. package/interfaces/base-query.interface.d.ts +0 -7
  196. package/interfaces/logger.interface.d.ts +0 -7
  197. package/modules/datasource/datasource.module.d.ts +0 -18
  198. package/utils/error-handler.util.d.ts +0 -24
@@ -0,0 +1,279 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "GlobalExceptionFilter", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return GlobalExceptionFilter;
9
+ }
10
+ });
11
+ const _common = require("@nestjs/common");
12
+ const _typeorm = require("typeorm");
13
+ const _messagekeys = require("../constants/message-keys");
14
+ const _baseappexception = require("../exceptions/base-app.exception");
15
+ const _loggermiddleware = require("../middlewares/logger.middleware");
16
+ function _define_property(obj, key, value) {
17
+ if (key in obj) {
18
+ Object.defineProperty(obj, key, {
19
+ value: value,
20
+ enumerable: true,
21
+ configurable: true,
22
+ writable: true
23
+ });
24
+ } else {
25
+ obj[key] = value;
26
+ }
27
+ return obj;
28
+ }
29
+ function _ts_decorate(decorators, target, key, desc) {
30
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
31
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
32
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
33
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
34
+ }
35
+ let GlobalExceptionFilter = class GlobalExceptionFilter {
36
+ catch(exception, host) {
37
+ const ctx = host.switchToHttp();
38
+ const request = ctx.getRequest();
39
+ const response = ctx.getResponse();
40
+ const responseTime = Date.now() - (request._startTime || Date.now());
41
+ const requestId = (0, _loggermiddleware.getRequestId)() || request.headers['x-request-id'] || 'no-request-id';
42
+ const { statusCode, errorResponse } = this.resolveException(exception, requestId, responseTime);
43
+ const logContext = {
44
+ requestId,
45
+ userId: (0, _loggermiddleware.getUserId)(),
46
+ tenantId: (0, _loggermiddleware.getTenantId)(),
47
+ companyId: (0, _loggermiddleware.getCompanyId)(),
48
+ method: request.method,
49
+ url: request.originalUrl,
50
+ path: request.path,
51
+ statusCode,
52
+ duration: `${responseTime}ms`,
53
+ messageKey: errorResponse.messageKey
54
+ };
55
+ const logMessage = `Exception caught "HTTP/1.1" "${request.method}" "${request.originalUrl}" - ${statusCode} "${errorResponse.messageKey}" ${responseTime}ms | ${errorResponse.message}`;
56
+ if (statusCode >= 500) {
57
+ this.logger.error(logMessage, exception instanceof Error ? exception.stack : undefined, logContext);
58
+ } else if (statusCode >= 400) {
59
+ this.logger.warn(logMessage, logContext);
60
+ }
61
+ response.status(statusCode).json(errorResponse);
62
+ }
63
+ resolveException(exception, requestId, responseTime) {
64
+ const meta = {
65
+ requestId,
66
+ timestamp: new Date().toISOString(),
67
+ responseTime
68
+ };
69
+ if (exception instanceof _baseappexception.BaseAppException) {
70
+ return {
71
+ statusCode: exception.getStatus(),
72
+ errorResponse: {
73
+ success: false,
74
+ message: exception.message,
75
+ messageKey: exception.messageKey,
76
+ messageVariables: exception.messageVariables,
77
+ errors: exception.errors,
78
+ _meta: meta
79
+ }
80
+ };
81
+ }
82
+ if (exception instanceof _common.HttpException) {
83
+ const resolved = this.resolveHttpException(exception);
84
+ return {
85
+ statusCode: resolved.statusCode,
86
+ errorResponse: {
87
+ success: false,
88
+ message: resolved.message,
89
+ messageKey: resolved.messageKey,
90
+ messageVariables: resolved.messageVariables,
91
+ errors: resolved.errors,
92
+ _meta: meta
93
+ }
94
+ };
95
+ }
96
+ if (exception instanceof _typeorm.QueryFailedError) {
97
+ const resolved = this.resolveQueryFailedError(exception);
98
+ if (resolved) {
99
+ return {
100
+ statusCode: resolved.getStatus(),
101
+ errorResponse: {
102
+ success: false,
103
+ message: resolved.message,
104
+ messageKey: resolved.messageKey,
105
+ messageVariables: resolved.messageVariables,
106
+ _meta: meta
107
+ }
108
+ };
109
+ }
110
+ }
111
+ return {
112
+ statusCode: _common.HttpStatus.INTERNAL_SERVER_ERROR,
113
+ errorResponse: {
114
+ success: false,
115
+ message: 'Internal server error',
116
+ messageKey: _messagekeys.ERROR_MESSAGES.INTERNAL,
117
+ _meta: meta
118
+ }
119
+ };
120
+ }
121
+ resolveHttpException(exception) {
122
+ const statusCode = exception.getStatus();
123
+ const response = exception.getResponse();
124
+ if (typeof response === 'string') {
125
+ return {
126
+ statusCode,
127
+ message: response,
128
+ messageKey: _messagekeys.ERROR_MESSAGES.HTTP
129
+ };
130
+ }
131
+ if (typeof response === 'object' && response !== null) {
132
+ const obj = response;
133
+ if (Array.isArray(obj.message)) {
134
+ const errors = obj.message.map((msg)=>{
135
+ const [field, ...rest] = msg.split(' ');
136
+ return {
137
+ field: field || 'unknown',
138
+ message: rest.join(' ') || msg
139
+ };
140
+ });
141
+ return {
142
+ statusCode,
143
+ message: 'Validation failed',
144
+ messageKey: _messagekeys.ERROR_MESSAGES.VALIDATION,
145
+ errors
146
+ };
147
+ }
148
+ return {
149
+ statusCode,
150
+ message: String(obj.message || 'Unknown error'),
151
+ messageKey: String(obj.messageKey || _messagekeys.ERROR_MESSAGES.HTTP),
152
+ messageVariables: obj.messageVariables,
153
+ errors: obj.errors
154
+ };
155
+ }
156
+ return {
157
+ statusCode,
158
+ message: 'Unknown error',
159
+ messageKey: _messagekeys.ERROR_MESSAGES.UNKNOWN
160
+ };
161
+ }
162
+ resolveQueryFailedError(exception) {
163
+ const dbError = exception;
164
+ const code = dbError.driverError?.code ?? dbError.code;
165
+ const number = dbError.driverError?.number ?? dbError.number;
166
+ const errno = dbError.driverError?.errno ?? dbError.errno;
167
+ const sqlMessage = dbError.driverError?.sqlMessage ?? '';
168
+ const detail = dbError.driverError?.detail ?? dbError.detail ?? '';
169
+ const table = dbError.driverError?.table ?? dbError.table ?? '';
170
+ const column = dbError.driverError?.column ?? dbError.column;
171
+ const pgField = detail.match(/Key \(([^)]+)\)/)?.[1] ?? column;
172
+ // DUPLICATE / UNIQUE VIOLATION
173
+ // PG: 23505 | MSSQL: 2627 (constraint), 2601 (index) | MySQL: ER_DUP_ENTRY / errno 1062
174
+ if (code === '23505' || number === 2627 || number === 2601 || code === 'ER_DUP_ENTRY' || errno === 1062) {
175
+ const mysqlTable = sqlMessage.match(/for key '([^'.]+)\./)?.[1];
176
+ const mysqlIndex = sqlMessage.match(/for key '(?:[^.]+\.)?([^']+)'/)?.[1];
177
+ const resolvedTable = table || mysqlTable || 'record';
178
+ const resolvedField = pgField ? this.toHumanReadable(pgField) : mysqlIndex ? this.toHumanReadable(mysqlIndex) : undefined;
179
+ return new _baseappexception.ConflictException(this.toHumanReadable(resolvedTable), resolvedField);
180
+ }
181
+ // FOREIGN KEY VIOLATION
182
+ // PG: 23503 | MSSQL: 547 | MySQL: ER_NO_REFERENCED_ROW_2/1452 (insert), ER_ROW_IS_REFERENCED_2/1451 (delete)
183
+ if (code === '23503' || number === 547 || code === 'ER_NO_REFERENCED_ROW_2' || code === 'ER_ROW_IS_REFERENCED_2' || errno === 1452 || errno === 1451) {
184
+ const mysqlColumn = sqlMessage.match(/FOREIGN KEY \(`([^`]+)`\)/)?.[1];
185
+ const raw = pgField ?? mysqlColumn;
186
+ const humanField = raw ? this.toHumanReadable(raw) : undefined;
187
+ return new _baseappexception.BaseAppException({
188
+ message: humanField ? `Invalid reference for '${humanField}'` : 'Invalid reference',
189
+ messageKey: _messagekeys.ERROR_MESSAGES.DB_FOREIGN_KEY,
190
+ messageVariables: humanField ? {
191
+ field: humanField
192
+ } : undefined,
193
+ status: _common.HttpStatus.BAD_REQUEST
194
+ });
195
+ }
196
+ // NOT NULL VIOLATION
197
+ // PG: 23502 | MSSQL: 515 | MySQL: ER_BAD_NULL_ERROR / errno 1048
198
+ if (code === '23502' || number === 515 || code === 'ER_BAD_NULL_ERROR' || errno === 1048) {
199
+ const mysqlColumn = sqlMessage.match(/Column '([^']+)' cannot be null/)?.[1];
200
+ const humanField = this.toHumanReadable(column ?? mysqlColumn ?? 'field');
201
+ return new _baseappexception.BaseAppException({
202
+ message: `Required field '${humanField}' cannot be null`,
203
+ messageKey: _messagekeys.ERROR_MESSAGES.DB_NOT_NULL,
204
+ messageVariables: {
205
+ field: humanField
206
+ },
207
+ status: _common.HttpStatus.BAD_REQUEST
208
+ });
209
+ }
210
+ // INVALID INPUT SYNTAX — PostgreSQL only (MySQL silently coerces types)
211
+ if (code === '22P02') {
212
+ const typeMatch = exception.message.match(/invalid input syntax for(?:\s+type)?\s+(\w+)/i);
213
+ const type = typeMatch?.[1] ?? 'value';
214
+ const humanField = column ? this.toHumanReadable(column) : undefined;
215
+ return new _baseappexception.BaseAppException({
216
+ message: humanField ? `Invalid ${type} value for '${humanField}'` : `Invalid ${type} value`,
217
+ messageKey: _messagekeys.ERROR_MESSAGES.DB_INVALID_INPUT,
218
+ messageVariables: {
219
+ type,
220
+ ...humanField !== undefined && {
221
+ field: humanField
222
+ }
223
+ },
224
+ status: _common.HttpStatus.BAD_REQUEST
225
+ });
226
+ }
227
+ // VALUE TOO LONG
228
+ // PG: 22001 | MySQL: ER_DATA_TOO_LONG / errno 1406
229
+ if (code === '22001' || code === 'ER_DATA_TOO_LONG' || errno === 1406) {
230
+ const mysqlColumn = sqlMessage.match(/Data too long for column '([^']+)'/)?.[1];
231
+ const resolvedColumn = column ?? mysqlColumn;
232
+ const humanField = resolvedColumn ? this.toHumanReadable(resolvedColumn) : undefined;
233
+ return new _baseappexception.BaseAppException({
234
+ message: humanField ? `Value too long for '${humanField}'` : 'Value exceeds maximum length',
235
+ messageKey: _messagekeys.ERROR_MESSAGES.DB_VALUE_TOO_LONG,
236
+ messageVariables: humanField !== undefined ? {
237
+ field: humanField
238
+ } : undefined,
239
+ status: _common.HttpStatus.BAD_REQUEST
240
+ });
241
+ }
242
+ // CHECK CONSTRAINT VIOLATION
243
+ // PG: 23514 | MySQL 8.0+: ER_CHECK_CONSTRAINT_VIOLATED / errno 3819
244
+ if (code === '23514' || code === 'ER_CHECK_CONSTRAINT_VIOLATED' || errno === 3819) {
245
+ const constraint = dbError.driverError?.constraint ?? dbError.constraint;
246
+ const humanTable = this.toHumanReadable(table || 'record');
247
+ return new _baseappexception.BaseAppException({
248
+ message: `${humanTable} violates a validation rule`,
249
+ messageKey: _messagekeys.ERROR_MESSAGES.DB_CHECK_CONSTRAINT,
250
+ messageVariables: {
251
+ entity: humanTable,
252
+ ...constraint !== undefined && {
253
+ constraint
254
+ }
255
+ },
256
+ status: _common.HttpStatus.BAD_REQUEST
257
+ });
258
+ }
259
+ // DEADLOCK
260
+ // PG: 40P01 | MSSQL: 1205 | MySQL: ER_LOCK_DEADLOCK / errno 1213
261
+ if (code === '40P01' || number === 1205 || code === 'ER_LOCK_DEADLOCK' || errno === 1213) {
262
+ return new _baseappexception.BaseAppException({
263
+ message: 'The request could not be completed due to a conflict, please try again',
264
+ messageKey: _messagekeys.ERROR_MESSAGES.DB_DEADLOCK,
265
+ status: _common.HttpStatus.SERVICE_UNAVAILABLE
266
+ });
267
+ }
268
+ return null;
269
+ }
270
+ toHumanReadable(dbName) {
271
+ return dbName.split(', ').map((part)=>part.split('_').map((word)=>word.toLowerCase() === 'id' ? 'ID' : word.charAt(0).toUpperCase() + word.slice(1)).join(' ')).join(', ');
272
+ }
273
+ constructor(){
274
+ _define_property(this, "logger", new _common.Logger('ExceptionFilter'));
275
+ }
276
+ };
277
+ GlobalExceptionFilter = _ts_decorate([
278
+ (0, _common.Catch)()
279
+ ], GlobalExceptionFilter);
@@ -0,0 +1,18 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ _export_star(require("./global-exception.filter"), exports);
6
+ function _export_star(from, to) {
7
+ Object.keys(from).forEach(function(k) {
8
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
9
+ Object.defineProperty(to, k, {
10
+ enumerable: true,
11
+ get: function() {
12
+ return from[k];
13
+ }
14
+ });
15
+ }
16
+ });
17
+ return from;
18
+ }
@@ -53,7 +53,10 @@ let JwtAuthGuard = class JwtAuthGuard extends (0, _passport.AuthGuard)('jwt') {
53
53
  }
54
54
  handleRequest(err, user) {
55
55
  if (err || !user) {
56
- throw err || new _common.UnauthorizedException('Invalid or expired token');
56
+ throw err || new _common.UnauthorizedException({
57
+ message: 'Invalid or expired token',
58
+ messageKey: _constants.AUTH_MESSAGES.TOKEN_INVALID
59
+ });
57
60
  }
58
61
  return user;
59
62
  }
@@ -11,10 +11,8 @@ Object.defineProperty(exports, "PermissionGuard", {
11
11
  const _common = require("@nestjs/common");
12
12
  const _core = require("@nestjs/core");
13
13
  const _hybridcacheclass = require("../classes/hybrid-cache.class");
14
- const _winstonloggeradapterclass = require("../classes/winston-logger-adapter.class");
15
14
  const _constants = require("../constants");
16
15
  const _permissionexception = require("../exceptions/permission.exception");
17
- const _loggerinterface = require("../interfaces/logger.interface");
18
16
  const _permissioninterface = require("../interfaces/permission.interface");
19
17
  function _define_property(obj, key, value) {
20
18
  if (key in obj) {
@@ -45,206 +43,151 @@ function _ts_param(paramIndex, decorator) {
45
43
  }
46
44
  let PermissionGuard = class PermissionGuard {
47
45
  async canActivate(context) {
48
- // Check if route is marked as public
49
46
  const isPublic = this.reflector.getAllAndOverride(_constants.IS_PUBLIC_KEY, [
50
47
  context.getHandler(),
51
48
  context.getClass()
52
49
  ]);
53
50
  if (isPublic) return true;
54
- // Get required permissions from decorator
55
51
  const permissionConfig = this.reflector.getAllAndOverride(_constants.PERMISSIONS_KEY, [
56
52
  context.getHandler(),
57
53
  context.getClass()
58
54
  ]);
59
- // If no permissions required, allow access
60
- if (!permissionConfig) {
61
- return true;
62
- }
63
- // Normalize permission config (support old format: string[])
64
- const { permissions: requiredPermissions, operator } = this.normalizePermissionConfig(permissionConfig);
65
- // If no permissions required, allow access
66
- if (!requiredPermissions || requiredPermissions.length === 0) {
67
- return true;
68
- }
55
+ if (!permissionConfig) return true;
69
56
  const request = context.switchToHttp().getRequest();
70
57
  const user = request.user;
71
- // User must be authenticated
72
- if (!user) {
73
- throw new _common.UnauthorizedException('Authentication required');
74
- }
75
- // Cache is required for permission checks - fail securely if unavailable
58
+ if (!user) throw new _common.UnauthorizedException({
59
+ message: 'Authentication required',
60
+ messageKey: _constants.AUTH_MESSAGES.TOKEN_REQUIRED
61
+ });
76
62
  if (!this.cache) {
77
- // Log error (in production, this should be monitored)
78
- this.logger.error(`Cache not available - permission system unavailable (userId: ${user.id})`, undefined, 'PermissionGuard');
79
- // Fail securely - deny access rather than allowing without permission check
80
63
  throw new _permissionexception.PermissionSystemUnavailableException();
81
64
  }
82
- // Get user's permissions from cache
83
65
  const userPermissions = await this.getUserPermissions(user);
84
- // If no permissions found in cache, deny access
85
66
  if (!userPermissions || userPermissions.length === 0) {
86
- this.logger.warn(`No permissions found for user (userId: ${user.id})`, 'PermissionGuard');
87
67
  throw new _permissionexception.NoPermissionsFoundException();
88
68
  }
89
- // Check if this is a nested condition or simple permission list
90
- if (this.isNestedCondition(permissionConfig)) {
91
- // Complex nested permission check
92
- const result = this.evaluateCondition(permissionConfig, userPermissions);
93
- if (!result.passed) {
94
- this.logger.warn(`Permission check failed (userId: ${user.id}, missing: ${result.missingPermissions.join(', ')})`, 'PermissionGuard');
95
- throw new _permissionexception.InsufficientPermissionsException(result.missingPermissions, result.operator);
96
- }
97
- } else {
98
- // Simple permission check (backward compatible)
99
- let hasRequiredPermissions;
100
- if (operator === 'or') {
101
- // OR: User must have at least ONE permission
102
- hasRequiredPermissions = requiredPermissions.some((permission)=>this.hasPermission(userPermissions, permission));
103
- if (!hasRequiredPermissions) {
104
- throw new _permissionexception.InsufficientPermissionsException(requiredPermissions, 'or');
105
- }
106
- } else {
107
- // AND (default): User must have ALL permissions
108
- hasRequiredPermissions = requiredPermissions.every((permission)=>this.hasPermission(userPermissions, permission));
109
- if (!hasRequiredPermissions) {
110
- const missing = requiredPermissions.filter((permission)=>!this.hasPermission(userPermissions, permission));
111
- throw new _permissionexception.InsufficientPermissionsException(missing, 'and');
112
- }
113
- }
69
+ const logicNode = this.normalizeToLogicNode(permissionConfig);
70
+ if (!logicNode) return true;
71
+ const result = this.evaluateLogicNode(logicNode, userPermissions);
72
+ if (!result.passed) {
73
+ throw new _permissionexception.InsufficientPermissionsException(result.missingPermissions, result.operator);
114
74
  }
115
75
  return true;
116
76
  }
117
- /**
118
- * Normalize permission config to handle both old and new formats
119
- */ normalizePermissionConfig(config) {
120
- // Old format: string[]
77
+ normalizeToLogicNode(config) {
78
+ // string - single permission
79
+ if (typeof config === 'string') {
80
+ return config ? {
81
+ type: 'action',
82
+ actionId: config
83
+ } : null;
84
+ }
85
+ // string[] - treat as AND of multiple permissions
121
86
  if (Array.isArray(config)) {
87
+ if (config.length === 0) return null;
88
+ if (config.length === 1) return {
89
+ type: 'action',
90
+ actionId: config[0]
91
+ };
122
92
  return {
123
- permissions: config,
124
- operator: 'and'
93
+ type: 'group',
94
+ operator: 'AND',
95
+ children: config.map((p)=>({
96
+ type: 'action',
97
+ actionId: p
98
+ }))
125
99
  };
126
100
  }
127
- // New format: PermissionConfig
128
- return {
129
- permissions: config.permissions || [],
130
- operator: config.operator || 'and'
131
- };
132
- }
133
- /**
134
- * Check if config is a nested condition (has children)
135
- */ isNestedCondition(config) {
136
- if (Array.isArray(config)) return false;
137
- return 'children' in config && Array.isArray(config.children) && config.children.length > 0;
101
+ // SimplePermissionConfig - { permissions: string[], operator: 'AND'|'OR' }
102
+ if ('permissions' in config && Array.isArray(config.permissions)) {
103
+ const simple = config;
104
+ if (simple.permissions.length === 0) return null;
105
+ if (simple.permissions.length === 1) return {
106
+ type: 'action',
107
+ actionId: simple.permissions[0]
108
+ };
109
+ return {
110
+ type: 'group',
111
+ operator: simple.operator || 'AND',
112
+ children: simple.permissions.map((p)=>({
113
+ type: 'action',
114
+ actionId: p
115
+ }))
116
+ };
117
+ }
118
+ // ILogicNode
119
+ return config;
138
120
  }
139
- /**
140
- * Evaluate a nested permission condition recursively
141
- */ evaluateCondition(condition, userPermissions) {
142
- const { permissions = [], operator, children = [] } = condition;
143
- // Results for this level
121
+ evaluateLogicNode(node, userPermissions) {
122
+ if (node.type === 'action') {
123
+ const passed = this.hasPermission(userPermissions, node.actionId);
124
+ return {
125
+ passed,
126
+ missingPermissions: passed ? [] : [
127
+ node.actionId
128
+ ],
129
+ operator: 'AND'
130
+ };
131
+ }
132
+ // Group node
133
+ const { operator, children } = node;
134
+ // SECURITY: Fail-closed - deny access when no children configured
135
+ if (!children || children.length === 0) {
136
+ return {
137
+ passed: false,
138
+ missingPermissions: [],
139
+ operator
140
+ };
141
+ }
144
142
  const results = [];
145
- const failureDetails = [];
146
143
  const missingPermissions = [];
147
- // Check permissions at this level
148
- if (permissions.length > 0) {
149
- if (operator === 'or') {
150
- const hasAny = permissions.some((p)=>this.hasPermission(userPermissions, p));
151
- results.push(hasAny);
152
- if (!hasAny) {
153
- failureDetails.push(`needs one of: [${permissions.join(', ')}]`);
154
- missingPermissions.push(...permissions);
155
- }
156
- } else {
157
- const hasAll = permissions.every((p)=>this.hasPermission(userPermissions, p));
158
- results.push(hasAll);
159
- if (!hasAll) {
160
- const missing = permissions.filter((p)=>!this.hasPermission(userPermissions, p));
161
- failureDetails.push(`missing: [${missing.join(', ')}]`);
162
- missingPermissions.push(...missing);
163
- }
164
- }
165
- }
166
- // Evaluate children recursively
167
144
  for (const child of children){
168
- const childResult = this.evaluateCondition(child, userPermissions);
145
+ const childResult = this.evaluateLogicNode(child, userPermissions);
169
146
  results.push(childResult.passed);
170
147
  if (!childResult.passed) {
171
- failureDetails.push(`(${childResult.message})`);
172
148
  missingPermissions.push(...childResult.missingPermissions);
173
149
  }
174
150
  }
175
- // Combine results based on operator
176
- let passed;
177
- if (operator === 'or') {
178
- passed = results.length === 0 || results.some((r)=>r);
179
- } else {
180
- passed = results.length === 0 || results.every((r)=>r);
181
- }
182
- const message = passed ? 'Permission granted' : `Permission denied: ${failureDetails.join(` ${operator.toUpperCase()} `)}`;
151
+ const passed = operator === 'OR' ? results.some((r)=>r) : results.every((r)=>r);
183
152
  return {
184
153
  passed,
185
- message,
186
- missingPermissions,
154
+ missingPermissions: passed ? [] : missingPermissions,
187
155
  operator
188
156
  };
189
157
  }
190
- /**
191
- * Get user's permissions from cache
192
- */ async getUserPermissions(user) {
193
- if (!this.cache) {
194
- throw new _permissionexception.PermissionSystemUnavailableException();
195
- }
196
- let cacheKey;
158
+ async getUserPermissions(user) {
159
+ if (!this.cache) throw new _permissionexception.PermissionSystemUnavailableException();
160
+ const cacheKey = this.buildPermissionCacheKey(user);
161
+ return await this.cache.get(cacheKey) || [];
162
+ }
163
+ buildPermissionCacheKey(user) {
197
164
  if (this.config.enableCompanyFeature && user.companyId) {
198
- // Company-based permissions (includes branchId for branch-scoped DIRECT permissions)
199
- const format = this.config.companyPermissionKeyFormat || `${_constants.PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`;
200
- cacheKey = format.replace('{userId}', user.id).replace('{companyId}', user.companyId).replace('{branchId}', user.branchId || 'null');
201
- } else {
202
- // User-based permissions
203
- const format = this.config.userPermissionKeyFormat || `${_constants.PERMISSIONS_CACHE_PREFIX}:user:{userId}`;
204
- cacheKey = format.replace('{userId}', user.id);
165
+ return (this.config.companyPermissionKeyFormat || `${_constants.PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`).replace('{userId}', user.id).replace('{companyId}', user.companyId).replace('{branchId}', user.branchId || 'null');
205
166
  }
206
- const permissions = await this.cache.get(cacheKey);
207
- return permissions || [];
167
+ return (this.config.userPermissionKeyFormat || `${_constants.PERMISSIONS_CACHE_PREFIX}:user:{userId}`).replace('{userId}', user.id);
208
168
  }
209
- /**
210
- * Check if user has a specific permission
211
- * Supports wildcard matching (e.g., 'admin.*' matches 'admin.users.read')
212
- */ hasPermission(userPermissions, requiredPermission) {
213
- // Direct match
214
- if (userPermissions.includes(requiredPermission)) {
215
- return true;
216
- }
217
- // Wildcard match (e.g., '*' or 'admin.*')
169
+ hasPermission(userPermissions, requiredPermission) {
170
+ if (userPermissions.includes(requiredPermission)) return true;
218
171
  for (const permission of userPermissions){
219
- if (permission === '*') {
220
- return true; // Super admin
221
- }
222
- if (permission.endsWith('.*')) {
223
- const prefix = permission.slice(0, -1); // Remove '*'
224
- if (requiredPermission.startsWith(prefix)) {
225
- return true;
226
- }
172
+ if (permission === '*') return true;
173
+ if (permission.endsWith('.*') && requiredPermission.startsWith(permission.slice(0, -1))) {
174
+ return true;
227
175
  }
228
176
  }
229
177
  return false;
230
178
  }
231
- // NOTE: @Inject(Reflector) required for bundled code - external classes need explicit injection
232
- constructor(reflector, cache, config, logger){
179
+ constructor(reflector, cache, config){
233
180
  _define_property(this, "reflector", void 0);
234
181
  _define_property(this, "cache", void 0);
235
182
  _define_property(this, "config", void 0);
236
- _define_property(this, "logger", void 0);
237
183
  this.reflector = reflector;
238
184
  this.cache = cache;
239
185
  this.config = {
240
186
  enableCompanyFeature: false,
241
- cacheKeyPrefix: _constants.PERMISSIONS_CACHE_PREFIX,
242
187
  userPermissionKeyFormat: `${_constants.PERMISSIONS_CACHE_PREFIX}:user:{userId}`,
243
188
  companyPermissionKeyFormat: `${_constants.PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`,
244
189
  ...config
245
190
  };
246
- // Use provided logger or fallback to NestJS Logger wrapped in adapter
247
- this.logger = logger || new _winstonloggeradapterclass.NestLoggerAdapter(new _common.Logger(PermissionGuard.name));
248
191
  }
249
192
  };
250
193
  PermissionGuard = _ts_decorate([
@@ -254,13 +197,10 @@ PermissionGuard = _ts_decorate([
254
197
  _ts_param(1, (0, _common.Inject)(_constants.CACHE_INSTANCE)),
255
198
  _ts_param(2, (0, _common.Optional)()),
256
199
  _ts_param(2, (0, _common.Inject)(_constants.PERMISSION_GUARD_CONFIG)),
257
- _ts_param(3, (0, _common.Optional)()),
258
- _ts_param(3, (0, _common.Inject)(_constants.LOGGER_INSTANCE)),
259
200
  _ts_metadata("design:type", Function),
260
201
  _ts_metadata("design:paramtypes", [
261
202
  typeof _core.Reflector === "undefined" ? Object : _core.Reflector,
262
203
  typeof _hybridcacheclass.HybridCache === "undefined" ? Object : _hybridcacheclass.HybridCache,
263
- typeof _permissioninterface.PermissionGuardConfig === "undefined" ? Object : _permissioninterface.PermissionGuardConfig,
264
- typeof _loggerinterface.ILogger === "undefined" ? Object : _loggerinterface.ILogger
204
+ typeof _permissioninterface.PermissionGuardConfig === "undefined" ? Object : _permissioninterface.PermissionGuardConfig
265
205
  ])
266
206
  ], PermissionGuard);
package/cjs/index.js CHANGED
@@ -7,7 +7,9 @@ _export_star(require("./constants"), exports);
7
7
  _export_star(require("./decorators"), exports);
8
8
  _export_star(require("./dtos"), exports);
9
9
  _export_star(require("./entities"), exports);
10
+ _export_star(require("./enums"), exports);
10
11
  _export_star(require("./exceptions"), exports);
12
+ _export_star(require("./filters"), exports);
11
13
  _export_star(require("./guards"), exports);
12
14
  _export_star(require("./interceptors"), exports);
13
15
  _export_star(require("./interfaces"), exports);
@@ -61,7 +61,7 @@ let IdempotencyInterceptor = class IdempotencyInterceptor {
61
61
  throw new _common.ConflictException({
62
62
  success: false,
63
63
  message: 'Request is already being processed',
64
- code: 'DUPLICATE_REQUEST_IN_PROGRESS'
64
+ messageKey: _constants.SYSTEM_MESSAGES.DUPLICATE_REQUEST
65
65
  });
66
66
  }
67
67
  // Return cached response