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