@flusys/nestjs-shared 3.0.0 → 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 +160 -80
- package/cjs/classes/api-controller.class.js +26 -8
- package/cjs/classes/api-service.class.js +100 -17
- package/cjs/classes/winston-logger-adapter.class.js +15 -20
- package/cjs/classes/winston.logger.class.js +103 -70
- package/cjs/constants/index.js +1 -0
- package/cjs/constants/message-keys.js +80 -0
- package/cjs/constants/permissions.js +65 -11
- package/cjs/decorators/index.js +1 -0
- package/cjs/decorators/log-action.decorator.js +149 -0
- package/cjs/dtos/response-payload.dto.js +72 -0
- package/cjs/enums/index.js +20 -0
- package/cjs/enums/notification-type.enum.js +17 -0
- package/cjs/enums/participant-status.enum.js +17 -0
- package/cjs/enums/recurrence-type.enum.js +18 -0
- package/cjs/exceptions/base-app.exception.js +145 -0
- package/cjs/exceptions/index.js +1 -0
- package/cjs/exceptions/permission.exception.js +12 -8
- package/cjs/filters/global-exception.filter.js +167 -0
- package/cjs/filters/index.js +18 -0
- package/cjs/guards/jwt-auth.guard.js +4 -1
- package/cjs/guards/permission.guard.js +6 -13
- package/cjs/index.js +2 -0
- package/cjs/interceptors/idempotency.interceptor.js +1 -1
- package/cjs/interceptors/index.js +0 -1
- package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
- package/cjs/interfaces/index.js +2 -0
- package/cjs/interfaces/logger.interface.js +1 -4
- package/cjs/interfaces/notification-adapter.interface.js +11 -0
- package/cjs/middlewares/logger.middleware.js +83 -26
- package/cjs/modules/datasource/multi-tenant-datasource.service.js +33 -11
- package/cjs/modules/utils/utils.service.js +4 -20
- package/cjs/utils/index.js +0 -1
- package/cjs/utils/query-helpers.util.js +8 -1
- package/classes/api-controller.class.d.ts +1 -0
- package/classes/api-service.class.d.ts +5 -10
- package/classes/winston-logger-adapter.class.d.ts +12 -11
- package/classes/winston.logger.class.d.ts +1 -0
- package/constants/index.d.ts +1 -0
- package/constants/message-keys.d.ts +81 -0
- package/constants/permissions.d.ts +72 -0
- package/decorators/index.d.ts +1 -0
- package/decorators/log-action.decorator.d.ts +8 -0
- package/dtos/response-payload.dto.d.ts +8 -0
- package/enums/index.d.ts +3 -0
- package/enums/notification-type.enum.d.ts +6 -0
- package/enums/participant-status.enum.d.ts +6 -0
- package/enums/recurrence-type.enum.d.ts +7 -0
- package/exceptions/base-app.exception.d.ts +41 -0
- package/exceptions/index.d.ts +1 -0
- package/exceptions/permission.exception.d.ts +1 -1
- package/fesm/classes/api-controller.class.js +26 -8
- package/fesm/classes/api-service.class.js +101 -18
- package/fesm/classes/winston-logger-adapter.class.js +18 -44
- package/fesm/classes/winston.logger.class.js +100 -68
- package/fesm/constants/index.js +2 -0
- package/fesm/constants/message-keys.js +59 -0
- package/fesm/constants/permissions.js +51 -14
- package/fesm/decorators/index.js +1 -0
- package/fesm/decorators/log-action.decorator.js +139 -0
- package/fesm/dtos/response-payload.dto.js +72 -0
- package/fesm/enums/index.js +3 -0
- package/fesm/enums/notification-type.enum.js +7 -0
- package/fesm/enums/participant-status.enum.js +7 -0
- package/fesm/enums/recurrence-type.enum.js +8 -0
- package/fesm/exceptions/base-app.exception.js +109 -0
- package/fesm/exceptions/index.js +1 -0
- package/fesm/exceptions/permission.exception.js +15 -17
- package/fesm/filters/global-exception.filter.js +157 -0
- package/fesm/filters/index.js +1 -0
- package/fesm/guards/jwt-auth.guard.js +5 -2
- package/fesm/guards/permission.guard.js +8 -15
- package/fesm/index.js +2 -0
- package/fesm/interceptors/idempotency.interceptor.js +2 -2
- package/fesm/interceptors/index.js +0 -1
- package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
- package/fesm/interfaces/index.js +2 -0
- package/fesm/interfaces/logger.interface.js +1 -4
- package/fesm/interfaces/notification-adapter.interface.js +1 -0
- package/fesm/middlewares/logger.middleware.js +83 -26
- package/fesm/modules/datasource/multi-tenant-datasource.service.js +34 -12
- package/fesm/modules/utils/utils.service.js +5 -21
- package/fesm/utils/index.js +0 -1
- package/fesm/utils/query-helpers.util.js +8 -1
- package/filters/global-exception.filter.d.ts +10 -0
- package/filters/index.d.ts +1 -0
- package/guards/permission.guard.d.ts +1 -3
- package/index.d.ts +2 -0
- package/interceptors/index.d.ts +0 -1
- package/interfaces/event-manager-adapter.interface.d.ts +43 -0
- package/interfaces/index.d.ts +2 -0
- package/interfaces/logger.interface.d.ts +5 -5
- package/interfaces/notification-adapter.interface.d.ts +22 -0
- package/modules/datasource/multi-tenant-datasource.service.d.ts +1 -2
- package/modules/utils/utils.service.d.ts +0 -1
- package/package.json +7 -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
|
@@ -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
|
@@ -0,0 +1,59 @@
|
|
|
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
|
+
export const AUTH_MESSAGES = {
|
|
14
|
+
TOKEN_REQUIRED: 'auth.token.required',
|
|
15
|
+
TOKEN_INVALID: 'auth.token.invalid',
|
|
16
|
+
TOKEN_EXPIRED: 'auth.token.expired',
|
|
17
|
+
COMPANY_NO_ACCESS: 'auth.company.no.access'
|
|
18
|
+
};
|
|
19
|
+
// ==================== ERROR (HTTP Exceptions) ====================
|
|
20
|
+
export const ERROR_MESSAGES = {
|
|
21
|
+
NOT_FOUND: 'error.not.found',
|
|
22
|
+
VALIDATION: 'error.validation',
|
|
23
|
+
UNAUTHORIZED: 'error.unauthorized',
|
|
24
|
+
FORBIDDEN: 'error.forbidden',
|
|
25
|
+
CONFLICT: 'error.conflict',
|
|
26
|
+
INTERNAL: 'error.internal',
|
|
27
|
+
SERVICE_UNAVAILABLE: 'error.service.unavailable',
|
|
28
|
+
UNKNOWN: 'error.unknown',
|
|
29
|
+
HTTP: 'error.http',
|
|
30
|
+
GENERIC: 'error.generic',
|
|
31
|
+
PERMISSION_SYSTEM_UNAVAILABLE: 'error.permission.system.unavailable',
|
|
32
|
+
INSUFFICIENT_PERMISSIONS: 'error.insufficient.permissions',
|
|
33
|
+
INSUFFICIENT_PERMISSIONS_OR: 'error.insufficient.permissions.or',
|
|
34
|
+
NO_PERMISSIONS_FOUND: 'error.no.permissions.found'
|
|
35
|
+
};
|
|
36
|
+
// ==================== SYSTEM (Infrastructure) ====================
|
|
37
|
+
export const SYSTEM_MESSAGES = {
|
|
38
|
+
REPOSITORY_NOT_AVAILABLE: 'system.repository.not.available',
|
|
39
|
+
DATASOURCE_NOT_AVAILABLE: 'system.datasource.not.available',
|
|
40
|
+
DATABASE_CONFIG_NOT_AVAILABLE: 'system.database.config.not.available',
|
|
41
|
+
SERVICE_NOT_AVAILABLE: 'system.service.not.available',
|
|
42
|
+
CONFIG_REQUIRED: 'system.config.required',
|
|
43
|
+
INTERNAL_ERROR: 'system.internal.error',
|
|
44
|
+
NOT_FOUND: 'system.not.found',
|
|
45
|
+
DUPLICATE_REQUEST: 'system.duplicate.request',
|
|
46
|
+
INVALID_TENANT_ID: 'system.invalid.tenant.id',
|
|
47
|
+
TENANT_NOT_FOUND: 'system.tenant.not.found',
|
|
48
|
+
TENANT_HEADER_REQUIRED: 'system.tenant.header.required',
|
|
49
|
+
MISSING_PARAMETER: 'system.missing.parameter',
|
|
50
|
+
SDK_NOT_INSTALLED: 'system.sdk.not.installed',
|
|
51
|
+
PATH_TRAVERSAL_DETECTED: 'system.path.traversal.detected',
|
|
52
|
+
INVALID_FILE_KEY: 'system.invalid.file.key'
|
|
53
|
+
};
|
|
54
|
+
// ==================== AGGREGATED EXPORTS ====================
|
|
55
|
+
export const MESSAGE_KEYS = {
|
|
56
|
+
AUTH: AUTH_MESSAGES,
|
|
57
|
+
ERROR: ERROR_MESSAGES,
|
|
58
|
+
SYSTEM: SYSTEM_MESSAGES
|
|
59
|
+
};
|
|
@@ -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',
|
|
@@ -100,10 +91,47 @@ export const FORM_RESULT_PERMISSIONS = {
|
|
|
100
91
|
UPDATE: 'form-result.update',
|
|
101
92
|
DELETE: 'form-result.delete'
|
|
102
93
|
};
|
|
94
|
+
// ==================== EVENT MANAGER MODULE ====================
|
|
95
|
+
export const EVENT_PERMISSIONS = {
|
|
96
|
+
CREATE: 'event.create',
|
|
97
|
+
READ: 'event.read',
|
|
98
|
+
UPDATE: 'event.update',
|
|
99
|
+
DELETE: 'event.delete'
|
|
100
|
+
};
|
|
101
|
+
export const EVENT_PARTICIPANT_PERMISSIONS = {
|
|
102
|
+
CREATE: 'event-participant.create',
|
|
103
|
+
READ: 'event-participant.read',
|
|
104
|
+
UPDATE: 'event-participant.update',
|
|
105
|
+
DELETE: 'event-participant.delete'
|
|
106
|
+
};
|
|
107
|
+
// ==================== NOTIFICATION MODULE ====================
|
|
108
|
+
export const NOTIFICATION_PERMISSIONS = {
|
|
109
|
+
CREATE: 'notification.create',
|
|
110
|
+
READ: 'notification.read',
|
|
111
|
+
UPDATE: 'notification.update',
|
|
112
|
+
DELETE: 'notification.delete'
|
|
113
|
+
};
|
|
114
|
+
// ==================== LOCALIZATION MODULE ====================
|
|
115
|
+
export const LANGUAGE_PERMISSIONS = {
|
|
116
|
+
CREATE: 'language.create',
|
|
117
|
+
READ: 'language.read',
|
|
118
|
+
UPDATE: 'language.update',
|
|
119
|
+
DELETE: 'language.delete'
|
|
120
|
+
};
|
|
121
|
+
export const TRANSLATION_KEY_PERMISSIONS = {
|
|
122
|
+
CREATE: 'translation-key.create',
|
|
123
|
+
READ: 'translation-key.read',
|
|
124
|
+
UPDATE: 'translation-key.update',
|
|
125
|
+
DELETE: 'translation-key.delete'
|
|
126
|
+
};
|
|
127
|
+
export const TRANSLATION_PERMISSIONS = {
|
|
128
|
+
CREATE: 'translation.create',
|
|
129
|
+
READ: 'translation.read',
|
|
130
|
+
UPDATE: 'translation.update',
|
|
131
|
+
DELETE: 'translation.delete'
|
|
132
|
+
};
|
|
103
133
|
// ==================== AGGREGATED EXPORTS ====================
|
|
104
|
-
|
|
105
|
-
* All permission codes grouped by module
|
|
106
|
-
*/ export const PERMISSIONS = {
|
|
134
|
+
export const PERMISSIONS = {
|
|
107
135
|
// Auth
|
|
108
136
|
USER: USER_PERMISSIONS,
|
|
109
137
|
COMPANY: COMPANY_PERMISSIONS,
|
|
@@ -124,5 +152,14 @@ export const FORM_RESULT_PERMISSIONS = {
|
|
|
124
152
|
EMAIL_TEMPLATE: EMAIL_TEMPLATE_PERMISSIONS,
|
|
125
153
|
// Form Builder
|
|
126
154
|
FORM: FORM_PERMISSIONS,
|
|
127
|
-
FORM_RESULT: FORM_RESULT_PERMISSIONS
|
|
155
|
+
FORM_RESULT: FORM_RESULT_PERMISSIONS,
|
|
156
|
+
// Event Manager
|
|
157
|
+
EVENT: EVENT_PERMISSIONS,
|
|
158
|
+
EVENT_PARTICIPANT: EVENT_PARTICIPANT_PERMISSIONS,
|
|
159
|
+
// Notification
|
|
160
|
+
NOTIFICATION: NOTIFICATION_PERMISSIONS,
|
|
161
|
+
// Localization
|
|
162
|
+
LANGUAGE: LANGUAGE_PERMISSIONS,
|
|
163
|
+
TRANSLATION_KEY: TRANSLATION_KEY_PERMISSIONS,
|
|
164
|
+
TRANSLATION: TRANSLATION_PERMISSIONS
|
|
128
165
|
};
|
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
|
+
}
|
|
@@ -50,6 +50,8 @@ export class SingleResponseDto {
|
|
|
50
50
|
constructor(){
|
|
51
51
|
_define_property(this, "success", void 0);
|
|
52
52
|
_define_property(this, "message", void 0);
|
|
53
|
+
_define_property(this, "messageKey", void 0);
|
|
54
|
+
_define_property(this, "messageVariables", void 0);
|
|
53
55
|
_define_property(this, "data", void 0);
|
|
54
56
|
_define_property(this, "_meta", void 0);
|
|
55
57
|
}
|
|
@@ -66,6 +68,22 @@ _ts_decorate([
|
|
|
66
68
|
}),
|
|
67
69
|
_ts_metadata("design:type", String)
|
|
68
70
|
], SingleResponseDto.prototype, "message", void 0);
|
|
71
|
+
_ts_decorate([
|
|
72
|
+
ApiPropertyOptional({
|
|
73
|
+
example: 'user.create.success',
|
|
74
|
+
description: 'Translation key for localization'
|
|
75
|
+
}),
|
|
76
|
+
_ts_metadata("design:type", String)
|
|
77
|
+
], SingleResponseDto.prototype, "messageKey", void 0);
|
|
78
|
+
_ts_decorate([
|
|
79
|
+
ApiPropertyOptional({
|
|
80
|
+
example: {
|
|
81
|
+
name: 'John'
|
|
82
|
+
},
|
|
83
|
+
description: 'Variables for message interpolation'
|
|
84
|
+
}),
|
|
85
|
+
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
86
|
+
], SingleResponseDto.prototype, "messageVariables", void 0);
|
|
69
87
|
_ts_decorate([
|
|
70
88
|
ApiPropertyOptional(),
|
|
71
89
|
_ts_metadata("design:type", typeof T === "undefined" ? Object : T)
|
|
@@ -129,6 +147,8 @@ export class ListResponseDto {
|
|
|
129
147
|
constructor(){
|
|
130
148
|
_define_property(this, "success", void 0);
|
|
131
149
|
_define_property(this, "message", void 0);
|
|
150
|
+
_define_property(this, "messageKey", void 0);
|
|
151
|
+
_define_property(this, "messageVariables", void 0);
|
|
132
152
|
_define_property(this, "data", void 0);
|
|
133
153
|
_define_property(this, "meta", void 0);
|
|
134
154
|
_define_property(this, "_meta", void 0);
|
|
@@ -146,6 +166,22 @@ _ts_decorate([
|
|
|
146
166
|
}),
|
|
147
167
|
_ts_metadata("design:type", String)
|
|
148
168
|
], ListResponseDto.prototype, "message", void 0);
|
|
169
|
+
_ts_decorate([
|
|
170
|
+
ApiPropertyOptional({
|
|
171
|
+
example: 'user.list.success',
|
|
172
|
+
description: 'Translation key for localization'
|
|
173
|
+
}),
|
|
174
|
+
_ts_metadata("design:type", String)
|
|
175
|
+
], ListResponseDto.prototype, "messageKey", void 0);
|
|
176
|
+
_ts_decorate([
|
|
177
|
+
ApiPropertyOptional({
|
|
178
|
+
example: {
|
|
179
|
+
count: 10
|
|
180
|
+
},
|
|
181
|
+
description: 'Variables for message interpolation'
|
|
182
|
+
}),
|
|
183
|
+
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
184
|
+
], ListResponseDto.prototype, "messageVariables", void 0);
|
|
149
185
|
_ts_decorate([
|
|
150
186
|
ApiPropertyOptional({
|
|
151
187
|
isArray: true
|
|
@@ -196,6 +232,8 @@ export class BulkResponseDto {
|
|
|
196
232
|
constructor(){
|
|
197
233
|
_define_property(this, "success", void 0);
|
|
198
234
|
_define_property(this, "message", void 0);
|
|
235
|
+
_define_property(this, "messageKey", void 0);
|
|
236
|
+
_define_property(this, "messageVariables", void 0);
|
|
199
237
|
_define_property(this, "data", void 0);
|
|
200
238
|
_define_property(this, "meta", void 0);
|
|
201
239
|
_define_property(this, "_meta", void 0);
|
|
@@ -213,6 +251,22 @@ _ts_decorate([
|
|
|
213
251
|
}),
|
|
214
252
|
_ts_metadata("design:type", String)
|
|
215
253
|
], BulkResponseDto.prototype, "message", void 0);
|
|
254
|
+
_ts_decorate([
|
|
255
|
+
ApiPropertyOptional({
|
|
256
|
+
example: 'user.bulk.success',
|
|
257
|
+
description: 'Translation key for localization'
|
|
258
|
+
}),
|
|
259
|
+
_ts_metadata("design:type", String)
|
|
260
|
+
], BulkResponseDto.prototype, "messageKey", void 0);
|
|
261
|
+
_ts_decorate([
|
|
262
|
+
ApiPropertyOptional({
|
|
263
|
+
example: {
|
|
264
|
+
count: 5
|
|
265
|
+
},
|
|
266
|
+
description: 'Variables for message interpolation'
|
|
267
|
+
}),
|
|
268
|
+
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
269
|
+
], BulkResponseDto.prototype, "messageVariables", void 0);
|
|
216
270
|
_ts_decorate([
|
|
217
271
|
ApiPropertyOptional({
|
|
218
272
|
isArray: true
|
|
@@ -238,6 +292,8 @@ export class MessageResponseDto {
|
|
|
238
292
|
constructor(){
|
|
239
293
|
_define_property(this, "success", void 0);
|
|
240
294
|
_define_property(this, "message", void 0);
|
|
295
|
+
_define_property(this, "messageKey", void 0);
|
|
296
|
+
_define_property(this, "messageVariables", void 0);
|
|
241
297
|
_define_property(this, "_meta", void 0);
|
|
242
298
|
}
|
|
243
299
|
}
|
|
@@ -253,6 +309,22 @@ _ts_decorate([
|
|
|
253
309
|
}),
|
|
254
310
|
_ts_metadata("design:type", String)
|
|
255
311
|
], MessageResponseDto.prototype, "message", void 0);
|
|
312
|
+
_ts_decorate([
|
|
313
|
+
ApiPropertyOptional({
|
|
314
|
+
example: 'user.delete.success',
|
|
315
|
+
description: 'Translation key for localization'
|
|
316
|
+
}),
|
|
317
|
+
_ts_metadata("design:type", String)
|
|
318
|
+
], MessageResponseDto.prototype, "messageKey", void 0);
|
|
319
|
+
_ts_decorate([
|
|
320
|
+
ApiPropertyOptional({
|
|
321
|
+
example: {
|
|
322
|
+
count: 3
|
|
323
|
+
},
|
|
324
|
+
description: 'Variables for message interpolation'
|
|
325
|
+
}),
|
|
326
|
+
_ts_metadata("design:type", typeof Record === "undefined" ? Object : Record)
|
|
327
|
+
], MessageResponseDto.prototype, "messageVariables", void 0);
|
|
256
328
|
_ts_decorate([
|
|
257
329
|
ApiPropertyOptional({
|
|
258
330
|
type: RequestMetaDto
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export var NotificationType = /*#__PURE__*/ function(NotificationType) {
|
|
2
|
+
NotificationType["INFO"] = "info";
|
|
3
|
+
NotificationType["WARNING"] = "warning";
|
|
4
|
+
NotificationType["SUCCESS"] = "success";
|
|
5
|
+
NotificationType["ERROR"] = "error";
|
|
6
|
+
return NotificationType;
|
|
7
|
+
}({});
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export var ParticipantStatus = /*#__PURE__*/ function(ParticipantStatus) {
|
|
2
|
+
ParticipantStatus["PENDING"] = "pending";
|
|
3
|
+
ParticipantStatus["ACCEPTED"] = "accepted";
|
|
4
|
+
ParticipantStatus["DECLINED"] = "declined";
|
|
5
|
+
ParticipantStatus["TENTATIVE"] = "tentative";
|
|
6
|
+
return ParticipantStatus;
|
|
7
|
+
}({});
|