@flusys/nestjs-iam 1.0.0-rc → 2.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 (63) hide show
  1. package/README.md +219 -118
  2. package/cjs/controllers/company-action-permission.controller.js +2 -17
  3. package/cjs/controllers/my-permission.controller.js +1 -2
  4. package/cjs/controllers/role-permission.controller.js +3 -9
  5. package/cjs/controllers/user-action-permission.controller.js +3 -9
  6. package/cjs/dtos/action.dto.js +0 -27
  7. package/cjs/dtos/permission.dto.js +81 -27
  8. package/cjs/dtos/role.dto.js +0 -27
  9. package/cjs/helpers/company-access.helper.js +19 -0
  10. package/cjs/helpers/index.js +1 -1
  11. package/cjs/interfaces/iam-module-options.interface.js +0 -14
  12. package/cjs/interfaces/index.js +0 -1
  13. package/cjs/modules/iam.module.js +38 -106
  14. package/cjs/services/action.service.js +30 -41
  15. package/cjs/services/iam-config.service.js +2 -5
  16. package/cjs/services/{iam-datasource.provider.js → iam-datasource.service.js} +33 -36
  17. package/cjs/services/index.js +1 -1
  18. package/cjs/services/permission-cache.service.js +6 -46
  19. package/cjs/services/permission.service.js +52 -41
  20. package/cjs/services/role.service.js +3 -3
  21. package/controllers/company-action-permission.controller.d.ts +2 -5
  22. package/controllers/role-permission.controller.d.ts +0 -1
  23. package/controllers/user-action-permission.controller.d.ts +0 -1
  24. package/dtos/action.dto.d.ts +0 -4
  25. package/dtos/role.dto.d.ts +0 -4
  26. package/fesm/controllers/company-action-permission.controller.js +4 -19
  27. package/fesm/controllers/my-permission.controller.js +1 -2
  28. package/fesm/controllers/role-permission.controller.js +4 -10
  29. package/fesm/controllers/user-action-permission.controller.js +4 -10
  30. package/fesm/dtos/action.dto.js +0 -24
  31. package/fesm/dtos/permission.dto.js +81 -27
  32. package/fesm/dtos/role.dto.js +0 -24
  33. package/fesm/helpers/company-access.helper.js +14 -0
  34. package/fesm/helpers/index.js +1 -1
  35. package/fesm/interfaces/iam-module-options.interface.js +3 -1
  36. package/fesm/interfaces/index.js +0 -1
  37. package/fesm/modules/iam.module.js +40 -108
  38. package/fesm/services/action.service.js +31 -42
  39. package/fesm/services/iam-config.service.js +2 -5
  40. package/fesm/services/{iam-datasource.provider.js → iam-datasource.service.js} +31 -34
  41. package/fesm/services/index.js +1 -1
  42. package/fesm/services/permission-cache.service.js +6 -46
  43. package/fesm/services/permission.service.js +53 -42
  44. package/fesm/services/role.service.js +3 -3
  45. package/helpers/company-access.helper.d.ts +3 -0
  46. package/helpers/index.d.ts +1 -1
  47. package/interfaces/iam-module-options.interface.d.ts +9 -1
  48. package/interfaces/index.d.ts +0 -1
  49. package/modules/iam.module.d.ts +1 -2
  50. package/package.json +3 -3
  51. package/services/action.service.d.ts +6 -4
  52. package/services/iam-config.service.d.ts +0 -1
  53. package/services/{iam-datasource.provider.d.ts → iam-datasource.service.d.ts} +4 -5
  54. package/services/index.d.ts +1 -1
  55. package/services/permission-cache.service.d.ts +1 -4
  56. package/services/permission.service.d.ts +4 -2
  57. package/services/role.service.d.ts +3 -3
  58. package/cjs/helpers/permission-evaluator.helper.js +0 -175
  59. package/cjs/interfaces/iam-module-async-options.interface.js +0 -4
  60. package/fesm/helpers/permission-evaluator.helper.js +0 -165
  61. package/fesm/interfaces/iam-module-async-options.interface.js +0 -3
  62. package/helpers/permission-evaluator.helper.d.ts +0 -26
  63. package/interfaces/iam-module-async-options.interface.d.ts +0 -11
@@ -2,18 +2,18 @@
2
2
  Object.defineProperty(exports, "__esModule", {
3
3
  value: true
4
4
  });
