@flusys/nestjs-shared 1.1.0-beta → 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.
Files changed (198) hide show
  1. package/README.md +146 -1330
  2. package/cjs/classes/api-controller.class.js +325 -49
  3. package/cjs/classes/api-service.class.js +271 -149
  4. package/cjs/classes/handle-custome-db.js +37 -0
  5. package/cjs/classes/index.js +2 -2
  6. package/cjs/classes/winston.logger.class.js +103 -70
  7. package/cjs/constants/index.js +15 -4
  8. package/cjs/constants/message-keys.js +68 -0
  9. package/cjs/constants/permissions.js +287 -0
  10. package/cjs/decorators/api-response.decorator.js +2 -3
  11. package/cjs/decorators/index.js +1 -0
  12. package/cjs/decorators/log-action.decorator.js +149 -0
  13. package/cjs/decorators/require-permission.decorator.js +5 -5
  14. package/cjs/dtos/delete.dto.js +10 -0
  15. package/cjs/dtos/filter-and-pagination.dto.js +24 -34
  16. package/cjs/dtos/get-by-ids.dto.js +65 -0
  17. package/cjs/dtos/identity-response.dto.js +4 -8
  18. package/cjs/dtos/index.js +1 -0
  19. package/cjs/dtos/pagination.dto.js +4 -8
  20. package/cjs/dtos/response-payload.dto.js +68 -112
  21. package/cjs/entities/identity.js +12 -6
  22. package/cjs/entities/index.js +1 -0
  23. package/cjs/entities/raw-type.js +4 -0
  24. package/cjs/entities/user-root.js +15 -15
  25. package/cjs/enums/index.js +20 -0
  26. package/cjs/enums/notification-type.enum.js +17 -0
  27. package/cjs/enums/participant-status.enum.js +17 -0
  28. package/cjs/enums/recurrence-type.enum.js +18 -0
  29. package/cjs/exceptions/base-app.exception.js +151 -0
  30. package/cjs/exceptions/index.js +1 -0
  31. package/cjs/exceptions/permission.exception.js +13 -9
  32. package/cjs/filters/global-exception.filter.js +279 -0
  33. package/cjs/filters/index.js +18 -0
  34. package/cjs/guards/jwt-auth.guard.js +4 -1
  35. package/cjs/guards/permission.guard.js +88 -148
  36. package/cjs/index.js +2 -0
  37. package/cjs/interceptors/idempotency.interceptor.js +1 -1
  38. package/cjs/interceptors/index.js +1 -4
  39. package/cjs/interceptors/set-user-field-on-body.interceptor.js +60 -0
  40. package/cjs/interceptors/slug.interceptor.js +30 -9
  41. package/cjs/interfaces/datasource.interface.js +4 -0
  42. package/cjs/interfaces/event-manager-adapter.interface.js +11 -0
  43. package/cjs/interfaces/index.js +4 -2
  44. package/cjs/interfaces/module-config.interface.js +4 -0
  45. package/cjs/interfaces/notification-adapter.interface.js +11 -0
  46. package/cjs/interfaces/permission.interface.js +1 -1
  47. package/cjs/middlewares/logger.middleware.js +133 -115
  48. package/cjs/modules/cache/cache.module.js +3 -3
  49. package/cjs/modules/datasource/index.js +0 -1
  50. package/cjs/modules/datasource/multi-tenant-datasource.service.js +60 -183
  51. package/cjs/modules/utils/utils.service.js +27 -206
  52. package/cjs/utils/date-time.util.js +94 -0
  53. package/cjs/utils/html-sanitizer.util.js +64 -0
  54. package/cjs/utils/index.js +5 -1
  55. package/cjs/utils/query-helpers.util.js +60 -0
  56. package/cjs/utils/request.util.js +96 -0
  57. package/cjs/utils/string.util.js +63 -0
  58. package/classes/api-controller.class.d.ts +16 -8
  59. package/classes/api-service.class.d.ts +30 -25
  60. package/classes/handle-custome-db.d.ts +3 -0
  61. package/classes/index.d.ts +2 -2
  62. package/classes/winston.logger.class.d.ts +1 -0
  63. package/constants/index.d.ts +2 -1
  64. package/constants/message-keys.d.ts +45 -0
  65. package/constants/permissions.d.ts +306 -0
  66. package/decorators/api-response.decorator.d.ts +1 -1
  67. package/decorators/current-user.decorator.d.ts +1 -1
  68. package/decorators/index.d.ts +1 -0
  69. package/decorators/log-action.decorator.d.ts +8 -0
  70. package/decorators/require-permission.decorator.d.ts +2 -2
  71. package/dtos/delete.dto.d.ts +1 -0
  72. package/dtos/filter-and-pagination.dto.d.ts +0 -2
  73. package/dtos/get-by-ids.dto.d.ts +4 -0
  74. package/dtos/index.d.ts +1 -0
  75. package/dtos/response-payload.dto.d.ts +8 -20
  76. package/entities/index.d.ts +1 -0
  77. package/entities/raw-type.d.ts +1 -0
  78. package/enums/index.d.ts +3 -0
  79. package/enums/notification-type.enum.d.ts +6 -0
  80. package/enums/participant-status.enum.d.ts +6 -0
  81. package/enums/recurrence-type.enum.d.ts +7 -0
  82. package/exceptions/base-app.exception.d.ts +41 -0
  83. package/exceptions/index.d.ts +1 -0
  84. package/exceptions/permission.exception.d.ts +2 -2
  85. package/fesm/classes/api-controller.class.js +326 -50
  86. package/fesm/classes/api-service.class.js +274 -152
  87. package/fesm/classes/handle-custome-db.js +19 -0
  88. package/fesm/classes/index.js +3 -2
  89. package/fesm/classes/winston.logger.class.js +100 -68
  90. package/fesm/constants/index.js +4 -1
  91. package/fesm/constants/message-keys.js +49 -0
  92. package/fesm/constants/permissions.js +200 -0
  93. package/fesm/decorators/api-response.decorator.js +3 -21
  94. package/fesm/decorators/index.js +1 -0
  95. package/fesm/decorators/log-action.decorator.js +139 -0
  96. package/fesm/decorators/require-permission.decorator.js +17 -65
  97. package/fesm/dtos/delete.dto.js +12 -2
  98. package/fesm/dtos/filter-and-pagination.dto.js +26 -47
  99. package/fesm/dtos/get-by-ids.dto.js +55 -0
  100. package/fesm/dtos/identity-response.dto.js +4 -8
  101. package/fesm/dtos/index.js +1 -0
  102. package/fesm/dtos/pagination.dto.js +4 -8
  103. package/fesm/dtos/response-payload.dto.js +68 -103
  104. package/fesm/entities/identity.js +12 -6
  105. package/fesm/entities/index.js +1 -0
  106. package/fesm/entities/raw-type.js +1 -0
  107. package/fesm/entities/user-root.js +15 -15
  108. package/fesm/enums/index.js +3 -0
  109. package/fesm/enums/notification-type.enum.js +7 -0
  110. package/fesm/enums/participant-status.enum.js +7 -0
  111. package/fesm/enums/recurrence-type.enum.js +8 -0
  112. package/fesm/exceptions/base-app.exception.js +115 -0
  113. package/fesm/exceptions/index.js +1 -0
  114. package/fesm/exceptions/permission.exception.js +16 -18
  115. package/fesm/filters/global-exception.filter.js +269 -0
  116. package/fesm/filters/index.js +1 -0
  117. package/fesm/guards/jwt-auth.guard.js +5 -2
  118. package/fesm/guards/permission.guard.js +90 -150
  119. package/fesm/index.js +2 -0
  120. package/fesm/interceptors/idempotency.interceptor.js +2 -2
  121. package/fesm/interceptors/index.js +1 -4
  122. package/fesm/interceptors/set-user-field-on-body.interceptor.js +39 -0
  123. package/fesm/interceptors/slug.interceptor.js +31 -10
  124. package/fesm/interfaces/datasource.interface.js +1 -0
  125. package/fesm/interfaces/event-manager-adapter.interface.js +1 -0
  126. package/fesm/interfaces/index.js +4 -2
  127. package/fesm/interfaces/module-config.interface.js +5 -0
  128. package/fesm/interfaces/notification-adapter.interface.js +1 -0
  129. package/fesm/interfaces/permission.interface.js +1 -3
  130. package/fesm/middlewares/logger.middleware.js +133 -109
  131. package/fesm/modules/cache/cache.module.js +2 -2
  132. package/fesm/modules/datasource/index.js +0 -1
  133. package/fesm/modules/datasource/multi-tenant-datasource.service.js +61 -184
  134. package/fesm/modules/utils/utils.service.js +27 -206
  135. package/fesm/utils/date-time.util.js +70 -0
  136. package/fesm/utils/html-sanitizer.util.js +69 -0
  137. package/fesm/utils/index.js +5 -1
  138. package/fesm/utils/query-helpers.util.js +85 -0
  139. package/fesm/utils/request.util.js +81 -0
  140. package/fesm/utils/string.util.js +71 -0
  141. package/filters/global-exception.filter.d.ts +9 -0
  142. package/filters/index.d.ts +1 -0
  143. package/guards/permission.guard.d.ts +4 -6
  144. package/index.d.ts +2 -0
  145. package/interceptors/idempotency.interceptor.d.ts +1 -1
  146. package/interceptors/index.d.ts +1 -4
  147. package/interceptors/set-user-field-on-body.interceptor.d.ts +5 -0
  148. package/interceptors/slug.interceptor.d.ts +2 -1
  149. package/interfaces/api.interface.d.ts +4 -2
  150. package/interfaces/datasource.interface.d.ts +5 -0
  151. package/interfaces/event-manager-adapter.interface.d.ts +43 -0
  152. package/interfaces/identity.interface.d.ts +4 -4
  153. package/interfaces/index.d.ts +4 -2
  154. package/interfaces/logged-user-info.interface.d.ts +0 -2
  155. package/interfaces/module-config.interface.d.ts +6 -0
  156. package/interfaces/notification-adapter.interface.d.ts +21 -0
  157. package/interfaces/permission.interface.d.ts +13 -9
  158. package/middlewares/logger.middleware.d.ts +2 -2
  159. package/modules/datasource/index.d.ts +0 -1
  160. package/modules/datasource/multi-tenant-datasource.service.d.ts +5 -16
  161. package/modules/utils/utils.service.d.ts +3 -14
  162. package/package.json +21 -14
  163. package/utils/date-time.util.d.ts +8 -0
  164. package/utils/html-sanitizer.util.d.ts +2 -0
  165. package/utils/index.d.ts +5 -1
  166. package/utils/query-helpers.util.d.ts +16 -0
  167. package/utils/request.util.d.ts +6 -0
  168. package/utils/string.util.d.ts +2 -0
  169. package/cjs/classes/request-scoped-api.service.js +0 -61
  170. package/cjs/classes/winston-logger-adapter.class.js +0 -121
  171. package/cjs/interceptors/query-performance.interceptor.js +0 -66
  172. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -40
  173. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -40
  174. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -40
  175. package/cjs/interfaces/base-query.interface.js +0 -6
  176. package/cjs/interfaces/logger.interface.js +0 -7
  177. package/cjs/modules/datasource/datasource.module.js +0 -120
  178. package/cjs/utils/error-handler.util.js +0 -67
  179. package/classes/request-scoped-api.service.d.ts +0 -13
  180. package/classes/winston-logger-adapter.class.d.ts +0 -20
  181. package/fesm/classes/request-scoped-api.service.js +0 -51
  182. package/fesm/classes/winston-logger-adapter.class.js +0 -124
  183. package/fesm/interceptors/query-performance.interceptor.js +0 -56
  184. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -30
  185. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -30
  186. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -30
  187. package/fesm/interfaces/base-query.interface.js +0 -3
  188. package/fesm/interfaces/logger.interface.js +0 -4
  189. package/fesm/modules/datasource/datasource.module.js +0 -110
  190. package/fesm/utils/error-handler.util.js +0 -59
  191. package/interceptors/query-performance.interceptor.d.ts +0 -8
  192. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -5
  193. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -5
  194. package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -5
  195. package/interfaces/base-query.interface.d.ts +0 -7
  196. package/interfaces/logger.interface.d.ts +0 -7
  197. package/modules/datasource/datasource.module.d.ts +0 -18
  198. package/utils/error-handler.util.d.ts +0 -24
