@flusys/nestjs-shared 3.0.0 → 4.0.0-lts

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (103) hide show
  1. package/README.md +160 -80
  2. package/cjs/classes/api-controller.class.js +26 -8
  3. package/cjs/classes/api-service.class.js +100 -17
  4. package/cjs/classes/winston-logger-adapter.class.js +15 -20
  5. package/cjs/classes/winston.logger.class.js +103 -70
  6. package/cjs/constants/index.js +1 -0
  7. package/cjs/constants/message-keys.js +80 -0
  8. package/cjs/constants/permissions.js +65 -11
  9. package/cjs/decorators/index.js +1 -0
  10. package/cjs/decorators/log-action.decorator.js +149 -0
  11. package/cjs/dtos/response-payload.dto.js +72 -0
  12. package/cjs/enums/index.js +20 -0
  13. package/cjs/enums/notification-type.enum.js +17 -0
  14. package/cjs/enums/participant-status.enum.js +17 -0
  15. package/cjs/enums/recurrence-type.enum.js +18 -0
  16. package/cjs/exceptions/base-app.exception.js +145 -0
  17. package/cjs/exceptions/index.js +1 -0
  18. package/cjs/exceptions/permission.exception.js +12 -8
  19. package/cjs/filters/global-exception.filter.js +167 -0
  20. package/cjs/filters/index.js +18 -0
  21. package/cjs/guards/jwt-auth.guard.js +4 -1
  22. package/cjs/guards/permission.guard.js +6 -13
  23. package/cjs/index.js +2 -0
  24. package/cjs/interceptors/idempotency.interceptor.js +1 -1
  25. package/cjs/interceptors/index.js +0 -1
  26. package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
  27. package/cjs/interfaces/index.js +2 -0
  28. package/cjs/interfaces/logger.interface.js +1 -4
  29. package/cjs/interfaces/notification-adapter.interface.js +11 -0
  30. package/cjs/middlewares/logger.middleware.js +83 -26
  31. package/cjs/modules/datasource/multi-tenant-datasource.service.js +33 -11
  32. package/cjs/modules/utils/utils.service.js +4 -20
  33. package/cjs/utils/index.js +0 -1
  34. package/cjs/utils/query-helpers.util.js +8 -1
  35. package/classes/api-controller.class.d.ts +1 -0
  36. package/classes/api-service.class.d.ts +5 -10
  37. package/classes/winston-logger-adapter.class.d.ts +12 -11
  38. package/classes/winston.logger.class.d.ts +1 -0
  39. package/constants/index.d.ts +1 -0
  40. package/constants/message-keys.d.ts +81 -0
  41. package/constants/permissions.d.ts +72 -0
  42. package/decorators/index.d.ts +1 -0
  43. package/decorators/log-action.decorator.d.ts +8 -0
  44. package/dtos/response-payload.dto.d.ts +8 -0
  45. package/enums/index.d.ts +3 -0
  46. package/enums/notification-type.enum.d.ts +6 -0
  47. package/enums/participant-status.enum.d.ts +6 -0
  48. package/enums/recurrence-type.enum.d.ts +7 -0
  49. package/exceptions/base-app.exception.d.ts +41 -0
  50. package/exceptions/index.d.ts +1 -0
  51. package/exceptions/permission.exception.d.ts +1 -1
  52. package/fesm/classes/api-controller.class.js +26 -8
  53. package/fesm/classes/api-service.class.js +101 -18
  54. package/fesm/classes/winston-logger-adapter.class.js +18 -44
  55. package/fesm/classes/winston.logger.class.js +100 -68
  56. package/fesm/constants/index.js +2 -0
  57. package/fesm/constants/message-keys.js +59 -0
  58. package/fesm/constants/permissions.js +51 -14
  59. package/fesm/decorators/index.js +1 -0
  60. package/fesm/decorators/log-action.decorator.js +139 -0
  61. package/fesm/dtos/response-payload.dto.js +72 -0
  62. package/fesm/enums/index.js +3 -0
  63. package/fesm/enums/notification-type.enum.js +7 -0
  64. package/fesm/enums/participant-status.enum.js +7 -0
  65. package/fesm/enums/recurrence-type.enum.js +8 -0
  66. package/fesm/exceptions/base-app.exception.js +109 -0
  67. package/fesm/exceptions/index.js +1 -0
  68. package/fesm/exceptions/permission.exception.js +15 -17
  69. package/fesm/filters/global-exception.filter.js +157 -0
  70. package/fesm/filters/index.js +1 -0
  71. package/fesm/guards/jwt-auth.guard.js +5 -2
  72. package/fesm/guards/permission.guard.js +8 -15
  73. package/fesm/index.js +2 -0
  74. package/fesm/interceptors/idempotency.interceptor.js +2 -2
  75. package/fesm/interceptors/index.js +0 -1
  76. package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
  77. package/fesm/interfaces/index.js +2 -0
  78. package/fesm/interfaces/logger.interface.js +1 -4
  79. package/fesm/interfaces/notification-adapter.interface.js +1 -0
  80. package/fesm/middlewares/logger.middleware.js +83 -26
  81. package/fesm/modules/datasource/multi-tenant-datasource.service.js +34 -12
  82. package/fesm/modules/utils/utils.service.js +5 -21
  83. package/fesm/utils/index.js +0 -1
  84. package/fesm/utils/query-helpers.util.js +8 -1
  85. package/filters/global-exception.filter.d.ts +10 -0
  86. package/filters/index.d.ts +1 -0
  87. package/guards/permission.guard.d.ts +1 -3
  88. package/index.d.ts +2 -0
  89. package/interceptors/index.d.ts +0 -1
  90. package/interfaces/event-manager-adapter.interface.d.ts +43 -0
  91. package/interfaces/index.d.ts +2 -0
  92. package/interfaces/logger.interface.d.ts +5 -5
  93. package/interfaces/notification-adapter.interface.d.ts +22 -0
  94. package/modules/datasource/multi-tenant-datasource.service.d.ts +1 -2
  95. package/modules/utils/utils.service.d.ts +0 -1
  96. package/package.json +10 -3
  97. package/utils/index.d.ts +0 -1
  98. package/cjs/interceptors/query-performance.interceptor.js +0 -66
  99. package/cjs/utils/error-handler.util.js +0 -90
  100. package/fesm/interceptors/query-performance.interceptor.js +0 -56
  101. package/fesm/utils/error-handler.util.js +0 -82
  102. package/interceptors/query-performance.interceptor.d.ts +0 -8
  103. 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
