@flusys/nestjs-shared 1.1.0 → 1.2.0-rc.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +139 -1103
- package/cjs/classes/api-controller.class.js +320 -29
- package/cjs/classes/api-service.class.js +219 -64
- package/cjs/classes/handle-custome-db.js +37 -0
- package/cjs/classes/index.js +1 -2
- package/cjs/classes/winston.logger.class.js +103 -70
- package/cjs/constants/index.js +1 -4
- package/cjs/constants/message-keys.js +68 -0
- package/cjs/constants/permissions.js +114 -11
- package/cjs/decorators/api-response.decorator.js +1 -2
- package/cjs/decorators/index.js +1 -1
- package/cjs/decorators/log-action.decorator.js +149 -0
- package/cjs/decorators/require-permission.decorator.js +5 -5
- package/cjs/dtos/get-by-ids.dto.js +65 -0
- package/cjs/dtos/identity-response.dto.js +4 -8
- package/cjs/dtos/index.js +1 -0
- package/cjs/dtos/response-payload.dto.js +72 -0
- package/cjs/entities/identity.js +8 -2
- package/cjs/entities/index.js +1 -0
- package/cjs/entities/raw-type.js +4 -0
- package/cjs/entities/user-root.js +2 -1
- package/cjs/enums/index.js +20 -0
- package/cjs/enums/notification-type.enum.js +17 -0
- package/cjs/enums/participant-status.enum.js +17 -0
- package/cjs/enums/recurrence-type.enum.js +18 -0
- package/cjs/exceptions/base-app.exception.js +151 -0
- package/cjs/exceptions/index.js +1 -0
- package/cjs/exceptions/permission.exception.js +13 -9
- package/cjs/filters/global-exception.filter.js +279 -0
- package/cjs/filters/index.js +18 -0
- package/cjs/guards/jwt-auth.guard.js +4 -1
- package/cjs/guards/permission.guard.js +71 -77
- package/cjs/index.js +2 -0
- package/cjs/interceptors/idempotency.interceptor.js +1 -1
- package/cjs/interceptors/index.js +0 -1
- package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
- package/cjs/interfaces/index.js +2 -1
- package/cjs/interfaces/notification-adapter.interface.js +11 -0
- package/cjs/interfaces/permission.interface.js +1 -1
- package/cjs/middlewares/logger.middleware.js +83 -26
- package/cjs/modules/datasource/index.js +0 -1
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +46 -85
- package/cjs/modules/utils/utils.service.js +4 -20
- package/cjs/utils/date-time.util.js +94 -0
- package/cjs/utils/index.js +1 -1
- package/cjs/utils/query-helpers.util.js +8 -1
- package/cjs/utils/request.util.js +25 -0
- package/classes/api-controller.class.d.ts +14 -6
- package/classes/api-service.class.d.ts +25 -22
- package/classes/handle-custome-db.d.ts +3 -0
- package/classes/index.d.ts +1 -2
- package/classes/winston.logger.class.d.ts +1 -0
- package/constants/index.d.ts +1 -1
- package/constants/message-keys.d.ts +45 -0
- package/constants/permissions.d.ts +127 -0
- package/decorators/api-response.decorator.d.ts +1 -1
- package/decorators/current-user.decorator.d.ts +1 -1
- package/decorators/index.d.ts +1 -1
- package/decorators/log-action.decorator.d.ts +8 -0
- package/decorators/require-permission.decorator.d.ts +2 -2
- package/dtos/get-by-ids.dto.d.ts +4 -0
- package/dtos/index.d.ts +1 -0
- package/dtos/response-payload.dto.d.ts +8 -0
- package/entities/index.d.ts +1 -0
- package/entities/raw-type.d.ts +1 -0
- package/enums/index.d.ts +3 -0
- package/enums/notification-type.enum.d.ts +6 -0
- package/enums/participant-status.enum.d.ts +6 -0
- package/enums/recurrence-type.enum.d.ts +7 -0
- package/exceptions/base-app.exception.d.ts +41 -0
- package/exceptions/index.d.ts +1 -0
- package/exceptions/permission.exception.d.ts +2 -2
- package/fesm/classes/api-controller.class.js +321 -30
- package/fesm/classes/api-service.class.js +222 -67
- package/fesm/classes/handle-custome-db.js +19 -0
- package/fesm/classes/index.js +1 -2
- package/fesm/classes/winston.logger.class.js +100 -68
- package/fesm/constants/index.js +2 -1
- package/fesm/constants/message-keys.js +49 -0
- package/fesm/constants/permissions.js +86 -14
- package/fesm/decorators/api-response.decorator.js +2 -20
- package/fesm/decorators/index.js +1 -1
- package/fesm/decorators/log-action.decorator.js +139 -0
- package/fesm/decorators/require-permission.decorator.js +17 -65
- package/fesm/dtos/get-by-ids.dto.js +55 -0
- package/fesm/dtos/identity-response.dto.js +4 -8
- package/fesm/dtos/index.js +1 -0
- package/fesm/dtos/response-payload.dto.js +72 -0
- package/fesm/entities/identity.js +8 -2
- package/fesm/entities/index.js +1 -0
- package/fesm/entities/raw-type.js +1 -0
- package/fesm/entities/user-root.js +2 -1
- package/fesm/enums/index.js +3 -0
- package/fesm/enums/notification-type.enum.js +7 -0
- package/fesm/enums/participant-status.enum.js +7 -0
- package/fesm/enums/recurrence-type.enum.js +8 -0
- package/fesm/exceptions/base-app.exception.js +115 -0
- package/fesm/exceptions/index.js +1 -0
- package/fesm/exceptions/permission.exception.js +16 -18
- package/fesm/filters/global-exception.filter.js +269 -0
- package/fesm/filters/index.js +1 -0
- package/fesm/guards/jwt-auth.guard.js +5 -2
- package/fesm/guards/permission.guard.js +73 -79
- package/fesm/index.js +2 -0
- package/fesm/interceptors/idempotency.interceptor.js +2 -2
- package/fesm/interceptors/index.js +0 -1
- package/fesm/interfaces/datasource.interface.js +1 -20
- package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
- package/fesm/interfaces/index.js +2 -1
- package/fesm/interfaces/notification-adapter.interface.js +1 -0
- package/fesm/interfaces/permission.interface.js +1 -3
- package/fesm/middlewares/logger.middleware.js +83 -26
- package/fesm/modules/datasource/index.js +0 -1
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +47 -86
- package/fesm/modules/utils/utils.service.js +5 -21
- package/fesm/utils/date-time.util.js +70 -0
- package/fesm/utils/index.js +1 -1
- package/fesm/utils/query-helpers.util.js +8 -1
- package/fesm/utils/request.util.js +22 -0
- package/filters/global-exception.filter.d.ts +9 -0
- package/filters/index.d.ts +1 -0
- package/guards/permission.guard.d.ts +3 -7
- package/index.d.ts +2 -0
- package/interceptors/idempotency.interceptor.d.ts +1 -1
- package/interceptors/index.d.ts +0 -1
- package/interfaces/api.interface.d.ts +2 -0
- package/interfaces/event-manager-adapter.interface.d.ts +43 -0
- package/interfaces/index.d.ts +2 -1
- package/interfaces/notification-adapter.interface.d.ts +21 -0
- package/interfaces/permission.interface.d.ts +13 -8
- package/modules/datasource/index.d.ts +0 -1
- package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -15
- package/modules/utils/utils.service.d.ts +0 -1
- package/package.json +21 -14
- package/utils/date-time.util.d.ts +8 -0
- package/utils/index.d.ts +1 -1
- package/utils/request.util.d.ts +2 -0
- package/cjs/classes/request-scoped-api.service.js +0 -61
- package/cjs/classes/winston-logger-adapter.class.js +0 -104
- package/cjs/decorators/sanitize-html.decorator.js +0 -36
- package/cjs/interceptors/query-performance.interceptor.js +0 -66
- package/cjs/interfaces/logger.interface.js +0 -7
- package/cjs/modules/datasource/datasource.module.js +0 -117
- package/cjs/utils/error-handler.util.js +0 -90
- package/classes/request-scoped-api.service.d.ts +0 -14
- package/classes/winston-logger-adapter.class.d.ts +0 -22
- package/decorators/sanitize-html.decorator.d.ts +0 -2
- package/fesm/classes/request-scoped-api.service.js +0 -51
- package/fesm/classes/winston-logger-adapter.class.js +0 -107
- package/fesm/decorators/sanitize-html.decorator.js +0 -45
- package/fesm/interceptors/query-performance.interceptor.js +0 -56
- package/fesm/interfaces/logger.interface.js +0 -4
- package/fesm/modules/datasource/datasource.module.js +0 -107
- package/fesm/utils/error-handler.util.js +0 -82
- package/interceptors/query-performance.interceptor.d.ts +0 -8
- package/interfaces/logger.interface.d.ts +0 -7
- package/modules/datasource/datasource.module.d.ts +0 -19
- package/utils/error-handler.util.d.ts +0 -19
|
@@ -16,9 +16,9 @@ import { existsSync, mkdirSync } from 'fs';
|
|
|
16
16
|
import { join } from 'path';
|
|
17
17
|
import { createLogger, format, transports } from 'winston';
|
|
18
18
|
// Handle both webpack (no default export) and esbuild (default export)
|
|
19
|
+
import * as DailyRotateFileModule from 'winston-daily-rotate-file';
|
|
19
20
|
import * as TransportModule from 'winston-transport';
|
|
20
21
|
const Transport = TransportModule.default || TransportModule;
|
|
21
|
-
import * as DailyRotateFileModule from 'winston-daily-rotate-file';
|
|
22
22
|
const DailyRotateFile = DailyRotateFileModule.default || DailyRotateFileModule;
|
|
23
23
|
// Configuration
|
|
24
24
|
const logConfig = envConfig.getLogConfig();
|
|
@@ -35,33 +35,48 @@ if (!existsSync(LOG_DIR)) {
|
|
|
35
35
|
}
|
|
36
36
|
// Custom Formats
|
|
37
37
|
/**
|
|
38
|
-
* Custom format for structured logging
|
|
39
|
-
*
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
38
|
+
* Custom format for structured logging (production)
|
|
39
|
+
* Multi-line format:
|
|
40
|
+
* - Line 1: Timestamp
|
|
41
|
+
* - Line 2: Log Level
|
|
42
|
+
* - Line 3: [RequestId]
|
|
43
|
+
* - Line 4: Empty
|
|
44
|
+
* - Line 5+: Message with context
|
|
45
|
+
*/ const LEVEL_MAP = {
|
|
46
|
+
error: 'Error',
|
|
47
|
+
warn: 'Warning',
|
|
48
|
+
info: 'Information',
|
|
49
|
+
debug: 'Debug',
|
|
50
|
+
verbose: 'Verbose'
|
|
51
|
+
};
|
|
52
|
+
const LOG_SEPARATOR = '─'.repeat(80);
|
|
53
|
+
const structuredFormat = format.printf(({ timestamp, level, message, context, requestId, userId, tenantId, companyId, method, url, path, statusCode, duration, stack, ...metadata })=>{
|
|
54
|
+
const reqId = requestId || 'no-request-id';
|
|
55
|
+
const levelFormatted = LEVEL_MAP[level] || level.charAt(0).toUpperCase() + level.slice(1);
|
|
56
|
+
const contextParts = [];
|
|
57
|
+
if (context) contextParts.push(`Context: ${context}`);
|
|
58
|
+
if (method && (path || url)) contextParts.push(`Endpoint: ${method} ${path || url}`);
|
|
59
|
+
if (statusCode) contextParts.push(`Status: ${statusCode}`);
|
|
60
|
+
if (duration) contextParts.push(`Duration: ${duration}`);
|
|
61
|
+
if (userId) contextParts.push(`UserId: ${userId}`);
|
|
62
|
+
if (tenantId) contextParts.push(`TenantId: ${tenantId}`);
|
|
63
|
+
if (companyId) contextParts.push(`CompanyId: ${companyId}`);
|
|
64
|
+
let content = message;
|
|
65
|
+
if (contextParts.length > 0) {
|
|
66
|
+
content = `${contextParts.join(' | ')}\n${message}`;
|
|
67
|
+
}
|
|
68
|
+
const metaKeys = Object.keys(metadata).filter((k)=>k !== 'splat');
|
|
69
|
+
if (metaKeys.length > 0) {
|
|
70
|
+
const metaStr = JSON.stringify(metaKeys.reduce((acc, k)=>({
|
|
71
|
+
...acc,
|
|
72
|
+
[k]: metadata[k]
|
|
73
|
+
}), {}));
|
|
74
|
+
content += `\nMetadata: ${metaStr}`;
|
|
75
|
+
}
|
|
76
|
+
if (stack) {
|
|
77
|
+
content += `\nStack: ${stack}`;
|
|
78
|
+
}
|
|
79
|
+
return `${LOG_SEPARATOR}\n${timestamp} | ${levelFormatted} | [${reqId}]\n${content}`;
|
|
65
80
|
});
|
|
66
81
|
// Transports
|
|
67
82
|
/**
|
|
@@ -84,63 +99,44 @@ if (!existsSync(LOG_DIR)) {
|
|
|
84
99
|
maxFiles: LOG_MAX_FILES,
|
|
85
100
|
level: 'error'
|
|
86
101
|
});
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
if (tenantTransportCache.has(tenantId)) {
|
|
97
|
-
return tenantTransportCache.get(tenantId);
|
|
98
|
-
}
|
|
99
|
-
const tenantLogDir = join(LOG_DIR, tenantId);
|
|
100
|
-
if (!existsSync(tenantLogDir)) {
|
|
101
|
-
mkdirSync(tenantLogDir, {
|
|
102
|
+
// Transport Caches
|
|
103
|
+
const moduleTransportCache = new Map();
|
|
104
|
+
const tenantTransportCache = new Map();
|
|
105
|
+
function getCachedTransport(cache, name) {
|
|
106
|
+
const cached = cache.get(name);
|
|
107
|
+
if (cached) return cached;
|
|
108
|
+
const logDir = join(LOG_DIR, name);
|
|
109
|
+
if (!existsSync(logDir)) {
|
|
110
|
+
mkdirSync(logDir, {
|
|
102
111
|
recursive: true
|
|
103
112
|
});
|
|
104
113
|
}
|
|
105
114
|
const transport = new DailyRotateFile({
|
|
106
|
-
filename: `${
|
|
115
|
+
filename: `${logDir}/combined-%DATE%.log`,
|
|
107
116
|
datePattern: 'YYYY-MM-DD',
|
|
108
117
|
zippedArchive: true,
|
|
109
118
|
maxSize: LOG_MAX_SIZE,
|
|
110
119
|
maxFiles: LOG_MAX_FILES,
|
|
111
120
|
level: LOG_LEVEL
|
|
112
121
|
});
|
|
113
|
-
|
|
122
|
+
cache.set(name, transport);
|
|
114
123
|
return transport;
|
|
115
124
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
*/ let TenantAwareTransport = class TenantAwareTransport extends Transport {
|
|
125
|
+
const getModuleTransport = (name)=>getCachedTransport(moduleTransportCache, name);
|
|
126
|
+
const getTenantTransport = (name)=>getCachedTransport(tenantTransportCache, name);
|
|
127
|
+
let TenantAwareTransport = class TenantAwareTransport extends Transport {
|
|
120
128
|
log(info, callback) {
|
|
121
129
|
setImmediate(()=>this.emit('logged', info));
|
|
122
130
|
const tenantId = info.tenantId || info.metadata?.tenantId;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
const tenantTransport = getTenantTransport(tenantId);
|
|
126
|
-
tenantTransport.log(info, callback);
|
|
127
|
-
} else {
|
|
128
|
-
// Write to default log folder
|
|
129
|
-
this.defaultTransport.log(info, callback);
|
|
130
|
-
}
|
|
131
|
+
const transport = USE_TENANT_MODE && tenantId ? getTenantTransport(tenantId) : this.defaultTransport;
|
|
132
|
+
transport.log(info, callback);
|
|
131
133
|
}
|
|
132
134
|
constructor(defaultTransport){
|
|
133
|
-
super(), _define_property(this, "defaultTransport", void 0);
|
|
134
|
-
this.defaultTransport = defaultTransport;
|
|
135
|
+
super(), _define_property(this, "defaultTransport", void 0), this.defaultTransport = defaultTransport;
|
|
135
136
|
}
|
|
136
137
|
};
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
* When USE_TENANT_MODE=true, routes to tenant folders (like database mode)
|
|
140
|
-
*/ const tenantAwareCombinedTransport = new TenantAwareTransport(combinedRotateTransport);
|
|
141
|
-
/**
|
|
142
|
-
* Console transport for development
|
|
143
|
-
*/ const consoleTransport = new transports.Console({
|
|
138
|
+
const tenantAwareCombinedTransport = new TenantAwareTransport(combinedRotateTransport);
|
|
139
|
+
const consoleTransport = new transports.Console({
|
|
144
140
|
level: LOG_LEVEL,
|
|
145
141
|
format: format.combine(format.colorize({
|
|
146
142
|
all: true
|
|
@@ -148,7 +144,7 @@ if (!existsSync(LOG_DIR)) {
|
|
|
148
144
|
format: 'YYYY-MM-DD HH:mm:ss'
|
|
149
145
|
}), format.errors({
|
|
150
146
|
stack: true
|
|
151
|
-
}),
|
|
147
|
+
}), structuredFormat)
|
|
152
148
|
});
|
|
153
149
|
// Logger Instances
|
|
154
150
|
/**
|
|
@@ -190,7 +186,43 @@ if (!existsSync(LOG_DIR)) {
|
|
|
190
186
|
});
|
|
191
187
|
// Exports
|
|
192
188
|
/**
|
|
193
|
-
* Winston logger instance
|
|
189
|
+
* Winston logger instance (global)
|
|
194
190
|
* - DEV: Console output with colors
|
|
195
191
|
* - PROD: File-based with daily rotation, tenant-aware routing
|
|
196
192
|
*/ export const instance = envConfig.isProduction() ? prodLogger : devLogger;
|
|
193
|
+
/**
|
|
194
|
+
* Module logger cache to avoid creating multiple loggers for the same module
|
|
195
|
+
*/ const moduleLoggerCache = new Map();
|
|
196
|
+
/**
|
|
197
|
+
* Create a module-specific logger instance
|
|
198
|
+
* - DEV: Console output (same as global)
|
|
199
|
+
* - PROD: File-based with module-specific folder + shared error log
|
|
200
|
+
*
|
|
201
|
+
* @param moduleName - The module name (e.g., 'auth', 'iam', 'storage')
|
|
202
|
+
* @returns Logger instance for the module
|
|
203
|
+
*/ export function createModuleLogger(moduleName) {
|
|
204
|
+
// In dev mode, use global console logger
|
|
205
|
+
if (!envConfig.isProduction()) {
|
|
206
|
+
return devLogger;
|
|
207
|
+
}
|
|
208
|
+
// Check cache first
|
|
209
|
+
if (moduleLoggerCache.has(moduleName)) {
|
|
210
|
+
return moduleLoggerCache.get(moduleName);
|
|
211
|
+
}
|
|
212
|
+
// Create module-specific logger for production
|
|
213
|
+
const moduleLogger = createLogger({
|
|
214
|
+
level: LOG_LEVEL,
|
|
215
|
+
format: format.combine(format.timestamp({
|
|
216
|
+
format: 'YYYY-MM-DD HH:mm:ss.SSS'
|
|
217
|
+
}), format.errors({
|
|
218
|
+
stack: true
|
|
219
|
+
}), structuredFormat),
|
|
220
|
+
transports: [
|
|
221
|
+
getModuleTransport(moduleName),
|
|
222
|
+
errorRotateTransport
|
|
223
|
+
],
|
|
224
|
+
exitOnError: false
|
|
225
|
+
});
|
|
226
|
+
moduleLoggerCache.set(moduleName, moduleLogger);
|
|
227
|
+
return moduleLogger;
|
|
228
|
+
}
|
package/fesm/constants/index.js
CHANGED
|
@@ -4,7 +4,6 @@ export const PERMISSIONS_KEY = 'permissions';
|
|
|
4
4
|
// Injection tokens
|
|
5
5
|
export const CACHE_INSTANCE = 'CACHE_INSTANCE';
|
|
6
6
|
export const PERMISSION_GUARD_CONFIG = 'PERMISSION_GUARD_CONFIG';
|
|
7
|
-
export const LOGGER_INSTANCE = 'LOGGER_INSTANCE';
|
|
8
7
|
// Header names
|
|
9
8
|
export const IDEMPOTENCY_KEY_HEADER = 'x-idempotency-key';
|
|
10
9
|
export const REQUEST_ID_HEADER = 'x-request-id';
|
|
@@ -14,3 +13,5 @@ export const PERMISSIONS_CACHE_PREFIX = 'permissions';
|
|
|
14
13
|
export const IDEMPOTENCY_CACHE_PREFIX = 'idempotency';
|
|
15
14
|
// Permission codes
|
|
16
15
|
export * from './permissions';
|
|
16
|
+
// Message keys for localization
|
|
17
|
+
export * from './message-keys';
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// ==================== AUTH (Shared across guards/interceptors) ====================
|
|
2
|
+
// These are duplicated in nestjs-auth but needed here to avoid circular dependencies
|
|
3
|
+
export const AUTH_MESSAGES = {
|
|
4
|
+
TOKEN_REQUIRED: 'auth.token.required',
|
|
5
|
+
TOKEN_INVALID: 'auth.token.invalid',
|
|
6
|
+
ENTITY_BELOG_ANOTHER_COMPANY: 'entity.belongs.another.company',
|
|
7
|
+
COMPANY_NO_ACCESS: 'auth.company.no.access'
|
|
8
|
+
};
|
|
9
|
+
// ==================== ERROR (HTTP Exceptions) ====================
|
|
10
|
+
export const ERROR_MESSAGES = {
|
|
11
|
+
NOT_FOUND: 'error.not.found',
|
|
12
|
+
VALIDATION: 'error.validation',
|
|
13
|
+
UNAUTHORIZED: 'error.unauthorized',
|
|
14
|
+
FORBIDDEN: 'error.forbidden',
|
|
15
|
+
CONFLICT: 'error.conflict',
|
|
16
|
+
DB_FOREIGN_KEY: 'error.db.foreign.key',
|
|
17
|
+
DB_NOT_NULL: 'error.db.not.null',
|
|
18
|
+
DB_INVALID_INPUT: 'error.db.invalid.input',
|
|
19
|
+
DB_VALUE_TOO_LONG: 'error.db.value.too.long',
|
|
20
|
+
DB_CHECK_CONSTRAINT: 'error.db.check.constraint',
|
|
21
|
+
DB_DEADLOCK: 'error.db.deadlock',
|
|
22
|
+
INTERNAL: 'error.internal',
|
|
23
|
+
SERVICE_UNAVAILABLE: 'error.service.unavailable',
|
|
24
|
+
UNKNOWN: 'error.unknown',
|
|
25
|
+
HTTP: 'error.http',
|
|
26
|
+
GENERIC: 'error.generic',
|
|
27
|
+
PERMISSION_SYSTEM_UNAVAILABLE: 'error.permission.system.unavailable',
|
|
28
|
+
INSUFFICIENT_PERMISSIONS: 'error.insufficient.permissions',
|
|
29
|
+
INSUFFICIENT_PERMISSIONS_OR: 'error.insufficient.permissions.or',
|
|
30
|
+
NO_PERMISSIONS_FOUND: 'error.no.permissions.found',
|
|
31
|
+
ENDPOINT_DISABLED: 'error.endpoint.disabled'
|
|
32
|
+
};
|
|
33
|
+
// ==================== SYSTEM (Infrastructure) ====================
|
|
34
|
+
export const SYSTEM_MESSAGES = {
|
|
35
|
+
REPOSITORY_NOT_AVAILABLE: 'system.repository.not.available',
|
|
36
|
+
DATASOURCE_NOT_AVAILABLE: 'system.datasource.not.available',
|
|
37
|
+
DATABASE_CONFIG_NOT_AVAILABLE: 'system.database.config.not.available',
|
|
38
|
+
SERVICE_NOT_AVAILABLE: 'system.service.not.available',
|
|
39
|
+
CONFIG_REQUIRED: 'system.config.required',
|
|
40
|
+
INTERNAL_ERROR: 'system.internal.error',
|
|
41
|
+
DUPLICATE_REQUEST: 'system.duplicate.request',
|
|
42
|
+
INVALID_TENANT_ID: 'system.invalid.tenant.id',
|
|
43
|
+
TENANT_NOT_FOUND: 'system.tenant.not.found',
|
|
44
|
+
TENANT_HEADER_REQUIRED: 'system.tenant.header.required',
|
|
45
|
+
MISSING_PARAMETER: 'system.missing.parameter',
|
|
46
|
+
SDK_NOT_INSTALLED: 'system.sdk.not.installed',
|
|
47
|
+
PATH_TRAVERSAL_DETECTED: 'system.path.traversal.detected',
|
|
48
|
+
INVALID_FILE_KEY: 'system.invalid.file.key'
|
|
49
|
+
};
|
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Centralized Permission Codes
|
|
3
|
-
*
|
|
4
|
-
* Single source of truth for all permission codes used across the application.
|
|
5
|
-
* Use these constants instead of hardcoded strings to prevent typos and enable easy refactoring.
|
|
6
|
-
*
|
|
7
|
-
* Naming Convention: <entity>.<action>
|
|
8
|
-
* - entity: The resource being accessed (e.g., user, role, company)
|
|
9
|
-
* - action: The operation being performed (create, read, update, delete, assign)
|
|
10
|
-
*/ // ==================== AUTH MODULE ====================
|
|
1
|
+
// ==================== AUTH MODULE ====================
|
|
11
2
|
export const USER_PERMISSIONS = {
|
|
12
3
|
CREATE: 'user.create',
|
|
13
4
|
READ: 'user.read',
|
|
@@ -74,6 +65,9 @@ export const STORAGE_CONFIG_PERMISSIONS = {
|
|
|
74
65
|
UPDATE: 'storage-config.update',
|
|
75
66
|
DELETE: 'storage-config.delete'
|
|
76
67
|
};
|
|
68
|
+
export const UPLOAD_CONFIG_PERMISSIONS = {
|
|
69
|
+
DELETE: 'upload-config.delete'
|
|
70
|
+
};
|
|
77
71
|
// ==================== EMAIL MODULE ====================
|
|
78
72
|
export const EMAIL_CONFIG_PERMISSIONS = {
|
|
79
73
|
CREATE: 'email-config.create',
|
|
@@ -100,10 +94,74 @@ export const FORM_RESULT_PERMISSIONS = {
|
|
|
100
94
|
UPDATE: 'form-result.update',
|
|
101
95
|
DELETE: 'form-result.delete'
|
|
102
96
|
};
|
|
97
|
+
// ==================== EVENT MANAGER MODULE ====================
|
|
98
|
+
export const EVENT_PERMISSIONS = {
|
|
99
|
+
CREATE: 'event.create',
|
|
100
|
+
READ: 'event.read',
|
|
101
|
+
UPDATE: 'event.update',
|
|
102
|
+
DELETE: 'event.delete'
|
|
103
|
+
};
|
|
104
|
+
// ==================== NOTIFICATION MODULE ====================
|
|
105
|
+
export const NOTIFICATION_PERMISSIONS = {
|
|
106
|
+
CREATE: 'notification.create',
|
|
107
|
+
READ: 'notification.read',
|
|
108
|
+
UPDATE: 'notification.update',
|
|
109
|
+
DELETE: 'notification.delete'
|
|
110
|
+
};
|
|
111
|
+
// ==================== LOCALIZATION MODULE ====================
|
|
112
|
+
export const LANGUAGE_PERMISSIONS = {
|
|
113
|
+
CREATE: 'language.create',
|
|
114
|
+
READ: 'language.read',
|
|
115
|
+
UPDATE: 'language.update',
|
|
116
|
+
DELETE: 'language.delete'
|
|
117
|
+
};
|
|
118
|
+
export const TRANSLATION_KEY_PERMISSIONS = {
|
|
119
|
+
CREATE: 'translation-key.create',
|
|
120
|
+
READ: 'translation-key.read',
|
|
121
|
+
UPDATE: 'translation-key.update',
|
|
122
|
+
DELETE: 'translation-key.delete'
|
|
123
|
+
};
|
|
124
|
+
export const TRANSLATION_PERMISSIONS = {
|
|
125
|
+
CREATE: 'translation.create',
|
|
126
|
+
READ: 'translation.read',
|
|
127
|
+
UPDATE: 'translation.update',
|
|
128
|
+
DELETE: 'translation.delete'
|
|
129
|
+
};
|
|
130
|
+
// ==================== TASK MANAGER MODULE ====================
|
|
131
|
+
export const TASK_BOARD_PERMISSIONS = {
|
|
132
|
+
CREATE: 'task-board.create',
|
|
133
|
+
READ: 'task-board.read',
|
|
134
|
+
UPDATE: 'task-board.update',
|
|
135
|
+
DELETE: 'task-board.delete',
|
|
136
|
+
MANAGE_MEMBERS: 'task-board.manage-members'
|
|
137
|
+
};
|
|
138
|
+
export const TASK_LIST_PERMISSIONS = {
|
|
139
|
+
CREATE: 'task-list.create',
|
|
140
|
+
READ: 'task-list.read',
|
|
141
|
+
UPDATE: 'task-list.update',
|
|
142
|
+
DELETE: 'task-list.delete'
|
|
143
|
+
};
|
|
144
|
+
export const TASK_PERMISSIONS = {
|
|
145
|
+
CREATE: 'task.create',
|
|
146
|
+
READ: 'task.read',
|
|
147
|
+
UPDATE: 'task.update',
|
|
148
|
+
DELETE: 'task.delete',
|
|
149
|
+
MOVE: 'task.move',
|
|
150
|
+
ASSIGN: 'task.assign'
|
|
151
|
+
};
|
|
152
|
+
export const TASK_LABEL_PERMISSIONS = {
|
|
153
|
+
CREATE: 'task-label.create',
|
|
154
|
+
READ: 'task-label.read',
|
|
155
|
+
UPDATE: 'task-label.update',
|
|
156
|
+
DELETE: 'task-label.delete'
|
|
157
|
+
};
|
|
158
|
+
export const TASK_COMMENT_PERMISSIONS = {
|
|
159
|
+
CREATE: 'task-comment.create',
|
|
160
|
+
READ: 'task-comment.read',
|
|
161
|
+
DELETE: 'task-comment.delete'
|
|
162
|
+
};
|
|
103
163
|
// ==================== AGGREGATED EXPORTS ====================
|
|
104
|
-
|
|
105
|
-
* All permission codes grouped by module
|
|
106
|
-
*/ export const PERMISSIONS = {
|
|
164
|
+
export const PERMISSIONS = {
|
|
107
165
|
// Auth
|
|
108
166
|
USER: USER_PERMISSIONS,
|
|
109
167
|
COMPANY: COMPANY_PERMISSIONS,
|
|
@@ -124,5 +182,19 @@ export const FORM_RESULT_PERMISSIONS = {
|
|
|
124
182
|
EMAIL_TEMPLATE: EMAIL_TEMPLATE_PERMISSIONS,
|
|
125
183
|
// Form Builder
|
|
126
184
|
FORM: FORM_PERMISSIONS,
|
|
127
|
-
FORM_RESULT: FORM_RESULT_PERMISSIONS
|
|
185
|
+
FORM_RESULT: FORM_RESULT_PERMISSIONS,
|
|
186
|
+
// Event Manager
|
|
187
|
+
EVENT: EVENT_PERMISSIONS,
|
|
188
|
+
// Notification
|
|
189
|
+
NOTIFICATION: NOTIFICATION_PERMISSIONS,
|
|
190
|
+
// Localization
|
|
191
|
+
LANGUAGE: LANGUAGE_PERMISSIONS,
|
|
192
|
+
TRANSLATION_KEY: TRANSLATION_KEY_PERMISSIONS,
|
|
193
|
+
TRANSLATION: TRANSLATION_PERMISSIONS,
|
|
194
|
+
// Task Manager
|
|
195
|
+
TASK_BOARD: TASK_BOARD_PERMISSIONS,
|
|
196
|
+
TASK_LIST: TASK_LIST_PERMISSIONS,
|
|
197
|
+
TASK: TASK_PERMISSIONS,
|
|
198
|
+
TASK_LABEL: TASK_LABEL_PERMISSIONS,
|
|
199
|
+
TASK_COMMENT: TASK_COMMENT_PERMISSIONS
|
|
128
200
|
};
|
|
@@ -1,28 +1,10 @@
|
|
|
1
1
|
import { BulkMetaDto, BulkResponseDto, ListResponseDto, PaginationMetaDto, SingleResponseDto } from '../dtos';
|
|
2
2
|
import { applyDecorators } from '@nestjs/common';
|
|
3
3
|
import { ApiExtraModels, ApiOkResponse, getSchemaPath } from '@nestjs/swagger';
|
|
4
|
-
|
|
5
|
-
* API Response Decorator
|
|
6
|
-
* Wraps response with the appropriate response structure
|
|
7
|
-
*
|
|
8
|
-
* @param dto - The DTO class for the data
|
|
9
|
-
* @param isArray - Whether the data is an array (default: false)
|
|
10
|
-
* @param arrayType - For arrays: 'list' (paginated) or 'bulk' (operations) (default: 'list')
|
|
11
|
-
*
|
|
12
|
-
* @example
|
|
13
|
-
* // Single item response
|
|
14
|
-
* @ApiResponseDto(UserDto)
|
|
15
|
-
*
|
|
16
|
-
* // Paginated list response (get-all)
|
|
17
|
-
* @ApiResponseDto(UserDto, true, 'list')
|
|
18
|
-
*
|
|
19
|
-
* // Bulk operation response (insert-many, update-many)
|
|
20
|
-
* @ApiResponseDto(UserDto, true, 'bulk')
|
|
21
|
-
*/ export const ApiResponseDto = (dto, isArray = false, arrayType = 'list')=>{
|
|
22
|
-
// Select appropriate wrapper based on response type
|
|
4
|
+
export const ApiResponseDto = (dto, isArray = false, arrayType = 'list')=>{
|
|
23
5
|
let wrapperDto;
|
|
24
6
|
let metaDto = null;
|
|
25
|
-
if (!isArray) {
|
|
7
|
+
if (!isArray || arrayType === 'single') {
|
|
26
8
|
wrapperDto = SingleResponseDto;
|
|
27
9
|
} else if (arrayType === 'bulk') {
|
|
28
10
|
wrapperDto = BulkResponseDto;
|
package/fesm/decorators/index.js
CHANGED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { Logger } from '@nestjs/common';
|
|
2
|
+
import { createModuleLogger } from '../classes/winston.logger.class';
|
|
3
|
+
import { getRequestId, getUserId, getTenantId, getCompanyId } from '../middlewares/logger.middleware';
|
|
4
|
+
function createLoggerAdapter(className, moduleName) {
|
|
5
|
+
if (moduleName) {
|
|
6
|
+
const winstonLogger = createModuleLogger(moduleName);
|
|
7
|
+
return {
|
|
8
|
+
log: (level, message, meta)=>{
|
|
9
|
+
winstonLogger.log(level, message, {
|
|
10
|
+
context: className,
|
|
11
|
+
...meta
|
|
12
|
+
});
|
|
13
|
+
},
|
|
14
|
+
error: (message, stack, meta)=>{
|
|
15
|
+
winstonLogger.error(message, {
|
|
16
|
+
context: className,
|
|
17
|
+
stack,
|
|
18
|
+
...meta
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
const nestLogger = new Logger(className);
|
|
24
|
+
return {
|
|
25
|
+
log: (level, message, meta)=>{
|
|
26
|
+
nestLogger[level](message, meta);
|
|
27
|
+
},
|
|
28
|
+
error: (message, stack, meta)=>{
|
|
29
|
+
nestLogger.error(message, stack, meta);
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
export function LogAction(options = {}) {
|
|
34
|
+
return function(target, propertyKey, descriptor) {
|
|
35
|
+
const originalMethod = descriptor.value;
|
|
36
|
+
const className = target.constructor.name;
|
|
37
|
+
const methodName = String(propertyKey);
|
|
38
|
+
descriptor.value = async function(...args) {
|
|
39
|
+
const startTime = Date.now();
|
|
40
|
+
// Resolve module: decorator option > instance property > undefined
|
|
41
|
+
const moduleName = options.module || this.moduleName;
|
|
42
|
+
const logger = createLoggerAdapter(className, moduleName);
|
|
43
|
+
const action = options.action || `${this.entityName || className}.${methodName}`;
|
|
44
|
+
const logLevel = options.logLevel || 'debug';
|
|
45
|
+
const context = {
|
|
46
|
+
requestId: getRequestId(),
|
|
47
|
+
userId: getUserId(),
|
|
48
|
+
tenantId: getTenantId(),
|
|
49
|
+
companyId: getCompanyId(),
|
|
50
|
+
method: methodName,
|
|
51
|
+
module: moduleName
|
|
52
|
+
};
|
|
53
|
+
const startMessage = `Executing action method "${action}"`;
|
|
54
|
+
const startContext = options.includeParams ? {
|
|
55
|
+
...context,
|
|
56
|
+
params: sanitizeParams(args)
|
|
57
|
+
} : context;
|
|
58
|
+
logger.log(logLevel, startMessage, startContext);
|
|
59
|
+
try {
|
|
60
|
+
const result = await originalMethod.apply(this, args);
|
|
61
|
+
const duration = Date.now() - startTime;
|
|
62
|
+
const resultType = getResultType(result);
|
|
63
|
+
const successMessage = `Executed action method "${action}", returned result "${resultType}" in ${duration.toFixed(2)}ms.`;
|
|
64
|
+
const successContext = options.includeResult && result ? {
|
|
65
|
+
...context,
|
|
66
|
+
duration: `${duration}ms`,
|
|
67
|
+
resultType,
|
|
68
|
+
resultPreview: getResultPreview(result)
|
|
69
|
+
} : {
|
|
70
|
+
...context,
|
|
71
|
+
duration: `${duration}ms`,
|
|
72
|
+
resultType
|
|
73
|
+
};
|
|
74
|
+
logger.log(logLevel, successMessage, successContext);
|
|
75
|
+
return result;
|
|
76
|
+
} catch (error) {
|
|
77
|
+
const duration = Date.now() - startTime;
|
|
78
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
79
|
+
logger.error(`Failed action method "${action}" after ${duration.toFixed(2)}ms: ${errorMessage}`, error instanceof Error ? error.stack : undefined, {
|
|
80
|
+
...context,
|
|
81
|
+
duration: `${duration}ms`,
|
|
82
|
+
error: errorMessage
|
|
83
|
+
});
|
|
84
|
+
throw error;
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
return descriptor;
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
function sanitizeParams(args) {
|
|
91
|
+
return args.map((arg)=>{
|
|
92
|
+
if (arg === null || arg === undefined) return arg;
|
|
93
|
+
if (typeof arg === 'object') {
|
|
94
|
+
const sanitized = {
|
|
95
|
+
...arg
|
|
96
|
+
};
|
|
97
|
+
const sensitiveKeys = [
|
|
98
|
+
'password',
|
|
99
|
+
'secret',
|
|
100
|
+
'token',
|
|
101
|
+
'apiKey',
|
|
102
|
+
'authorization'
|
|
103
|
+
];
|
|
104
|
+
for (const key of Object.keys(sanitized)){
|
|
105
|
+
if (sensitiveKeys.some((sk)=>key.toLowerCase().includes(sk))) {
|
|
106
|
+
sanitized[key] = '[REDACTED]';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return sanitized;
|
|
110
|
+
}
|
|
111
|
+
return arg;
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
function getResultType(result) {
|
|
115
|
+
if (result === null) return 'null';
|
|
116
|
+
if (result === undefined) return 'undefined';
|
|
117
|
+
if (Array.isArray(result)) return `Array<${result.length} items>`;
|
|
118
|
+
if (typeof result === 'object') {
|
|
119
|
+
const name = result.constructor?.name;
|
|
120
|
+
return name && name !== 'Object' ? name : 'Object';
|
|
121
|
+
}
|
|
122
|
+
return typeof result;
|
|
123
|
+
}
|
|
124
|
+
function getResultPreview(result) {
|
|
125
|
+
if (Array.isArray(result)) {
|
|
126
|
+
return {
|
|
127
|
+
count: result.length,
|
|
128
|
+
sample: result.slice(0, 2)
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
if (typeof result === 'object' && result !== null) {
|
|
132
|
+
const keys = Object.keys(result).slice(0, 5);
|
|
133
|
+
return {
|
|
134
|
+
keys,
|
|
135
|
+
hasMore: Object.keys(result).length > 5
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|