@flusys/nestjs-shared 1.0.0-beta → 1.0.0

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 (113) hide show
  1. package/README.md +501 -720
  2. package/cjs/classes/api-controller.class.js +9 -24
  3. package/cjs/classes/api-service.class.js +59 -92
  4. package/cjs/classes/index.js +1 -0
  5. package/cjs/classes/winston-logger-adapter.class.js +23 -40
  6. package/cjs/constants/index.js +14 -0
  7. package/cjs/constants/permissions.js +184 -0
  8. package/cjs/decorators/api-response.decorator.js +1 -1
  9. package/cjs/decorators/index.js +1 -0
  10. package/cjs/decorators/sanitize-html.decorator.js +36 -0
  11. package/cjs/dtos/delete.dto.js +10 -0
  12. package/cjs/dtos/filter-and-pagination.dto.js +24 -34
  13. package/cjs/dtos/pagination.dto.js +4 -8
  14. package/cjs/dtos/response-payload.dto.js +0 -116
  15. package/cjs/entities/identity.js +4 -4
  16. package/cjs/entities/user-root.js +13 -14
  17. package/cjs/guards/permission.guard.js +51 -105
  18. package/cjs/interceptors/index.js +1 -3
  19. package/cjs/interceptors/set-user-field-on-body.interceptor.js +60 -0
  20. package/cjs/interceptors/slug.interceptor.js +30 -9
  21. package/cjs/interfaces/datasource.interface.js +4 -0
  22. package/cjs/interfaces/index.js +2 -1
  23. package/cjs/interfaces/module-config.interface.js +4 -0
  24. package/cjs/middlewares/logger.middleware.js +50 -89
  25. package/cjs/modules/cache/cache.module.js +3 -3
  26. package/cjs/modules/datasource/datasource.module.js +11 -14
  27. package/cjs/modules/datasource/multi-tenant-datasource.service.js +29 -113
  28. package/cjs/modules/utils/utils.service.js +40 -203
  29. package/cjs/utils/error-handler.util.js +35 -12
  30. package/cjs/utils/html-sanitizer.util.js +64 -0
  31. package/cjs/utils/index.js +4 -0
  32. package/cjs/utils/query-helpers.util.js +53 -0
  33. package/cjs/utils/request.util.js +70 -0
  34. package/cjs/utils/string.util.js +63 -0
  35. package/classes/api-controller.class.d.ts +5 -5
  36. package/classes/api-service.class.d.ts +7 -5
  37. package/classes/index.d.ts +1 -0
  38. package/classes/request-scoped-api.service.d.ts +3 -2
  39. package/classes/winston-logger-adapter.class.d.ts +2 -0
  40. package/constants/index.d.ts +1 -0
  41. package/constants/permissions.d.ts +179 -0
  42. package/decorators/index.d.ts +1 -0
  43. package/decorators/sanitize-html.decorator.d.ts +2 -0
  44. package/dtos/delete.dto.d.ts +1 -0
  45. package/dtos/filter-and-pagination.dto.d.ts +0 -2
  46. package/dtos/response-payload.dto.d.ts +0 -20
  47. package/fesm/classes/api-controller.class.js +9 -24
  48. package/fesm/classes/api-service.class.js +59 -92
  49. package/fesm/classes/index.js +2 -0
  50. package/fesm/classes/winston-logger-adapter.class.js +23 -40
  51. package/fesm/constants/index.js +2 -0
  52. package/fesm/constants/permissions.js +128 -0
  53. package/fesm/decorators/api-response.decorator.js +1 -1
  54. package/fesm/decorators/index.js +1 -0
  55. package/fesm/decorators/sanitize-html.decorator.js +45 -0
  56. package/fesm/dtos/delete.dto.js +12 -2
  57. package/fesm/dtos/filter-and-pagination.dto.js +26 -47
  58. package/fesm/dtos/pagination.dto.js +4 -8
  59. package/fesm/dtos/response-payload.dto.js +0 -107
  60. package/fesm/entities/identity.js +4 -4
  61. package/fesm/entities/user-root.js +13 -14
  62. package/fesm/guards/permission.guard.js +51 -105
  63. package/fesm/interceptors/index.js +1 -3
  64. package/fesm/interceptors/set-user-field-on-body.interceptor.js +39 -0
  65. package/fesm/interceptors/slug.interceptor.js +31 -10
  66. package/fesm/interfaces/datasource.interface.js +20 -0
  67. package/fesm/interfaces/index.js +2 -1
  68. package/fesm/interfaces/module-config.interface.js +5 -0
  69. package/fesm/middlewares/logger.middleware.js +50 -83
  70. package/fesm/modules/cache/cache.module.js +2 -2
  71. package/fesm/modules/datasource/datasource.module.js +11 -14
  72. package/fesm/modules/datasource/multi-tenant-datasource.service.js +29 -113
  73. package/fesm/modules/utils/utils.service.js +41 -204
  74. package/fesm/utils/error-handler.util.js +36 -13
  75. package/fesm/utils/html-sanitizer.util.js +69 -0
  76. package/fesm/utils/index.js +4 -0
  77. package/fesm/utils/query-helpers.util.js +78 -0
  78. package/fesm/utils/request.util.js +58 -0
  79. package/fesm/utils/string.util.js +71 -0
  80. package/guards/permission.guard.d.ts +2 -0
  81. package/interceptors/index.d.ts +1 -3
  82. package/interceptors/set-user-field-on-body.interceptor.d.ts +5 -0
  83. package/interceptors/slug.interceptor.d.ts +2 -1
  84. package/interfaces/api.interface.d.ts +2 -2
  85. package/interfaces/datasource.interface.d.ts +5 -0
  86. package/interfaces/identity.interface.d.ts +4 -4
  87. package/interfaces/index.d.ts +2 -1
  88. package/interfaces/logged-user-info.interface.d.ts +0 -2
  89. package/interfaces/module-config.interface.d.ts +6 -0
  90. package/interfaces/permission.interface.d.ts +0 -1
  91. package/middlewares/logger.middleware.d.ts +2 -2
  92. package/modules/datasource/datasource.module.d.ts +1 -0
  93. package/modules/datasource/multi-tenant-datasource.service.d.ts +0 -1
  94. package/modules/utils/utils.service.d.ts +4 -14
  95. package/package.json +4 -4
  96. package/utils/error-handler.util.d.ts +14 -19
  97. package/utils/html-sanitizer.util.d.ts +2 -0
  98. package/utils/index.d.ts +4 -0
  99. package/utils/query-helpers.util.d.ts +16 -0
  100. package/utils/request.util.d.ts +4 -0
  101. package/utils/string.util.d.ts +2 -0
  102. package/cjs/interceptors/set-create-by-on-body.interceptor.js +0 -40
  103. package/cjs/interceptors/set-delete-by-on-body.interceptor.js +0 -40
  104. package/cjs/interceptors/set-update-by-on-body.interceptor.js +0 -40
  105. package/cjs/interfaces/base-query.interface.js +0 -6
  106. package/fesm/interceptors/set-create-by-on-body.interceptor.js +0 -30
  107. package/fesm/interceptors/set-delete-by-on-body.interceptor.js +0 -30
  108. package/fesm/interceptors/set-update-by-on-body.interceptor.js +0 -30
  109. package/fesm/interfaces/base-query.interface.js +0 -3
  110. package/interceptors/set-create-by-on-body.interceptor.d.ts +0 -5
  111. package/interceptors/set-delete-by-on-body.interceptor.d.ts +0 -5
  112. package/interceptors/set-update-by-on-body.interceptor.d.ts +0 -5
  113. package/interfaces/base-query.interface.d.ts +0 -7