5
- Object.defineProperty(exports, "IAMDataSourceProvider", {
5
+ Object.defineProperty(exports, "IAMDataSourceService", {
6
6
  enumerable: true,
7
7
  get: function() {
8
- return IAMDataSourceProvider;
8
+ return IAMDataSourceService;
9
9
  }
10
10
  });
11
11
  const _modules = require("@flusys/nestjs-shared/modules");
12
12
  const _common = require("@nestjs/common");
13
13
  const _core = require("@nestjs/core");
14
14
  const _express = require("express");
15
- const _iamconstants = require("../config/iam.constants");
16
- const _iammoduleoptionsinterface = require("../interfaces/iam-module-options.interface");
15
+ const _helpers = require("../helpers");
16
+ const _iamconfigservice = require("./iam-config.service");
17
17
  function _define_property(obj, key, value) {
18
18
  if (key in obj) {
19
19
  Object.defineProperty(obj, key, {
@@ -82,7 +82,7 @@ function _ts_param(paramIndex, decorator) {
82
82
  decorator(target, key, paramIndex);
83
83
  };
84
84
  }
85
- let IAMDataSourceProvider = class IAMDataSourceProvider extends _modules.MultiTenantDataSourceService {
85
+ let IAMDataSourceService = class IAMDataSourceService extends _modules.MultiTenantDataSourceService {
86
86
  // Factory Methods
87
87
  static buildParentOptions(options) {
88
88
  return {
@@ -93,20 +93,17 @@ let IAMDataSourceProvider = class IAMDataSourceProvider extends _modules.MultiTe
93
93
  };
94
94
  }
95
95
  // Feature Flags
96
- getEnableCompanyFeature() {
97
- return this.iamOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
98
- }
99
96
  getEnableCompanyFeatureForTenant(tenant) {
100
- return tenant?.enableCompanyFeature ?? this.getEnableCompanyFeature();
97
+ return tenant?.enableCompanyFeature ?? this.configService.isCompanyFeatureEnabled();
101
98
  }
102
99
  getEnableCompanyFeatureForCurrentTenant() {
103
100
  return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
104
101
  }
105
102
  // Entity Management
106
103
  async getIAMEntities() {
107
- const { Action, Role, RoleWithCompany, UserIamPermission, UserIamPermissionWithCompany, getIAMEntitiesByConfig } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
104
+ const { getIAMEntitiesByConfig } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
108
105
  const enableCompanyFeature = this.getEnableCompanyFeatureForCurrentTenant();
109
- const permissionMode = this.iamOptions.bootstrapAppConfig?.permissionMode || 'FULL';
106
+ const permissionMode = _helpers.PermissionModeHelper.toString(this.configService.getPermissionMode());
110
107
  return getIAMEntitiesByConfig(enableCompanyFeature, permissionMode);
111
108
  }
112
109
  // Overrides
@@ -115,9 +112,9 @@ let IAMDataSourceProvider = class IAMDataSourceProvider extends _modules.MultiTe
115
112
  return super.createDataSourceFromConfig(config, entities);
116
113
  }
117
114
  async getSingleDataSource() {
118
- if (!IAMDataSourceProvider.singleDataSource) {
119
- if (IAMDataSourceProvider.singleConnectionLock) {
120
- return IAMDataSourceProvider.singleConnectionLock;
115
+ if (!IAMDataSourceService.singleDataSource) {
116
+ if (IAMDataSourceService.singleConnectionLock) {
117
+ return IAMDataSourceService.singleConnectionLock;
121
118
  }
122
119
  const lockPromise = (async ()=>{
123
120
  const config = this.getDefaultDatabaseConfig();
@@ -125,63 +122,63 @@ let IAMDataSourceProvider = class IAMDataSourceProvider extends _modules.MultiTe
125
122
  throw new Error('Default database config is not available');
126
123
  }
127
124
  const ds = await this.createDataSourceFromConfig(config);
128
- IAMDataSourceProvider.singleDataSource = ds;
129
- IAMDataSourceProvider.initialized = true;
125
+ IAMDataSourceService.singleDataSource = ds;
126
+ IAMDataSourceService.initialized = true;
130
127
  return ds;
131
128
  })();
132
- IAMDataSourceProvider.singleConnectionLock = lockPromise;
129
+ IAMDataSourceService.singleConnectionLock = lockPromise;
133
130
  try {
134
131
  return await lockPromise;
135
132
  } finally{
136
- IAMDataSourceProvider.singleConnectionLock = null;
133
+ IAMDataSourceService.singleConnectionLock = null;
137
134
  }
138
135
  }
139
- return IAMDataSourceProvider.singleDataSource;
136
+ return IAMDataSourceService.singleDataSource;
140
137
  }
141
138
  async getOrCreateTenantConnection(tenant) {
142
139
  // Return existing initialized connection from IAM-specific cache
143
- const existing = IAMDataSourceProvider.tenantConnections.get(tenant.id);
140
+ const existing = IAMDataSourceService.tenantConnections.get(tenant.id);
144
141
  if (existing?.isInitialized) {
145
142
  return existing;
146
143
  }
147
144
  // If another request is creating this tenant's connection, wait for it
148
- const pendingConnection = IAMDataSourceProvider.connectionLocks.get(tenant.id);
145
+ const pendingConnection = IAMDataSourceService.connectionLocks.get(tenant.id);
149
146
  if (pendingConnection) {
150
147
  return pendingConnection;
151
148
  }
152
149
  // Create connection with lock to prevent race conditions
153
150
  const config = this.buildTenantDatabaseConfig(tenant);
154
151
  const connectionPromise = this.createDataSourceFromConfig(config);
155
- IAMDataSourceProvider.connectionLocks.set(tenant.id, connectionPromise);
152
+ IAMDataSourceService.connectionLocks.set(tenant.id, connectionPromise);
156
153
  try {
157
154
  const dataSource = await connectionPromise;
158
- IAMDataSourceProvider.tenantConnections.set(tenant.id, dataSource);
155
+ IAMDataSourceService.tenantConnections.set(tenant.id, dataSource);
159
156
  return dataSource;
160
157
  } finally{
161
- IAMDataSourceProvider.connectionLocks.delete(tenant.id);
158
+ IAMDataSourceService.connectionLocks.delete(tenant.id);
162
159
  }
163
160
  }
164
- constructor(iamOptions, request){
165
- super(IAMDataSourceProvider.buildParentOptions(iamOptions), request), _define_property(this, "iamOptions", void 0), _define_property(this, "logger", void 0), this.iamOptions = iamOptions, this.logger = new _common.Logger(IAMDataSourceProvider.name);
161
+ constructor(configService, request){
162
+ super(IAMDataSourceService.buildParentOptions(configService.getOptions()), request), _define_property(this, "configService", void 0), _define_property(this, "logger", void 0), this.configService = configService, this.logger = new _common.Logger(IAMDataSourceService.name);
166
163
  }
167
164
  };
168
165
  // Override parent's static properties to have IAM-specific cache
169
- _define_property(IAMDataSourceProvider, "tenantConnections", new Map());
170
- _define_property(IAMDataSourceProvider, "singleDataSource", null);
171
- _define_property(IAMDataSourceProvider, "tenantsRegistry", new Map());
172
- _define_property(IAMDataSourceProvider, "initialized", false);
173
- _define_property(IAMDataSourceProvider, "connectionLocks", new Map());
174
- _define_property(IAMDataSourceProvider, "singleConnectionLock", null);
175
- IAMDataSourceProvider = _ts_decorate([
166
+ _define_property(IAMDataSourceService, "tenantConnections", new Map());
167
+ _define_property(IAMDataSourceService, "singleDataSource", null);
168
+ _define_property(IAMDataSourceService, "tenantsRegistry", new Map());
169
+ _define_property(IAMDataSourceService, "initialized", false);
170
+ _define_property(IAMDataSourceService, "connectionLocks", new Map());
171
+ _define_property(IAMDataSourceService, "singleConnectionLock", null);
172
+ IAMDataSourceService = _ts_decorate([
176
173
  (0, _common.Injectable)({
177
174
  scope: _common.Scope.REQUEST
178
175
  }),
179
- _ts_param(0, (0, _common.Inject)(_iamconstants.IAM_MODULE_OPTIONS)),
176
+ _ts_param(0, (0, _common.Inject)(_iamconfigservice.IAMConfigService)),
180
177
  _ts_param(1, (0, _common.Optional)()),
181
178
  _ts_param(1, (0, _common.Inject)(_core.REQUEST)),
182
179
  _ts_metadata("design:type", Function),
183
180
  _ts_metadata("design:paramtypes", [
184
- typeof _iammoduleoptionsinterface.IAMModuleOptions === "undefined" ? Object : _iammoduleoptionsinterface.IAMModuleOptions,
181
+ typeof _iamconfigservice.IAMConfigService === "undefined" ? Object : _iamconfigservice.IAMConfigService,
185
182
  typeof _express.Request === "undefined" ? Object : _express.Request
186
183
  ])
187
- ], IAMDataSourceProvider);
184
+ ], IAMDataSourceService);
@@ -4,7 +4,7 @@ Object.defineProperty(exports, "__esModule", {
4
4
  });
5
5
  _export_star(require("./action.service"), exports);
6
6
  _export_star(require("./iam-config.service"), exports);
7
- _export_star(require("./iam-datasource.provider"), exports);
7
+ _export_star(require("./iam-datasource.service"), exports);
8
8
  _export_star(require("./permission-cache.service"), exports);
9
9
  _export_star(require("./permission.service"), exports);
10
10
  _export_star(require("./role.service"), exports);
@@ -41,18 +41,17 @@ function _ts_param(paramIndex, decorator) {
41
41
  let PermissionCacheService = class PermissionCacheService {
42
42
  // Cache Key Generation
43
43
  generateCacheKey(options) {
44
- const { userId, companyId, branchId, enableCompanyFeature } = options;
45
- if (enableCompanyFeature && companyId) {
46
- return `${this.CACHE_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
47
- }
48
- return `${this.CACHE_PREFIX}:user:${userId}`;
44
+ return this.buildCacheKey(this.CACHE_PREFIX, options);
49
45
  }
50
46
  generateMyPermissionsCacheKey(options) {
47
+ return this.buildCacheKey(this.MY_PERMISSIONS_PREFIX, options);
48
+ }
49
+ buildCacheKey(prefix, options) {
51
50
  const { userId, companyId, branchId, enableCompanyFeature } = options;
52
51
  if (enableCompanyFeature && companyId) {
53
- return `${this.MY_PERMISSIONS_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
52
+ return `${prefix}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
54
53
  }
55
- return `${this.MY_PERMISSIONS_PREFIX}:user:${userId}`;
54
+ return `${prefix}:user:${userId}`;
56
55
  }
57
56
  // Cache Operations
58
57
  async setPermissions(options, permissions) {
@@ -66,17 +65,6 @@ let PermissionCacheService = class PermissionCacheService {
66
65
  // Don't throw - cache failure shouldn't break the operation
67
66
  }
68
67
  }
69
- async getPermissions(options) {
70
- try {
71
- const key = this.generateCacheKey(options);
72
- const result = await this.cacheManager.get(key);
73
- return result || null;
74
- } catch (error) {
75
- const errorMessage = _utils.ErrorHandler.getErrorMessage(error);
76
- this.logger.error(`Failed to get permissions from cache: ${errorMessage}`);
77
- return null;
78
- }
79
- }
80
68
  // My-Permissions Cache Operations
81
69
  async setMyPermissions(options, data) {
82
70
  try {
@@ -139,16 +127,6 @@ let PermissionCacheService = class PermissionCacheService {
139
127
  return null;
140
128
  }
141
129
  }
142
- async invalidateActionCodeCache(tenantId) {
143
- try {
144
- const key = this.generateActionCodeCacheKey(tenantId);
145
- await this.cacheManager.del(key);
146
- this.logger.debug(`Invalidated action code cache${tenantId ? ` for tenant ${tenantId}` : ''}`);
147
- } catch (error) {
148
- const errorMessage = _utils.ErrorHandler.getErrorMessage(error);
149
- this.logger.warn(`Failed to invalidate action code cache: ${errorMessage}`);
150
- }
151
- }
152
130
  // Cache Invalidation
153
131
  async invalidateUser(userId, companyId, branchIds) {
154
132
  try {
@@ -188,13 +166,6 @@ let PermissionCacheService = class PermissionCacheService {
188
166
  }
189
167
  return successCount;
190
168
  }
191
- /** Invalidate cache for all users in company (requires userIds via invalidateUsers) */ async invalidateCompany(companyId) {
192
- // Note: HybridCache doesn't support pattern matching (keys() method)
193
- // Company-wide invalidation requires passing individual user IDs
194
- // This is a placeholder that logs a warning
195
- this.logger.warn(`invalidateCompany called for ${companyId}, but pattern matching is not supported. ` + `Use invalidateUsers() with specific user IDs instead.`);
196
- return 0;
197
- }
198
169
  async invalidateRole(roleId, userIds, companyId, branchIds) {
199
170
  if (userIds.length === 0) {
200
171
  this.logger.debug(`No users found for role ${roleId}`);
@@ -206,17 +177,6 @@ let PermissionCacheService = class PermissionCacheService {
206
177
  }
207
178
  return count;
208
179
  }
209
- // Administrative Operations
210
- /** Clear all permission caches (memory and redis) */ async clearAll() {
211
- try {
212
- await this.cacheManager.reset(); // Clear memory cache
213
- await this.cacheManager.resetL2(); // Clear redis cache
214
- this.logger.warn('Cleared all cache entries (memory and redis)');
215
- } catch (error) {
216
- const errorMessage = _utils.ErrorHandler.getErrorMessage(error);
217
- this.logger.error(`Failed to clear all caches: ${errorMessage}`);
218
- }
219
- }
220
180
  constructor(cacheManager){
221
181
  _define_property(this, "cacheManager", void 0);
222
182
  _define_property(this, "logger", void 0);
@@ -19,7 +19,7 @@ const _useriampermissionentity = require("../entities/user-iam-permission.entity
19
19
  const _actiontypeenum = require("../enums/action-type.enum");
20
20
  const _permissiontypeenum = require("../enums/permission-type.enum");
21
21
  const _iamconfigservice = require("./iam-config.service");
22
- const _iamdatasourceprovider = require("./iam-datasource.provider");
22
+ const _iamdatasourceservice = require("./iam-datasource.service");
23
23
  const _permissioncacheservice = require("./permission-cache.service");
24
24
  function _define_property(obj, key, value) {
25
25
  if (key in obj) {
@@ -72,8 +72,7 @@ let PermissionService = class PermissionService {
72
72
  const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
73
73
  const branchId = dto.branchId ?? null;
74
74
  const companyId = dto.companyId ?? null;
75
- const itemsToAdd = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
76
- const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
75
+ const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
77
76
  let added = 0;
78
77
  let removed = 0;
79
78
  if (itemsToAdd.length > 0) {
@@ -107,8 +106,15 @@ let PermissionService = class PermissionService {
107
106
  branchId: enableCompanyFeature ? branchId : null
108
107
  }));
109
108
  if (newPermissions.length > 0) {
110
- await permissionRepo.save(newPermissions);
111
- added = newPermissions.length;
109
+ try {
110
+ await permissionRepo.save(newPermissions);
111
+ added = newPermissions.length;
112
+ } catch (error) {
113
+ if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
114
+ throw new _common.ConflictException('Some permissions already exist for this user. Please refresh and try again.');
115
+ }
116
+ throw error;
117
+ }
112
118
  }
113
119
  }
114
120
  if (itemsToRemove.length > 0) {
@@ -128,12 +134,7 @@ let PermissionService = class PermissionService {
128
134
  removed = result.affected || 0;
129
135
  }
130
136
  await this.invalidateUserPermissionCache(dto.userId, branchId, companyId);
131
- return {
132
- success: true,
133
- added,
134
- removed,
135
- message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed`
136
- };
137
+ return this.buildOperationResult(dto.items.length, added, removed);
137
138
  }
138
139
  async getUserActions(userId, branchId, companyId) {
139
140
  const permissionRepo = await this.getPermissionRepository();
@@ -199,8 +200,7 @@ let PermissionService = class PermissionService {
199
200
  });
200
201
  roleCompanyId = role?.companyId ?? null;
201
202
  }
202
- const itemsToAdd = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
203
- const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
203
+ const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
204
204
  let added = 0;
205
205
  let removed = 0;
206
206
  if (itemsToAdd.length > 0) {
@@ -229,8 +229,15 @@ let PermissionService = class PermissionService {
229
229
  branchId: null
230
230
  }));
231
231
  if (newPermissions.length > 0) {
232
- await permissionRepo.save(newPermissions);
233
- added = newPermissions.length;
232
+ try {
233
+ await permissionRepo.save(newPermissions);
234
+ added = newPermissions.length;
235
+ } catch (error) {
236
+ if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
237
+ throw new _common.ConflictException('Some role-action permissions already exist. Please refresh and try again.');
238
+ }
239
+ throw error;
240
+ }
234
241
  }
235
242
  }
236
243
  if (itemsToRemove.length > 0) {
@@ -245,12 +252,7 @@ let PermissionService = class PermissionService {
245
252
  removed = result.affected || 0;
246
253
  }
247
254
  const affectedUsers = await this.invalidateRoleMembersCache(dto.roleId);
248
- return {
249
- success: true,
250
- added,
251
- removed,
252
- message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed. Invalidated cache for ${affectedUsers} users.`
253
- };
255
+ return this.buildOperationResult(dto.items.length, added, removed, `. Invalidated cache for ${affectedUsers} users.`);
254
256
  }
255
257
  async getRoleActions(roleId) {
256
258
  const permissionRepo = await this.getPermissionRepository();
@@ -292,8 +294,7 @@ let PermissionService = class PermissionService {
292
294
  /** Assign or remove actions to/from a company (whitelist) */ async assignCompanyActions(dto) {
293
295
  const permissionRepo = await this.getPermissionRepository();
294
296
  const dataSource = permissionRepo.manager.connection;
295
- const itemsToAdd = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
296
- const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
297
+ const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
297
298
  let added = 0;
298
299
  let removed = 0;
299
300
  let removedRoleActions = 0;
@@ -313,12 +314,7 @@ let PermissionService = class PermissionService {
313
314
  });
314
315
  const affectedCacheEntries = await this.invalidateCompanyMembersCache(dto.companyId);
315
316
  const cascadeInfo = removedRoleActions > 0 || removedUserActions > 0 ? ` Cascaded removal: ${removedRoleActions} role permissions, ${removedUserActions} user permissions.` : '';
316
- return {
317
- success: true,
318
- added,
319
- removed,
320
- message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed.${cascadeInfo} Invalidated ${affectedCacheEntries} cache entries.`
321
- };
317
+ return this.buildOperationResult(dto.items.length, added, removed, `.${cascadeInfo} Invalidated ${affectedCacheEntries} cache entries.`);
322
318
  }
323
319
  async addCompanyActions(permissionRepo, companyId, actionIds) {
324
320
  const existingPermissions = await permissionRepo.find({
@@ -459,8 +455,7 @@ let PermissionService = class PermissionService {
459
455
  const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
460
456
  const branchId = dto.branchId ?? null;
461
457
  const companyId = dto.companyId ?? null;
462
- const itemsToAdd = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
463
- const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
458
+ const { toAdd: itemsToAdd, toRemove: itemsToRemove } = this.splitItemsByAction(dto.items);
464
459
  let added = 0;
465
460
  let removed = 0;
466
461
  if (itemsToAdd.length > 0) {
@@ -494,8 +489,15 @@ let PermissionService = class PermissionService {
494
489
  branchId: enableCompanyFeature ? branchId : null
495
490
  }));
496
491
  if (newPermissions.length > 0) {
497
- await permissionRepo.save(newPermissions);
498
- added = newPermissions.length;
492
+ try {
493
+ await permissionRepo.save(newPermissions);
494
+ added = newPermissions.length;
495
+ } catch (error) {
496
+ if (error?.code === 'ER_DUP_ENTRY' || error?.message?.includes('Duplicate entry')) {
497
+ throw new _common.ConflictException('Some user-role permissions already exist. Please refresh and try again.');
498
+ }
499
+ throw error;
500
+ }
499
501
  }
500
502
  }
501
503
  if (itemsToRemove.length > 0) {
@@ -515,12 +517,7 @@ let PermissionService = class PermissionService {
515
517
  removed = result.affected || 0;
516
518
  }
517
519
  await this.invalidateUserPermissionCache(dto.userId, branchId, companyId);
518
- return {
519
- success: true,
520
- added,
521
- removed,
522
- message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed`
523
- };
520
+ return this.buildOperationResult(dto.items.length, added, removed);
524
521
  }
525
522
  /** Get user's roles (branch-scoped, filtered by companyId and branchId if provided) */ async getUserRoles(userId, branchId, companyId) {
526
523
  const permissionRepo = await this.getPermissionRepository();
@@ -712,6 +709,20 @@ let PermissionService = class PermissionService {
712
709
  return cacheData;
713
710
  }
714
711
  // Helper Methods
712
+ /** Split permission items into add and remove arrays */ splitItemsByAction(items) {
713
+ return {
714
+ toAdd: items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD),
715
+ toRemove: items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE)
716
+ };
717
+ }
718
+ /** Build standard operation result DTO */ buildOperationResult(totalItems, added, removed, additionalMessage = '') {
719
+ return {
720
+ success: true,
721
+ added,
722
+ removed,
723
+ message: `Successfully processed ${totalItems} items: ${added} added, ${removed} removed${additionalMessage}`
724
+ };
725
+ }
715
726
  /** Get role IDs assigned to a user (merges company-wide + branch-specific roles) */ async getUserRoleIds(userId, branchId, companyId) {
716
727
  const permissionRepo = await this.getPermissionRepository();
717
728
  const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
@@ -899,11 +910,11 @@ PermissionService = _ts_decorate([
899
910
  }),
900
911
  _ts_param(0, (0, _common.Inject)(_permissioncacheservice.PermissionCacheService)),
901
912
  _ts_param(1, (0, _common.Inject)(_iamconfigservice.IAMConfigService)),
902
- _ts_param(2, (0, _common.Inject)(_iamdatasourceprovider.IAMDataSourceProvider)),
913
+ _ts_param(2, (0, _common.Inject)(_iamdatasourceservice.IAMDataSourceService)),
903
914
  _ts_metadata("design:type", Function),
904
915
  _ts_metadata("design:paramtypes", [
905
916
  typeof _permissioncacheservice.PermissionCacheService === "undefined" ? Object : _permissioncacheservice.PermissionCacheService,
906
917
  typeof _iamconfigservice.IAMConfigService === "undefined" ? Object : _iamconfigservice.IAMConfigService,
907
- typeof _iamdatasourceprovider.IAMDataSourceProvider === "undefined" ? Object : _iamdatasourceprovider.IAMDataSourceProvider
918
+ typeof _iamdatasourceservice.IAMDataSourceService === "undefined" ? Object : _iamdatasourceservice.IAMDataSourceService
908
919
  ])
909
920
  ], PermissionService);
@@ -15,7 +15,7 @@ const _common = require("@nestjs/common");
15
15
  const _rolewithcompanyentity = require("../entities/role-with-company.entity");
16
16
  const _roleentity = require("../entities/role.entity");
17
17
  const _iamconfigservice = require("./iam-config.service");
18
- const _iamdatasourceprovider = require("./iam-datasource.provider");
18
+ const _iamdatasourceservice = require("./iam-datasource.service");
19
19
  function _define_property(obj, key, value) {
20
20
  if (key in obj) {
21
21
  Object.defineProperty(obj, key, {
@@ -132,12 +132,12 @@ RoleService = _ts_decorate([
132
132
  _ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
133
133
  _ts_param(1, (0, _common.Inject)(_modules.UtilsService)),
134
134
  _ts_param(2, (0, _common.Inject)(_iamconfigservice.IAMConfigService)),
135
- _ts_param(3, (0, _common.Inject)(_iamdatasourceprovider.IAMDataSourceProvider)),
135
+ _ts_param(3, (0, _common.Inject)(_iamdatasourceservice.IAMDataSourceService)),
136
136
  _ts_metadata("design:type", Function),
137
137
  _ts_metadata("design:paramtypes", [
138
138
  typeof _classes.HybridCache === "undefined" ? Object : _classes.HybridCache,
139
139
  typeof _modules.UtilsService === "undefined" ? Object : _modules.UtilsService,
140
140
  typeof _iamconfigservice.IAMConfigService === "undefined" ? Object : _iamconfigservice.IAMConfigService,
141
- typeof _iamdatasourceprovider.IAMDataSourceProvider === "undefined" ? Object : _iamdatasourceprovider.IAMDataSourceProvider
141
+ typeof _iamdatasourceservice.IAMDataSourceService === "undefined" ? Object : _iamdatasourceservice.IAMDataSourceService
142
142
  ])
143
143
  ], RoleService);
@@ -1,12 +1,9 @@
1
- import { SingleResponseDto, ILoggedUserInfo } from '@flusys/nestjs-shared';
1
+ import { ILoggedUserInfo, SingleResponseDto } from '@flusys/nestjs-shared';
2
2
  import { AssignCompanyActionsDto, CompanyActionResponseDto, GetCompanyActionsDto, PermissionOperationResultDto } from '../dtos/permission.dto';
3
3
  import { PermissionService } from '../services/permission.service';
4
- import { IAMConfigService } from '../services/iam-config.service';
5
4
  export declare class CompanyActionPermissionController {
6
5
  private readonly permissionService;
7
- private readonly config;
8
- constructor(permissionService: PermissionService, config: IAMConfigService);
9
- private validateCompanyAccess;
6
+ constructor(permissionService: PermissionService);
10
7
  assignCompanyActions(dto: AssignCompanyActionsDto, user: ILoggedUserInfo): Promise<PermissionOperationResultDto>;
11
8
  getCompanyActions(dto: GetCompanyActionsDto, user: ILoggedUserInfo): Promise<SingleResponseDto<CompanyActionResponseDto[]>>;
12
9
  }
@@ -6,7 +6,6 @@ export declare class RolePermissionController {
6
6
  private readonly permissionService;
7
7
  private readonly config;
8
8
  constructor(permissionService: PermissionService, config: IAMConfigService);
9
- private validateCompanyAccess;
10
9
  assignRoleActions(dto: AssignRoleActionsDto): Promise<PermissionOperationResultDto>;
11
10
  getRoleActions(dto: GetRoleActionsDto): Promise<SingleResponseDto<RoleActionResponseDto[]>>;
12
11
  assignUserRoles(dto: AssignUserRolesDto, user: ILoggedUserInfo): Promise<PermissionOperationResultDto>;
@@ -6,7 +6,6 @@ export declare class UserActionPermissionController {
6
6
  private readonly permissionService;
7
7
  private readonly config;
8
8
  constructor(permissionService: PermissionService, config: IAMConfigService);
9
- private validateCompanyAccess;
10
9
  assignUserActions(dto: AssignUserActionsDto, user: ILoggedUserInfo): Promise<PermissionOperationResultDto>;
11
10
  getUserActions(dto: GetUserActionsDto, user: ILoggedUserInfo): Promise<SingleResponseDto<UserActionResponseDto[]>>;
12
11
  }
@@ -37,10 +37,6 @@ export declare class ActionResponseDto {
37
37
  export declare class ActionTreeDto extends ActionResponseDto {
38
38
  children: ActionTreeDto[];
39
39
  }
40
- export declare class ActionQueryDto {
41
- isActive?: boolean;
42
- parentId?: string;
43
- }
44
40
  export declare class ActionTreeQueryDto {
45
41
  search?: string;
46
42
  isActive?: boolean;
@@ -10,10 +10,6 @@ declare const UpdateRoleDto_base: import("@nestjs/common").Type<Partial<CreateRo
10
10
  export declare class UpdateRoleDto extends UpdateRoleDto_base {
11
11
  id: string;
12
12
  }
13
- export declare class RoleQueryDto {
14
- companyId?: string;
15
- isActive?: boolean;
16
- }
17
13
  export declare class RoleResponseDto {
18
14
  id: string;
19
15
  readOnly: boolean;
@@ -25,26 +25,16 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { JwtAuthGuard, SingleResponseDto, RequirePermission, COMPANY_ACTION_PERMISSIONS, CurrentUser, ILoggedUserInfo } from '@flusys/nestjs-shared';
29
- import { BadRequestException, Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
28
+ import { COMPANY_ACTION_PERMISSIONS, CurrentUser, ILoggedUserInfo, JwtAuthGuard, RequirePermission, SingleResponseDto } from '@flusys/nestjs-shared';
29
+ import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
30
30
  import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
31
31
  import { AssignCompanyActionsDto, GetCompanyActionsDto, PermissionOperationResultDto } from '../dtos/permission.dto';
32
32
  import { PermissionService } from '../services/permission.service';
33
- import { IAMConfigService } from '../services/iam-config.service';
34
33
  export class CompanyActionPermissionController {
35
- /** Validates that user can only manage permissions for their own company */ validateCompanyAccess(companyId, user) {
36
- if (this.config.isCompanyFeatureEnabled() && user.companyId) {
37
- if (companyId !== user.companyId) {
38
- throw new BadRequestException('Cannot manage permissions for another company');
39
- }
40
- }
41
- }
42
34
  async assignCompanyActions(dto, user) {
43
- this.validateCompanyAccess(dto.companyId, user);
44
35
  return this.permissionService.assignCompanyActions(dto);
45
36
  }
46
37
  async getCompanyActions(dto, user) {
47
- this.validateCompanyAccess(dto.companyId, user);
48
38
  const actions = await this.permissionService.getCompanyActions(dto.companyId);
49
39
  return {
50
40
  success: true,
@@ -52,12 +42,9 @@ export class CompanyActionPermissionController {
52
42
  data: actions
53
43
  };
54
44
  }
55
- // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
56
- constructor(permissionService, config){
45
+ constructor(permissionService){
57
46
  _define_property(this, "permissionService", void 0);
58
- _define_property(this, "config", void 0);
59
47
  this.permissionService = permissionService;
60
- this.config = config;
61
48
  }
62
49
  }
63
50
  _ts_decorate([
@@ -112,10 +99,8 @@ CompanyActionPermissionController = _ts_decorate([
112
99
  UseGuards(JwtAuthGuard),
113
100
  ApiBearerAuth(),
114
101
  _ts_param(0, Inject(PermissionService)),
115
- _ts_param(1, Inject(IAMConfigService)),
116
102
  _ts_metadata("design:type", Function),
117
103
  _ts_metadata("design:paramtypes", [
118
- typeof PermissionService === "undefined" ? Object : PermissionService,
119
- typeof IAMConfigService === "undefined" ? Object : IAMConfigService
104
+ typeof PermissionService === "undefined" ? Object : PermissionService
120
105
  ])
121
106
  ], CompanyActionPermissionController);
@@ -25,8 +25,7 @@ function _ts_param(paramIndex, decorator) {
25
25
  decorator(target, key, paramIndex);
26
26
  };
27
27
  }
28
- import { CurrentUser, ILoggedUserInfo } from '@flusys/nestjs-shared';
29
- import { JwtAuthGuard } from '@flusys/nestjs-shared/guards';
28
+ import { CurrentUser, ILoggedUserInfo, JwtAuthGuard } from '@flusys/nestjs-shared';
30
29
  import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
31
30
  import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
32
31
  import { MyPermissionsQueryDto, MyPermissionsResponseDto } from '../dtos/permission.dto';
@@ -26,19 +26,13 @@ function _ts_param(paramIndex, decorator) {
26
26
  };
27
27
  }
28
28
  import { JwtAuthGuard, SingleResponseDto, RequirePermission, ROLE_ACTION_PERMISSIONS, USER_ROLE_PERMISSIONS, CurrentUser, ILoggedUserInfo } from '@flusys/nestjs-shared';
29
- import { BadRequestException, Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
29
+ import { Body, Controller, Inject, Post, UseGuards } from '@nestjs/common';
30
30
  import { ApiBearerAuth, ApiBody, ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger';
31
31
  import { AssignRoleActionsDto, AssignUserRolesDto, GetRoleActionsDto, GetUserRolesDto, PermissionOperationResultDto } from '../dtos/permission.dto';
32
+ import { validateCompanyAccess } from '../helpers';
32
33
  import { PermissionService } from '../services/permission.service';
33
34
  import { IAMConfigService } from '../services/iam-config.service';
34
35
  export class RolePermissionController {
35
- /** Validates that user can only manage permissions within their company */ validateCompanyAccess(companyId, user) {
36
- if (this.config.isCompanyFeatureEnabled() && user.companyId && companyId) {
37
- if (companyId !== user.companyId) {
38
- throw new BadRequestException('Cannot manage permissions for users in another company');
39
- }
40
- }
41
- }
42
36
  async assignRoleActions(dto) {
43
37
  return this.permissionService.assignRoleActions(dto);
44
38
  }
@@ -51,11 +45,11 @@ export class RolePermissionController {
51
45
  };
52
46
  }
53
47
  async assignUserRoles(dto, user) {
54
- this.validateCompanyAccess(dto.companyId, user);
48
+ validateCompanyAccess(this.config, dto.companyId, user, 'Cannot manage permissions for users in another company');
55
49
  return this.permissionService.assignUserRoles(dto);
56
50
  }
57
51
  async getUserRoles(dto, user) {
58
- this.validateCompanyAccess(dto.companyId, user);
52
+ validateCompanyAccess(this.config, dto.companyId, user, 'Cannot manage permissions for users in another company');
59
53
  const roles = await this.permissionService.getUserRoles(dto.userId, dto.branchId, dto.companyId);
60
54
  return {
61
55
  success: true,