@flusys/nestjs-shared 1.1.0 → 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.
- package/README.md +139 -1103
- package/cjs/classes/api-controller.class.js +320 -29
- package/cjs/classes/api-service.class.js +219 -64
- package/cjs/classes/handle-custome-db.js +37 -0
- package/cjs/classes/index.js +1 -2
- package/cjs/classes/winston.logger.class.js +103 -70
- package/cjs/constants/index.js +1 -4
- package/cjs/constants/message-keys.js +68 -0
- package/cjs/constants/permissions.js +114 -11
- package/cjs/decorators/api-response.decorator.js +1 -2
- package/cjs/decorators/index.js +1 -1
- package/cjs/decorators/log-action.decorator.js +149 -0
- package/cjs/decorators/require-permission.decorator.js +5 -5
- package/cjs/dtos/get-by-ids.dto.js +65 -0
- package/cjs/dtos/identity-response.dto.js +4 -8
- package/cjs/dtos/index.js +1 -0
- package/cjs/dtos/response-payload.dto.js +72 -0
- package/cjs/entities/identity.js +8 -2
- package/cjs/entities/index.js +1 -0
- package/cjs/entities/raw-type.js +4 -0
- package/cjs/entities/user-root.js +2 -1
- package/cjs/enums/index.js +20 -0
- package/cjs/enums/notification-type.enum.js +17 -0
- package/cjs/enums/participant-status.enum.js +17 -0
- package/cjs/enums/recurrence-type.enum.js +18 -0
- package/cjs/exceptions/base-app.exception.js +151 -0
- package/cjs/exceptions/index.js +1 -0
- package/cjs/exceptions/permission.exception.js +13 -9
- package/cjs/filters/global-exception.filter.js +279 -0
- package/cjs/filters/index.js +18 -0
- package/cjs/guards/jwt-auth.guard.js +4 -1
- package/cjs/guards/permission.guard.js +71 -77
- package/cjs/index.js +2 -0
- package/cjs/interceptors/idempotency.interceptor.js +1 -1
- package/cjs/interceptors/index.js +0 -1
- package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
- package/cjs/interfaces/index.js +2 -1
- package/cjs/interfaces/notification-adapter.interface.js +11 -0
- package/cjs/interfaces/permission.interface.js +1 -1
- package/cjs/middlewares/logger.middleware.js +83 -26
- package/cjs/modules/datasource/index.js +0 -1
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +46 -85
- package/cjs/modules/utils/utils.service.js +4 -20
- package/cjs/utils/date-time.util.js +94 -0
- package/cjs/utils/index.js +1 -1
- package/cjs/utils/query-helpers.util.js +8 -1
- package/cjs/utils/request.util.js +25 -0
- package/classes/api-controller.class.d.ts +14 -6
- package/classes/api-service.class.d.ts +25 -22
- package/classes/handle-custome-db.d.ts +3 -0
- package/classes/index.d.ts +1 -2
- package/classes/winston.logger.class.d.ts +1 -0
- package/constants/index.d.ts +1 -1
- package/constants/message-keys.d.ts +45 -0
- package/constants/permissions.d.ts +127 -0
- package/decorators/api-response.decorator.d.ts +1 -1
- package/decorators/current-user.decorator.d.ts +1 -1
- package/decorators/index.d.ts +1 -1
- package/decorators/log-action.decorator.d.ts +8 -0
- package/decorators/require-permission.decorator.d.ts +2 -2
- package/dtos/get-by-ids.dto.d.ts +4 -0
- package/dtos/index.d.ts +1 -0
- package/dtos/response-payload.dto.d.ts +8 -0
- package/entities/index.d.ts +1 -0
- package/entities/raw-type.d.ts +1 -0
- package/enums/index.d.ts +3 -0
- package/enums/notification-type.enum.d.ts +6 -0
- package/enums/participant-status.enum.d.ts +6 -0
- package/enums/recurrence-type.enum.d.ts +7 -0
- package/exceptions/base-app.exception.d.ts +41 -0
- package/exceptions/index.d.ts +1 -0
- package/exceptions/permission.exception.d.ts +2 -2
- package/fesm/classes/api-controller.class.js +321 -30
- package/fesm/classes/api-service.class.js +222 -67
- package/fesm/classes/handle-custome-db.js +19 -0
- package/fesm/classes/index.js +1 -2
- package/fesm/classes/winston.logger.class.js +100 -68
- package/fesm/constants/index.js +2 -1
- package/fesm/constants/message-keys.js +49 -0
- package/fesm/constants/permissions.js +86 -14
- package/fesm/decorators/api-response.decorator.js +2 -20
- package/fesm/decorators/index.js +1 -1
- package/fesm/decorators/log-action.decorator.js +139 -0
- package/fesm/decorators/require-permission.decorator.js +17 -65
- package/fesm/dtos/get-by-ids.dto.js +55 -0
- package/fesm/dtos/identity-response.dto.js +4 -8
- package/fesm/dtos/index.js +1 -0
- package/fesm/dtos/response-payload.dto.js +72 -0
- package/fesm/entities/identity.js +8 -2
- package/fesm/entities/index.js +1 -0
- package/fesm/entities/raw-type.js +1 -0
- package/fesm/entities/user-root.js +2 -1
- package/fesm/enums/index.js +3 -0
- package/fesm/enums/notification-type.enum.js +7 -0
- package/fesm/enums/participant-status.enum.js +7 -0
- package/fesm/enums/recurrence-type.enum.js +8 -0
- package/fesm/exceptions/base-app.exception.js +115 -0
- package/fesm/exceptions/index.js +1 -0
- package/fesm/exceptions/permission.exception.js +16 -18
- package/fesm/filters/global-exception.filter.js +269 -0
- package/fesm/filters/index.js +1 -0
- package/fesm/guards/jwt-auth.guard.js +5 -2
- package/fesm/guards/permission.guard.js +73 -79
- package/fesm/index.js +2 -0
- package/fesm/interceptors/idempotency.interceptor.js +2 -2
- package/fesm/interceptors/index.js +0 -1
- package/fesm/interfaces/datasource.interface.js +1 -20
- package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
- package/fesm/interfaces/index.js +2 -1
- package/fesm/interfaces/notification-adapter.interface.js +1 -0
- package/fesm/interfaces/permission.interface.js +1 -3
- package/fesm/middlewares/logger.middleware.js +83 -26
- package/fesm/modules/datasource/index.js +0 -1
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +47 -86
- package/fesm/modules/utils/utils.service.js +5 -21
- package/fesm/utils/date-time.util.js +70 -0
- package/fesm/utils/index.js +1 -1
- package/fesm/utils/query-helpers.util.js +8 -1
- package/fesm/utils/request.util.js +22 -0
- package/filters/global-exception.filter.d.ts +9 -0
- package/filters/index.d.ts +1 -0
- package/guards/permission.guard.d.ts +3 -7
- package/index.d.ts +2 -0
- package/interceptors/idempotency.interceptor.d.ts +1 -1
- package/interceptors/index.d.ts +0 -1
- package/interfaces/api.interface.d.ts +2 -0
- package/interfaces/event-manager-adapter.interface.d.ts +43 -0
- package/interfaces/index.d.ts +2 -1
- package/interfaces/notification-adapter.interface.d.ts +21 -0
- package/interfaces/permission.interface.d.ts +13 -8
- package/modules/datasource/index.d.ts +0 -1
- package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -15
- package/modules/utils/utils.service.d.ts +0 -1
- package/package.json +21 -14
- package/utils/date-time.util.d.ts +8 -0
- package/utils/index.d.ts +1 -1
- package/utils/request.util.d.ts +2 -0
- package/cjs/classes/request-scoped-api.service.js +0 -61
- package/cjs/classes/winston-logger-adapter.class.js +0 -104
- package/cjs/decorators/sanitize-html.decorator.js +0 -36
- package/cjs/interceptors/query-performance.interceptor.js +0 -66
- package/cjs/interfaces/logger.interface.js +0 -7
- package/cjs/modules/datasource/datasource.module.js +0 -117
- package/cjs/utils/error-handler.util.js +0 -90
- package/classes/request-scoped-api.service.d.ts +0 -14
- package/classes/winston-logger-adapter.class.d.ts +0 -22
- package/decorators/sanitize-html.decorator.d.ts +0 -2
- package/fesm/classes/request-scoped-api.service.js +0 -51
- package/fesm/classes/winston-logger-adapter.class.js +0 -107
- package/fesm/decorators/sanitize-html.decorator.js +0 -45
- package/fesm/interceptors/query-performance.interceptor.js +0 -56
- package/fesm/interfaces/logger.interface.js +0 -4
- package/fesm/modules/datasource/datasource.module.js +0 -107
- package/fesm/utils/error-handler.util.js +0 -82
- package/interceptors/query-performance.interceptor.d.ts +0 -8
- package/interfaces/logger.interface.d.ts +0 -7
- package/modules/datasource/datasource.module.d.ts +0 -19
- package/utils/error-handler.util.d.ts +0 -19
|
@@ -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(
|
|
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) {
|
|
@@ -55,104 +53,105 @@ let PermissionGuard = class PermissionGuard {
|
|
|
55
53
|
context.getClass()
|
|
56
54
|
]);
|
|
57
55
|
if (!permissionConfig) return true;
|
|
58
|
-
const { permissions: requiredPermissions, operator } = this.normalizePermissionConfig(permissionConfig);
|
|
59
|
-
if (!requiredPermissions || requiredPermissions.length === 0) return true;
|
|
60
56
|
const request = context.switchToHttp().getRequest();
|
|
61
57
|
const user = request.user;
|
|
62
|
-
if (!user) throw new _common.UnauthorizedException(
|
|
58
|
+
if (!user) throw new _common.UnauthorizedException({
|
|
59
|
+
message: 'Authentication required',
|
|
60
|
+
messageKey: _constants.AUTH_MESSAGES.TOKEN_REQUIRED
|
|
61
|
+
});
|
|
63
62
|
if (!this.cache) {
|
|
64
|
-
this.logger.error(`Cache not available (userId: ${user.id})`, undefined, 'PermissionGuard');
|
|
65
63
|
throw new _permissionexception.PermissionSystemUnavailableException();
|
|
66
64
|
}
|
|
67
65
|
const userPermissions = await this.getUserPermissions(user);
|
|
68
66
|
if (!userPermissions || userPermissions.length === 0) {
|
|
69
|
-
this.logger.warn(`No permissions found (userId: ${user.id})`, 'PermissionGuard');
|
|
70
67
|
throw new _permissionexception.NoPermissionsFoundException();
|
|
71
68
|
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
}
|
|
78
|
-
} else {
|
|
79
|
-
this.validateSimplePermissions(requiredPermissions, userPermissions, operator);
|
|
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);
|
|
80
74
|
}
|
|
81
75
|
return true;
|
|
82
76
|
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
operator: config.operator || 'and'
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
validateSimplePermissions(requiredPermissions, userPermissions, operator) {
|
|
94
|
-
if (operator === 'or') {
|
|
95
|
-
const hasAny = requiredPermissions.some((p)=>this.hasPermission(userPermissions, p));
|
|
96
|
-
if (!hasAny) throw new _permissionexception.InsufficientPermissionsException(requiredPermissions, 'or');
|
|
97
|
-
} else {
|
|
98
|
-
const hasAll = requiredPermissions.every((p)=>this.hasPermission(userPermissions, p));
|
|
99
|
-
if (!hasAll) {
|
|
100
|
-
const missing = requiredPermissions.filter((p)=>!this.hasPermission(userPermissions, p));
|
|
101
|
-
throw new _permissionexception.InsufficientPermissionsException(missing, 'and');
|
|
102
|
-
}
|
|
77
|
+
normalizeToLogicNode(config) {
|
|
78
|
+
// string - single permission
|
|
79
|
+
if (typeof config === 'string') {
|
|
80
|
+
return config ? {
|
|
81
|
+
type: 'action',
|
|
82
|
+
actionId: config
|
|
83
|
+
} : null;
|
|
103
84
|
}
|
|
85
|
+
// string[] - treat as AND of multiple permissions
|
|
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
|
+
};
|
|
92
|
+
return {
|
|
93
|
+
type: 'group',
|
|
94
|
+
operator: 'AND',
|
|
95
|
+
children: config.map((p)=>({
|
|
96
|
+
type: 'action',
|
|
97
|
+
actionId: p
|
|
98
|
+
}))
|
|
99
|
+
};
|
|
100
|
+
}
|
|
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;
|
|
104
120
|
}
|
|
105
|
-
|
|
106
|
-
if (
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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) {
|
|
113
136
|
return {
|
|
114
137
|
passed: false,
|
|
115
|
-
message: 'No permissions configured - access denied by default',
|
|
116
138
|
missingPermissions: [],
|
|
117
139
|
operator
|
|
118
140
|
};
|
|
119
141
|
}
|
|
120
142
|
const results = [];
|
|
121
|
-
const failureDetails = [];
|
|
122
143
|
const missingPermissions = [];
|
|
123
|
-
if (permissions.length > 0) {
|
|
124
|
-
if (operator === 'or') {
|
|
125
|
-
const hasAny = permissions.some((p)=>this.hasPermission(userPermissions, p));
|
|
126
|
-
results.push(hasAny);
|
|
127
|
-
if (!hasAny) {
|
|
128
|
-
failureDetails.push(`needs one of: [${permissions.join(', ')}]`);
|
|
129
|
-
missingPermissions.push(...permissions);
|
|
130
|
-
}
|
|
131
|
-
} else {
|
|
132
|
-
const hasAll = permissions.every((p)=>this.hasPermission(userPermissions, p));
|
|
133
|
-
results.push(hasAll);
|
|
134
|
-
if (!hasAll) {
|
|
135
|
-
const missing = permissions.filter((p)=>!this.hasPermission(userPermissions, p));
|
|
136
|
-
failureDetails.push(`missing: [${missing.join(', ')}]`);
|
|
137
|
-
missingPermissions.push(...missing);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
144
|
for (const child of children){
|
|
142
|
-
const childResult = this.
|
|
145
|
+
const childResult = this.evaluateLogicNode(child, userPermissions);
|
|
143
146
|
results.push(childResult.passed);
|
|
144
147
|
if (!childResult.passed) {
|
|
145
|
-
failureDetails.push(`(${childResult.message})`);
|
|
146
148
|
missingPermissions.push(...childResult.missingPermissions);
|
|
147
149
|
}
|
|
148
150
|
}
|
|
149
|
-
|
|
150
|
-
const passed = operator === 'or' ? results.some((r)=>r) : results.every((r)=>r);
|
|
151
|
-
const message = passed ? 'OK' : `Denied: ${failureDetails.join(` ${operator.toUpperCase()} `)}`;
|
|
151
|
+
const passed = operator === 'OR' ? results.some((r)=>r) : results.every((r)=>r);
|
|
152
152
|
return {
|
|
153
153
|
passed,
|
|
154
|
-
|
|
155
|
-
missingPermissions,
|
|
154
|
+
missingPermissions: passed ? [] : missingPermissions,
|
|
156
155
|
operator
|
|
157
156
|
};
|
|
158
157
|
}
|
|
@@ -177,11 +176,10 @@ let PermissionGuard = class PermissionGuard {
|
|
|
177
176
|
}
|
|
178
177
|
return false;
|
|
179
178
|
}
|
|
180
|
-
constructor(reflector, cache, config
|
|
179
|
+
constructor(reflector, cache, config){
|
|
181
180
|
_define_property(this, "reflector", void 0);
|
|
182
181
|
_define_property(this, "cache", void 0);
|
|
183
182
|
_define_property(this, "config", void 0);
|
|
184
|
-
_define_property(this, "logger", void 0);
|
|
185
183
|
this.reflector = reflector;
|
|
186
184
|
this.cache = cache;
|
|
187
185
|
this.config = {
|
|
@@ -190,7 +188,6 @@ let PermissionGuard = class PermissionGuard {
|
|
|
190
188
|
companyPermissionKeyFormat: `${_constants.PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`,
|
|
191
189
|
...config
|
|
192
190
|
};
|
|
193
|
-
this.logger = logger || new _winstonloggeradapterclass.NestLoggerAdapter(new _common.Logger(PermissionGuard.name));
|
|
194
191
|
}
|
|
195
192
|
};
|
|
196
193
|
PermissionGuard = _ts_decorate([
|
|
@@ -200,13 +197,10 @@ PermissionGuard = _ts_decorate([
|
|
|
200
197
|
_ts_param(1, (0, _common.Inject)(_constants.CACHE_INSTANCE)),
|
|
201
198
|
_ts_param(2, (0, _common.Optional)()),
|
|
202
199
|
_ts_param(2, (0, _common.Inject)(_constants.PERMISSION_GUARD_CONFIG)),
|
|
203
|
-
_ts_param(3, (0, _common.Optional)()),
|
|
204
|
-
_ts_param(3, (0, _common.Inject)(_constants.LOGGER_INSTANCE)),
|
|
205
200
|
_ts_metadata("design:type", Function),
|
|
206
201
|
_ts_metadata("design:paramtypes", [
|
|
207
202
|
typeof _core.Reflector === "undefined" ? Object : _core.Reflector,
|
|
208
203
|
typeof _hybridcacheclass.HybridCache === "undefined" ? Object : _hybridcacheclass.HybridCache,
|
|
209
|
-
typeof _permissioninterface.PermissionGuardConfig === "undefined" ? Object : _permissioninterface.PermissionGuardConfig
|
|
210
|
-
typeof _loggerinterface.ILogger === "undefined" ? Object : _loggerinterface.ILogger
|
|
204
|
+
typeof _permissioninterface.PermissionGuardConfig === "undefined" ? Object : _permissioninterface.PermissionGuardConfig
|
|
211
205
|
])
|
|
212
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
|
-
|
|
64
|
+
messageKey: _constants.SYSTEM_MESSAGES.DUPLICATE_REQUEST
|
|
65
65
|
});
|
|
66
66
|
}
|
|
67
67
|
// Return cached response
|
|
@@ -4,7 +4,6 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
});
|
|
5
5
|
_export_star(require("./delete-empty-id-from-body.interceptor"), exports);
|
|
6
6
|
_export_star(require("./idempotency.interceptor"), exports);
|
|
7
|
-
_export_star(require("./query-performance.interceptor"), exports);
|
|
8
7
|
_export_star(require("./response-meta.interceptor"), exports);
|
|
9
8
|
_export_star(require("./set-user-field-on-body.interceptor"), exports);
|
|
10
9
|
_export_star(require("./slug.interceptor"), exports);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "EVENT_MANAGER_ADAPTER", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return EVENT_MANAGER_ADAPTER;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const EVENT_MANAGER_ADAPTER = 'EVENT_MANAGER_ADAPTER';
|
package/cjs/interfaces/index.js
CHANGED
|
@@ -4,10 +4,11 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
});
|
|
5
5
|
_export_star(require("./api.interface"), exports);
|
|
6
6
|
_export_star(require("./datasource.interface"), exports);
|
|
7
|
+
_export_star(require("./event-manager-adapter.interface"), exports);
|
|
7
8
|
_export_star(require("./identity.interface"), exports);
|
|
8
9
|
_export_star(require("./logged-user-info.interface"), exports);
|
|
9
|
-
_export_star(require("./logger.interface"), exports);
|
|
10
10
|
_export_star(require("./module-config.interface"), exports);
|
|
11
|
+
_export_star(require("./notification-adapter.interface"), exports);
|
|
11
12
|
_export_star(require("./permission.interface"), exports);
|
|
12
13
|
function _export_star(from, to) {
|
|
13
14
|
Object.keys(from).forEach(function(k) {
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "NOTIFICATION_ADAPTER", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return NOTIFICATION_ADAPTER;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const NOTIFICATION_ADAPTER = 'NOTIFICATION_ADAPTER';
|