@flusys/nestjs-iam 0.1.0-beta.1 → 0.1.0-beta.2

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 (118) hide show
  1. package/README.md +665 -0
  2. package/cjs/config/iam.constants.js +11 -0
  3. package/cjs/config/index.js +18 -0
  4. package/cjs/controllers/action.controller.js +117 -0
  5. package/cjs/controllers/company-action-permission.controller.js +110 -0
  6. package/cjs/controllers/index.js +23 -0
  7. package/cjs/controllers/my-permission.controller.js +90 -0
  8. package/cjs/controllers/role-permission.controller.js +160 -0
  9. package/cjs/controllers/role.controller.js +58 -0
  10. package/cjs/controllers/user-action-permission.controller.js +110 -0
  11. package/cjs/docs/iam-swagger.config.js +202 -0
  12. package/cjs/docs/index.js +18 -0
  13. package/cjs/dtos/action.dto.js +347 -0
  14. package/cjs/dtos/index.js +21 -0
  15. package/cjs/dtos/permission.dto.js +554 -0
  16. package/cjs/dtos/role.dto.js +238 -0
  17. package/cjs/entities/action-base.entity.js +135 -0
  18. package/cjs/entities/action.entity.js +28 -0
  19. package/cjs/entities/index.js +81 -0
  20. package/cjs/entities/permission-base.entity.js +156 -0
  21. package/cjs/entities/permission-with-company.entity.js +99 -0
  22. package/cjs/entities/role-base.entity.js +86 -0
  23. package/cjs/entities/role-with-company.entity.js +55 -0
  24. package/cjs/entities/role.entity.js +25 -0
  25. package/cjs/entities/user-iam-permission.entity.js +57 -0
  26. package/cjs/enums/action-type.enum.js +22 -0
  27. package/cjs/enums/index.js +19 -0
  28. package/cjs/enums/permission-type.enum.js +16 -0
  29. package/cjs/helpers/index.js +19 -0
  30. package/cjs/helpers/permission-evaluator.helper.js +175 -0
  31. package/cjs/helpers/permission-mode.helper.js +49 -0
  32. package/cjs/index.js +28 -79
  33. package/cjs/interfaces/action.interface.js +4 -0
  34. package/cjs/interfaces/iam-module-async-options.interface.js +4 -0
  35. package/cjs/interfaces/iam-module-options.interface.js +18 -0
  36. package/cjs/interfaces/index.js +21 -0
  37. package/cjs/interfaces/role.interface.js +7 -0
  38. package/cjs/modules/iam.module.js +237 -0
  39. package/cjs/modules/index.js +18 -0
  40. package/cjs/services/action.service.js +253 -0
  41. package/cjs/services/iam-config.service.js +107 -0
  42. package/cjs/services/iam-datasource.provider.js +205 -0
  43. package/cjs/services/index.js +23 -0
  44. package/cjs/services/permission-cache.service.js +308 -0
  45. package/cjs/services/permission.service.js +1020 -0
  46. package/cjs/services/role.service.js +181 -0
  47. package/cjs/types/index.js +18 -0
  48. package/cjs/types/logic-node.type.js +54 -0
  49. package/fesm/config/iam.constants.js +1 -0
  50. package/fesm/config/index.js +1 -0
  51. package/fesm/controllers/action.controller.js +107 -0
  52. package/fesm/controllers/company-action-permission.controller.js +100 -0
  53. package/fesm/controllers/index.js +7 -0
  54. package/fesm/controllers/my-permission.controller.js +80 -0
  55. package/fesm/controllers/role-permission.controller.js +150 -0
  56. package/fesm/controllers/role.controller.js +48 -0
  57. package/fesm/controllers/user-action-permission.controller.js +100 -0
  58. package/fesm/docs/iam-swagger.config.js +192 -0
  59. package/fesm/docs/index.js +1 -0
  60. package/fesm/dtos/action.dto.js +317 -0
  61. package/fesm/dtos/index.js +4 -0
  62. package/fesm/dtos/permission.dto.js +490 -0
  63. package/fesm/dtos/role.dto.js +214 -0
  64. package/fesm/entities/action-base.entity.js +128 -0
  65. package/fesm/entities/action.entity.js +18 -0
  66. package/fesm/entities/index.js +56 -0
  67. package/fesm/entities/permission-base.entity.js +138 -0
  68. package/fesm/entities/permission-with-company.entity.js +89 -0
  69. package/fesm/entities/role-base.entity.js +79 -0
  70. package/fesm/entities/role-with-company.entity.js +45 -0
  71. package/fesm/entities/role.entity.js +15 -0
  72. package/fesm/entities/user-iam-permission.entity.js +38 -0
  73. package/fesm/enums/action-type.enum.js +12 -0
  74. package/fesm/enums/index.js +2 -0
  75. package/fesm/enums/permission-type.enum.js +6 -0
  76. package/fesm/helpers/index.js +2 -0
  77. package/fesm/helpers/permission-evaluator.helper.js +165 -0
  78. package/fesm/helpers/permission-mode.helper.js +49 -0
  79. package/fesm/index.js +11 -79
  80. package/fesm/interfaces/action.interface.js +3 -0
  81. package/fesm/interfaces/iam-module-async-options.interface.js +3 -0
  82. package/fesm/interfaces/iam-module-options.interface.js +1 -0
  83. package/fesm/interfaces/index.js +4 -0
  84. package/fesm/interfaces/role.interface.js +4 -0
  85. package/fesm/modules/iam.module.js +227 -0
  86. package/fesm/modules/index.js +1 -0
  87. package/fesm/services/action.service.js +243 -0
  88. package/fesm/services/iam-config.service.js +97 -0
  89. package/fesm/services/iam-datasource.provider.js +154 -0
  90. package/fesm/services/index.js +6 -0
  91. package/fesm/services/permission-cache.service.js +298 -0
  92. package/fesm/services/permission.service.js +1010 -0
  93. package/fesm/services/role.service.js +171 -0
  94. package/fesm/types/index.js +1 -0
  95. package/fesm/types/logic-node.type.js +36 -0
  96. package/package.json +25 -25
  97. package/cjs/config-index.js +0 -1
  98. package/cjs/controllers-index.js +0 -1
  99. package/cjs/docs-index.js +0 -79
  100. package/cjs/dtos-index.js +0 -1
  101. package/cjs/entities-index.js +0 -1
  102. package/cjs/enums-index.js +0 -1
  103. package/cjs/helpers-index.js +0 -1
  104. package/cjs/interfaces-index.js +0 -1
  105. package/cjs/modules-index.js +0 -1
  106. package/cjs/services-index.js +0 -1
  107. package/cjs/types-index.js +0 -1
  108. package/fesm/config-index.js +0 -1
  109. package/fesm/controllers-index.js +0 -1
  110. package/fesm/docs-index.js +0 -79
  111. package/fesm/dtos-index.js +0 -1
  112. package/fesm/entities-index.js +0 -1
  113. package/fesm/enums-index.js +0 -1
  114. package/fesm/helpers-index.js +0 -1
  115. package/fesm/interfaces-index.js +0 -0
  116. package/fesm/modules-index.js +0 -1
  117. package/fesm/services-index.js +0 -1
  118. package/fesm/types-index.js +0 -1
