@flusys/nestjs-shared 3.0.1 → 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.
Files changed (82) hide show
  1. package/README.md +159 -79
  2. package/cjs/classes/api-controller.class.js +26 -8
  3. package/cjs/classes/api-service.class.js +100 -17
  4. package/cjs/classes/winston-logger-adapter.class.js +15 -20
  5. package/cjs/classes/winston.logger.class.js +103 -70
  6. package/cjs/constants/index.js +1 -0
  7. package/cjs/constants/message-keys.js +80 -0
  8. package/cjs/constants/permissions.js +32 -1
  9. package/cjs/decorators/index.js +1 -0
  10. package/cjs/decorators/log-action.decorator.js +149 -0
  11. package/cjs/dtos/response-payload.dto.js +72 -0
  12. package/cjs/exceptions/base-app.exception.js +145 -0
  13. package/cjs/exceptions/index.js +1 -0
  14. package/cjs/exceptions/permission.exception.js +12 -8
  15. package/cjs/filters/global-exception.filter.js +167 -0
  16. package/cjs/filters/index.js +18 -0
  17. package/cjs/guards/jwt-auth.guard.js +4 -1
  18. package/cjs/guards/permission.guard.js +6 -13
  19. package/cjs/index.js +1 -0
  20. package/cjs/interceptors/idempotency.interceptor.js +1 -1
  21. package/cjs/interceptors/index.js +0 -1
  22. package/cjs/interfaces/logger.interface.js +1 -4
  23. package/cjs/middlewares/logger.middleware.js +83 -26
  24. package/cjs/modules/datasource/multi-tenant-datasource.service.js +33 -11
  25. package/cjs/modules/utils/utils.service.js +4 -20
  26. package/cjs/utils/index.js +0 -1
  27. package/cjs/utils/query-helpers.util.js +8 -1
  28. package/classes/api-controller.class.d.ts +1 -0
  29. package/classes/api-service.class.d.ts +5 -10
  30. package/classes/winston-logger-adapter.class.d.ts +12 -11
  31. package/classes/winston.logger.class.d.ts +1 -0
  32. package/constants/index.d.ts +1 -0
  33. package/constants/message-keys.d.ts +81 -0
  34. package/constants/permissions.d.ts +36 -0
  35. package/decorators/index.d.ts +1 -0
  36. package/decorators/log-action.decorator.d.ts +8 -0
  37. package/dtos/response-payload.dto.d.ts +8 -0
  38. package/exceptions/base-app.exception.d.ts +41 -0
  39. package/exceptions/index.d.ts +1 -0
  40. package/exceptions/permission.exception.d.ts +1 -1
  41. package/fesm/classes/api-controller.class.js +26 -8
  42. package/fesm/classes/api-service.class.js +101 -18
  43. package/fesm/classes/winston-logger-adapter.class.js +18 -44
  44. package/fesm/classes/winston.logger.class.js +100 -68
  45. package/fesm/constants/index.js +2 -0
  46. package/fesm/constants/message-keys.js +59 -0
  47. package/fesm/constants/permissions.js +24 -1
  48. package/fesm/decorators/index.js +1 -0
  49. package/fesm/decorators/log-action.decorator.js +139 -0
  50. package/fesm/dtos/response-payload.dto.js +72 -0
  51. package/fesm/exceptions/base-app.exception.js +109 -0
  52. package/fesm/exceptions/index.js +1 -0
  53. package/fesm/exceptions/permission.exception.js +15 -17
  54. package/fesm/filters/global-exception.filter.js +157 -0
  55. package/fesm/filters/index.js +1 -0
  56. package/fesm/guards/jwt-auth.guard.js +5 -2
  57. package/fesm/guards/permission.guard.js +8 -15
  58. package/fesm/index.js +1 -0
  59. package/fesm/interceptors/idempotency.interceptor.js +2 -2
  60. package/fesm/interceptors/index.js +0 -1
  61. package/fesm/interfaces/logger.interface.js +1 -4
  62. package/fesm/middlewares/logger.middleware.js +83 -26
  63. package/fesm/modules/datasource/multi-tenant-datasource.service.js +34 -12
  64. package/fesm/modules/utils/utils.service.js +5 -21
  65. package/fesm/utils/index.js +0 -1
  66. package/fesm/utils/query-helpers.util.js +8 -1
  67. package/filters/global-exception.filter.d.ts +10 -0
  68. package/filters/index.d.ts +1 -0
  69. package/guards/permission.guard.d.ts +1 -3
  70. package/index.d.ts +1 -0
  71. package/interceptors/index.d.ts +0 -1
  72. package/interfaces/logger.interface.d.ts +5 -5
  73. package/modules/datasource/multi-tenant-datasource.service.d.ts +1 -2
  74. package/modules/utils/utils.service.d.ts +0 -1
  75. package/package.json +5 -3
  76. package/utils/index.d.ts +0 -1
  77. package/cjs/interceptors/query-performance.interceptor.js +0 -66
  78. package/cjs/utils/error-handler.util.js +0 -90
  79. package/fesm/interceptors/query-performance.interceptor.js +0 -56
  80. package/fesm/utils/error-handler.util.js +0 -82
  81. package/interceptors/query-performance.interceptor.d.ts +0 -8
  82. package/utils/error-handler.util.d.ts +0 -19
