@flusys/nestjs-shared 3.0.0 → 4.0.0-lts
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 +160 -80
- package/cjs/classes/api-controller.class.js +26 -8
- package/cjs/classes/api-service.class.js +100 -17
- package/cjs/classes/winston-logger-adapter.class.js +15 -20
- package/cjs/classes/winston.logger.class.js +103 -70
- package/cjs/constants/index.js +1 -0
- package/cjs/constants/message-keys.js +80 -0
- package/cjs/constants/permissions.js +65 -11
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/log-action.decorator.js +149 -0
- package/cjs/dtos/response-payload.dto.js +72 -0
- 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 +145 -0
- package/cjs/exceptions/index.js +1 -0
- package/cjs/exceptions/permission.exception.js +12 -8
- package/cjs/filters/global-exception.filter.js +167 -0
- package/cjs/filters/index.js +18 -0
- package/cjs/guards/jwt-auth.guard.js +4 -1
- package/cjs/guards/permission.guard.js +6 -13
- 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 -0
- package/cjs/interfaces/logger.interface.js +1 -4
- package/cjs/interfaces/notification-adapter.interface.js +11 -0
- package/cjs/middlewares/logger.middleware.js +83 -26
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +33 -11
- package/cjs/modules/utils/utils.service.js +4 -20
- package/cjs/utils/index.js +0 -1
- package/cjs/utils/query-helpers.util.js +8 -1
- package/classes/api-controller.class.d.ts +1 -0
- package/classes/api-service.class.d.ts +5 -10
- package/classes/winston-logger-adapter.class.d.ts +12 -11
- package/classes/winston.logger.class.d.ts +1 -0
- package/constants/index.d.ts +1 -0
- package/constants/message-keys.d.ts +81 -0
- package/constants/permissions.d.ts +72 -0
- package/decorators/index.d.ts +1 -0
- package/decorators/log-action.decorator.d.ts +8 -0
- package/dtos/response-payload.dto.d.ts +8 -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 +1 -1
- package/fesm/classes/api-controller.class.js +26 -8
- package/fesm/classes/api-service.class.js +101 -18
- package/fesm/classes/winston-logger-adapter.class.js +18 -44
- package/fesm/classes/winston.logger.class.js +100 -68
- package/fesm/constants/index.js +2 -0
- package/fesm/constants/message-keys.js +59 -0
- package/fesm/constants/permissions.js +51 -14
- package/fesm/decorators/index.js +1 -0
- package/fesm/decorators/log-action.decorator.js +139 -0
- package/fesm/dtos/response-payload.dto.js +72 -0
- 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 +109 -0
- package/fesm/exceptions/index.js +1 -0
- package/fesm/exceptions/permission.exception.js +15 -17
- package/fesm/filters/global-exception.filter.js +157 -0
- package/fesm/filters/index.js +1 -0
- package/fesm/guards/jwt-auth.guard.js +5 -2
- package/fesm/guards/permission.guard.js +8 -15
- package/fesm/index.js +2 -0
- package/fesm/interceptors/idempotency.interceptor.js +2 -2
- package/fesm/interceptors/index.js +0 -1
- package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
- package/fesm/interfaces/index.js +2 -0
- package/fesm/interfaces/logger.interface.js +1 -4
- package/fesm/interfaces/notification-adapter.interface.js +1 -0
- package/fesm/middlewares/logger.middleware.js +83 -26
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +34 -12
- package/fesm/modules/utils/utils.service.js +5 -21
- package/fesm/utils/index.js +0 -1
- package/fesm/utils/query-helpers.util.js +8 -1
- package/filters/global-exception.filter.d.ts +10 -0
- package/filters/index.d.ts +1 -0
- package/guards/permission.guard.d.ts +1 -3
- package/index.d.ts +2 -0
- package/interceptors/index.d.ts +0 -1
- package/interfaces/event-manager-adapter.interface.d.ts +43 -0
- package/interfaces/index.d.ts +2 -0
- package/interfaces/logger.interface.d.ts +5 -5
- package/interfaces/notification-adapter.interface.d.ts +22 -0
- package/modules/datasource/multi-tenant-datasource.service.d.ts +1 -2
- package/modules/utils/utils.service.d.ts +0 -1
- package/package.json +10 -3
- package/utils/index.d.ts +0 -1
- package/cjs/interceptors/query-performance.interceptor.js +0 -66
- package/cjs/utils/error-handler.util.js +0 -90
- package/fesm/interceptors/query-performance.interceptor.js +0 -56
- package/fesm/utils/error-handler.util.js +0 -82
- package/interceptors/query-performance.interceptor.d.ts +0 -8
- package/utils/error-handler.util.d.ts +0 -19
|
@@ -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,109 @@
|
|
|
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
|
+
messageParams: options.messageParams,
|
|
24
|
+
errors: options.errors
|
|
25
|
+
}, status), _define_property(this, "messageKey", void 0), _define_property(this, "messageParams", void 0), _define_property(this, "errors", void 0), _define_property(this, "metadata", void 0);
|
|
26
|
+
this.messageKey = options.messageKey || ERROR_MESSAGES.GENERIC;
|
|
27
|
+
this.messageParams = options.messageParams;
|
|
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
|
+
status: HttpStatus.CONFLICT,
|
|
79
|
+
metadata: {
|
|
80
|
+
entity,
|
|
81
|
+
field
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
export class InternalServerException extends BaseAppException {
|
|
87
|
+
constructor(message = 'Internal server error'){
|
|
88
|
+
super({
|
|
89
|
+
message,
|
|
90
|
+
messageKey: ERROR_MESSAGES.INTERNAL,
|
|
91
|
+
status: HttpStatus.INTERNAL_SERVER_ERROR
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
export class ServiceUnavailableException extends BaseAppException {
|
|
96
|
+
constructor(service){
|
|
97
|
+
super({
|
|
98
|
+
message: `${service} is temporarily unavailable`,
|
|
99
|
+
messageKey: ERROR_MESSAGES.SERVICE_UNAVAILABLE,
|
|
100
|
+
messageParams: {
|
|
101
|
+
service
|
|
102
|
+
},
|
|
103
|
+
status: HttpStatus.SERVICE_UNAVAILABLE,
|
|
104
|
+
metadata: {
|
|
105
|
+
service
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
package/fesm/exceptions/index.js
CHANGED
|
@@ -1,37 +1,35 @@
|
|
|
1
1
|
import { ForbiddenException, InternalServerErrorException } from '@nestjs/common';
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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 {
|
|
12
|
+
export class InsufficientPermissionsException extends ForbiddenException {
|
|
16
13
|
constructor(missingPermissions, operator = 'AND'){
|
|
17
|
-
const
|
|
14
|
+
const messageKey = operator === 'OR' ? ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS_OR : ERROR_MESSAGES.INSUFFICIENT_PERMISSIONS;
|
|
18
15
|
super({
|
|
19
16
|
success: false,
|
|
20
|
-
message,
|
|
21
|
-
|
|
17
|
+
message: 'Insufficient permissions',
|
|
18
|
+
messageKey,
|
|
19
|
+
messageParams: {
|
|
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
|
|
34
|
-
|
|
31
|
+
message: 'No permissions found',
|
|
32
|
+
messageKey: ERROR_MESSAGES.NO_PERMISSIONS_FOUND
|
|
35
33
|
});
|
|
36
34
|
}
|
|
37
35
|
}
|
|
@@ -0,0 +1,157 @@
|
|
|
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 { ERROR_MESSAGES } from '../constants/message-keys';
|
|
22
|
+
import { BaseAppException } from '../exceptions/base-app.exception';
|
|
23
|
+
import { getRequestId, getUserId, getTenantId, getCompanyId } from '../middlewares/logger.middleware';
|
|
24
|
+
export class GlobalExceptionFilter {
|
|
25
|
+
catch(exception, host) {
|
|
26
|
+
const ctx = host.switchToHttp();
|
|
27
|
+
const request = ctx.getRequest();
|
|
28
|
+
const response = ctx.getResponse();
|
|
29
|
+
const startTime = request._startTime || Date.now();
|
|
30
|
+
const responseTime = Date.now() - startTime;
|
|
31
|
+
const requestId = getRequestId() || request.headers['x-request-id'] || 'no-request-id';
|
|
32
|
+
const userId = getUserId();
|
|
33
|
+
const tenantId = getTenantId();
|
|
34
|
+
const companyId = getCompanyId();
|
|
35
|
+
const errorResponse = this.buildErrorResponse(exception, requestId, responseTime);
|
|
36
|
+
const statusCode = this.getStatusCode(exception);
|
|
37
|
+
const logContext = {
|
|
38
|
+
requestId,
|
|
39
|
+
userId,
|
|
40
|
+
tenantId,
|
|
41
|
+
companyId,
|
|
42
|
+
method: request.method,
|
|
43
|
+
url: request.originalUrl,
|
|
44
|
+
path: request.path,
|
|
45
|
+
statusCode,
|
|
46
|
+
duration: `${responseTime}ms`,
|
|
47
|
+
messageKey: errorResponse.messageKey
|
|
48
|
+
};
|
|
49
|
+
const logMessage = this.buildLogMessage(request, statusCode, responseTime, errorResponse);
|
|
50
|
+
if (statusCode >= 500) {
|
|
51
|
+
this.logger.error(logMessage, this.getStack(exception), logContext);
|
|
52
|
+
} else if (statusCode >= 400) {
|
|
53
|
+
this.logger.warn(logMessage, logContext);
|
|
54
|
+
}
|
|
55
|
+
response.status(statusCode).json(errorResponse);
|
|
56
|
+
}
|
|
57
|
+
buildErrorResponse(exception, requestId, responseTime) {
|
|
58
|
+
const timestamp = new Date().toISOString();
|
|
59
|
+
if (exception instanceof BaseAppException) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
message: exception.message,
|
|
63
|
+
messageKey: exception.messageKey,
|
|
64
|
+
messageParams: exception.messageParams,
|
|
65
|
+
errors: exception.errors,
|
|
66
|
+
_meta: {
|
|
67
|
+
requestId,
|
|
68
|
+
timestamp,
|
|
69
|
+
responseTime
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
if (exception instanceof HttpException) {
|
|
74
|
+
const response = exception.getResponse();
|
|
75
|
+
const { message, messageKey, messageParams, errors } = this.extractHttpExceptionDetails(response);
|
|
76
|
+
return {
|
|
77
|
+
success: false,
|
|
78
|
+
message,
|
|
79
|
+
messageKey,
|
|
80
|
+
messageParams,
|
|
81
|
+
errors,
|
|
82
|
+
_meta: {
|
|
83
|
+
requestId,
|
|
84
|
+
timestamp,
|
|
85
|
+
responseTime
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
return {
|
|
90
|
+
success: false,
|
|
91
|
+
message: 'Internal server error',
|
|
92
|
+
messageKey: ERROR_MESSAGES.INTERNAL,
|
|
93
|
+
_meta: {
|
|
94
|
+
requestId,
|
|
95
|
+
timestamp,
|
|
96
|
+
responseTime
|
|
97
|
+
}
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
extractHttpExceptionDetails(response) {
|
|
101
|
+
if (typeof response === 'string') {
|
|
102
|
+
return {
|
|
103
|
+
message: response,
|
|
104
|
+
messageKey: ERROR_MESSAGES.HTTP
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (typeof response === 'object' && response !== null) {
|
|
108
|
+
const obj = response;
|
|
109
|
+
// Handle class-validator validation errors
|
|
110
|
+
if (Array.isArray(obj.message)) {
|
|
111
|
+
const errors = obj.message.map((msg)=>{
|
|
112
|
+
const [field, ...rest] = msg.split(' ');
|
|
113
|
+
return {
|
|
114
|
+
field: field || 'unknown',
|
|
115
|
+
message: rest.join(' ') || msg
|
|
116
|
+
};
|
|
117
|
+
});
|
|
118
|
+
return {
|
|
119
|
+
message: 'Validation failed',
|
|
120
|
+
messageKey: ERROR_MESSAGES.VALIDATION,
|
|
121
|
+
errors
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
return {
|
|
125
|
+
message: String(obj.message || 'Unknown error'),
|
|
126
|
+
messageKey: String(obj.messageKey || ERROR_MESSAGES.HTTP),
|
|
127
|
+
messageParams: obj.messageParams,
|
|
128
|
+
errors: obj.errors
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return {
|
|
132
|
+
message: 'Unknown error',
|
|
133
|
+
messageKey: ERROR_MESSAGES.UNKNOWN
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
getStatusCode(exception) {
|
|
137
|
+
if (exception instanceof HttpException) {
|
|
138
|
+
return exception.getStatus();
|
|
139
|
+
}
|
|
140
|
+
return HttpStatus.INTERNAL_SERVER_ERROR;
|
|
141
|
+
}
|
|
142
|
+
getStack(exception) {
|
|
143
|
+
if (exception instanceof Error) {
|
|
144
|
+
return exception.stack;
|
|
145
|
+
}
|
|
146
|
+
return undefined;
|
|
147
|
+
}
|
|
148
|
+
buildLogMessage(request, statusCode, responseTime, errorResponse) {
|
|
149
|
+
return `Exception caught "HTTP/1.1" "${request.method}" "${request.originalUrl}" - ${statusCode} "${errorResponse.messageKey}" ${responseTime}ms | ${errorResponse.message}`;
|
|
150
|
+
}
|
|
151
|
+
constructor(){
|
|
152
|
+
_define_property(this, "logger", new Logger('ExceptionFilter'));
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
GlobalExceptionFilter = _ts_decorate([
|
|
156
|
+
Catch()
|
|
157
|
+
], 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(
|
|
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
|
}
|
|
@@ -25,13 +25,11 @@ function _ts_param(paramIndex, decorator) {
|
|
|
25
25
|
decorator(target, key, paramIndex);
|
|
26
26
|
};
|
|
27
27
|
}
|
|
28
|
-
import { Inject, Injectable,
|
|
28
|
+
import { Inject, Injectable, Optional, UnauthorizedException } from '@nestjs/common';
|
|
29
29
|
import { Reflector } from '@nestjs/core';
|
|
30
30
|
import { HybridCache } from '../classes/hybrid-cache.class';
|
|
31
|
-
import {
|
|
32
|
-
import { CACHE_INSTANCE, IS_PUBLIC_KEY, LOGGER_INSTANCE, PERMISSION_GUARD_CONFIG, PERMISSIONS_CACHE_PREFIX, PERMISSIONS_KEY } from '../constants';
|
|
31
|
+
import { AUTH_MESSAGES, CACHE_INSTANCE, IS_PUBLIC_KEY, PERMISSION_GUARD_CONFIG, PERMISSIONS_CACHE_PREFIX, PERMISSIONS_KEY } from '../constants';
|
|
33
32
|
import { InsufficientPermissionsException, NoPermissionsFoundException, PermissionSystemUnavailableException } from '../exceptions/permission.exception';
|
|
34
|
-
import { ILogger } from '../interfaces/logger.interface';
|
|
35
33
|
import { PermissionGuardConfig } from '../interfaces/permission.interface';
|
|
36
34
|
export class PermissionGuard {
|
|
37
35
|
async canActivate(context) {
|
|
@@ -47,21 +45,21 @@ export class PermissionGuard {
|
|
|
47
45
|
if (!permissionConfig) return true;
|
|
48
46
|
const request = context.switchToHttp().getRequest();
|
|
49
47
|
const user = request.user;
|
|
50
|
-
if (!user) throw new UnauthorizedException(
|
|
48
|
+
if (!user) throw new UnauthorizedException({
|
|
49
|
+
message: 'Authentication required',
|
|
50
|
+
messageKey: AUTH_MESSAGES.TOKEN_REQUIRED
|
|
51
|
+
});
|
|
51
52
|
if (!this.cache) {
|
|
52
|
-
this.logger.error(`Cache not available (userId: ${user.id})`, undefined, 'PermissionGuard');
|
|
53
53
|
throw new PermissionSystemUnavailableException();
|
|
54
54
|
}
|
|
55
55
|
const userPermissions = await this.getUserPermissions(user);
|
|
56
56
|
if (!userPermissions || userPermissions.length === 0) {
|
|
57
|
-
this.logger.warn(`No permissions found (userId: ${user.id})`, 'PermissionGuard');
|
|
58
57
|
throw new NoPermissionsFoundException();
|
|
59
58
|
}
|
|
60
59
|
const logicNode = this.normalizeToLogicNode(permissionConfig);
|
|
61
60
|
if (!logicNode) return true;
|
|
62
61
|
const result = this.evaluateLogicNode(logicNode, userPermissions);
|
|
63
62
|
if (!result.passed) {
|
|
64
|
-
this.logger.warn(`Permission denied (userId: ${user.id})`, 'PermissionGuard');
|
|
65
63
|
throw new InsufficientPermissionsException(result.missingPermissions, result.operator);
|
|
66
64
|
}
|
|
67
65
|
return true;
|
|
@@ -168,11 +166,10 @@ export class PermissionGuard {
|
|
|
168
166
|
}
|
|
169
167
|
return false;
|
|
170
168
|
}
|
|
171
|
-
constructor(reflector, cache, config
|
|
169
|
+
constructor(reflector, cache, config){
|
|
172
170
|
_define_property(this, "reflector", void 0);
|
|
173
171
|
_define_property(this, "cache", void 0);
|
|
174
172
|
_define_property(this, "config", void 0);
|
|
175
|
-
_define_property(this, "logger", void 0);
|
|
176
173
|
this.reflector = reflector;
|
|
177
174
|
this.cache = cache;
|
|
178
175
|
this.config = {
|
|
@@ -181,7 +178,6 @@ export class PermissionGuard {
|
|
|
181
178
|
companyPermissionKeyFormat: `${PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`,
|
|
182
179
|
...config
|
|
183
180
|
};
|
|
184
|
-
this.logger = logger || new NestLoggerAdapter(new Logger(PermissionGuard.name));
|
|
185
181
|
}
|
|
186
182
|
}
|
|
187
183
|
PermissionGuard = _ts_decorate([
|
|
@@ -191,13 +187,10 @@ PermissionGuard = _ts_decorate([
|
|
|
191
187
|
_ts_param(1, Inject(CACHE_INSTANCE)),
|
|
192
188
|
_ts_param(2, Optional()),
|
|
193
189
|
_ts_param(2, Inject(PERMISSION_GUARD_CONFIG)),
|
|
194
|
-
_ts_param(3, Optional()),
|
|
195
|
-
_ts_param(3, Inject(LOGGER_INSTANCE)),
|
|
196
190
|
_ts_metadata("design:type", Function),
|
|
197
191
|
_ts_metadata("design:paramtypes", [
|
|
198
192
|
typeof Reflector === "undefined" ? Object : Reflector,
|
|
199
193
|
typeof HybridCache === "undefined" ? Object : HybridCache,
|
|
200
|
-
typeof PermissionGuardConfig === "undefined" ? Object : PermissionGuardConfig
|
|
201
|
-
typeof ILogger === "undefined" ? Object : ILogger
|
|
194
|
+
typeof PermissionGuardConfig === "undefined" ? Object : PermissionGuardConfig
|
|
202
195
|
])
|
|
203
196
|
], PermissionGuard);
|
package/fesm/index.js
CHANGED
|
@@ -3,7 +3,9 @@ export * from './constants';
|
|
|
3
3
|
export * from './decorators';
|
|
4
4
|
export * from './dtos';
|
|
5
5
|
export * from './entities';
|
|
6
|
+
export * from './enums';
|
|
6
7
|
export * from './exceptions';
|
|
8
|
+
export * from './filters';
|
|
7
9
|
export * from './guards';
|
|
8
10
|
export * from './interceptors';
|
|
9
11
|
export * from './interfaces';
|
|
@@ -29,7 +29,7 @@ import { ConflictException, Inject, Injectable, Optional } from '@nestjs/common'
|
|
|
29
29
|
import { of } from 'rxjs';
|
|
30
30
|
import { tap } from 'rxjs/operators';
|
|
31
31
|
import { HybridCache } from '../classes/hybrid-cache.class';
|
|
32
|
-
import { CACHE_INSTANCE, IDEMPOTENCY_CACHE_PREFIX, IDEMPOTENCY_KEY_HEADER } from '../constants';
|
|
32
|
+
import { CACHE_INSTANCE, IDEMPOTENCY_CACHE_PREFIX, IDEMPOTENCY_KEY_HEADER, SYSTEM_MESSAGES } from '../constants';
|
|
33
33
|
export class IdempotencyInterceptor {
|
|
34
34
|
async intercept(context, next) {
|
|
35
35
|
// Skip if no cache available
|
|
@@ -51,7 +51,7 @@ export class IdempotencyInterceptor {
|
|
|
51
51
|
throw new ConflictException({
|
|
52
52
|
success: false,
|
|
53
53
|
message: 'Request is already being processed',
|
|
54
|
-
|
|
54
|
+
messageKey: SYSTEM_MESSAGES.DUPLICATE_REQUEST
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
57
|
// Return cached response
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export * from './delete-empty-id-from-body.interceptor';
|
|
2
2
|
export * from './idempotency.interceptor';
|
|
3
|
-
export * from './query-performance.interceptor';
|
|
4
3
|
export * from './response-meta.interceptor';
|
|
5
4
|
export * from './set-user-field-on-body.interceptor';
|
|
6
5
|
export * from './slug.interceptor';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const EVENT_MANAGER_ADAPTER = 'EVENT_MANAGER_ADAPTER';
|
package/fesm/interfaces/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
export * from './api.interface';
|
|
2
2
|
export * from './datasource.interface';
|
|
3
|
+
export * from './event-manager-adapter.interface';
|
|
3
4
|
export * from './identity.interface';
|
|
4
5
|
export * from './logged-user-info.interface';
|
|
5
6
|
export * from './logger.interface';
|
|
6
7
|
export * from './module-config.interface';
|
|
8
|
+
export * from './notification-adapter.interface';
|
|
7
9
|
export * from './permission.interface';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const NOTIFICATION_ADAPTER = 'NOTIFICATION_ADAPTER';
|
|
@@ -24,18 +24,15 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
24
24
|
import { instance as winstonLogger } from '../classes/winston.logger.class';
|
|
25
25
|
import { REQUEST_ID_HEADER } from '../constants';
|
|
26
26
|
// Configuration
|
|
27
|
-
const
|
|
27
|
+
const logConfig = envConfig.getLogConfig();
|
|
28
|
+
const IS_DEBUG = logConfig.level === 'debug';
|
|
29
|
+
const DISABLE_HTTP_LOGGING = logConfig.disableHttpLogging;
|
|
28
30
|
const TENANT_ID_HEADER = 'x-tenant-id';
|
|
29
31
|
const EXCLUDED_PATHS = [
|
|
30
32
|
'/health',
|
|
31
33
|
'/metrics',
|
|
32
34
|
'/favicon.ico'
|
|
33
35
|
];
|
|
34
|
-
const EXCLUDED_HEADERS = [
|
|
35
|
-
'authorization',
|
|
36
|
-
'cookie',
|
|
37
|
-
'x-api-key'
|
|
38
|
-
];
|
|
39
36
|
const MAX_BODY_LOG_SIZE = 1000;
|
|
40
37
|
export const requestContext = new AsyncLocalStorage();
|
|
41
38
|
// Context accessors
|
|
@@ -44,17 +41,71 @@ export const getTenantId = ()=>requestContext.getStore()?.tenantId;
|
|
|
44
41
|
export const getUserId = ()=>requestContext.getStore()?.userId;
|
|
45
42
|
export const getCompanyId = ()=>requestContext.getStore()?.companyId;
|
|
46
43
|
// Helper Functions
|
|
47
|
-
function
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
44
|
+
function summarizeRequestBody(body) {
|
|
45
|
+
if (!body) return undefined;
|
|
46
|
+
const parsed = typeof body === 'string' ? tryParseJson(body) : body;
|
|
47
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
48
|
+
const str = String(body);
|
|
49
|
+
return str.length > MAX_BODY_LOG_SIZE ? str.substring(0, MAX_BODY_LOG_SIZE) + '...' : str;
|
|
50
|
+
}
|
|
51
|
+
// For API request bodies, provide a concise summary
|
|
52
|
+
const summary = {};
|
|
53
|
+
// Pagination info
|
|
54
|
+
if (parsed.pagination) {
|
|
55
|
+
summary.page = parsed.pagination.currentPage;
|
|
56
|
+
summary.pageSize = parsed.pagination.pageSize;
|
|
57
|
+
}
|
|
58
|
+
// Filter keys only (not values - may contain sensitive data)
|
|
59
|
+
if (parsed.filter && typeof parsed.filter === 'object') {
|
|
60
|
+
const filterKeys = Object.keys(parsed.filter);
|
|
61
|
+
if (filterKeys.length > 0) summary.filterKeys = filterKeys;
|
|
62
|
+
}
|
|
63
|
+
// Select fields count
|
|
64
|
+
if (Array.isArray(parsed.select) && parsed.select.length > 0) {
|
|
65
|
+
summary.selectCount = parsed.select.length;
|
|
66
|
+
}
|
|
67
|
+
// Sort keys
|
|
68
|
+
if (parsed.sort && typeof parsed.sort === 'object') {
|
|
69
|
+
const sortKeys = Object.keys(parsed.sort);
|
|
70
|
+
if (sortKeys.length > 0) summary.sortKeys = sortKeys;
|
|
51
71
|
}
|
|
52
|
-
|
|
72
|
+
// ID for single item operations
|
|
73
|
+
if (parsed.id) summary.id = parsed.id;
|
|
74
|
+
// IDs for bulk operations
|
|
75
|
+
if (Array.isArray(parsed.ids)) summary.idsCount = parsed.ids.length;
|
|
76
|
+
return Object.keys(summary).length > 0 ? JSON.stringify(summary) : '{}';
|
|
53
77
|
}
|
|
54
|
-
function
|
|
78
|
+
function summarizeResponseBody(body) {
|
|
55
79
|
if (!body) return undefined;
|
|
56
|
-
const
|
|
57
|
-
|
|
80
|
+
const parsed = typeof body === 'string' ? tryParseJson(body) : body;
|
|
81
|
+
if (!parsed || typeof parsed !== 'object') {
|
|
82
|
+
const str = String(body);
|
|
83
|
+
return str.length > MAX_BODY_LOG_SIZE ? str.substring(0, MAX_BODY_LOG_SIZE) + '...' : str;
|
|
84
|
+
}
|
|
85
|
+
const summary = {};
|
|
86
|
+
if ('success' in parsed) summary.success = parsed.success;
|
|
87
|
+
if ('message' in parsed) summary.message = parsed.message;
|
|
88
|
+
if ('code' in parsed) summary.code = parsed.code;
|
|
89
|
+
if (Array.isArray(parsed.data)) {
|
|
90
|
+
summary.dataCount = parsed.data.length;
|
|
91
|
+
} else if (parsed.data && typeof parsed.data === 'object') {
|
|
92
|
+
summary.hasData = true;
|
|
93
|
+
}
|
|
94
|
+
if (parsed.meta) {
|
|
95
|
+
summary.total = parsed.meta.total;
|
|
96
|
+
summary.page = parsed.meta.page;
|
|
97
|
+
}
|
|
98
|
+
if (parsed.errors) {
|
|
99
|
+
summary.errorsCount = Array.isArray(parsed.errors) ? parsed.errors.length : 1;
|
|
100
|
+
}
|
|
101
|
+
return JSON.stringify(summary);
|
|
102
|
+
}
|
|
103
|
+
function tryParseJson(str) {
|
|
104
|
+
try {
|
|
105
|
+
return JSON.parse(str);
|
|
106
|
+
} catch {
|
|
107
|
+
return null;
|
|
108
|
+
}
|
|
58
109
|
}
|
|
59
110
|
function getClientIp(req) {
|
|
60
111
|
return req.headers['x-forwarded-for']?.split(',')[0] || req.socket?.remoteAddress || 'unknown';
|
|
@@ -71,7 +122,7 @@ export class LoggerMiddleware {
|
|
|
71
122
|
startTime
|
|
72
123
|
};
|
|
73
124
|
requestContext.run(context, ()=>{
|
|
74
|
-
const shouldSkipLogging = EXCLUDED_PATHS.some((path)=>req.originalUrl.startsWith(path));
|
|
125
|
+
const shouldSkipLogging = DISABLE_HTTP_LOGGING || EXCLUDED_PATHS.some((path)=>req.originalUrl.startsWith(path));
|
|
75
126
|
if (!shouldSkipLogging) {
|
|
76
127
|
this.logRequest(req, requestId, tenantId);
|
|
77
128
|
this.setupResponseLogging(req, res, startTime, requestId, tenantId);
|
|
@@ -112,20 +163,22 @@ export class LoggerMiddleware {
|
|
|
112
163
|
});
|
|
113
164
|
}
|
|
114
165
|
logRequest(req, requestId, tenantId) {
|
|
166
|
+
const ip = getClientIp(req);
|
|
167
|
+
const contentType = req.headers['content-type'] || 'none';
|
|
168
|
+
// Build descriptive message like: Started processing request "POST" "/api/users" from "192.168.1.1"
|
|
169
|
+
const message = `Started processing request "${req.method}" "${req.originalUrl}" from "${ip}"`;
|
|
115
170
|
const logData = {
|
|
116
171
|
...this.buildBaseLogData(req, requestId, tenantId),
|
|
117
172
|
query: Object.keys(req.query).length > 0 ? req.query : undefined,
|
|
118
|
-
ip
|
|
173
|
+
ip,
|
|
119
174
|
userAgent: req.headers['user-agent'],
|
|
120
|
-
contentType
|
|
175
|
+
contentType,
|
|
121
176
|
contentLength: req.headers['content-length']
|
|
122
177
|
};
|
|
123
178
|
if (IS_DEBUG) {
|
|
124
|
-
logData.
|
|
125
|
-
logData.body = truncateBody(req.body);
|
|
126
|
-
logData.params = Object.keys(req.params).length > 0 ? req.params : undefined;
|
|
179
|
+
logData.body = summarizeRequestBody(req.body);
|
|
127
180
|
}
|
|
128
|
-
this.logger.info(
|
|
181
|
+
this.logger.info(message, logData);
|
|
129
182
|
}
|
|
130
183
|
logResponse(req, res, startTime, body, requestId, tenantId) {
|
|
131
184
|
const duration = Date.now() - startTime;
|
|
@@ -133,6 +186,10 @@ export class LoggerMiddleware {
|
|
|
133
186
|
const level = statusCode >= 500 ? 'error' : statusCode >= 400 ? 'warn' : 'info';
|
|
134
187
|
const userId = getUserId();
|
|
135
188
|
const companyId = getCompanyId();
|
|
189
|
+
const contentType = res.getHeader('content-type') || 'none';
|
|
190
|
+
const contentLength = res.getHeader('content-length') || 'null';
|
|
191
|
+
// Build descriptive message like: Request finished "HTTP/1.1" "POST" "/api/users" - 200 "application/json" 534.29ms
|
|
192
|
+
const message = `Request finished "HTTP/1.1" "${req.method}" "${req.originalUrl}" - ${statusCode} ${contentLength} "${contentType}" ${duration.toFixed(2)}ms`;
|
|
136
193
|
const logData = {
|
|
137
194
|
...this.buildBaseLogData(req, requestId, tenantId),
|
|
138
195
|
userId,
|
|
@@ -141,15 +198,15 @@ export class LoggerMiddleware {
|
|
|
141
198
|
statusMessage: res.statusMessage,
|
|
142
199
|
duration: `${duration}ms`,
|
|
143
200
|
durationMs: duration,
|
|
144
|
-
contentType
|
|
145
|
-
contentLength
|
|
201
|
+
contentType,
|
|
202
|
+
contentLength
|
|
146
203
|
};
|
|
147
204
|
if (statusCode >= 400 || IS_DEBUG) {
|
|
148
|
-
logData.responseBody =
|
|
205
|
+
logData.responseBody = summarizeResponseBody(body);
|
|
149
206
|
}
|
|
150
|
-
this.logger.log(level,
|
|
207
|
+
this.logger.log(level, message, logData);
|
|
151
208
|
if (duration > 3000 && statusCode < 400) {
|
|
152
|
-
this.logger.warn(
|
|
209
|
+
this.logger.warn(`Slow request detected: "${req.method}" "${req.originalUrl}" took ${duration}ms (threshold: 3000ms)`, {
|
|
153
210
|
...this.buildBaseLogData(req, requestId, tenantId),
|
|
154
211
|
userId,
|
|
155
212
|
companyId,
|