@@ -35,106 +35,81 @@ import { ILogger } from '../interfaces/logger.interface';
35
35
  import { PermissionGuardConfig } from '../interfaces/permission.interface';
36
36
  export class PermissionGuard {
37
37
  async canActivate(context) {
38
- // Check if route is marked as public
39
38
  const isPublic = this.reflector.getAllAndOverride(IS_PUBLIC_KEY, [
40
39
  context.getHandler(),
41
40
  context.getClass()
42
41
  ]);
43
42
  if (isPublic) return true;
44
- // Get required permissions from decorator
45
43
  const permissionConfig = this.reflector.getAllAndOverride(PERMISSIONS_KEY, [
46
44
  context.getHandler(),
47
45
  context.getClass()
48
46
  ]);
49
- // If no permissions required, allow access
50
- if (!permissionConfig) {
51
- return true;
52
- }
53
- // Normalize permission config (support old format: string[])
47
+ if (!permissionConfig) return true;
54
48
  const { permissions: requiredPermissions, operator } = this.normalizePermissionConfig(permissionConfig);
55
- // If no permissions required, allow access
56
- if (!requiredPermissions || requiredPermissions.length === 0) {
57
- return true;
58
- }
49
+ if (!requiredPermissions || requiredPermissions.length === 0) return true;
59
50
  const request = context.switchToHttp().getRequest();
60
51
  const user = request.user;
61
- // User must be authenticated
62
- if (!user) {
63
- throw new UnauthorizedException('Authentication required');
64
- }
65
- // Cache is required for permission checks - fail securely if unavailable
52
+ if (!user) throw new UnauthorizedException('Authentication required');
66
53
  if (!this.cache) {
67
- // Log error (in production, this should be monitored)
68
- this.logger.error(`Cache not available - permission system unavailable (userId: ${user.id})`, undefined, 'PermissionGuard');
69
- // Fail securely - deny access rather than allowing without permission check
54
+ this.logger.error(`Cache not available (userId: ${user.id})`, undefined, 'PermissionGuard');
70
55
  throw new PermissionSystemUnavailableException();
71
56
  }
72
- // Get user's permissions from cache
73
57
  const userPermissions = await this.getUserPermissions(user);
74
- // If no permissions found in cache, deny access
75
58
  if (!userPermissions || userPermissions.length === 0) {
76
- this.logger.warn(`No permissions found for user (userId: ${user.id})`, 'PermissionGuard');
59
+ this.logger.warn(`No permissions found (userId: ${user.id})`, 'PermissionGuard');
77
60
  throw new NoPermissionsFoundException();
78
61
  }
79
- // Check if this is a nested condition or simple permission list
80
62
  if (this.isNestedCondition(permissionConfig)) {
81
- // Complex nested permission check
82
63
  const result = this.evaluateCondition(permissionConfig, userPermissions);
83
64
  if (!result.passed) {
84
- this.logger.warn(`Permission check failed (userId: ${user.id}, missing: ${result.missingPermissions.join(', ')})`, 'PermissionGuard');
65
+ this.logger.warn(`Permission denied (userId: ${user.id})`, 'PermissionGuard');
85
66
  throw new InsufficientPermissionsException(result.missingPermissions, result.operator);
86
67
  }
87
68
  } else {
88
- // Simple permission check (backward compatible)
89
- let hasRequiredPermissions;
90
- if (operator === 'or') {
91
- // OR: User must have at least ONE permission
92
- hasRequiredPermissions = requiredPermissions.some((permission)=>this.hasPermission(userPermissions, permission));
93
- if (!hasRequiredPermissions) {
94
- throw new InsufficientPermissionsException(requiredPermissions, 'or');
95
- }
96
- } else {
97
- // AND (default): User must have ALL permissions
98
- hasRequiredPermissions = requiredPermissions.every((permission)=>this.hasPermission(userPermissions, permission));
99
- if (!hasRequiredPermissions) {
100
- const missing = requiredPermissions.filter((permission)=>!this.hasPermission(userPermissions, permission));
101
- throw new InsufficientPermissionsException(missing, 'and');
102
- }
103
- }
69
+ this.validateSimplePermissions(requiredPermissions, userPermissions, operator);
104
70
  }
105
71
  return true;
106
72
  }
107
- /**
108
- * Normalize permission config to handle both old and new formats
109
- */ normalizePermissionConfig(config) {
110
- // Old format: string[]
111
- if (Array.isArray(config)) {
112
- return {
113
- permissions: config,
114
- operator: 'and'
115
- };
116
- }
117
- // New format: PermissionConfig
73
+ normalizePermissionConfig(config) {
74
+ if (Array.isArray(config)) return {
75
+ permissions: config,
76
+ operator: 'and'
77
+ };
118
78
  return {
119
79
  permissions: config.permissions || [],
120
80
  operator: config.operator || 'and'
121
81
  };
122
82
  }
123
- /**
124
- * Check if config is a nested condition (has children)
125
- */ isNestedCondition(config) {
83
+ validateSimplePermissions(requiredPermissions, userPermissions, operator) {
84
+ if (operator === 'or') {
85
+ const hasAny = requiredPermissions.some((p)=>this.hasPermission(userPermissions, p));
86
+ if (!hasAny) throw new InsufficientPermissionsException(requiredPermissions, 'or');
87
+ } else {
88
+ const hasAll = requiredPermissions.every((p)=>this.hasPermission(userPermissions, p));
89
+ if (!hasAll) {
90
+ const missing = requiredPermissions.filter((p)=>!this.hasPermission(userPermissions, p));
91
+ throw new InsufficientPermissionsException(missing, 'and');
92
+ }
93
+ }
94
+ }
95
+ isNestedCondition(config) {
126
96
  if (Array.isArray(config)) return false;
127
97
  return 'children' in config && Array.isArray(config.children) && config.children.length > 0;
128
98
  }
129
- /**
130
- * Evaluate a nested permission condition recursively
131
- */ evaluateCondition(condition, userPermissions) {
99
+ evaluateCondition(condition, userPermissions) {
132
100
  const { permissions = [], operator, children = [] } = condition;
133
- // Results for this level
101
+ // SECURITY: Fail-closed - deny access when no permissions configured (empty condition)
102
+ if (permissions.length === 0 && children.length === 0) {
103
+ return {
104
+ passed: false,
105
+ message: 'No permissions configured - access denied by default',
106
+ missingPermissions: [],
107
+ operator
108
+ };
109
+ }
134
110
  const results = [];
135
111
  const failureDetails = [];
136
112
  const missingPermissions = [];
137
- // Check permissions at this level
138
113
  if (permissions.length > 0) {
139
114
  if (operator === 'or') {
140
115
  const hasAny = permissions.some((p)=>this.hasPermission(userPermissions, p));
@@ -153,7 +128,6 @@ export class PermissionGuard {
153
128
  }
154
129
  }
155
130
  }
156
- // Evaluate children recursively
157
131
  for (const child of children){
158
132
  const childResult = this.evaluateCondition(child, userPermissions);
159
133
  results.push(childResult.passed);
@@ -162,14 +136,9 @@ export class PermissionGuard {
162
136
  missingPermissions.push(...childResult.missingPermissions);
163
137
  }
164
138
  }
165
- // Combine results based on operator
166
- let passed;
167
- if (operator === 'or') {
168
- passed = results.length === 0 || results.some((r)=>r);
169
- } else {
170
- passed = results.length === 0 || results.every((r)=>r);
171
- }
172
- const message = passed ? 'Permission granted' : `Permission denied: ${failureDetails.join(` ${operator.toUpperCase()} `)}`;
139
+ // Evaluate based on operator - empty results already handled above
140
+ const passed = operator === 'or' ? results.some((r)=>r) : results.every((r)=>r);
141
+ const message = passed ? 'OK' : `Denied: ${failureDetails.join(` ${operator.toUpperCase()} `)}`;
173
142
  return {
174
143
  passed,
175
144
  message,
@@ -177,48 +146,27 @@ export class PermissionGuard {
177
146
  operator
178
147
  };
179
148
  }
180
- /**
181
- * Get user's permissions from cache
182
- */ async getUserPermissions(user) {
183
- if (!this.cache) {
184
- throw new PermissionSystemUnavailableException();
185
- }
186
- let cacheKey;
149
+ async getUserPermissions(user) {
150
+ if (!this.cache) throw new PermissionSystemUnavailableException();
151
+ const cacheKey = this.buildPermissionCacheKey(user);
152
+ return await this.cache.get(cacheKey) || [];
153
+ }
154
+ buildPermissionCacheKey(user) {
187
155
  if (this.config.enableCompanyFeature && user.companyId) {
188
- // Company-based permissions (includes branchId for branch-scoped DIRECT permissions)
189
- const format = this.config.companyPermissionKeyFormat || `${PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`;
190
- cacheKey = format.replace('{userId}', user.id).replace('{companyId}', user.companyId).replace('{branchId}', user.branchId || 'null');
191
- } else {
192
- // User-based permissions
193
- const format = this.config.userPermissionKeyFormat || `${PERMISSIONS_CACHE_PREFIX}:user:{userId}`;
194
- cacheKey = format.replace('{userId}', user.id);
156
+ return (this.config.companyPermissionKeyFormat || `${PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`).replace('{userId}', user.id).replace('{companyId}', user.companyId).replace('{branchId}', user.branchId || 'null');
195
157
  }
196
- const permissions = await this.cache.get(cacheKey);
197
- return permissions || [];
158
+ return (this.config.userPermissionKeyFormat || `${PERMISSIONS_CACHE_PREFIX}:user:{userId}`).replace('{userId}', user.id);
198
159
  }
199
- /**
200
- * Check if user has a specific permission
201
- * Supports wildcard matching (e.g., 'admin.*' matches 'admin.users.read')
202
- */ hasPermission(userPermissions, requiredPermission) {
203
- // Direct match
204
- if (userPermissions.includes(requiredPermission)) {
205
- return true;
206
- }
207
- // Wildcard match (e.g., '*' or 'admin.*')
160
+ hasPermission(userPermissions, requiredPermission) {
161
+ if (userPermissions.includes(requiredPermission)) return true;
208
162
  for (const permission of userPermissions){
209
- if (permission === '*') {
210
- return true; // Super admin
211
- }
212
- if (permission.endsWith('.*')) {
213
- const prefix = permission.slice(0, -1); // Remove '*'
214
- if (requiredPermission.startsWith(prefix)) {
215
- return true;
216
- }
163
+ if (permission === '*') return true;
164
+ if (permission.endsWith('.*') && requiredPermission.startsWith(permission.slice(0, -1))) {
165
+ return true;
217
166
  }
218
167
  }
219
168
  return false;
220
169
  }
221
- // NOTE: @Inject(Reflector) required for bundled code - external classes need explicit injection
222
170
  constructor(reflector, cache, config, logger){
223
171
  _define_property(this, "reflector", void 0);
224
172
  _define_property(this, "cache", void 0);
@@ -228,12 +176,10 @@ export class PermissionGuard {
228
176
  this.cache = cache;
229
177
  this.config = {
230
178
  enableCompanyFeature: false,
231
- cacheKeyPrefix: PERMISSIONS_CACHE_PREFIX,
232
179
  userPermissionKeyFormat: `${PERMISSIONS_CACHE_PREFIX}:user:{userId}`,
233
180
  companyPermissionKeyFormat: `${PERMISSIONS_CACHE_PREFIX}:company:{companyId}:branch:{branchId}:user:{userId}`,
234
181
  ...config
235
182
  };
236
- // Use provided logger or fallback to NestJS Logger wrapped in adapter
237
183
  this.logger = logger || new NestLoggerAdapter(new Logger(PermissionGuard.name));
238
184
  }
239
185
  }
@@ -2,7 +2,5 @@ export * from './delete-empty-id-from-body.interceptor';
2
2
  export * from './idempotency.interceptor';
3
3
  export * from './query-performance.interceptor';
4
4
  export * from './response-meta.interceptor';
5
- export * from './set-create-by-on-body.interceptor';
6
- export * from './set-delete-by-on-body.interceptor';
7
- export * from './set-update-by-on-body.interceptor';
5
+ export * from './set-user-field-on-body.interceptor';
8
6
  export * from './slug.interceptor';
@@ -0,0 +1,39 @@
1
+ function _ts_decorate(decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ }
7
+ import { Injectable } from '@nestjs/common';
8
+ /**
9
+ * Factory function to create interceptors that set user ID fields on request body
10
+ * @param fieldName - The field name to set (e.g., 'createdById', 'updatedById', 'deletedById')
11
+ */ export function createSetUserFieldInterceptor(fieldName) {
12
+ let SetUserFieldOnBody = class SetUserFieldOnBody {
13
+ intercept(context, next) {
14
+ const request = context.switchToHttp().getRequest();
15
+ const user = request?.user;
16
+ if (user) {
17
+ if (Array.isArray(request.body)) {
18
+ request.body = request.body.map((item)=>({
19
+ ...item,
20
+ [fieldName]: user.id
21
+ }));
22
+ } else if (typeof request.body === 'object' && request.body !== null) {
23
+ request.body = {
24
+ ...request.body,
25
+ [fieldName]: user.id
26
+ };
27
+ }
28
+ }
29
+ return next.handle();
30
+ }
31
+ };
32
+ SetUserFieldOnBody = _ts_decorate([
33
+ Injectable()
34
+ ], SetUserFieldOnBody);
35
+ return SetUserFieldOnBody;
36
+ }
37
+ /** Sets createdById field on request body from authenticated user */ export const SetCreatedByOnBody = createSetUserFieldInterceptor('createdById');
38
+ /** Sets updatedById field on request body from authenticated user */ export const SetUpdateByOnBody = createSetUserFieldInterceptor('updatedById');
39
+ /** Sets deletedById field on request body from authenticated user */ export const SetDeletedByOnBody = createSetUserFieldInterceptor('deletedById');
@@ -17,28 +17,49 @@ function _ts_decorate(decorators, target, key, desc) {
17
17
  else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
18
18
  return c > 3 && r && Object.defineProperty(target, key, r), r;
19
19
  }
20
- import { Injectable } from '@nestjs/common';
20
+ function _ts_metadata(k, v) {
21
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
22
+ }
23
+ function _ts_param(paramIndex, decorator) {
24
+ return function(target, key) {
25
+ decorator(target, key, paramIndex);
26
+ };
27
+ }
28
+ import { Inject, Injectable } from '@nestjs/common';
21
29
  import { UtilsService } from '../modules/utils/utils.service';
22
30
  export class Slug {
23
31
  intercept(context, next) {
24
32
  const request = context.switchToHttp().getRequest();
25
33
  if (Array.isArray(request.body)) {
26
- request.body = request.body.map((item)=>({
27
- ...item,
28
- slug: item.slug || (item?.name ? this.utilsService.transformToSlug(item.name) : null)
29
- }));
30
- } else if (!request.body.slug) {
34
+ request.body = request.body.map((item)=>{
35
+ // Only generate slug if not provided and name exists
36
+ if (!item.slug && item?.name) {
37
+ return {
38
+ ...item,
39
+ slug: this.utilsService.transformToSlug(item.name)
40
+ };
41
+ }
42
+ return item;
43
+ });
44
+ } else if (!request.body?.slug && request.body?.name) {
45
+ // Only generate slug if not provided and name exists
31
46
  request.body = {
32
47
  ...request.body,
33
- slug: request.body?.name ? this.utilsService.transformToSlug(request.body?.name) : null
48
+ slug: this.utilsService.transformToSlug(request.body.name)
34
49
  };
35
50
  }
36
51
  return next.handle();
37
52
  }
38
- constructor(){
39
- _define_property(this, "utilsService", new UtilsService());
53
+ constructor(utilsService){
54
+ _define_property(this, "utilsService", void 0);
55
+ this.utilsService = utilsService;
40
56
  }
41
57
  }
42
58
  Slug = _ts_decorate([
43
- Injectable()
59
+ Injectable(),
60
+ _ts_param(0, Inject(UtilsService)),
61
+ _ts_metadata("design:type", Function),
62
+ _ts_metadata("design:paramtypes", [
63
+ typeof UtilsService === "undefined" ? Object : UtilsService
64
+ ])
44
65
  ], Slug);
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Interface for DataSource providers used by RequestScopedApiService.
3
+ * Implement this interface in each module's DataSourceProvider to ensure type safety.
4
+ *
5
+ * @example
6
+ * ```typescript
7
+ * @Injectable()
8
+ * export class IAMDataSourceProvider implements IDataSourceProvider {
9
+ * constructor(private readonly dataSource: DataSource) {}
10
+ *
11
+ * async getRepository<T extends ObjectLiteral>(entity: EntityTarget<T>): Promise<Repository<T>> {
12
+ * return this.dataSource.getRepository(entity);
13
+ * }
14
+ *
15
+ * async getDataSource(): Promise<DataSource> {
16
+ * return this.dataSource;
17
+ * }
18
+ * }
19
+ * ```
20
+ */ export { };
@@ -1,6 +1,7 @@
1
1
  export * from './api.interface';
2
- export * from './base-query.interface';
2
+ export * from './datasource.interface';
3
3
  export * from './identity.interface';
4
4
  export * from './logged-user-info.interface';
5
5
  export * from './logger.interface';
6
+ export * from './module-config.interface';
6
7
  export * from './permission.interface';
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Common interface for module configuration services.
3
+ * All feature module config services should implement this interface
4
+ * to ensure consistent access to bootstrap configuration.
5
+ */ export { };
@@ -43,14 +43,6 @@ export const getRequestId = ()=>requestContext.getStore()?.requestId;
43
43
  export const getTenantId = ()=>requestContext.getStore()?.tenantId;
44
44
  export const getUserId = ()=>requestContext.getStore()?.userId;
45
45
  export const getCompanyId = ()=>requestContext.getStore()?.companyId;
46
- export const setUserId = (userId)=>{
47
- const store = requestContext.getStore();
48
- if (store) store.userId = userId;
49
- };
50
- export const setCompanyId = (companyId)=>{
51
- const store = requestContext.getStore();
52
- if (store) store.companyId = companyId;
53
- };
54
46
  // Helper Functions
55
47
  function sanitizeHeaders(headers) {
56
48
  const sanitized = {};
@@ -72,84 +64,62 @@ export class LoggerMiddleware {
72
64
  const requestId = req.headers[REQUEST_ID_HEADER] || uuidv4();
73
65
  const tenantId = req.headers[TENANT_ID_HEADER];
74
66
  const startTime = Date.now();
75
- // Set response header
76
67
  res.setHeader(REQUEST_ID_HEADER, requestId);
77
- // Create context
78
68
  const context = {
79
69
  requestId,
80
70
  tenantId,
81
71
  startTime
82
72
  };
83
- // Run in AsyncLocalStorage context
84
73
  requestContext.run(context, ()=>{
85
- // Check if we should skip logging
86
74
  const shouldSkipLogging = EXCLUDED_PATHS.some((path)=>req.originalUrl.startsWith(path));
87
75
  if (!shouldSkipLogging) {
88
76
  this.logRequest(req, requestId, tenantId);
89
- // Track if response was already logged
90
- let responseLogged = false;
91
- // Capture response using multiple hooks to ensure we catch it
92
- const originalSend = res.send;
93
- const originalJson = res.json;
94
- const originalEnd = res.end;
95
- // Store reference to this for use in callbacks
96
- const self = this;
97
- // Override res.send
98
- res.send = function(body) {
99
- if (!responseLogged) {
100
- responseLogged = true;
101
- self.logResponse(req, res, startTime, body, requestId, tenantId);
102
- }
103
- return originalSend.call(this, body);
104
- };
105
- // Override res.json
106
- res.json = function(body) {
107
- if (!responseLogged) {
108
- responseLogged = true;
109
- self.logResponse(req, res, startTime, body, requestId, tenantId);
110
- }
111
- return originalJson.call(this, body);
112
- };
113
- // Override res.end as fallback
114
- res.end = function(...args) {
115
- if (!responseLogged) {
116
- responseLogged = true;
117
- self.logResponse(req, res, startTime, args[0], requestId, tenantId);
118
- }
119
- return originalEnd.apply(this, args);
120
- };
121
- // Handle errors
122
- res.on('error', (error)=>{
123
- this.logger.error('Response error', {
124
- context: 'HTTP',
125
- requestId,
126
- tenantId,
127
- method: req.method,
128
- url: req.originalUrl,
129
- path: req.path,
130
- error: error.message,
131
- stack: error.stack
132
- });
133
- });
77
+ this.setupResponseLogging(req, res, startTime, requestId, tenantId);
134
78
  }
135
79
  next();
136
80
  });
137
81
  }
82
+ setupResponseLogging(req, res, startTime, requestId, tenantId) {
83
+ let responseLogged = false;
84
+ const originalSend = res.send;
85
+ const originalJson = res.json;
86
+ const originalEnd = res.end;
87
+ const self = this;
88
+ const logOnce = (body)=>{
89
+ if (!responseLogged) {
90
+ responseLogged = true;
91
+ self.logResponse(req, res, startTime, body, requestId, tenantId);
92
+ }
93
+ };
94
+ res.send = function(body) {
95
+ logOnce(body);
96
+ return originalSend.call(this, body);
97
+ };
98
+ res.json = function(body) {
99
+ logOnce(body);
100
+ return originalJson.call(this, body);
101
+ };
102
+ res.end = function(...args) {
103
+ logOnce(args[0]);
104
+ return originalEnd.apply(this, args);
105
+ };
106
+ res.on('error', (error)=>{
107
+ this.logger.error('Response error', {
108
+ ...this.buildBaseLogData(req, requestId, tenantId),
109
+ error: error.message,
110
+ stack: error.stack
111
+ });
112
+ });
113
+ }
138
114
  logRequest(req, requestId, tenantId) {
139
115
  const logData = {
140
- context: 'HTTP',
141
- requestId,
142
- tenantId,
143
- method: req.method,
144
- url: req.originalUrl,
145
- path: req.path,
116
+ ...this.buildBaseLogData(req, requestId, tenantId),
146
117
  query: Object.keys(req.query).length > 0 ? req.query : undefined,
147
118
  ip: getClientIp(req),
148
119
  userAgent: req.headers['user-agent'],
149
120
  contentType: req.headers['content-type'],
150
121
  contentLength: req.headers['content-length']
151
122
  };
152
- // Add debug details if enabled
153
123
  if (IS_DEBUG) {
154
124
  logData.headers = sanitizeHeaders(req.headers);
155
125
  logData.body = truncateBody(req.body);
@@ -161,13 +131,12 @@ export class LoggerMiddleware {
161
131
  const duration = Date.now() - startTime;
162
132
  const statusCode = res.statusCode;
163
133
  const level = statusCode >= 500 ? 'error' : statusCode >= 400 ? 'warn' : 'info';
134
+ const userId = getUserId();
135
+ const companyId = getCompanyId();
164
136
  const logData = {
165
- context: 'HTTP',
166
- requestId,
167
- tenantId,
168
- method: req.method,
169
- url: req.originalUrl,
170
- path: req.path,
137
+ ...this.buildBaseLogData(req, requestId, tenantId),
138
+ userId,
139
+ companyId,
171
140
  statusCode,
172
141
  statusMessage: res.statusMessage,
173
142
  duration: `${duration}ms`,
@@ -175,33 +144,31 @@ export class LoggerMiddleware {
175
144
  contentType: res.getHeader('content-type'),
176
145
  contentLength: res.getHeader('content-length')
177
146
  };
178
- // Add user context if available
179
- const userId = getUserId();
180
- const companyId = getCompanyId();
181
- if (userId) logData.userId = userId;
182
- if (companyId) logData.companyId = companyId;
183
- // Add response body for errors or debug mode
184
147
  if (statusCode >= 400 || IS_DEBUG) {
185
148
  logData.responseBody = truncateBody(body);
186
149
  }
187
150
  this.logger.log(level, `Response [${statusCode}]`, logData);
188
- // Log slow requests separately
189
151
  if (duration > 3000 && statusCode < 400) {
190
152
  this.logger.warn('Slow request detected', {
191
- context: 'HTTP',
192
- requestId,
193
- tenantId,
153
+ ...this.buildBaseLogData(req, requestId, tenantId),
194
154
  userId,
195
155
  companyId,
196
- method: req.method,
197
- url: req.originalUrl,
198
- path: req.path,
199
156
  duration: `${duration}ms`,
200
157
  durationMs: duration,
201
158
  threshold: '3000ms'
202
159
  });
203
160
  }
204
161
  }
162
+ buildBaseLogData(req, requestId, tenantId) {
163
+ return {
164
+ context: 'HTTP',
165
+ requestId,
166
+ tenantId,
167
+ method: req.method,
168
+ url: req.originalUrl,
169
+ path: req.path
170
+ };
171
+ }
205
172
  constructor(){
206
173
  _define_property(this, "logger", winstonLogger);
207
174
  }
@@ -4,8 +4,8 @@ function _ts_decorate(decorators, target, key, desc) {
4
4
  else for(var i = decorators.length - 1; i >= 0; i--)if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
5
  return c > 3 && r && Object.defineProperty(target, key, r), r;
6
6
  }
7
- import { HybridCache } from '@flusys/nestjs-shared/classes';
8
- import { CACHE_INSTANCE } from '@flusys/nestjs-shared/constants';
7
+ import { HybridCache } from '../../classes/hybrid-cache.class';
8
+ import { CACHE_INSTANCE } from '../../constants';
9
9
  import { Module } from '@nestjs/common';
10
10
  export class CacheModule {
11
11
  static forRoot(isGlobal = true, memoryTtl = 60_000, memorySize = 5000) {
@@ -77,24 +77,12 @@ export class DataSourceModule {
77
77
  provide: options.useClass,
78
78
  useClass: options.useClass
79
79
  },
80
- {
81
- provide: MODULE_OPTIONS,
82
- useFactory: async (factory)=>factory.createOptions(),
83
- inject: [
84
- options.useClass
85
- ]
86
- }
80
+ this.createFactoryProvider(options.useClass)
87
81
  ];
88
82
  }
89
83
  if (options.useExisting) {
90
84
  return [
91
- {
92
- provide: MODULE_OPTIONS,
93
- useFactory: async (factory)=>factory.createOptions(),
94
- inject: [
95
- options.useExisting
96
- ]
97
- }
85
+ this.createFactoryProvider(options.useExisting)
98
86
  ];
99
87
  }
100
88
  return [
@@ -104,6 +92,15 @@ export class DataSourceModule {
104
92
  }
105
93
  ];
106
94
  }
95
+ static createFactoryProvider(factoryClass) {
96
+ return {
97
+ provide: MODULE_OPTIONS,
98
+ useFactory: async (factory)=>factory.createOptions(),
99
+ inject: [
100
+ factoryClass
101
+ ]
102
+ };
103
+ }
107
104
  }
108
105
  DataSourceModule = _ts_decorate([
109
106
  Module({})