@flusys/nestjs-shared 3.0.1 → 4.0.0-rc
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 +159 -79
- 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 +32 -1
- 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/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 +1 -0
- package/cjs/interceptors/idempotency.interceptor.js +1 -1
- package/cjs/interceptors/index.js +0 -1
- package/cjs/interfaces/logger.interface.js +1 -4
- 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 +36 -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/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 +24 -1
- 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/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 +1 -0
- package/fesm/interceptors/idempotency.interceptor.js +2 -2
- package/fesm/interceptors/index.js +0 -1
- package/fesm/interfaces/logger.interface.js +1 -4
- 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 +1 -0
- package/interceptors/index.d.ts +0 -1
- package/interfaces/logger.interface.d.ts +5 -5
- package/modules/datasource/multi-tenant-datasource.service.d.ts +1 -2
- package/modules/utils/utils.service.d.ts +0 -1
- package/package.json +2 -2
- 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
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", {
|
|
3
3
|
value: true
|
|
4
4
|
});
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
function _export(target, all) {
|
|
6
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
7
|
+
enumerable: true,
|
|
8
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
_export(exports, {
|
|
12
|
+
get createModuleLogger () {
|
|
13
|
+
return createModuleLogger;
|
|
14
|
+
},
|
|
15
|
+
get instance () {
|
|
8
16
|
return instance;
|
|
9
17
|
}
|
|
10
18
|
});
|
|
@@ -12,8 +20,8 @@ const _config = require("@flusys/nestjs-core/config");
|
|
|
12
20
|
const _fs = require("fs");
|
|
13
21
|
const _path = require("path");
|
|
14
22
|
const _winston = require("winston");
|
|
15
|
-
const _winstontransport = /*#__PURE__*/ _interop_require_wildcard(require("winston-transport"));
|
|
16
23
|
const _winstondailyrotatefile = /*#__PURE__*/ _interop_require_wildcard(require("winston-daily-rotate-file"));
|
|
24
|
+
const _winstontransport = /*#__PURE__*/ _interop_require_wildcard(require("winston-transport"));
|
|
17
25
|
function _define_property(obj, key, value) {
|
|
18
26
|
if (key in obj) {
|
|
19
27
|
Object.defineProperty(obj, key, {
|
|
@@ -85,33 +93,48 @@ if (!(0, _fs.existsSync)(LOG_DIR)) {
|
|
|
85
93
|
}
|
|
86
94
|
// Custom Formats
|
|
87
95
|
/**
|
|
88
|
-
* Custom format for structured logging
|
|
89
|
-
*
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
96
|
+
* Custom format for structured logging (production)
|
|
97
|
+
* Multi-line format:
|
|
98
|
+
* - Line 1: Timestamp
|
|
99
|
+
* - Line 2: Log Level
|
|
100
|
+
* - Line 3: [RequestId]
|
|
101
|
+
* - Line 4: Empty
|
|
102
|
+
* - Line 5+: Message with context
|
|
103
|
+
*/ const LEVEL_MAP = {
|
|
104
|
+
error: 'Error',
|
|
105
|
+
warn: 'Warning',
|
|
106
|
+
info: 'Information',
|
|
107
|
+
debug: 'Debug',
|
|
108
|
+
verbose: 'Verbose'
|
|
109
|
+
};
|
|
110
|
+
const LOG_SEPARATOR = '─'.repeat(80);
|
|
111
|
+
const structuredFormat = _winston.format.printf(({ timestamp, level, message, context, requestId, userId, tenantId, companyId, method, url, path, statusCode, duration, stack, ...metadata })=>{
|
|
112
|
+
const reqId = requestId || 'no-request-id';
|
|
113
|
+
const levelFormatted = LEVEL_MAP[level] || level.charAt(0).toUpperCase() + level.slice(1);
|
|
114
|
+
const contextParts = [];
|
|
115
|
+
if (context) contextParts.push(`Context: ${context}`);
|
|
116
|
+
if (method && (path || url)) contextParts.push(`Endpoint: ${method} ${path || url}`);
|
|
117
|
+
if (statusCode) contextParts.push(`Status: ${statusCode}`);
|
|
118
|
+
if (duration) contextParts.push(`Duration: ${duration}`);
|
|
119
|
+
if (userId) contextParts.push(`UserId: ${userId}`);
|
|
120
|
+
if (tenantId) contextParts.push(`TenantId: ${tenantId}`);
|
|
121
|
+
if (companyId) contextParts.push(`CompanyId: ${companyId}`);
|
|
122
|
+
let content = message;
|
|
123
|
+
if (contextParts.length > 0) {
|
|
124
|
+
content = `${contextParts.join(' | ')}\n${message}`;
|
|
125
|
+
}
|
|
126
|
+
const metaKeys = Object.keys(metadata).filter((k)=>k !== 'splat');
|
|
127
|
+
if (metaKeys.length > 0) {
|
|
128
|
+
const metaStr = JSON.stringify(metaKeys.reduce((acc, k)=>({
|
|
129
|
+
...acc,
|
|
130
|
+
[k]: metadata[k]
|
|
131
|
+
}), {}));
|
|
132
|
+
content += `\nMetadata: ${metaStr}`;
|
|
133
|
+
}
|
|
134
|
+
if (stack) {
|
|
135
|
+
content += `\nStack: ${stack}`;
|
|
136
|
+
}
|
|
137
|
+
return `${LOG_SEPARATOR}\n${timestamp} | ${levelFormatted} | [${reqId}]\n${content}`;
|
|
115
138
|
});
|
|
116
139
|
// Transports
|
|
117
140
|
/**
|
|
@@ -134,63 +157,44 @@ if (!(0, _fs.existsSync)(LOG_DIR)) {
|
|
|
134
157
|
maxFiles: LOG_MAX_FILES,
|
|
135
158
|
level: 'error'
|
|
136
159
|
});
|
|
137
|
-
//
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
if (tenantTransportCache.has(tenantId)) {
|
|
147
|
-
return tenantTransportCache.get(tenantId);
|
|
148
|
-
}
|
|
149
|
-
const tenantLogDir = (0, _path.join)(LOG_DIR, tenantId);
|
|
150
|
-
if (!(0, _fs.existsSync)(tenantLogDir)) {
|
|
151
|
-
(0, _fs.mkdirSync)(tenantLogDir, {
|
|
160
|
+
// Transport Caches
|
|
161
|
+
const moduleTransportCache = new Map();
|
|
162
|
+
const tenantTransportCache = new Map();
|
|
163
|
+
function getCachedTransport(cache, name) {
|
|
164
|
+
const cached = cache.get(name);
|
|
165
|
+
if (cached) return cached;
|
|
166
|
+
const logDir = (0, _path.join)(LOG_DIR, name);
|
|
167
|
+
if (!(0, _fs.existsSync)(logDir)) {
|
|
168
|
+
(0, _fs.mkdirSync)(logDir, {
|
|
152
169
|
recursive: true
|
|
153
170
|
});
|
|
154
171
|
}
|
|
155
172
|
const transport = new DailyRotateFile({
|
|
156
|
-
filename: `${
|
|
173
|
+
filename: `${logDir}/combined-%DATE%.log`,
|
|
157
174
|
datePattern: 'YYYY-MM-DD',
|
|
158
175
|
zippedArchive: true,
|
|
159
176
|
maxSize: LOG_MAX_SIZE,
|
|
160
177
|
maxFiles: LOG_MAX_FILES,
|
|
161
178
|
level: LOG_LEVEL
|
|
162
179
|
});
|
|
163
|
-
|
|
180
|
+
cache.set(name, transport);
|
|
164
181
|
return transport;
|
|
165
182
|
}
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
*/ let TenantAwareTransport = class TenantAwareTransport extends Transport {
|
|
183
|
+
const getModuleTransport = (name)=>getCachedTransport(moduleTransportCache, name);
|
|
184
|
+
const getTenantTransport = (name)=>getCachedTransport(tenantTransportCache, name);
|
|
185
|
+
let TenantAwareTransport = class TenantAwareTransport extends Transport {
|
|
170
186
|
log(info, callback) {
|
|
171
187
|
setImmediate(()=>this.emit('logged', info));
|
|
172
188
|
const tenantId = info.tenantId || info.metadata?.tenantId;
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const tenantTransport = getTenantTransport(tenantId);
|
|
176
|
-
tenantTransport.log(info, callback);
|
|
177
|
-
} else {
|
|
178
|
-
// Write to default log folder
|
|
179
|
-
this.defaultTransport.log(info, callback);
|
|
180
|
-
}
|
|
189
|
+
const transport = USE_TENANT_MODE && tenantId ? getTenantTransport(tenantId) : this.defaultTransport;
|
|
190
|
+
transport.log(info, callback);
|
|
181
191
|
}
|
|
182
192
|
constructor(defaultTransport){
|
|
183
|
-
super(), _define_property(this, "defaultTransport", void 0);
|
|
184
|
-
this.defaultTransport = defaultTransport;
|
|
193
|
+
super(), _define_property(this, "defaultTransport", void 0), this.defaultTransport = defaultTransport;
|
|
185
194
|
}
|
|
186
195
|
};
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
* When USE_TENANT_MODE=true, routes to tenant folders (like database mode)
|
|
190
|
-
*/ const tenantAwareCombinedTransport = new TenantAwareTransport(combinedRotateTransport);
|
|
191
|
-
/**
|
|
192
|
-
* Console transport for development
|
|
193
|
-
*/ const consoleTransport = new _winston.transports.Console({
|
|
196
|
+
const tenantAwareCombinedTransport = new TenantAwareTransport(combinedRotateTransport);
|
|
197
|
+
const consoleTransport = new _winston.transports.Console({
|
|
194
198
|
level: LOG_LEVEL,
|
|
195
199
|
format: _winston.format.combine(_winston.format.colorize({
|
|
196
200
|
all: true
|
|
@@ -198,7 +202,7 @@ if (!(0, _fs.existsSync)(LOG_DIR)) {
|
|
|
198
202
|
format: 'YYYY-MM-DD HH:mm:ss'
|
|
199
203
|
}), _winston.format.errors({
|
|
200
204
|
stack: true
|
|
201
|
-
}),
|
|
205
|
+
}), structuredFormat)
|
|
202
206
|
});
|
|
203
207
|
// Logger Instances
|
|
204
208
|
/**
|
|
@@ -239,3 +243,32 @@ if (!(0, _fs.existsSync)(LOG_DIR)) {
|
|
|
239
243
|
exitOnError: false
|
|
240
244
|
});
|
|
241
245
|
const instance = _config.envConfig.isProduction() ? prodLogger : devLogger;
|
|
246
|
+
/**
|
|
247
|
+
* Module logger cache to avoid creating multiple loggers for the same module
|
|
248
|
+
*/ const moduleLoggerCache = new Map();
|
|
249
|
+
function createModuleLogger(moduleName) {
|
|
250
|
+
// In dev mode, use global console logger
|
|
251
|
+
if (!_config.envConfig.isProduction()) {
|
|
252
|
+
return devLogger;
|
|
253
|
+
}
|
|
254
|
+
// Check cache first
|
|
255
|
+
if (moduleLoggerCache.has(moduleName)) {
|
|
256
|
+
return moduleLoggerCache.get(moduleName);
|
|
257
|
+
}
|
|
258
|
+
// Create module-specific logger for production
|
|
259
|
+
const moduleLogger = (0, _winston.createLogger)({
|
|
260
|
+
level: LOG_LEVEL,
|
|
261
|
+
format: _winston.format.combine(_winston.format.timestamp({
|
|
262
|
+
format: 'YYYY-MM-DD HH:mm:ss.SSS'
|
|
263
|
+
}), _winston.format.errors({
|
|
264
|
+
stack: true
|
|
265
|
+
}), structuredFormat),
|
|
266
|
+
transports: [
|
|
267
|
+
getModuleTransport(moduleName),
|
|
268
|
+
errorRotateTransport
|
|
269
|
+
],
|
|
270
|
+
exitOnError: false
|
|
271
|
+
});
|
|
272
|
+
moduleLoggerCache.set(moduleName, moduleLogger);
|
|
273
|
+
return moduleLogger;
|
|
274
|
+
}
|
package/cjs/constants/index.js
CHANGED
|
@@ -42,6 +42,7 @@ _export(exports, {
|
|
|
42
42
|
}
|
|
43
43
|
});
|
|
44
44
|
_export_star(require("./permissions"), exports);
|
|
45
|
+
_export_star(require("./message-keys"), exports);
|
|
45
46
|
function _export_star(from, to) {
|
|
46
47
|
Object.keys(from).forEach(function(k) {
|
|
47
48
|
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// ==================== SHARED/SYSTEM MESSAGE KEYS ====================
|
|
2
|
+
// Package-specific messages are now in their respective packages:
|
|
3
|
+
// - nestjs-auth/src/config/message-keys.ts
|
|
4
|
+
// - nestjs-iam/src/config/message-keys.ts
|
|
5
|
+
// - nestjs-storage/src/config/message-keys.ts
|
|
6
|
+
// - nestjs-email/src/config/message-keys.ts
|
|
7
|
+
// - nestjs-form-builder/src/config/message-keys.ts
|
|
8
|
+
// - nestjs-event-manager/src/config/message-keys.ts
|
|
9
|
+
// - nestjs-notification/src/config/message-keys.ts
|
|
10
|
+
// - nestjs-localization/src/config/message-keys.ts
|
|
11
|
+
// ==================== AUTH (Shared across guards/interceptors) ====================
|
|
12
|
+
// These are duplicated in nestjs-auth but needed here to avoid circular dependencies
|
|
13
|
+
"use strict";
|
|
14
|
+
Object.defineProperty(exports, "__esModule", {
|
|
15
|
+
value: true
|
|
16
|
+
});
|
|
17
|
+
function _export(target, all) {
|
|
18
|
+
for(var name in all)Object.defineProperty(target, name, {
|
|
19
|
+
enumerable: true,
|
|
20
|
+
get: Object.getOwnPropertyDescriptor(all, name).get
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
_export(exports, {
|
|
24
|
+
get AUTH_MESSAGES () {
|
|
25
|
+
return AUTH_MESSAGES;
|
|
26
|
+
},
|
|
27
|
+
get ERROR_MESSAGES () {
|
|
28
|
+
return ERROR_MESSAGES;
|
|
29
|
+
},
|
|
30
|
+
get MESSAGE_KEYS () {
|
|
31
|
+
return MESSAGE_KEYS;
|
|
32
|
+
},
|
|
33
|
+
get SYSTEM_MESSAGES () {
|
|
34
|
+
return SYSTEM_MESSAGES;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
const AUTH_MESSAGES = {
|
|
38
|
+
TOKEN_REQUIRED: 'auth.token.required',
|
|
39
|
+
TOKEN_INVALID: 'auth.token.invalid',
|
|
40
|
+
TOKEN_EXPIRED: 'auth.token.expired',
|
|
41
|
+
COMPANY_NO_ACCESS: 'auth.company.no.access'
|
|
42
|
+
};
|
|
43
|
+
const ERROR_MESSAGES = {
|
|
44
|
+
NOT_FOUND: 'error.not.found',
|
|
45
|
+
VALIDATION: 'error.validation',
|
|
46
|
+
UNAUTHORIZED: 'error.unauthorized',
|
|
47
|
+
FORBIDDEN: 'error.forbidden',
|
|
48
|
+
CONFLICT: 'error.conflict',
|
|
49
|
+
INTERNAL: 'error.internal',
|
|
50
|
+
SERVICE_UNAVAILABLE: 'error.service.unavailable',
|
|
51
|
+
UNKNOWN: 'error.unknown',
|
|
52
|
+
HTTP: 'error.http',
|
|
53
|
+
GENERIC: 'error.generic',
|
|
54
|
+
PERMISSION_SYSTEM_UNAVAILABLE: 'error.permission.system.unavailable',
|
|
55
|
+
INSUFFICIENT_PERMISSIONS: 'error.insufficient.permissions',
|
|
56
|
+
INSUFFICIENT_PERMISSIONS_OR: 'error.insufficient.permissions.or',
|
|
57
|
+
NO_PERMISSIONS_FOUND: 'error.no.permissions.found'
|
|
58
|
+
};
|
|
59
|
+
const SYSTEM_MESSAGES = {
|
|
60
|
+
REPOSITORY_NOT_AVAILABLE: 'system.repository.not.available',
|
|
61
|
+
DATASOURCE_NOT_AVAILABLE: 'system.datasource.not.available',
|
|
62
|
+
DATABASE_CONFIG_NOT_AVAILABLE: 'system.database.config.not.available',
|
|
63
|
+
SERVICE_NOT_AVAILABLE: 'system.service.not.available',
|
|
64
|
+
CONFIG_REQUIRED: 'system.config.required',
|
|
65
|
+
INTERNAL_ERROR: 'system.internal.error',
|
|
66
|
+
NOT_FOUND: 'system.not.found',
|
|
67
|
+
DUPLICATE_REQUEST: 'system.duplicate.request',
|
|
68
|
+
INVALID_TENANT_ID: 'system.invalid.tenant.id',
|
|
69
|
+
TENANT_NOT_FOUND: 'system.tenant.not.found',
|
|
70
|
+
TENANT_HEADER_REQUIRED: 'system.tenant.header.required',
|
|
71
|
+
MISSING_PARAMETER: 'system.missing.parameter',
|
|
72
|
+
SDK_NOT_INSTALLED: 'system.sdk.not.installed',
|
|
73
|
+
PATH_TRAVERSAL_DETECTED: 'system.path.traversal.detected',
|
|
74
|
+
INVALID_FILE_KEY: 'system.invalid.file.key'
|
|
75
|
+
};
|
|
76
|
+
const MESSAGE_KEYS = {
|
|
77
|
+
AUTH: AUTH_MESSAGES,
|
|
78
|
+
ERROR: ERROR_MESSAGES,
|
|
79
|
+
SYSTEM: SYSTEM_MESSAGES
|
|
80
|
+
};
|
|
@@ -46,6 +46,9 @@ _export(exports, {
|
|
|
46
46
|
get FORM_RESULT_PERMISSIONS () {
|
|
47
47
|
return FORM_RESULT_PERMISSIONS;
|
|
48
48
|
},
|
|
49
|
+
get LANGUAGE_PERMISSIONS () {
|
|
50
|
+
return LANGUAGE_PERMISSIONS;
|
|
51
|
+
},
|
|
49
52
|
get NOTIFICATION_PERMISSIONS () {
|
|
50
53
|
return NOTIFICATION_PERMISSIONS;
|
|
51
54
|
},
|
|
@@ -61,6 +64,12 @@ _export(exports, {
|
|
|
61
64
|
get STORAGE_CONFIG_PERMISSIONS () {
|
|
62
65
|
return STORAGE_CONFIG_PERMISSIONS;
|
|
63
66
|
},
|
|
67
|
+
get TRANSLATION_KEY_PERMISSIONS () {
|
|
68
|
+
return TRANSLATION_KEY_PERMISSIONS;
|
|
69
|
+
},
|
|
70
|
+
get TRANSLATION_PERMISSIONS () {
|
|
71
|
+
return TRANSLATION_PERMISSIONS;
|
|
72
|
+
},
|
|
64
73
|
get USER_ACTION_PERMISSIONS () {
|
|
65
74
|
return USER_ACTION_PERMISSIONS;
|
|
66
75
|
},
|
|
@@ -177,6 +186,24 @@ const NOTIFICATION_PERMISSIONS = {
|
|
|
177
186
|
UPDATE: 'notification.update',
|
|
178
187
|
DELETE: 'notification.delete'
|
|
179
188
|
};
|
|
189
|
+
const LANGUAGE_PERMISSIONS = {
|
|
190
|
+
CREATE: 'language.create',
|
|
191
|
+
READ: 'language.read',
|
|
192
|
+
UPDATE: 'language.update',
|
|
193
|
+
DELETE: 'language.delete'
|
|
194
|
+
};
|
|
195
|
+
const TRANSLATION_KEY_PERMISSIONS = {
|
|
196
|
+
CREATE: 'translation-key.create',
|
|
197
|
+
READ: 'translation-key.read',
|
|
198
|
+
UPDATE: 'translation-key.update',
|
|
199
|
+
DELETE: 'translation-key.delete'
|
|
200
|
+
};
|
|
201
|
+
const TRANSLATION_PERMISSIONS = {
|
|
202
|
+
CREATE: 'translation.create',
|
|
203
|
+
READ: 'translation.read',
|
|
204
|
+
UPDATE: 'translation.update',
|
|
205
|
+
DELETE: 'translation.delete'
|
|
206
|
+
};
|
|
180
207
|
const PERMISSIONS = {
|
|
181
208
|
// Auth
|
|
182
209
|
USER: USER_PERMISSIONS,
|
|
@@ -203,5 +230,9 @@ const PERMISSIONS = {
|
|
|
203
230
|
EVENT: EVENT_PERMISSIONS,
|
|
204
231
|
EVENT_PARTICIPANT: EVENT_PARTICIPANT_PERMISSIONS,
|
|
205
232
|
// Notification
|
|
206
|
-
NOTIFICATION: NOTIFICATION_PERMISSIONS
|
|
233
|
+
NOTIFICATION: NOTIFICATION_PERMISSIONS,
|
|
234
|
+
// Localization
|
|
235
|
+
LANGUAGE: LANGUAGE_PERMISSIONS,
|
|
236
|
+
TRANSLATION_KEY: TRANSLATION_KEY_PERMISSIONS,
|
|
237
|
+
TRANSLATION: TRANSLATION_PERMISSIONS
|
|
207
238
|
};
|
package/cjs/decorators/index.js
CHANGED
|
@@ -4,6 +4,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
});
|
|
5
5
|
_export_star(require("./api-response.decorator"), exports);
|
|
6
6
|
_export_star(require("./current-user.decorator"), exports);
|
|
7
|
+
_export_star(require("./log-action.decorator"), exports);
|
|
7
8
|
_export_star(require("./public.decorator"), exports);
|
|
8
9
|
_export_star(require("./require-permission.decorator"), exports);
|
|
9
10
|
_export_star(require("./sanitize-html.decorator"), exports);
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", {
|
|
3
|
+
value: true
|
|
4
|
+
});
|
|
5
|
+
Object.defineProperty(exports, "LogAction", {
|
|
6
|
+
enumerable: true,
|
|
7
|
+
get: function() {
|
|
8
|
+
return LogAction;
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
const _common = require("@nestjs/common");
|
|
12
|
+
const _winstonloggerclass = require("../classes/winston.logger.class");
|
|
13
|
+
const _loggermiddleware = require("../middlewares/logger.middleware");
|
|
14
|
+
function createLoggerAdapter(className, moduleName) {
|
|
15
|
+
if (moduleName) {
|
|
16
|
+
const winstonLogger = (0, _winstonloggerclass.createModuleLogger)(moduleName);
|
|
17
|
+
return {
|
|
18
|
+
log: (level, message, meta)=>{
|
|
19
|
+
winstonLogger.log(level, message, {
|
|
20
|
+
context: className,
|
|
21
|
+
...meta
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
error: (message, stack, meta)=>{
|
|
25
|
+
winstonLogger.error(message, {
|
|
26
|
+
context: className,
|
|
27
|
+
stack,
|
|
28
|
+
...meta
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const nestLogger = new _common.Logger(className);
|
|
34
|
+
return {
|
|
35
|
+
log: (level, message, meta)=>{
|
|
36
|
+
nestLogger[level](message, meta);
|
|
37
|
+
},
|
|
38
|
+
error: (message, stack, meta)=>{
|
|
39
|
+
nestLogger.error(message, stack, meta);
|
|
40
|
+
}
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
function LogAction(options = {}) {
|
|
44
|
+
return function(target, propertyKey, descriptor) {
|
|
45
|
+
const originalMethod = descriptor.value;
|
|
46
|
+
const className = target.constructor.name;
|
|
47
|
+
const methodName = String(propertyKey);
|
|
48
|
+
descriptor.value = async function(...args) {
|
|
49
|
+
const startTime = Date.now();
|
|
50
|
+
// Resolve module: decorator option > instance property > undefined
|
|
51
|
+
const moduleName = options.module || this.moduleName;
|
|
52
|
+
const logger = createLoggerAdapter(className, moduleName);
|
|
53
|
+
const action = options.action || `${this.entityName || className}.${methodName}`;
|
|
54
|
+
const logLevel = options.logLevel || 'debug';
|
|
55
|
+
const context = {
|
|
56
|
+
requestId: (0, _loggermiddleware.getRequestId)(),
|
|
57
|
+
userId: (0, _loggermiddleware.getUserId)(),
|
|
58
|
+
tenantId: (0, _loggermiddleware.getTenantId)(),
|
|
59
|
+
companyId: (0, _loggermiddleware.getCompanyId)(),
|
|
60
|
+
method: methodName,
|
|
61
|
+
module: moduleName
|
|
62
|
+
};
|
|
63
|
+
const startMessage = `Executing action method "${action}"`;
|
|
64
|
+
const startContext = options.includeParams ? {
|
|
65
|
+
...context,
|
|
66
|
+
params: sanitizeParams(args)
|
|
67
|
+
} : context;
|
|
68
|
+
logger.log(logLevel, startMessage, startContext);
|
|
69
|
+
try {
|
|
70
|
+
const result = await originalMethod.apply(this, args);
|
|
71
|
+
const duration = Date.now() - startTime;
|
|
72
|
+
const resultType = getResultType(result);
|
|
73
|
+
const successMessage = `Executed action method "${action}", returned result "${resultType}" in ${duration.toFixed(2)}ms.`;
|
|
74
|
+
const successContext = options.includeResult && result ? {
|
|
75
|
+
...context,
|
|
76
|
+
duration: `${duration}ms`,
|
|
77
|
+
resultType,
|
|
78
|
+
resultPreview: getResultPreview(result)
|
|
79
|
+
} : {
|
|
80
|
+
...context,
|
|
81
|
+
duration: `${duration}ms`,
|
|
82
|
+
resultType
|
|
83
|
+
};
|
|
84
|
+
logger.log(logLevel, successMessage, successContext);
|
|
85
|
+
return result;
|
|
86
|
+
} catch (error) {
|
|
87
|
+
const duration = Date.now() - startTime;
|
|
88
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
89
|
+
logger.error(`Failed action method "${action}" after ${duration.toFixed(2)}ms: ${errorMessage}`, error instanceof Error ? error.stack : undefined, {
|
|
90
|
+
...context,
|
|
91
|
+
duration: `${duration}ms`,
|
|
92
|
+
error: errorMessage
|
|
93
|
+
});
|
|
94
|
+
throw error;
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
return descriptor;
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
function sanitizeParams(args) {
|
|
101
|
+
return args.map((arg)=>{
|
|
102
|
+
if (arg === null || arg === undefined) return arg;
|
|
103
|
+
if (typeof arg === 'object') {
|
|
104
|
+
const sanitized = {
|
|
105
|
+
...arg
|
|
106
|
+
};
|
|
107
|
+
const sensitiveKeys = [
|
|
108
|
+
'password',
|
|
109
|
+
'secret',
|
|
110
|
+
'token',
|
|
111
|
+
'apiKey',
|
|
112
|
+
'authorization'
|
|
113
|
+
];
|
|
114
|
+
for (const key of Object.keys(sanitized)){
|
|
115
|
+
if (sensitiveKeys.some((sk)=>key.toLowerCase().includes(sk))) {
|
|
116
|
+
sanitized[key] = '[REDACTED]';
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return sanitized;
|
|
120
|
+
}
|
|
121
|
+
return arg;
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
function getResultType(result) {
|
|
125
|
+
if (result === null) return 'null';
|
|
126
|
+
if (result === undefined) return 'undefined';
|
|
127
|
+
if (Array.isArray(result)) return `Array<${result.length} items>`;
|
|
128
|
+
if (typeof result === 'object') {
|
|
129
|
+
const name = result.constructor?.name;
|
|
130
|
+
return name && name !== 'Object' ? name : 'Object';
|
|
131
|
+
}
|
|
132
|
+
return typeof result;
|
|
133
|
+
}
|
|
134
|
+
function getResultPreview(result) {
|
|
135
|
+
if (Array.isArray(result)) {
|
|
136
|
+
return {
|
|
137
|
+
count: result.length,
|
|
138
|
+
sample: result.slice(0, 2)
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (typeof result === 'object' && result !== null) {
|
|
142
|
+
const keys = Object.keys(result).slice(0, 5);
|
|
143
|
+
return {
|
|
144
|
+
keys,
|
|
145
|
+
hasMore: Object.keys(result).length > 5
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
return result;
|
|
149
|
+
}
|
|
@@ -83,6 +83,8 @@ let SingleResponseDto = class SingleResponseDto {
|
|
|
83
83
|
constructor(){
|
|
84
84
|
_define_property(this, "success", void 0);
|
|
85
85
|
_define_property(this, "message", void 0);
|
|
86
|
+
_define_property(this, "messageKey", void 0);
|
|
87
|
+
_define_property(this, "messageVariables", void 0);
|
|
86
88
|
_define_property(this, "data", void 0);
|
|
87
89
|
_define_property(this, "_meta", void 0);
|
|
88
90
|
}
|
|
@@ -99,6 +101,22 @@ _ts_decorate([
|
|
|
99
101
|
}),
|
|
100
102
|
_ts_metadata("design:type", String)
|
|
101
103
|
], SingleResponseDto.prototype, "message", void 0);
|
|
104
|
+
_ts_decorate([
|
|
105
|
+
(0, _swagger.ApiPropertyOptional)({
|
|
106
|
+
example: 'user.create.success',
|
|
107
|
+
description: 'Translation key for localization'
|
|
108
|
+
}),
|
|
109
|
+
_ts_metadata("design:type", String)
|
|
110
|
+
], SingleResponseDto.prototype, "messageKey", void 0);
|
|
111
|
+
_ts_decorate([
|
|
112
|
+
(0, _swagger.ApiPropertyOptional)({
|
|
113
|
+
example: {
|
|
114
|
+
name: 'John'
|
|
115
|
+
},
|
|
116
|
+
description: 'Variables for message interpolation'
|
|
117
|
+
}),
|
|
118
|
+
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
119
|
+
], SingleResponseDto.prototype, "messageVariables", void 0);
|
|
102
120
|
_ts_decorate([
|
|
103
121
|
(0, _swagger.ApiPropertyOptional)(),
|
|
104
122
|
_ts_metadata("design:type", typeof T === "undefined" ? Object : T)
|
|
@@ -162,6 +180,8 @@ let ListResponseDto = class ListResponseDto {
|
|
|
162
180
|
constructor(){
|
|
163
181
|
_define_property(this, "success", void 0);
|
|
164
182
|
_define_property(this, "message", void 0);
|
|
183
|
+
_define_property(this, "messageKey", void 0);
|
|
184
|
+
_define_property(this, "messageVariables", void 0);
|
|
165
185
|
_define_property(this, "data", void 0);
|
|
166
186
|
_define_property(this, "meta", void 0);
|
|
167
187
|
_define_property(this, "_meta", void 0);
|
|
@@ -179,6 +199,22 @@ _ts_decorate([
|
|
|
179
199
|
}),
|
|
180
200
|
_ts_metadata("design:type", String)
|
|
181
201
|
], ListResponseDto.prototype, "message", void 0);
|
|
202
|
+
_ts_decorate([
|
|
203
|
+
(0, _swagger.ApiPropertyOptional)({
|
|
204
|
+
example: 'user.list.success',
|
|
205
|
+
description: 'Translation key for localization'
|
|
206
|
+
}),
|
|
207
|
+
_ts_metadata("design:type", String)
|
|
208
|
+
], ListResponseDto.prototype, "messageKey", void 0);
|
|
209
|
+
_ts_decorate([
|
|
210
|
+
(0, _swagger.ApiPropertyOptional)({
|
|
211
|
+
example: {
|
|
212
|
+
count: 10
|
|
213
|
+
},
|
|
214
|
+
description: 'Variables for message interpolation'
|
|
215
|
+
}),
|
|
216
|
+
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
217
|
+
], ListResponseDto.prototype, "messageVariables", void 0);
|
|
182
218
|
_ts_decorate([
|
|
183
219
|
(0, _swagger.ApiPropertyOptional)({
|
|
184
220
|
isArray: true
|
|
@@ -229,6 +265,8 @@ let BulkResponseDto = class BulkResponseDto {
|
|
|
229
265
|
constructor(){
|
|
230
266
|
_define_property(this, "success", void 0);
|
|
231
267
|
_define_property(this, "message", void 0);
|
|
268
|
+
_define_property(this, "messageKey", void 0);
|
|
269
|
+
_define_property(this, "messageVariables", void 0);
|
|
232
270
|
_define_property(this, "data", void 0);
|
|
233
271
|
_define_property(this, "meta", void 0);
|
|
234
272
|
_define_property(this, "_meta", void 0);
|
|
@@ -246,6 +284,22 @@ _ts_decorate([
|
|
|
246
284
|
}),
|
|
247
285
|
_ts_metadata("design:type", String)
|
|
248
286
|
], BulkResponseDto.prototype, "message", void 0);
|
|
287
|
+
_ts_decorate([
|
|
288
|
+
(0, _swagger.ApiPropertyOptional)({
|
|
289
|
+
example: 'user.bulk.success',
|
|
290
|
+
description: 'Translation key for localization'
|
|
291
|
+
}),
|
|
292
|
+
_ts_metadata("design:type", String)
|
|
293
|
+
], BulkResponseDto.prototype, "messageKey", void 0);
|
|
294
|
+
_ts_decorate([
|
|
295
|
+
(0, _swagger.ApiPropertyOptional)({
|
|
296
|
+
example: {
|
|
297
|
+
count: 5
|
|
298
|
+
},
|
|
299
|
+
description: 'Variables for message interpolation'
|
|
300
|
+
}),
|
|
301
|
+
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
302
|
+
], BulkResponseDto.prototype, "messageVariables", void 0);
|
|
249
303
|
_ts_decorate([
|
|
250
304
|
(0, _swagger.ApiPropertyOptional)({
|
|
251
305
|
isArray: true
|
|
@@ -271,6 +325,8 @@ let MessageResponseDto = class MessageResponseDto {
|
|
|
271
325
|
constructor(){
|
|
272
326
|
_define_property(this, "success", void 0);
|
|
273
327
|
_define_property(this, "message", void 0);
|
|
328
|
+
_define_property(this, "messageKey", void 0);
|
|
329
|
+
_define_property(this, "messageVariables", void 0);
|
|
274
330
|
_define_property(this, "_meta", void 0);
|
|
275
331
|
}
|
|
276
332
|
};
|
|
@@ -286,6 +342,22 @@ _ts_decorate([
|
|
|
286
342
|
}),
|
|
287
343
|
_ts_metadata("design:type", String)
|
|
288
344
|
], MessageResponseDto.prototype, "message", void 0);
|
|
345
|
+
_ts_decorate([
|
|
346
|
+
(0, _swagger.ApiPropertyOptional)({
|
|
347
|
+
example: 'user.delete.success',
|
|
348
|
+
description: 'Translation key for localization'
|
|
349
|
+
}),
|
|
350
|
+
_ts_metadata("design:type", String)
|
|
351
|
+
], MessageResponseDto.prototype, "messageKey", void 0);
|
|
352
|
+
_ts_decorate([
|
|
353
|
+
(0, _swagger.ApiPropertyOptional)({
|
|
354
|
+
example: {
|
|
355
|
+
count: 3
|
|
356
|
+
},
|
|
357
|
+
description: 'Variables for message interpolation'
|
|
358
|
+
}),
|
|
359
|
+
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
360
|
+
], MessageResponseDto.prototype, "messageVariables", void 0);
|
|
289
361
|
_ts_decorate([
|
|
290
362
|
(0, _swagger.ApiPropertyOptional)({
|
|
291
363
|
type: RequestMetaDto
|