@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.
- package/README.md +665 -0
- package/cjs/config/iam.constants.js +11 -0
- package/cjs/config/index.js +18 -0
- package/cjs/controllers/action.controller.js +117 -0
- package/cjs/controllers/company-action-permission.controller.js +110 -0
- package/cjs/controllers/index.js +23 -0
- package/cjs/controllers/my-permission.controller.js +90 -0
- package/cjs/controllers/role-permission.controller.js +160 -0
- package/cjs/controllers/role.controller.js +58 -0
- package/cjs/controllers/user-action-permission.controller.js +110 -0
- package/cjs/docs/iam-swagger.config.js +202 -0
- package/cjs/docs/index.js +18 -0
- package/cjs/dtos/action.dto.js +347 -0
- package/cjs/dtos/index.js +21 -0
- package/cjs/dtos/permission.dto.js +554 -0
- package/cjs/dtos/role.dto.js +238 -0
- package/cjs/entities/action-base.entity.js +135 -0
- package/cjs/entities/action.entity.js +28 -0
- package/cjs/entities/index.js +81 -0
- package/cjs/entities/permission-base.entity.js +156 -0
- package/cjs/entities/permission-with-company.entity.js +99 -0
- package/cjs/entities/role-base.entity.js +86 -0
- package/cjs/entities/role-with-company.entity.js +55 -0
- package/cjs/entities/role.entity.js +25 -0
- package/cjs/entities/user-iam-permission.entity.js +57 -0
- package/cjs/enums/action-type.enum.js +22 -0
- package/cjs/enums/index.js +19 -0
- package/cjs/enums/permission-type.enum.js +16 -0
- package/cjs/helpers/index.js +19 -0
- package/cjs/helpers/permission-evaluator.helper.js +175 -0
- package/cjs/helpers/permission-mode.helper.js +49 -0
- package/cjs/index.js +28 -79
- package/cjs/interfaces/action.interface.js +4 -0
- package/cjs/interfaces/iam-module-async-options.interface.js +4 -0
- package/cjs/interfaces/iam-module-options.interface.js +18 -0
- package/cjs/interfaces/index.js +21 -0
- package/cjs/interfaces/role.interface.js +7 -0
- package/cjs/modules/iam.module.js +237 -0
- package/cjs/modules/index.js +18 -0
- package/cjs/services/action.service.js +253 -0
- package/cjs/services/iam-config.service.js +107 -0
- package/cjs/services/iam-datasource.provider.js +205 -0
- package/cjs/services/index.js +23 -0
- package/cjs/services/permission-cache.service.js +308 -0
- package/cjs/services/permission.service.js +1020 -0
- package/cjs/services/role.service.js +181 -0
- package/cjs/types/index.js +18 -0
- package/cjs/types/logic-node.type.js +54 -0
- package/fesm/config/iam.constants.js +1 -0
- package/fesm/config/index.js +1 -0
- package/fesm/controllers/action.controller.js +107 -0
- package/fesm/controllers/company-action-permission.controller.js +100 -0
- package/fesm/controllers/index.js +7 -0
- package/fesm/controllers/my-permission.controller.js +80 -0
- package/fesm/controllers/role-permission.controller.js +150 -0
- package/fesm/controllers/role.controller.js +48 -0
- package/fesm/controllers/user-action-permission.controller.js +100 -0
- package/fesm/docs/iam-swagger.config.js +192 -0
- package/fesm/docs/index.js +1 -0
- package/fesm/dtos/action.dto.js +317 -0
- package/fesm/dtos/index.js +4 -0
- package/fesm/dtos/permission.dto.js +490 -0
- package/fesm/dtos/role.dto.js +214 -0
- package/fesm/entities/action-base.entity.js +128 -0
- package/fesm/entities/action.entity.js +18 -0
- package/fesm/entities/index.js +56 -0
- package/fesm/entities/permission-base.entity.js +138 -0
- package/fesm/entities/permission-with-company.entity.js +89 -0
- package/fesm/entities/role-base.entity.js +79 -0
- package/fesm/entities/role-with-company.entity.js +45 -0
- package/fesm/entities/role.entity.js +15 -0
- package/fesm/entities/user-iam-permission.entity.js +38 -0
- package/fesm/enums/action-type.enum.js +12 -0
- package/fesm/enums/index.js +2 -0
- package/fesm/enums/permission-type.enum.js +6 -0
- package/fesm/helpers/index.js +2 -0
- package/fesm/helpers/permission-evaluator.helper.js +165 -0
- package/fesm/helpers/permission-mode.helper.js +49 -0
- package/fesm/index.js +11 -79
- package/fesm/interfaces/action.interface.js +3 -0
- package/fesm/interfaces/iam-module-async-options.interface.js +3 -0
- package/fesm/interfaces/iam-module-options.interface.js +1 -0
- package/fesm/interfaces/index.js +4 -0
- package/fesm/interfaces/role.interface.js +4 -0
- package/fesm/modules/iam.module.js +227 -0
- package/fesm/modules/index.js +1 -0
- package/fesm/services/action.service.js +243 -0
- package/fesm/services/iam-config.service.js +97 -0
- package/fesm/services/iam-datasource.provider.js +154 -0
- package/fesm/services/index.js +6 -0
- package/fesm/services/permission-cache.service.js +298 -0
- package/fesm/services/permission.service.js +1010 -0
- package/fesm/services/role.service.js +171 -0
- package/fesm/types/index.js +1 -0
- package/fesm/types/logic-node.type.js +36 -0
- package/package.json +25 -25
- package/cjs/config-index.js +0 -1
- package/cjs/controllers-index.js +0 -1
- package/cjs/docs-index.js +0 -79
- package/cjs/dtos-index.js +0 -1
- package/cjs/entities-index.js +0 -1
- package/cjs/enums-index.js +0 -1
- package/cjs/helpers-index.js +0 -1
- package/cjs/interfaces-index.js +0 -1
- package/cjs/modules-index.js +0 -1
- package/cjs/services-index.js +0 -1
- package/cjs/types-index.js +0 -1
- package/fesm/config-index.js +0 -1
- package/fesm/controllers-index.js +0 -1
- package/fesm/docs-index.js +0 -79
- package/fesm/dtos-index.js +0 -1
- package/fesm/entities-index.js +0 -1
- package/fesm/enums-index.js +0 -1
- package/fesm/helpers-index.js +0 -1
- package/fesm/interfaces-index.js +0 -0
- package/fesm/modules-index.js +0 -1
- package/fesm/services-index.js +0 -1
- 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);
|