@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,205 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "IAMDataSourceProvider", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return IAMDataSourceProvider;
9
+ }
10
+ });
11
+ const _modules = require("@flusys/nestjs-shared/modules");
12
+ const _common = require("@nestjs/common");
13
+ const _core = require("@nestjs/core");
14
+ const _express = require("express");
15
+ const _iamconstants = require("../config/iam.constants");
16
+ const _iammoduleoptionsinterface = require("../interfaces/iam-module-options.interface");
17
+ function _define_property(obj, key, value) {
18
+ if (key in obj) {
19
+ Object.defineProperty(obj, key, {
20
+ value: value,
21
+ enumerable: true,
22
+ configurable: true,
23
+ writable: true
24
+ });
25
+ } else {
26
+ obj[key] = value;
27
+ }
28
+ return obj;
29
+ }
30
+ function _getRequireWildcardCache(nodeInterop) {
31
+ if (typeof WeakMap !== "function") return null;
32
+ var cacheBabelInterop = new WeakMap();
33
+ var cacheNodeInterop = new WeakMap();
34
+ return (_getRequireWildcardCache = function(nodeInterop) {
35
+ return nodeInterop ? cacheNodeInterop : cacheBabelInterop;
36
+ })(nodeInterop);
37
+ }
38
+ function _interop_require_wildcard(obj, nodeInterop) {
39
+ if (!nodeInterop && obj && obj.__esModule) {
40
+ return obj;
41
+ }
42
+ if (obj === null || typeof obj !== "object" && typeof obj !== "function") {
43
+ return {
44
+ default: obj
45
+ };
46
+ }
47
+ var cache = _getRequireWildcardCache(nodeInterop);
48
+ if (cache && cache.has(obj)) {
49
+ return cache.get(obj);
50
+ }
51
+ var newObj = {
52
+ __proto__: null
53
+ };
54
+ var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor;
55
+ for(var key in obj){
56
+ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) {
57
+ var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null;
58
+ if (desc && (desc.get || desc.set)) {
59
+ Object.defineProperty(newObj, key, desc);
60
+ } else {
61
+ newObj[key] = obj[key];
62
+ }
63
+ }
64
+ }
65
+ newObj.default = obj;
66
+ if (cache) {
67
+ cache.set(obj, newObj);
68
+ }
69
+ return newObj;
70
+ }
71
+ function _ts_decorate(decorators, target, key, desc) {
72
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
73
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
74
+ 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;
75
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
76
+ }
77
+ function _ts_metadata(k, v) {
78
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
79
+ }
80
+ function _ts_param(paramIndex, decorator) {
81
+ return function(target, key) {
82
+ decorator(target, key, paramIndex);
83
+ };
84
+ }
85
+ let IAMDataSourceProvider = class IAMDataSourceProvider extends _modules.MultiTenantDataSourceService {
86
+ // ==================== Factory Methods ====================
87
+ /**
88
+ * Build parent options from IAMModuleOptions
89
+ */ static buildParentOptions(options) {
90
+ return {
91
+ bootstrapAppConfig: options.bootstrapAppConfig,
92
+ defaultDatabaseConfig: options.config?.defaultDatabaseConfig,
93
+ tenantDefaultDatabaseConfig: options.config?.tenantDefaultDatabaseConfig,
94
+ tenants: options.config?.tenants
95
+ };
96
+ }
97
+ // ==================== Feature Flags ====================
98
+ /**
99
+ * Get global enable company feature flag
100
+ */ getEnableCompanyFeature() {
101
+ return this.iamOptions.bootstrapAppConfig?.enableCompanyFeature ?? false;
102
+ }
103
+ /**
104
+ * Get enable company feature for specific tenant
105
+ * Falls back to global setting if not specified per-tenant
106
+ */ getEnableCompanyFeatureForTenant(tenant) {
107
+ return tenant?.enableCompanyFeature ?? this.getEnableCompanyFeature();
108
+ }
109
+ /**
110
+ * Get enable company feature for current request context
111
+ */ getEnableCompanyFeatureForCurrentTenant() {
112
+ return this.getEnableCompanyFeatureForTenant(this.getCurrentTenant() ?? undefined);
113
+ }
114
+ // ==================== Entity Management ====================
115
+ /**
116
+ * Get IAM entities dynamically based on configuration
117
+ * Returns appropriate entities based on company feature and permission mode
118
+ */ async getIAMEntities() {
119
+ const { Action, Role, RoleWithCompany, UserIamPermission, UserIamPermissionWithCompany, getIAMEntitiesByConfig } = await Promise.resolve().then(()=>/*#__PURE__*/ _interop_require_wildcard(require("../entities")));
120
+ const enableCompanyFeature = this.getEnableCompanyFeatureForCurrentTenant();
121
+ const permissionMode = this.iamOptions.bootstrapAppConfig?.permissionMode || 'FULL';
122
+ return getIAMEntitiesByConfig(enableCompanyFeature, permissionMode);
123
+ }
124
+ // ==================== Overrides ====================
125
+ /**
126
+ * Override to dynamically set entities based on tenant config
127
+ */ async createDataSourceFromConfig(config) {
128
+ const entities = await this.getIAMEntities();
129
+ return super.createDataSourceFromConfig(config, entities);
130
+ }
131
+ /**
132
+ * Override to use IAM-specific static cache
133
+ */ async getSingleDataSource() {
134
+ if (!IAMDataSourceProvider.singleDataSource) {
135
+ if (IAMDataSourceProvider.singleConnectionLock) {
136
+ return IAMDataSourceProvider.singleConnectionLock;
137
+ }
138
+ const lockPromise = (async ()=>{
139
+ const config = this.getDefaultDatabaseConfig();
140
+ if (!config) {
141
+ throw new Error('Default database config is not available');
142
+ }
143
+ const ds = await this.createDataSourceFromConfig(config);
144
+ IAMDataSourceProvider.singleDataSource = ds;
145
+ IAMDataSourceProvider.initialized = true;
146
+ return ds;
147
+ })();
148
+ IAMDataSourceProvider.singleConnectionLock = lockPromise;
149
+ try {
150
+ return await lockPromise;
151
+ } finally{
152
+ IAMDataSourceProvider.singleConnectionLock = null;
153
+ }
154
+ }
155
+ return IAMDataSourceProvider.singleDataSource;
156
+ }
157
+ /**
158
+ * Override to use IAM-specific static cache for tenant connections
159
+ */ async getOrCreateTenantConnection(tenant) {
160
+ // Return existing initialized connection from IAM-specific cache
161
+ const existing = IAMDataSourceProvider.tenantConnections.get(tenant.id);
162
+ if (existing?.isInitialized) {
163
+ return existing;
164
+ }
165
+ // If another request is creating this tenant's connection, wait for it
166
+ const pendingConnection = IAMDataSourceProvider.connectionLocks.get(tenant.id);
167
+ if (pendingConnection) {
168
+ return pendingConnection;
169
+ }
170
+ // Create connection with lock to prevent race conditions
171
+ const config = this.buildTenantDatabaseConfig(tenant);
172
+ const connectionPromise = this.createDataSourceFromConfig(config);
173
+ IAMDataSourceProvider.connectionLocks.set(tenant.id, connectionPromise);
174
+ try {
175
+ const dataSource = await connectionPromise;
176
+ IAMDataSourceProvider.tenantConnections.set(tenant.id, dataSource);
177
+ return dataSource;
178
+ } finally{
179
+ IAMDataSourceProvider.connectionLocks.delete(tenant.id);
180
+ }
181
+ }
182
+ constructor(iamOptions, request){
183
+ super(IAMDataSourceProvider.buildParentOptions(iamOptions), request), _define_property(this, "iamOptions", void 0), _define_property(this, "logger", void 0), this.iamOptions = iamOptions, this.logger = new _common.Logger(IAMDataSourceProvider.name);
184
+ }
185
+ };
186
+ // Override parent's static properties to have IAM-specific cache
187
+ _define_property(IAMDataSourceProvider, "tenantConnections", new Map());
188
+ _define_property(IAMDataSourceProvider, "singleDataSource", null);
189
+ _define_property(IAMDataSourceProvider, "tenantsRegistry", new Map());
190
+ _define_property(IAMDataSourceProvider, "initialized", false);
191
+ _define_property(IAMDataSourceProvider, "connectionLocks", new Map());
192
+ _define_property(IAMDataSourceProvider, "singleConnectionLock", null);
193
+ IAMDataSourceProvider = _ts_decorate([
194
+ (0, _common.Injectable)({
195
+ scope: _common.Scope.REQUEST
196
+ }),
197
+ _ts_param(0, (0, _common.Inject)(_iamconstants.IAM_MODULE_OPTIONS)),
198
+ _ts_param(1, (0, _common.Optional)()),
199
+ _ts_param(1, (0, _common.Inject)(_core.REQUEST)),
200
+ _ts_metadata("design:type", Function),
201
+ _ts_metadata("design:paramtypes", [
202
+ typeof _iammoduleoptionsinterface.IAMModuleOptions === "undefined" ? Object : _iammoduleoptionsinterface.IAMModuleOptions,
203
+ typeof _express.Request === "undefined" ? Object : _express.Request
204
+ ])
205
+ ], IAMDataSourceProvider);
@@ -0,0 +1,23 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ _export_star(require("./action.service"), exports);
6
+ _export_star(require("./iam-config.service"), exports);
7
+ _export_star(require("./iam-datasource.provider"), exports);
8
+ _export_star(require("./permission-cache.service"), exports);
9
+ _export_star(require("./permission.service"), exports);
10
+ _export_star(require("./role.service"), exports);
11
+ function _export_star(from, to) {
12
+ Object.keys(from).forEach(function(k) {
13
+ if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) {
14
+ Object.defineProperty(to, k, {
15
+ enumerable: true,
16
+ get: function() {
17
+ return from[k];
18
+ }
19
+ });
20
+ }
21
+ });
22
+ return from;
23
+ }
@@ -0,0 +1,308 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", {
3
+ value: true
4
+ });
5
+ Object.defineProperty(exports, "PermissionCacheService", {
6
+ enumerable: true,
7
+ get: function() {
8
+ return PermissionCacheService;
9
+ }
10
+ });
11
+ const _nestjsshared = require("@flusys/nestjs-shared");
12
+ const _common = require("@nestjs/common");
13
+ function _define_property(obj, key, value) {
14
+ if (key in obj) {
15
+ Object.defineProperty(obj, key, {
16
+ value: value,
17
+ enumerable: true,
18
+ configurable: true,
19
+ writable: true
20
+ });
21
+ } else {
22
+ obj[key] = value;
23
+ }
24
+ return obj;
25
+ }
26
+ function _ts_decorate(decorators, target, key, desc) {
27
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
28
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
29
+ 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;
30
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
31
+ }
32
+ function _ts_metadata(k, v) {
33
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
34
+ }
35
+ function _ts_param(paramIndex, decorator) {
36
+ return function(target, key) {
37
+ decorator(target, key, paramIndex);
38
+ };
39
+ }
40
+ let PermissionCacheService = class PermissionCacheService {
41
+ // ==================== Cache Key Generation ====================
42
+ /**
43
+ * Generate cache key for user permissions (backend codes for PermissionGuard)
44
+ * Format matches PermissionGuard in nestjs-shared
45
+ */ generateCacheKey(options) {
46
+ const { userId, companyId, branchId, enableCompanyFeature } = options;
47
+ if (enableCompanyFeature && companyId) {
48
+ return `${this.CACHE_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
49
+ }
50
+ return `${this.CACHE_PREFIX}:user:${userId}`;
51
+ }
52
+ /**
53
+ * Generate cache key for full my-permissions response
54
+ */ generateMyPermissionsCacheKey(options) {
55
+ const { userId, companyId, branchId, enableCompanyFeature } = options;
56
+ if (enableCompanyFeature && companyId) {
57
+ return `${this.MY_PERMISSIONS_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`;
58
+ }
59
+ return `${this.MY_PERMISSIONS_PREFIX}:user:${userId}`;
60
+ }
61
+ // ==================== Cache Operations ====================
62
+ /**
63
+ * Set user permissions in cache
64
+ *
65
+ * @param options - Cache key options
66
+ * @param permissions - Array of permission codes (action codes)
67
+ */ async setPermissions(options, permissions) {
68
+ try {
69
+ const key = this.generateCacheKey(options);
70
+ await this.cacheManager.set(key, permissions, this.TTL);
71
+ this.logger.debug(`Cached ${permissions.length} permissions for key: ${key}`);
72
+ } catch (error) {
73
+ this.logger.error(`Failed to cache permissions: ${error}`);
74
+ // Don't throw - cache failure shouldn't break the operation
75
+ }
76
+ }
77
+ /**
78
+ * Get user permissions from cache
79
+ *
80
+ * @param options - Cache key options
81
+ * @returns Array of permission codes or null if not found
82
+ */ async getPermissions(options) {
83
+ try {
84
+ const key = this.generateCacheKey(options);
85
+ const result = await this.cacheManager.get(key);
86
+ return result || null;
87
+ } catch (error) {
88
+ this.logger.error(`Failed to get permissions from cache: ${error}`);
89
+ return null;
90
+ }
91
+ }
92
+ // ==================== My-Permissions Cache Operations ====================
93
+ /**
94
+ * Set full my-permissions response in cache
95
+ * Caches frontend actions and backend codes for quick retrieval
96
+ *
97
+ * @param options - Cache key options
98
+ * @param data - Full permissions data to cache
99
+ */ async setMyPermissions(options, data) {
100
+ try {
101
+ const key = this.generateMyPermissionsCacheKey(options);
102
+ await this.cacheManager.set(key, data, this.TTL);
103
+ this.logger.debug(`Cached my-permissions for key: ${key} (${data.frontendActions.length} frontend, ${data.backendCodes.length} backend)`);
104
+ } catch (error) {
105
+ this.logger.error(`Failed to cache my-permissions: ${error}`);
106
+ }
107
+ }
108
+ /**
109
+ * Get full my-permissions response from cache
110
+ *
111
+ * @param options - Cache key options
112
+ * @returns Cached permissions data or null if not found
113
+ */ async getMyPermissions(options) {
114
+ try {
115
+ const key = this.generateMyPermissionsCacheKey(options);
116
+ const result = await this.cacheManager.get(key);
117
+ if (result) {
118
+ this.logger.debug(`Cache hit for my-permissions: ${key}`);
119
+ }
120
+ return result || null;
121
+ } catch (error) {
122
+ this.logger.error(`Failed to get my-permissions from cache: ${error}`);
123
+ return null;
124
+ }
125
+ }
126
+ // ==================== Action Code Cache Operations ====================
127
+ /**
128
+ * Cache action codes to IDs mapping
129
+ * Used for parentCodes filter to avoid DB query on cache hit
130
+ *
131
+ * @param codeToIdMap - Map of action code to action ID
132
+ */ async setActionCodeMap(codeToIdMap) {
133
+ try {
134
+ const key = `${this.ACTION_CODE_PREFIX}:map`;
135
+ await this.cacheManager.set(key, codeToIdMap, this.ACTION_CODE_TTL);
136
+ this.logger.debug(`Cached ${Object.keys(codeToIdMap).length} action code mappings`);
137
+ } catch (error) {
138
+ this.logger.error(`Failed to cache action code map: ${error}`);
139
+ }
140
+ }
141
+ /**
142
+ * Get action IDs for given codes from cache
143
+ *
144
+ * @param codes - Array of action codes
145
+ * @returns Map of code to ID, or null if not cached
146
+ */ async getActionIdsByCodes(codes) {
147
+ try {
148
+ const key = `${this.ACTION_CODE_PREFIX}:map`;
149
+ const fullMap = await this.cacheManager.get(key);
150
+ if (!fullMap) {
151
+ return null;
152
+ }
153
+ // Return only requested codes
154
+ const result = {};
155
+ for (const code of codes){
156
+ if (fullMap[code]) {
157
+ result[code] = fullMap[code];
158
+ }
159
+ }
160
+ return Object.keys(result).length > 0 ? result : null;
161
+ } catch (error) {
162
+ this.logger.error(`Failed to get action IDs from cache: ${error}`);
163
+ return null;
164
+ }
165
+ }
166
+ /**
167
+ * Invalidate action code cache
168
+ * Call when actions are created, updated, or deleted
169
+ */ async invalidateActionCodeCache() {
170
+ try {
171
+ const key = `${this.ACTION_CODE_PREFIX}:map`;
172
+ await this.cacheManager.del(key);
173
+ this.logger.debug('Invalidated action code cache');
174
+ } catch (error) {
175
+ this.logger.warn(`Failed to invalidate action code cache: ${error}`);
176
+ }
177
+ }
178
+ // ==================== Cache Invalidation ====================
179
+ /**
180
+ * Invalidate cache for specific user
181
+ * Clears both permission codes and my-permissions cache for ALL branches
182
+ *
183
+ * @param userId - User ID
184
+ * @param companyId - Optional company ID
185
+ * @param branchIds - Optional array of branch IDs to invalidate (if not provided, only null branch is cleared)
186
+ */ async invalidateUser(userId, companyId, branchIds) {
187
+ try {
188
+ const keysToDelete = [
189
+ // Permission codes cache (for PermissionGuard) - user-based key
190
+ `${this.CACHE_PREFIX}:user:${userId}`,
191
+ // My-permissions cache (full response) - user-based key
192
+ `${this.MY_PERMISSIONS_PREFIX}:user:${userId}`
193
+ ];
194
+ // Add company-based keys if companyId provided
195
+ if (companyId) {
196
+ // If branchIds provided, invalidate all specified branches
197
+ // Otherwise, invalidate only null branch (company-wide)
198
+ const branches = branchIds?.length ? branchIds : [
199
+ null
200
+ ];
201
+ for (const branchId of branches){
202
+ keysToDelete.push(`${this.CACHE_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`, `${this.MY_PERMISSIONS_PREFIX}:company:${companyId}:branch:${branchId || 'null'}:user:${userId}`);
203
+ }
204
+ }
205
+ // Parallel deletion for better performance
206
+ await Promise.all(keysToDelete.map((key)=>this.cacheManager.del(key)));
207
+ this.logger.debug(`Invalidated ${keysToDelete.length} cache keys for user ${userId}`);
208
+ } catch (error) {
209
+ this.logger.warn(`Failed to invalidate user cache for ${userId}: ${error}`);
210
+ }
211
+ }
212
+ /**
213
+ * Invalidate cache for multiple users
214
+ * Useful when role or company permissions change
215
+ *
216
+ * @param userIds - Array of user IDs
217
+ * @param companyId - Optional company ID
218
+ * @param branchIds - Optional array of branch IDs to invalidate for each user
219
+ * @returns Number of users whose cache was invalidated
220
+ */ async invalidateUsers(userIds, companyId, branchIds) {
221
+ if (userIds.length === 0) {
222
+ return 0;
223
+ }
224
+ // Parallel invalidation for better performance
225
+ const results = await Promise.allSettled(userIds.map((userId)=>this.invalidateUser(userId, companyId, branchIds)));
226
+ const successCount = results.filter((r)=>r.status === 'fulfilled').length;
227
+ const failedCount = results.filter((r)=>r.status === 'rejected').length;
228
+ if (failedCount > 0) {
229
+ this.logger.warn(`Failed to invalidate cache for ${failedCount} users`);
230
+ }
231
+ if (successCount > 0) {
232
+ this.logger.log(`Invalidated cache for ${successCount} users`);
233
+ }
234
+ return successCount;
235
+ }
236
+ /**
237
+ * Invalidate cache for all users in company
238
+ * WARNING: Without pattern matching support in HybridCache, this method
239
+ * invalidates individual user caches passed in the userIds array.
240
+ * For true company-wide invalidation, consider upgrading HybridCache with keys() support.
241
+ *
242
+ * @param companyId - Company ID
243
+ * @returns Number of cache entries deleted (always 0 without keys() support)
244
+ */ async invalidateCompany(companyId) {
245
+ // Note: HybridCache doesn't support pattern matching (keys() method)
246
+ // Company-wide invalidation requires passing individual user IDs
247
+ // This is a placeholder that logs a warning
248
+ this.logger.warn(`invalidateCompany called for ${companyId}, but pattern matching is not supported. ` + `Use invalidateUsers() with specific user IDs instead.`);
249
+ return 0;
250
+ }
251
+ /**
252
+ * Invalidate cache for all users with specific role
253
+ *
254
+ * @param roleId - Role ID (for logging)
255
+ * @param userIds - Array of user IDs who have this role
256
+ * @param companyId - Optional company ID
257
+ * @param branchIds - Optional array of branch IDs to invalidate
258
+ * @returns Number of users whose cache was invalidated
259
+ */ async invalidateRole(roleId, userIds, companyId, branchIds) {
260
+ if (userIds.length === 0) {
261
+ this.logger.debug(`No users found for role ${roleId}`);
262
+ return 0;
263
+ }
264
+ const count = await this.invalidateUsers(userIds, companyId, branchIds);
265
+ if (count > 0) {
266
+ this.logger.log(`Invalidated cache for ${count} users with role ${roleId}`);
267
+ }
268
+ return count;
269
+ }
270
+ // ==================== Administrative Operations ====================
271
+ /**
272
+ * Clear all permission caches
273
+ * Uses HybridCache reset methods (memory and redis)
274
+ * WARNING: Use with caution - this affects all caches, not just permissions
275
+ */ async clearAll() {
276
+ try {
277
+ await this.cacheManager.reset(); // Clear memory cache
278
+ await this.cacheManager.resetL2(); // Clear redis cache
279
+ this.logger.warn('Cleared all cache entries (memory and redis)');
280
+ } catch (error) {
281
+ this.logger.error(`Failed to clear all caches: ${error}`);
282
+ }
283
+ }
284
+ constructor(cacheManager){
285
+ _define_property(this, "cacheManager", void 0);
286
+ _define_property(this, "logger", void 0);
287
+ _define_property(this, "TTL", void 0); // 1 hour
288
+ _define_property(this, "ACTION_CODE_TTL", void 0); // 2 hours for action codes (less frequent changes)
289
+ _define_property(this, "CACHE_PREFIX", void 0);
290
+ _define_property(this, "MY_PERMISSIONS_PREFIX", void 0);
291
+ _define_property(this, "ACTION_CODE_PREFIX", void 0);
292
+ this.cacheManager = cacheManager;
293
+ this.logger = new _common.Logger(PermissionCacheService.name);
294
+ this.TTL = 3600000;
295
+ this.ACTION_CODE_TTL = 7200000;
296
+ this.CACHE_PREFIX = 'permissions';
297
+ this.MY_PERMISSIONS_PREFIX = 'my-permissions';
298
+ this.ACTION_CODE_PREFIX = 'action-codes';
299
+ }
300
+ };
301
+ PermissionCacheService = _ts_decorate([
302
+ (0, _common.Injectable)(),
303
+ _ts_param(0, (0, _common.Inject)('CACHE_INSTANCE')),
304
+ _ts_metadata("design:type", Function),
305
+ _ts_metadata("design:paramtypes", [
306
+ typeof _nestjsshared.HybridCache === "undefined" ? Object : _nestjsshared.HybridCache
307
+ ])
308
+ ], PermissionCacheService);