@@ -0,0 +1,1020 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "PermissionService", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return PermissionService;
9
+ }
10
+ });
11
+ const _common = require("@nestjs/common");
12
+ const _typeorm = require("typeorm");
13
+ const _permissiondto = require("../dtos/permission.dto");
14
+ const _actionentity = require("../entities/action.entity");
15
+ const _permissionwithcompanyentity = require("../entities/permission-with-company.entity");
16
+ const _rolewithcompanyentity = require("../entities/role-with-company.entity");
17
+ const _roleentity = require("../entities/role.entity");
18
+ const _useriampermissionentity = require("../entities/user-iam-permission.entity");
19
+ const _actiontypeenum = require("../enums/action-type.enum");
20
+ const _permissiontypeenum = require("../enums/permission-type.enum");
21
+ const _permissionevaluatorhelper = require("../helpers/permission-evaluator.helper");
22
+ const _iamconfigservice = require("./iam-config.service");
23
+ const _iamdatasourceprovider = require("./iam-datasource.provider");
24
+ const _permissioncacheservice = require("./permission-cache.service");
25
+ function _define_property(obj, key, value) {
26
+ if (key in obj) {
27
+ Object.defineProperty(obj, key, {
28
+ value: value,
29
+ enumerable: true,
30
+ configurable: true,
31
+ writable: true
32
+ });
33
+ } else {
34
+ obj[key] = value;
35
+ }
36
+ return obj;
37
+ }
38
+ function _ts_decorate(decorators, target, key, desc) {
39
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
40
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
41
+ 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;
42
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
43
+ }
44
+ function _ts_metadata(k, v) {
45
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
46
+ }
47
+ function _ts_param(paramIndex, decorator) {
48
+ return function(target, key) {
49
+ decorator(target, key, paramIndex);
50
+ };
51
+ }
52
+ let PermissionService = class PermissionService {
53
+ // ==================== Repository Getters ====================
54
+ async getPermissionRepository() {
55
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
56
+ const entity = enableCompanyFeature ? _permissionwithcompanyentity.UserIamPermissionWithCompany : _useriampermissionentity.UserIamPermission;
57
+ return this.dataSourceProvider.getRepository(entity);
58
+ }
59
+ async getActionRepository() {
60
+ return this.dataSourceProvider.getRepository(_actionentity.Action);
61
+ }
62
+ async getRoleRepository() {
63
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
64
+ const entity = enableCompanyFeature ? _rolewithcompanyentity.RoleWithCompany : _roleentity.Role;
65
+ return this.dataSourceProvider.getRepository(entity);
66
+ }
67
+ // ==================== User-Action Permissions ====================
68
+ async assignUserActions(dto) {
69
+ const permissionRepo = await this.getPermissionRepository();
70
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
71
+ const branchId = dto.branchId ?? null;
72
+ const companyId = dto.companyId ?? null;
73
+ // Separate items into add and remove batches
74
+ const itemsToAdd = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
75
+ const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
76
+ let added = 0;
77
+ let removed = 0;
78
+ // Batch add: Find existing permissions in one query
79
+ if (itemsToAdd.length > 0) {
80
+ const actionIdsToAdd = itemsToAdd.map((item)=>item.id);
81
+ const whereFind = {
82
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ACTION,
83
+ sourceType: _useriampermissionentity.IamEntityType.USER,
84
+ sourceId: dto.userId,
85
+ targetType: _useriampermissionentity.IamEntityType.ACTION,
86
+ targetId: (0, _typeorm.In)(actionIdsToAdd)
87
+ };
88
+ if (enableCompanyFeature) {
89
+ if (companyId) whereFind.companyId = companyId;
90
+ if (branchId) whereFind.branchId = branchId;
91
+ }
92
+ const existingPermissions = await permissionRepo.find({
93
+ where: whereFind,
94
+ select: [
95
+ 'targetId'
96
+ ]
97
+ });
98
+ const existingActionIds = new Set(existingPermissions.map((p)=>p.targetId));
99
+ const newPermissions = itemsToAdd.filter((item)=>!existingActionIds.has(item.id)).map((item)=>({
100
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ACTION,
101
+ sourceType: _useriampermissionentity.IamEntityType.USER,
102
+ sourceId: dto.userId,
103
+ targetType: _useriampermissionentity.IamEntityType.ACTION,
104
+ targetId: item.id,
105
+ userId: dto.userId,
106
+ companyId: enableCompanyFeature ? companyId : null,
107
+ branchId: enableCompanyFeature ? branchId : null
108
+ }));
109
+ if (newPermissions.length > 0) {
110
+ await permissionRepo.save(newPermissions);
111
+ added = newPermissions.length;
112
+ }
113
+ }
114
+ // Batch remove: Delete all at once using IN clause
115
+ if (itemsToRemove.length > 0) {
116
+ const actionIdsToRemove = itemsToRemove.map((item)=>item.id);
117
+ const whereDelete = {
118
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ACTION,
119
+ sourceType: _useriampermissionentity.IamEntityType.USER,
120
+ sourceId: dto.userId,
121
+ targetType: _useriampermissionentity.IamEntityType.ACTION,
122
+ targetId: (0, _typeorm.In)(actionIdsToRemove)
123
+ };
124
+ if (enableCompanyFeature) {
125
+ if (companyId) whereDelete.companyId = companyId;
126
+ if (branchId) whereDelete.branchId = branchId;
127
+ }
128
+ const result = await permissionRepo.delete(whereDelete);
129
+ removed = result.affected || 0;
130
+ }
131
+ // Invalidate permission cache for this user
132
+ await this.invalidateUserPermissionCache(dto.userId, branchId, companyId);
133
+ return {
134
+ success: true,
135
+ added,
136
+ removed,
137
+ message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed`
138
+ };
139
+ }
140
+ async getUserActions(userId, branchId, companyId) {
141
+ const permissionRepo = await this.getPermissionRepository();
142
+ const actionRepo = await this.getActionRepository();
143
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
144
+ const where = {
145
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ACTION,
146
+ sourceType: _useriampermissionentity.IamEntityType.USER,
147
+ sourceId: userId
148
+ };
149
+ // When company feature enabled:
150
+ // - companyId provided → Filter by that company (optional additional filter)
151
+ // - branchId provided → Filter by that branch (branch-specific actions)
152
+ // - branchId not provided → Filter by IS NULL (company-wide actions)
153
+ if (enableCompanyFeature) {
154
+ if (companyId) {
155
+ // Filter by companyId if provided
156
+ where.companyId = companyId;
157
+ }
158
+ if (branchId) {
159
+ // Branch-specific actions only
160
+ where.branchId = branchId;
161
+ } else {
162
+ // Company-wide actions only (branchId IS NULL)
163
+ where.branchId = null;
164
+ }
165
+ }
166
+ const permissions = await permissionRepo.find({
167
+ where
168
+ });
169
+ if (permissions.length === 0) {
170
+ return [];
171
+ }
172
+ const actionIds = permissions.map((p)=>p.targetId);
173
+ const actionWhere = {
174
+ id: (0, _typeorm.In)(actionIds)
175
+ };
176
+ const actions = await actionRepo.find({
177
+ where: actionWhere
178
+ });
179
+ const actionMap = new Map(actions.map((a)=>[
180
+ a.id,
181
+ a
182
+ ]));
183
+ return permissions.filter((p)=>actionMap.has(p.targetId)).map((p)=>{
184
+ const action = actionMap.get(p.targetId);
185
+ return {
186
+ id: p.id,
187
+ userId: p.userId,
188
+ actionId: action.id,
189
+ actionCode: action.code ?? '',
190
+ actionName: action.name,
191
+ branchId: ('branchId' in p ? p.branchId : null) ?? null,
192
+ createdAt: p.createdAt
193
+ };
194
+ });
195
+ }
196
+ // ==================== Role-Action Permissions ====================
197
+ async assignRoleActions(dto) {
198
+ const permissionRepo = await this.getPermissionRepository();
199
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
200
+ // Fetch role companyId if company feature is enabled
201
+ let roleCompanyId = null;
202
+ if (enableCompanyFeature) {
203
+ const roleRepo = await this.getRoleRepository();
204
+ const role = await roleRepo.findOne({
205
+ where: {
206
+ id: dto.roleId
207
+ },
208
+ select: [
209
+ 'id',
210
+ 'companyId'
211
+ ]
212
+ });
213
+ roleCompanyId = role?.companyId ?? null;
214
+ }
215
+ // Separate items into add and remove batches
216
+ const itemsToAdd = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
217
+ const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
218
+ let added = 0;
219
+ let removed = 0;
220
+ // Batch add: Find existing permissions in one query
221
+ if (itemsToAdd.length > 0) {
222
+ const actionIdsToAdd = itemsToAdd.map((item)=>item.id);
223
+ const existingPermissions = await permissionRepo.find({
224
+ where: {
225
+ permissionType: _useriampermissionentity.IamPermissionType.ROLE_ACTION,
226
+ sourceType: _useriampermissionentity.IamEntityType.ROLE,
227
+ sourceId: dto.roleId,
228
+ targetType: _useriampermissionentity.IamEntityType.ACTION,
229
+ targetId: (0, _typeorm.In)(actionIdsToAdd)
230
+ },
231
+ select: [
232
+ 'targetId'
233
+ ]
234
+ });
235
+ const existingActionIds = new Set(existingPermissions.map((p)=>p.targetId));
236
+ const newPermissions = itemsToAdd.filter((item)=>!existingActionIds.has(item.id)).map((item)=>({
237
+ permissionType: _useriampermissionentity.IamPermissionType.ROLE_ACTION,
238
+ sourceType: _useriampermissionentity.IamEntityType.ROLE,
239
+ sourceId: dto.roleId,
240
+ targetType: _useriampermissionentity.IamEntityType.ACTION,
241
+ targetId: item.id,
242
+ userId: null,
243
+ companyId: enableCompanyFeature ? roleCompanyId : null,
244
+ branchId: null
245
+ }));
246
+ if (newPermissions.length > 0) {
247
+ await permissionRepo.save(newPermissions);
248
+ added = newPermissions.length;
249
+ }
250
+ }
251
+ // Batch remove: Delete all at once using IN clause
252
+ if (itemsToRemove.length > 0) {
253
+ const actionIdsToRemove = itemsToRemove.map((item)=>item.id);
254
+ const result = await permissionRepo.delete({
255
+ permissionType: _useriampermissionentity.IamPermissionType.ROLE_ACTION,
256
+ sourceType: _useriampermissionentity.IamEntityType.ROLE,
257
+ sourceId: dto.roleId,
258
+ targetType: _useriampermissionentity.IamEntityType.ACTION,
259
+ targetId: (0, _typeorm.In)(actionIdsToRemove)
260
+ });
261
+ removed = result.affected || 0;
262
+ }
263
+ // Invalidate cache for all users with this role
264
+ const affectedUsers = await this.invalidateRoleMembersCache(dto.roleId);
265
+ return {
266
+ success: true,
267
+ added,
268
+ removed,
269
+ message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed. Invalidated cache for ${affectedUsers} users.`
270
+ };
271
+ }
272
+ async getRoleActions(roleId) {
273
+ const permissionRepo = await this.getPermissionRepository();
274
+ const actionRepo = await this.getActionRepository();
275
+ const permissions = await permissionRepo.find({
276
+ where: {
277
+ permissionType: _useriampermissionentity.IamPermissionType.ROLE_ACTION,
278
+ sourceType: _useriampermissionentity.IamEntityType.ROLE,
279
+ sourceId: roleId
280
+ }
281
+ });
282
+ if (permissions.length === 0) {
283
+ return [];
284
+ }
285
+ const actionIds = permissions.map((p)=>p.targetId);
286
+ const actionWhere = {
287
+ id: (0, _typeorm.In)(actionIds)
288
+ };
289
+ const actions = await actionRepo.find({
290
+ where: actionWhere
291
+ });
292
+ const actionMap = new Map(actions.map((a)=>[
293
+ a.id,
294
+ a
295
+ ]));
296
+ return permissions.filter((p)=>actionMap.has(p.targetId)).map((p)=>{
297
+ const action = actionMap.get(p.targetId);
298
+ return {
299
+ id: p.id,
300
+ roleId: p.sourceId,
301
+ actionId: action.id,
302
+ actionCode: action.code ?? '',
303
+ actionName: action.name,
304
+ createdAt: p.createdAt
305
+ };
306
+ });
307
+ }
308
+ // ==================== Company-Action Permissions (Whitelist) ====================
309
+ /**
310
+ * Assign or remove actions to/from a company (whitelist)
311
+ * This controls which actions are available to a company
312
+ */ async assignCompanyActions(dto) {
313
+ const permissionRepo = await this.getPermissionRepository();
314
+ const dataSource = permissionRepo.manager.connection;
315
+ const itemsToAdd = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
316
+ const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
317
+ let added = 0;
318
+ let removed = 0;
319
+ let removedRoleActions = 0;
320
+ let removedUserActions = 0;
321
+ await dataSource.transaction(async (manager)=>{
322
+ const transactionalPermissionRepo = manager.getRepository(permissionRepo.target);
323
+ if (itemsToAdd.length > 0) {
324
+ added = await this.addCompanyActions(transactionalPermissionRepo, dto.companyId, itemsToAdd.map((item)=>item.id));
325
+ }
326
+ if (itemsToRemove.length > 0) {
327
+ const actionIdsToRemove = itemsToRemove.map((item)=>item.id);
328
+ const cascadeResult = await this.removeCompanyActionsWithCascade(manager, dto.companyId, actionIdsToRemove);
329
+ removed = cascadeResult.removedCompanyActions;
330
+ removedRoleActions = cascadeResult.removedRoleActions;
331
+ removedUserActions = cascadeResult.removedUserActions;
332
+ }
333
+ });
334
+ const affectedCacheEntries = await this.invalidateCompanyMembersCache(dto.companyId);
335
+ const cascadeInfo = removedRoleActions > 0 || removedUserActions > 0 ? ` Cascaded removal: ${removedRoleActions} role permissions, ${removedUserActions} user permissions.` : '';
336
+ return {
337
+ success: true,
338
+ added,
339
+ removed,
340
+ message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed.${cascadeInfo} Invalidated ${affectedCacheEntries} cache entries.`
341
+ };
342
+ }
343
+ async addCompanyActions(permissionRepo, companyId, actionIds) {
344
+ const existingPermissions = await permissionRepo.find({
345
+ where: {
346
+ permissionType: _useriampermissionentity.IamPermissionType.COMPANY_ACTION,
347
+ sourceType: _useriampermissionentity.IamEntityType.COMPANY,
348
+ sourceId: companyId,
349
+ targetType: _useriampermissionentity.IamEntityType.ACTION,
350
+ targetId: (0, _typeorm.In)(actionIds)
351
+ },
352
+ select: [
353
+ 'targetId'
354
+ ]
355
+ });
356
+ const existingActionIds = new Set(existingPermissions.map((p)=>p.targetId));
357
+ const newActionIds = actionIds.filter((id)=>!existingActionIds.has(id));
358
+ if (newActionIds.length === 0) {
359
+ return 0;
360
+ }
361
+ const newPermissions = newActionIds.map((actionId)=>permissionRepo.create({
362
+ permissionType: _useriampermissionentity.IamPermissionType.COMPANY_ACTION,
363
+ sourceType: _useriampermissionentity.IamEntityType.COMPANY,
364
+ sourceId: companyId,
365
+ targetType: _useriampermissionentity.IamEntityType.ACTION,
366
+ targetId: actionId,
367
+ userId: null
368
+ }));
369
+ await permissionRepo.save(newPermissions);
370
+ return newPermissions.length;
371
+ }
372
+ async removeCompanyActionsWithCascade(manager, companyId, actionIds) {
373
+ const permissionEntity = this.iamConfigService.isCompanyFeatureEnabled() ? _permissionwithcompanyentity.UserIamPermissionWithCompany : _useriampermissionentity.UserIamPermission;
374
+ const permissionRepo = manager.getRepository(permissionEntity);
375
+ const companyResult = await permissionRepo.delete({
376
+ permissionType: _useriampermissionentity.IamPermissionType.COMPANY_ACTION,
377
+ sourceType: _useriampermissionentity.IamEntityType.COMPANY,
378
+ sourceId: companyId,
379
+ targetType: _useriampermissionentity.IamEntityType.ACTION,
380
+ targetId: (0, _typeorm.In)(actionIds)
381
+ });
382
+ const roleEntity = this.iamConfigService.isCompanyFeatureEnabled() ? _rolewithcompanyentity.RoleWithCompany : _roleentity.Role;
383
+ const roleRepo = manager.getRepository(roleEntity);
384
+ const companyRoles = await roleRepo.find({
385
+ where: {
386
+ companyId,
387
+ deletedAt: (0, _typeorm.IsNull)()
388
+ },
389
+ select: [
390
+ 'id'
391
+ ]
392
+ });
393
+ let removedRoleActions = 0;
394
+ let removedUserActions = 0;
395
+ if (companyRoles.length > 0) {
396
+ const roleIds = companyRoles.map((role)=>role.id);
397
+ const roleResult = await permissionRepo.delete({
398
+ permissionType: _useriampermissionentity.IamPermissionType.ROLE_ACTION,
399
+ sourceType: _useriampermissionentity.IamEntityType.ROLE,
400
+ sourceId: (0, _typeorm.In)(roleIds),
401
+ targetType: _useriampermissionentity.IamEntityType.ACTION,
402
+ targetId: (0, _typeorm.In)(actionIds)
403
+ });
404
+ removedRoleActions = roleResult.affected || 0;
405
+ }
406
+ if (this.iamConfigService.isCompanyFeatureEnabled()) {
407
+ const userResult = await permissionRepo.delete({
408
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ACTION,
409
+ companyId,
410
+ targetType: _useriampermissionentity.IamEntityType.ACTION,
411
+ targetId: (0, _typeorm.In)(actionIds)
412
+ });
413
+ removedUserActions = userResult.affected || 0;
414
+ }
415
+ if (removedRoleActions > 0 || removedUserActions > 0) {
416
+ this.logger.log(`Cascade deleted for company ${companyId}: ${removedRoleActions} role actions, ${removedUserActions} user actions`);
417
+ }
418
+ return {
419
+ removedCompanyActions: companyResult.affected || 0,
420
+ removedRoleActions,
421
+ removedUserActions
422
+ };
423
+ }
424
+ /**
425
+ * Get all actions assigned to a company (whitelist)
426
+ */ async getCompanyActions(companyId) {
427
+ const permissionRepo = await this.getPermissionRepository();
428
+ const actionRepo = await this.getActionRepository();
429
+ const permissions = await permissionRepo.find({
430
+ where: {
431
+ permissionType: _useriampermissionentity.IamPermissionType.COMPANY_ACTION,
432
+ sourceType: _useriampermissionentity.IamEntityType.COMPANY,
433
+ sourceId: companyId
434
+ }
435
+ });
436
+ if (permissions.length === 0) {
437
+ return [];
438
+ }
439
+ const actionIds = permissions.map((p)=>p.targetId);
440
+ const actions = await actionRepo.find({
441
+ where: {
442
+ id: (0, _typeorm.In)(actionIds)
443
+ }
444
+ });
445
+ const actionMap = new Map(actions.map((a)=>[
446
+ a.id,
447
+ a
448
+ ]));
449
+ return permissions.filter((p)=>actionMap.has(p.targetId)).map((p)=>{
450
+ const action = actionMap.get(p.targetId);
451
+ return {
452
+ id: p.id,
453
+ companyId: companyId,
454
+ actionId: action.id,
455
+ actionCode: action.code ?? '',
456
+ actionName: action.name,
457
+ createdAt: p.createdAt
458
+ };
459
+ });
460
+ }
461
+ /**
462
+ * Get action IDs whitelisted for a company
463
+ * Used internally to filter actions
464
+ */ async getCompanyActionIds(companyId) {
465
+ const permissionRepo = await this.getPermissionRepository();
466
+ const permissions = await permissionRepo.find({
467
+ where: {
468
+ permissionType: _useriampermissionentity.IamPermissionType.COMPANY_ACTION,
469
+ sourceType: _useriampermissionentity.IamEntityType.COMPANY,
470
+ sourceId: companyId
471
+ },
472
+ select: [
473
+ 'targetId'
474
+ ]
475
+ });
476
+ return permissions.map((p)=>p.targetId);
477
+ }
478
+ // ==================== User-Role Permissions ====================
479
+ /**
480
+ * Assign user to roles (branch-scoped)
481
+ * User roles are now branch-specific when company feature is enabled.
482
+ */ async assignUserRoles(dto) {
483
+ const permissionRepo = await this.getPermissionRepository();
484
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
485
+ const branchId = dto.branchId ?? null;
486
+ const companyId = dto.companyId ?? null;
487
+ // Separate items into add and remove batches
488
+ const itemsToAdd = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.ADD);
489
+ const itemsToRemove = dto.items.filter((item)=>item.action === _permissiondto.PermissionAction.REMOVE);
490
+ let added = 0;
491
+ let removed = 0;
492
+ // Batch add: Find existing permissions in one query
493
+ if (itemsToAdd.length > 0) {
494
+ const roleIdsToAdd = itemsToAdd.map((item)=>item.id);
495
+ const whereFind = {
496
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
497
+ sourceType: _useriampermissionentity.IamEntityType.USER,
498
+ sourceId: dto.userId,
499
+ targetType: _useriampermissionentity.IamEntityType.ROLE,
500
+ targetId: (0, _typeorm.In)(roleIdsToAdd)
501
+ };
502
+ if (enableCompanyFeature) {
503
+ if (companyId) whereFind.companyId = companyId;
504
+ if (branchId) whereFind.branchId = branchId;
505
+ }
506
+ const existingPermissions = await permissionRepo.find({
507
+ where: whereFind,
508
+ select: [
509
+ 'targetId'
510
+ ]
511
+ });
512
+ const existingRoleIds = new Set(existingPermissions.map((p)=>p.targetId));
513
+ const newPermissions = itemsToAdd.filter((item)=>!existingRoleIds.has(item.id)).map((item)=>({
514
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
515
+ sourceType: _useriampermissionentity.IamEntityType.USER,
516
+ sourceId: dto.userId,
517
+ targetType: _useriampermissionentity.IamEntityType.ROLE,
518
+ targetId: item.id,
519
+ userId: dto.userId,
520
+ companyId: enableCompanyFeature ? companyId : null,
521
+ branchId: enableCompanyFeature ? branchId : null
522
+ }));
523
+ if (newPermissions.length > 0) {
524
+ await permissionRepo.save(newPermissions);
525
+ added = newPermissions.length;
526
+ }
527
+ }
528
+ // Batch remove: Delete all at once using IN clause
529
+ if (itemsToRemove.length > 0) {
530
+ const roleIdsToRemove = itemsToRemove.map((item)=>item.id);
531
+ const whereDelete = {
532
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
533
+ sourceType: _useriampermissionentity.IamEntityType.USER,
534
+ sourceId: dto.userId,
535
+ targetType: _useriampermissionentity.IamEntityType.ROLE,
536
+ targetId: (0, _typeorm.In)(roleIdsToRemove)
537
+ };
538
+ if (enableCompanyFeature) {
539
+ if (companyId) whereDelete.companyId = companyId;
540
+ if (branchId) whereDelete.branchId = branchId;
541
+ }
542
+ const result = await permissionRepo.delete(whereDelete);
543
+ removed = result.affected || 0;
544
+ }
545
+ // Invalidate permission cache for this user
546
+ await this.invalidateUserPermissionCache(dto.userId, branchId, companyId);
547
+ return {
548
+ success: true,
549
+ added,
550
+ removed,
551
+ message: `Successfully processed ${dto.items.length} items: ${added} added, ${removed} removed`
552
+ };
553
+ }
554
+ /**
555
+ * Get user's roles (branch-scoped)
556
+ * Returns roles assigned to user, filtered by companyId and branchId if provided.
557
+ */ async getUserRoles(userId, branchId, companyId) {
558
+ const permissionRepo = await this.getPermissionRepository();
559
+ const roleRepo = await this.getRoleRepository();
560
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
561
+ // Build where clause with optional companyId and branchId filter
562
+ // When company feature enabled:
563
+ // - companyId provided → Filter by that company (optional additional filter)
564
+ // - branchId provided → Filter by that branch (branch-specific roles)
565
+ // - branchId not provided → Filter by IS NULL (company-wide roles)
566
+ const where = {
567
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
568
+ sourceType: _useriampermissionentity.IamEntityType.USER,
569
+ sourceId: userId
570
+ };
571
+ if (enableCompanyFeature) {
572
+ if (companyId) {
573
+ // Filter by companyId if provided
574
+ where.companyId = companyId;
575
+ }
576
+ if (branchId) {
577
+ // Branch-specific roles only
578
+ where.branchId = branchId;
579
+ } else {
580
+ // Company-wide roles only (branchId IS NULL)
581
+ where.branchId = null;
582
+ }
583
+ }
584
+ const permissions = await permissionRepo.find({
585
+ where
586
+ });
587
+ if (permissions.length === 0) {
588
+ return [];
589
+ }
590
+ const roleIds = permissions.map((p)=>p.targetId);
591
+ const roleWhere = {
592
+ id: (0, _typeorm.In)(roleIds)
593
+ };
594
+ const roles = await roleRepo.find({
595
+ where: roleWhere
596
+ });
597
+ const roleMap = new Map(roles.map((r)=>[
598
+ r.id,
599
+ r
600
+ ]));
601
+ return permissions.filter((p)=>roleMap.has(p.targetId)).map((p)=>{
602
+ const role = roleMap.get(p.targetId);
603
+ const permissionEntity = p;
604
+ return {
605
+ id: p.id,
606
+ userId: p.userId,
607
+ roleId: role.id,
608
+ roleName: role.name,
609
+ branchId: enableCompanyFeature ? permissionEntity.branchId ?? null : null,
610
+ createdAt: p.createdAt
611
+ };
612
+ });
613
+ }
614
+ // ==================== My Permissions ====================
615
+ /**
616
+ * Get user's effective permissions (cache-first approach)
617
+ * 1. Check cache for existing permissions
618
+ * 2. If cached, apply parentCodes filter and return
619
+ * 3. If not cached, fetch from DB, cache, and return
620
+ *
621
+ * Cache is invalidated when:
622
+ * - User's direct actions change (assignUserActions)
623
+ * - User's roles change (assignUserRoles)
624
+ * - Role's actions change (assignRoleActions) - affects all users with that role
625
+ * - Company's actions change (assignCompanyActions) - affects all users in that company
626
+ *
627
+ * @param userId - User ID
628
+ * @param branchId - Branch ID (null = only company-wide, value = company-wide + branch-specific)
629
+ * @param companyId - Company ID (for role filtering and company action whitelist)
630
+ * @param parentCodes - Optional parent action codes to filter by (only affects frontend actions)
631
+ */ async getMyPermissions(userId, branchId, companyId, parentCodes) {
632
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
633
+ const cacheOptions = {
634
+ userId,
635
+ companyId,
636
+ branchId,
637
+ enableCompanyFeature
638
+ };
639
+ // Step 1: Check cache first
640
+ const cachedData = await this.permissionCacheService.getMyPermissions(cacheOptions);
641
+ if (cachedData) {
642
+ // Cache hit - use cached data (even if empty)
643
+ return this.buildResponseFromCache(cachedData, parentCodes);
644
+ }
645
+ // Step 2: Cache miss - fetch from DB and cache (even if empty)
646
+ const freshData = await this.fetchAndCachePermissions(userId, branchId, companyId);
647
+ // Step 3: Apply parentCodes filter and return
648
+ return this.buildResponseFromCache(freshData, parentCodes);
649
+ }
650
+ /**
651
+ * Build response from cached data, applying optional parentCodes filter
652
+ * Uses cached action code → ID mapping to avoid DB query
653
+ */ async buildResponseFromCache(cachedData, parentCodes) {
654
+ let frontendActions = cachedData.frontendActions;
655
+ // Apply parentCodes filter if provided
656
+ if (parentCodes?.length) {
657
+ const parentIds = await this.getParentIdsByCodesWithCache(parentCodes);
658
+ if (parentIds.size > 0) {
659
+ frontendActions = frontendActions.filter((a)=>a.parentId && parentIds.has(a.parentId));
660
+ } else {
661
+ frontendActions = [];
662
+ }
663
+ }
664
+ return {
665
+ frontendActions: frontendActions.map((a)=>({
666
+ id: a.id,
667
+ code: a.code,
668
+ name: a.name,
669
+ description: a.description
670
+ })),
671
+ cachedEndpoints: cachedData.backendCodes.length
672
+ };
673
+ }
674
+ /**
675
+ * Get parent IDs by codes, using cache first
676
+ */ async getParentIdsByCodesWithCache(codes) {
677
+ // Try cache first
678
+ const cachedMap = await this.permissionCacheService.getActionIdsByCodes(codes);
679
+ if (cachedMap) {
680
+ return new Set(Object.values(cachedMap));
681
+ }
682
+ // Cache miss - fetch from DB and cache
683
+ const actionRepo = await this.getActionRepository();
684
+ const allActions = await actionRepo.find({
685
+ select: [
686
+ 'id',
687
+ 'code'
688
+ ]
689
+ });
690
+ // Build full code → ID map and cache it
691
+ const fullMap = {};
692
+ for (const action of allActions){
693
+ if (action.code) {
694
+ fullMap[action.code] = action.id;
695
+ }
696
+ }
697
+ await this.permissionCacheService.setActionCodeMap(fullMap);
698
+ // Return only requested codes
699
+ return new Set(codes.map((code)=>fullMap[code]).filter(Boolean));
700
+ }
701
+ /**
702
+ * Fetch permissions from DB and cache them
703
+ * Always returns a valid cache data structure (may be empty)
704
+ * Empty permissions are also cached to avoid repeated DB hits
705
+ */ async fetchAndCachePermissions(userId, branchId, companyId) {
706
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
707
+ const permissionMode = this.iamConfigService.getPermissionMode();
708
+ const cacheOptions = {
709
+ userId,
710
+ companyId,
711
+ branchId,
712
+ enableCompanyFeature
713
+ };
714
+ // Empty cache data for users with no permissions
715
+ const emptyData = {
716
+ frontendActions: [],
717
+ backendCodes: []
718
+ };
719
+ const allActionIds = new Set();
720
+ // Collect action IDs based on permission mode
721
+ if (permissionMode === _permissiontypeenum.IAMPermissionMode.RBAC || permissionMode === _permissiontypeenum.IAMPermissionMode.FULL) {
722
+ const userRoleIds = await this.getUserRoleIds(userId, branchId, companyId);
723
+ if (userRoleIds.length > 0) {
724
+ const roleActionIds = await this.getRoleActionIds(userRoleIds);
725
+ roleActionIds.forEach((id)=>allActionIds.add(id));
726
+ }
727
+ }
728
+ if (permissionMode === _permissiontypeenum.IAMPermissionMode.DIRECT || permissionMode === _permissiontypeenum.IAMPermissionMode.FULL) {
729
+ const userActionIds = await this.getUserActionIds(userId, branchId, companyId);
730
+ userActionIds.forEach((id)=>allActionIds.add(id));
731
+ }
732
+ if (allActionIds.size === 0) {
733
+ // Cache empty result to avoid repeated DB hits
734
+ await this.permissionCacheService.setMyPermissions(cacheOptions, emptyData);
735
+ return emptyData;
736
+ }
737
+ // Filter by company whitelist
738
+ if (enableCompanyFeature && companyId) {
739
+ const companyActionIds = await this.getCompanyActionIds(companyId);
740
+ if (companyActionIds.length > 0) {
741
+ const allowedActionIds = new Set(companyActionIds);
742
+ for (const actionId of allActionIds){
743
+ if (!allowedActionIds.has(actionId)) {
744
+ allActionIds.delete(actionId);
745
+ }
746
+ }
747
+ }
748
+ }
749
+ if (allActionIds.size === 0) {
750
+ // Cache empty result to avoid repeated DB hits
751
+ await this.permissionCacheService.setMyPermissions(cacheOptions, emptyData);
752
+ return emptyData;
753
+ }
754
+ // Fetch actions from DB
755
+ const actionRepo = await this.getActionRepository();
756
+ const actions = await actionRepo.find({
757
+ where: {
758
+ id: (0, _typeorm.In)(Array.from(allActionIds))
759
+ }
760
+ });
761
+ // Separate by type
762
+ const backendActions = actions.filter((a)=>a.actionType === _actiontypeenum.ActionType.BACKEND || a.actionType === _actiontypeenum.ActionType.BOTH);
763
+ const frontendActions = actions.filter((a)=>a.actionType === _actiontypeenum.ActionType.FRONTEND || a.actionType === _actiontypeenum.ActionType.BOTH);
764
+ const backendCodes = backendActions.map((a)=>a.code).filter((c)=>!!c);
765
+ // Build cache data (includes parentId for filtering)
766
+ const cacheData = {
767
+ frontendActions: frontendActions.map((a)=>({
768
+ id: a.id,
769
+ code: a.code ?? '',
770
+ name: a.name,
771
+ description: a.description,
772
+ parentId: a.parentId
773
+ })),
774
+ backendCodes
775
+ };
776
+ // Cache both: full permissions and backend codes for PermissionGuard
777
+ await Promise.all([
778
+ this.permissionCacheService.setMyPermissions(cacheOptions, cacheData),
779
+ this.permissionCacheService.setPermissions(cacheOptions, backendCodes)
780
+ ]);
781
+ return cacheData;
782
+ }
783
+ // ==================== Helper Methods ====================
784
+ /**
785
+ * Get role IDs assigned to a user
786
+ * Merges company-wide roles (branchId=null) with branch-specific roles (branchId=value)
787
+ *
788
+ * @param userId - User ID
789
+ * @param branchId - Branch ID (if null or not provided, only company-wide roles returned)
790
+ * @param companyId - Company ID for filtering roles (security)
791
+ * @returns Array of unique role IDs
792
+ */ async getUserRoleIds(userId, branchId, companyId) {
793
+ const permissionRepo = await this.getPermissionRepository();
794
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
795
+ if (!enableCompanyFeature) {
796
+ // Simple case: no company feature, get all user roles
797
+ const permissions = await permissionRepo.find({
798
+ where: {
799
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
800
+ sourceType: _useriampermissionentity.IamEntityType.USER,
801
+ sourceId: userId
802
+ }
803
+ });
804
+ return permissions.map((p)=>p.targetId);
805
+ }
806
+ // Company feature enabled: merge company-wide + branch-specific roles
807
+ const roleIds = new Set();
808
+ // Step 1: Get company-wide roles (branchId = null)
809
+ const companyWidePermissions = await permissionRepo.find({
810
+ where: {
811
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
812
+ sourceType: _useriampermissionentity.IamEntityType.USER,
813
+ sourceId: userId,
814
+ branchId: (0, _typeorm.IsNull)(),
815
+ companyId: companyId
816
+ }
817
+ });
818
+ companyWidePermissions.forEach((p)=>roleIds.add(p.targetId));
819
+ // Step 2: Get branch-specific roles (branchId = value) if branchId provided
820
+ if (branchId) {
821
+ const branchPermissions = await permissionRepo.find({
822
+ where: {
823
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
824
+ sourceType: _useriampermissionentity.IamEntityType.USER,
825
+ sourceId: userId,
826
+ branchId,
827
+ companyId: companyId
828
+ }
829
+ });
830
+ branchPermissions.forEach((p)=>roleIds.add(p.targetId));
831
+ }
832
+ return Array.from(roleIds);
833
+ }
834
+ async getRoleActionIds(roleIds) {
835
+ const permissionRepo = await this.getPermissionRepository();
836
+ const permissions = await permissionRepo.find({
837
+ where: {
838
+ permissionType: _useriampermissionentity.IamPermissionType.ROLE_ACTION,
839
+ sourceType: _useriampermissionentity.IamEntityType.ROLE,
840
+ sourceId: (0, _typeorm.In)(roleIds)
841
+ }
842
+ });
843
+ return permissions.map((p)=>p.targetId);
844
+ }
845
+ /**
846
+ * Get action IDs directly assigned to a user
847
+ * Merges company-wide actions (branchId=null) with branch-specific actions (branchId=value)
848
+ *
849
+ * @param userId - User ID
850
+ * @param branchId - Branch ID (if null or not provided, only company-wide actions returned)
851
+ * @param companyId - Company ID for filtering actions (security)
852
+ * @returns Array of unique action IDs
853
+ */ async getUserActionIds(userId, branchId, companyId) {
854
+ const permissionRepo = await this.getPermissionRepository();
855
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
856
+ if (!enableCompanyFeature) {
857
+ // Simple case: no company feature, get all user actions
858
+ const permissions = await permissionRepo.find({
859
+ where: {
860
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ACTION,
861
+ sourceType: _useriampermissionentity.IamEntityType.USER,
862
+ sourceId: userId
863
+ }
864
+ });
865
+ return permissions.map((p)=>p.targetId);
866
+ }
867
+ // Company feature enabled: merge company-wide + branch-specific actions
868
+ const actionIds = new Set();
869
+ // Step 1: Get company-wide actions (branchId = null, companyId = current)
870
+ const companyWideWhere = {
871
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ACTION,
872
+ sourceType: _useriampermissionentity.IamEntityType.USER,
873
+ sourceId: userId,
874
+ branchId: (0, _typeorm.IsNull)()
875
+ };
876
+ if (companyId) {
877
+ companyWideWhere.companyId = companyId;
878
+ }
879
+ const companyWidePermissions = await permissionRepo.find({
880
+ where: companyWideWhere
881
+ });
882
+ companyWidePermissions.forEach((p)=>actionIds.add(p.targetId));
883
+ // Step 2: Get branch-specific actions (branchId = value, companyId = current) if branchId provided
884
+ if (branchId) {
885
+ const branchWhere = {
886
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ACTION,
887
+ sourceType: _useriampermissionentity.IamEntityType.USER,
888
+ sourceId: userId,
889
+ branchId
890
+ };
891
+ if (companyId) {
892
+ branchWhere.companyId = companyId;
893
+ }
894
+ const branchPermissions = await permissionRepo.find({
895
+ where: branchWhere
896
+ });
897
+ branchPermissions.forEach((p)=>actionIds.add(p.targetId));
898
+ }
899
+ return Array.from(actionIds);
900
+ }
901
+ /**
902
+ * Invalidate permission cache for a user
903
+ * Delegates to PermissionCacheService
904
+ */ async invalidateUserPermissionCache(userId, branchId, companyId) {
905
+ // Wrap branchId in array if provided, otherwise invalidate null branch only
906
+ const branchIds = branchId !== undefined ? [
907
+ branchId
908
+ ] : [
909
+ null
910
+ ];
911
+ await this.permissionCacheService.invalidateUser(userId, companyId, branchIds);
912
+ }
913
+ /**
914
+ * Invalidate permission cache for all users with a specific role
915
+ * Called when role-action permissions are modified
916
+ * Invalidates ALL branches for affected users
917
+ *
918
+ * @param roleId - Role ID
919
+ * @returns Number of users whose cache was invalidated
920
+ */ async invalidateRoleMembersCache(roleId) {
921
+ const permissionRepo = await this.getPermissionRepository();
922
+ const roleRepo = await this.getRoleRepository();
923
+ const enableCompanyFeature = this.iamConfigService.isCompanyFeatureEnabled();
924
+ // Find all users assigned to this role with their branch IDs
925
+ const userRoles = await permissionRepo.find({
926
+ where: {
927
+ permissionType: _useriampermissionentity.IamPermissionType.USER_ROLE,
928
+ sourceType: _useriampermissionentity.IamEntityType.USER,
929
+ targetType: _useriampermissionentity.IamEntityType.ROLE,
930
+ targetId: roleId
931
+ }
932
+ });
933
+ // Get unique user IDs
934
+ const userIds = [
935
+ ...new Set(userRoles.map((ur)=>ur.sourceId))
936
+ ];
937
+ if (userIds.length === 0) {
938
+ return 0;
939
+ }
940
+ // Get company ID from role (if company feature enabled)
941
+ const role = await roleRepo.findOne({
942
+ where: {
943
+ id: roleId
944
+ }
945
+ });
946
+ const companyId = role?.companyId || null;
947
+ // Get all branch IDs for these users (to invalidate all their cached branches)
948
+ let branchIds = [
949
+ null
950
+ ];
951
+ if (enableCompanyFeature && companyId) {
952
+ const userBranches = await permissionRepo.createQueryBuilder('p').select('DISTINCT p.branch_id', 'branchId').where('p.user_id IN (:...userIds)', {
953
+ userIds
954
+ }).andWhere('p.company_id = :companyId', {
955
+ companyId
956
+ }).getRawMany();
957
+ branchIds = [
958
+ ...new Set(userBranches.map((p)=>p.branchId))
959
+ ];
960
+ }
961
+ // Invalidate cache for all users with all their branches
962
+ return await this.permissionCacheService.invalidateRole(roleId, userIds, companyId, branchIds);
963
+ }
964
+ /**
965
+ * Invalidate permission cache for all users in a company
966
+ * Called when company-action permissions are modified
967
+ * Finds all users with permissions in this company and invalidates their caches for ALL branches
968
+ *
969
+ * @param companyId - Company ID
970
+ * @returns Number of cache entries invalidated
971
+ */ async invalidateCompanyMembersCache(companyId) {
972
+ if (!this.iamConfigService.isCompanyFeatureEnabled()) {
973
+ return 0;
974
+ }
975
+ const permissionRepo = await this.getPermissionRepository();
976
+ // Find all unique user IDs and branch IDs that have permissions in this company
977
+ const userPermissions = await permissionRepo.createQueryBuilder('p').select('DISTINCT p.user_id', 'userId').addSelect('p.branch_id', 'branchId').where('p.company_id = :companyId', {
978
+ companyId
979
+ }).andWhere('p.user_id IS NOT NULL').getRawMany();
980
+ const userIds = [
981
+ ...new Set(userPermissions.map((p)=>p.userId).filter(Boolean))
982
+ ];
983
+ const branchIds = [
984
+ ...new Set(userPermissions.map((p)=>p.branchId))
985
+ ]; // includes null
986
+ if (userIds.length === 0) {
987
+ return 0;
988
+ }
989
+ return await this.permissionCacheService.invalidateUsers(userIds, companyId, branchIds);
990
+ }
991
+ // NOTE: @Inject() required for bundled code - type metadata may be lost during esbuild
992
+ constructor(permissionEvaluator, permissionCacheService, iamConfigService, dataSourceProvider){
993
+ _define_property(this, "permissionEvaluator", void 0);
994
+ _define_property(this, "permissionCacheService", void 0);
995
+ _define_property(this, "iamConfigService", void 0);
996
+ _define_property(this, "dataSourceProvider", void 0);
997
+ _define_property(this, "logger", void 0);
998
+ this.permissionEvaluator = permissionEvaluator;
999
+ this.permissionCacheService = permissionCacheService;
1000
+ this.iamConfigService = iamConfigService;
1001
+ this.dataSourceProvider = dataSourceProvider;
1002
+ this.logger = new _common.Logger(PermissionService.name);
1003
+ }
1004
+ };
1005
+ PermissionService = _ts_decorate([
1006
+ (0, _common.Injectable)({
1007
+ scope: _common.Scope.REQUEST
1008
+ }),
1009
+ _ts_param(0, (0, _common.Inject)(_permissionevaluatorhelper.PermissionEvaluatorHelper)),
1010
+ _ts_param(1, (0, _common.Inject)(_permissioncacheservice.PermissionCacheService)),
1011
+ _ts_param(2, (0, _common.Inject)(_iamconfigservice.IAMConfigService)),
1012
+ _ts_param(3, (0, _common.Inject)(_iamdatasourceprovider.IAMDataSourceProvider)),
1013
+ _ts_metadata("design:type", Function),
1014
+ _ts_metadata("design:paramtypes", [
1015
+ typeof _permissionevaluatorhelper.PermissionEvaluatorHelper === "undefined" ? Object : _permissionevaluatorhelper.PermissionEvaluatorHelper,
1016
+ typeof _permissioncacheservice.PermissionCacheService === "undefined" ? Object : _permissioncacheservice.PermissionCacheService,
1017
+ typeof _iamconfigservice.IAMConfigService === "undefined" ? Object : _iamconfigservice.IAMConfigService,
1018
+ typeof _iamdatasourceprovider.IAMDataSourceProvider === "undefined" ? Object : _iamdatasourceprovider.IAMDataSourceProvider
1019
+ ])
1020
+ ], PermissionService);