@saasicat/nest 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +202 -0
- package/README.md +53 -0
- package/dist/admin/index.cjs +1283 -0
- package/dist/admin/index.d.cts +225 -0
- package/dist/admin/index.d.ts +225 -0
- package/dist/admin/index.js +58 -0
- package/dist/admin-audit.service-9IqXMlZm.d.cts +30 -0
- package/dist/admin-audit.service-9IqXMlZm.d.ts +30 -0
- package/dist/admin-manifest.config-DyrQNT7M.d.cts +10 -0
- package/dist/admin-manifest.config-DyrQNT7M.d.ts +10 -0
- package/dist/aggregation-CJ3qQf92.d.cts +288 -0
- package/dist/aggregation-Dvz9e8X2.d.ts +288 -0
- package/dist/billing/index.cjs +5205 -0
- package/dist/billing/index.d.cts +1326 -0
- package/dist/billing/index.d.ts +1326 -0
- package/dist/billing/index.js +197 -0
- package/dist/catalog/index.cjs +6449 -0
- package/dist/catalog/index.d.cts +1073 -0
- package/dist/catalog/index.d.ts +1073 -0
- package/dist/catalog/index.js +158 -0
- package/dist/checkout-offer/index.cjs +807 -0
- package/dist/checkout-offer/index.d.cts +120 -0
- package/dist/checkout-offer/index.d.ts +120 -0
- package/dist/checkout-offer/index.js +20 -0
- package/dist/chunk-2FR6ZL7R.js +0 -0
- package/dist/chunk-2PNX2QL2.js +347 -0
- package/dist/chunk-2VNZYRDV.js +326 -0
- package/dist/chunk-7MLGHWKO.js +12 -0
- package/dist/chunk-ABB2CRAS.js +31 -0
- package/dist/chunk-AJ3EPELS.js +157 -0
- package/dist/chunk-E56W4U2P.js +90 -0
- package/dist/chunk-HRU3CP57.js +71 -0
- package/dist/chunk-L4SUWH5B.js +14 -0
- package/dist/chunk-M6CZQYPY.js +314 -0
- package/dist/chunk-MDIZUVIK.js +87 -0
- package/dist/chunk-MV6LW5UB.js +217 -0
- package/dist/chunk-NYLON2VC.js +6 -0
- package/dist/chunk-P6MYZMXQ.js +951 -0
- package/dist/chunk-Q53N43LQ.js +13 -0
- package/dist/chunk-QJVPRD3R.js +573 -0
- package/dist/chunk-RIGLQ4BV.js +3367 -0
- package/dist/chunk-SABTXESR.js +24 -0
- package/dist/chunk-SHUYVCID.js +6 -0
- package/dist/chunk-SYEVGPFC.js +686 -0
- package/dist/chunk-V7HUZ6UQ.js +5923 -0
- package/dist/chunk-WPUVJRIN.js +29 -0
- package/dist/chunk-XCZ3TTFK.js +22 -0
- package/dist/chunk-XSGU2Y2O.js +1611 -0
- package/dist/chunk-YNEW3L63.js +190 -0
- package/dist/chunk-YXGHPILG.js +614 -0
- package/dist/chunk-ZL4LLE2Z.js +465 -0
- package/dist/di-CcNeq9v-.d.cts +16 -0
- package/dist/di-CcNeq9v-.d.ts +16 -0
- package/dist/discovery/index.cjs +588 -0
- package/dist/discovery/index.d.cts +299 -0
- package/dist/discovery/index.d.ts +299 -0
- package/dist/discovery/index.js +49 -0
- package/dist/discovery.scanner-CUYLKlYT.d.cts +59 -0
- package/dist/discovery.scanner-CUYLKlYT.d.ts +59 -0
- package/dist/entitlement/index.cjs +805 -0
- package/dist/entitlement/index.d.cts +60 -0
- package/dist/entitlement/index.d.ts +60 -0
- package/dist/entitlement/index.js +57 -0
- package/dist/index.cjs +15918 -0
- package/dist/index.d.cts +85 -0
- package/dist/index.d.ts +85 -0
- package/dist/index.js +956 -0
- package/dist/plan-resolution-CFCoUkrE.d.cts +41 -0
- package/dist/plan-resolution-CFCoUkrE.d.ts +41 -0
- package/dist/platform/index.cjs +2772 -0
- package/dist/platform/index.d.cts +118 -0
- package/dist/platform/index.d.ts +118 -0
- package/dist/platform/index.js +37 -0
- package/dist/promo/index.cjs +995 -0
- package/dist/promo/index.d.cts +121 -0
- package/dist/promo/index.d.ts +121 -0
- package/dist/promo/index.js +58 -0
- package/dist/registration/index.cjs +1687 -0
- package/dist/registration/index.d.cts +444 -0
- package/dist/registration/index.d.ts +444 -0
- package/dist/registration/index.js +82 -0
- package/dist/saas-platform.module-CgZ2omgg.d.cts +180 -0
- package/dist/saas-platform.module-DT9TnCzk.d.ts +180 -0
- package/dist/service-DX8KbGXl.d.cts +109 -0
- package/dist/service-DX8KbGXl.d.ts +109 -0
- package/dist/subscription-contract/index.cjs +471 -0
- package/dist/subscription-contract/index.d.cts +17 -0
- package/dist/subscription-contract/index.d.ts +17 -0
- package/dist/subscription-contract/index.js +21 -0
- package/dist/subscription-contract.service--cm47ZJJ.d.cts +39 -0
- package/dist/subscription-contract.service--cm47ZJJ.d.ts +39 -0
- package/dist/testing/index.cjs +3942 -0
- package/dist/testing/index.d.cts +279 -0
- package/dist/testing/index.d.ts +279 -0
- package/dist/testing/index.js +1195 -0
- package/package.json +185 -0
|
@@ -0,0 +1,2772 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
9
|
+
var __export = (target, all) => {
|
|
10
|
+
for (var name in all)
|
|
11
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
12
|
+
};
|
|
13
|
+
var __copyProps = (to, from3, except, desc) => {
|
|
14
|
+
if (from3 && typeof from3 === "object" || typeof from3 === "function") {
|
|
15
|
+
for (let key of __getOwnPropNames(from3))
|
|
16
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
17
|
+
__defProp(to, key, { get: () => from3[key], enumerable: !(desc = __getOwnPropDesc(from3, key)) || desc.enumerable });
|
|
18
|
+
}
|
|
19
|
+
return to;
|
|
20
|
+
};
|
|
21
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
22
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
23
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
24
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
25
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
26
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
27
|
+
mod
|
|
28
|
+
));
|
|
29
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
30
|
+
|
|
31
|
+
// src/platform/index.ts
|
|
32
|
+
var platform_exports = {};
|
|
33
|
+
__export(platform_exports, {
|
|
34
|
+
EnforceQuotaInterceptor: () => EnforceQuotaInterceptor,
|
|
35
|
+
PLAN_RESOLVER_PORT_TOKEN: () => PLAN_RESOLVER_PORT_TOKEN,
|
|
36
|
+
QUOTA_PROVIDERS_TOKEN: () => QUOTA_PROVIDERS_TOKEN,
|
|
37
|
+
STATIC_FEATURE_GUARD_CONFIG_TOKEN: () => STATIC_FEATURE_GUARD_CONFIG_TOKEN,
|
|
38
|
+
SaasPlatformModule: () => SaasPlatformModule,
|
|
39
|
+
StaticEntitlementService: () => StaticEntitlementService,
|
|
40
|
+
StaticFeatureGuard: () => StaticFeatureGuard,
|
|
41
|
+
StaticPlanResolver: () => StaticPlanResolver,
|
|
42
|
+
TenantManifestService: () => TenantManifestService,
|
|
43
|
+
buildTenantManifestController: () => buildTenantManifestController
|
|
44
|
+
});
|
|
45
|
+
module.exports = __toCommonJS(platform_exports);
|
|
46
|
+
|
|
47
|
+
// src/platform/saas-platform.module.ts
|
|
48
|
+
var import_common22 = require("@nestjs/common");
|
|
49
|
+
var import_core6 = require("@nestjs/core");
|
|
50
|
+
|
|
51
|
+
// src/core/di.ts
|
|
52
|
+
function asProvider(token, impl) {
|
|
53
|
+
if (typeof impl === "object" && impl !== null && "useFactory" in impl && typeof impl.useFactory === "function") {
|
|
54
|
+
const factory = impl;
|
|
55
|
+
return {
|
|
56
|
+
provide: token,
|
|
57
|
+
useFactory: factory.useFactory,
|
|
58
|
+
inject: factory.inject
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
provide: token,
|
|
63
|
+
useValue: impl
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
__name(asProvider, "asProvider");
|
|
67
|
+
|
|
68
|
+
// src/admin/module.ts
|
|
69
|
+
var import_common6 = require("@nestjs/common");
|
|
70
|
+
|
|
71
|
+
// src/admin/admin-audit.service.ts
|
|
72
|
+
var os = __toESM(require("os"), 1);
|
|
73
|
+
var import_common = require("@nestjs/common");
|
|
74
|
+
|
|
75
|
+
// src/admin/tokens.ts
|
|
76
|
+
var MFA_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/MfaPort");
|
|
77
|
+
var AUDIT_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/AuditPort");
|
|
78
|
+
var RLS_BYPASS_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/RlsBypassPort");
|
|
79
|
+
|
|
80
|
+
// src/admin/admin-audit.service.ts
|
|
81
|
+
function _ts_decorate(decorators, target, key, desc) {
|
|
82
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
83
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
84
|
+
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;
|
|
85
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
86
|
+
}
|
|
87
|
+
__name(_ts_decorate, "_ts_decorate");
|
|
88
|
+
function _ts_metadata(k, v) {
|
|
89
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
90
|
+
}
|
|
91
|
+
__name(_ts_metadata, "_ts_metadata");
|
|
92
|
+
function _ts_param(paramIndex, decorator) {
|
|
93
|
+
return function(target, key) {
|
|
94
|
+
decorator(target, key, paramIndex);
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
__name(_ts_param, "_ts_param");
|
|
98
|
+
var AdminAuditService = class {
|
|
99
|
+
static {
|
|
100
|
+
__name(this, "AdminAuditService");
|
|
101
|
+
}
|
|
102
|
+
audit;
|
|
103
|
+
constructor(audit) {
|
|
104
|
+
this.audit = audit;
|
|
105
|
+
}
|
|
106
|
+
async log(input) {
|
|
107
|
+
await this.audit.write({
|
|
108
|
+
actor: input.actor,
|
|
109
|
+
entity: input.entity,
|
|
110
|
+
entityId: input.entityId,
|
|
111
|
+
action: input.action,
|
|
112
|
+
changes: {
|
|
113
|
+
...input.changes ?? {},
|
|
114
|
+
actor: this.actorTag(input.actor)
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Standardized actor tag for the `changes.actor` column:
|
|
120
|
+
* `<source>:<email>:<context>`.
|
|
121
|
+
*/
|
|
122
|
+
actorTag(actor) {
|
|
123
|
+
return `${actor.source}:${actor.email}:${actor.context}`;
|
|
124
|
+
}
|
|
125
|
+
/** Helper: build an AdminActor from an authenticated web request. */
|
|
126
|
+
fromWebRequest(user, sessionId) {
|
|
127
|
+
return {
|
|
128
|
+
userId: user.id,
|
|
129
|
+
email: user.email,
|
|
130
|
+
source: "web",
|
|
131
|
+
context: sessionId ?? "unknown"
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
/** Helper: build an AdminActor from a CLI identity (hostname as context). */
|
|
135
|
+
fromCli(user) {
|
|
136
|
+
return {
|
|
137
|
+
userId: user.id,
|
|
138
|
+
email: user.email,
|
|
139
|
+
source: "cli",
|
|
140
|
+
context: os.hostname()
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
};
|
|
144
|
+
AdminAuditService = _ts_decorate([
|
|
145
|
+
(0, import_common.Injectable)(),
|
|
146
|
+
_ts_param(0, (0, import_common.Inject)(AUDIT_PORT_TOKEN)),
|
|
147
|
+
_ts_metadata("design:type", Function),
|
|
148
|
+
_ts_metadata("design:paramtypes", [
|
|
149
|
+
typeof AuditPort === "undefined" ? Object : AuditPort
|
|
150
|
+
])
|
|
151
|
+
], AdminAuditService);
|
|
152
|
+
|
|
153
|
+
// src/admin/admin-bypass-rls.interceptor.ts
|
|
154
|
+
var import_common2 = require("@nestjs/common");
|
|
155
|
+
var import_rxjs = require("rxjs");
|
|
156
|
+
var import_operators = require("rxjs/operators");
|
|
157
|
+
function _ts_decorate2(decorators, target, key, desc) {
|
|
158
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
159
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
160
|
+
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;
|
|
161
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
162
|
+
}
|
|
163
|
+
__name(_ts_decorate2, "_ts_decorate");
|
|
164
|
+
function _ts_metadata2(k, v) {
|
|
165
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
166
|
+
}
|
|
167
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
168
|
+
function _ts_param2(paramIndex, decorator) {
|
|
169
|
+
return function(target, key) {
|
|
170
|
+
decorator(target, key, paramIndex);
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
__name(_ts_param2, "_ts_param");
|
|
174
|
+
var AdminBypassRlsInterceptor = class {
|
|
175
|
+
static {
|
|
176
|
+
__name(this, "AdminBypassRlsInterceptor");
|
|
177
|
+
}
|
|
178
|
+
port;
|
|
179
|
+
constructor(port) {
|
|
180
|
+
this.port = port;
|
|
181
|
+
}
|
|
182
|
+
intercept(_context, next) {
|
|
183
|
+
return (0, import_rxjs.defer)(() => (0, import_rxjs.from)(this.port.runWithBypass(async () => {
|
|
184
|
+
return next.handle();
|
|
185
|
+
})).pipe((0, import_operators.switchAll)()));
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
AdminBypassRlsInterceptor = _ts_decorate2([
|
|
189
|
+
(0, import_common2.Injectable)(),
|
|
190
|
+
_ts_param2(0, (0, import_common2.Inject)(RLS_BYPASS_PORT_TOKEN)),
|
|
191
|
+
_ts_metadata2("design:type", Function),
|
|
192
|
+
_ts_metadata2("design:paramtypes", [
|
|
193
|
+
typeof RlsBypassPort === "undefined" ? Object : RlsBypassPort
|
|
194
|
+
])
|
|
195
|
+
], AdminBypassRlsInterceptor);
|
|
196
|
+
|
|
197
|
+
// src/admin/mfa.ts
|
|
198
|
+
var import_common3 = require("@nestjs/common");
|
|
199
|
+
var import_otplib = require("otplib");
|
|
200
|
+
function _ts_decorate3(decorators, target, key, desc) {
|
|
201
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
202
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
203
|
+
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;
|
|
204
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
205
|
+
}
|
|
206
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
207
|
+
function _ts_metadata3(k, v) {
|
|
208
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
209
|
+
}
|
|
210
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
211
|
+
function _ts_param3(paramIndex, decorator) {
|
|
212
|
+
return function(target, key) {
|
|
213
|
+
decorator(target, key, paramIndex);
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
__name(_ts_param3, "_ts_param");
|
|
217
|
+
var CLOCK_DRIFT_TOLERANCE_SECONDS = 30;
|
|
218
|
+
var OTPLIB_TOKEN_ERROR_NAMES = /* @__PURE__ */ new Set([
|
|
219
|
+
"TokenError",
|
|
220
|
+
"TokenFormatError",
|
|
221
|
+
"TokenLengthError"
|
|
222
|
+
]);
|
|
223
|
+
function isOtplibTokenError(error) {
|
|
224
|
+
return error instanceof Error && OTPLIB_TOKEN_ERROR_NAMES.has(error.name);
|
|
225
|
+
}
|
|
226
|
+
__name(isOtplibTokenError, "isOtplibTokenError");
|
|
227
|
+
var MfaService = class _MfaService {
|
|
228
|
+
static {
|
|
229
|
+
__name(this, "MfaService");
|
|
230
|
+
}
|
|
231
|
+
mfa;
|
|
232
|
+
logger = new import_common3.Logger(_MfaService.name);
|
|
233
|
+
constructor(mfa) {
|
|
234
|
+
this.mfa = mfa;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Generates a new TOTP secret, persists it via `MfaPort.setSecret`
|
|
238
|
+
* and returns the secret + the otpauth URI for the QR code.
|
|
239
|
+
*
|
|
240
|
+
* `issuer` and `label` shape the display in the authenticator (e.g.
|
|
241
|
+
* "DemoApp:taci@example.com").
|
|
242
|
+
*/
|
|
243
|
+
async setup(userId, label, issuer) {
|
|
244
|
+
const secret = (0, import_otplib.generateSecret)();
|
|
245
|
+
const otpauthUri = (0, import_otplib.generateURI)({
|
|
246
|
+
issuer,
|
|
247
|
+
label,
|
|
248
|
+
secret
|
|
249
|
+
});
|
|
250
|
+
await this.mfa.setSecret(userId, secret);
|
|
251
|
+
return {
|
|
252
|
+
secret,
|
|
253
|
+
otpauthUri
|
|
254
|
+
};
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Verifies the entered code against the stored secret,
|
|
258
|
+
* clock-drift-tolerant by ±1 time step.
|
|
259
|
+
*/
|
|
260
|
+
async verify(input) {
|
|
261
|
+
const secret = await this.mfa.getSecret(input.userId);
|
|
262
|
+
if (!secret) return false;
|
|
263
|
+
try {
|
|
264
|
+
const result = await (0, import_otplib.verify)({
|
|
265
|
+
secret,
|
|
266
|
+
token: input.code.trim(),
|
|
267
|
+
epochTolerance: CLOCK_DRIFT_TOLERANCE_SECONDS
|
|
268
|
+
});
|
|
269
|
+
return result.valid;
|
|
270
|
+
} catch (error) {
|
|
271
|
+
if (isOtplibTokenError(error)) {
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
275
|
+
this.logger.warn(`TOTP-Verify f\xFCr User ${input.userId} unerwartet fehlgeschlagen: ${message}`);
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
/**
|
|
280
|
+
* Deletes a user's secret — used by `ahp admin mfa-reset`.
|
|
281
|
+
*/
|
|
282
|
+
async disable(userId) {
|
|
283
|
+
await this.mfa.setSecret(userId, null);
|
|
284
|
+
}
|
|
285
|
+
/** Delegates to `MfaPort.isEnabled`. */
|
|
286
|
+
async isEnabled(userId) {
|
|
287
|
+
return this.mfa.isEnabled(userId);
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
MfaService = _ts_decorate3([
|
|
291
|
+
(0, import_common3.Injectable)(),
|
|
292
|
+
_ts_param3(0, (0, import_common3.Inject)(MFA_PORT_TOKEN)),
|
|
293
|
+
_ts_metadata3("design:type", Function),
|
|
294
|
+
_ts_metadata3("design:paramtypes", [
|
|
295
|
+
typeof MfaPort === "undefined" ? Object : MfaPort
|
|
296
|
+
])
|
|
297
|
+
], MfaService);
|
|
298
|
+
|
|
299
|
+
// src/admin/mfa.guard.ts
|
|
300
|
+
var import_common4 = require("@nestjs/common");
|
|
301
|
+
var import_core = require("@nestjs/core");
|
|
302
|
+
function _ts_decorate4(decorators, target, key, desc) {
|
|
303
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
304
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
305
|
+
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;
|
|
306
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
307
|
+
}
|
|
308
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
309
|
+
function _ts_metadata4(k, v) {
|
|
310
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
311
|
+
}
|
|
312
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
313
|
+
function _ts_param4(paramIndex, decorator) {
|
|
314
|
+
return function(target, key) {
|
|
315
|
+
decorator(target, key, paramIndex);
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
__name(_ts_param4, "_ts_param");
|
|
319
|
+
var REQUIRE_MFA_KEY = "saas-platform/require-mfa";
|
|
320
|
+
var MfaGuard = class _MfaGuard {
|
|
321
|
+
static {
|
|
322
|
+
__name(this, "MfaGuard");
|
|
323
|
+
}
|
|
324
|
+
reflector;
|
|
325
|
+
mfaService;
|
|
326
|
+
logger = new import_common4.Logger(_MfaGuard.name);
|
|
327
|
+
constructor(reflector, mfaService) {
|
|
328
|
+
this.reflector = reflector;
|
|
329
|
+
this.mfaService = mfaService;
|
|
330
|
+
}
|
|
331
|
+
async canActivate(context) {
|
|
332
|
+
const required = this.reflector.getAllAndOverride(REQUIRE_MFA_KEY, [
|
|
333
|
+
context.getHandler(),
|
|
334
|
+
context.getClass()
|
|
335
|
+
]);
|
|
336
|
+
if (!required) return true;
|
|
337
|
+
if (process.env.SAAS_PLATFORM_SKIP_MFA === "1" && process.env.NODE_ENV !== "production") {
|
|
338
|
+
return true;
|
|
339
|
+
}
|
|
340
|
+
const request = context.switchToHttp().getRequest();
|
|
341
|
+
const user = request.user;
|
|
342
|
+
if (!user) throw new import_common4.UnauthorizedException({
|
|
343
|
+
reason: "NOT_AUTHENTICATED"
|
|
344
|
+
});
|
|
345
|
+
const enabled = await this.mfaService.isEnabled(user.id);
|
|
346
|
+
if (!enabled) {
|
|
347
|
+
throw new import_common4.UnauthorizedException({
|
|
348
|
+
reason: "MFA_NOT_SET_UP",
|
|
349
|
+
message: "Bitte zuerst MFA-Setup \xFCber das CLI ausf\xFChren."
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
const headerVal = request.headers?.["x-mfa-code"];
|
|
353
|
+
const code = Array.isArray(headerVal) ? headerVal[0] : headerVal;
|
|
354
|
+
if (!code) {
|
|
355
|
+
throw new import_common4.UnauthorizedException({
|
|
356
|
+
reason: "MFA_REQUIRED",
|
|
357
|
+
message: "TOTP-Code im Header X-Mfa-Code erforderlich."
|
|
358
|
+
});
|
|
359
|
+
}
|
|
360
|
+
const valid = await this.mfaService.verify({
|
|
361
|
+
userId: user.id,
|
|
362
|
+
code: String(code)
|
|
363
|
+
});
|
|
364
|
+
if (!valid) {
|
|
365
|
+
throw new import_common4.UnauthorizedException({
|
|
366
|
+
reason: "MFA_FAILED",
|
|
367
|
+
message: "TOTP-Code ung\xFCltig."
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
return true;
|
|
371
|
+
}
|
|
372
|
+
};
|
|
373
|
+
MfaGuard = _ts_decorate4([
|
|
374
|
+
(0, import_common4.Injectable)(),
|
|
375
|
+
_ts_param4(0, (0, import_common4.Inject)(import_core.Reflector)),
|
|
376
|
+
_ts_metadata4("design:type", Function),
|
|
377
|
+
_ts_metadata4("design:paramtypes", [
|
|
378
|
+
typeof import_core.Reflector === "undefined" ? Object : import_core.Reflector,
|
|
379
|
+
typeof MfaService === "undefined" ? Object : MfaService
|
|
380
|
+
])
|
|
381
|
+
], MfaGuard);
|
|
382
|
+
|
|
383
|
+
// src/admin/super-admin.guard.ts
|
|
384
|
+
var import_common5 = require("@nestjs/common");
|
|
385
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
386
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
387
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
388
|
+
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;
|
|
389
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
390
|
+
}
|
|
391
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
392
|
+
var SuperAdminGuard = class {
|
|
393
|
+
static {
|
|
394
|
+
__name(this, "SuperAdminGuard");
|
|
395
|
+
}
|
|
396
|
+
canActivate(context) {
|
|
397
|
+
const request = context.switchToHttp().getRequest();
|
|
398
|
+
if (!request.user) throw new import_common5.ForbiddenException("Nicht authentifiziert");
|
|
399
|
+
if (request.user.role !== "SUPER_ADMIN") {
|
|
400
|
+
throw new import_common5.ForbiddenException("Nur SUPER_ADMIN-Rolle erlaubt");
|
|
401
|
+
}
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
};
|
|
405
|
+
SuperAdminGuard = _ts_decorate5([
|
|
406
|
+
(0, import_common5.Injectable)()
|
|
407
|
+
], SuperAdminGuard);
|
|
408
|
+
|
|
409
|
+
// src/admin/module.ts
|
|
410
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
411
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
412
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
413
|
+
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;
|
|
414
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
415
|
+
}
|
|
416
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
417
|
+
var AdminModule = class _AdminModule {
|
|
418
|
+
static {
|
|
419
|
+
__name(this, "AdminModule");
|
|
420
|
+
}
|
|
421
|
+
static forRoot(options) {
|
|
422
|
+
const providers = [
|
|
423
|
+
asProvider(MFA_PORT_TOKEN, options.mfaPort),
|
|
424
|
+
asProvider(AUDIT_PORT_TOKEN, options.auditPort),
|
|
425
|
+
asProvider(RLS_BYPASS_PORT_TOKEN, options.rlsBypassPort),
|
|
426
|
+
MfaService,
|
|
427
|
+
AdminAuditService,
|
|
428
|
+
SuperAdminGuard,
|
|
429
|
+
MfaGuard,
|
|
430
|
+
AdminBypassRlsInterceptor
|
|
431
|
+
];
|
|
432
|
+
return {
|
|
433
|
+
module: _AdminModule,
|
|
434
|
+
global: options.global ?? false,
|
|
435
|
+
providers,
|
|
436
|
+
exports: [
|
|
437
|
+
MFA_PORT_TOKEN,
|
|
438
|
+
AUDIT_PORT_TOKEN,
|
|
439
|
+
RLS_BYPASS_PORT_TOKEN,
|
|
440
|
+
MfaService,
|
|
441
|
+
AdminAuditService,
|
|
442
|
+
SuperAdminGuard,
|
|
443
|
+
MfaGuard,
|
|
444
|
+
AdminBypassRlsInterceptor
|
|
445
|
+
]
|
|
446
|
+
};
|
|
447
|
+
}
|
|
448
|
+
};
|
|
449
|
+
AdminModule = _ts_decorate6([
|
|
450
|
+
(0, import_common6.Module)({})
|
|
451
|
+
], AdminModule);
|
|
452
|
+
|
|
453
|
+
// src/admin/admin-manifest.module.ts
|
|
454
|
+
var import_common9 = require("@nestjs/common");
|
|
455
|
+
|
|
456
|
+
// src/admin/admin-manifest.config.ts
|
|
457
|
+
var ADMIN_MANIFEST_CONFIG = /* @__PURE__ */ Symbol("ADMIN_MANIFEST_CONFIG");
|
|
458
|
+
|
|
459
|
+
// src/admin/admin-manifest.service.ts
|
|
460
|
+
var import_common7 = require("@nestjs/common");
|
|
461
|
+
var import_node_crypto = require("crypto");
|
|
462
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
463
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
464
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
465
|
+
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;
|
|
466
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
467
|
+
}
|
|
468
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
469
|
+
function _ts_metadata5(k, v) {
|
|
470
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
471
|
+
}
|
|
472
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
473
|
+
function _ts_param5(paramIndex, decorator) {
|
|
474
|
+
return function(target, key) {
|
|
475
|
+
decorator(target, key, paramIndex);
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
__name(_ts_param5, "_ts_param");
|
|
479
|
+
var PLATFORM_CORE_CONTRIBUTION_TOKEN = /* @__PURE__ */ Symbol("PLATFORM_CORE_CONTRIBUTION");
|
|
480
|
+
var AdminManifestService = class _AdminManifestService {
|
|
481
|
+
static {
|
|
482
|
+
__name(this, "AdminManifestService");
|
|
483
|
+
}
|
|
484
|
+
config;
|
|
485
|
+
logger = new import_common7.Logger(_AdminManifestService.name);
|
|
486
|
+
contributions = [];
|
|
487
|
+
cached = null;
|
|
488
|
+
constructor(config, platformCore = null) {
|
|
489
|
+
this.config = config;
|
|
490
|
+
if (platformCore) {
|
|
491
|
+
this.contributions.push(platformCore);
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
register(contribution) {
|
|
495
|
+
this.contributions.push(contribution);
|
|
496
|
+
this.cached = null;
|
|
497
|
+
}
|
|
498
|
+
getManifest() {
|
|
499
|
+
if (this.cached) return this.cached;
|
|
500
|
+
this.cached = this.build();
|
|
501
|
+
this.logger.log(`Manifest built: ${this.contributions.length} contribution(s), hash=${this.cached.build.manifestHash.slice(0, 24)}\u2026`);
|
|
502
|
+
return this.cached;
|
|
503
|
+
}
|
|
504
|
+
rebuild() {
|
|
505
|
+
this.cached = null;
|
|
506
|
+
return this.getManifest();
|
|
507
|
+
}
|
|
508
|
+
build() {
|
|
509
|
+
const merged = this.mergeContributions(this.contributions);
|
|
510
|
+
const draft = {
|
|
511
|
+
schemaVersion: 1,
|
|
512
|
+
project: this.config.project,
|
|
513
|
+
build: {
|
|
514
|
+
...this.config.build,
|
|
515
|
+
manifestHash: "sha256-pending"
|
|
516
|
+
},
|
|
517
|
+
planCatalogSnapshot: this.config.planCatalogSnapshot,
|
|
518
|
+
capabilities: merged.capabilities,
|
|
519
|
+
navigation: merged.navigation,
|
|
520
|
+
dashboard: merged.dashboard,
|
|
521
|
+
tenants: merged.tenants,
|
|
522
|
+
audit: merged.audit
|
|
523
|
+
};
|
|
524
|
+
return {
|
|
525
|
+
...draft,
|
|
526
|
+
build: {
|
|
527
|
+
...draft.build,
|
|
528
|
+
manifestHash: this.computeHash(draft)
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
}
|
|
532
|
+
mergeContributions(contributions) {
|
|
533
|
+
const capabilities = {};
|
|
534
|
+
const standardPages = {};
|
|
535
|
+
const projectPages = [];
|
|
536
|
+
const kpiCards = [];
|
|
537
|
+
const tenantColumns = [];
|
|
538
|
+
const tenantActions = [];
|
|
539
|
+
const auditActions = [];
|
|
540
|
+
for (const c of contributions) {
|
|
541
|
+
if (c.capabilities) Object.assign(capabilities, c.capabilities);
|
|
542
|
+
if (c.navigation?.standardPages) Object.assign(standardPages, c.navigation.standardPages);
|
|
543
|
+
if (c.navigation?.projectPages) projectPages.push(...c.navigation.projectPages);
|
|
544
|
+
if (c.dashboard?.kpiCards) kpiCards.push(...c.dashboard.kpiCards);
|
|
545
|
+
if (c.tenants?.columns) tenantColumns.push(...c.tenants.columns);
|
|
546
|
+
if (c.tenants?.actions) tenantActions.push(...c.tenants.actions);
|
|
547
|
+
if (c.audit?.actions) auditActions.push(...c.audit.actions);
|
|
548
|
+
}
|
|
549
|
+
kpiCards.sort((a, b) => (b.slotPriority ?? 0) - (a.slotPriority ?? 0));
|
|
550
|
+
return {
|
|
551
|
+
capabilities,
|
|
552
|
+
navigation: {
|
|
553
|
+
standardPages,
|
|
554
|
+
projectPages
|
|
555
|
+
},
|
|
556
|
+
dashboard: kpiCards.length > 0 ? {
|
|
557
|
+
kpiCards
|
|
558
|
+
} : void 0,
|
|
559
|
+
tenants: tenantColumns.length > 0 || tenantActions.length > 0 ? {
|
|
560
|
+
columns: tenantColumns,
|
|
561
|
+
actions: tenantActions
|
|
562
|
+
} : void 0,
|
|
563
|
+
audit: auditActions.length > 0 ? {
|
|
564
|
+
actions: auditActions
|
|
565
|
+
} : void 0
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
* Deterministic SHA-256 hash over the manifest. Uses the insertion
|
|
570
|
+
* order of the object keys — so that semantically meaningful reorders (e.g.
|
|
571
|
+
* the order of the sidebar items in `navigation.standardPages`) change the
|
|
572
|
+
* hash and invalidate the browser ETag cache. The manifest's own
|
|
573
|
+
* `manifestHash` field is hidden before serialization.
|
|
574
|
+
*/
|
|
575
|
+
computeHash(manifest) {
|
|
576
|
+
const canonical = stableStringify({
|
|
577
|
+
...manifest,
|
|
578
|
+
build: {
|
|
579
|
+
...manifest.build,
|
|
580
|
+
manifestHash: void 0
|
|
581
|
+
}
|
|
582
|
+
});
|
|
583
|
+
const digest = (0, import_node_crypto.createHash)("sha256").update(canonical).digest("base64url");
|
|
584
|
+
return `sha256-${digest}`;
|
|
585
|
+
}
|
|
586
|
+
};
|
|
587
|
+
AdminManifestService = _ts_decorate7([
|
|
588
|
+
(0, import_common7.Injectable)(),
|
|
589
|
+
_ts_param5(0, (0, import_common7.Inject)(ADMIN_MANIFEST_CONFIG)),
|
|
590
|
+
_ts_param5(1, (0, import_common7.Optional)()),
|
|
591
|
+
_ts_param5(1, (0, import_common7.Inject)(PLATFORM_CORE_CONTRIBUTION_TOKEN)),
|
|
592
|
+
_ts_metadata5("design:type", Function),
|
|
593
|
+
_ts_metadata5("design:paramtypes", [
|
|
594
|
+
typeof AdminManifestConfig === "undefined" ? Object : AdminManifestConfig,
|
|
595
|
+
Object
|
|
596
|
+
])
|
|
597
|
+
], AdminManifestService);
|
|
598
|
+
function stableStringify(value) {
|
|
599
|
+
if (value === null) return "null";
|
|
600
|
+
if (value === void 0) return "null";
|
|
601
|
+
if (typeof value === "number") return Number.isFinite(value) ? String(value) : "null";
|
|
602
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
603
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
604
|
+
if (Array.isArray(value)) {
|
|
605
|
+
return "[" + value.map(stableStringify).join(",") + "]";
|
|
606
|
+
}
|
|
607
|
+
if (typeof value === "object") {
|
|
608
|
+
const obj = value;
|
|
609
|
+
const keys = Object.keys(obj).filter((k) => obj[k] !== void 0);
|
|
610
|
+
return "{" + keys.map((k) => JSON.stringify(k) + ":" + stableStringify(obj[k])).join(",") + "}";
|
|
611
|
+
}
|
|
612
|
+
return JSON.stringify(value);
|
|
613
|
+
}
|
|
614
|
+
__name(stableStringify, "stableStringify");
|
|
615
|
+
|
|
616
|
+
// src/admin/admin-public-boot.controller.ts
|
|
617
|
+
var import_common8 = require("@nestjs/common");
|
|
618
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
619
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
620
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
621
|
+
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;
|
|
622
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
623
|
+
}
|
|
624
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
625
|
+
function _ts_metadata6(k, v) {
|
|
626
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
627
|
+
}
|
|
628
|
+
__name(_ts_metadata6, "_ts_metadata");
|
|
629
|
+
function _ts_param6(paramIndex, decorator) {
|
|
630
|
+
return function(target, key) {
|
|
631
|
+
decorator(target, key, paramIndex);
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
__name(_ts_param6, "_ts_param");
|
|
635
|
+
var AdminPublicBootController = class {
|
|
636
|
+
static {
|
|
637
|
+
__name(this, "AdminPublicBootController");
|
|
638
|
+
}
|
|
639
|
+
config;
|
|
640
|
+
constructor(config) {
|
|
641
|
+
this.config = config;
|
|
642
|
+
}
|
|
643
|
+
getBoot() {
|
|
644
|
+
return {
|
|
645
|
+
project: {
|
|
646
|
+
key: this.config.project.key,
|
|
647
|
+
displayName: this.config.project.displayName,
|
|
648
|
+
label: this.config.project.label,
|
|
649
|
+
icon: this.config.project.icon,
|
|
650
|
+
logoUrl: this.config.project.logoUrl,
|
|
651
|
+
environment: this.config.project.environment
|
|
652
|
+
}
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
};
|
|
656
|
+
_ts_decorate8([
|
|
657
|
+
(0, import_common8.Get)("boot"),
|
|
658
|
+
_ts_metadata6("design:type", Function),
|
|
659
|
+
_ts_metadata6("design:paramtypes", []),
|
|
660
|
+
_ts_metadata6("design:returntype", typeof PublicBootResponse === "undefined" ? Object : PublicBootResponse)
|
|
661
|
+
], AdminPublicBootController.prototype, "getBoot", null);
|
|
662
|
+
AdminPublicBootController = _ts_decorate8([
|
|
663
|
+
(0, import_common8.Controller)("admin"),
|
|
664
|
+
_ts_param6(0, (0, import_common8.Inject)(ADMIN_MANIFEST_CONFIG)),
|
|
665
|
+
_ts_metadata6("design:type", Function),
|
|
666
|
+
_ts_metadata6("design:paramtypes", [
|
|
667
|
+
typeof AdminManifestConfig === "undefined" ? Object : AdminManifestConfig
|
|
668
|
+
])
|
|
669
|
+
], AdminPublicBootController);
|
|
670
|
+
|
|
671
|
+
// src/admin/manifest-core.ts
|
|
672
|
+
var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
|
|
673
|
+
navigation: {
|
|
674
|
+
// Key order is render order in the sidebar (within the
|
|
675
|
+
// section, see `DEFAULT_NAV_SECTIONS` in the nav-builder). The order per
|
|
676
|
+
// section is deliberately curated:
|
|
677
|
+
// Overview: dashboard
|
|
678
|
+
// Product catalog: discovery → bundles → businessTypes → plans →
|
|
679
|
+
// planVersions → marketingCatalog → promoCodes
|
|
680
|
+
// (promoCodes is appended by the app contributions
|
|
681
|
+
// and therefore lands at the end of the section)
|
|
682
|
+
// Customers: tenants → users → pilots
|
|
683
|
+
// System: audit
|
|
684
|
+
standardPages: {
|
|
685
|
+
dashboard: {
|
|
686
|
+
enabled: true,
|
|
687
|
+
requiredCapability: "dashboard.read"
|
|
688
|
+
},
|
|
689
|
+
// SPEC_V2 §3.3 — discovery snapshot of the running backend.
|
|
690
|
+
// Apps must set `discovery.read` in their manifest capabilities
|
|
691
|
+
// to `true` so that the NavBuilder wires the page into the
|
|
692
|
+
// sidebar; otherwise it is filtered out automatically.
|
|
693
|
+
discovery: {
|
|
694
|
+
enabled: true,
|
|
695
|
+
requiredCapability: "discovery.read"
|
|
696
|
+
},
|
|
697
|
+
// SPEC_V2 §11.1 M3 — Bundle editor in SuperAdmin.
|
|
698
|
+
// Apps with a DB-driven catalog set `bundles.read = true`.
|
|
699
|
+
bundles: {
|
|
700
|
+
enabled: true,
|
|
701
|
+
requiredCapability: "bundles.read"
|
|
702
|
+
},
|
|
703
|
+
// SPEC_V2 §11.1 M3 — BusinessType editor in SuperAdmin.
|
|
704
|
+
// Apps with domain verticals set `businessTypes.read = true`.
|
|
705
|
+
businessTypes: {
|
|
706
|
+
enabled: true,
|
|
707
|
+
requiredCapability: "businessTypes.read"
|
|
708
|
+
},
|
|
709
|
+
plans: {
|
|
710
|
+
enabled: true,
|
|
711
|
+
requiredCapability: "plans.read"
|
|
712
|
+
},
|
|
713
|
+
planVersions: {
|
|
714
|
+
enabled: true,
|
|
715
|
+
requiredCapability: "plans.read"
|
|
716
|
+
},
|
|
717
|
+
// SPEC_V2 §11.1 M3 — marketing catalog (locale pivot).
|
|
718
|
+
// Apps that market a public catalog set
|
|
719
|
+
// `marketingProjections.read = true`.
|
|
720
|
+
marketingCatalog: {
|
|
721
|
+
enabled: true,
|
|
722
|
+
requiredCapability: "marketingProjections.read"
|
|
723
|
+
},
|
|
724
|
+
tenants: {
|
|
725
|
+
enabled: true,
|
|
726
|
+
requiredCapability: "tenants.read"
|
|
727
|
+
},
|
|
728
|
+
users: {
|
|
729
|
+
enabled: true,
|
|
730
|
+
requiredCapability: "users.read"
|
|
731
|
+
},
|
|
732
|
+
pilots: {
|
|
733
|
+
enabled: true,
|
|
734
|
+
requiredCapability: "pilots.read"
|
|
735
|
+
},
|
|
736
|
+
// Audit log deliberately at the end — rarely needed, should
|
|
737
|
+
// not obscure the productive pages.
|
|
738
|
+
audit: {
|
|
739
|
+
enabled: true,
|
|
740
|
+
requiredCapability: "audit.read"
|
|
741
|
+
},
|
|
742
|
+
// Platform email sender (system/registration mails). Apps
|
|
743
|
+
// set `platformEmail.read = true` so that the NavBuilder wires the page
|
|
744
|
+
// in; otherwise it is filtered out automatically.
|
|
745
|
+
platformEmail: {
|
|
746
|
+
enabled: true,
|
|
747
|
+
requiredCapability: "platformEmail.read"
|
|
748
|
+
},
|
|
749
|
+
// Platform email history (audit of the sent system mails). Apps
|
|
750
|
+
// set `platformEmailHistory.read = true` so that the NavBuilder wires the
|
|
751
|
+
// page in; otherwise it is filtered out automatically.
|
|
752
|
+
platformEmailHistory: {
|
|
753
|
+
enabled: true,
|
|
754
|
+
requiredCapability: "platformEmailHistory.read"
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
},
|
|
758
|
+
tenants: {
|
|
759
|
+
actions: [
|
|
760
|
+
{
|
|
761
|
+
id: "platform.tenants.suspend",
|
|
762
|
+
label: "Mandant suspendieren",
|
|
763
|
+
actionKey: "tenants.suspend",
|
|
764
|
+
requiredCapability: "tenants.suspend",
|
|
765
|
+
requiresMfa: true,
|
|
766
|
+
confirmType: "typed-slug"
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
id: "platform.tenants.reactivate",
|
|
770
|
+
label: "Mandant reaktivieren",
|
|
771
|
+
actionKey: "tenants.reactivate",
|
|
772
|
+
requiredCapability: "tenants.reactivate",
|
|
773
|
+
requiresMfa: true,
|
|
774
|
+
confirmType: "simple"
|
|
775
|
+
},
|
|
776
|
+
{
|
|
777
|
+
id: "platform.tenants.impersonate",
|
|
778
|
+
label: "Impersonate",
|
|
779
|
+
actionKey: "tenants.impersonate",
|
|
780
|
+
requiredCapability: "tenants.impersonate",
|
|
781
|
+
requiresMfa: true,
|
|
782
|
+
confirmType: "simple"
|
|
783
|
+
},
|
|
784
|
+
{
|
|
785
|
+
id: "platform.tenants.export",
|
|
786
|
+
label: "DSGVO-Export",
|
|
787
|
+
actionKey: "tenants.export",
|
|
788
|
+
requiredCapability: "tenants.export",
|
|
789
|
+
requiresMfa: true,
|
|
790
|
+
confirmType: "simple"
|
|
791
|
+
},
|
|
792
|
+
{
|
|
793
|
+
id: "platform.subscriptions.cancel",
|
|
794
|
+
label: "Subscription k\xFCndigen",
|
|
795
|
+
actionKey: "subscriptions.cancel",
|
|
796
|
+
requiredCapability: "subscriptions.cancel",
|
|
797
|
+
requiresMfa: true,
|
|
798
|
+
confirmType: "typed-slug"
|
|
799
|
+
},
|
|
800
|
+
{
|
|
801
|
+
id: "platform.pilots.grant",
|
|
802
|
+
label: "Pilot-Status setzen",
|
|
803
|
+
actionKey: "pilots.grant",
|
|
804
|
+
requiredCapability: "pilots.grant",
|
|
805
|
+
requiresMfa: true,
|
|
806
|
+
confirmType: "simple"
|
|
807
|
+
},
|
|
808
|
+
{
|
|
809
|
+
id: "platform.pilots.revoke",
|
|
810
|
+
label: "Pilot-Status entziehen",
|
|
811
|
+
actionKey: "pilots.revoke",
|
|
812
|
+
requiredCapability: "pilots.revoke",
|
|
813
|
+
requiresMfa: true,
|
|
814
|
+
confirmType: "typed-slug"
|
|
815
|
+
},
|
|
816
|
+
{
|
|
817
|
+
id: "platform.pilots.extend",
|
|
818
|
+
label: "Pilot verl\xE4ngern",
|
|
819
|
+
actionKey: "pilots.extend",
|
|
820
|
+
requiredCapability: "pilots.extend",
|
|
821
|
+
requiresMfa: true,
|
|
822
|
+
confirmType: "date"
|
|
823
|
+
}
|
|
824
|
+
]
|
|
825
|
+
},
|
|
826
|
+
audit: {
|
|
827
|
+
actions: [
|
|
828
|
+
{
|
|
829
|
+
key: "TENANT_SUSPEND",
|
|
830
|
+
label: "Mandant suspendiert",
|
|
831
|
+
severity: "high"
|
|
832
|
+
},
|
|
833
|
+
{
|
|
834
|
+
key: "TENANT_REACTIVATE",
|
|
835
|
+
label: "Mandant reaktiviert",
|
|
836
|
+
severity: "medium"
|
|
837
|
+
},
|
|
838
|
+
{
|
|
839
|
+
key: "TENANT_IMPERSONATE",
|
|
840
|
+
label: "Impersonation gestartet",
|
|
841
|
+
severity: "high"
|
|
842
|
+
},
|
|
843
|
+
{
|
|
844
|
+
key: "TENANT_EXPORT",
|
|
845
|
+
label: "DSGVO-Export ausgel\xF6st",
|
|
846
|
+
severity: "medium"
|
|
847
|
+
},
|
|
848
|
+
{
|
|
849
|
+
key: "PILOT_CREATE",
|
|
850
|
+
label: "Pilot-Tenant angelegt",
|
|
851
|
+
severity: "medium"
|
|
852
|
+
},
|
|
853
|
+
{
|
|
854
|
+
key: "PILOT_GRANT",
|
|
855
|
+
label: "Pilot-Status gesetzt",
|
|
856
|
+
severity: "medium"
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
key: "PILOT_REVOKE",
|
|
860
|
+
label: "Pilot-Status entzogen",
|
|
861
|
+
severity: "medium"
|
|
862
|
+
},
|
|
863
|
+
{
|
|
864
|
+
key: "PILOT_EXTEND",
|
|
865
|
+
label: "Pilot verl\xE4ngert",
|
|
866
|
+
severity: "low"
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
key: "PLAN_VERSION_PUBLISH",
|
|
870
|
+
label: "Plan-Version freigegeben",
|
|
871
|
+
severity: "high"
|
|
872
|
+
},
|
|
873
|
+
{
|
|
874
|
+
key: "PLAN_VERSION_TERMINATE",
|
|
875
|
+
label: "Plan-Version terminiert",
|
|
876
|
+
severity: "high"
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
key: "SUBSCRIPTION_CANCEL",
|
|
880
|
+
label: "Subscription gek\xFCndigt",
|
|
881
|
+
severity: "high"
|
|
882
|
+
},
|
|
883
|
+
{
|
|
884
|
+
key: "USER_RESET_PASSWORD",
|
|
885
|
+
label: "Passwort-Reset ausgel\xF6st",
|
|
886
|
+
severity: "medium"
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
key: "USER_DEACTIVATE",
|
|
890
|
+
label: "User deaktiviert",
|
|
891
|
+
severity: "high"
|
|
892
|
+
},
|
|
893
|
+
{
|
|
894
|
+
key: "USER_REASSIGN_ADMIN",
|
|
895
|
+
label: "Admin-Rolle \xFCbertragen",
|
|
896
|
+
severity: "high"
|
|
897
|
+
},
|
|
898
|
+
{
|
|
899
|
+
key: "PLATFORM_EMAIL_PROVIDER_CREATE",
|
|
900
|
+
label: "Plattform-Absender angelegt",
|
|
901
|
+
severity: "medium"
|
|
902
|
+
},
|
|
903
|
+
{
|
|
904
|
+
key: "PLATFORM_EMAIL_PROVIDER_UPDATE",
|
|
905
|
+
label: "Plattform-Absender ge\xE4ndert",
|
|
906
|
+
severity: "medium"
|
|
907
|
+
},
|
|
908
|
+
{
|
|
909
|
+
key: "PLATFORM_EMAIL_PROVIDER_DELETE",
|
|
910
|
+
label: "Plattform-Absender gel\xF6scht",
|
|
911
|
+
severity: "medium"
|
|
912
|
+
},
|
|
913
|
+
{
|
|
914
|
+
key: "PLATFORM_EMAIL_PROVIDER_TEST",
|
|
915
|
+
label: "Plattform-Absender Test-Versand",
|
|
916
|
+
severity: "low"
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
key: "EMAIL_HISTORY_DELETE",
|
|
920
|
+
label: "Plattform-E-Mail aus Verlauf entfernt",
|
|
921
|
+
severity: "medium"
|
|
922
|
+
},
|
|
923
|
+
{
|
|
924
|
+
key: "EMAIL_HISTORY_RESEND",
|
|
925
|
+
label: "Plattform-E-Mail erneut versendet",
|
|
926
|
+
severity: "low"
|
|
927
|
+
}
|
|
928
|
+
]
|
|
929
|
+
}
|
|
930
|
+
};
|
|
931
|
+
|
|
932
|
+
// src/admin/admin-manifest.module.ts
|
|
933
|
+
function _ts_decorate9(decorators, target, key, desc) {
|
|
934
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
935
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
936
|
+
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;
|
|
937
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
938
|
+
}
|
|
939
|
+
__name(_ts_decorate9, "_ts_decorate");
|
|
940
|
+
function _ts_metadata7(k, v) {
|
|
941
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
942
|
+
}
|
|
943
|
+
__name(_ts_metadata7, "_ts_metadata");
|
|
944
|
+
function _ts_param7(paramIndex, decorator) {
|
|
945
|
+
return function(target, key) {
|
|
946
|
+
decorator(target, key, paramIndex);
|
|
947
|
+
};
|
|
948
|
+
}
|
|
949
|
+
__name(_ts_param7, "_ts_param");
|
|
950
|
+
function asConfigProvider(impl) {
|
|
951
|
+
if (typeof impl === "object" && impl !== null && "useFactory" in impl && typeof impl.useFactory === "function") {
|
|
952
|
+
return {
|
|
953
|
+
provide: ADMIN_MANIFEST_CONFIG,
|
|
954
|
+
useFactory: impl.useFactory,
|
|
955
|
+
inject: impl.inject
|
|
956
|
+
};
|
|
957
|
+
}
|
|
958
|
+
return {
|
|
959
|
+
provide: ADMIN_MANIFEST_CONFIG,
|
|
960
|
+
useValue: impl
|
|
961
|
+
};
|
|
962
|
+
}
|
|
963
|
+
__name(asConfigProvider, "asConfigProvider");
|
|
964
|
+
function buildManifestController(guards, reloadGuards) {
|
|
965
|
+
const reloadChain = [
|
|
966
|
+
...guards,
|
|
967
|
+
...reloadGuards
|
|
968
|
+
];
|
|
969
|
+
let GeneratedAdminManifestController = class GeneratedAdminManifestController {
|
|
970
|
+
static {
|
|
971
|
+
__name(this, "GeneratedAdminManifestController");
|
|
972
|
+
}
|
|
973
|
+
manifest;
|
|
974
|
+
// Explicit @Inject instead of type reflection: tsup/esbuild do not emit
|
|
975
|
+
// `design:paramtypes` metadata, so Nest cannot otherwise resolve the
|
|
976
|
+
// service type at the constructor.
|
|
977
|
+
constructor(manifest) {
|
|
978
|
+
this.manifest = manifest;
|
|
979
|
+
}
|
|
980
|
+
getManifest(ifNoneMatch, res) {
|
|
981
|
+
const m = this.manifest.getManifest();
|
|
982
|
+
const etag = `"${m.build.manifestHash}"`;
|
|
983
|
+
res.header("ETag", etag);
|
|
984
|
+
if (ifNoneMatch && ifNoneMatch === etag) {
|
|
985
|
+
res.status(import_common9.HttpStatus.NOT_MODIFIED);
|
|
986
|
+
return null;
|
|
987
|
+
}
|
|
988
|
+
return m;
|
|
989
|
+
}
|
|
990
|
+
reload() {
|
|
991
|
+
const m = this.manifest.rebuild();
|
|
992
|
+
return {
|
|
993
|
+
manifestHash: m.build.manifestHash,
|
|
994
|
+
reloadedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
995
|
+
};
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
_ts_decorate9([
|
|
999
|
+
(0, import_common9.Get)("manifest"),
|
|
1000
|
+
(0, import_common9.Header)("Cache-Control", "private, max-age=60, must-revalidate"),
|
|
1001
|
+
_ts_param7(0, (0, import_common9.Headers)("if-none-match")),
|
|
1002
|
+
_ts_param7(1, (0, import_common9.Res)({
|
|
1003
|
+
passthrough: true
|
|
1004
|
+
})),
|
|
1005
|
+
_ts_metadata7("design:type", Function),
|
|
1006
|
+
_ts_metadata7("design:paramtypes", [
|
|
1007
|
+
Object,
|
|
1008
|
+
typeof HttpResponseLike === "undefined" ? Object : HttpResponseLike
|
|
1009
|
+
]),
|
|
1010
|
+
_ts_metadata7("design:returntype", void 0)
|
|
1011
|
+
], GeneratedAdminManifestController.prototype, "getManifest", null);
|
|
1012
|
+
_ts_decorate9([
|
|
1013
|
+
(0, import_common9.Post)("manifest/reload"),
|
|
1014
|
+
(0, import_common9.UseGuards)(...reloadChain),
|
|
1015
|
+
(0, import_common9.HttpCode)(import_common9.HttpStatus.OK),
|
|
1016
|
+
_ts_metadata7("design:type", Function),
|
|
1017
|
+
_ts_metadata7("design:paramtypes", []),
|
|
1018
|
+
_ts_metadata7("design:returntype", void 0)
|
|
1019
|
+
], GeneratedAdminManifestController.prototype, "reload", null);
|
|
1020
|
+
GeneratedAdminManifestController = _ts_decorate9([
|
|
1021
|
+
(0, import_common9.Controller)("admin"),
|
|
1022
|
+
(0, import_common9.UseGuards)(...guards),
|
|
1023
|
+
_ts_param7(0, (0, import_common9.Inject)(AdminManifestService)),
|
|
1024
|
+
_ts_metadata7("design:type", Function),
|
|
1025
|
+
_ts_metadata7("design:paramtypes", [
|
|
1026
|
+
typeof AdminManifestService === "undefined" ? Object : AdminManifestService
|
|
1027
|
+
])
|
|
1028
|
+
], GeneratedAdminManifestController);
|
|
1029
|
+
return GeneratedAdminManifestController;
|
|
1030
|
+
}
|
|
1031
|
+
__name(buildManifestController, "buildManifestController");
|
|
1032
|
+
var AdminManifestModule = class _AdminManifestModule {
|
|
1033
|
+
static {
|
|
1034
|
+
__name(this, "AdminManifestModule");
|
|
1035
|
+
}
|
|
1036
|
+
static forRoot(options) {
|
|
1037
|
+
const includeManifestController = options.includeManifestController ?? true;
|
|
1038
|
+
const registerPlatformCore = options.registerPlatformCore ?? true;
|
|
1039
|
+
const controllers = [
|
|
1040
|
+
AdminPublicBootController
|
|
1041
|
+
];
|
|
1042
|
+
if (includeManifestController) {
|
|
1043
|
+
if (options.guards === void 0) {
|
|
1044
|
+
throw new Error("AdminManifestModule.forRoot: `guards` ist Pflicht, wenn `includeManifestController` aktiv ist (Default `true`). Setze `guards: [JwtAuthGuard, SuperAdminGuard]` (oder analog) \u2014 oder `guards: []`, um den Endpoint absichtlich auth-frei zu betreiben. Alternative: `includeManifestController: false` und einen eigenen Wrapping-Controller registrieren.");
|
|
1045
|
+
}
|
|
1046
|
+
controllers.push(buildManifestController(options.guards, options.reloadGuards ?? []));
|
|
1047
|
+
}
|
|
1048
|
+
const providers = [
|
|
1049
|
+
asConfigProvider(options.config),
|
|
1050
|
+
AdminManifestService,
|
|
1051
|
+
...options.extraProviders ?? []
|
|
1052
|
+
];
|
|
1053
|
+
if (registerPlatformCore) {
|
|
1054
|
+
providers.push({
|
|
1055
|
+
provide: PLATFORM_CORE_CONTRIBUTION_TOKEN,
|
|
1056
|
+
useValue: PLATFORM_CORE_MANIFEST_CONTRIBUTION
|
|
1057
|
+
});
|
|
1058
|
+
}
|
|
1059
|
+
return {
|
|
1060
|
+
module: _AdminManifestModule,
|
|
1061
|
+
global: options.global ?? false,
|
|
1062
|
+
imports: options.imports ?? [],
|
|
1063
|
+
controllers,
|
|
1064
|
+
providers,
|
|
1065
|
+
exports: [
|
|
1066
|
+
ADMIN_MANIFEST_CONFIG,
|
|
1067
|
+
AdminManifestService
|
|
1068
|
+
]
|
|
1069
|
+
};
|
|
1070
|
+
}
|
|
1071
|
+
};
|
|
1072
|
+
AdminManifestModule = _ts_decorate9([
|
|
1073
|
+
(0, import_common9.Module)({})
|
|
1074
|
+
], AdminManifestModule);
|
|
1075
|
+
|
|
1076
|
+
// src/billing/plan-catalog.module.ts
|
|
1077
|
+
var import_common10 = require("@nestjs/common");
|
|
1078
|
+
|
|
1079
|
+
// src/billing/plan-catalog-from-snapshot.ts
|
|
1080
|
+
function buildPlanCatalogFromSnapshot(settings, snapshot) {
|
|
1081
|
+
const liveByPlanKey = new Map(snapshot.livePlanVersions.map((v) => [
|
|
1082
|
+
v.planId,
|
|
1083
|
+
v
|
|
1084
|
+
]));
|
|
1085
|
+
const plans = snapshot.plans.filter((p) => p.deletedAt === null).sort((a, b) => a.sortOrder - b.sortOrder).map((stem) => {
|
|
1086
|
+
const live = liveByPlanKey.get(stem.planKey);
|
|
1087
|
+
if (!live) {
|
|
1088
|
+
return {
|
|
1089
|
+
id: stem.planKey,
|
|
1090
|
+
name: stem.label,
|
|
1091
|
+
tagline: stem.description ?? void 0,
|
|
1092
|
+
marketed: false,
|
|
1093
|
+
monthlyNet: null,
|
|
1094
|
+
yearlyNet: null,
|
|
1095
|
+
features: [],
|
|
1096
|
+
quotas: {}
|
|
1097
|
+
};
|
|
1098
|
+
}
|
|
1099
|
+
return {
|
|
1100
|
+
id: stem.planKey,
|
|
1101
|
+
name: stem.label,
|
|
1102
|
+
tagline: stem.description ?? void 0,
|
|
1103
|
+
marketed: live.marketed,
|
|
1104
|
+
monthlyNet: parseFloat(live.monthlyNet),
|
|
1105
|
+
yearlyNet: parseFloat(live.yearlyNet),
|
|
1106
|
+
features: live.features,
|
|
1107
|
+
quotas: live.quotas ?? {}
|
|
1108
|
+
};
|
|
1109
|
+
});
|
|
1110
|
+
const features = snapshot.featureEntries.filter((f) => f.deletedAt === null).sort((a, b) => a.sortOrder - b.sortOrder).map((row) => ({
|
|
1111
|
+
key: row.featureKey,
|
|
1112
|
+
label: row.label,
|
|
1113
|
+
icon: row.icon ?? void 0,
|
|
1114
|
+
tier: row.tier ?? void 0,
|
|
1115
|
+
plannedOnly: row.plannedOnly
|
|
1116
|
+
}));
|
|
1117
|
+
return {
|
|
1118
|
+
schemaVersion: 1,
|
|
1119
|
+
projectKey: settings.projectKey,
|
|
1120
|
+
...settings.app ? {
|
|
1121
|
+
app: settings.app
|
|
1122
|
+
} : {},
|
|
1123
|
+
currency: settings.currency,
|
|
1124
|
+
vatRate: settings.vatRate,
|
|
1125
|
+
...settings.marketing ? {
|
|
1126
|
+
marketing: settings.marketing
|
|
1127
|
+
} : {},
|
|
1128
|
+
features,
|
|
1129
|
+
plans
|
|
1130
|
+
};
|
|
1131
|
+
}
|
|
1132
|
+
__name(buildPlanCatalogFromSnapshot, "buildPlanCatalogFromSnapshot");
|
|
1133
|
+
|
|
1134
|
+
// src/billing/plan-catalog.module.ts
|
|
1135
|
+
function _ts_decorate10(decorators, target, key, desc) {
|
|
1136
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1137
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1138
|
+
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;
|
|
1139
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1140
|
+
}
|
|
1141
|
+
__name(_ts_decorate10, "_ts_decorate");
|
|
1142
|
+
var PLAN_CATALOG_TOKEN = /* @__PURE__ */ Symbol("PLAN_CATALOG");
|
|
1143
|
+
var PLAN_CATALOG_READ_SINK_TOKEN = /* @__PURE__ */ Symbol("PLAN_CATALOG_READ_SINK");
|
|
1144
|
+
var PlanCatalogModule = class _PlanCatalogModule {
|
|
1145
|
+
static {
|
|
1146
|
+
__name(this, "PlanCatalogModule");
|
|
1147
|
+
}
|
|
1148
|
+
static forRoot(options) {
|
|
1149
|
+
return {
|
|
1150
|
+
module: _PlanCatalogModule,
|
|
1151
|
+
global: options.global ?? true,
|
|
1152
|
+
imports: options.imports ?? [],
|
|
1153
|
+
providers: [
|
|
1154
|
+
...options.extraProviders ?? [],
|
|
1155
|
+
asProvider(PLAN_CATALOG_READ_SINK_TOKEN, options.sink),
|
|
1156
|
+
{
|
|
1157
|
+
provide: PLAN_CATALOG_TOKEN,
|
|
1158
|
+
useFactory: /* @__PURE__ */ __name(async (sink) => {
|
|
1159
|
+
const snapshot = await sink.loadSnapshot(options.projectKey);
|
|
1160
|
+
return buildPlanCatalogFromSnapshot({
|
|
1161
|
+
projectKey: options.projectKey,
|
|
1162
|
+
app: options.app,
|
|
1163
|
+
currency: options.currency,
|
|
1164
|
+
vatRate: options.vatRate,
|
|
1165
|
+
marketing: options.marketing
|
|
1166
|
+
}, snapshot);
|
|
1167
|
+
}, "useFactory"),
|
|
1168
|
+
inject: [
|
|
1169
|
+
PLAN_CATALOG_READ_SINK_TOKEN
|
|
1170
|
+
]
|
|
1171
|
+
}
|
|
1172
|
+
],
|
|
1173
|
+
exports: [
|
|
1174
|
+
PLAN_CATALOG_TOKEN,
|
|
1175
|
+
PLAN_CATALOG_READ_SINK_TOKEN
|
|
1176
|
+
]
|
|
1177
|
+
};
|
|
1178
|
+
}
|
|
1179
|
+
/**
|
|
1180
|
+
* Tests / in-memory setup: takes the catalog object directly, without a
|
|
1181
|
+
* sink/DB lookup. For unit tests + test bootstraps.
|
|
1182
|
+
*/
|
|
1183
|
+
static forRootWithCatalog(catalog, opts = {}) {
|
|
1184
|
+
return {
|
|
1185
|
+
module: _PlanCatalogModule,
|
|
1186
|
+
providers: [
|
|
1187
|
+
{
|
|
1188
|
+
provide: PLAN_CATALOG_TOKEN,
|
|
1189
|
+
useValue: catalog
|
|
1190
|
+
}
|
|
1191
|
+
],
|
|
1192
|
+
exports: [
|
|
1193
|
+
PLAN_CATALOG_TOKEN
|
|
1194
|
+
],
|
|
1195
|
+
global: opts.global ?? true
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
PlanCatalogModule = _ts_decorate10([
|
|
1200
|
+
(0, import_common10.Module)({})
|
|
1201
|
+
], PlanCatalogModule);
|
|
1202
|
+
|
|
1203
|
+
// src/discovery/discovery.module.ts
|
|
1204
|
+
var import_common13 = require("@nestjs/common");
|
|
1205
|
+
var import_core3 = require("@nestjs/core");
|
|
1206
|
+
|
|
1207
|
+
// src/discovery/discovery.controller.ts
|
|
1208
|
+
var import_common12 = require("@nestjs/common");
|
|
1209
|
+
|
|
1210
|
+
// src/discovery/discovery.scanner.ts
|
|
1211
|
+
var import_node_crypto2 = require("crypto");
|
|
1212
|
+
var import_node_fs = require("fs");
|
|
1213
|
+
var import_node_path = require("path");
|
|
1214
|
+
var import_common11 = require("@nestjs/common");
|
|
1215
|
+
var import_core2 = require("@nestjs/core");
|
|
1216
|
+
|
|
1217
|
+
// src/discovery/tokens.ts
|
|
1218
|
+
var IMPLEMENTS_CAPABILITY_KEY = "discovery:implements-capability";
|
|
1219
|
+
var DEFINES_QUOTA_KEY = "discovery:defines-quota";
|
|
1220
|
+
var ENFORCE_QUOTA_KEY = "discovery:enforce-quota";
|
|
1221
|
+
var DISCOVERY_SNAPSHOT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/DiscoverySnapshot");
|
|
1222
|
+
|
|
1223
|
+
// src/discovery/discovery.scanner.ts
|
|
1224
|
+
function _ts_decorate11(decorators, target, key, desc) {
|
|
1225
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1226
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1227
|
+
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;
|
|
1228
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1229
|
+
}
|
|
1230
|
+
__name(_ts_decorate11, "_ts_decorate");
|
|
1231
|
+
function _ts_metadata8(k, v) {
|
|
1232
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1233
|
+
}
|
|
1234
|
+
__name(_ts_metadata8, "_ts_metadata");
|
|
1235
|
+
function _ts_param8(paramIndex, decorator) {
|
|
1236
|
+
return function(target, key) {
|
|
1237
|
+
decorator(target, key, paramIndex);
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1240
|
+
__name(_ts_param8, "_ts_param");
|
|
1241
|
+
var DEFAULT_APP_INFO = {
|
|
1242
|
+
key: "unknown",
|
|
1243
|
+
version: "0.0.0"
|
|
1244
|
+
};
|
|
1245
|
+
var DISCOVERY_APP_INFO_TOKEN = /* @__PURE__ */ Symbol("DISCOVERY_APP_INFO");
|
|
1246
|
+
var DISCOVERY_SNAPSHOT_PATH_TOKEN = /* @__PURE__ */ Symbol("DISCOVERY_SNAPSHOT_PATH");
|
|
1247
|
+
var DiscoveryScanner = class _DiscoveryScanner {
|
|
1248
|
+
static {
|
|
1249
|
+
__name(this, "DiscoveryScanner");
|
|
1250
|
+
}
|
|
1251
|
+
discoveryService;
|
|
1252
|
+
metadataScanner;
|
|
1253
|
+
reflector;
|
|
1254
|
+
appInfo;
|
|
1255
|
+
snapshotPath;
|
|
1256
|
+
logger = new import_common11.Logger(_DiscoveryScanner.name);
|
|
1257
|
+
snapshot = null;
|
|
1258
|
+
// Explicit @Inject instead of type reflection: in the platform package
|
|
1259
|
+
// build tsup/esbuild do not emit `design:paramtypes` metadata, so Nest
|
|
1260
|
+
// cannot otherwise resolve the DI targets at the constructor
|
|
1261
|
+
// (UndefinedDependencyException at the consumer's boot). Pattern
|
|
1262
|
+
// analogous to admin/admin-manifest.module.ts.
|
|
1263
|
+
constructor(discoveryService, metadataScanner, reflector, appInfo = DEFAULT_APP_INFO, snapshotPath = null) {
|
|
1264
|
+
this.discoveryService = discoveryService;
|
|
1265
|
+
this.metadataScanner = metadataScanner;
|
|
1266
|
+
this.reflector = reflector;
|
|
1267
|
+
this.appInfo = appInfo;
|
|
1268
|
+
this.snapshotPath = snapshotPath;
|
|
1269
|
+
}
|
|
1270
|
+
onApplicationBootstrap() {
|
|
1271
|
+
this.snapshot = this.buildSnapshot();
|
|
1272
|
+
this.logger.log(`Discovery snapshot built: ${this.snapshot.capabilities.length} capabilities, ${this.snapshot.features.length} features, ${this.snapshot.quotas.length} quotas (hash=${this.snapshot.hash.slice(0, 12)}\u2026)`);
|
|
1273
|
+
if (this.snapshotPath) {
|
|
1274
|
+
this.persistSnapshot(this.snapshotPath, this.snapshot);
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
persistSnapshot(path, snapshot) {
|
|
1278
|
+
try {
|
|
1279
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), {
|
|
1280
|
+
recursive: true
|
|
1281
|
+
});
|
|
1282
|
+
(0, import_node_fs.writeFileSync)(path, JSON.stringify(snapshot, null, 2) + "\n", "utf-8");
|
|
1283
|
+
this.logger.log(`Discovery snapshot persisted to ${path}`);
|
|
1284
|
+
} catch (err) {
|
|
1285
|
+
this.logger.warn(`Discovery snapshot konnte nicht nach ${path} geschrieben werden: ${err.message}`);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
/**
|
|
1289
|
+
* Returns the boot-time snapshot. Before `onApplicationBootstrap` (e.g. in
|
|
1290
|
+
* tests) the snapshot is built lazily.
|
|
1291
|
+
*/
|
|
1292
|
+
getSnapshot() {
|
|
1293
|
+
if (!this.snapshot) {
|
|
1294
|
+
this.snapshot = this.buildSnapshot();
|
|
1295
|
+
}
|
|
1296
|
+
return this.snapshot;
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* Forces a re-scan (SuperAdmin "re-run discovery" or tests with
|
|
1300
|
+
* dynamically registered providers). `scannedAt` is set anew and the
|
|
1301
|
+
* snapshot — as at boot — is persisted to disk so that the last scan
|
|
1302
|
+
* actually performed is preserved.
|
|
1303
|
+
*/
|
|
1304
|
+
rebuildSnapshot() {
|
|
1305
|
+
this.snapshot = this.buildSnapshot();
|
|
1306
|
+
if (this.snapshotPath) {
|
|
1307
|
+
this.persistSnapshot(this.snapshotPath, this.snapshot);
|
|
1308
|
+
}
|
|
1309
|
+
return this.snapshot;
|
|
1310
|
+
}
|
|
1311
|
+
buildSnapshot() {
|
|
1312
|
+
const capabilitiesByKey = /* @__PURE__ */ new Map();
|
|
1313
|
+
const quotasByKey = /* @__PURE__ */ new Map();
|
|
1314
|
+
const enforceRefs = [];
|
|
1315
|
+
const wrappers = [
|
|
1316
|
+
...this.discoveryService.getProviders(),
|
|
1317
|
+
...this.discoveryService.getControllers()
|
|
1318
|
+
];
|
|
1319
|
+
for (const wrapper of wrappers) {
|
|
1320
|
+
const instance = wrapper.instance;
|
|
1321
|
+
if (!instance || typeof instance !== "object") continue;
|
|
1322
|
+
const ctor = instance.constructor;
|
|
1323
|
+
if (!ctor) continue;
|
|
1324
|
+
const quotaOpts = this.reflector.get(DEFINES_QUOTA_KEY, ctor);
|
|
1325
|
+
if (quotaOpts) {
|
|
1326
|
+
this.registerQuota(quotasByKey, quotaOpts, ctor.name);
|
|
1327
|
+
}
|
|
1328
|
+
const prototype = Object.getPrototypeOf(instance);
|
|
1329
|
+
const methodNames = this.metadataScanner.getAllMethodNames(prototype);
|
|
1330
|
+
for (const methodName of methodNames) {
|
|
1331
|
+
const methodRef = instance[methodName];
|
|
1332
|
+
if (typeof methodRef !== "function") continue;
|
|
1333
|
+
const capMeta = this.reflector.get(IMPLEMENTS_CAPABILITY_KEY, methodRef);
|
|
1334
|
+
if (capMeta) {
|
|
1335
|
+
this.registerCapability(capabilitiesByKey, capMeta, `${ctor.name}.${methodName}`);
|
|
1336
|
+
}
|
|
1337
|
+
const enforceMeta = this.reflector.get(ENFORCE_QUOTA_KEY, methodRef);
|
|
1338
|
+
if (enforceMeta && capMeta) {
|
|
1339
|
+
enforceRefs.push({
|
|
1340
|
+
capabilityKey: capMeta.capabilityKey,
|
|
1341
|
+
quotaKey: enforceMeta.quotaKey
|
|
1342
|
+
});
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
}
|
|
1346
|
+
const capabilities = sortByKey([
|
|
1347
|
+
...capabilitiesByKey.values()
|
|
1348
|
+
], (c) => c.capabilityKey);
|
|
1349
|
+
const features = aggregateFeatures(capabilities);
|
|
1350
|
+
const quotas = sortByKey(decorateQuotasWithEnforcers([
|
|
1351
|
+
...quotasByKey.values()
|
|
1352
|
+
], enforceRefs), (q) => q.quotaKey);
|
|
1353
|
+
const snapshot = {
|
|
1354
|
+
schemaVersion: 1,
|
|
1355
|
+
scannedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1356
|
+
app: {
|
|
1357
|
+
...this.appInfo
|
|
1358
|
+
},
|
|
1359
|
+
capabilities,
|
|
1360
|
+
features,
|
|
1361
|
+
quotas,
|
|
1362
|
+
hash: ""
|
|
1363
|
+
};
|
|
1364
|
+
snapshot.hash = computeSnapshotHash(snapshot);
|
|
1365
|
+
return snapshot;
|
|
1366
|
+
}
|
|
1367
|
+
registerCapability(target, meta, declaredAt) {
|
|
1368
|
+
const existing = target.get(meta.capabilityKey);
|
|
1369
|
+
if (existing) {
|
|
1370
|
+
this.logger.warn(`Capability '${meta.capabilityKey}' wird an mehreren Stellen deklariert (${existing.declaredAt} und ${declaredAt}); erste Deklaration gewinnt`);
|
|
1371
|
+
return;
|
|
1372
|
+
}
|
|
1373
|
+
target.set(meta.capabilityKey, {
|
|
1374
|
+
capabilityKey: meta.capabilityKey,
|
|
1375
|
+
label: meta.label ?? null,
|
|
1376
|
+
feature: meta.feature ?? null,
|
|
1377
|
+
status: meta.status ?? "active",
|
|
1378
|
+
kind: meta.kind ?? "endpoint",
|
|
1379
|
+
owner: meta.owner ?? null,
|
|
1380
|
+
replacementKey: meta.replacementKey ?? null,
|
|
1381
|
+
removalPlannedAt: meta.removalPlannedAt ?? null,
|
|
1382
|
+
reason: meta.reason ?? null,
|
|
1383
|
+
requires: normalizeKeyList(meta.requires),
|
|
1384
|
+
replaces: normalizeKeyList(meta.replaces),
|
|
1385
|
+
declaredAt
|
|
1386
|
+
});
|
|
1387
|
+
}
|
|
1388
|
+
registerQuota(target, opts, declaredAt) {
|
|
1389
|
+
const existing = target.get(opts.key);
|
|
1390
|
+
if (existing) {
|
|
1391
|
+
this.logger.warn(`Quota '${opts.key}' wird an mehreren Stellen definiert (${existing.declaredAt} und ${declaredAt}); erste Definition gewinnt`);
|
|
1392
|
+
return;
|
|
1393
|
+
}
|
|
1394
|
+
target.set(opts.key, {
|
|
1395
|
+
quotaKey: opts.key,
|
|
1396
|
+
label: opts.label,
|
|
1397
|
+
unit: opts.unit,
|
|
1398
|
+
policy: opts.policy,
|
|
1399
|
+
feature: opts.feature ?? null,
|
|
1400
|
+
replaces: normalizeKeyList(opts.replaces),
|
|
1401
|
+
declaredAt,
|
|
1402
|
+
enforcedBy: []
|
|
1403
|
+
});
|
|
1404
|
+
}
|
|
1405
|
+
};
|
|
1406
|
+
DiscoveryScanner = _ts_decorate11([
|
|
1407
|
+
(0, import_common11.Injectable)(),
|
|
1408
|
+
_ts_param8(0, (0, import_common11.Inject)(import_core2.DiscoveryService)),
|
|
1409
|
+
_ts_param8(1, (0, import_common11.Inject)(import_core2.MetadataScanner)),
|
|
1410
|
+
_ts_param8(2, (0, import_common11.Inject)(import_core2.Reflector)),
|
|
1411
|
+
_ts_param8(3, (0, import_common11.Optional)()),
|
|
1412
|
+
_ts_param8(3, (0, import_common11.Inject)(DISCOVERY_APP_INFO_TOKEN)),
|
|
1413
|
+
_ts_param8(4, (0, import_common11.Optional)()),
|
|
1414
|
+
_ts_param8(4, (0, import_common11.Inject)(DISCOVERY_SNAPSHOT_PATH_TOKEN)),
|
|
1415
|
+
_ts_metadata8("design:type", Function),
|
|
1416
|
+
_ts_metadata8("design:paramtypes", [
|
|
1417
|
+
typeof import_core2.DiscoveryService === "undefined" ? Object : import_core2.DiscoveryService,
|
|
1418
|
+
typeof import_core2.MetadataScanner === "undefined" ? Object : import_core2.MetadataScanner,
|
|
1419
|
+
typeof import_core2.Reflector === "undefined" ? Object : import_core2.Reflector,
|
|
1420
|
+
typeof DiscoveryAppInfo === "undefined" ? Object : DiscoveryAppInfo,
|
|
1421
|
+
Object
|
|
1422
|
+
])
|
|
1423
|
+
], DiscoveryScanner);
|
|
1424
|
+
function aggregateFeatures(capabilities) {
|
|
1425
|
+
const byFeature = /* @__PURE__ */ new Map();
|
|
1426
|
+
for (const cap of capabilities) {
|
|
1427
|
+
if (!cap.feature) continue;
|
|
1428
|
+
const acc = byFeature.get(cap.feature) ?? {
|
|
1429
|
+
capabilityKeys: [],
|
|
1430
|
+
requires: /* @__PURE__ */ new Set(),
|
|
1431
|
+
replaces: /* @__PURE__ */ new Set()
|
|
1432
|
+
};
|
|
1433
|
+
acc.capabilityKeys.push(cap.capabilityKey);
|
|
1434
|
+
for (const key of cap.requires ?? []) acc.requires.add(key);
|
|
1435
|
+
for (const key of cap.replaces ?? []) acc.replaces.add(key);
|
|
1436
|
+
byFeature.set(cap.feature, acc);
|
|
1437
|
+
}
|
|
1438
|
+
return sortByKey([
|
|
1439
|
+
...byFeature.entries()
|
|
1440
|
+
].map(([featureKey, acc]) => {
|
|
1441
|
+
acc.requires.delete(featureKey);
|
|
1442
|
+
acc.replaces.delete(featureKey);
|
|
1443
|
+
return {
|
|
1444
|
+
featureKey,
|
|
1445
|
+
capabilityKeys: [
|
|
1446
|
+
...acc.capabilityKeys
|
|
1447
|
+
].sort((a, b) => a.localeCompare(b)),
|
|
1448
|
+
requires: setToSortedListOrNull(acc.requires),
|
|
1449
|
+
replaces: setToSortedListOrNull(acc.replaces)
|
|
1450
|
+
};
|
|
1451
|
+
}), (f) => f.featureKey);
|
|
1452
|
+
}
|
|
1453
|
+
__name(aggregateFeatures, "aggregateFeatures");
|
|
1454
|
+
function normalizeKeyList(keys) {
|
|
1455
|
+
if (!keys || keys.length === 0) return null;
|
|
1456
|
+
return [
|
|
1457
|
+
...new Set(keys)
|
|
1458
|
+
].sort((a, b) => a.localeCompare(b));
|
|
1459
|
+
}
|
|
1460
|
+
__name(normalizeKeyList, "normalizeKeyList");
|
|
1461
|
+
function setToSortedListOrNull(keys) {
|
|
1462
|
+
if (keys.size === 0) return null;
|
|
1463
|
+
return [
|
|
1464
|
+
...keys
|
|
1465
|
+
].sort((a, b) => a.localeCompare(b));
|
|
1466
|
+
}
|
|
1467
|
+
__name(setToSortedListOrNull, "setToSortedListOrNull");
|
|
1468
|
+
function decorateQuotasWithEnforcers(quotas, enforceRefs) {
|
|
1469
|
+
const enforcedByQuota = /* @__PURE__ */ new Map();
|
|
1470
|
+
for (const ref of enforceRefs) {
|
|
1471
|
+
const set = enforcedByQuota.get(ref.quotaKey) ?? /* @__PURE__ */ new Set();
|
|
1472
|
+
set.add(ref.capabilityKey);
|
|
1473
|
+
enforcedByQuota.set(ref.quotaKey, set);
|
|
1474
|
+
}
|
|
1475
|
+
return quotas.map((q) => ({
|
|
1476
|
+
...q,
|
|
1477
|
+
enforcedBy: [
|
|
1478
|
+
...enforcedByQuota.get(q.quotaKey) ?? /* @__PURE__ */ new Set()
|
|
1479
|
+
].sort((a, b) => a.localeCompare(b))
|
|
1480
|
+
}));
|
|
1481
|
+
}
|
|
1482
|
+
__name(decorateQuotasWithEnforcers, "decorateQuotasWithEnforcers");
|
|
1483
|
+
function sortByKey(items, keyFn) {
|
|
1484
|
+
return [
|
|
1485
|
+
...items
|
|
1486
|
+
].sort((a, b) => keyFn(a).localeCompare(keyFn(b)));
|
|
1487
|
+
}
|
|
1488
|
+
__name(sortByKey, "sortByKey");
|
|
1489
|
+
function computeSnapshotHash(snapshot) {
|
|
1490
|
+
const stableInput = {
|
|
1491
|
+
schemaVersion: snapshot.schemaVersion,
|
|
1492
|
+
appKey: snapshot.app.key,
|
|
1493
|
+
capabilities: snapshot.capabilities.map((c) => ({
|
|
1494
|
+
...c
|
|
1495
|
+
})),
|
|
1496
|
+
features: snapshot.features,
|
|
1497
|
+
quotas: snapshot.quotas
|
|
1498
|
+
};
|
|
1499
|
+
const json = canonicalStringify(stableInput);
|
|
1500
|
+
return `sha256-${(0, import_node_crypto2.createHash)("sha256").update(json).digest("hex")}`;
|
|
1501
|
+
}
|
|
1502
|
+
__name(computeSnapshotHash, "computeSnapshotHash");
|
|
1503
|
+
function canonicalStringify(value) {
|
|
1504
|
+
return JSON.stringify(value, (_key, val) => {
|
|
1505
|
+
if (val && typeof val === "object" && !Array.isArray(val)) {
|
|
1506
|
+
const sorted = {};
|
|
1507
|
+
for (const k of Object.keys(val).sort()) {
|
|
1508
|
+
sorted[k] = val[k];
|
|
1509
|
+
}
|
|
1510
|
+
return sorted;
|
|
1511
|
+
}
|
|
1512
|
+
return val;
|
|
1513
|
+
});
|
|
1514
|
+
}
|
|
1515
|
+
__name(canonicalStringify, "canonicalStringify");
|
|
1516
|
+
|
|
1517
|
+
// src/discovery/discovery.controller.ts
|
|
1518
|
+
function _ts_decorate12(decorators, target, key, desc) {
|
|
1519
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1520
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1521
|
+
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;
|
|
1522
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1523
|
+
}
|
|
1524
|
+
__name(_ts_decorate12, "_ts_decorate");
|
|
1525
|
+
function _ts_metadata9(k, v) {
|
|
1526
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1527
|
+
}
|
|
1528
|
+
__name(_ts_metadata9, "_ts_metadata");
|
|
1529
|
+
function _ts_param9(paramIndex, decorator) {
|
|
1530
|
+
return function(target, key) {
|
|
1531
|
+
decorator(target, key, paramIndex);
|
|
1532
|
+
};
|
|
1533
|
+
}
|
|
1534
|
+
__name(_ts_param9, "_ts_param");
|
|
1535
|
+
function buildDiscoveryController(guards) {
|
|
1536
|
+
let GeneratedDiscoveryController = class GeneratedDiscoveryController {
|
|
1537
|
+
static {
|
|
1538
|
+
__name(this, "GeneratedDiscoveryController");
|
|
1539
|
+
}
|
|
1540
|
+
scanner;
|
|
1541
|
+
// Explicit @Inject instead of type reflection: tsup/esbuild do not emit
|
|
1542
|
+
// any `design:paramtypes` metadata, so otherwise Nest cannot resolve the
|
|
1543
|
+
// service type on the constructor.
|
|
1544
|
+
constructor(scanner) {
|
|
1545
|
+
this.scanner = scanner;
|
|
1546
|
+
}
|
|
1547
|
+
getDiscovery(ifNoneMatch, res) {
|
|
1548
|
+
const snapshot = this.scanner.getSnapshot();
|
|
1549
|
+
const etag = `"${snapshot.hash}-${snapshot.scannedAt}"`;
|
|
1550
|
+
res.header("ETag", etag);
|
|
1551
|
+
if (ifNoneMatch && ifNoneMatch === etag) {
|
|
1552
|
+
res.status(import_common12.HttpStatus.NOT_MODIFIED);
|
|
1553
|
+
return null;
|
|
1554
|
+
}
|
|
1555
|
+
return snapshot;
|
|
1556
|
+
}
|
|
1557
|
+
/**
|
|
1558
|
+
* Forces a fresh code scan and returns the new snapshot.
|
|
1559
|
+
* `scannedAt` is updated; the Capability/Feature/Quota data
|
|
1560
|
+
* are identical within the running process (code is stable at runtime).
|
|
1561
|
+
*/
|
|
1562
|
+
rescanDiscovery() {
|
|
1563
|
+
return this.scanner.rebuildSnapshot();
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
_ts_decorate12([
|
|
1567
|
+
(0, import_common12.Get)("discovery"),
|
|
1568
|
+
(0, import_common12.Header)("Cache-Control", "private, no-cache"),
|
|
1569
|
+
_ts_param9(0, (0, import_common12.Headers)("if-none-match")),
|
|
1570
|
+
_ts_param9(1, (0, import_common12.Res)({
|
|
1571
|
+
passthrough: true
|
|
1572
|
+
})),
|
|
1573
|
+
_ts_metadata9("design:type", Function),
|
|
1574
|
+
_ts_metadata9("design:paramtypes", [
|
|
1575
|
+
Object,
|
|
1576
|
+
typeof HttpResponseLike === "undefined" ? Object : HttpResponseLike
|
|
1577
|
+
]),
|
|
1578
|
+
_ts_metadata9("design:returntype", Object)
|
|
1579
|
+
], GeneratedDiscoveryController.prototype, "getDiscovery", null);
|
|
1580
|
+
_ts_decorate12([
|
|
1581
|
+
(0, import_common12.Post)("discovery/rescan"),
|
|
1582
|
+
_ts_metadata9("design:type", Function),
|
|
1583
|
+
_ts_metadata9("design:paramtypes", []),
|
|
1584
|
+
_ts_metadata9("design:returntype", typeof DiscoverySnapshot === "undefined" ? Object : DiscoverySnapshot)
|
|
1585
|
+
], GeneratedDiscoveryController.prototype, "rescanDiscovery", null);
|
|
1586
|
+
GeneratedDiscoveryController = _ts_decorate12([
|
|
1587
|
+
(0, import_common12.Controller)("admin"),
|
|
1588
|
+
(0, import_common12.UseGuards)(...guards),
|
|
1589
|
+
_ts_param9(0, (0, import_common12.Inject)(DiscoveryScanner)),
|
|
1590
|
+
_ts_metadata9("design:type", Function),
|
|
1591
|
+
_ts_metadata9("design:paramtypes", [
|
|
1592
|
+
typeof DiscoveryScanner === "undefined" ? Object : DiscoveryScanner
|
|
1593
|
+
])
|
|
1594
|
+
], GeneratedDiscoveryController);
|
|
1595
|
+
return GeneratedDiscoveryController;
|
|
1596
|
+
}
|
|
1597
|
+
__name(buildDiscoveryController, "buildDiscoveryController");
|
|
1598
|
+
|
|
1599
|
+
// src/discovery/discovery.module.ts
|
|
1600
|
+
function _ts_decorate13(decorators, target, key, desc) {
|
|
1601
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1602
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1603
|
+
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;
|
|
1604
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1605
|
+
}
|
|
1606
|
+
__name(_ts_decorate13, "_ts_decorate");
|
|
1607
|
+
var DiscoveryModule = class _DiscoveryModule {
|
|
1608
|
+
static {
|
|
1609
|
+
__name(this, "DiscoveryModule");
|
|
1610
|
+
}
|
|
1611
|
+
static forRoot(options) {
|
|
1612
|
+
const isGlobal = options.isGlobal ?? true;
|
|
1613
|
+
const controllers = [];
|
|
1614
|
+
if (options.controller) {
|
|
1615
|
+
controllers.push(buildDiscoveryController(options.controller.guards));
|
|
1616
|
+
}
|
|
1617
|
+
const providers = [
|
|
1618
|
+
{
|
|
1619
|
+
provide: DISCOVERY_APP_INFO_TOKEN,
|
|
1620
|
+
useValue: options.app
|
|
1621
|
+
},
|
|
1622
|
+
{
|
|
1623
|
+
provide: DISCOVERY_SNAPSHOT_PATH_TOKEN,
|
|
1624
|
+
useValue: options.snapshotPath ?? null
|
|
1625
|
+
},
|
|
1626
|
+
DiscoveryScanner,
|
|
1627
|
+
{
|
|
1628
|
+
provide: DISCOVERY_SNAPSHOT_TOKEN,
|
|
1629
|
+
useFactory: /* @__PURE__ */ __name((scanner) => scanner.getSnapshot(), "useFactory"),
|
|
1630
|
+
inject: [
|
|
1631
|
+
DiscoveryScanner
|
|
1632
|
+
]
|
|
1633
|
+
}
|
|
1634
|
+
];
|
|
1635
|
+
return {
|
|
1636
|
+
module: _DiscoveryModule,
|
|
1637
|
+
imports: [
|
|
1638
|
+
import_core3.DiscoveryModule,
|
|
1639
|
+
...options.imports ?? []
|
|
1640
|
+
],
|
|
1641
|
+
controllers,
|
|
1642
|
+
providers,
|
|
1643
|
+
exports: [
|
|
1644
|
+
DiscoveryScanner,
|
|
1645
|
+
DISCOVERY_SNAPSHOT_TOKEN
|
|
1646
|
+
],
|
|
1647
|
+
global: isGlobal
|
|
1648
|
+
};
|
|
1649
|
+
}
|
|
1650
|
+
};
|
|
1651
|
+
DiscoveryModule = _ts_decorate13([
|
|
1652
|
+
(0, import_common13.Module)({})
|
|
1653
|
+
], DiscoveryModule);
|
|
1654
|
+
|
|
1655
|
+
// src/entitlement/module.ts
|
|
1656
|
+
var import_common15 = require("@nestjs/common");
|
|
1657
|
+
|
|
1658
|
+
// src/entitlement/service.ts
|
|
1659
|
+
var import_common14 = require("@nestjs/common");
|
|
1660
|
+
|
|
1661
|
+
// src/catalog/tokens.ts
|
|
1662
|
+
var BUNDLE_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol("BUNDLE_REPOSITORY");
|
|
1663
|
+
|
|
1664
|
+
// src/billing/subscription-bundles.tokens.ts
|
|
1665
|
+
var SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/SubscriptionBundleRepository");
|
|
1666
|
+
|
|
1667
|
+
// src/subscription-contract/tokens.ts
|
|
1668
|
+
var SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/SubscriptionContractRepository");
|
|
1669
|
+
|
|
1670
|
+
// src/billing/plan-helpers.ts
|
|
1671
|
+
function isFeaturePlannedOnly(catalog, featureKey) {
|
|
1672
|
+
const def = (catalog.features ?? []).find((f) => f.key === featureKey);
|
|
1673
|
+
return def?.plannedOnly === true;
|
|
1674
|
+
}
|
|
1675
|
+
__name(isFeaturePlannedOnly, "isFeaturePlannedOnly");
|
|
1676
|
+
|
|
1677
|
+
// src/entitlement/aggregation.ts
|
|
1678
|
+
function aggregateBusinessTypeQuotas(snapshot) {
|
|
1679
|
+
const sums = {};
|
|
1680
|
+
for (const bundle of snapshot.bundles) {
|
|
1681
|
+
for (const [key, value] of Object.entries(bundle.quotas)) {
|
|
1682
|
+
if (sums[key] === -1 || value === -1) {
|
|
1683
|
+
sums[key] = -1;
|
|
1684
|
+
} else {
|
|
1685
|
+
sums[key] = (sums[key] ?? 0) + value;
|
|
1686
|
+
}
|
|
1687
|
+
}
|
|
1688
|
+
}
|
|
1689
|
+
for (const [key, value] of Object.entries(snapshot.quotaOverrides)) {
|
|
1690
|
+
if (value !== void 0) {
|
|
1691
|
+
sums[key] = value;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
return sums;
|
|
1695
|
+
}
|
|
1696
|
+
__name(aggregateBusinessTypeQuotas, "aggregateBusinessTypeQuotas");
|
|
1697
|
+
function filterActiveSubscriptionBundles(bundles, now) {
|
|
1698
|
+
return bundles.filter((b) => b.canceledEffectiveAt === null || b.canceledEffectiveAt > now);
|
|
1699
|
+
}
|
|
1700
|
+
__name(filterActiveSubscriptionBundles, "filterActiveSubscriptionBundles");
|
|
1701
|
+
function aggregateSubscriptionBundleQuotas(bundles) {
|
|
1702
|
+
const sums = {};
|
|
1703
|
+
for (const bundle of bundles) {
|
|
1704
|
+
for (const [key, value] of Object.entries(bundle.quotas)) {
|
|
1705
|
+
if (sums[key] === -1 || value === -1) {
|
|
1706
|
+
sums[key] = -1;
|
|
1707
|
+
} else {
|
|
1708
|
+
sums[key] = (sums[key] ?? 0) + value;
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
}
|
|
1712
|
+
return sums;
|
|
1713
|
+
}
|
|
1714
|
+
__name(aggregateSubscriptionBundleQuotas, "aggregateSubscriptionBundleQuotas");
|
|
1715
|
+
function collectSubscriptionBundleFeatures(bundles) {
|
|
1716
|
+
const set = /* @__PURE__ */ new Set();
|
|
1717
|
+
for (const bundle of bundles) {
|
|
1718
|
+
for (const f of bundle.features) set.add(f);
|
|
1719
|
+
}
|
|
1720
|
+
return Array.from(set);
|
|
1721
|
+
}
|
|
1722
|
+
__name(collectSubscriptionBundleFeatures, "collectSubscriptionBundleFeatures");
|
|
1723
|
+
function aggregateContractLineItemEntitlements(lineItems, fallbackPlan = "UNKNOWN") {
|
|
1724
|
+
const planItem = lineItems.find((item) => item.kind === "plan");
|
|
1725
|
+
const plan = planItem?.sourceKey ?? fallbackPlan;
|
|
1726
|
+
const quotas = {};
|
|
1727
|
+
const features = /* @__PURE__ */ new Set();
|
|
1728
|
+
for (const item of lineItems) {
|
|
1729
|
+
for (const feature of item.featuresSnapshot) {
|
|
1730
|
+
features.add(feature);
|
|
1731
|
+
}
|
|
1732
|
+
for (const [key, value] of Object.entries(item.quotaEffectsSnapshot)) {
|
|
1733
|
+
if (quotas[key] === -1 || value === -1) {
|
|
1734
|
+
quotas[key] = -1;
|
|
1735
|
+
} else {
|
|
1736
|
+
quotas[key] = (quotas[key] ?? 0) + value;
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
}
|
|
1740
|
+
return {
|
|
1741
|
+
plan,
|
|
1742
|
+
quotas,
|
|
1743
|
+
features
|
|
1744
|
+
};
|
|
1745
|
+
}
|
|
1746
|
+
__name(aggregateContractLineItemEntitlements, "aggregateContractLineItemEntitlements");
|
|
1747
|
+
function collectBusinessTypeFeatures(snapshot) {
|
|
1748
|
+
const set = /* @__PURE__ */ new Set();
|
|
1749
|
+
for (const bundle of snapshot.bundles) {
|
|
1750
|
+
for (const f of bundle.features) {
|
|
1751
|
+
set.add(f);
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
return Array.from(set);
|
|
1755
|
+
}
|
|
1756
|
+
__name(collectBusinessTypeFeatures, "collectBusinessTypeFeatures");
|
|
1757
|
+
function filterPlannedOnlyFeatures(features, catalog) {
|
|
1758
|
+
const out = /* @__PURE__ */ new Set();
|
|
1759
|
+
for (const f of features) {
|
|
1760
|
+
if (!isFeaturePlannedOnly(catalog, f)) {
|
|
1761
|
+
out.add(f);
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1764
|
+
return out;
|
|
1765
|
+
}
|
|
1766
|
+
__name(filterPlannedOnlyFeatures, "filterPlannedOnlyFeatures");
|
|
1767
|
+
function applyCustomLimits(base, custom) {
|
|
1768
|
+
if (!custom) return base;
|
|
1769
|
+
const quotas = {
|
|
1770
|
+
...base.quotas
|
|
1771
|
+
};
|
|
1772
|
+
if (custom.quotas) {
|
|
1773
|
+
for (const [k, v] of Object.entries(custom.quotas)) {
|
|
1774
|
+
quotas[k] = v;
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
const features = new Set(base.features);
|
|
1778
|
+
if (custom.features) {
|
|
1779
|
+
for (const f of custom.features) features.add(f);
|
|
1780
|
+
}
|
|
1781
|
+
return {
|
|
1782
|
+
plan: base.plan,
|
|
1783
|
+
quotas,
|
|
1784
|
+
features
|
|
1785
|
+
};
|
|
1786
|
+
}
|
|
1787
|
+
__name(applyCustomLimits, "applyCustomLimits");
|
|
1788
|
+
function aggregateLimits(input, catalog, now) {
|
|
1789
|
+
const businessTypeQuotas = input.businessTypeVersion ? aggregateBusinessTypeQuotas(input.businessTypeVersion) : {};
|
|
1790
|
+
const businessTypeFeatures = input.businessTypeVersion ? collectBusinessTypeFeatures(input.businessTypeVersion) : [];
|
|
1791
|
+
const activeBundles = filterActiveSubscriptionBundles(input.subscriptionBundles ?? [], now);
|
|
1792
|
+
const subBundleQuotas = aggregateSubscriptionBundleQuotas(activeBundles);
|
|
1793
|
+
const subBundleFeatures = collectSubscriptionBundleFeatures(activeBundles);
|
|
1794
|
+
const quotas = {};
|
|
1795
|
+
for (const [k, v] of Object.entries(input.planVersion.quotas)) {
|
|
1796
|
+
quotas[k] = v;
|
|
1797
|
+
}
|
|
1798
|
+
for (const [k, v] of Object.entries(businessTypeQuotas)) {
|
|
1799
|
+
if (quotas[k] === -1 || v === -1) {
|
|
1800
|
+
quotas[k] = -1;
|
|
1801
|
+
} else {
|
|
1802
|
+
quotas[k] = (quotas[k] ?? 0) + v;
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
for (const [k, v] of Object.entries(subBundleQuotas)) {
|
|
1806
|
+
if (quotas[k] === -1 || v === -1) {
|
|
1807
|
+
quotas[k] = -1;
|
|
1808
|
+
} else {
|
|
1809
|
+
quotas[k] = (quotas[k] ?? 0) + v;
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
const features = /* @__PURE__ */ new Set([
|
|
1813
|
+
...input.planVersion.features,
|
|
1814
|
+
...businessTypeFeatures,
|
|
1815
|
+
...subBundleFeatures
|
|
1816
|
+
]);
|
|
1817
|
+
const withCustom = applyCustomLimits({
|
|
1818
|
+
plan: input.plan,
|
|
1819
|
+
quotas,
|
|
1820
|
+
features
|
|
1821
|
+
}, input.customLimits);
|
|
1822
|
+
const filteredFeatures = filterPlannedOnlyFeatures(withCustom.features, catalog);
|
|
1823
|
+
return {
|
|
1824
|
+
plan: withCustom.plan,
|
|
1825
|
+
quotas: withCustom.quotas,
|
|
1826
|
+
features: filteredFeatures
|
|
1827
|
+
};
|
|
1828
|
+
}
|
|
1829
|
+
__name(aggregateLimits, "aggregateLimits");
|
|
1830
|
+
|
|
1831
|
+
// src/entitlement/feature-aliases.ts
|
|
1832
|
+
function buildReplacedByIndex(features) {
|
|
1833
|
+
const index = /* @__PURE__ */ new Map();
|
|
1834
|
+
for (const feature of features) {
|
|
1835
|
+
for (const oldKey of feature.replaces ?? []) {
|
|
1836
|
+
if (oldKey === feature.featureKey) continue;
|
|
1837
|
+
const list = index.get(oldKey) ?? [];
|
|
1838
|
+
list.push(feature.featureKey);
|
|
1839
|
+
index.set(oldKey, list);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
for (const list of index.values()) list.sort((a, b) => a.localeCompare(b));
|
|
1843
|
+
assertNoReplacesCycle(index);
|
|
1844
|
+
return index;
|
|
1845
|
+
}
|
|
1846
|
+
__name(buildReplacedByIndex, "buildReplacedByIndex");
|
|
1847
|
+
function expandReplacedFeatures(granted, index) {
|
|
1848
|
+
const expanded = new Set(granted);
|
|
1849
|
+
const worklist = [
|
|
1850
|
+
...granted
|
|
1851
|
+
];
|
|
1852
|
+
while (worklist.length > 0) {
|
|
1853
|
+
const key = worklist.pop();
|
|
1854
|
+
for (const successor of index.get(key) ?? []) {
|
|
1855
|
+
if (expanded.has(successor)) continue;
|
|
1856
|
+
expanded.add(successor);
|
|
1857
|
+
worklist.push(successor);
|
|
1858
|
+
}
|
|
1859
|
+
}
|
|
1860
|
+
return expanded;
|
|
1861
|
+
}
|
|
1862
|
+
__name(expandReplacedFeatures, "expandReplacedFeatures");
|
|
1863
|
+
function assertNoReplacesCycle(index) {
|
|
1864
|
+
const DONE = 2;
|
|
1865
|
+
const IN_PROGRESS = 1;
|
|
1866
|
+
const state = /* @__PURE__ */ new Map();
|
|
1867
|
+
const visit = /* @__PURE__ */ __name((key, path) => {
|
|
1868
|
+
const s = state.get(key);
|
|
1869
|
+
if (s === DONE) return;
|
|
1870
|
+
if (s === IN_PROGRESS) {
|
|
1871
|
+
const cycleStart = path.indexOf(key);
|
|
1872
|
+
const cycle = [
|
|
1873
|
+
...path.slice(cycleStart),
|
|
1874
|
+
key
|
|
1875
|
+
].join(" \u2192 ");
|
|
1876
|
+
throw new Error(`replaces-Zyklus in den Discovery-Metadaten: ${cycle}. Feature-Ersetzungen m\xFCssen azyklisch sein \u2014 replaces-Deklarationen im Code korrigieren (#39).`);
|
|
1877
|
+
}
|
|
1878
|
+
state.set(key, IN_PROGRESS);
|
|
1879
|
+
for (const successor of index.get(key) ?? []) {
|
|
1880
|
+
visit(successor, [
|
|
1881
|
+
...path,
|
|
1882
|
+
key
|
|
1883
|
+
]);
|
|
1884
|
+
}
|
|
1885
|
+
state.set(key, DONE);
|
|
1886
|
+
}, "visit");
|
|
1887
|
+
for (const key of index.keys()) visit(key, []);
|
|
1888
|
+
}
|
|
1889
|
+
__name(assertNoReplacesCycle, "assertNoReplacesCycle");
|
|
1890
|
+
|
|
1891
|
+
// src/entitlement/limit-exceeded-error.ts
|
|
1892
|
+
var LimitExceededError = class extends Error {
|
|
1893
|
+
static {
|
|
1894
|
+
__name(this, "LimitExceededError");
|
|
1895
|
+
}
|
|
1896
|
+
dimension;
|
|
1897
|
+
max;
|
|
1898
|
+
used;
|
|
1899
|
+
constructor(dimension, max, used) {
|
|
1900
|
+
super(`Limit f\xFCr ${dimension} erreicht: ${used}/${max}.`), this.dimension = dimension, this.max = max, this.used = used;
|
|
1901
|
+
this.name = "LimitExceededError";
|
|
1902
|
+
}
|
|
1903
|
+
};
|
|
1904
|
+
|
|
1905
|
+
// src/entitlement/plan-resolution.ts
|
|
1906
|
+
function resolveEntitlementPlan(input, config, now) {
|
|
1907
|
+
if (input.isPilot && config.pilotEntitlementPlan !== void 0) {
|
|
1908
|
+
return config.pilotEntitlementPlan;
|
|
1909
|
+
}
|
|
1910
|
+
if (input.status === "TRIAL") {
|
|
1911
|
+
return input.trialEntitlementPlan ?? config.defaultTrialEntitlementPlan ?? input.plan;
|
|
1912
|
+
}
|
|
1913
|
+
if (input.status === "PENDING_SALES" && config.pendingSalesEntitlementPlan !== void 0) {
|
|
1914
|
+
return config.pendingSalesEntitlementPlan;
|
|
1915
|
+
}
|
|
1916
|
+
if (input.pendingPlan && input.pendingEffectiveAt && input.pendingEffectiveAt <= now) {
|
|
1917
|
+
return input.pendingPlan;
|
|
1918
|
+
}
|
|
1919
|
+
return input.plan;
|
|
1920
|
+
}
|
|
1921
|
+
__name(resolveEntitlementPlan, "resolveEntitlementPlan");
|
|
1922
|
+
|
|
1923
|
+
// src/entitlement/tokens.ts
|
|
1924
|
+
var SUBSCRIPTION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/SubscriptionRepository");
|
|
1925
|
+
var PLAN_VERSION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/PlanVersionRepository");
|
|
1926
|
+
var TRANSACTION_RUNNER_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/TransactionRunner");
|
|
1927
|
+
var ENTITLEMENT_RESOLUTION_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform/EntitlementResolutionConfig");
|
|
1928
|
+
|
|
1929
|
+
// src/entitlement/service.ts
|
|
1930
|
+
function _ts_decorate14(decorators, target, key, desc) {
|
|
1931
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1932
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1933
|
+
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;
|
|
1934
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1935
|
+
}
|
|
1936
|
+
__name(_ts_decorate14, "_ts_decorate");
|
|
1937
|
+
function _ts_metadata10(k, v) {
|
|
1938
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1939
|
+
}
|
|
1940
|
+
__name(_ts_metadata10, "_ts_metadata");
|
|
1941
|
+
function _ts_param10(paramIndex, decorator) {
|
|
1942
|
+
return function(target, key) {
|
|
1943
|
+
decorator(target, key, paramIndex);
|
|
1944
|
+
};
|
|
1945
|
+
}
|
|
1946
|
+
__name(_ts_param10, "_ts_param");
|
|
1947
|
+
var CACHE_TTL_MS = 6e4;
|
|
1948
|
+
var CACHE_MAX_ENTRIES = 1e3;
|
|
1949
|
+
var EntitlementService = class {
|
|
1950
|
+
static {
|
|
1951
|
+
__name(this, "EntitlementService");
|
|
1952
|
+
}
|
|
1953
|
+
catalog;
|
|
1954
|
+
subscriptions;
|
|
1955
|
+
planVersions;
|
|
1956
|
+
tx;
|
|
1957
|
+
resolutionConfig;
|
|
1958
|
+
subscriptionBundles;
|
|
1959
|
+
bundles;
|
|
1960
|
+
subscriptionContracts;
|
|
1961
|
+
discoverySnapshot;
|
|
1962
|
+
// Map preserves insert order — on a hit the entry is removed and
|
|
1963
|
+
// re-inserted (LRU). The TTL is a safety net for forgotten
|
|
1964
|
+
// invalidations — 60s is enough, because limits generally only change
|
|
1965
|
+
// through mutations, not through time (a pending plan's effective date
|
|
1966
|
+
// is day-granular).
|
|
1967
|
+
cache = /* @__PURE__ */ new Map();
|
|
1968
|
+
// #39 — replaces alias index, built lazily from the boot-static snapshot;
|
|
1969
|
+
// throws on replaces cycles (a clear error instead of an infinite loop).
|
|
1970
|
+
replacedByIndex = null;
|
|
1971
|
+
constructor(catalog, subscriptions, planVersions, tx, resolutionConfig = null, subscriptionBundles = null, bundles = null, subscriptionContracts = null, discoverySnapshot = null) {
|
|
1972
|
+
this.catalog = catalog;
|
|
1973
|
+
this.subscriptions = subscriptions;
|
|
1974
|
+
this.planVersions = planVersions;
|
|
1975
|
+
this.tx = tx;
|
|
1976
|
+
this.resolutionConfig = resolutionConfig;
|
|
1977
|
+
this.subscriptionBundles = subscriptionBundles;
|
|
1978
|
+
this.bundles = bundles;
|
|
1979
|
+
this.subscriptionContracts = subscriptionContracts;
|
|
1980
|
+
this.discoverySnapshot = discoverySnapshot;
|
|
1981
|
+
}
|
|
1982
|
+
// ---------------------------------------------------------------------
|
|
1983
|
+
// Read path — for FeatureGuard, sidebar hooks, GET /billing/entitlement
|
|
1984
|
+
// ---------------------------------------------------------------------
|
|
1985
|
+
async computeLimits(tenantId, now = /* @__PURE__ */ new Date()) {
|
|
1986
|
+
const cached = this.readCache(tenantId, now.getTime());
|
|
1987
|
+
if (cached) return cached;
|
|
1988
|
+
const sub = await this.subscriptions.findByTenantId(tenantId);
|
|
1989
|
+
if (!sub) {
|
|
1990
|
+
throw new import_common14.NotFoundException(`Keine Subscription f\xFCr Tenant ${tenantId}`);
|
|
1991
|
+
}
|
|
1992
|
+
const limits = await this.deriveLimits(sub, now);
|
|
1993
|
+
this.writeCache(tenantId, limits, now.getTime());
|
|
1994
|
+
return limits;
|
|
1995
|
+
}
|
|
1996
|
+
/**
|
|
1997
|
+
* Clears a tenant's cache entry. MUST be called as soon as a mutation
|
|
1998
|
+
* changes the subscription, a bundle booking, or a PlanVersion binding —
|
|
1999
|
+
* otherwise FeatureGuard and the sidebar stay on stale state for up to
|
|
2000
|
+
* 60s.
|
|
2001
|
+
*/
|
|
2002
|
+
invalidateTenant(tenantId) {
|
|
2003
|
+
this.cache.delete(tenantId);
|
|
2004
|
+
}
|
|
2005
|
+
/** Clears the entire cache. Only for tests / bootstrap. */
|
|
2006
|
+
invalidateAll() {
|
|
2007
|
+
this.cache.clear();
|
|
2008
|
+
}
|
|
2009
|
+
/**
|
|
2010
|
+
* Aggregator path — also callable directly by consumers that already
|
|
2011
|
+
* have a `SubscriptionRecord` (e.g. within a transaction).
|
|
2012
|
+
*/
|
|
2013
|
+
async deriveLimits(sub, now, tx) {
|
|
2014
|
+
const contractLimits = await this.deriveLimitsFromContract(sub.tenantId, now);
|
|
2015
|
+
if (contractLimits) return this.withReplacedFeatureAliases(contractLimits);
|
|
2016
|
+
const effectivePlan = resolveEntitlementPlan(sub, this.resolutionConfig ?? {}, now);
|
|
2017
|
+
const planVersion = effectivePlan === sub.plan ? sub.planVersion : await this.findActivePlanVersionOrFallback(effectivePlan, now, tx);
|
|
2018
|
+
const subscriptionBundles = await this.loadSubscriptionBundleSnapshots(sub.id, now);
|
|
2019
|
+
return this.withReplacedFeatureAliases(aggregateLimits({
|
|
2020
|
+
plan: effectivePlan,
|
|
2021
|
+
planVersion,
|
|
2022
|
+
subscriptionBundles,
|
|
2023
|
+
customLimits: sub.customLimits ?? null
|
|
2024
|
+
}, this.catalog, now));
|
|
2025
|
+
}
|
|
2026
|
+
/**
|
|
2027
|
+
* Grandfathering (#39): granted old feature keys transitively grant
|
|
2028
|
+
* their successors from the `replaces` chains of the discovery snapshot.
|
|
2029
|
+
* A no-op without a snapshot or without replaces declarations.
|
|
2030
|
+
*/
|
|
2031
|
+
withReplacedFeatureAliases(limits) {
|
|
2032
|
+
if (!this.discoverySnapshot) return limits;
|
|
2033
|
+
if (!this.replacedByIndex) {
|
|
2034
|
+
this.replacedByIndex = buildReplacedByIndex(this.discoverySnapshot.features);
|
|
2035
|
+
}
|
|
2036
|
+
if (this.replacedByIndex.size === 0) return limits;
|
|
2037
|
+
return {
|
|
2038
|
+
...limits,
|
|
2039
|
+
features: expandReplacedFeatures(limits.features, this.replacedByIndex)
|
|
2040
|
+
};
|
|
2041
|
+
}
|
|
2042
|
+
async deriveLimitsFromContract(tenantId, now) {
|
|
2043
|
+
if (!this.subscriptionContracts) return null;
|
|
2044
|
+
const contract = await this.subscriptionContracts.findActiveByTenantId(tenantId, now);
|
|
2045
|
+
if (!contract) return null;
|
|
2046
|
+
if (contract.entitlementSnapshot) {
|
|
2047
|
+
return {
|
|
2048
|
+
plan: contract.entitlementSnapshot.plan,
|
|
2049
|
+
quotas: {
|
|
2050
|
+
...contract.entitlementSnapshot.quotas
|
|
2051
|
+
},
|
|
2052
|
+
features: new Set(contract.entitlementSnapshot.features)
|
|
2053
|
+
};
|
|
2054
|
+
}
|
|
2055
|
+
return aggregateContractLineItemEntitlements(contract.lineItems);
|
|
2056
|
+
}
|
|
2057
|
+
/**
|
|
2058
|
+
* Loads a subscription's active bundle bookings and resolves the
|
|
2059
|
+
* `BundleVersion` features/quotas per entry. Without a registered
|
|
2060
|
+
* `SubscriptionBundleRepository` or `BundleRepository` the method
|
|
2061
|
+
* returns an empty list — apps without a bundle schema stay unchanged
|
|
2062
|
+
* (plan-only aggregation).
|
|
2063
|
+
*/
|
|
2064
|
+
async loadSubscriptionBundleSnapshots(subscriptionId, now) {
|
|
2065
|
+
if (!this.subscriptionBundles || !this.bundles) return [];
|
|
2066
|
+
const active = await this.subscriptionBundles.listActiveBySubscription(subscriptionId, now);
|
|
2067
|
+
if (active.length === 0) return [];
|
|
2068
|
+
return Promise.all(active.map(async (booking) => {
|
|
2069
|
+
const bv = await this.bundles.findVersionById(booking.bundleVersionId);
|
|
2070
|
+
if (!bv) {
|
|
2071
|
+
throw new Error(`BundleVersion '${booking.bundleVersionId}' aus aktiver SubscriptionBundle nicht gefunden`);
|
|
2072
|
+
}
|
|
2073
|
+
return {
|
|
2074
|
+
bundleKey: bv.bundleKey,
|
|
2075
|
+
features: bv.features,
|
|
2076
|
+
quotas: bv.quotas,
|
|
2077
|
+
canceledEffectiveAt: booking.canceledEffectiveAt
|
|
2078
|
+
};
|
|
2079
|
+
}));
|
|
2080
|
+
}
|
|
2081
|
+
// ---------------------------------------------------------------------
|
|
2082
|
+
// Write/consumption path — row lock + count + insert in the same
|
|
2083
|
+
// transaction. Protects against race conditions on concurrent creations
|
|
2084
|
+
// for the same tenant.
|
|
2085
|
+
// ---------------------------------------------------------------------
|
|
2086
|
+
async enforceLimit(input) {
|
|
2087
|
+
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
2088
|
+
const delta = input.delta ?? 1;
|
|
2089
|
+
return this.tx.run(async (tx) => {
|
|
2090
|
+
const sub = await this.subscriptions.findByTenantIdLocked(input.tenantId, tx);
|
|
2091
|
+
if (!sub) {
|
|
2092
|
+
throw new import_common14.NotFoundException(`Keine Subscription f\xFCr Tenant ${input.tenantId}`);
|
|
2093
|
+
}
|
|
2094
|
+
const limits = await this.deriveLimits(sub, now, tx);
|
|
2095
|
+
const max = limits.quotas[input.dimension];
|
|
2096
|
+
if (max === void 0) {
|
|
2097
|
+
throw new Error(`Quota-Dimension "${input.dimension}" nicht im PlanCatalog (${this.catalog.projectKey}).`);
|
|
2098
|
+
}
|
|
2099
|
+
const used = await input.currentUsage(tx, input.tenantId);
|
|
2100
|
+
if (max !== -1 && used + delta > max) {
|
|
2101
|
+
throw new LimitExceededError(input.dimension, max, used);
|
|
2102
|
+
}
|
|
2103
|
+
return input.insert(tx);
|
|
2104
|
+
});
|
|
2105
|
+
}
|
|
2106
|
+
// ---------------------------------------------------------------------
|
|
2107
|
+
// Helpers
|
|
2108
|
+
// ---------------------------------------------------------------------
|
|
2109
|
+
/**
|
|
2110
|
+
* Plan fallback for TRIAL/PENDING_SALES: returns the PlanVersion active
|
|
2111
|
+
* as of `asOf` (SPEC_V2 §4.2). If the repo does not implement
|
|
2112
|
+
* `findActive`, we fall back to `findLatestLive` (backward compat for
|
|
2113
|
+
* adapters without `validFrom` columns).
|
|
2114
|
+
*/
|
|
2115
|
+
async findActivePlanVersionOrFallback(planId, asOf, tx) {
|
|
2116
|
+
const v = this.planVersions.findActive ? await this.planVersions.findActive(planId, asOf, tx) : await this.planVersions.findLatestLive(planId, tx);
|
|
2117
|
+
if (!v) {
|
|
2118
|
+
throw new import_common14.NotFoundException(`Keine zu ${asOf.toISOString().slice(0, 10)} aktive PlanVersion f\xFCr ${planId} \u2014 weder validFrom-window erf\xFCllt noch latest-live verf\xFCgbar.`);
|
|
2119
|
+
}
|
|
2120
|
+
return v;
|
|
2121
|
+
}
|
|
2122
|
+
readCache(tenantId, nowMs) {
|
|
2123
|
+
const entry = this.cache.get(tenantId);
|
|
2124
|
+
if (!entry) return null;
|
|
2125
|
+
if (entry.expiresAt < nowMs) {
|
|
2126
|
+
this.cache.delete(tenantId);
|
|
2127
|
+
return null;
|
|
2128
|
+
}
|
|
2129
|
+
this.cache.delete(tenantId);
|
|
2130
|
+
this.cache.set(tenantId, entry);
|
|
2131
|
+
return entry.value;
|
|
2132
|
+
}
|
|
2133
|
+
writeCache(tenantId, value, nowMs) {
|
|
2134
|
+
if (this.cache.has(tenantId)) this.cache.delete(tenantId);
|
|
2135
|
+
this.cache.set(tenantId, {
|
|
2136
|
+
value,
|
|
2137
|
+
expiresAt: nowMs + CACHE_TTL_MS
|
|
2138
|
+
});
|
|
2139
|
+
while (this.cache.size > CACHE_MAX_ENTRIES) {
|
|
2140
|
+
const oldest = this.cache.keys().next().value;
|
|
2141
|
+
if (oldest === void 0) break;
|
|
2142
|
+
this.cache.delete(oldest);
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
};
|
|
2146
|
+
EntitlementService = _ts_decorate14([
|
|
2147
|
+
(0, import_common14.Injectable)(),
|
|
2148
|
+
_ts_param10(0, (0, import_common14.Inject)(PLAN_CATALOG_TOKEN)),
|
|
2149
|
+
_ts_param10(1, (0, import_common14.Inject)(SUBSCRIPTION_REPOSITORY_TOKEN)),
|
|
2150
|
+
_ts_param10(2, (0, import_common14.Inject)(PLAN_VERSION_REPOSITORY_TOKEN)),
|
|
2151
|
+
_ts_param10(3, (0, import_common14.Inject)(TRANSACTION_RUNNER_TOKEN)),
|
|
2152
|
+
_ts_param10(4, (0, import_common14.Optional)()),
|
|
2153
|
+
_ts_param10(4, (0, import_common14.Inject)(ENTITLEMENT_RESOLUTION_CONFIG_TOKEN)),
|
|
2154
|
+
_ts_param10(5, (0, import_common14.Optional)()),
|
|
2155
|
+
_ts_param10(5, (0, import_common14.Inject)(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN)),
|
|
2156
|
+
_ts_param10(6, (0, import_common14.Optional)()),
|
|
2157
|
+
_ts_param10(6, (0, import_common14.Inject)(BUNDLE_REPOSITORY_TOKEN)),
|
|
2158
|
+
_ts_param10(7, (0, import_common14.Optional)()),
|
|
2159
|
+
_ts_param10(7, (0, import_common14.Inject)(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN)),
|
|
2160
|
+
_ts_param10(8, (0, import_common14.Optional)()),
|
|
2161
|
+
_ts_param10(8, (0, import_common14.Inject)(DISCOVERY_SNAPSHOT_TOKEN)),
|
|
2162
|
+
_ts_metadata10("design:type", Function),
|
|
2163
|
+
_ts_metadata10("design:paramtypes", [
|
|
2164
|
+
typeof PlanCatalog === "undefined" ? Object : PlanCatalog,
|
|
2165
|
+
typeof SubscriptionRepository === "undefined" ? Object : SubscriptionRepository,
|
|
2166
|
+
typeof PlanVersionRepository === "undefined" ? Object : PlanVersionRepository,
|
|
2167
|
+
typeof TransactionRunner === "undefined" ? Object : TransactionRunner,
|
|
2168
|
+
Object,
|
|
2169
|
+
Object,
|
|
2170
|
+
Object,
|
|
2171
|
+
Object,
|
|
2172
|
+
Object
|
|
2173
|
+
])
|
|
2174
|
+
], EntitlementService);
|
|
2175
|
+
|
|
2176
|
+
// src/entitlement/module.ts
|
|
2177
|
+
function _ts_decorate15(decorators, target, key, desc) {
|
|
2178
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2179
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2180
|
+
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;
|
|
2181
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2182
|
+
}
|
|
2183
|
+
__name(_ts_decorate15, "_ts_decorate");
|
|
2184
|
+
var EntitlementModule = class _EntitlementModule {
|
|
2185
|
+
static {
|
|
2186
|
+
__name(this, "EntitlementModule");
|
|
2187
|
+
}
|
|
2188
|
+
static forRoot(options) {
|
|
2189
|
+
const providers = [
|
|
2190
|
+
asProvider(SUBSCRIPTION_REPOSITORY_TOKEN, options.subscriptionRepository),
|
|
2191
|
+
asProvider(PLAN_VERSION_REPOSITORY_TOKEN, options.planVersionRepository),
|
|
2192
|
+
asProvider(TRANSACTION_RUNNER_TOKEN, options.transactionRunner),
|
|
2193
|
+
EntitlementService
|
|
2194
|
+
];
|
|
2195
|
+
if (options.resolutionConfig) {
|
|
2196
|
+
providers.push({
|
|
2197
|
+
provide: ENTITLEMENT_RESOLUTION_CONFIG_TOKEN,
|
|
2198
|
+
useValue: options.resolutionConfig
|
|
2199
|
+
});
|
|
2200
|
+
}
|
|
2201
|
+
if (options.subscriptionContractRepository) {
|
|
2202
|
+
providers.push(asProvider(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN, options.subscriptionContractRepository));
|
|
2203
|
+
}
|
|
2204
|
+
if (options.subscriptionBundleRepository) {
|
|
2205
|
+
providers.push(asProvider(SUBSCRIPTION_BUNDLE_REPOSITORY_TOKEN, options.subscriptionBundleRepository));
|
|
2206
|
+
}
|
|
2207
|
+
if (options.bundleRepository) {
|
|
2208
|
+
providers.push(asProvider(BUNDLE_REPOSITORY_TOKEN, options.bundleRepository));
|
|
2209
|
+
}
|
|
2210
|
+
return {
|
|
2211
|
+
module: _EntitlementModule,
|
|
2212
|
+
global: options.global ?? false,
|
|
2213
|
+
providers,
|
|
2214
|
+
exports: [
|
|
2215
|
+
EntitlementService,
|
|
2216
|
+
SUBSCRIPTION_REPOSITORY_TOKEN
|
|
2217
|
+
]
|
|
2218
|
+
};
|
|
2219
|
+
}
|
|
2220
|
+
};
|
|
2221
|
+
EntitlementModule = _ts_decorate15([
|
|
2222
|
+
(0, import_common15.Module)({})
|
|
2223
|
+
], EntitlementModule);
|
|
2224
|
+
|
|
2225
|
+
// src/platform/plan-resolver.port.ts
|
|
2226
|
+
var PLAN_RESOLVER_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/PlanResolverPort");
|
|
2227
|
+
var StaticPlanResolver = class {
|
|
2228
|
+
static {
|
|
2229
|
+
__name(this, "StaticPlanResolver");
|
|
2230
|
+
}
|
|
2231
|
+
planId;
|
|
2232
|
+
constructor(planId) {
|
|
2233
|
+
this.planId = planId;
|
|
2234
|
+
}
|
|
2235
|
+
async getPlanIdForTenant() {
|
|
2236
|
+
return this.planId;
|
|
2237
|
+
}
|
|
2238
|
+
};
|
|
2239
|
+
|
|
2240
|
+
// src/platform/static-entitlement.service.ts
|
|
2241
|
+
var import_common16 = require("@nestjs/common");
|
|
2242
|
+
function _ts_decorate16(decorators, target, key, desc) {
|
|
2243
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2244
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2245
|
+
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;
|
|
2246
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2247
|
+
}
|
|
2248
|
+
__name(_ts_decorate16, "_ts_decorate");
|
|
2249
|
+
function _ts_metadata11(k, v) {
|
|
2250
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2251
|
+
}
|
|
2252
|
+
__name(_ts_metadata11, "_ts_metadata");
|
|
2253
|
+
function _ts_param11(paramIndex, decorator) {
|
|
2254
|
+
return function(target, key) {
|
|
2255
|
+
decorator(target, key, paramIndex);
|
|
2256
|
+
};
|
|
2257
|
+
}
|
|
2258
|
+
__name(_ts_param11, "_ts_param");
|
|
2259
|
+
var StaticEntitlementService = class {
|
|
2260
|
+
static {
|
|
2261
|
+
__name(this, "StaticEntitlementService");
|
|
2262
|
+
}
|
|
2263
|
+
catalog;
|
|
2264
|
+
resolver;
|
|
2265
|
+
constructor(catalog, resolver) {
|
|
2266
|
+
this.catalog = catalog;
|
|
2267
|
+
this.resolver = resolver;
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Returns the effective features + quotas of a tenant from the
|
|
2271
|
+
* plan catalog. When the plan is missing: an empty snapshot (no feature,
|
|
2272
|
+
* all quotas at 0).
|
|
2273
|
+
*/
|
|
2274
|
+
async snapshot(tenantId) {
|
|
2275
|
+
const planId = await this.resolver.getPlanIdForTenant(tenantId);
|
|
2276
|
+
if (!planId) {
|
|
2277
|
+
return {
|
|
2278
|
+
planId: null,
|
|
2279
|
+
features: [],
|
|
2280
|
+
quotas: {}
|
|
2281
|
+
};
|
|
2282
|
+
}
|
|
2283
|
+
const plan = this.findPlan(planId);
|
|
2284
|
+
if (!plan) {
|
|
2285
|
+
return {
|
|
2286
|
+
planId,
|
|
2287
|
+
features: [],
|
|
2288
|
+
quotas: {}
|
|
2289
|
+
};
|
|
2290
|
+
}
|
|
2291
|
+
return {
|
|
2292
|
+
planId,
|
|
2293
|
+
features: plan.features ?? [],
|
|
2294
|
+
quotas: plan.quotas ?? {}
|
|
2295
|
+
};
|
|
2296
|
+
}
|
|
2297
|
+
/** Convenience helper for tests / synchronous checks in the UI manifest. */
|
|
2298
|
+
async hasFeature(tenantId, featureKey) {
|
|
2299
|
+
const snap = await this.snapshot(tenantId);
|
|
2300
|
+
return snap.features.includes(featureKey);
|
|
2301
|
+
}
|
|
2302
|
+
async quotaLimit(tenantId, quotaKey) {
|
|
2303
|
+
const snap = await this.snapshot(tenantId);
|
|
2304
|
+
const value = snap.quotas[quotaKey];
|
|
2305
|
+
return value === void 0 ? null : value;
|
|
2306
|
+
}
|
|
2307
|
+
findPlan(planId) {
|
|
2308
|
+
return (this.catalog.plans ?? []).find((p) => p.id === planId);
|
|
2309
|
+
}
|
|
2310
|
+
};
|
|
2311
|
+
StaticEntitlementService = _ts_decorate16([
|
|
2312
|
+
(0, import_common16.Injectable)(),
|
|
2313
|
+
_ts_param11(0, (0, import_common16.Inject)(PLAN_CATALOG_TOKEN)),
|
|
2314
|
+
_ts_param11(1, (0, import_common16.Inject)(PLAN_RESOLVER_PORT_TOKEN)),
|
|
2315
|
+
_ts_metadata11("design:type", Function),
|
|
2316
|
+
_ts_metadata11("design:paramtypes", [
|
|
2317
|
+
typeof PlanCatalog === "undefined" ? Object : PlanCatalog,
|
|
2318
|
+
typeof PlanResolverPort === "undefined" ? Object : PlanResolverPort
|
|
2319
|
+
])
|
|
2320
|
+
], StaticEntitlementService);
|
|
2321
|
+
|
|
2322
|
+
// src/platform/static-feature.guard.ts
|
|
2323
|
+
var import_common18 = require("@nestjs/common");
|
|
2324
|
+
var import_core4 = require("@nestjs/core");
|
|
2325
|
+
|
|
2326
|
+
// src/billing/require-feature.decorator.ts
|
|
2327
|
+
var import_common17 = require("@nestjs/common");
|
|
2328
|
+
var REQUIRE_FEATURE_KEY = "require-feature";
|
|
2329
|
+
|
|
2330
|
+
// src/platform/static-feature.guard.ts
|
|
2331
|
+
function _ts_decorate17(decorators, target, key, desc) {
|
|
2332
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2333
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2334
|
+
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;
|
|
2335
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2336
|
+
}
|
|
2337
|
+
__name(_ts_decorate17, "_ts_decorate");
|
|
2338
|
+
function _ts_metadata12(k, v) {
|
|
2339
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2340
|
+
}
|
|
2341
|
+
__name(_ts_metadata12, "_ts_metadata");
|
|
2342
|
+
function _ts_param12(paramIndex, decorator) {
|
|
2343
|
+
return function(target, key) {
|
|
2344
|
+
decorator(target, key, paramIndex);
|
|
2345
|
+
};
|
|
2346
|
+
}
|
|
2347
|
+
__name(_ts_param12, "_ts_param");
|
|
2348
|
+
var STATIC_FEATURE_GUARD_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/StaticFeatureGuardConfig");
|
|
2349
|
+
var StaticFeatureGuard = class {
|
|
2350
|
+
static {
|
|
2351
|
+
__name(this, "StaticFeatureGuard");
|
|
2352
|
+
}
|
|
2353
|
+
reflector;
|
|
2354
|
+
entitlements;
|
|
2355
|
+
config;
|
|
2356
|
+
constructor(reflector, entitlements, config = null) {
|
|
2357
|
+
this.reflector = reflector;
|
|
2358
|
+
this.entitlements = entitlements;
|
|
2359
|
+
this.config = config;
|
|
2360
|
+
}
|
|
2361
|
+
async canActivate(context) {
|
|
2362
|
+
const required = this.reflector.getAllAndOverride(REQUIRE_FEATURE_KEY, [
|
|
2363
|
+
context.getHandler(),
|
|
2364
|
+
context.getClass()
|
|
2365
|
+
]);
|
|
2366
|
+
if (!required || required.length === 0) return true;
|
|
2367
|
+
const request = context.switchToHttp().getRequest();
|
|
2368
|
+
const user = request.user;
|
|
2369
|
+
if (!user) throw new import_common18.ForbiddenException("Nicht authentifiziert");
|
|
2370
|
+
const role = this.config?.userRoleResolver ? this.config.userRoleResolver(user) : user.role ?? user.platformRole;
|
|
2371
|
+
if (role === "SUPER_ADMIN") return true;
|
|
2372
|
+
const tenantId = this.config?.tenantIdResolver ? this.config.tenantIdResolver(request) : request.tenantId ?? user.tenantId;
|
|
2373
|
+
if (!tenantId) throw new import_common18.ForbiddenException("Kein Mandant zugeordnet");
|
|
2374
|
+
const snap = await this.entitlements.snapshot(tenantId);
|
|
2375
|
+
const allowed = required.some((f) => snap.features.includes(f));
|
|
2376
|
+
if (!allowed) {
|
|
2377
|
+
throw new import_common18.ForbiddenException(`Feature ${required.join(" / ")} nicht im aktuellen Plan enthalten.`);
|
|
2378
|
+
}
|
|
2379
|
+
return true;
|
|
2380
|
+
}
|
|
2381
|
+
};
|
|
2382
|
+
StaticFeatureGuard = _ts_decorate17([
|
|
2383
|
+
(0, import_common18.Injectable)(),
|
|
2384
|
+
_ts_param12(0, (0, import_common18.Inject)(import_core4.Reflector)),
|
|
2385
|
+
_ts_param12(1, (0, import_common18.Inject)(StaticEntitlementService)),
|
|
2386
|
+
_ts_param12(2, (0, import_common18.Optional)()),
|
|
2387
|
+
_ts_param12(2, (0, import_common18.Inject)(STATIC_FEATURE_GUARD_CONFIG_TOKEN)),
|
|
2388
|
+
_ts_metadata12("design:type", Function),
|
|
2389
|
+
_ts_metadata12("design:paramtypes", [
|
|
2390
|
+
typeof import_core4.Reflector === "undefined" ? Object : import_core4.Reflector,
|
|
2391
|
+
typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService,
|
|
2392
|
+
Object
|
|
2393
|
+
])
|
|
2394
|
+
], StaticFeatureGuard);
|
|
2395
|
+
|
|
2396
|
+
// src/platform/enforce-quota.interceptor.ts
|
|
2397
|
+
var import_common19 = require("@nestjs/common");
|
|
2398
|
+
var import_core5 = require("@nestjs/core");
|
|
2399
|
+
var import_rxjs2 = require("rxjs");
|
|
2400
|
+
var import_operators2 = require("rxjs/operators");
|
|
2401
|
+
function _ts_decorate18(decorators, target, key, desc) {
|
|
2402
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2403
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2404
|
+
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;
|
|
2405
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2406
|
+
}
|
|
2407
|
+
__name(_ts_decorate18, "_ts_decorate");
|
|
2408
|
+
function _ts_metadata13(k, v) {
|
|
2409
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2410
|
+
}
|
|
2411
|
+
__name(_ts_metadata13, "_ts_metadata");
|
|
2412
|
+
function _ts_param13(paramIndex, decorator) {
|
|
2413
|
+
return function(target, key) {
|
|
2414
|
+
decorator(target, key, paramIndex);
|
|
2415
|
+
};
|
|
2416
|
+
}
|
|
2417
|
+
__name(_ts_param13, "_ts_param");
|
|
2418
|
+
var QUOTA_PROVIDERS_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/QuotaProviders");
|
|
2419
|
+
var EnforceQuotaInterceptor = class {
|
|
2420
|
+
static {
|
|
2421
|
+
__name(this, "EnforceQuotaInterceptor");
|
|
2422
|
+
}
|
|
2423
|
+
reflector;
|
|
2424
|
+
entitlements;
|
|
2425
|
+
providers;
|
|
2426
|
+
constructor(reflector, entitlements, providers = null) {
|
|
2427
|
+
this.reflector = reflector;
|
|
2428
|
+
this.entitlements = entitlements;
|
|
2429
|
+
this.providers = providers;
|
|
2430
|
+
}
|
|
2431
|
+
intercept(context, next) {
|
|
2432
|
+
const meta = this.reflector.getAllAndOverride(ENFORCE_QUOTA_KEY, [
|
|
2433
|
+
context.getHandler(),
|
|
2434
|
+
context.getClass()
|
|
2435
|
+
]);
|
|
2436
|
+
if (!meta?.quotaKey) return next.handle();
|
|
2437
|
+
return (0, import_rxjs2.from)(this.check(context, meta)).pipe((0, import_operators2.switchMap)(() => next.handle()));
|
|
2438
|
+
}
|
|
2439
|
+
async check(context, meta) {
|
|
2440
|
+
const request = context.switchToHttp().getRequest();
|
|
2441
|
+
const user = request.user;
|
|
2442
|
+
if (!user) return;
|
|
2443
|
+
const role = user.role ?? user.platformRole;
|
|
2444
|
+
if (role === "SUPER_ADMIN") return;
|
|
2445
|
+
const tenantId = request.tenantId ?? user.tenantId;
|
|
2446
|
+
if (!tenantId) return;
|
|
2447
|
+
const provider = (this.providers ?? []).find((p) => p.key === meta.quotaKey);
|
|
2448
|
+
if (!provider) {
|
|
2449
|
+
return;
|
|
2450
|
+
}
|
|
2451
|
+
const limit = await this.entitlements.quotaLimit(tenantId, meta.quotaKey);
|
|
2452
|
+
if (limit === null || limit === -1) return;
|
|
2453
|
+
const current = await provider.count(tenantId);
|
|
2454
|
+
const delta = meta.incrementBy ?? 1;
|
|
2455
|
+
if (current + delta > limit) {
|
|
2456
|
+
throw new LimitExceededError(meta.quotaKey, limit, current);
|
|
2457
|
+
}
|
|
2458
|
+
}
|
|
2459
|
+
};
|
|
2460
|
+
EnforceQuotaInterceptor = _ts_decorate18([
|
|
2461
|
+
(0, import_common19.Injectable)(),
|
|
2462
|
+
_ts_param13(0, (0, import_common19.Inject)(import_core5.Reflector)),
|
|
2463
|
+
_ts_param13(1, (0, import_common19.Inject)(StaticEntitlementService)),
|
|
2464
|
+
_ts_param13(2, (0, import_common19.Optional)()),
|
|
2465
|
+
_ts_param13(2, (0, import_common19.Inject)(QUOTA_PROVIDERS_TOKEN)),
|
|
2466
|
+
_ts_metadata13("design:type", Function),
|
|
2467
|
+
_ts_metadata13("design:paramtypes", [
|
|
2468
|
+
typeof import_core5.Reflector === "undefined" ? Object : import_core5.Reflector,
|
|
2469
|
+
typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService,
|
|
2470
|
+
Object
|
|
2471
|
+
])
|
|
2472
|
+
], EnforceQuotaInterceptor);
|
|
2473
|
+
|
|
2474
|
+
// src/platform/tenant-manifest.service.ts
|
|
2475
|
+
var import_common20 = require("@nestjs/common");
|
|
2476
|
+
function _ts_decorate19(decorators, target, key, desc) {
|
|
2477
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2478
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2479
|
+
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;
|
|
2480
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2481
|
+
}
|
|
2482
|
+
__name(_ts_decorate19, "_ts_decorate");
|
|
2483
|
+
function _ts_metadata14(k, v) {
|
|
2484
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2485
|
+
}
|
|
2486
|
+
__name(_ts_metadata14, "_ts_metadata");
|
|
2487
|
+
var TenantManifestService = class {
|
|
2488
|
+
static {
|
|
2489
|
+
__name(this, "TenantManifestService");
|
|
2490
|
+
}
|
|
2491
|
+
entitlements;
|
|
2492
|
+
navItems = [];
|
|
2493
|
+
constructor(entitlements) {
|
|
2494
|
+
this.entitlements = entitlements;
|
|
2495
|
+
}
|
|
2496
|
+
/**
|
|
2497
|
+
* Call in the app's `OnModuleInit`: registers app navigation for the
|
|
2498
|
+
* Tenant UI. Idempotent: the same `id` overwrites.
|
|
2499
|
+
*/
|
|
2500
|
+
registerNavItem(item) {
|
|
2501
|
+
const existing = this.navItems.findIndex((i) => i.id === item.id);
|
|
2502
|
+
if (existing >= 0) this.navItems[existing] = item;
|
|
2503
|
+
else this.navItems.push(item);
|
|
2504
|
+
}
|
|
2505
|
+
/** Bulk variant. */
|
|
2506
|
+
registerNavItems(items) {
|
|
2507
|
+
for (const item of items) this.registerNavItem(item);
|
|
2508
|
+
}
|
|
2509
|
+
/**
|
|
2510
|
+
* Returns the filtered manifest for a Tenant. NavItems with
|
|
2511
|
+
* `requiresFeature` are filtered out server-side as soon as none of the
|
|
2512
|
+
* required features are present in the entitlement snapshot.
|
|
2513
|
+
*/
|
|
2514
|
+
async getManifest(tenantId) {
|
|
2515
|
+
const snap = await this.entitlements.snapshot(tenantId);
|
|
2516
|
+
const featureSet = new Set(snap.features);
|
|
2517
|
+
const visible = this.navItems.filter((item) => isVisible(item, featureSet)).map((item) => ({
|
|
2518
|
+
...item
|
|
2519
|
+
})).sort((a, b) => (a.order ?? 100) - (b.order ?? 100));
|
|
2520
|
+
return {
|
|
2521
|
+
schemaVersion: 1,
|
|
2522
|
+
tenant: {
|
|
2523
|
+
id: tenantId
|
|
2524
|
+
},
|
|
2525
|
+
planId: snap.planId,
|
|
2526
|
+
features: snap.features,
|
|
2527
|
+
quotas: snap.quotas,
|
|
2528
|
+
navigation: visible
|
|
2529
|
+
};
|
|
2530
|
+
}
|
|
2531
|
+
};
|
|
2532
|
+
TenantManifestService = _ts_decorate19([
|
|
2533
|
+
(0, import_common20.Injectable)(),
|
|
2534
|
+
_ts_metadata14("design:type", Function),
|
|
2535
|
+
_ts_metadata14("design:paramtypes", [
|
|
2536
|
+
typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService
|
|
2537
|
+
])
|
|
2538
|
+
], TenantManifestService);
|
|
2539
|
+
function isVisible(item, features) {
|
|
2540
|
+
if (!item.requiresFeature) return true;
|
|
2541
|
+
const required = Array.isArray(item.requiresFeature) ? item.requiresFeature : [
|
|
2542
|
+
item.requiresFeature
|
|
2543
|
+
];
|
|
2544
|
+
return required.some((f) => features.has(f));
|
|
2545
|
+
}
|
|
2546
|
+
__name(isVisible, "isVisible");
|
|
2547
|
+
|
|
2548
|
+
// src/platform/tenant-manifest.controller.ts
|
|
2549
|
+
var import_common21 = require("@nestjs/common");
|
|
2550
|
+
function _ts_decorate20(decorators, target, key, desc) {
|
|
2551
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2552
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2553
|
+
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;
|
|
2554
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2555
|
+
}
|
|
2556
|
+
__name(_ts_decorate20, "_ts_decorate");
|
|
2557
|
+
function _ts_metadata15(k, v) {
|
|
2558
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2559
|
+
}
|
|
2560
|
+
__name(_ts_metadata15, "_ts_metadata");
|
|
2561
|
+
function _ts_param14(paramIndex, decorator) {
|
|
2562
|
+
return function(target, key) {
|
|
2563
|
+
decorator(target, key, paramIndex);
|
|
2564
|
+
};
|
|
2565
|
+
}
|
|
2566
|
+
__name(_ts_param14, "_ts_param");
|
|
2567
|
+
function buildTenantManifestController(options) {
|
|
2568
|
+
const path = options.path ?? "tenant/manifest";
|
|
2569
|
+
let GeneratedTenantManifestController = class GeneratedTenantManifestController {
|
|
2570
|
+
static {
|
|
2571
|
+
__name(this, "GeneratedTenantManifestController");
|
|
2572
|
+
}
|
|
2573
|
+
svc;
|
|
2574
|
+
constructor(svc) {
|
|
2575
|
+
this.svc = svc;
|
|
2576
|
+
}
|
|
2577
|
+
async get(req) {
|
|
2578
|
+
const tenantId = req.user?.tenantId ?? req.tenantId;
|
|
2579
|
+
if (!tenantId) {
|
|
2580
|
+
throw new Error("Kein Mandant im Request \u2014 TenantManifest braucht user.tenantId.");
|
|
2581
|
+
}
|
|
2582
|
+
return this.svc.getManifest(tenantId);
|
|
2583
|
+
}
|
|
2584
|
+
};
|
|
2585
|
+
_ts_decorate20([
|
|
2586
|
+
(0, import_common21.Get)(),
|
|
2587
|
+
_ts_param14(0, (0, import_common21.Req)()),
|
|
2588
|
+
_ts_metadata15("design:type", Function),
|
|
2589
|
+
_ts_metadata15("design:paramtypes", [
|
|
2590
|
+
typeof RequestWithUser === "undefined" ? Object : RequestWithUser
|
|
2591
|
+
]),
|
|
2592
|
+
_ts_metadata15("design:returntype", Promise)
|
|
2593
|
+
], GeneratedTenantManifestController.prototype, "get", null);
|
|
2594
|
+
GeneratedTenantManifestController = _ts_decorate20([
|
|
2595
|
+
(0, import_common21.Controller)(path),
|
|
2596
|
+
(0, import_common21.UseGuards)(...options.guards),
|
|
2597
|
+
_ts_param14(0, (0, import_common21.Inject)(TenantManifestService)),
|
|
2598
|
+
_ts_metadata15("design:type", Function),
|
|
2599
|
+
_ts_metadata15("design:paramtypes", [
|
|
2600
|
+
typeof TenantManifestService === "undefined" ? Object : TenantManifestService
|
|
2601
|
+
])
|
|
2602
|
+
], GeneratedTenantManifestController);
|
|
2603
|
+
return GeneratedTenantManifestController;
|
|
2604
|
+
}
|
|
2605
|
+
__name(buildTenantManifestController, "buildTenantManifestController");
|
|
2606
|
+
|
|
2607
|
+
// src/platform/saas-platform.module.ts
|
|
2608
|
+
function _ts_decorate21(decorators, target, key, desc) {
|
|
2609
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2610
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2611
|
+
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;
|
|
2612
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2613
|
+
}
|
|
2614
|
+
__name(_ts_decorate21, "_ts_decorate");
|
|
2615
|
+
function buildMinimalManifestConfig() {
|
|
2616
|
+
return {
|
|
2617
|
+
useFactory: /* @__PURE__ */ __name((catalog) => ({
|
|
2618
|
+
project: {
|
|
2619
|
+
key: catalog.projectKey,
|
|
2620
|
+
displayName: catalog.app?.name ?? catalog.projectKey,
|
|
2621
|
+
label: catalog.app?.label,
|
|
2622
|
+
icon: catalog.app?.icon,
|
|
2623
|
+
logoUrl: catalog.app?.logoUrl,
|
|
2624
|
+
environment: process.env.NODE_ENV === "production" ? "production" : "development",
|
|
2625
|
+
availableLocales: catalog.marketing?.availableLocales,
|
|
2626
|
+
defaultLocale: catalog.marketing?.availableLocales?.[0]
|
|
2627
|
+
},
|
|
2628
|
+
build: {
|
|
2629
|
+
platformPackageVersion: "0.0.0",
|
|
2630
|
+
appVersion: "0.0.0"
|
|
2631
|
+
},
|
|
2632
|
+
planCatalogSnapshot: {
|
|
2633
|
+
source: "saas-platform-module",
|
|
2634
|
+
hash: "sha256-quickstart",
|
|
2635
|
+
currency: catalog.currency,
|
|
2636
|
+
vatRate: catalog.vatRate,
|
|
2637
|
+
plans: catalog.plans ?? [],
|
|
2638
|
+
features: catalog.features ?? []
|
|
2639
|
+
}
|
|
2640
|
+
}), "useFactory"),
|
|
2641
|
+
inject: [
|
|
2642
|
+
PLAN_CATALOG_TOKEN
|
|
2643
|
+
]
|
|
2644
|
+
};
|
|
2645
|
+
}
|
|
2646
|
+
__name(buildMinimalManifestConfig, "buildMinimalManifestConfig");
|
|
2647
|
+
var SaasPlatformModule = class _SaasPlatformModule {
|
|
2648
|
+
static {
|
|
2649
|
+
__name(this, "SaasPlatformModule");
|
|
2650
|
+
}
|
|
2651
|
+
static forRoot(options) {
|
|
2652
|
+
if (!options.planCatalog && !options.adapters.planCatalogReadSink) {
|
|
2653
|
+
throw new Error("SaasPlatformModule.forRoot: entweder `planCatalog` (Quickstart) oder `adapters.planCatalogReadSink` (DB-Hydration) muss gesetzt sein.");
|
|
2654
|
+
}
|
|
2655
|
+
if (options.entitlement) {
|
|
2656
|
+
const missing = [];
|
|
2657
|
+
if (!options.adapters.subscriptionRepository) missing.push("subscriptionRepository");
|
|
2658
|
+
if (!options.adapters.planVersionRepository) missing.push("planVersionRepository");
|
|
2659
|
+
if (!options.adapters.transactionRunner) missing.push("transactionRunner");
|
|
2660
|
+
if (missing.length) {
|
|
2661
|
+
throw new Error(`SaasPlatformModule.forRoot: entitlement aktiv, aber Adapter fehlen: ${missing.join(", ")}`);
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
const planCatalogModule = options.planCatalog ? PlanCatalogModule.forRootWithCatalog(options.planCatalog, {
|
|
2665
|
+
global: true
|
|
2666
|
+
}) : PlanCatalogModule.forRoot({
|
|
2667
|
+
projectKey: "",
|
|
2668
|
+
currency: "",
|
|
2669
|
+
vatRate: 0,
|
|
2670
|
+
sink: options.adapters.planCatalogReadSink,
|
|
2671
|
+
imports: options.imports
|
|
2672
|
+
});
|
|
2673
|
+
const appInfo = options.app ?? {
|
|
2674
|
+
key: options.planCatalog?.projectKey ?? "app",
|
|
2675
|
+
version: options.planCatalog?.app?.version ?? "0.0.0"
|
|
2676
|
+
};
|
|
2677
|
+
const imports = [
|
|
2678
|
+
planCatalogModule,
|
|
2679
|
+
DiscoveryModule.forRoot({
|
|
2680
|
+
app: appInfo,
|
|
2681
|
+
controller: {
|
|
2682
|
+
guards: options.controller.guards
|
|
2683
|
+
},
|
|
2684
|
+
imports: options.imports,
|
|
2685
|
+
snapshotPath: options.discoverySnapshotPath === void 0 ? "var/discovery-snapshot.json" : options.discoverySnapshotPath
|
|
2686
|
+
}),
|
|
2687
|
+
AdminModule.forRoot({
|
|
2688
|
+
mfaPort: options.adapters.mfa,
|
|
2689
|
+
auditPort: options.adapters.audit,
|
|
2690
|
+
rlsBypassPort: options.adapters.rlsBypass,
|
|
2691
|
+
global: true
|
|
2692
|
+
}),
|
|
2693
|
+
AdminManifestModule.forRoot({
|
|
2694
|
+
config: options.adminManifestConfig ?? buildMinimalManifestConfig(),
|
|
2695
|
+
guards: options.controller.guards,
|
|
2696
|
+
reloadGuards: options.reloadGuards
|
|
2697
|
+
})
|
|
2698
|
+
];
|
|
2699
|
+
if (options.entitlement) {
|
|
2700
|
+
imports.push(EntitlementModule.forRoot({
|
|
2701
|
+
subscriptionRepository: options.adapters.subscriptionRepository,
|
|
2702
|
+
planVersionRepository: options.adapters.planVersionRepository,
|
|
2703
|
+
transactionRunner: options.adapters.transactionRunner,
|
|
2704
|
+
resolutionConfig: options.entitlement.resolutionConfig
|
|
2705
|
+
}));
|
|
2706
|
+
}
|
|
2707
|
+
const lightweightProviders = [];
|
|
2708
|
+
const lightweightExports = [];
|
|
2709
|
+
const hasResolver = !!options.adapters.planResolver;
|
|
2710
|
+
const hasFallback = !!options.defaultPlanId;
|
|
2711
|
+
if (hasResolver || hasFallback) {
|
|
2712
|
+
lightweightProviders.push(hasResolver ? asProvider(PLAN_RESOLVER_PORT_TOKEN, options.adapters.planResolver) : {
|
|
2713
|
+
provide: PLAN_RESOLVER_PORT_TOKEN,
|
|
2714
|
+
useValue: new StaticPlanResolver(options.defaultPlanId)
|
|
2715
|
+
}, StaticEntitlementService, StaticFeatureGuard, EnforceQuotaInterceptor, ...options.quotaProviders ?? [], {
|
|
2716
|
+
provide: QUOTA_PROVIDERS_TOKEN,
|
|
2717
|
+
useFactory: /* @__PURE__ */ __name((...providers) => providers, "useFactory"),
|
|
2718
|
+
inject: options.quotaProviders ?? []
|
|
2719
|
+
}, {
|
|
2720
|
+
provide: import_core6.APP_GUARD,
|
|
2721
|
+
useExisting: StaticFeatureGuard
|
|
2722
|
+
}, {
|
|
2723
|
+
provide: import_core6.APP_INTERCEPTOR,
|
|
2724
|
+
useExisting: EnforceQuotaInterceptor
|
|
2725
|
+
});
|
|
2726
|
+
lightweightExports.push(PLAN_RESOLVER_PORT_TOKEN, StaticEntitlementService, StaticFeatureGuard, EnforceQuotaInterceptor, QUOTA_PROVIDERS_TOKEN);
|
|
2727
|
+
}
|
|
2728
|
+
const tenantControllers = [];
|
|
2729
|
+
if (options.tenantManifest) {
|
|
2730
|
+
if (!hasResolver && !hasFallback) {
|
|
2731
|
+
throw new Error("SaasPlatformModule.forRoot: tenantManifest erfordert defaultPlanId oder adapters.planResolver, sonst kann das Manifest keine Features aufl\xF6sen.");
|
|
2732
|
+
}
|
|
2733
|
+
lightweightProviders.push(TenantManifestService);
|
|
2734
|
+
lightweightExports.push(TenantManifestService);
|
|
2735
|
+
tenantControllers.push(buildTenantManifestController(options.tenantManifest));
|
|
2736
|
+
}
|
|
2737
|
+
return {
|
|
2738
|
+
module: _SaasPlatformModule,
|
|
2739
|
+
imports,
|
|
2740
|
+
providers: lightweightProviders,
|
|
2741
|
+
controllers: tenantControllers,
|
|
2742
|
+
exports: [
|
|
2743
|
+
PlanCatalogModule,
|
|
2744
|
+
DiscoveryModule,
|
|
2745
|
+
AdminModule,
|
|
2746
|
+
AdminManifestModule,
|
|
2747
|
+
...options.entitlement ? [
|
|
2748
|
+
EntitlementModule
|
|
2749
|
+
] : [],
|
|
2750
|
+
ADMIN_MANIFEST_CONFIG,
|
|
2751
|
+
...lightweightExports
|
|
2752
|
+
],
|
|
2753
|
+
global: true
|
|
2754
|
+
};
|
|
2755
|
+
}
|
|
2756
|
+
};
|
|
2757
|
+
SaasPlatformModule = _ts_decorate21([
|
|
2758
|
+
(0, import_common22.Module)({})
|
|
2759
|
+
], SaasPlatformModule);
|
|
2760
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2761
|
+
0 && (module.exports = {
|
|
2762
|
+
EnforceQuotaInterceptor,
|
|
2763
|
+
PLAN_RESOLVER_PORT_TOKEN,
|
|
2764
|
+
QUOTA_PROVIDERS_TOKEN,
|
|
2765
|
+
STATIC_FEATURE_GUARD_CONFIG_TOKEN,
|
|
2766
|
+
SaasPlatformModule,
|
|
2767
|
+
StaticEntitlementService,
|
|
2768
|
+
StaticFeatureGuard,
|
|
2769
|
+
StaticPlanResolver,
|
|
2770
|
+
TenantManifestService,
|
|
2771
|
+
buildTenantManifestController
|
|
2772
|
+
});
|