@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
@@ -26,10 +26,10 @@ export class Identity {
26
26
  _define_property(this, "id", void 0);
27
27
  _define_property(this, "createdAt", void 0);
28
28
  _define_property(this, "updatedAt", void 0);
29
- _define_property(this, "deletedAt", void 0);
30
- _define_property(this, "createdById", void 0);
31
- _define_property(this, "updatedById", void 0);
32
- _define_property(this, "deletedById", void 0);
29
+ _define_property(this, "deletedAt", null);
30
+ _define_property(this, "createdById", null);
31
+ _define_property(this, "updatedById", null);
32
+ _define_property(this, "deletedById", null);
33
33
  }
34
34
  }
35
35
  _ts_decorate([
@@ -39,14 +39,16 @@ _ts_decorate([
39
39
  _ts_decorate([
40
40
  CreateDateColumn({
41
41
  name: 'created_at',
42
- type: 'timestamp'
42
+ type: 'timestamp',
43
+ select: false
43
44
  }),
44
45
  _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
45
46
  ], Identity.prototype, "createdAt", void 0);
46
47
  _ts_decorate([
47
48
  UpdateDateColumn({
48
49
  name: 'updated_at',
49
- type: 'timestamp'
50
+ type: 'timestamp',
51
+ select: false
50
52
  }),
51
53
  _ts_metadata("design:type", typeof Date === "undefined" ? Object : Date)
52
54
  ], Identity.prototype, "updatedAt", void 0);
@@ -54,6 +56,7 @@ _ts_decorate([
54
56
  DeleteDateColumn({
55
57
  name: 'deleted_at',
56
58
  type: 'timestamp',
59
+ select: false,
57
60
  nullable: true
58
61
  }),
59
62
  _ts_metadata("design:type", Object)
@@ -62,6 +65,7 @@ _ts_decorate([
62
65
  Column({
63
66
  name: 'created_by_id',
64
67
  type: 'uuid',
68
+ select: false,
65
69
  nullable: true
66
70
  }),
67
71
  _ts_metadata("design:type", Object)
@@ -70,6 +74,7 @@ _ts_decorate([
70
74
  Column({
71
75
  name: 'updated_by_id',
72
76
  type: 'uuid',
77
+ select: false,
73
78
  nullable: true
74
79
  }),
75
80
  _ts_metadata("design:type", Object)
@@ -78,6 +83,7 @@ _ts_decorate([
78
83
  Column({
79
84
  name: 'deleted_by_id',
80
85
  type: 'uuid',
86
+ select: false,
81
87
  nullable: true
82
88
  }),
83
89
  _ts_metadata("design:type", Object)
@@ -1,2 +1,3 @@
1
1
  export * from './identity';
2
+ export * from './raw-type';
2
3
  export * from './user-root';
@@ -0,0 +1 @@
1
+ export { };
@@ -20,27 +20,27 @@ function _ts_decorate(decorators, target, key, desc) {
20
20
  function _ts_metadata(k, v) {
21
21
  if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
22
  }
23
+ import { getJsonColumnType } from '@flusys/nestjs-shared';
23
24
  import { Column, CreateDateColumn, DeleteDateColumn, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm';
24
25
  export class UserRoot {
25
26
  constructor(){
26
27
  _define_property(this, "id", void 0);
27
- _define_property(this, "name", void 0);
28
- _define_property(this, "password", void 0);
28
+ _define_property(this, "name", null);
29
+ _define_property(this, "password", null);
29
30
  _define_property(this, "email", void 0);
30
- _define_property(this, "phone", void 0);
31
- _define_property(this, "isActive", void 0);
32
- _define_property(this, "emailVerified", void 0);
33
- _define_property(this, "phoneVerified", void 0);
34
- _define_property(this, "profilePictureId", void 0);
35
- _define_property(this, "lastLoginAt", void 0);
36
- _define_property(this, "additionalFields", void 0);
31
+ _define_property(this, "phone", null);
32
+ _define_property(this, "isActive", true);
33
+ _define_property(this, "emailVerified", false);
34
+ _define_property(this, "phoneVerified", false);
35
+ _define_property(this, "profilePictureId", null);
36
+ _define_property(this, "lastLoginAt", null);
37
+ _define_property(this, "additionalFields", null);
37
38
  _define_property(this, "createdAt", void 0);
38
39
  _define_property(this, "updatedAt", void 0);
39
- _define_property(this, "deletedAt", void 0);
40
- // Audit columns (for ApiService compatibility)
41
- _define_property(this, "createdById", void 0);
42
- _define_property(this, "updatedById", void 0);
43
- _define_property(this, "deletedById", void 0);
40
+ _define_property(this, "deletedAt", null);
41
+ _define_property(this, "createdById", null);
42
+ _define_property(this, "updatedById", null);
43
+ _define_property(this, "deletedById", null);
44
44
  }
45
45
  }
46
46
  _ts_decorate([
@@ -125,7 +125,7 @@ _ts_decorate([
125
125
  ], UserRoot.prototype, "lastLoginAt", void 0);
126
126
  _ts_decorate([
127
127
  Column({
128
- type: 'json',
128
+ type: getJsonColumnType(),
129
129
  nullable: true,
130
130
  name: 'additional_fields'
131
131
  }),
@@ -0,0 +1,3 @@
1
+ export * from './notification-type.enum';
2
+ export * from './participant-status.enum';
3
+ export * from './recurrence-type.enum';
@@ -0,0 +1,7 @@
1
+ export var NotificationType = /*#__PURE__*/ function(NotificationType) {
2
+ NotificationType["INFO"] = "info";
3
+ NotificationType["WARNING"] = "warning";
4
+ NotificationType["SUCCESS"] = "success";
5
+ NotificationType["ERROR"] = "error";
6
+ return NotificationType;
7
+ }({});
@@ -0,0 +1,7 @@
1
+ export var ParticipantStatus = /*#__PURE__*/ function(ParticipantStatus) {
2
+ ParticipantStatus["PENDING"] = "pending";
3
+ ParticipantStatus["ACCEPTED"] = "accepted";
4
+ ParticipantStatus["DECLINED"] = "declined";
5
+ ParticipantStatus["TENTATIVE"] = "tentative";
6
+ return ParticipantStatus;
7
+ }({});
@@ -0,0 +1,8 @@
1
+ export var RecurrenceType = /*#__PURE__*/ function(RecurrenceType) {
2
+ RecurrenceType["NONE"] = "none";
3
+ RecurrenceType["DAILY"] = "daily";
4
+ RecurrenceType["WEEKLY"] = "weekly";
5
+ RecurrenceType["BIWEEKLY"] = "biweekly";
6
+ RecurrenceType["MONTHLY"] = "monthly";
7
+ return RecurrenceType;
8
+ }({});
@@ -0,0 +1,115 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ import { HttpException, HttpStatus } from '@nestjs/common';
15
+ import { ERROR_MESSAGES } from '../constants/message-keys';
16
+ export class BaseAppException extends HttpException {
17
+ constructor(options){
18
+ const status = options.status || HttpStatus.BAD_REQUEST;
19
+ super({
20
+ success: false,
21
+ message: options.message,
22
+ messageKey: options.messageKey || ERROR_MESSAGES.GENERIC,
23
+ messageVariables: options.messageVariables,
24
+ errors: options.errors
25
+ }, status), _define_property(this, "messageKey", void 0), _define_property(this, "messageVariables", void 0), _define_property(this, "errors", void 0), _define_property(this, "metadata", void 0);
26
+ this.messageKey = options.messageKey || ERROR_MESSAGES.GENERIC;
27
+ this.messageVariables = options.messageVariables;
28
+ this.errors = options.errors;
29
+ this.metadata = options.metadata;
30
+ }
31
+ }
32
+ export class NotFoundException extends BaseAppException {
33
+ constructor(entity, id){
34
+ super({
35
+ message: id ? `${entity} with id "${id}" not found` : `${entity} not found`,
36
+ messageKey: ERROR_MESSAGES.NOT_FOUND,
37
+ status: HttpStatus.NOT_FOUND,
38
+ metadata: {
39
+ entity,
40
+ id
41
+ }
42
+ });
43
+ }
44
+ }
45
+ export class ValidationException extends BaseAppException {
46
+ constructor(errors){
47
+ super({
48
+ message: 'Validation failed',
49
+ messageKey: ERROR_MESSAGES.VALIDATION,
50
+ status: HttpStatus.BAD_REQUEST,
51
+ errors
52
+ });
53
+ }
54
+ }
55
+ export class UnauthorizedException extends BaseAppException {
56
+ constructor(message = 'Unauthorized access'){
57
+ super({
58
+ message,
59
+ messageKey: ERROR_MESSAGES.UNAUTHORIZED,
60
+ status: HttpStatus.UNAUTHORIZED
61
+ });
62
+ }
63
+ }
64
+ export class ForbiddenException extends BaseAppException {
65
+ constructor(message = 'Access forbidden'){
66
+ super({
67
+ message,
68
+ messageKey: ERROR_MESSAGES.FORBIDDEN,
69
+ status: HttpStatus.FORBIDDEN
70
+ });
71
+ }
72
+ }
73
+ export class ConflictException extends BaseAppException {
74
+ constructor(entity, field){
75
+ super({
76
+ message: field ? `${entity} with this ${field} already exists` : `${entity} already exists`,
77
+ messageKey: ERROR_MESSAGES.CONFLICT,
78
+ messageVariables: {
79
+ entity,
80
+ ...field !== undefined && {
81
+ field
82
+ }
83
+ },
84
+ status: HttpStatus.CONFLICT,
85
+ metadata: {
86
+ entity,
87
+ field
88
+ }
89
+ });
90
+ }
91
+ }
92
+ export class InternalServerException extends BaseAppException {
93
+ constructor(message = 'Internal server error'){
94
+ super({
95
+ message,
96
+ messageKey: ERROR_MESSAGES.INTERNAL,
97
+ status: HttpStatus.INTERNAL_SERVER_ERROR
98
+ });
99
+ }
100
+ }
101
+ export class ServiceUnavailableException extends BaseAppException {
102
+ constructor(service){
103
+ super({
104
+ message: `${service} is temporarily unavailable`,
105
+ messageKey: ERROR_MESSAGES.SERVICE_UNAVAILABLE,
106
+ messageVariables: {
107
+ service
108
+ },
109
+ status: HttpStatus.SERVICE_UNAVAILABLE,
110
+ metadata: {
111
+ service
112
+ }
113
+ });
114
+ }
115
+ }
@@ -1 +1,2 @@
1
+ export * from './base-app.exception';
1
2
  export * from './permission.exception';
@@ -1,37 +1,35 @@
1
1
  import { ForbiddenException, InternalServerErrorException } from '@nestjs/common';
2
- /**
3
- * Exception thrown when permission system is unavailable
4
- */ export class PermissionSystemUnavailableException extends InternalServerErrorException {
5
- constructor(message = 'Permission system temporarily unavailable. Please try again later.'){
2
+ import { ERROR_MESSAGES } from '../constants/message-keys';
3
+ export class PermissionSystemUnavailableException extends InternalServerErrorException {
4
+ constructor(){
6
5
  super({
7
6
  success: false,
8
- message,
9
- code: 'PERMISSION_SYSTEM_UNAVAILABLE'
7
+ message: 'Permission system temporarily unavailable',
8
+ messageKey: ERROR_MESSAGES.PERMISSION_SYSTEM_UNAVAILABLE
10
9
  });
11
10
  }
12
11
  }
13
- /**
14
- * Exception thrown when user lacks required permissions
15
- */ export class InsufficientPermissionsException extends ForbiddenException {
16
- constructor(missingPermissions, operator = 'and'){
17
- const message = operator === 'or' ? `Requires at least one of: ${missingPermissions.join(', ')}` : `Missing required permissions: ${missingPermissions.join(', ')}`;
12
+ export class InsufficientPermissionsException extends ForbiddenException {
13
+ constructor(missingPermissions, operator = 'AND'){
14
+ const messageKey = operator === 'OR' ? ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS_OR : ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS;
18
15
  super({
19
16
  success: false,
20
- message,
21
- code: 'INSUFFICIENT_PERMISSIONS',
17
+ message: 'Insufficient permissions',
18
+ messageKey,
19
+ messageVariables: {
20
+ permissions: missingPermissions.join(', ')
21
+ },
22
22
  missingPermissions,
23
23
  operator
24
24
  });
25
25
  }
26
26
  }
27
- /**
28
- * Exception thrown when no permissions found for user
29
- */ export class NoPermissionsFoundException extends ForbiddenException {
27
+ export class NoPermissionsFoundException extends ForbiddenException {
30
28
  constructor(){
31
29
  super({
32
30
  success: false,
33
- message: 'No permissions found. Please contact administrator.',
34
- code: 'NO_PERMISSIONS_FOUND'
31
+ message: 'No permissions found',
32
+ messageKey: ERROR_MESSAGES.NO_PERMISSIONS_FOUND
35
33
  });
36
34
  }
37
35
  }
@@ -0,0 +1,269 @@
1
+ function _define_property(obj, key, value) {
2
+ if (key in obj) {
3
+ Object.defineProperty(obj, key, {
4
+ value: value,
5
+ enumerable: true,
6
+ configurable: true,
7
+ writable: true
8
+ });
9
+ } else {
10
+ obj[key] = value;
11
+ }
12
+ return obj;
13
+ }
14
+ function _ts_decorate(decorators, target, key, desc) {
15
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
+ 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;
18
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
19
+ }
20
+ import { Catch, HttpException, HttpStatus, Logger } from '@nestjs/common';
21
+ import { QueryFailedError } from 'typeorm';
22
+ import { ERROR_MESSAGES } from '../constants/message-keys';
23
+ import { BaseAppException, ConflictException } from '../exceptions/base-app.exception';
24
+ import { getCompanyId, getRequestId, getTenantId, getUserId } from '../middlewares/logger.middleware';
25
+ export class GlobalExceptionFilter {
26
+ catch(exception, host) {
27
+ const ctx = host.switchToHttp();
28
+ const request = ctx.getRequest();
29
+ const response = ctx.getResponse();
30
+ const responseTime = Date.now() - (request._startTime || Date.now());
31
+ const requestId = getRequestId() || request.headers['x-request-id'] || 'no-request-id';
32
+ const { statusCode, errorResponse } = this.resolveException(exception, requestId, responseTime);
33
+ const logContext = {
34
+ requestId,
35
+ userId: getUserId(),
36
+ tenantId: getTenantId(),
37
+ companyId: getCompanyId(),
38
+ method: request.method,
39
+ url: request.originalUrl,
40
+ path: request.path,
41
+ statusCode,
42
+ duration: `${responseTime}ms`,
43
+ messageKey: errorResponse.messageKey
44
+ };
45
+ const logMessage = `Exception caught "HTTP/1.1" "${request.method}" "${request.originalUrl}" - ${statusCode} "${errorResponse.messageKey}" ${responseTime}ms | ${errorResponse.message}`;
46
+ if (statusCode >= 500) {
47
+ this.logger.error(logMessage, exception instanceof Error ? exception.stack : undefined, logContext);
48
+ } else if (statusCode >= 400) {
49
+ this.logger.warn(logMessage, logContext);
50
+ }
51
+ response.status(statusCode).json(errorResponse);
52
+ }
53
+ resolveException(exception, requestId, responseTime) {
54
+ const meta = {
55
+ requestId,
56
+ timestamp: new Date().toISOString(),
57
+ responseTime
58
+ };
59
+ if (exception instanceof BaseAppException) {
60
+ return {
61
+ statusCode: exception.getStatus(),
62
+ errorResponse: {
63
+ success: false,
64
+ message: exception.message,
65
+ messageKey: exception.messageKey,
66
+ messageVariables: exception.messageVariables,
67
+ errors: exception.errors,
68
+ _meta: meta
69
+ }
70
+ };
71
+ }
72
+ if (exception instanceof HttpException) {
73
+ const resolved = this.resolveHttpException(exception);
74
+ return {
75
+ statusCode: resolved.statusCode,
76
+ errorResponse: {
77
+ success: false,
78
+ message: resolved.message,
79
+ messageKey: resolved.messageKey,
80
+ messageVariables: resolved.messageVariables,
81
+ errors: resolved.errors,
82
+ _meta: meta
83
+ }
84
+ };
85
+ }
86
+ if (exception instanceof QueryFailedError) {
87
+ const resolved = this.resolveQueryFailedError(exception);
88
+ if (resolved) {
89
+ return {
90
+ statusCode: resolved.getStatus(),
91
+ errorResponse: {
92
+ success: false,
93
+ message: resolved.message,
94
+ messageKey: resolved.messageKey,
95
+ messageVariables: resolved.messageVariables,
96
+ _meta: meta
97
+ }
98
+ };
99
+ }
100
+ }
101
+ return {
102
+ statusCode: HttpStatus.INTERNAL_SERVER_ERROR,
103
+ errorResponse: {
104
+ success: false,
105
+ message: 'Internal server error',
106
+ messageKey: ERROR_MESSAGES.INTERNAL,
107
+ _meta: meta
108
+ }
109
+ };
110
+ }
111
+ resolveHttpException(exception) {
112
+ const statusCode = exception.getStatus();
113
+ const response = exception.getResponse();
114
+ if (typeof response === 'string') {
115
+ return {
116
+ statusCode,
117
+ message: response,
118
+ messageKey: ERROR_MESSAGES.HTTP
119
+ };
120
+ }
121
+ if (typeof response === 'object' && response !== null) {
122
+ const obj = response;
123
+ if (Array.isArray(obj.message)) {
124
+ const errors = obj.message.map((msg)=>{
125
+ const [field, ...rest] = msg.split(' ');
126
+ return {
127
+ field: field || 'unknown',
128
+ message: rest.join(' ') || msg
129
+ };
130
+ });
131
+ return {
132
+ statusCode,
133
+ message: 'Validation failed',
134
+ messageKey: ERROR_MESSAGES.VALIDATION,
135
+ errors
136
+ };
137
+ }
138
+ return {
139
+ statusCode,
140
+ message: String(obj.message || 'Unknown error'),
141
+ messageKey: String(obj.messageKey || ERROR_MESSAGES.HTTP),
142
+ messageVariables: obj.messageVariables,
143
+ errors: obj.errors
144
+ };
145
+ }
146
+ return {
147
+ statusCode,
148
+ message: 'Unknown error',
149
+ messageKey: ERROR_MESSAGES.UNKNOWN
150
+ };
151
+ }
152
+ resolveQueryFailedError(exception) {
153
+ const dbError = exception;
154
+ const code = dbError.driverError?.code ?? dbError.code;
155
+ const number = dbError.driverError?.number ?? dbError.number;
156
+ const errno = dbError.driverError?.errno ?? dbError.errno;
157
+ const sqlMessage = dbError.driverError?.sqlMessage ?? '';
158
+ const detail = dbError.driverError?.detail ?? dbError.detail ?? '';
159
+ const table = dbError.driverError?.table ?? dbError.table ?? '';
160
+ const column = dbError.driverError?.column ?? dbError.column;
161
+ const pgField = detail.match(/Key \(([^)]+)\)/)?.[1] ?? column;
162
+ // DUPLICATE / UNIQUE VIOLATION
163
+ // PG: 23505 | MSSQL: 2627 (constraint), 2601 (index) | MySQL: ER_DUP_ENTRY / errno 1062
164
+ if (code === '23505' || number === 2627 || number === 2601 || code === 'ER_DUP_ENTRY' || errno === 1062) {
165
+ const mysqlTable = sqlMessage.match(/for key '([^'.]+)\./)?.[1];
166
+ const mysqlIndex = sqlMessage.match(/for key '(?:[^.]+\.)?([^']+)'/)?.[1];
167
+ const resolvedTable = table || mysqlTable || 'record';
168
+ const resolvedField = pgField ? this.toHumanReadable(pgField) : mysqlIndex ? this.toHumanReadable(mysqlIndex) : undefined;
169
+ return new ConflictException(this.toHumanReadable(resolvedTable), resolvedField);
170
+ }
171
+ // FOREIGN KEY VIOLATION
172
+ // PG: 23503 | MSSQL: 547 | MySQL: ER_NO_REFERENCED_ROW_2/1452 (insert), ER_ROW_IS_REFERENCED_2/1451 (delete)
173
+ if (code === '23503' || number === 547 || code === 'ER_NO_REFERENCED_ROW_2' || code === 'ER_ROW_IS_REFERENCED_2' || errno === 1452 || errno === 1451) {
174
+ const mysqlColumn = sqlMessage.match(/FOREIGN KEY \(`([^`]+)`\)/)?.[1];
175
+ const raw = pgField ?? mysqlColumn;
176
+ const humanField = raw ? this.toHumanReadable(raw) : undefined;
177
+ return new BaseAppException({
178
+ message: humanField ? `Invalid reference for '${humanField}'` : 'Invalid reference',
179
+ messageKey: ERROR_MESSAGES.DB_FOREIGN_KEY,
180
+ messageVariables: humanField ? {
181
+ field: humanField
182
+ } : undefined,
183
+ status: HttpStatus.BAD_REQUEST
184
+ });
185
+ }
186
+ // NOT NULL VIOLATION
187
+ // PG: 23502 | MSSQL: 515 | MySQL: ER_BAD_NULL_ERROR / errno 1048
188
+ if (code === '23502' || number === 515 || code === 'ER_BAD_NULL_ERROR' || errno === 1048) {
189
+ const mysqlColumn = sqlMessage.match(/Column '([^']+)' cannot be null/)?.[1];
190
+ const humanField = this.toHumanReadable(column ?? mysqlColumn ?? 'field');
191
+ return new BaseAppException({
192
+ message: `Required field '${humanField}' cannot be null`,
193
+ messageKey: ERROR_MESSAGES.DB_NOT_NULL,
194
+ messageVariables: {
195
+ field: humanField
196
+ },
197
+ status: HttpStatus.BAD_REQUEST
198
+ });
199
+ }
200
+ // INVALID INPUT SYNTAX — PostgreSQL only (MySQL silently coerces types)
201
+ if (code === '22P02') {
202
+ const typeMatch = exception.message.match(/invalid input syntax for(?:\s+type)?\s+(\w+)/i);
203
+ const type = typeMatch?.[1] ?? 'value';
204
+ const humanField = column ? this.toHumanReadable(column) : undefined;
205
+ return new BaseAppException({
206
+ message: humanField ? `Invalid ${type} value for '${humanField}'` : `Invalid ${type} value`,
207
+ messageKey: ERROR_MESSAGES.DB_INVALID_INPUT,
208
+ messageVariables: {
209
+ type,
210
+ ...humanField !== undefined && {
211
+ field: humanField
212
+ }
213
+ },
214
+ status: HttpStatus.BAD_REQUEST
215
+ });
216
+ }
217
+ // VALUE TOO LONG
218
+ // PG: 22001 | MySQL: ER_DATA_TOO_LONG / errno 1406
219
+ if (code === '22001' || code === 'ER_DATA_TOO_LONG' || errno === 1406) {
220
+ const mysqlColumn = sqlMessage.match(/Data too long for column '([^']+)'/)?.[1];
221
+ const resolvedColumn = column ?? mysqlColumn;
222
+ const humanField = resolvedColumn ? this.toHumanReadable(resolvedColumn) : undefined;
223
+ return new BaseAppException({
224
+ message: humanField ? `Value too long for '${humanField}'` : 'Value exceeds maximum length',
225
+ messageKey: ERROR_MESSAGES.DB_VALUE_TOO_LONG,
226
+ messageVariables: humanField !== undefined ? {
227
+ field: humanField
228
+ } : undefined,
229
+ status: HttpStatus.BAD_REQUEST
230
+ });
231
+ }
232
+ // CHECK CONSTRAINT VIOLATION
233
+ // PG: 23514 | MySQL 8.0+: ER_CHECK_CONSTRAINT_VIOLATED / errno 3819
234
+ if (code === '23514' || code === 'ER_CHECK_CONSTRAINT_VIOLATED' || errno === 3819) {
235
+ const constraint = dbError.driverError?.constraint ?? dbError.constraint;
236
+ const humanTable = this.toHumanReadable(table || 'record');
237
+ return new BaseAppException({
238
+ message: `${humanTable} violates a validation rule`,
239
+ messageKey: ERROR_MESSAGES.DB_CHECK_CONSTRAINT,
240
+ messageVariables: {
241
+ entity: humanTable,
242
+ ...constraint !== undefined && {
243
+ constraint
244
+ }
245
+ },
246
+ status: HttpStatus.BAD_REQUEST
247
+ });
248
+ }
249
+ // DEADLOCK
250
+ // PG: 40P01 | MSSQL: 1205 | MySQL: ER_LOCK_DEADLOCK / errno 1213
251
+ if (code === '40P01' || number === 1205 || code === 'ER_LOCK_DEADLOCK' || errno === 1213) {
252
+ return new BaseAppException({
253
+ message: 'The request could not be completed due to a conflict, please try again',
254
+ messageKey: ERROR_MESSAGES.DB_DEADLOCK,
255
+ status: HttpStatus.SERVICE_UNAVAILABLE
256
+ });
257
+ }
258
+ return null;
259
+ }
260
+ toHumanReadable(dbName) {
261
+ return dbName.split(', ').map((part)=>part.split('_').map((word)=>word.toLowerCase() === 'id' ? 'ID' : word.charAt(0).toUpperCase() + word.slice(1)).join(' ')).join(', ');
262
+ }
263
+ constructor(){
264
+ _define_property(this, "logger", new Logger('ExceptionFilter'));
265
+ }
266
+ }
267
+ GlobalExceptionFilter = _ts_decorate([
268
+ Catch()
269
+ ], GlobalExceptionFilter);
@@ -0,0 +1 @@
1
+ export * from './global-exception.filter';
@@ -25,7 +25,7 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { IS_PUBLIC_KEY } from '../constants';
28
+ import { AUTH_MESSAGES, IS_PUBLIC_KEY } from '../constants';
29
29
  import { Inject, Injectable, UnauthorizedException } from '@nestjs/common';
30
30
  import { Reflector } from '@nestjs/core';
31
31
  import { AuthGuard } from '@nestjs/passport';
@@ -43,7 +43,10 @@ export class JwtAuthGuard extends AuthGuard('jwt') {
43
43
  }
44
44
  handleRequest(err, user) {
45
45
  if (err || !user) {
46
- throw err || new UnauthorizedException('Invalid or expired token');
46
+ throw err || new UnauthorizedException({
47
+ message: 'Invalid or expired token',
48
+ messageKey: AUTH_MESSAGES.TOKEN_INVALID
49
+ });
47
50
  }
48
51
  return user;
49
52
  }