@@ -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('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
  }
@@ -25,13 +25,11 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { Inject, Injectable, Logger, Optional, UnauthorizedException } from '@nestjs/common';
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 { NestLoggerAdapter } from '../classes/winston-logger-adapter.class';
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('Authentication required');
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, logger){
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
@@ -5,6 +5,7 @@ export * from './dtos';
5
5
  export * from './entities';
6
6
  export * from './enums';
7
7
  export * from './exceptions';
8
+ export * from './filters';
8
9
  export * from './guards';
9
10
  export * from './interceptors';
10
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
- code: 'DUPLICATE_REQUEST_IN_PROGRESS'
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';
@@ -1,4 +1 @@
1
- /**
2
- * Logger interface for dependency injection
3
- * Compatible with Winston, NestJS Logger, and console
4
- */ export { };
1
+ export { };
@@ -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 IS_DEBUG = envConfig.getLogConfig().level === 'debug';
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 sanitizeHeaders(headers) {
48
- const sanitized = {};
49
- for (const [key, value] of Object.entries(headers)){
50
- sanitized[key] = EXCLUDED_HEADERS.includes(key.toLowerCase()) ? '[REDACTED]' : value;
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
- return sanitized;
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 truncateBody(body) {
78
+ function summarizeResponseBody(body) {
55
79
  if (!body) return undefined;
56
- const str = typeof body === 'string' ? body : JSON.stringify(body);
57
- return str.length > MAX_BODY_LOG_SIZE ? str.substring(0, MAX_BODY_LOG_SIZE) + '...' : body;
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: getClientIp(req),
173
+ ip,
119
174
  userAgent: req.headers['user-agent'],
120
- contentType: req.headers['content-type'],
175
+ contentType,
121
176
  contentLength: req.headers['content-length']
122
177
  };
123
178
  if (IS_DEBUG) {
124
- logData.headers = sanitizeHeaders(req.headers);
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('Incoming request', logData);
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: res.getHeader('content-type'),
145
- contentLength: res.getHeader('content-length')
201
+ contentType,
202
+ contentLength
146
203
  };
147
204
  if (statusCode >= 400 || IS_DEBUG) {
148
- logData.responseBody = truncateBody(body);
205
+ logData.responseBody = summarizeResponseBody(body);
149
206
  }
150
- this.logger.log(level, `Response [${statusCode}]`, logData);
207
+ this.logger.log(level, message, logData);
151
208
  if (duration > 3000 && statusCode < 400) {
152
- this.logger.warn('Slow request detected', {
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,
@@ -26,7 +26,8 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { DEFAULT_TENANT_HEADER, IDataSourceServiceOptions, MODULE_OPTIONS } from '@flusys/nestjs-core';
29
- import { BadRequestException, Inject, Injectable, Logger, Optional, Scope } from '@nestjs/common';
29
+ import { BadRequestException, Inject, Injectable, InternalServerErrorException, Optional, Scope } from '@nestjs/common';
30
+ import { SYSTEM_MESSAGES } from '../../constants';
30
31
  import { REQUEST } from '@nestjs/core';
31
32
  import { Request } from 'express';
32
33
  import { DataSource } from 'typeorm';
@@ -58,7 +59,10 @@ export class MultiTenantDataSourceService {
58
59
  const tenantId = this.request.headers[this.tenantHeader];
59
60
  if (!tenantId) return null;
60
61
  if (!/^[a-zA-Z0-9_-]+$/.test(tenantId)) {
61
- throw new BadRequestException('Invalid tenant ID format');
62
+ throw new BadRequestException({
63
+ message: 'Invalid tenant ID format',
64
+ messageKey: SYSTEM_MESSAGES.INVALID_TENANT_ID
65
+ });
62
66
  }
63
67
  return tenantId;
64
68
  }
@@ -80,7 +84,15 @@ export class MultiTenantDataSourceService {
80
84
  }
81
85
  async getDataSourceForTenant(tenantId) {
82
86
  const tenant = this.getTenant(tenantId);
83
- if (!tenant) throw new Error(`Tenant '${tenantId}' not found`);
87
+ if (!tenant) {
88
+ throw new BadRequestException({
89
+ message: `Tenant '${tenantId}' not found`,
90
+ messageKey: SYSTEM_MESSAGES.TENANT_NOT_FOUND,
91
+ messageParams: {
92
+ tenantId
93
+ }
94
+ });
95
+ }
84
96
  return this.getOrCreateTenantConnection(tenant);
85
97
  }
86
98
  setDataSource(dataSource) {
@@ -100,8 +112,8 @@ export class MultiTenantDataSourceService {
100
112
  try {
101
113
  const dataSource = await this.getOrCreateTenantConnection(tenant);
102
114
  results.set(tenant.id, await callback(tenant, dataSource));
103
- } catch (error) {
104
- this.logger.error(`Error processing tenant ${tenant.id}: ${error}`);
115
+ } catch {
116
+ // Silent failure for individual tenant
105
117
  }
106
118
  }
107
119
  return results;
@@ -118,7 +130,6 @@ export class MultiTenantDataSourceService {
118
130
  if (connection?.isInitialized) {
119
131
  await connection.destroy();
120
132
  MultiTenantDataSourceService.tenantConnections.delete(tenantId);
121
- this.logger.log(`Closed connection for tenant: ${tenantId}`);
122
133
  }
123
134
  }
124
135
  async onModuleDestroy() {
@@ -148,7 +159,10 @@ export class MultiTenantDataSourceService {
148
159
  }
149
160
  const config = this.getDefaultDatabaseConfig();
150
161
  if (!config) {
151
- throw new Error('No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.');
162
+ throw new InternalServerErrorException({
163
+ message: 'No database config available. Provide defaultDatabaseConfig or tenantDefaultDatabaseConfig.',
164
+ messageKey: SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
165
+ });
152
166
  }
153
167
  // Create connection with lock to prevent race conditions
154
168
  const connectionPromise = this.createDataSourceFromConfig(config);
@@ -166,7 +180,13 @@ export class MultiTenantDataSourceService {
166
180
  */ async getTenantDataSource() {
167
181
  const tenant = this.getCurrentTenant();
168
182
  if (!tenant) {
169
- throw new Error(`Tenant not found. Ensure '${this.tenantHeader}' header is set.`);
183
+ throw new BadRequestException({
184
+ message: `Tenant not found. Ensure '${this.tenantHeader}' header is set.`,
185
+ messageKey: SYSTEM_MESSAGES.TENANT_HEADER_REQUIRED,
186
+ messageParams: {
187
+ header: this.tenantHeader
188
+ }
189
+ });
170
190
  }
171
191
  return this.getOrCreateTenantConnection(tenant);
172
192
  }
@@ -181,7 +201,6 @@ export class MultiTenantDataSourceService {
181
201
  try {
182
202
  const dataSource = await connectionPromise;
183
203
  MultiTenantDataSourceService.tenantConnections.set(tenant.id, dataSource);
184
- this.logger.log(`Created connection for tenant: ${tenant.id}`);
185
204
  return dataSource;
186
205
  } finally{
187
206
  MultiTenantDataSourceService.connectionLocks.delete(tenant.id);
@@ -192,7 +211,12 @@ export class MultiTenantDataSourceService {
192
211
  }
193
212
  buildTenantDatabaseConfig(tenant) {
194
213
  const defaultConfig = this.getDefaultDatabaseConfig();
195
- if (!defaultConfig) throw new Error('No default database config for multi-tenant mode.');
214
+ if (!defaultConfig) {
215
+ throw new InternalServerErrorException({
216
+ message: 'No default database config for multi-tenant mode.',
217
+ messageKey: SYSTEM_MESSAGES.DATABASE_CONFIG_NOT_AVAILABLE
218
+ });
219
+ }
196
220
  return {
197
221
  type: defaultConfig.type,
198
222
  host: tenant.host ?? defaultConfig.host,
@@ -220,11 +244,9 @@ export class MultiTenantDataSourceService {
220
244
  constructor(options, request){
221
245
  _define_property(this, "options", void 0);
222
246
  _define_property(this, "request", void 0);
223
- _define_property(this, "logger", void 0);
224
247
  _define_property(this, "tenantHeader", void 0);
225
248
  this.options = options;
226
249
  this.request = request;
227
- this.logger = new Logger(this.constructor.name);
228
250
  this.tenantHeader = DEFAULT_TENANT_HEADER;
229
251
  this.initializeFromOptions();
230
252
  }
@@ -1,23 +1,10 @@
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
1
  function _ts_decorate(decorators, target, key, desc) {
15
2
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
16
3
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
17
4
  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
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
19
6
  }
20
- import { Injectable, Logger } from '@nestjs/common';
7
+ import { Injectable } from '@nestjs/common';
21
8
  export class UtilsService {
22
9
  // ---------------- CACHE HELPERS ----------------
23
10
  /**
@@ -37,8 +24,8 @@ export class UtilsService {
37
24
  const keys = await cacheManager.get(trackingKey) || [];
38
25
  if (!keys.includes(cacheKey)) keys.push(cacheKey);
39
26
  await cacheManager.set(trackingKey, keys);
40
- } catch (error) {
41
- this.logger.error(`Cache tracking failed for ${entityName}`, error instanceof Error ? error.stack : String(error));
27
+ } catch {
28
+ // Silent failure - cache tracking is non-critical
42
29
  }
43
30
  }
44
31
  /**
@@ -49,8 +36,8 @@ export class UtilsService {
49
36
  const keys = await cacheManager.get(trackingKey) || [];
50
37
  await Promise.allSettled(keys.map((key)=>cacheManager.del(key)));
51
38
  await cacheManager.del(trackingKey);
52
- } catch (error) {
53
- this.logger.error(`Cache invalidation failed for ${entityName}`, error instanceof Error ? error.stack : String(error));
39
+ } catch {
40
+ // Silent failure - cache invalidation is non-critical
54
41
  }
55
42
  }
56
43
  // ---------------- STRING HELPERS ----------------
@@ -73,9 +60,6 @@ export class UtilsService {
73
60
  const prefix = this.buildTenantPrefix(tenantId);
74
61
  return entityId ? `${prefix}entity_${entityName}_id_${entityId}_keys` : `${prefix}entity_${entityName}_keys`;
75
62
  }
76
- constructor(){
77
- _define_property(this, "logger", new Logger(UtilsService.name));
78
- }
79
63
  }
80
64
  UtilsService = _ts_decorate([
81
65
  Injectable()
@@ -1,4 +1,3 @@
1
- export * from './error-handler.util';
2
1
  export * from './html-sanitizer.util';
3
2
  export * from './query-helpers.util';
4
3
  export * from './request.util';