@@ -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
+ }
@@ -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';
@@ -12,3 +11,7 @@ export const CLIENT_TYPE_HEADER = 'x-client-type';
12
11
  // Cache key prefixes
13
12
  export const PERMISSIONS_CACHE_PREFIX = 'permissions';
14
13
  export const IDEMPOTENCY_CACHE_PREFIX = 'idempotency';
14
+ // Permission codes
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
+ };
@@ -0,0 +1,200 @@
1
+ // ==================== AUTH MODULE ====================
2
+ export const USER_PERMISSIONS = {
3
+ CREATE: 'user.create',
4
+ READ: 'user.read',
5
+ UPDATE: 'user.update',
6
+ DELETE: 'user.delete'
7
+ };
8
+ export const COMPANY_PERMISSIONS = {
9
+ CREATE: 'company.create',
10
+ READ: 'company.read',
11
+ UPDATE: 'company.update',
12
+ DELETE: 'company.delete'
13
+ };
14
+ export const BRANCH_PERMISSIONS = {
15
+ CREATE: 'branch.create',
16
+ READ: 'branch.read',
17
+ UPDATE: 'branch.update',
18
+ DELETE: 'branch.delete'
19
+ };
20
+ // ==================== IAM MODULE ====================
21
+ export const ACTION_PERMISSIONS = {
22
+ CREATE: 'action.create',
23
+ READ: 'action.read',
24
+ UPDATE: 'action.update',
25
+ DELETE: 'action.delete'
26
+ };
27
+ export const ROLE_PERMISSIONS = {
28
+ CREATE: 'role.create',
29
+ READ: 'role.read',
30
+ UPDATE: 'role.update',
31
+ DELETE: 'role.delete'
32
+ };
33
+ export const ROLE_ACTION_PERMISSIONS = {
34
+ READ: 'role-action.read',
35
+ ASSIGN: 'role-action.assign'
36
+ };
37
+ export const USER_ROLE_PERMISSIONS = {
38
+ READ: 'user-role.read',
39
+ ASSIGN: 'user-role.assign'
40
+ };
41
+ export const USER_ACTION_PERMISSIONS = {
42
+ READ: 'user-action.read',
43
+ ASSIGN: 'user-action.assign'
44
+ };
45
+ export const COMPANY_ACTION_PERMISSIONS = {
46
+ READ: 'company-action.read',
47
+ ASSIGN: 'company-action.assign'
48
+ };
49
+ // ==================== STORAGE MODULE ====================
50
+ export const FILE_PERMISSIONS = {
51
+ CREATE: 'file.create',
52
+ READ: 'file.read',
53
+ UPDATE: 'file.update',
54
+ DELETE: 'file.delete'
55
+ };
56
+ export const FOLDER_PERMISSIONS = {
57
+ CREATE: 'folder.create',
58
+ READ: 'folder.read',
59
+ UPDATE: 'folder.update',
60
+ DELETE: 'folder.delete'
61
+ };
62
+ export const STORAGE_CONFIG_PERMISSIONS = {
63
+ CREATE: 'storage-config.create',
64
+ READ: 'storage-config.read',
65
+ UPDATE: 'storage-config.update',
66
+ DELETE: 'storage-config.delete'
67
+ };
68
+ export const UPLOAD_CONFIG_PERMISSIONS = {
69
+ DELETE: 'upload-config.delete'
70
+ };
71
+ // ==================== EMAIL MODULE ====================
72
+ export const EMAIL_CONFIG_PERMISSIONS = {
73
+ CREATE: 'email-config.create',
74
+ READ: 'email-config.read',
75
+ UPDATE: 'email-config.update',
76
+ DELETE: 'email-config.delete'
77
+ };
78
+ export const EMAIL_TEMPLATE_PERMISSIONS = {
79
+ CREATE: 'email-template.create',
80
+ READ: 'email-template.read',
81
+ UPDATE: 'email-template.update',
82
+ DELETE: 'email-template.delete'
83
+ };
84
+ // ==================== FORM BUILDER MODULE ====================
85
+ export const FORM_PERMISSIONS = {
86
+ CREATE: 'form.create',
87
+ READ: 'form.read',
88
+ UPDATE: 'form.update',
89
+ DELETE: 'form.delete'
90
+ };
91
+ export const FORM_RESULT_PERMISSIONS = {
92
+ CREATE: 'form-result.create',
93
+ READ: 'form-result.read',
94
+ UPDATE: 'form-result.update',
95
+ DELETE: 'form-result.delete'
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
+ };
163
+ // ==================== AGGREGATED EXPORTS ====================
164
+ export const PERMISSIONS = {
165
+ // Auth
166
+ USER: USER_PERMISSIONS,
167
+ COMPANY: COMPANY_PERMISSIONS,
168
+ BRANCH: BRANCH_PERMISSIONS,
169
+ // IAM
170
+ ACTION: ACTION_PERMISSIONS,
171
+ ROLE: ROLE_PERMISSIONS,
172
+ ROLE_ACTION: ROLE_ACTION_PERMISSIONS,
173
+ USER_ROLE: USER_ROLE_PERMISSIONS,
174
+ USER_ACTION: USER_ACTION_PERMISSIONS,
175
+ COMPANY_ACTION: COMPANY_ACTION_PERMISSIONS,
176
+ // Storage
177
+ FILE: FILE_PERMISSIONS,
178
+ FOLDER: FOLDER_PERMISSIONS,
179
+ STORAGE_CONFIG: STORAGE_CONFIG_PERMISSIONS,
180
+ // Email
181
+ EMAIL_CONFIG: EMAIL_CONFIG_PERMISSIONS,
182
+ EMAIL_TEMPLATE: EMAIL_TEMPLATE_PERMISSIONS,
183
+ // Form Builder
184
+ FORM: FORM_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
200
+ };
@@ -1,28 +1,10 @@
1
- import { BulkMetaDto, BulkResponseDto, ListResponseDto, PaginationMetaDto, SingleResponseDto } from '@flusys/nestjs-shared/dtos';
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;
@@ -1,4 +1,5 @@
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';
@@ -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
+ }