@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.
Files changed (96) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +53 -0
  3. package/dist/admin/index.cjs +1283 -0
  4. package/dist/admin/index.d.cts +225 -0
  5. package/dist/admin/index.d.ts +225 -0
  6. package/dist/admin/index.js +58 -0
  7. package/dist/admin-audit.service-9IqXMlZm.d.cts +30 -0
  8. package/dist/admin-audit.service-9IqXMlZm.d.ts +30 -0
  9. package/dist/admin-manifest.config-DyrQNT7M.d.cts +10 -0
  10. package/dist/admin-manifest.config-DyrQNT7M.d.ts +10 -0
  11. package/dist/aggregation-CJ3qQf92.d.cts +288 -0
  12. package/dist/aggregation-Dvz9e8X2.d.ts +288 -0
  13. package/dist/billing/index.cjs +5205 -0
  14. package/dist/billing/index.d.cts +1326 -0
  15. package/dist/billing/index.d.ts +1326 -0
  16. package/dist/billing/index.js +197 -0
  17. package/dist/catalog/index.cjs +6449 -0
  18. package/dist/catalog/index.d.cts +1073 -0
  19. package/dist/catalog/index.d.ts +1073 -0
  20. package/dist/catalog/index.js +158 -0
  21. package/dist/checkout-offer/index.cjs +807 -0
  22. package/dist/checkout-offer/index.d.cts +120 -0
  23. package/dist/checkout-offer/index.d.ts +120 -0
  24. package/dist/checkout-offer/index.js +20 -0
  25. package/dist/chunk-2FR6ZL7R.js +0 -0
  26. package/dist/chunk-2PNX2QL2.js +347 -0
  27. package/dist/chunk-2VNZYRDV.js +326 -0
  28. package/dist/chunk-7MLGHWKO.js +12 -0
  29. package/dist/chunk-ABB2CRAS.js +31 -0
  30. package/dist/chunk-AJ3EPELS.js +157 -0
  31. package/dist/chunk-E56W4U2P.js +90 -0
  32. package/dist/chunk-HRU3CP57.js +71 -0
  33. package/dist/chunk-L4SUWH5B.js +14 -0
  34. package/dist/chunk-M6CZQYPY.js +314 -0
  35. package/dist/chunk-MDIZUVIK.js +87 -0
  36. package/dist/chunk-MV6LW5UB.js +217 -0
  37. package/dist/chunk-NYLON2VC.js +6 -0
  38. package/dist/chunk-P6MYZMXQ.js +951 -0
  39. package/dist/chunk-Q53N43LQ.js +13 -0
  40. package/dist/chunk-QJVPRD3R.js +573 -0
  41. package/dist/chunk-RIGLQ4BV.js +3367 -0
  42. package/dist/chunk-SABTXESR.js +24 -0
  43. package/dist/chunk-SHUYVCID.js +6 -0
  44. package/dist/chunk-SYEVGPFC.js +686 -0
  45. package/dist/chunk-V7HUZ6UQ.js +5923 -0
  46. package/dist/chunk-WPUVJRIN.js +29 -0
  47. package/dist/chunk-XCZ3TTFK.js +22 -0
  48. package/dist/chunk-XSGU2Y2O.js +1611 -0
  49. package/dist/chunk-YNEW3L63.js +190 -0
  50. package/dist/chunk-YXGHPILG.js +614 -0
  51. package/dist/chunk-ZL4LLE2Z.js +465 -0
  52. package/dist/di-CcNeq9v-.d.cts +16 -0
  53. package/dist/di-CcNeq9v-.d.ts +16 -0
  54. package/dist/discovery/index.cjs +588 -0
  55. package/dist/discovery/index.d.cts +299 -0
  56. package/dist/discovery/index.d.ts +299 -0
  57. package/dist/discovery/index.js +49 -0
  58. package/dist/discovery.scanner-CUYLKlYT.d.cts +59 -0
  59. package/dist/discovery.scanner-CUYLKlYT.d.ts +59 -0
  60. package/dist/entitlement/index.cjs +805 -0
  61. package/dist/entitlement/index.d.cts +60 -0
  62. package/dist/entitlement/index.d.ts +60 -0
  63. package/dist/entitlement/index.js +57 -0
  64. package/dist/index.cjs +15918 -0
  65. package/dist/index.d.cts +85 -0
  66. package/dist/index.d.ts +85 -0
  67. package/dist/index.js +956 -0
  68. package/dist/plan-resolution-CFCoUkrE.d.cts +41 -0
  69. package/dist/plan-resolution-CFCoUkrE.d.ts +41 -0
  70. package/dist/platform/index.cjs +2772 -0
  71. package/dist/platform/index.d.cts +118 -0
  72. package/dist/platform/index.d.ts +118 -0
  73. package/dist/platform/index.js +37 -0
  74. package/dist/promo/index.cjs +995 -0
  75. package/dist/promo/index.d.cts +121 -0
  76. package/dist/promo/index.d.ts +121 -0
  77. package/dist/promo/index.js +58 -0
  78. package/dist/registration/index.cjs +1687 -0
  79. package/dist/registration/index.d.cts +444 -0
  80. package/dist/registration/index.d.ts +444 -0
  81. package/dist/registration/index.js +82 -0
  82. package/dist/saas-platform.module-CgZ2omgg.d.cts +180 -0
  83. package/dist/saas-platform.module-DT9TnCzk.d.ts +180 -0
  84. package/dist/service-DX8KbGXl.d.cts +109 -0
  85. package/dist/service-DX8KbGXl.d.ts +109 -0
  86. package/dist/subscription-contract/index.cjs +471 -0
  87. package/dist/subscription-contract/index.d.cts +17 -0
  88. package/dist/subscription-contract/index.d.ts +17 -0
  89. package/dist/subscription-contract/index.js +21 -0
  90. package/dist/subscription-contract.service--cm47ZJJ.d.cts +39 -0
  91. package/dist/subscription-contract.service--cm47ZJJ.d.ts +39 -0
  92. package/dist/testing/index.cjs +3942 -0
  93. package/dist/testing/index.d.cts +279 -0
  94. package/dist/testing/index.d.ts +279 -0
  95. package/dist/testing/index.js +1195 -0
  96. 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
+ });