- * Includes: timestamp, level, context, requestId, userId, message, metadata
40
- */ const structuredFormat = format.printf(({ timestamp, level, message, context, requestId, userId, stack, ...metadata })=>{
41
- const logEntry = {
42
- timestamp,
43
- level: level.toUpperCase(),
44
- context: context || 'App',
45
- message
46
- };
47
- if (requestId) logEntry.requestId = requestId;
48
- if (userId) logEntry.userId = userId;
49
- if (Object.keys(metadata).length > 0) logEntry.metadata = metadata;
50
- if (stack) logEntry.stack = stack;
51
- return JSON.stringify(logEntry);
52
- });
53
- /**
54
- * Custom format for development console output
55
- * Human-readable format with colors
56
- */ const devConsoleFormat = format.printf(({ timestamp, level, message, context, requestId, userId, method, url, path, statusCode, duration, stack })=>{
57
- const ctx = context || 'App';
58
- const reqId = requestId ? ` [${requestId}]` : '';
59
- const user = userId ? ` (user:${userId})` : '';
60
- const endpoint = method && (path || url) ? ` [${method} ${path || url}]` : '';
61
- const status = statusCode ? ` [${statusCode}]` : '';
62
- const time = duration ? ` (${duration})` : '';
63
- const stackTrace = stack ? `\n${stack}` : '';
64
- return `${timestamp} [${level.toUpperCase().padEnd(7)}] [${ctx}]${endpoint}${status}${time}${reqId}${user} ${message}${stackTrace}`;
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
- // Tenant-Aware Transport
88
- /**
89
- * Cache for tenant-specific transports
90
- * Avoids creating new transport instances for each log entry
91
- */ const tenantTransportCache = new Map();
92
- /**
93
- * Get or create a tenant-specific transport
94
- * Creates logs in: logs/{tenantId}/combined-YYYY-MM-DD.log
95
- */ function getTenantTransport(tenantId) {
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: `${tenantLogDir}/combined-%DATE%.log`,
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
- tenantTransportCache.set(tenantId, transport);
122
+ cache.set(name, transport);
114
123
  return transport;
115
124
  }
116
- /**
117
- * Tenant-aware transport wrapper
118
- * Routes logs to tenant-specific folders when tenantId is present
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
- if (USE_TENANT_MODE && tenantId) {
124
- // Write to tenant-specific folder
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
- * Tenant-aware combined transport
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
- }), devConsoleFormat)
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
+ }
@@ -14,3 +14,5 @@ export const PERMISSIONS_CACHE_PREFIX = 'permissions';
14
14
  export const IDEMPOTENCY_CACHE_PREFIX = 'idempotency';
15
15
  // Permission codes
16
16
  export * from './permissions';
17
+ // Message keys for localization
18
+ export * from './message-keys';
@@ -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
  };
@@ -1,5 +1,6 @@
1
1
  export * from './api-response.decorator';
2
2
  export * from './current-user.decorator';
3
+ export * from './log-action.decorator';
3
4
  export * from './public.decorator';
4
5
  export * from './require-permission.decorator';
5
6
  export * from './sanitize-html.decorator';
@@ -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,3 @@
1
+ export * from './notification-type.enum';
2
+ export * from './participant-status.enum';
3
+ export * from './recurrence-type.enum';
@@ -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
+ }({});