@saasicat/nest 1.0.0-rc.2 → 1.0.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +44 -8
- package/dist/.build-stamp +1 -1
- package/dist/_entries.cjs +10214 -4796
- package/dist/admin/index.d.cts +5 -4
- package/dist/admin/index.d.ts +5 -4
- package/dist/admin/index.js +13 -9
- package/dist/{admin-resources.module-CpvzcfCc.d.cts → admin-resources.module-B4XNvdH-.d.cts} +1 -2
- package/dist/{admin-resources.module-DF5_Q7RD.d.ts → admin-resources.module-CWqdI9BV.d.ts} +1 -2
- package/dist/{admin-stats.service-TYiSUwIR.d.ts → admin-stats.service-CsLNt19Z.d.ts} +11 -7
- package/dist/{admin-stats.service-DfKupywR.d.cts → admin-stats.service-DrZrU8IJ.d.cts} +11 -7
- package/dist/{aggregation-B3CD0v_d.d.cts → aggregation-ChpZDh0g.d.ts} +39 -6
- package/dist/{aggregation-BFzW07DE.d.ts → aggregation-OpnhwzF5.d.cts} +39 -6
- package/dist/billing/index.d.cts +448 -108
- package/dist/billing/index.d.ts +448 -108
- package/dist/billing/index.js +108 -41
- package/dist/catalog/index.d.cts +74 -45
- package/dist/catalog/index.d.ts +74 -45
- package/dist/catalog/index.js +21 -13
- package/dist/catalog.module-C9O7BxRq.d.ts +180 -0
- package/dist/catalog.module-T-I5Ricz.d.cts +180 -0
- package/dist/checkout-offer/index.d.cts +17 -22
- package/dist/checkout-offer/index.d.ts +17 -22
- package/dist/checkout-offer/index.js +21 -7
- package/dist/checkout-offer.module-DFhGy6JO.d.cts +58 -0
- package/dist/checkout-offer.module-x7SP_dX4.d.ts +58 -0
- package/dist/checkout-offer.service-BeHgYikI.d.ts +242 -0
- package/dist/checkout-offer.service-CCVxG9b2.d.cts +242 -0
- package/dist/chunk-34XVPR6P.js +222 -0
- package/dist/{chunk-AOQJEYOL.js → chunk-3QVGA6JX.js} +3 -1
- package/dist/chunk-3RLWML2S.js +896 -0
- package/dist/{chunk-TXC3LHHB.js → chunk-4SDSYV4A.js} +21 -13
- package/dist/chunk-55QEDAMX.js +946 -0
- package/dist/chunk-5CKC7O52.js +140 -0
- package/dist/chunk-6PJE7EWO.js +185 -0
- package/dist/chunk-7NJKVC5X.js +353 -0
- package/dist/{chunk-RUNZ3X4C.js → chunk-AOS2JPDD.js} +1263 -185
- package/dist/{chunk-WOVQPXV4.js → chunk-CQ2ZZMTD.js} +702 -725
- package/dist/{chunk-NA6O63B7.js → chunk-F5X66HF5.js} +5 -5
- package/dist/chunk-FEQZCWUL.js +174 -0
- package/dist/{chunk-VIJ5NJWC.js → chunk-G6EZWECL.js} +107 -31
- package/dist/chunk-G7RQO2XO.js +0 -0
- package/dist/{chunk-N3L3AICU.js → chunk-GPQWGA6B.js} +5 -6
- package/dist/chunk-GZTL64QK.js +7 -0
- package/dist/{chunk-SABTXESR.js → chunk-J7NJRK3K.js} +17 -1
- package/dist/{chunk-SEPN52AD.js → chunk-JBCC6C3M.js} +566 -307
- package/dist/{chunk-I7GTA3RX.js → chunk-JQRA724W.js} +1 -1
- package/dist/{chunk-WXYJHZCN.js → chunk-JVAEKTJ4.js} +2 -14
- package/dist/chunk-KPEMTBOP.js +26 -0
- package/dist/chunk-LF6J4YYN.js +301 -0
- package/dist/{chunk-LTT736P3.js → chunk-LLYVYRGJ.js} +228 -387
- package/dist/{chunk-6Z7JR4EW.js → chunk-M47BNEY2.js} +1897 -633
- package/dist/{chunk-KFT5AIIH.js → chunk-NDLC5GYK.js} +206 -24
- package/dist/{chunk-O2J2HDXA.js → chunk-OOTEXP47.js} +3 -8
- package/dist/chunk-QPVDCKYS.js +702 -0
- package/dist/{chunk-NHVDCYK5.js → chunk-R5YCJBHY.js} +71 -106
- package/dist/{chunk-XBYAFEOR.js → chunk-S33SO5XX.js} +1 -1
- package/dist/chunk-SZ7RFPXA.js +10 -0
- package/dist/chunk-TBBZWZQT.js +98 -0
- package/dist/chunk-TOEFN7DN.js +295 -0
- package/dist/{chunk-7ZEGFL42.js → chunk-WHJSYKNC.js} +6 -6
- package/dist/chunk-WOEJ6K7M.js +55 -0
- package/dist/{chunk-AU3OOREM.js → chunk-XSSWYPP5.js} +16 -3
- package/dist/contract-line-item-money-B0Z3Ogur.d.cts +43 -0
- package/dist/contract-line-item-money-B0Z3Ogur.d.ts +43 -0
- package/dist/discovery/index.d.cts +2 -2
- package/dist/discovery/index.d.ts +2 -2
- package/dist/discovery/index.js +6 -6
- package/dist/{discovery.scanner-9cMqF95o.d.cts → discovery.scanner-DXKc6JkV.d.cts} +1 -1
- package/dist/{discovery.scanner-9cMqF95o.d.ts → discovery.scanner-DXKc6JkV.d.ts} +1 -1
- package/dist/{enforce-quota.interceptor-Df0e3OWe.d.cts → enforce-quota.interceptor-BFyVdWe8.d.cts} +6 -6
- package/dist/{enforce-quota.interceptor-DW3etVyI.d.ts → enforce-quota.interceptor-GaBXboGx.d.ts} +6 -6
- package/dist/entitlement/index.d.cts +27 -4
- package/dist/entitlement/index.d.ts +27 -4
- package/dist/entitlement/index.js +13 -8
- package/dist/index.d.cts +209 -28
- package/dist/index.d.ts +209 -28
- package/dist/index.js +285 -163
- package/dist/issuer-identity.check-CBO0vcsq.d.cts +197 -0
- package/dist/issuer-identity.check-Cz9CEMkZ.d.ts +197 -0
- package/dist/{module-options-COSEb0VW.d.ts → module-options-DmQ3G4sZ.d.ts} +135 -41
- package/dist/{module-options-BG3MzQva.d.cts → module-options-qAOwinHR.d.cts} +135 -41
- package/dist/payment-callback.service-CR84Xw1Z.d.cts +125 -0
- package/dist/payment-callback.service-CR84Xw1Z.d.ts +125 -0
- package/dist/payments/index.cjs +4 -0
- package/dist/payments/index.d.cts +187 -0
- package/dist/payments/index.d.ts +187 -0
- package/dist/payments/index.js +141 -0
- package/dist/payments.module-BdUq9ot_.d.cts +57 -0
- package/dist/payments.module-CQGv_VG7.d.ts +57 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.cts +21 -0
- package/dist/plan-catalog-source-DWe-BGY1.d.ts +21 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.cts → plan-resolution-CwUy_OqC.d.cts} +14 -0
- package/dist/{plan-resolution-Cgo_TR1H.d.ts → plan-resolution-CwUy_OqC.d.ts} +14 -0
- package/dist/{plan-versions.service-DChhbK6h.d.ts → plan-versions.service-C0N-Em2W.d.ts} +38 -64
- package/dist/{plan-versions.service-TPEtbN0r.d.cts → plan-versions.service-DARLh-qI.d.cts} +38 -64
- package/dist/platform/index.d.cts +52 -23
- package/dist/platform/index.d.ts +52 -23
- package/dist/platform/index.js +57 -36
- package/dist/promo/index.d.cts +23 -8
- package/dist/promo/index.d.ts +23 -8
- package/dist/promo/index.js +15 -7
- package/dist/{promo.module-DtvIycpt.d.cts → promo.module-CKkECmU8.d.cts} +9 -3
- package/dist/{promo.module-Z8h1PNX-.d.ts → promo.module-DuIJdK3U.d.ts} +9 -3
- package/dist/promo.service-BYBu6dy3.d.cts +215 -0
- package/dist/promo.service-CFjfF0Xm.d.ts +215 -0
- package/dist/registration/index.d.cts +104 -47
- package/dist/registration/index.d.ts +104 -47
- package/dist/registration/index.js +15 -9
- package/dist/subscriber/index.cjs +4 -0
- package/dist/subscriber/index.d.cts +28 -0
- package/dist/subscriber/index.d.ts +28 -0
- package/dist/subscriber/index.js +16 -0
- package/dist/subscriber.service-5W6Gi-1B.d.cts +68 -0
- package/dist/subscriber.service-5W6Gi-1B.d.ts +68 -0
- package/dist/subscription-contract/index.d.cts +4 -2
- package/dist/subscription-contract/index.d.ts +4 -2
- package/dist/subscription-contract/index.js +13 -5
- package/dist/{subscription-contract.module-fDFdQ0jm.d.ts → subscription-contract.module-BfIRpc1o.d.ts} +8 -2
- package/dist/{subscription-contract.module-CEK8HS5-.d.cts → subscription-contract.module-DTMuUJYd.d.cts} +8 -2
- package/dist/{subscription-contract.service-hD87MKyg.d.cts → subscription-contract.service-CPZrTM9C.d.ts} +32 -3
- package/dist/{subscription-contract.service-hD87MKyg.d.ts → subscription-contract.service-CfNApLuP.d.cts} +32 -3
- package/dist/tenant-billing.controller-BLqbsONe.d.ts +812 -0
- package/dist/tenant-billing.controller-DKbAqTbM.d.cts +812 -0
- package/dist/tenant-billing.module-ClbjzocC.d.cts +364 -0
- package/dist/tenant-billing.module-DohgJJ2m.d.ts +364 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.cts +129 -0
- package/dist/tenant-billing.tokens-G-1xOlMr.d.ts +129 -0
- package/dist/testing/index.d.cts +44 -17
- package/dist/testing/index.d.ts +44 -17
- package/dist/testing/index.js +189 -40
- package/package.json +24 -4
- package/dist/catalog.module-BDDO6iwq.d.cts +0 -103
- package/dist/catalog.module-BTnMsE6u.d.ts +0 -103
- package/dist/checkout-offer.module-CMtiQJTT.d.ts +0 -39
- package/dist/checkout-offer.module-CVpWrzbe.d.cts +0 -39
- package/dist/checkout-offer.service-BLOv2HOo.d.cts +0 -49
- package/dist/checkout-offer.service-BLOv2HOo.d.ts +0 -49
- package/dist/chunk-57V6ZTI6.js +0 -22
- package/dist/chunk-BSK6YBLI.js +0 -87
- package/dist/chunk-DQKCK7DX.js +0 -217
- package/dist/chunk-VXEYLNIB.js +0 -632
- package/dist/chunk-WUDYIPYH.js +0 -715
- package/dist/define-saasicat-DROBe-b1.d.ts +0 -69
- package/dist/define-saasicat-vGBCkFys.d.cts +0 -69
- package/dist/promo.service-BJbKAmw3.d.cts +0 -112
- package/dist/promo.service-BJbKAmw3.d.ts +0 -112
- package/dist/tenant-billing.controller-BQ67mlmI.d.ts +0 -438
- package/dist/tenant-billing.controller-DS8kB8a7.d.cts +0 -438
- package/dist/tenant-billing.module-BznC3XtW.d.cts +0 -299
- package/dist/tenant-billing.module-wUbsQSFG.d.ts +0 -299
- /package/dist/{chunk-2FR6ZL7R.js → chunk-BHZOH2DY.js} +0 -0
- /package/dist/{chunk-2SLTRKXC.js → chunk-DFOW3JVO.js} +0 -0
- /package/dist/{chunk-DBZGV3HC.js → chunk-DU56UKEH.js} +0 -0
- /package/dist/{chunk-OYNHOY45.js → chunk-GAUEVIAE.js} +0 -0
|
@@ -1,61 +1,86 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PromoCodesModule
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4SDSYV4A.js";
|
|
4
4
|
import {
|
|
5
5
|
SubscriptionContractModule
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-XSSWYPP5.js";
|
|
7
7
|
import {
|
|
8
8
|
AdminManifestModule,
|
|
9
9
|
AdminManifestService,
|
|
10
10
|
AdminModule,
|
|
11
11
|
AdminResourcesModule,
|
|
12
12
|
AdminStatsModule,
|
|
13
|
-
MfaService,
|
|
14
13
|
SuperAdminGuard
|
|
15
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-LLYVYRGJ.js";
|
|
16
15
|
import {
|
|
17
16
|
FEATURE_GUARD_MARKER,
|
|
18
17
|
PublicCatalogModule,
|
|
19
18
|
REQUIRE_FEATURE_KEY,
|
|
20
19
|
SubscriptionBundleModule,
|
|
21
20
|
TenantBillingModule,
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
catalogSource,
|
|
22
|
+
isPlatformFeatureGuard,
|
|
23
|
+
loadPlanCatalogFromFile
|
|
24
|
+
} from "./chunk-M47BNEY2.js";
|
|
24
25
|
import {
|
|
25
|
-
CatalogModule
|
|
26
|
-
|
|
26
|
+
CatalogModule,
|
|
27
|
+
WebAuditLogger,
|
|
28
|
+
actorTagOf,
|
|
29
|
+
defaultActorFromRequest
|
|
30
|
+
} from "./chunk-CQ2ZZMTD.js";
|
|
31
|
+
import {
|
|
32
|
+
MfaService
|
|
33
|
+
} from "./chunk-34XVPR6P.js";
|
|
27
34
|
import {
|
|
28
35
|
CheckoutOfferModule
|
|
29
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-TOEFN7DN.js";
|
|
30
37
|
import {
|
|
31
|
-
|
|
32
|
-
} from "./chunk-
|
|
38
|
+
DiscoveryModule
|
|
39
|
+
} from "./chunk-S33SO5XX.js";
|
|
33
40
|
import {
|
|
34
41
|
EntitlementModule
|
|
35
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-WHJSYKNC.js";
|
|
36
43
|
import {
|
|
37
|
-
LimitExceededError
|
|
38
|
-
|
|
44
|
+
LimitExceededError,
|
|
45
|
+
cancellationHasLanded
|
|
46
|
+
} from "./chunk-G6EZWECL.js";
|
|
39
47
|
import {
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
} from "./chunk-DQKCK7DX.js";
|
|
43
|
-
import {
|
|
44
|
-
codedError
|
|
45
|
-
} from "./chunk-57V6ZTI6.js";
|
|
48
|
+
findPlan
|
|
49
|
+
} from "./chunk-TBBZWZQT.js";
|
|
46
50
|
import {
|
|
47
|
-
|
|
48
|
-
} from "./chunk-
|
|
49
|
-
import {
|
|
50
|
-
DiscoveryModule
|
|
51
|
-
} from "./chunk-XBYAFEOR.js";
|
|
51
|
+
SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN
|
|
52
|
+
} from "./chunk-VBRYPU7V.js";
|
|
52
53
|
import {
|
|
53
54
|
ENFORCE_QUOTA_KEY
|
|
54
55
|
} from "./chunk-QUZU2M33.js";
|
|
56
|
+
import {
|
|
57
|
+
PaymentsModule
|
|
58
|
+
} from "./chunk-QPVDCKYS.js";
|
|
59
|
+
import {
|
|
60
|
+
PaymentGatewayRegistry
|
|
61
|
+
} from "./chunk-7NJKVC5X.js";
|
|
62
|
+
import {
|
|
63
|
+
RLS_BYPASS_PORT_TOKEN
|
|
64
|
+
} from "./chunk-SZ7RFPXA.js";
|
|
65
|
+
import {
|
|
66
|
+
SaaSiCatPublicRoute
|
|
67
|
+
} from "./chunk-YSC46BGM.js";
|
|
68
|
+
import {
|
|
69
|
+
PLAN_CATALOG_SETTINGS_TOKEN,
|
|
70
|
+
PLAN_CATALOG_SOURCE_TOKEN,
|
|
71
|
+
PlanCatalogModule
|
|
72
|
+
} from "./chunk-FEQZCWUL.js";
|
|
73
|
+
import {
|
|
74
|
+
asProvider,
|
|
75
|
+
codedError
|
|
76
|
+
} from "./chunk-J7NJRK3K.js";
|
|
55
77
|
import {
|
|
56
78
|
__name
|
|
57
79
|
} from "./chunk-SHUYVCID.js";
|
|
58
80
|
|
|
81
|
+
// src/core/email.tokens.ts
|
|
82
|
+
var EMAIL_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/EmailPort");
|
|
83
|
+
|
|
59
84
|
// src/setup/setup.tokens.ts
|
|
60
85
|
var SETUP_PROVISIONING_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/SetupProvisioningPort");
|
|
61
86
|
var SETUP_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/SetupConfig");
|
|
@@ -352,9 +377,9 @@ SetupModule = _ts_decorate3([
|
|
|
352
377
|
Module({})
|
|
353
378
|
], SetupModule);
|
|
354
379
|
|
|
355
|
-
// src/
|
|
356
|
-
import { Inject as Inject2, Injectable as Injectable2, Optional } from "@nestjs/common";
|
|
357
|
-
import {
|
|
380
|
+
// src/settings/settings-change-notifier.ts
|
|
381
|
+
import { Inject as Inject2, Injectable as Injectable2, Logger, Optional } from "@nestjs/common";
|
|
382
|
+
import { diffSettings } from "@saasicat/core";
|
|
358
383
|
function _ts_decorate4(decorators, target, key, desc) {
|
|
359
384
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
360
385
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -372,6 +397,527 @@ function _ts_param3(paramIndex, decorator) {
|
|
|
372
397
|
};
|
|
373
398
|
}
|
|
374
399
|
__name(_ts_param3, "_ts_param");
|
|
400
|
+
var MAIL_SEND_TIMEOUT_MS = 3e4;
|
|
401
|
+
function withinTime(promise, ms, to) {
|
|
402
|
+
return new Promise((resolve, reject) => {
|
|
403
|
+
const timer = setTimeout(() => reject(new Error(`no answer from the email port within ${ms} ms for ${to}`)), ms);
|
|
404
|
+
timer.unref();
|
|
405
|
+
promise.then((value) => {
|
|
406
|
+
clearTimeout(timer);
|
|
407
|
+
resolve(value);
|
|
408
|
+
}, (error) => {
|
|
409
|
+
clearTimeout(timer);
|
|
410
|
+
reject(error instanceof Error ? error : new Error(String(error)));
|
|
411
|
+
});
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
__name(withinTime, "withinTime");
|
|
415
|
+
var SettingsChangeNotifier = class _SettingsChangeNotifier {
|
|
416
|
+
static {
|
|
417
|
+
__name(this, "SettingsChangeNotifier");
|
|
418
|
+
}
|
|
419
|
+
settings;
|
|
420
|
+
email;
|
|
421
|
+
logger = new Logger(_SettingsChangeNotifier.name);
|
|
422
|
+
constructor(settings, email = null) {
|
|
423
|
+
this.settings = settings;
|
|
424
|
+
this.email = email;
|
|
425
|
+
}
|
|
426
|
+
/** The addresses `config/saas.yaml` names under `notifications.settingsChanged`. */
|
|
427
|
+
get addresses() {
|
|
428
|
+
return this.settings.notifications?.settingsChanged ?? [];
|
|
429
|
+
}
|
|
430
|
+
/**
|
|
431
|
+
* Which of the two it is, said once at boot — and only where the question
|
|
432
|
+
* arises. An installation naming nobody asked for in-app only, and a line
|
|
433
|
+
* about it would be a warning at a correct configuration.
|
|
434
|
+
*/
|
|
435
|
+
reportModeAtBoot() {
|
|
436
|
+
if (this.addresses.length === 0) return;
|
|
437
|
+
if (this.email) {
|
|
438
|
+
this.logger.log(`Settings changes are mailed to ${this.addresses.length} address(es) from config/saas.yaml#notifications.settingsChanged, and recorded in the application.`);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
this.logger.warn(`config/saas.yaml#notifications.settingsChanged names ${this.addresses.length} address(es), but no email port is bound (\`adapters.email\`). Changes are recorded in the application only; nobody is mailed.`);
|
|
442
|
+
}
|
|
443
|
+
/**
|
|
444
|
+
* Mails everybody the change concerns: the addresses the file names now,
|
|
445
|
+
* and — when the list itself is what moved — the ones it named before. An
|
|
446
|
+
* address taken off the list is told that once; otherwise whoever can edit
|
|
447
|
+
* the file could point the channel elsewhere and the people it pointed at
|
|
448
|
+
* would go quiet without a word, which is the failure this exists to
|
|
449
|
+
* prevent. Never throws: a mail that fails is logged.
|
|
450
|
+
*/
|
|
451
|
+
async notify(change) {
|
|
452
|
+
const addresses = recipientsOf(this.addresses, change.previous);
|
|
453
|
+
if (addresses.length === 0) return {
|
|
454
|
+
kind: "nobody-to-tell"
|
|
455
|
+
};
|
|
456
|
+
if (!this.email) return {
|
|
457
|
+
kind: "no-email-port",
|
|
458
|
+
addresses
|
|
459
|
+
};
|
|
460
|
+
const message = describeChange(this.settings.app.name, change);
|
|
461
|
+
const failed = [];
|
|
462
|
+
for (const to of addresses) {
|
|
463
|
+
try {
|
|
464
|
+
await withinTime(this.email.send({
|
|
465
|
+
to,
|
|
466
|
+
...message
|
|
467
|
+
}), MAIL_SEND_TIMEOUT_MS, to);
|
|
468
|
+
} catch (error) {
|
|
469
|
+
failed.push(to);
|
|
470
|
+
this.logger.error(`The settings-change mail to ${to} could not be sent; the change is recorded in the application regardless.`, error instanceof Error ? error.stack : String(error));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
kind: "sent",
|
|
475
|
+
addresses,
|
|
476
|
+
failed
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
};
|
|
480
|
+
SettingsChangeNotifier = _ts_decorate4([
|
|
481
|
+
Injectable2(),
|
|
482
|
+
_ts_param3(0, Inject2(PLAN_CATALOG_SETTINGS_TOKEN)),
|
|
483
|
+
_ts_param3(1, Optional()),
|
|
484
|
+
_ts_param3(1, Inject2(EMAIL_PORT_TOKEN)),
|
|
485
|
+
_ts_metadata3("design:type", Function),
|
|
486
|
+
_ts_metadata3("design:paramtypes", [
|
|
487
|
+
typeof PlanCatalogSettings === "undefined" ? Object : PlanCatalogSettings,
|
|
488
|
+
Object
|
|
489
|
+
])
|
|
490
|
+
], SettingsChangeNotifier);
|
|
491
|
+
function recipientsOf(running, previous) {
|
|
492
|
+
return [
|
|
493
|
+
.../* @__PURE__ */ new Set([
|
|
494
|
+
...running,
|
|
495
|
+
...addressListIn(previous)
|
|
496
|
+
])
|
|
497
|
+
];
|
|
498
|
+
}
|
|
499
|
+
__name(recipientsOf, "recipientsOf");
|
|
500
|
+
function addressListIn(settings) {
|
|
501
|
+
const notifications = settings.notifications;
|
|
502
|
+
if (typeof notifications !== "object" || notifications === null) return [];
|
|
503
|
+
const list2 = notifications.settingsChanged;
|
|
504
|
+
return Array.isArray(list2) ? list2.filter((a) => typeof a === "string") : [];
|
|
505
|
+
}
|
|
506
|
+
__name(addressListIn, "addressListIn");
|
|
507
|
+
function describeChange(appName, change) {
|
|
508
|
+
const lines = diffSettings(change.previous, change.current).map((d) => ` ${d.path}: ${JSON.stringify(d.before)} \u2192 ${JSON.stringify(d.after)}`);
|
|
509
|
+
return {
|
|
510
|
+
subject: `[${appName}] The configuration changed at the last start`,
|
|
511
|
+
text: [
|
|
512
|
+
`${appName} started at ${change.noticedAt.toISOString()} and found its settings changed`,
|
|
513
|
+
`since the previous start. The new values are running. What moved:`,
|
|
514
|
+
"",
|
|
515
|
+
...lines,
|
|
516
|
+
"",
|
|
517
|
+
`Source: ${change.source}`,
|
|
518
|
+
"The change is recorded in the administration under Settings until somebody acknowledges it."
|
|
519
|
+
].join("\n")
|
|
520
|
+
};
|
|
521
|
+
}
|
|
522
|
+
__name(describeChange, "describeChange");
|
|
523
|
+
|
|
524
|
+
// src/settings/settings-fingerprint.ts
|
|
525
|
+
import { createHash } from "crypto";
|
|
526
|
+
import { canonicalJson } from "@saasicat/core";
|
|
527
|
+
function fingerprintOf(settings) {
|
|
528
|
+
return `sha256-${createHash("sha256").update(canonicalJson(settings)).digest("hex")}`;
|
|
529
|
+
}
|
|
530
|
+
__name(fingerprintOf, "fingerprintOf");
|
|
531
|
+
|
|
532
|
+
// src/settings/settings.tokens.ts
|
|
533
|
+
var APPLIED_SETTINGS_PORT_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/AppliedSettingsPort");
|
|
534
|
+
var SETTINGS_SOURCE_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/SettingsSource");
|
|
535
|
+
|
|
536
|
+
// src/settings/applied-settings.recorder.ts
|
|
537
|
+
import { Inject as Inject3, Injectable as Injectable3, Logger as Logger2, Optional as Optional2 } from "@nestjs/common";
|
|
538
|
+
import { diffSettings as diffSettings2, settingsSubtreeOf } from "@saasicat/core";
|
|
539
|
+
function _ts_decorate5(decorators, target, key, desc) {
|
|
540
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
541
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
542
|
+
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;
|
|
543
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
544
|
+
}
|
|
545
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
546
|
+
function _ts_metadata4(k, v) {
|
|
547
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
548
|
+
}
|
|
549
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
550
|
+
function _ts_param4(paramIndex, decorator) {
|
|
551
|
+
return function(target, key) {
|
|
552
|
+
decorator(target, key, paramIndex);
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
__name(_ts_param4, "_ts_param");
|
|
556
|
+
var MAIL_BOOT_BUDGET_MS = 2e3;
|
|
557
|
+
var RECORD_ATTEMPTS = 5;
|
|
558
|
+
function pause(ms) {
|
|
559
|
+
return new Promise((resolve) => setTimeout(resolve, ms).unref());
|
|
560
|
+
}
|
|
561
|
+
__name(pause, "pause");
|
|
562
|
+
var AppliedSettingsRecorder = class _AppliedSettingsRecorder {
|
|
563
|
+
static {
|
|
564
|
+
__name(this, "AppliedSettingsRecorder");
|
|
565
|
+
}
|
|
566
|
+
settings;
|
|
567
|
+
source;
|
|
568
|
+
port;
|
|
569
|
+
notifier;
|
|
570
|
+
logger = new Logger2(_AppliedSettingsRecorder.name);
|
|
571
|
+
constructor(settings, source, port = null, notifier = null) {
|
|
572
|
+
this.settings = settings;
|
|
573
|
+
this.source = source;
|
|
574
|
+
this.port = port;
|
|
575
|
+
this.notifier = notifier;
|
|
576
|
+
}
|
|
577
|
+
/** Whether this installation keeps a record at all. */
|
|
578
|
+
get recording() {
|
|
579
|
+
return this.port !== null;
|
|
580
|
+
}
|
|
581
|
+
async onApplicationBootstrap() {
|
|
582
|
+
if (!this.port) {
|
|
583
|
+
this.logger.warn("The applied settings are not recorded: the persistence adapter provides no `core.appliedSettings` port, so the platform cannot say when its configuration was applied or notice that it changed.");
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
this.notifier?.reportModeAtBoot();
|
|
587
|
+
try {
|
|
588
|
+
const outcome = await this.record(this.port, () => /* @__PURE__ */ new Date());
|
|
589
|
+
this.report(outcome);
|
|
590
|
+
if (outcome.kind === "changed" && this.notifier) {
|
|
591
|
+
await this.tellSomebody(this.notifier, outcome.change);
|
|
592
|
+
}
|
|
593
|
+
} catch (error) {
|
|
594
|
+
this.logger.error(`The applied settings could not be recorded \u2014 the configuration in ${this.source} is running, but the record of it is stale.`, error instanceof Error ? error.stack : String(error));
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
/**
|
|
598
|
+
* Mails the change without holding the boot for it.
|
|
599
|
+
*
|
|
600
|
+
* Nest resolves `app.listen()` only once every bootstrap hook has settled,
|
|
601
|
+
* and an SMTP host that does not answer would otherwise turn a changed
|
|
602
|
+
* setting into an installation that does not start. So the boot waits a
|
|
603
|
+
* moment — long enough for a working adapter to hand the mail over, so
|
|
604
|
+
* the log reads in order — and then goes on; a slow adapter finishes on
|
|
605
|
+
* its own, and `notify()` bounds each send and logs what timed out.
|
|
606
|
+
*/
|
|
607
|
+
async tellSomebody(notifier, change) {
|
|
608
|
+
await Promise.race([
|
|
609
|
+
notifier.notify(change),
|
|
610
|
+
pause(MAIL_BOOT_BUDGET_MS)
|
|
611
|
+
]);
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Compares the running settings with the record and writes what follows.
|
|
615
|
+
*
|
|
616
|
+
* `clock` is read once per attempt, after the record has been read and
|
|
617
|
+
* right before it is written, so the moment a row carries is the moment
|
|
618
|
+
* of the write that produced it. Read once when the start began, it would
|
|
619
|
+
* date a write by its start — and a start whose write landed after
|
|
620
|
+
* another's could carry the earlier moment, listing the changes in an
|
|
621
|
+
* order the record never went through.
|
|
622
|
+
*/
|
|
623
|
+
async record(port, clock) {
|
|
624
|
+
const settings = settingsSubtreeOf(this.settings);
|
|
625
|
+
const fingerprint = fingerprintOf(settings);
|
|
626
|
+
for (let attempt = 1; attempt <= RECORD_ATTEMPTS; attempt++) {
|
|
627
|
+
const previous = await port.readApplied();
|
|
628
|
+
const running = {
|
|
629
|
+
fingerprint,
|
|
630
|
+
settings,
|
|
631
|
+
source: this.source,
|
|
632
|
+
appliedAt: clock()
|
|
633
|
+
};
|
|
634
|
+
const outcome = await this.reconcile(port, previous, running);
|
|
635
|
+
if (outcome) return outcome;
|
|
636
|
+
}
|
|
637
|
+
throw new Error(`The applied settings record moved ${RECORD_ATTEMPTS} times while this start was writing it. Either concurrent starts are running different configuration files, or the persistence adapter never confirms the guarded write: \`writeApplied\` must answer true, and \`recordChange\` the change, while the record still carries the fingerprint that was read.`);
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* What one read of the record calls for, written. `null` means the write
|
|
641
|
+
* found the record moved since `previous` was read, and wrote nothing.
|
|
642
|
+
*/
|
|
643
|
+
async reconcile(port, previous, running) {
|
|
644
|
+
if (previous?.fingerprint === running.fingerprint) {
|
|
645
|
+
if (previous.source === running.source) return {
|
|
646
|
+
kind: "unchanged",
|
|
647
|
+
record: previous
|
|
648
|
+
};
|
|
649
|
+
const relocated = {
|
|
650
|
+
...previous,
|
|
651
|
+
source: running.source
|
|
652
|
+
};
|
|
653
|
+
const written = await port.writeApplied(relocated, previous.fingerprint);
|
|
654
|
+
return written ? {
|
|
655
|
+
kind: "unchanged",
|
|
656
|
+
record: relocated
|
|
657
|
+
} : null;
|
|
658
|
+
}
|
|
659
|
+
if (!previous) {
|
|
660
|
+
const written = await port.writeApplied(running, null);
|
|
661
|
+
return written ? {
|
|
662
|
+
kind: "first-record",
|
|
663
|
+
record: running
|
|
664
|
+
} : null;
|
|
665
|
+
}
|
|
666
|
+
const change = await port.recordChange({
|
|
667
|
+
noticedAt: running.appliedAt,
|
|
668
|
+
source: running.source,
|
|
669
|
+
previous: previous.settings,
|
|
670
|
+
current: running.settings
|
|
671
|
+
}, running, previous.fingerprint);
|
|
672
|
+
return change ? {
|
|
673
|
+
kind: "changed",
|
|
674
|
+
record: running,
|
|
675
|
+
change
|
|
676
|
+
} : null;
|
|
677
|
+
}
|
|
678
|
+
report(outcome) {
|
|
679
|
+
switch (outcome.kind) {
|
|
680
|
+
case "first-record":
|
|
681
|
+
this.logger.log(`Applied settings recorded for the first time, from ${this.source}.`);
|
|
682
|
+
return;
|
|
683
|
+
case "changed": {
|
|
684
|
+
const lines = diffSettings2(outcome.change.previous, outcome.change.current).map((d) => `${d.path}: ${JSON.stringify(d.before)} \u2192 ${JSON.stringify(d.after)}`);
|
|
685
|
+
this.logger.warn(`The settings changed since the last start (${this.source}):
|
|
686
|
+
` + lines.join("\n "));
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
default:
|
|
690
|
+
return;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
};
|
|
694
|
+
AppliedSettingsRecorder = _ts_decorate5([
|
|
695
|
+
Injectable3(),
|
|
696
|
+
_ts_param4(0, Inject3(PLAN_CATALOG_SETTINGS_TOKEN)),
|
|
697
|
+
_ts_param4(1, Inject3(SETTINGS_SOURCE_TOKEN)),
|
|
698
|
+
_ts_param4(2, Optional2()),
|
|
699
|
+
_ts_param4(2, Inject3(APPLIED_SETTINGS_PORT_TOKEN)),
|
|
700
|
+
_ts_param4(3, Optional2()),
|
|
701
|
+
_ts_param4(3, Inject3(SettingsChangeNotifier)),
|
|
702
|
+
_ts_metadata4("design:type", Function),
|
|
703
|
+
_ts_metadata4("design:paramtypes", [
|
|
704
|
+
typeof PlanCatalogSettings === "undefined" ? Object : PlanCatalogSettings,
|
|
705
|
+
String,
|
|
706
|
+
Object,
|
|
707
|
+
Object
|
|
708
|
+
])
|
|
709
|
+
], AppliedSettingsRecorder);
|
|
710
|
+
|
|
711
|
+
// src/settings/settings.controller.ts
|
|
712
|
+
import { Controller as Controller2, Get as Get2, Inject as Inject4, NotFoundException, Optional as Optional3, Param, Post as Post2, Req, UseGuards } from "@nestjs/common";
|
|
713
|
+
import { SETTINGS_ERROR_CODES } from "@saasicat/core";
|
|
714
|
+
import { diffSettings as diffSettings3, settingsSubtreeOf as settingsSubtreeOf2 } from "@saasicat/core";
|
|
715
|
+
function _ts_decorate6(decorators, target, key, desc) {
|
|
716
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
717
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
718
|
+
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;
|
|
719
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
720
|
+
}
|
|
721
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
722
|
+
function _ts_metadata5(k, v) {
|
|
723
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
724
|
+
}
|
|
725
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
726
|
+
function _ts_param5(paramIndex, decorator) {
|
|
727
|
+
return function(target, key) {
|
|
728
|
+
decorator(target, key, paramIndex);
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
__name(_ts_param5, "_ts_param");
|
|
732
|
+
var RECENT_CHANGES = 20;
|
|
733
|
+
function buildSettingsController(guards) {
|
|
734
|
+
let GeneratedSettingsController = class GeneratedSettingsController {
|
|
735
|
+
static {
|
|
736
|
+
__name(this, "GeneratedSettingsController");
|
|
737
|
+
}
|
|
738
|
+
settings;
|
|
739
|
+
source;
|
|
740
|
+
port;
|
|
741
|
+
audit;
|
|
742
|
+
// Explicit @Inject: tsup/esbuild emit no `design:paramtypes`.
|
|
743
|
+
constructor(settings, source, port, audit = null) {
|
|
744
|
+
this.settings = settings;
|
|
745
|
+
this.source = source;
|
|
746
|
+
this.port = port;
|
|
747
|
+
this.audit = audit;
|
|
748
|
+
}
|
|
749
|
+
async getSettings() {
|
|
750
|
+
const settings = settingsSubtreeOf2(this.settings);
|
|
751
|
+
const fingerprint = fingerprintOf(settings);
|
|
752
|
+
const base = {
|
|
753
|
+
source: this.source,
|
|
754
|
+
fingerprint,
|
|
755
|
+
settings
|
|
756
|
+
};
|
|
757
|
+
if (!this.port) return {
|
|
758
|
+
...base,
|
|
759
|
+
recorded: false,
|
|
760
|
+
appliedAt: null,
|
|
761
|
+
changes: []
|
|
762
|
+
};
|
|
763
|
+
const [applied, changes] = await Promise.all([
|
|
764
|
+
this.port.readApplied(),
|
|
765
|
+
this.port.listChanges({
|
|
766
|
+
limit: RECENT_CHANGES
|
|
767
|
+
})
|
|
768
|
+
]);
|
|
769
|
+
return {
|
|
770
|
+
...base,
|
|
771
|
+
recorded: true,
|
|
772
|
+
appliedAt: applied?.fingerprint === fingerprint ? applied.appliedAt.toISOString() : null,
|
|
773
|
+
changes: changes.map(toChangeView)
|
|
774
|
+
};
|
|
775
|
+
}
|
|
776
|
+
/**
|
|
777
|
+
* Marks a change as seen. The record survives until this is called —
|
|
778
|
+
* that is what makes it a record rather than a log line — and the
|
|
779
|
+
* acknowledgement keeps its first author: a second call changes nothing
|
|
780
|
+
* and answers the record as it stands.
|
|
781
|
+
*/
|
|
782
|
+
async acknowledge(id, request) {
|
|
783
|
+
if (!this.port) {
|
|
784
|
+
throw new NotFoundException(codedError(SETTINGS_ERROR_CODES.SETTINGS_CHANGE_NOT_FOUND));
|
|
785
|
+
}
|
|
786
|
+
const acknowledged = await this.port.acknowledgeChange(id, this.audit?.actorTagFromRequest(request) ?? actorTagOf(defaultActorFromRequest(request)), /* @__PURE__ */ new Date());
|
|
787
|
+
if (!acknowledged) {
|
|
788
|
+
throw new NotFoundException(codedError(SETTINGS_ERROR_CODES.SETTINGS_CHANGE_NOT_FOUND));
|
|
789
|
+
}
|
|
790
|
+
await this.audit?.logFromRequest(request, "SettingsChange", id, "SETTINGS_CHANGE_ACKNOWLEDGE");
|
|
791
|
+
return toChangeView(acknowledged);
|
|
792
|
+
}
|
|
793
|
+
};
|
|
794
|
+
_ts_decorate6([
|
|
795
|
+
Get2("settings"),
|
|
796
|
+
_ts_metadata5("design:type", Function),
|
|
797
|
+
_ts_metadata5("design:paramtypes", []),
|
|
798
|
+
_ts_metadata5("design:returntype", Promise)
|
|
799
|
+
], GeneratedSettingsController.prototype, "getSettings", null);
|
|
800
|
+
_ts_decorate6([
|
|
801
|
+
Post2("settings/changes/:id/acknowledge"),
|
|
802
|
+
_ts_param5(0, Param("id")),
|
|
803
|
+
_ts_param5(1, Req()),
|
|
804
|
+
_ts_metadata5("design:type", Function),
|
|
805
|
+
_ts_metadata5("design:paramtypes", [
|
|
806
|
+
String,
|
|
807
|
+
Object
|
|
808
|
+
]),
|
|
809
|
+
_ts_metadata5("design:returntype", Promise)
|
|
810
|
+
], GeneratedSettingsController.prototype, "acknowledge", null);
|
|
811
|
+
GeneratedSettingsController = _ts_decorate6([
|
|
812
|
+
Controller2("admin"),
|
|
813
|
+
UseGuards(...guards),
|
|
814
|
+
_ts_param5(0, Inject4(PLAN_CATALOG_SETTINGS_TOKEN)),
|
|
815
|
+
_ts_param5(1, Inject4(SETTINGS_SOURCE_TOKEN)),
|
|
816
|
+
_ts_param5(2, Inject4(APPLIED_SETTINGS_PORT_TOKEN)),
|
|
817
|
+
_ts_param5(3, Optional3()),
|
|
818
|
+
_ts_param5(3, Inject4(WebAuditLogger)),
|
|
819
|
+
_ts_metadata5("design:type", Function),
|
|
820
|
+
_ts_metadata5("design:paramtypes", [
|
|
821
|
+
typeof PlanCatalogSettings === "undefined" ? Object : PlanCatalogSettings,
|
|
822
|
+
String,
|
|
823
|
+
Object,
|
|
824
|
+
Object
|
|
825
|
+
])
|
|
826
|
+
], GeneratedSettingsController);
|
|
827
|
+
return GeneratedSettingsController;
|
|
828
|
+
}
|
|
829
|
+
__name(buildSettingsController, "buildSettingsController");
|
|
830
|
+
function toChangeView(change) {
|
|
831
|
+
return {
|
|
832
|
+
id: change.id,
|
|
833
|
+
noticedAt: change.noticedAt.toISOString(),
|
|
834
|
+
source: change.source,
|
|
835
|
+
differences: diffSettings3(change.previous, change.current),
|
|
836
|
+
acknowledgedAt: change.acknowledgedAt?.toISOString() ?? null,
|
|
837
|
+
acknowledgedBy: change.acknowledgedBy
|
|
838
|
+
};
|
|
839
|
+
}
|
|
840
|
+
__name(toChangeView, "toChangeView");
|
|
841
|
+
|
|
842
|
+
// src/settings/settings.module.ts
|
|
843
|
+
import { Module as Module2 } from "@nestjs/common";
|
|
844
|
+
function _ts_decorate7(decorators, target, key, desc) {
|
|
845
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
846
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
847
|
+
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;
|
|
848
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
849
|
+
}
|
|
850
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
851
|
+
var SettingsModule = class _SettingsModule {
|
|
852
|
+
static {
|
|
853
|
+
__name(this, "SettingsModule");
|
|
854
|
+
}
|
|
855
|
+
static forRoot(options) {
|
|
856
|
+
const providers = [
|
|
857
|
+
{
|
|
858
|
+
provide: SETTINGS_SOURCE_TOKEN,
|
|
859
|
+
useValue: options.source
|
|
860
|
+
},
|
|
861
|
+
// Bound to null rather than left unbound: the controller and the
|
|
862
|
+
// recorder then read one shape, and `@Optional()` is not asked to
|
|
863
|
+
// paper over a provider that is sometimes there.
|
|
864
|
+
options.port ? asProvider(APPLIED_SETTINGS_PORT_TOKEN, options.port) : {
|
|
865
|
+
provide: APPLIED_SETTINGS_PORT_TOKEN,
|
|
866
|
+
useValue: null
|
|
867
|
+
},
|
|
868
|
+
options.email ? asProvider(EMAIL_PORT_TOKEN, options.email) : {
|
|
869
|
+
provide: EMAIL_PORT_TOKEN,
|
|
870
|
+
useValue: null
|
|
871
|
+
},
|
|
872
|
+
SettingsChangeNotifier,
|
|
873
|
+
AppliedSettingsRecorder,
|
|
874
|
+
// The acknowledgement is an operator's action and is audited like
|
|
875
|
+
// one. Best-effort: the logger writes nothing where the admin core
|
|
876
|
+
// is not in scope, and never fails the request.
|
|
877
|
+
WebAuditLogger
|
|
878
|
+
];
|
|
879
|
+
return {
|
|
880
|
+
module: _SettingsModule,
|
|
881
|
+
imports: options.imports ?? [],
|
|
882
|
+
controllers: options.includeController === false ? [] : [
|
|
883
|
+
buildSettingsController(options.controller.guards)
|
|
884
|
+
],
|
|
885
|
+
providers,
|
|
886
|
+
exports: [
|
|
887
|
+
AppliedSettingsRecorder,
|
|
888
|
+
SettingsChangeNotifier,
|
|
889
|
+
WebAuditLogger,
|
|
890
|
+
APPLIED_SETTINGS_PORT_TOKEN,
|
|
891
|
+
EMAIL_PORT_TOKEN,
|
|
892
|
+
SETTINGS_SOURCE_TOKEN
|
|
893
|
+
]
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
};
|
|
897
|
+
SettingsModule = _ts_decorate7([
|
|
898
|
+
Module2({})
|
|
899
|
+
], SettingsModule);
|
|
900
|
+
|
|
901
|
+
// src/platform/enforcement-chain.check.ts
|
|
902
|
+
import { Inject as Inject5, Injectable as Injectable4, Optional as Optional4 } from "@nestjs/common";
|
|
903
|
+
import { DiscoveryService, MetadataScanner } from "@nestjs/core";
|
|
904
|
+
function _ts_decorate8(decorators, target, key, desc) {
|
|
905
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
906
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
907
|
+
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;
|
|
908
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
909
|
+
}
|
|
910
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
911
|
+
function _ts_metadata6(k, v) {
|
|
912
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
913
|
+
}
|
|
914
|
+
__name(_ts_metadata6, "_ts_metadata");
|
|
915
|
+
function _ts_param6(paramIndex, decorator) {
|
|
916
|
+
return function(target, key) {
|
|
917
|
+
decorator(target, key, paramIndex);
|
|
918
|
+
};
|
|
919
|
+
}
|
|
920
|
+
__name(_ts_param6, "_ts_param");
|
|
375
921
|
var GUARDS_METADATA = "__guards__";
|
|
376
922
|
var METHOD_METADATA = "method";
|
|
377
923
|
var ENFORCEMENT_CHAIN_STATE_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/EnforcementChainState");
|
|
@@ -460,14 +1006,14 @@ Two things this cannot see, and the way out of each: a guard of your own that wr
|
|
|
460
1006
|
return annotated;
|
|
461
1007
|
}
|
|
462
1008
|
};
|
|
463
|
-
EnforcementChainCheck =
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
1009
|
+
EnforcementChainCheck = _ts_decorate8([
|
|
1010
|
+
Injectable4(),
|
|
1011
|
+
_ts_param6(0, Inject5(DiscoveryService)),
|
|
1012
|
+
_ts_param6(1, Inject5(MetadataScanner)),
|
|
1013
|
+
_ts_param6(2, Optional4()),
|
|
1014
|
+
_ts_param6(2, Inject5(ENFORCEMENT_CHAIN_STATE_TOKEN)),
|
|
1015
|
+
_ts_metadata6("design:type", Function),
|
|
1016
|
+
_ts_metadata6("design:paramtypes", [
|
|
471
1017
|
typeof DiscoveryService === "undefined" ? Object : DiscoveryService,
|
|
472
1018
|
typeof MetadataScanner === "undefined" ? Object : MetadataScanner,
|
|
473
1019
|
typeof EnforcementChainState === "undefined" ? Object : EnforcementChainState
|
|
@@ -504,8 +1050,367 @@ function readGuards(target) {
|
|
|
504
1050
|
}
|
|
505
1051
|
__name(readGuards, "readGuards");
|
|
506
1052
|
|
|
1053
|
+
// src/platform/issuer-identity.check.ts
|
|
1054
|
+
import { Inject as Inject6, Injectable as Injectable5, Logger as Logger3, Optional as Optional5 } from "@nestjs/common";
|
|
1055
|
+
import { classifyIssuerChange, recordedIssuerIdentity } from "@saasicat/core";
|
|
1056
|
+
function _ts_decorate9(decorators, target, key, desc) {
|
|
1057
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1058
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1059
|
+
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;
|
|
1060
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1061
|
+
}
|
|
1062
|
+
__name(_ts_decorate9, "_ts_decorate");
|
|
1063
|
+
function _ts_metadata7(k, v) {
|
|
1064
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1065
|
+
}
|
|
1066
|
+
__name(_ts_metadata7, "_ts_metadata");
|
|
1067
|
+
function _ts_param7(paramIndex, decorator) {
|
|
1068
|
+
return function(target, key) {
|
|
1069
|
+
decorator(target, key, paramIndex);
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
__name(_ts_param7, "_ts_param");
|
|
1073
|
+
var CONTRACTS_NAMED = 5;
|
|
1074
|
+
var NOT_RECORDED = "this installation records no applied settings";
|
|
1075
|
+
var ISSUER_BLOCK_KEYS = [
|
|
1076
|
+
"legalName",
|
|
1077
|
+
"addressLine1",
|
|
1078
|
+
"addressLine2",
|
|
1079
|
+
"postalCode",
|
|
1080
|
+
"city",
|
|
1081
|
+
"country",
|
|
1082
|
+
"vatId",
|
|
1083
|
+
"taxNumber"
|
|
1084
|
+
];
|
|
1085
|
+
var IssuerIdentityInspector = class _IssuerIdentityInspector {
|
|
1086
|
+
static {
|
|
1087
|
+
__name(this, "IssuerIdentityInspector");
|
|
1088
|
+
}
|
|
1089
|
+
fileSettings;
|
|
1090
|
+
source;
|
|
1091
|
+
settings;
|
|
1092
|
+
contracts;
|
|
1093
|
+
rlsBypass;
|
|
1094
|
+
logger = new Logger3(_IssuerIdentityInspector.name);
|
|
1095
|
+
/** This start's answer, settled the first time anything asks for it. */
|
|
1096
|
+
verdict = null;
|
|
1097
|
+
constructor(fileSettings, source, settings = null, contracts = null, rlsBypass = null) {
|
|
1098
|
+
this.fileSettings = fileSettings;
|
|
1099
|
+
this.source = source;
|
|
1100
|
+
this.settings = settings;
|
|
1101
|
+
this.contracts = contracts;
|
|
1102
|
+
this.rlsBypass = rlsBypass;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* What the issuer in the file is, against the identity the record holds.
|
|
1106
|
+
*
|
|
1107
|
+
* Answered once per process, and the cached answer is the point rather than
|
|
1108
|
+
* the saved query: `AppliedSettingsRecorder` replaces the record moments
|
|
1109
|
+
* later, in a bootstrap hook, so a second comparison would find the file
|
|
1110
|
+
* agreeing with what this very start wrote. A reader asking afterwards —
|
|
1111
|
+
* `<app> doctor`, a health endpoint — would then be told that nothing had
|
|
1112
|
+
* moved, on a start that had just carried a correction through. What this
|
|
1113
|
+
* start found is a fact about this start, so it is settled when the start
|
|
1114
|
+
* asks and does not change afterwards.
|
|
1115
|
+
*/
|
|
1116
|
+
inspect() {
|
|
1117
|
+
return this.verdict ??= this.compare();
|
|
1118
|
+
}
|
|
1119
|
+
/**
|
|
1120
|
+
* How many contracts are running, for a report that says what changing the
|
|
1121
|
+
* identity would cost. A query of its own rather than a field on the
|
|
1122
|
+
* verdict: a start needs this only to name the contracts in a refusal, and
|
|
1123
|
+
* no boot should pay for a number nobody reads. `null` where they are not
|
|
1124
|
+
* known.
|
|
1125
|
+
*/
|
|
1126
|
+
async runningContractCount() {
|
|
1127
|
+
const running = await this.readRunning(0);
|
|
1128
|
+
return running.known ? running.contracts.total : null;
|
|
1129
|
+
}
|
|
1130
|
+
async compare() {
|
|
1131
|
+
if (!this.settings) return {
|
|
1132
|
+
kind: "not-compared",
|
|
1133
|
+
why: NOT_RECORDED
|
|
1134
|
+
};
|
|
1135
|
+
const recorded = await this.readRecorded();
|
|
1136
|
+
if (!recorded.read) return {
|
|
1137
|
+
kind: "not-compared",
|
|
1138
|
+
why: recorded.why
|
|
1139
|
+
};
|
|
1140
|
+
const change = classifyIssuerChange(recordedIssuerIdentity(recorded.settings), this.fileSettings.issuer);
|
|
1141
|
+
if (change.kind !== "undeclared") return {
|
|
1142
|
+
kind: "settled",
|
|
1143
|
+
change
|
|
1144
|
+
};
|
|
1145
|
+
const running = await this.readRunning();
|
|
1146
|
+
return {
|
|
1147
|
+
kind: "refused",
|
|
1148
|
+
change,
|
|
1149
|
+
running: running.known ? running.contracts : null,
|
|
1150
|
+
refusal: refusalFor(change, running, this.source, recorded.settings?.issuer)
|
|
1151
|
+
};
|
|
1152
|
+
}
|
|
1153
|
+
/**
|
|
1154
|
+
* The settings of the last start, or what stopped this from reading them.
|
|
1155
|
+
*
|
|
1156
|
+
* A read that fails while the file names an issuer takes the boot down: a
|
|
1157
|
+
* guard that cannot see is not a guard that saw nothing, and an installation
|
|
1158
|
+
* that puts a legal entity on every contract it concludes should not conclude
|
|
1159
|
+
* one while nothing can tell whether that entity is still the recorded one.
|
|
1160
|
+
*
|
|
1161
|
+
* Where the file names no issuer, the same failure is only a warning. There
|
|
1162
|
+
* is then no entity to conclude a contract on behalf of, and the promise the
|
|
1163
|
+
* record makes — an installation whose record cannot be kept still runs the
|
|
1164
|
+
* right configuration — is worth more than a boot refused over a comparison
|
|
1165
|
+
* with nothing on one side. What that leaves uncaught is narrow and worth
|
|
1166
|
+
* naming: an operator who removes the issuer block while contracts run, in
|
|
1167
|
+
* the same start where the record cannot be read.
|
|
1168
|
+
*/
|
|
1169
|
+
async readRecorded() {
|
|
1170
|
+
try {
|
|
1171
|
+
const record = await this.settings.readApplied();
|
|
1172
|
+
return {
|
|
1173
|
+
read: true,
|
|
1174
|
+
settings: record?.settings ?? null
|
|
1175
|
+
};
|
|
1176
|
+
} catch (error) {
|
|
1177
|
+
const why = `the applied settings could not be read: ${messageOf(error)}`;
|
|
1178
|
+
if (this.fileSettings.issuer) {
|
|
1179
|
+
throw new Error(`The issuer named in ${this.source} could not be compared with the one this installation recorded, because ${why}`, {
|
|
1180
|
+
cause: error
|
|
1181
|
+
});
|
|
1182
|
+
}
|
|
1183
|
+
return {
|
|
1184
|
+
read: false,
|
|
1185
|
+
why
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* The contracts a refusal is about. Best effort on purpose: the refusal is
|
|
1191
|
+
* already decided, and losing it because the list behind it could not be
|
|
1192
|
+
* read would replace a message an operator can act on with one they cannot.
|
|
1193
|
+
* Where they are not known, the message says which of the two it is —
|
|
1194
|
+
* "nothing here writes contracts" and "the query failed" send an operator
|
|
1195
|
+
* looking in different places.
|
|
1196
|
+
*/
|
|
1197
|
+
async readRunning(named = CONTRACTS_NAMED) {
|
|
1198
|
+
if (!this.contracts) {
|
|
1199
|
+
return {
|
|
1200
|
+
known: false,
|
|
1201
|
+
why: "This installation writes no contracts."
|
|
1202
|
+
};
|
|
1203
|
+
}
|
|
1204
|
+
try {
|
|
1205
|
+
const listed = await this.withBypass(() => this.contracts.listRunningIssuers(named));
|
|
1206
|
+
return {
|
|
1207
|
+
known: true,
|
|
1208
|
+
contracts: listed
|
|
1209
|
+
};
|
|
1210
|
+
} catch (error) {
|
|
1211
|
+
const why = `The contracts still running could not be read: ${messageOf(error)}`;
|
|
1212
|
+
this.logger.warn(why);
|
|
1213
|
+
return {
|
|
1214
|
+
known: false,
|
|
1215
|
+
why
|
|
1216
|
+
};
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
/**
|
|
1220
|
+
* Inside the bypass frame where one is bound, and plainly where none is.
|
|
1221
|
+
*
|
|
1222
|
+
* An installation without row-level security binds a port that only calls
|
|
1223
|
+
* through; one that has it cannot be read platform-wide without this.
|
|
1224
|
+
* `@Optional()` rather than required because a hand-wired application may
|
|
1225
|
+
* have no `AdminModule` in scope, and a boot check is not the place to
|
|
1226
|
+
* refuse to construct over a message.
|
|
1227
|
+
*/
|
|
1228
|
+
withBypass(read) {
|
|
1229
|
+
return this.rlsBypass ? this.rlsBypass.runWithBypass(read) : read();
|
|
1230
|
+
}
|
|
1231
|
+
/**
|
|
1232
|
+
* The line a start that continues is worth, and nothing where it is worth
|
|
1233
|
+
* none. Public because the hook beside this class calls it; not an offer.
|
|
1234
|
+
*
|
|
1235
|
+
* @internal
|
|
1236
|
+
*/
|
|
1237
|
+
report(verdict) {
|
|
1238
|
+
if (verdict.kind === "not-compared") {
|
|
1239
|
+
this.logger.warn(`The issuer in the configuration is compared with nothing \u2014 ${verdict.why} \u2014 so a start that names another legal entity than the last one is not refused.`);
|
|
1240
|
+
return;
|
|
1241
|
+
}
|
|
1242
|
+
const change = verdict.change;
|
|
1243
|
+
if (change.kind === "first-naming") {
|
|
1244
|
+
this.logger.log(`The issuer of every contract concluded from now on is '${change.identity.legalName}'.`);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1247
|
+
if (change.kind === "corrected") {
|
|
1248
|
+
this.logger.log(`The issuer's ${listFields(change.moved)} changed as a declared correction of the same legal entity: ${change.reason}. Contracts already concluded keep the copy they name; what is issued from now on carries the corrected identity.`);
|
|
1249
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
};
|
|
1252
|
+
IssuerIdentityInspector = _ts_decorate9([
|
|
1253
|
+
Injectable5(),
|
|
1254
|
+
_ts_param7(0, Inject6(PLAN_CATALOG_SETTINGS_TOKEN)),
|
|
1255
|
+
_ts_param7(1, Inject6(SETTINGS_SOURCE_TOKEN)),
|
|
1256
|
+
_ts_param7(2, Optional5()),
|
|
1257
|
+
_ts_param7(2, Inject6(APPLIED_SETTINGS_PORT_TOKEN)),
|
|
1258
|
+
_ts_param7(3, Optional5()),
|
|
1259
|
+
_ts_param7(3, Inject6(SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN)),
|
|
1260
|
+
_ts_param7(4, Optional5()),
|
|
1261
|
+
_ts_param7(4, Inject6(RLS_BYPASS_PORT_TOKEN)),
|
|
1262
|
+
_ts_metadata7("design:type", Function),
|
|
1263
|
+
_ts_metadata7("design:paramtypes", [
|
|
1264
|
+
typeof PlanCatalogSettings === "undefined" ? Object : PlanCatalogSettings,
|
|
1265
|
+
String,
|
|
1266
|
+
Object,
|
|
1267
|
+
Object,
|
|
1268
|
+
Object
|
|
1269
|
+
])
|
|
1270
|
+
], IssuerIdentityInspector);
|
|
1271
|
+
var IssuerIdentityCheck = class {
|
|
1272
|
+
static {
|
|
1273
|
+
__name(this, "IssuerIdentityCheck");
|
|
1274
|
+
}
|
|
1275
|
+
inspector;
|
|
1276
|
+
constructor(inspector) {
|
|
1277
|
+
this.inspector = inspector;
|
|
1278
|
+
}
|
|
1279
|
+
async onModuleInit() {
|
|
1280
|
+
const verdict = await this.inspector.inspect();
|
|
1281
|
+
if (verdict.kind === "refused") throw new Error(verdict.refusal);
|
|
1282
|
+
this.inspector.report(verdict);
|
|
1283
|
+
}
|
|
1284
|
+
};
|
|
1285
|
+
IssuerIdentityCheck = _ts_decorate9([
|
|
1286
|
+
Injectable5(),
|
|
1287
|
+
_ts_metadata7("design:type", Function),
|
|
1288
|
+
_ts_metadata7("design:paramtypes", [
|
|
1289
|
+
typeof IssuerIdentityInspector === "undefined" ? Object : IssuerIdentityInspector
|
|
1290
|
+
])
|
|
1291
|
+
], IssuerIdentityCheck);
|
|
1292
|
+
function refusalFor(change, running, source, recordedIssuer) {
|
|
1293
|
+
return [
|
|
1294
|
+
`The issuer in ${source} is not the legal entity this installation recorded.`,
|
|
1295
|
+
` recorded: ${describe2(change.recorded)}`,
|
|
1296
|
+
` in the file: ${change.current ? describe2(change.current) : "no issuer is named at all"}`,
|
|
1297
|
+
faultSentence(change.fault),
|
|
1298
|
+
contractsSentence(running),
|
|
1299
|
+
...wayOut(change, source, recordedIssuer)
|
|
1300
|
+
].join("\n");
|
|
1301
|
+
}
|
|
1302
|
+
__name(refusalFor, "refusalFor");
|
|
1303
|
+
function wayOut(change, source, recordedIssuer) {
|
|
1304
|
+
if (change.fault.kind === "names-no-issuer") {
|
|
1305
|
+
return [
|
|
1306
|
+
`Write the issuer block again as this installation recorded it \u2014 replacing the one in ${source} where there is still one there, because a second \`issuer:\` key is a file YAML refuses to read. Where that entity has since been corrected, declare the correction beside the corrected values. A declaration on its own cannot help here: there is nothing in the file for it to be about.`,
|
|
1307
|
+
issuerBlockFor(recordedIssuer)
|
|
1308
|
+
];
|
|
1309
|
+
}
|
|
1310
|
+
return [
|
|
1311
|
+
"Moving a contract to another legal entity is a transfer, not an edit of a setting. Where this is the same entity under a new name, or with a tax identifier that was wrong or missing, declare it beside the values it replaces and start again:",
|
|
1312
|
+
declarationFor(change.recorded, change.moved)
|
|
1313
|
+
];
|
|
1314
|
+
}
|
|
1315
|
+
__name(wayOut, "wayOut");
|
|
1316
|
+
function issuerBlockFor(recordedIssuer) {
|
|
1317
|
+
const source = recordedIssuer && typeof recordedIssuer === "object" && !Array.isArray(recordedIssuer) ? recordedIssuer : {};
|
|
1318
|
+
const lines = ISSUER_BLOCK_KEYS.filter((key) => typeof source[key] === "string").map((key) => ` ${key}: ${JSON.stringify(source[key])}`);
|
|
1319
|
+
return [
|
|
1320
|
+
"issuer:",
|
|
1321
|
+
...lines
|
|
1322
|
+
].join("\n");
|
|
1323
|
+
}
|
|
1324
|
+
__name(issuerBlockFor, "issuerBlockFor");
|
|
1325
|
+
function faultSentence(fault) {
|
|
1326
|
+
switch (fault.kind) {
|
|
1327
|
+
case "absent":
|
|
1328
|
+
return "`issuer.correctionOf` declares nothing, so nothing says this is the same entity.";
|
|
1329
|
+
case "names-no-issuer":
|
|
1330
|
+
return "The file names no issuer for a declaration to be about, so there is no entity here for the recorded one to be the same as.";
|
|
1331
|
+
case "names-another-value":
|
|
1332
|
+
return `\`issuer.correctionOf.${fault.field}\` names ${quote(fault.declared)}, which is not what the record holds (${quote(fault.recorded)}). A declaration names the values it replaces, so one left over from a correction already applied does not cover this one.`;
|
|
1333
|
+
case "leaves-a-field-out":
|
|
1334
|
+
return `\`issuer.correctionOf\` says nothing about \`${fault.field}\`, which the file moves away from ${quote(fault.recorded)}. Every identity field that moves is named, or the declaration covers only half the change.`;
|
|
1335
|
+
}
|
|
1336
|
+
}
|
|
1337
|
+
__name(faultSentence, "faultSentence");
|
|
1338
|
+
function contractsSentence(running) {
|
|
1339
|
+
if (!running.known) return running.why;
|
|
1340
|
+
const { total, contracts } = running.contracts;
|
|
1341
|
+
if (total === 0) return "No contract is running.";
|
|
1342
|
+
const rest = total - contracts.length;
|
|
1343
|
+
return [
|
|
1344
|
+
`${total} contract(s) are still running:`,
|
|
1345
|
+
...contracts.map(describeContract),
|
|
1346
|
+
...rest > 0 ? [
|
|
1347
|
+
` \u2026 and ${rest} more.`
|
|
1348
|
+
] : []
|
|
1349
|
+
].join("\n");
|
|
1350
|
+
}
|
|
1351
|
+
__name(contractsSentence, "contractsSentence");
|
|
1352
|
+
function describeContract(contract) {
|
|
1353
|
+
const under = contract.issuerLegalName ? `concluded under ${quote(contract.issuerLegalName)}` : "names no issuer";
|
|
1354
|
+
const from2 = contract.effectiveFrom instanceof Date ? contract.effectiveFrom.toISOString().slice(0, 10) : "an unreadable date";
|
|
1355
|
+
return ` ${contract.id} (tenant ${contract.tenantId}, from ${from2}, ${under})`;
|
|
1356
|
+
}
|
|
1357
|
+
__name(describeContract, "describeContract");
|
|
1358
|
+
function declarationFor(recorded, moved) {
|
|
1359
|
+
const lines = moved.map((field) => ` ${field}: ${recorded[field] === null ? "null" : JSON.stringify(recorded[field])}`);
|
|
1360
|
+
return [
|
|
1361
|
+
"issuer:",
|
|
1362
|
+
" correctionOf:",
|
|
1363
|
+
...lines,
|
|
1364
|
+
" reason: <why the same entity now reads differently>"
|
|
1365
|
+
].join("\n");
|
|
1366
|
+
}
|
|
1367
|
+
__name(declarationFor, "declarationFor");
|
|
1368
|
+
function describe2(identity) {
|
|
1369
|
+
return `${quote(identity.legalName)} (VAT id ${quote(identity.vatId)}, tax number ${quote(identity.taxNumber)})`;
|
|
1370
|
+
}
|
|
1371
|
+
__name(describe2, "describe");
|
|
1372
|
+
function listFields(fields) {
|
|
1373
|
+
return fields.map((field) => `\`${field}\``).join(", ");
|
|
1374
|
+
}
|
|
1375
|
+
__name(listFields, "listFields");
|
|
1376
|
+
function quote(value) {
|
|
1377
|
+
return value === null ? "none" : `'${value}'`;
|
|
1378
|
+
}
|
|
1379
|
+
__name(quote, "quote");
|
|
1380
|
+
function messageOf(error) {
|
|
1381
|
+
return error instanceof Error ? error.message : String(error);
|
|
1382
|
+
}
|
|
1383
|
+
__name(messageOf, "messageOf");
|
|
1384
|
+
|
|
1385
|
+
// src/platform/module-options.ts
|
|
1386
|
+
var DB_CATALOG_MEMBERS = [
|
|
1387
|
+
"path",
|
|
1388
|
+
"env"
|
|
1389
|
+
];
|
|
1390
|
+
function dbCatalogNamesAPath(dbCatalog) {
|
|
1391
|
+
return typeof dbCatalog?.path === "string" && dbCatalog.path.trim() !== "";
|
|
1392
|
+
}
|
|
1393
|
+
__name(dbCatalogNamesAPath, "dbCatalogNamesAPath");
|
|
1394
|
+
function dbCatalogLeftovers(dbCatalog) {
|
|
1395
|
+
const taken = new Set(DB_CATALOG_MEMBERS);
|
|
1396
|
+
return Object.entries(dbCatalog).filter(([key, value]) => !taken.has(key) && value !== void 0).map(([key]) => key);
|
|
1397
|
+
}
|
|
1398
|
+
__name(dbCatalogLeftovers, "dbCatalogLeftovers");
|
|
1399
|
+
function dbCatalogNamesAFile(dbCatalog) {
|
|
1400
|
+
return dbCatalogNamesAPath(dbCatalog) && dbCatalogLeftovers(dbCatalog).length === 0;
|
|
1401
|
+
}
|
|
1402
|
+
__name(dbCatalogNamesAFile, "dbCatalogNamesAFile");
|
|
1403
|
+
|
|
507
1404
|
// src/platform/validation/rules.ts
|
|
508
1405
|
import { assertPersistenceCapabilities } from "@saasicat/core";
|
|
1406
|
+
|
|
1407
|
+
// src/platform/compose/bundle-repository-source.ts
|
|
1408
|
+
function resolveBundleRepository(persistence) {
|
|
1409
|
+
return persistence?.catalog?.bundleRepository ?? persistence?.entitlement?.bundleRepository;
|
|
1410
|
+
}
|
|
1411
|
+
__name(resolveBundleRepository, "resolveBundleRepository");
|
|
1412
|
+
|
|
1413
|
+
// src/platform/validation/rules.ts
|
|
509
1414
|
var DOCS_BASE = "https://github.com/uelker70/saasicat/blob/main/docs/reference/options.md";
|
|
510
1415
|
var headingSlug = /* @__PURE__ */ __name((text) => text.trim().toLowerCase().replace(/[^a-z0-9 -]/g, "").replace(/ /g, "-"), "headingSlug");
|
|
511
1416
|
var docsUrlFor = /* @__PURE__ */ __name((id) => `${DOCS_BASE}#${headingSlug(id)}`, "docsUrlFor");
|
|
@@ -521,6 +1426,7 @@ function absent(required) {
|
|
|
521
1426
|
}
|
|
522
1427
|
__name(absent, "absent");
|
|
523
1428
|
var list = /* @__PURE__ */ __name((names) => names.join(", "), "list");
|
|
1429
|
+
var catalogOf = /* @__PURE__ */ __name((c) => c.catalog ?? c.options.planCatalog, "catalogOf");
|
|
524
1430
|
var RULE_SPECS = [
|
|
525
1431
|
{
|
|
526
1432
|
id: "core.adapters-bound",
|
|
@@ -532,7 +1438,42 @@ var RULE_SPECS = [
|
|
|
532
1438
|
id: "catalog.identity-or-sink",
|
|
533
1439
|
when: /* @__PURE__ */ __name((c) => !c.options.planCatalog, "when"),
|
|
534
1440
|
assert: /* @__PURE__ */ __name((c) => Boolean(c.adapters.planCatalogReadSink && c.options.dbCatalog), "assert"),
|
|
535
|
-
message: "no plan catalogue is reachable. Either set `planCatalog` (the quickstart YAML path) or, for DB hydration, BOTH a `planCatalogReadSink` (via `adapters` or `persistence`) AND `dbCatalog` ({
|
|
1441
|
+
message: "no plan catalogue is reachable. Either set `planCatalog` (the quickstart YAML path) or, for DB hydration, BOTH a `planCatalogReadSink` (via `adapters` or `persistence`) AND `dbCatalog` ({ path: 'config/saas.yaml' }, the file the settings are read from). Without the file the app boots with a silently empty catalogue and no settings."
|
|
1442
|
+
},
|
|
1443
|
+
{
|
|
1444
|
+
id: "catalog.db-catalog-names-the-file",
|
|
1445
|
+
when: /* @__PURE__ */ __name((c) => c.options.dbCatalog !== void 0, "when"),
|
|
1446
|
+
assert: /* @__PURE__ */ __name((c) => dbCatalogNamesAFile(c.options.dbCatalog), "assert"),
|
|
1447
|
+
message: /* @__PURE__ */ __name((c) => {
|
|
1448
|
+
const given = c.options.dbCatalog;
|
|
1449
|
+
const leftovers = dbCatalogNamesAPath(given) ? dbCatalogLeftovers(given) : [];
|
|
1450
|
+
if (leftovers.length > 0) {
|
|
1451
|
+
return `\`dbCatalog\` names the file and nothing else, and this one still carries ${list(leftovers)}. Delete that here; the platform reads the settings from the file it names, and a value left beside the path would be ignored while looking like the one that runs. See docs/guides/upgrade-to-1.0.md.`;
|
|
1452
|
+
}
|
|
1453
|
+
return "`dbCatalog` names the file: `dbCatalog: { path: 'config/saas.yaml' }`. It used to take `app`, `currency`, `vatRate` and `tenantBilling` as values, and that was a second place a setting could live. Delete the values here; the platform reads them from the file it names \u2014 the same one they were forwarded from. See docs/guides/upgrade-to-1.0.md.";
|
|
1454
|
+
}, "message")
|
|
1455
|
+
},
|
|
1456
|
+
{
|
|
1457
|
+
id: "catalog.db-catalog-file-loads",
|
|
1458
|
+
// Only where a file was named at all. On the quickstart path the
|
|
1459
|
+
// consumer calls the loader themselves, and their own stack says where.
|
|
1460
|
+
when: /* @__PURE__ */ __name((c) => c.options.dbCatalog !== void 0, "when"),
|
|
1461
|
+
assert: /* @__PURE__ */ __name((c) => c.catalogFailure === void 0, "assert"),
|
|
1462
|
+
message: /* @__PURE__ */ __name((c) => {
|
|
1463
|
+
const given = c.options.dbCatalog;
|
|
1464
|
+
const named = dbCatalogNamesAPath(given) ? given.path : "config/saas.yaml";
|
|
1465
|
+
const why = c.catalogFailure ? `: ${c.catalogFailure.message}` : "";
|
|
1466
|
+
return `\`dbCatalog\` names '${named}' and the file did not load${why}. A relative path is resolved against the directory the process was started in, not against the file the option is written in. See docs/guides/wire-the-backend.md.`;
|
|
1467
|
+
}, "message")
|
|
1468
|
+
},
|
|
1469
|
+
{
|
|
1470
|
+
id: "catalog.app-is-named",
|
|
1471
|
+
// Only where a catalogue is reachable at all — otherwise
|
|
1472
|
+
// `catalog.identity-or-sink` is the finding, and reporting both would
|
|
1473
|
+
// name the same omission twice.
|
|
1474
|
+
when: /* @__PURE__ */ __name((c) => catalogOf(c) !== void 0, "when"),
|
|
1475
|
+
assert: /* @__PURE__ */ __name((c) => Boolean(catalogOf(c)?.app?.name?.trim()), "assert"),
|
|
1476
|
+
message: "the catalogue names no application. `app.name` is the one place an installation names itself \u2014 it is the manifest display name, the login-page brand and the discovery snapshot key \u2014 so an absent one is not a default to fill in: the app would boot identified by an empty string. Set `app: { name: \u2026 }` in config/saas.yaml."
|
|
536
1477
|
},
|
|
537
1478
|
{
|
|
538
1479
|
id: "catalog.requires-persistence",
|
|
@@ -576,6 +1517,24 @@ var RULE_SPECS = [
|
|
|
576
1517
|
assert: /* @__PURE__ */ __name((c) => Boolean(optionsOf(c.options.subscriptionContract)?.subscriptionContractRepository ?? bundle(c)?.entitlement?.subscriptionContractRepository), "assert"),
|
|
577
1518
|
message: "subscriptionContract is enabled, but no repository is available. Set `subscriptionContract.subscriptionContractRepository` or use a compatible persistence bundle."
|
|
578
1519
|
},
|
|
1520
|
+
{
|
|
1521
|
+
id: "subscription-contract.requires-subscriber-repository",
|
|
1522
|
+
when: /* @__PURE__ */ __name((c) => Boolean(c.options.subscriptionContract), "when"),
|
|
1523
|
+
assert: /* @__PURE__ */ __name((c) => Boolean(optionsOf(c.options.subscriptionContract)?.subscriberRepository ?? bundle(c)?.entitlement?.subscriberRepository), "assert"),
|
|
1524
|
+
message: "subscriptionContract is enabled, but no subscriber repository is available: every contract names the subscriber it is concluded with. Set `subscriptionContract.subscriberRepository` or use a persistence bundle providing `persistence.entitlement.subscriberRepository`."
|
|
1525
|
+
},
|
|
1526
|
+
{
|
|
1527
|
+
id: "payments.requires-persistence",
|
|
1528
|
+
when: /* @__PURE__ */ __name((c) => Boolean(c.options.payments), "when"),
|
|
1529
|
+
assert: /* @__PURE__ */ __name((c) => absent(paymentStores(c)).length === 0, "assert"),
|
|
1530
|
+
message: /* @__PURE__ */ __name((c) => `payments is enabled, but these adapters are missing: ${list(absent(paymentStores(c)))}. A gateway event is claimed and its effect written on one transaction, so all of them come from one persistence bundle.`, "message")
|
|
1531
|
+
},
|
|
1532
|
+
{
|
|
1533
|
+
id: "payments.requires-accounts-in-the-file",
|
|
1534
|
+
when: /* @__PURE__ */ __name((c) => Boolean(c.options.payments) && catalogOf(c) !== void 0, "when"),
|
|
1535
|
+
assert: /* @__PURE__ */ __name((c) => Boolean(catalogOf(c)?.payments), "assert"),
|
|
1536
|
+
message: "payments is enabled, and config/saas.yaml has no `payments` block naming the gateway accounts the bound gateways belong to."
|
|
1537
|
+
},
|
|
579
1538
|
{
|
|
580
1539
|
id: "promo-codes.public-preview-requires-first-time-check",
|
|
581
1540
|
when: /* @__PURE__ */ __name((c) => {
|
|
@@ -650,9 +1609,15 @@ var tenantBillingPorts = /* @__PURE__ */ __name((c) => {
|
|
|
650
1609
|
subscriptionWritePort: config?.subscriptionWritePort
|
|
651
1610
|
};
|
|
652
1611
|
}, "tenantBillingPorts");
|
|
1612
|
+
var paymentStores = /* @__PURE__ */ __name((c) => ({
|
|
1613
|
+
paymentEventLog: bundle(c)?.payments?.paymentEventLog,
|
|
1614
|
+
subscriberPaymentMethodRepository: bundle(c)?.payments?.subscriberPaymentMethodRepository,
|
|
1615
|
+
subscriberRepository: bundle(c)?.entitlement?.subscriberRepository,
|
|
1616
|
+
transactionRunner: c.adapters.transactionRunner
|
|
1617
|
+
}), "paymentStores");
|
|
653
1618
|
var bundleRepositories = /* @__PURE__ */ __name((c) => ({
|
|
654
1619
|
subscriptionBundleRepository: bundle(c)?.entitlement?.subscriptionBundleRepository,
|
|
655
|
-
bundleRepository: bundle(c)
|
|
1620
|
+
bundleRepository: resolveBundleRepository(bundle(c))
|
|
656
1621
|
}), "bundleRepositories");
|
|
657
1622
|
var entitlementAdapters = /* @__PURE__ */ __name((c) => ({
|
|
658
1623
|
subscriptionRepository: c.adapters.subscriptionRepository,
|
|
@@ -727,6 +1692,13 @@ var QuotaProvidersUsageSnapshot = class {
|
|
|
727
1692
|
};
|
|
728
1693
|
|
|
729
1694
|
// src/platform/compose/context.ts
|
|
1695
|
+
function operatorGuards(options) {
|
|
1696
|
+
return [
|
|
1697
|
+
...options.controller.guards,
|
|
1698
|
+
SuperAdminGuard
|
|
1699
|
+
];
|
|
1700
|
+
}
|
|
1701
|
+
__name(operatorGuards, "operatorGuards");
|
|
730
1702
|
function optionsOf2(slot) {
|
|
731
1703
|
return typeof slot === "object" ? slot : {};
|
|
732
1704
|
}
|
|
@@ -739,10 +1711,7 @@ function composeAdminResources({ options, persistence }) {
|
|
|
739
1711
|
return [
|
|
740
1712
|
AdminResourcesModule.forRoot({
|
|
741
1713
|
resources: config.resources ?? persistence?.adminResources?.resources,
|
|
742
|
-
guards: config.guards ??
|
|
743
|
-
...options.controller.guards,
|
|
744
|
-
SuperAdminGuard
|
|
745
|
-
],
|
|
1714
|
+
guards: config.guards ?? operatorGuards(options),
|
|
746
1715
|
imports: config.imports ?? options.imports
|
|
747
1716
|
})
|
|
748
1717
|
];
|
|
@@ -750,7 +1719,7 @@ function composeAdminResources({ options, persistence }) {
|
|
|
750
1719
|
__name(composeAdminResources, "composeAdminResources");
|
|
751
1720
|
|
|
752
1721
|
// src/platform/compose/catalog.ts
|
|
753
|
-
function composeCatalog({ options, persistence
|
|
1722
|
+
function composeCatalog({ options, persistence }) {
|
|
754
1723
|
const config = options.catalog;
|
|
755
1724
|
if (!config) return [];
|
|
756
1725
|
const catalog = persistence?.catalog;
|
|
@@ -763,7 +1732,7 @@ function composeCatalog({ options, persistence, appInfo }) {
|
|
|
763
1732
|
promotionRepository: catalog.promotionRepository,
|
|
764
1733
|
marketingSettingsRepository: catalog.marketingSettingsRepository,
|
|
765
1734
|
controller: config.adminControllers === false ? void 0 : {
|
|
766
|
-
guards: options
|
|
1735
|
+
guards: operatorGuards(options)
|
|
767
1736
|
},
|
|
768
1737
|
imports: config.imports ?? options.imports,
|
|
769
1738
|
extraProviders: config.extraProviders,
|
|
@@ -771,13 +1740,18 @@ function composeCatalog({ options, persistence, appInfo }) {
|
|
|
771
1740
|
autoSyncDiscoveryAtBoot: config.autoSyncDiscoveryAtBoot,
|
|
772
1741
|
marketedOnlyFeatures: config.marketedOnlyFeatures,
|
|
773
1742
|
featureUiRegistry: config.featureUiRegistry,
|
|
774
|
-
|
|
1743
|
+
// What the public catalogue says about payment methods comes from
|
|
1744
|
+
// the same option that wires the gateways, so the two cannot
|
|
1745
|
+
// disagree: no `payments`, nothing takes new payment methods.
|
|
1746
|
+
publicMarketingCatalog: config.publicMarketingCatalog && {
|
|
1747
|
+
...config.publicMarketingCatalog,
|
|
1748
|
+
newPaymentMethodsFrom: config.publicMarketingCatalog.newPaymentMethodsFrom ?? (options.payments ? PaymentGatewayRegistry : null)
|
|
1749
|
+
}
|
|
775
1750
|
})
|
|
776
1751
|
];
|
|
777
1752
|
if (config.publicCatalog !== false) {
|
|
778
1753
|
modules.push(PublicCatalogModule.forRoot({
|
|
779
1754
|
featureUiRegistry: config.featureUiRegistry,
|
|
780
|
-
projectKey: appInfo.key,
|
|
781
1755
|
bundleRepository: catalog.bundleRepository,
|
|
782
1756
|
marketingRepository: catalog.marketingProjectionRepository,
|
|
783
1757
|
catalogEntryRepository: catalog.catalogEntryRepository,
|
|
@@ -826,25 +1800,34 @@ function composeAdminStats({ options, persistence }) {
|
|
|
826
1800
|
return [
|
|
827
1801
|
AdminStatsModule.forRoot({
|
|
828
1802
|
...config,
|
|
829
|
-
guards: config.guards ??
|
|
830
|
-
...options.controller.guards,
|
|
831
|
-
SuperAdminGuard
|
|
832
|
-
],
|
|
1803
|
+
guards: config.guards ?? operatorGuards(options),
|
|
833
1804
|
auditStatsPort: config.auditStatsPort ?? persistence?.core.auditStats,
|
|
834
1805
|
imports: config.imports ?? options.imports
|
|
835
1806
|
})
|
|
836
1807
|
];
|
|
837
1808
|
}
|
|
838
1809
|
__name(composeAdminStats, "composeAdminStats");
|
|
839
|
-
function composeCheckoutOffer({ options, persistence }) {
|
|
1810
|
+
function composeCheckoutOffer({ options, adapters, persistence }) {
|
|
840
1811
|
const config = options.checkoutOffer;
|
|
841
1812
|
if (!config) return [];
|
|
1813
|
+
const contractRepository = config.conclusion?.subscriptionContractRepository ?? persistence?.entitlement?.subscriptionContractRepository;
|
|
1814
|
+
const subscriberRepository = config.conclusion?.subscriberRepository ?? persistence?.entitlement?.subscriberRepository;
|
|
1815
|
+
const transactionRunner = config.conclusion?.transactionRunner ?? adapters.transactionRunner;
|
|
842
1816
|
return [
|
|
843
1817
|
CheckoutOfferModule.forRoot({
|
|
844
1818
|
...config,
|
|
845
|
-
bundleRepository: config.bundleRepository ?? persistence
|
|
1819
|
+
bundleRepository: config.bundleRepository ?? resolveBundleRepository(persistence),
|
|
846
1820
|
planRepository: config.planRepository ?? persistence?.catalog?.planRepository,
|
|
1821
|
+
promotionRepository: config.promotionRepository ?? persistence?.catalog?.promotionRepository,
|
|
847
1822
|
catalogEntryRepository: config.catalogEntryRepository ?? persistence?.catalog?.catalogEntryRepository,
|
|
1823
|
+
// Named by hand, it is passed on even when part of it resolves to
|
|
1824
|
+
// nothing, so the module refuses to start the same way it does when
|
|
1825
|
+
// wired directly; derived from the bundle, it is wired when all exist.
|
|
1826
|
+
conclusion: config.conclusion || contractRepository && subscriberRepository && transactionRunner ? {
|
|
1827
|
+
subscriptionContractRepository: contractRepository,
|
|
1828
|
+
subscriberRepository,
|
|
1829
|
+
transactionRunner
|
|
1830
|
+
} : void 0,
|
|
848
1831
|
imports: config.imports ?? options.imports
|
|
849
1832
|
})
|
|
850
1833
|
];
|
|
@@ -857,12 +1840,39 @@ function composeSubscriptionContract({ options, persistence }) {
|
|
|
857
1840
|
SubscriptionContractModule.forRoot({
|
|
858
1841
|
...config,
|
|
859
1842
|
subscriptionContractRepository: config.subscriptionContractRepository ?? persistence?.entitlement?.subscriptionContractRepository,
|
|
1843
|
+
subscriberRepository: config.subscriberRepository ?? persistence?.entitlement?.subscriberRepository,
|
|
860
1844
|
imports: config.imports ?? options.imports
|
|
861
1845
|
})
|
|
862
1846
|
];
|
|
863
1847
|
}
|
|
864
1848
|
__name(composeSubscriptionContract, "composeSubscriptionContract");
|
|
865
1849
|
|
|
1850
|
+
// src/platform/compose/payments.ts
|
|
1851
|
+
function composePayments(ctx) {
|
|
1852
|
+
const config = ctx.options.payments;
|
|
1853
|
+
if (!config) return [];
|
|
1854
|
+
const tenantBilling = optionsOf2(ctx.options.tenantBilling);
|
|
1855
|
+
const authGuards = ctx.shared.authGuards;
|
|
1856
|
+
return [
|
|
1857
|
+
PaymentsModule.forRoot({
|
|
1858
|
+
gateways: config.gateways,
|
|
1859
|
+
paymentEventLog: ctx.persistence?.payments?.paymentEventLog,
|
|
1860
|
+
subscriberPaymentMethodRepository: ctx.persistence?.payments?.subscriberPaymentMethodRepository,
|
|
1861
|
+
subscriberRepository: ctx.persistence?.entitlement?.subscriberRepository,
|
|
1862
|
+
transactionRunner: ctx.adapters.transactionRunner,
|
|
1863
|
+
tenantRoutes: authGuards ? {
|
|
1864
|
+
authGuards,
|
|
1865
|
+
billingPermissionGuards: config.billingPermissionGuards,
|
|
1866
|
+
tenantIdResolver: tenantBilling.tenantIdResolver,
|
|
1867
|
+
userEmailResolver: tenantBilling.userEmailResolver
|
|
1868
|
+
} : void 0,
|
|
1869
|
+
imports: config.imports ?? ctx.options.imports,
|
|
1870
|
+
extraProviders: config.extraProviders
|
|
1871
|
+
})
|
|
1872
|
+
];
|
|
1873
|
+
}
|
|
1874
|
+
__name(composePayments, "composePayments");
|
|
1875
|
+
|
|
866
1876
|
// src/platform/compose/promo-codes.ts
|
|
867
1877
|
function composePromoCodes({ options, adapters, persistence }) {
|
|
868
1878
|
if (!options.promoCodes) return [];
|
|
@@ -877,18 +1887,19 @@ function composePromoCodes({ options, adapters, persistence }) {
|
|
|
877
1887
|
validationLogRepository: promo.validationLogRepository,
|
|
878
1888
|
subscriptionLookup: promo.subscriptionLookup,
|
|
879
1889
|
revenueAggregator: promo.revenueAggregator,
|
|
1890
|
+
holdRepository: promo.holdRepository,
|
|
880
1891
|
transactionRunner: adapters.transactionRunner,
|
|
881
1892
|
firstTimeCustomerCheck: firstTimeCustomerCheck ?? {
|
|
882
1893
|
hasExistingCustomerForEmail: /* @__PURE__ */ __name(async () => false, "hasExistingCustomerForEmail")
|
|
883
1894
|
},
|
|
884
1895
|
includePublicController: includePublicController ?? false,
|
|
885
1896
|
adminController: adminController === false ? false : {
|
|
886
|
-
guards: adminGuards ??
|
|
887
|
-
...options.controller.guards,
|
|
888
|
-
SuperAdminGuard
|
|
889
|
-
]
|
|
1897
|
+
guards: adminGuards ?? operatorGuards(options)
|
|
890
1898
|
},
|
|
891
|
-
imports: promoImports ?? options.imports
|
|
1899
|
+
imports: promoImports ?? options.imports,
|
|
1900
|
+
// A checkout offer prices a promo code through `PromoCodesService`,
|
|
1901
|
+
// from a module that does not import this one.
|
|
1902
|
+
global: Boolean(options.checkoutOffer)
|
|
892
1903
|
})
|
|
893
1904
|
];
|
|
894
1905
|
}
|
|
@@ -927,6 +1938,12 @@ function composeTenantBilling(ctx) {
|
|
|
927
1938
|
TenantBillingModule.forRoot({
|
|
928
1939
|
...tenantOptions,
|
|
929
1940
|
authGuards: ctx.shared.authGuards,
|
|
1941
|
+
// The same adapter `SubscriptionBundleModule` gets. That module
|
|
1942
|
+
// exports the token, but it is a sibling import here rather than an
|
|
1943
|
+
// ancestor, so its exports never reach this module's providers —
|
|
1944
|
+
// and the plan-change rule that reads bookings would resolve to
|
|
1945
|
+
// "none" and allow the move it exists to refuse.
|
|
1946
|
+
subscriptionBundleRepository: ctx.persistence?.entitlement?.subscriptionBundleRepository,
|
|
930
1947
|
subscriptionUsagePort: ctx.shared.subscriptionUsagePort,
|
|
931
1948
|
usageSnapshotPort: usageSnapshotPort ?? tenantSlice?.usageSnapshotPort ?? quotaUsageSnapshotProvider(quotaProviders),
|
|
932
1949
|
subscriptionWritePort: subscriptionWritePort ?? tenantSlice?.subscriptionWritePort,
|
|
@@ -951,7 +1968,7 @@ function composeSubscriptionBundles(ctx) {
|
|
|
951
1968
|
SubscriptionBundleModule.forRoot({
|
|
952
1969
|
...bundleOptions,
|
|
953
1970
|
subscriptionBundleRepository: ctx.persistence?.entitlement?.subscriptionBundleRepository,
|
|
954
|
-
bundleRepository: ctx.persistence
|
|
1971
|
+
bundleRepository: resolveBundleRepository(ctx.persistence),
|
|
955
1972
|
controller: controller === false ? void 0 : {
|
|
956
1973
|
...controller ?? {},
|
|
957
1974
|
authGuards: ctx.shared.authGuards,
|
|
@@ -976,6 +1993,8 @@ var FEATURE_COMPOSERS = [
|
|
|
976
1993
|
// Before the bundles — see the note above.
|
|
977
1994
|
composeTenantBilling,
|
|
978
1995
|
composeSubscriptionBundles,
|
|
1996
|
+
// After tenant billing too: its tenant routes share the guards.
|
|
1997
|
+
composePayments,
|
|
979
1998
|
composeSetup,
|
|
980
1999
|
composeAdminStats,
|
|
981
2000
|
composeCheckoutOffer,
|
|
@@ -1069,32 +2088,32 @@ var StaticPlanResolver = class {
|
|
|
1069
2088
|
};
|
|
1070
2089
|
|
|
1071
2090
|
// src/platform/static-entitlement.service.ts
|
|
1072
|
-
import { Inject as
|
|
1073
|
-
function
|
|
2091
|
+
import { Inject as Inject7, Injectable as Injectable6 } from "@nestjs/common";
|
|
2092
|
+
function _ts_decorate10(decorators, target, key, desc) {
|
|
1074
2093
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1075
2094
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1076
2095
|
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;
|
|
1077
2096
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1078
2097
|
}
|
|
1079
|
-
__name(
|
|
1080
|
-
function
|
|
2098
|
+
__name(_ts_decorate10, "_ts_decorate");
|
|
2099
|
+
function _ts_metadata8(k, v) {
|
|
1081
2100
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1082
2101
|
}
|
|
1083
|
-
__name(
|
|
1084
|
-
function
|
|
2102
|
+
__name(_ts_metadata8, "_ts_metadata");
|
|
2103
|
+
function _ts_param8(paramIndex, decorator) {
|
|
1085
2104
|
return function(target, key) {
|
|
1086
2105
|
decorator(target, key, paramIndex);
|
|
1087
2106
|
};
|
|
1088
2107
|
}
|
|
1089
|
-
__name(
|
|
2108
|
+
__name(_ts_param8, "_ts_param");
|
|
1090
2109
|
var StaticEntitlementService = class {
|
|
1091
2110
|
static {
|
|
1092
2111
|
__name(this, "StaticEntitlementService");
|
|
1093
2112
|
}
|
|
1094
|
-
|
|
2113
|
+
catalogs;
|
|
1095
2114
|
resolver;
|
|
1096
|
-
constructor(
|
|
1097
|
-
this.
|
|
2115
|
+
constructor(catalogs, resolver) {
|
|
2116
|
+
this.catalogs = catalogs;
|
|
1098
2117
|
this.resolver = resolver;
|
|
1099
2118
|
}
|
|
1100
2119
|
/**
|
|
@@ -1111,7 +2130,7 @@ var StaticEntitlementService = class {
|
|
|
1111
2130
|
quotas: {}
|
|
1112
2131
|
};
|
|
1113
2132
|
}
|
|
1114
|
-
const plan = this.
|
|
2133
|
+
const plan = findPlan(await this.catalogs.current(), planId);
|
|
1115
2134
|
if (!plan) {
|
|
1116
2135
|
return {
|
|
1117
2136
|
planId,
|
|
@@ -1135,34 +2154,31 @@ var StaticEntitlementService = class {
|
|
|
1135
2154
|
const value = snap.quotas[quotaKey];
|
|
1136
2155
|
return value === void 0 ? null : value;
|
|
1137
2156
|
}
|
|
1138
|
-
findPlan(planId) {
|
|
1139
|
-
return (this.catalog.plans ?? []).find((p) => p.id === planId);
|
|
1140
|
-
}
|
|
1141
2157
|
};
|
|
1142
|
-
StaticEntitlementService =
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
typeof
|
|
2158
|
+
StaticEntitlementService = _ts_decorate10([
|
|
2159
|
+
Injectable6(),
|
|
2160
|
+
_ts_param8(0, Inject7(PLAN_CATALOG_SOURCE_TOKEN)),
|
|
2161
|
+
_ts_param8(1, Inject7(PLAN_RESOLVER_PORT_TOKEN)),
|
|
2162
|
+
_ts_metadata8("design:type", Function),
|
|
2163
|
+
_ts_metadata8("design:paramtypes", [
|
|
2164
|
+
typeof PlanCatalogSource === "undefined" ? Object : PlanCatalogSource,
|
|
1149
2165
|
typeof PlanResolverPort === "undefined" ? Object : PlanResolverPort
|
|
1150
2166
|
])
|
|
1151
2167
|
], StaticEntitlementService);
|
|
1152
2168
|
|
|
1153
2169
|
// src/platform/tenant-manifest.service.ts
|
|
1154
|
-
import { Injectable as
|
|
1155
|
-
function
|
|
2170
|
+
import { Injectable as Injectable7 } from "@nestjs/common";
|
|
2171
|
+
function _ts_decorate11(decorators, target, key, desc) {
|
|
1156
2172
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1157
2173
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1158
2174
|
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;
|
|
1159
2175
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1160
2176
|
}
|
|
1161
|
-
__name(
|
|
1162
|
-
function
|
|
2177
|
+
__name(_ts_decorate11, "_ts_decorate");
|
|
2178
|
+
function _ts_metadata9(k, v) {
|
|
1163
2179
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1164
2180
|
}
|
|
1165
|
-
__name(
|
|
2181
|
+
__name(_ts_metadata9, "_ts_metadata");
|
|
1166
2182
|
var TenantManifestService = class {
|
|
1167
2183
|
static {
|
|
1168
2184
|
__name(this, "TenantManifestService");
|
|
@@ -1208,10 +2224,10 @@ var TenantManifestService = class {
|
|
|
1208
2224
|
};
|
|
1209
2225
|
}
|
|
1210
2226
|
};
|
|
1211
|
-
TenantManifestService =
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
2227
|
+
TenantManifestService = _ts_decorate11([
|
|
2228
|
+
Injectable7(),
|
|
2229
|
+
_ts_metadata9("design:type", Function),
|
|
2230
|
+
_ts_metadata9("design:paramtypes", [
|
|
1215
2231
|
typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService
|
|
1216
2232
|
])
|
|
1217
2233
|
], TenantManifestService);
|
|
@@ -1225,24 +2241,24 @@ function isVisible(item, features) {
|
|
|
1225
2241
|
__name(isVisible, "isVisible");
|
|
1226
2242
|
|
|
1227
2243
|
// src/platform/tenant-manifest.controller.ts
|
|
1228
|
-
import { Controller as
|
|
1229
|
-
function
|
|
2244
|
+
import { Controller as Controller3, Get as Get3, Inject as Inject8, Req as Req2, UseGuards as UseGuards2 } from "@nestjs/common";
|
|
2245
|
+
function _ts_decorate12(decorators, target, key, desc) {
|
|
1230
2246
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1231
2247
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1232
2248
|
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;
|
|
1233
2249
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1234
2250
|
}
|
|
1235
|
-
__name(
|
|
1236
|
-
function
|
|
2251
|
+
__name(_ts_decorate12, "_ts_decorate");
|
|
2252
|
+
function _ts_metadata10(k, v) {
|
|
1237
2253
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1238
2254
|
}
|
|
1239
|
-
__name(
|
|
1240
|
-
function
|
|
2255
|
+
__name(_ts_metadata10, "_ts_metadata");
|
|
2256
|
+
function _ts_param9(paramIndex, decorator) {
|
|
1241
2257
|
return function(target, key) {
|
|
1242
2258
|
decorator(target, key, paramIndex);
|
|
1243
2259
|
};
|
|
1244
2260
|
}
|
|
1245
|
-
__name(
|
|
2261
|
+
__name(_ts_param9, "_ts_param");
|
|
1246
2262
|
function buildTenantManifestController(options) {
|
|
1247
2263
|
const path = options.path ?? "tenant/manifest";
|
|
1248
2264
|
let GeneratedTenantManifestController = class GeneratedTenantManifestController {
|
|
@@ -1261,21 +2277,21 @@ function buildTenantManifestController(options) {
|
|
|
1261
2277
|
return this.svc.getManifest(tenantId);
|
|
1262
2278
|
}
|
|
1263
2279
|
};
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
2280
|
+
_ts_decorate12([
|
|
2281
|
+
Get3(),
|
|
2282
|
+
_ts_param9(0, Req2()),
|
|
2283
|
+
_ts_metadata10("design:type", Function),
|
|
2284
|
+
_ts_metadata10("design:paramtypes", [
|
|
1269
2285
|
typeof RequestWithUser === "undefined" ? Object : RequestWithUser
|
|
1270
2286
|
]),
|
|
1271
|
-
|
|
2287
|
+
_ts_metadata10("design:returntype", Promise)
|
|
1272
2288
|
], GeneratedTenantManifestController.prototype, "get", null);
|
|
1273
|
-
GeneratedTenantManifestController =
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
2289
|
+
GeneratedTenantManifestController = _ts_decorate12([
|
|
2290
|
+
Controller3(path),
|
|
2291
|
+
UseGuards2(...options.guards),
|
|
2292
|
+
_ts_param9(0, Inject8(TenantManifestService)),
|
|
2293
|
+
_ts_metadata10("design:type", Function),
|
|
2294
|
+
_ts_metadata10("design:paramtypes", [
|
|
1279
2295
|
typeof TenantManifestService === "undefined" ? Object : TenantManifestService
|
|
1280
2296
|
])
|
|
1281
2297
|
], GeneratedTenantManifestController);
|
|
@@ -1284,27 +2300,27 @@ function buildTenantManifestController(options) {
|
|
|
1284
2300
|
__name(buildTenantManifestController, "buildTenantManifestController");
|
|
1285
2301
|
|
|
1286
2302
|
// src/platform/enforce-quota.interceptor.ts
|
|
1287
|
-
import { Inject as
|
|
2303
|
+
import { Inject as Inject9, Injectable as Injectable8, Optional as Optional6 } from "@nestjs/common";
|
|
1288
2304
|
import { Reflector } from "@nestjs/core";
|
|
1289
2305
|
import { from } from "rxjs";
|
|
1290
2306
|
import { switchMap } from "rxjs/operators";
|
|
1291
|
-
function
|
|
2307
|
+
function _ts_decorate13(decorators, target, key, desc) {
|
|
1292
2308
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1293
2309
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1294
2310
|
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;
|
|
1295
2311
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1296
2312
|
}
|
|
1297
|
-
__name(
|
|
1298
|
-
function
|
|
2313
|
+
__name(_ts_decorate13, "_ts_decorate");
|
|
2314
|
+
function _ts_metadata11(k, v) {
|
|
1299
2315
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1300
2316
|
}
|
|
1301
|
-
__name(
|
|
1302
|
-
function
|
|
2317
|
+
__name(_ts_metadata11, "_ts_metadata");
|
|
2318
|
+
function _ts_param10(paramIndex, decorator) {
|
|
1303
2319
|
return function(target, key) {
|
|
1304
2320
|
decorator(target, key, paramIndex);
|
|
1305
2321
|
};
|
|
1306
2322
|
}
|
|
1307
|
-
__name(
|
|
2323
|
+
__name(_ts_param10, "_ts_param");
|
|
1308
2324
|
var QUOTA_PROVIDERS_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/QuotaProviders");
|
|
1309
2325
|
var EnforceQuotaInterceptor = class {
|
|
1310
2326
|
static {
|
|
@@ -1347,14 +2363,14 @@ var EnforceQuotaInterceptor = class {
|
|
|
1347
2363
|
}
|
|
1348
2364
|
}
|
|
1349
2365
|
};
|
|
1350
|
-
EnforceQuotaInterceptor =
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
2366
|
+
EnforceQuotaInterceptor = _ts_decorate13([
|
|
2367
|
+
Injectable8(),
|
|
2368
|
+
_ts_param10(0, Inject9(Reflector)),
|
|
2369
|
+
_ts_param10(1, Inject9(StaticEntitlementService)),
|
|
2370
|
+
_ts_param10(2, Optional6()),
|
|
2371
|
+
_ts_param10(2, Inject9(QUOTA_PROVIDERS_TOKEN)),
|
|
2372
|
+
_ts_metadata11("design:type", Function),
|
|
2373
|
+
_ts_metadata11("design:paramtypes", [
|
|
1358
2374
|
typeof Reflector === "undefined" ? Object : Reflector,
|
|
1359
2375
|
typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService,
|
|
1360
2376
|
Object
|
|
@@ -1362,26 +2378,26 @@ EnforceQuotaInterceptor = _ts_decorate8([
|
|
|
1362
2378
|
], EnforceQuotaInterceptor);
|
|
1363
2379
|
|
|
1364
2380
|
// src/platform/static-feature.guard.ts
|
|
1365
|
-
import { ForbiddenException as ForbiddenException2, Inject as
|
|
2381
|
+
import { ForbiddenException as ForbiddenException2, Inject as Inject10, Injectable as Injectable9, Optional as Optional7 } from "@nestjs/common";
|
|
1366
2382
|
import { Reflector as Reflector2 } from "@nestjs/core";
|
|
1367
2383
|
import { AUTH_ERROR_CODES, FEATURE_NOT_LICENSED } from "@saasicat/core";
|
|
1368
|
-
function
|
|
2384
|
+
function _ts_decorate14(decorators, target, key, desc) {
|
|
1369
2385
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1370
2386
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1371
2387
|
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;
|
|
1372
2388
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1373
2389
|
}
|
|
1374
|
-
__name(
|
|
1375
|
-
function
|
|
2390
|
+
__name(_ts_decorate14, "_ts_decorate");
|
|
2391
|
+
function _ts_metadata12(k, v) {
|
|
1376
2392
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1377
2393
|
}
|
|
1378
|
-
__name(
|
|
1379
|
-
function
|
|
2394
|
+
__name(_ts_metadata12, "_ts_metadata");
|
|
2395
|
+
function _ts_param11(paramIndex, decorator) {
|
|
1380
2396
|
return function(target, key) {
|
|
1381
2397
|
decorator(target, key, paramIndex);
|
|
1382
2398
|
};
|
|
1383
2399
|
}
|
|
1384
|
-
__name(
|
|
2400
|
+
__name(_ts_param11, "_ts_param");
|
|
1385
2401
|
var STATIC_FEATURE_GUARD_CONFIG_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/StaticFeatureGuardConfig");
|
|
1386
2402
|
var StaticFeatureGuard = class {
|
|
1387
2403
|
static {
|
|
@@ -1435,14 +2451,14 @@ var StaticFeatureGuard = class {
|
|
|
1435
2451
|
return true;
|
|
1436
2452
|
}
|
|
1437
2453
|
};
|
|
1438
|
-
StaticFeatureGuard =
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
2454
|
+
StaticFeatureGuard = _ts_decorate14([
|
|
2455
|
+
Injectable9(),
|
|
2456
|
+
_ts_param11(0, Inject10(Reflector2)),
|
|
2457
|
+
_ts_param11(1, Inject10(StaticEntitlementService)),
|
|
2458
|
+
_ts_param11(2, Optional7()),
|
|
2459
|
+
_ts_param11(2, Inject10(STATIC_FEATURE_GUARD_CONFIG_TOKEN)),
|
|
2460
|
+
_ts_metadata12("design:type", Function),
|
|
2461
|
+
_ts_metadata12("design:paramtypes", [
|
|
1446
2462
|
typeof Reflector2 === "undefined" ? Object : Reflector2,
|
|
1447
2463
|
typeof StaticEntitlementService === "undefined" ? Object : StaticEntitlementService,
|
|
1448
2464
|
Object
|
|
@@ -1460,27 +2476,37 @@ var SubscriptionPlanResolver = class {
|
|
|
1460
2476
|
}
|
|
1461
2477
|
subscriptions;
|
|
1462
2478
|
activeStatuses;
|
|
1463
|
-
|
|
2479
|
+
canceledEntitlementPlan;
|
|
2480
|
+
constructor(subscriptions, activeStatuses = DEFAULT_ACTIVE_STATUSES, canceledEntitlementPlan = null) {
|
|
1464
2481
|
this.subscriptions = subscriptions;
|
|
1465
2482
|
this.activeStatuses = activeStatuses;
|
|
2483
|
+
this.canceledEntitlementPlan = canceledEntitlementPlan;
|
|
1466
2484
|
}
|
|
1467
2485
|
async getPlanIdForTenant(tenantId) {
|
|
1468
2486
|
const subscription = await this.subscriptions.findByTenantId(tenantId);
|
|
1469
|
-
if (!subscription
|
|
2487
|
+
if (!subscription) return null;
|
|
2488
|
+
if (cancellationHasLanded(subscription, /* @__PURE__ */ new Date())) {
|
|
2489
|
+
return this.canceledEntitlementPlan;
|
|
2490
|
+
}
|
|
2491
|
+
if (!this.activeStatuses.has(subscription.status)) return null;
|
|
1470
2492
|
return subscription.plan;
|
|
1471
2493
|
}
|
|
1472
2494
|
};
|
|
1473
2495
|
|
|
1474
2496
|
// src/platform/saasicat.module.ts
|
|
1475
|
-
import { Logger, Module as
|
|
2497
|
+
import { Logger as Logger4, Module as Module3 } from "@nestjs/common";
|
|
1476
2498
|
import { DiscoveryModule as NestDiscoveryModule } from "@nestjs/core";
|
|
1477
2499
|
|
|
2500
|
+
// src/platform/compose/base.ts
|
|
2501
|
+
import { planCatalogSettingsOf } from "@saasicat/core";
|
|
2502
|
+
|
|
1478
2503
|
// src/platform/compose/manifest.ts
|
|
1479
2504
|
var PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/PlatformSubscriptionRepository");
|
|
1480
2505
|
var STANDARD_MANIFEST_REGISTRATION_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/nest/StandardManifestRegistration");
|
|
1481
2506
|
function buildStandardManifestContribution(catalog, adminResources, promoCodes) {
|
|
1482
2507
|
const capabilities = {
|
|
1483
|
-
"discovery.read": true
|
|
2508
|
+
"discovery.read": true,
|
|
2509
|
+
"settings.read": true
|
|
1484
2510
|
};
|
|
1485
2511
|
if (catalog && catalog.adminControllers !== false) {
|
|
1486
2512
|
Object.assign(capabilities, {
|
|
@@ -1532,32 +2558,25 @@ function buildStandardManifestContribution(catalog, adminResources, promoCodes)
|
|
|
1532
2558
|
__name(buildStandardManifestContribution, "buildStandardManifestContribution");
|
|
1533
2559
|
function buildMinimalManifestConfig() {
|
|
1534
2560
|
return {
|
|
1535
|
-
useFactory: /* @__PURE__ */ __name((
|
|
2561
|
+
useFactory: /* @__PURE__ */ __name((settings) => ({
|
|
1536
2562
|
project: {
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
2563
|
+
// One name, from the one place an installation carries it.
|
|
2564
|
+
key: settings.app.name,
|
|
2565
|
+
displayName: settings.app.name,
|
|
2566
|
+
label: settings.app.label,
|
|
2567
|
+
icon: settings.app.icon,
|
|
2568
|
+
logoUrl: settings.app.logoUrl,
|
|
1542
2569
|
environment: process.env.NODE_ENV === "production" ? "production" : "development",
|
|
1543
|
-
availableLocales:
|
|
1544
|
-
defaultLocale:
|
|
2570
|
+
availableLocales: settings.marketing?.availableLocales,
|
|
2571
|
+
defaultLocale: settings.marketing?.availableLocales?.[0]
|
|
1545
2572
|
},
|
|
1546
2573
|
build: {
|
|
1547
2574
|
platformPackageVersion: "0.0.0",
|
|
1548
2575
|
appVersion: "0.0.0"
|
|
1549
|
-
},
|
|
1550
|
-
planCatalogSnapshot: {
|
|
1551
|
-
source: "saasicat-module",
|
|
1552
|
-
hash: "sha256-quickstart",
|
|
1553
|
-
currency: catalog.currency,
|
|
1554
|
-
vatRate: catalog.vatRate,
|
|
1555
|
-
plans: catalog.plans ?? [],
|
|
1556
|
-
features: catalog.features ?? []
|
|
1557
2576
|
}
|
|
1558
2577
|
}), "useFactory"),
|
|
1559
2578
|
inject: [
|
|
1560
|
-
|
|
2579
|
+
PLAN_CATALOG_SETTINGS_TOKEN
|
|
1561
2580
|
]
|
|
1562
2581
|
};
|
|
1563
2582
|
}
|
|
@@ -1565,43 +2584,61 @@ __name(buildMinimalManifestConfig, "buildMinimalManifestConfig");
|
|
|
1565
2584
|
|
|
1566
2585
|
// src/platform/compose/base.ts
|
|
1567
2586
|
var DEFAULT_SNAPSHOT_PATH = "var/discovery-snapshot.json";
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
2587
|
+
var SOURCE_IN_CODE = "the object passed to SaaSiCatModule.forRoot({ planCatalog })";
|
|
2588
|
+
function resolveCatalog(options) {
|
|
2589
|
+
if (options.planCatalog) return {
|
|
2590
|
+
catalog: options.planCatalog
|
|
2591
|
+
};
|
|
2592
|
+
if (!dbCatalogNamesAFile(options.dbCatalog)) return {};
|
|
2593
|
+
try {
|
|
2594
|
+
return {
|
|
2595
|
+
catalog: loadPlanCatalogFromFile(options.dbCatalog)
|
|
2596
|
+
};
|
|
2597
|
+
} catch (failure) {
|
|
2598
|
+
return {
|
|
2599
|
+
failure: failure instanceof Error ? failure : new Error(String(failure))
|
|
2600
|
+
};
|
|
2601
|
+
}
|
|
2602
|
+
}
|
|
2603
|
+
__name(resolveCatalog, "resolveCatalog");
|
|
2604
|
+
function resolveSettingsSource(catalog) {
|
|
2605
|
+
return catalogSource(catalog) ?? SOURCE_IN_CODE;
|
|
2606
|
+
}
|
|
2607
|
+
__name(resolveSettingsSource, "resolveSettingsSource");
|
|
2608
|
+
function resolveAppInfo(options, catalog) {
|
|
2609
|
+
if (options.app) return options.app;
|
|
2610
|
+
return {
|
|
2611
|
+
key: catalog.app.name,
|
|
2612
|
+
version: catalog.app.version ?? "0.0.0"
|
|
1572
2613
|
};
|
|
1573
2614
|
}
|
|
1574
2615
|
__name(resolveAppInfo, "resolveAppInfo");
|
|
1575
|
-
function composePlanCatalog(options, sink) {
|
|
2616
|
+
function composePlanCatalog(options, catalog, sink) {
|
|
1576
2617
|
if (options.planCatalog) {
|
|
1577
2618
|
return PlanCatalogModule.forRootWithCatalog(options.planCatalog, {
|
|
1578
2619
|
global: true
|
|
1579
2620
|
});
|
|
1580
2621
|
}
|
|
1581
|
-
const dbCatalog = options.dbCatalog;
|
|
1582
2622
|
return PlanCatalogModule.forRoot({
|
|
1583
|
-
|
|
1584
|
-
app: dbCatalog.app,
|
|
1585
|
-
currency: dbCatalog.currency,
|
|
1586
|
-
vatRate: dbCatalog.vatRate,
|
|
1587
|
-
marketing: dbCatalog.marketing,
|
|
2623
|
+
...planCatalogSettingsOf(catalog),
|
|
1588
2624
|
sink,
|
|
1589
2625
|
imports: options.imports
|
|
1590
2626
|
});
|
|
1591
2627
|
}
|
|
1592
2628
|
__name(composePlanCatalog, "composePlanCatalog");
|
|
1593
|
-
function composeBaseModules(options, appInfo, ports) {
|
|
2629
|
+
function composeBaseModules(options, catalog, appInfo, ports) {
|
|
2630
|
+
const { appliedSettingsPort, emailPort, ...adminPorts } = ports;
|
|
1594
2631
|
return [
|
|
1595
2632
|
DiscoveryModule.forRoot({
|
|
1596
2633
|
app: appInfo,
|
|
1597
2634
|
controller: {
|
|
1598
|
-
guards: options
|
|
2635
|
+
guards: operatorGuards(options)
|
|
1599
2636
|
},
|
|
1600
2637
|
imports: options.imports,
|
|
1601
2638
|
snapshotPath: options.discoverySnapshotPath === void 0 ? DEFAULT_SNAPSHOT_PATH : options.discoverySnapshotPath
|
|
1602
2639
|
}),
|
|
1603
2640
|
AdminModule.forRoot({
|
|
1604
|
-
...
|
|
2641
|
+
...adminPorts,
|
|
1605
2642
|
imports: options.imports,
|
|
1606
2643
|
global: true
|
|
1607
2644
|
}),
|
|
@@ -1609,14 +2646,25 @@ function composeBaseModules(options, appInfo, ports) {
|
|
|
1609
2646
|
config: options.adminManifestConfig ?? buildMinimalManifestConfig(),
|
|
1610
2647
|
extraProviders: options.adminManifestExtraProviders,
|
|
1611
2648
|
includeManifestController: options.includeManifestController,
|
|
1612
|
-
guards: options
|
|
2649
|
+
guards: operatorGuards(options),
|
|
1613
2650
|
reloadGuards: options.reloadGuards,
|
|
1614
2651
|
imports: options.imports,
|
|
1615
2652
|
// Global like AdminModule above: apps register their manifest
|
|
1616
2653
|
// contribution by injecting AdminManifestService into one of their
|
|
1617
|
-
// own modules (
|
|
2654
|
+
// own modules (docs/guides/wire-the-backend.md, "Manifest
|
|
2655
|
+
// Contributions"). Re-exporting the module from here
|
|
1618
2656
|
// does not make that injection resolvable.
|
|
1619
2657
|
global: true
|
|
2658
|
+
}),
|
|
2659
|
+
SettingsModule.forRoot({
|
|
2660
|
+
port: appliedSettingsPort,
|
|
2661
|
+
email: emailPort,
|
|
2662
|
+
source: resolveSettingsSource(catalog),
|
|
2663
|
+
controller: {
|
|
2664
|
+
guards: operatorGuards(options)
|
|
2665
|
+
},
|
|
2666
|
+
includeController: options.includeSettingsController,
|
|
2667
|
+
imports: options.imports
|
|
1620
2668
|
})
|
|
1621
2669
|
];
|
|
1622
2670
|
}
|
|
@@ -1716,7 +2764,15 @@ function planResolverProvider(ctx, resolution) {
|
|
|
1716
2764
|
if (resolution.viaSubscriptions) {
|
|
1717
2765
|
return {
|
|
1718
2766
|
provide: PLAN_RESOLVER_PORT_TOKEN,
|
|
1719
|
-
useFactory: /* @__PURE__ */ __name((subscriptions) => new SubscriptionPlanResolver(
|
|
2767
|
+
useFactory: /* @__PURE__ */ __name((subscriptions) => new SubscriptionPlanResolver(
|
|
2768
|
+
subscriptions,
|
|
2769
|
+
void 0,
|
|
2770
|
+
// The same floor `EntitlementService` applies, read from the
|
|
2771
|
+
// same option: two enforcement paths that disagree about
|
|
2772
|
+
// what a cancelled subscription keeps would be worse than
|
|
2773
|
+
// either answer on its own.
|
|
2774
|
+
ctx.options.entitlement?.resolutionConfig?.canceledEntitlementPlan ?? null
|
|
2775
|
+
), "useFactory"),
|
|
1720
2776
|
inject: [
|
|
1721
2777
|
PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN
|
|
1722
2778
|
]
|
|
@@ -1730,14 +2786,14 @@ function planResolverProvider(ctx, resolution) {
|
|
|
1730
2786
|
__name(planResolverProvider, "planResolverProvider");
|
|
1731
2787
|
|
|
1732
2788
|
// src/platform/saasicat.module.ts
|
|
1733
|
-
function
|
|
2789
|
+
function _ts_decorate15(decorators, target, key, desc) {
|
|
1734
2790
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1735
2791
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
1736
2792
|
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;
|
|
1737
2793
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1738
2794
|
}
|
|
1739
|
-
__name(
|
|
1740
|
-
var PLATFORM_LOGGER = new
|
|
2795
|
+
__name(_ts_decorate15, "_ts_decorate");
|
|
2796
|
+
var PLATFORM_LOGGER = new Logger4("SaaSiCat");
|
|
1741
2797
|
function enforcementChainState(state) {
|
|
1742
2798
|
return {
|
|
1743
2799
|
provide: ENFORCEMENT_CHAIN_STATE_TOKEN,
|
|
@@ -1762,29 +2818,37 @@ var SaaSiCatModule = class _SaaSiCatModule {
|
|
|
1762
2818
|
mfa: explicit.mfa ?? persistence?.core.mfa,
|
|
1763
2819
|
audit: explicit.audit ?? persistence?.core.audit,
|
|
1764
2820
|
rlsBypass: explicit.rlsBypass ?? persistence?.core.rlsBypass,
|
|
2821
|
+
appliedSettings: explicit.appliedSettings ?? persistence?.core.appliedSettings,
|
|
2822
|
+
email: explicit.email,
|
|
1765
2823
|
planCatalogReadSink: explicit.planCatalogReadSink ?? persistence?.planCatalogReadSink,
|
|
1766
2824
|
planResolver: explicit.planResolver,
|
|
1767
2825
|
subscriptionRepository: explicit.subscriptionRepository ?? persistence?.entitlement?.subscriptionRepository,
|
|
1768
2826
|
planVersionRepository: explicit.planVersionRepository ?? persistence?.entitlement?.planVersionRepository,
|
|
1769
2827
|
transactionRunner: explicit.transactionRunner ?? persistence?.core.transactionRunner
|
|
1770
2828
|
};
|
|
2829
|
+
const { catalog, failure: catalogFailure } = resolveCatalog(options);
|
|
1771
2830
|
assertConfiguration({
|
|
1772
2831
|
options,
|
|
1773
|
-
adapters
|
|
2832
|
+
adapters,
|
|
2833
|
+
catalog,
|
|
2834
|
+
catalogFailure
|
|
1774
2835
|
});
|
|
1775
2836
|
const mfaPort = adapters.mfa;
|
|
1776
2837
|
const auditPort = adapters.audit;
|
|
1777
2838
|
const rlsBypassPort = adapters.rlsBypass;
|
|
1778
|
-
const
|
|
2839
|
+
const running = catalog;
|
|
2840
|
+
const appInfo = resolveAppInfo(options, running);
|
|
1779
2841
|
const imports = [
|
|
1780
2842
|
// Make injected client/auth providers visible to the high-level
|
|
1781
2843
|
// module's own quota providers and automatic plan resolver too.
|
|
1782
2844
|
...options.imports ?? [],
|
|
1783
|
-
composePlanCatalog(options, adapters.planCatalogReadSink),
|
|
1784
|
-
...composeBaseModules(options, appInfo, {
|
|
2845
|
+
composePlanCatalog(options, running, adapters.planCatalogReadSink),
|
|
2846
|
+
...composeBaseModules(options, running, appInfo, {
|
|
1785
2847
|
mfaPort,
|
|
1786
2848
|
auditPort,
|
|
1787
|
-
rlsBypassPort
|
|
2849
|
+
rlsBypassPort,
|
|
2850
|
+
appliedSettingsPort: adapters.appliedSettings,
|
|
2851
|
+
emailPort: adapters.email
|
|
1788
2852
|
})
|
|
1789
2853
|
];
|
|
1790
2854
|
const composition = {
|
|
@@ -1833,6 +2897,8 @@ var SaaSiCatModule = class _SaaSiCatModule {
|
|
|
1833
2897
|
}));
|
|
1834
2898
|
}
|
|
1835
2899
|
if (options.enforcementChainCheck !== false) imports.push(NestDiscoveryModule);
|
|
2900
|
+
lightweightProviders.push(IssuerIdentityInspector, IssuerIdentityCheck);
|
|
2901
|
+
lightweightExports.push(IssuerIdentityInspector, IssuerIdentityCheck);
|
|
1836
2902
|
const tenantManifest = composeTenantManifest(composition);
|
|
1837
2903
|
lightweightProviders.push(...tenantManifest.providers);
|
|
1838
2904
|
lightweightExports.push(...tenantManifest.exports);
|
|
@@ -1846,18 +2912,30 @@ var SaaSiCatModule = class _SaaSiCatModule {
|
|
|
1846
2912
|
};
|
|
1847
2913
|
}
|
|
1848
2914
|
};
|
|
1849
|
-
SaaSiCatModule =
|
|
1850
|
-
|
|
2915
|
+
SaaSiCatModule = _ts_decorate15([
|
|
2916
|
+
Module3({})
|
|
1851
2917
|
], SaaSiCatModule);
|
|
1852
2918
|
|
|
1853
2919
|
export {
|
|
2920
|
+
EMAIL_PORT_TOKEN,
|
|
1854
2921
|
SETUP_PROVISIONING_PORT_TOKEN,
|
|
1855
2922
|
SETUP_CONFIG_TOKEN,
|
|
1856
2923
|
SetupService,
|
|
1857
2924
|
SetupController,
|
|
1858
2925
|
SetupModule,
|
|
2926
|
+
SettingsChangeNotifier,
|
|
2927
|
+
describeChange,
|
|
2928
|
+
fingerprintOf,
|
|
2929
|
+
APPLIED_SETTINGS_PORT_TOKEN,
|
|
2930
|
+
SETTINGS_SOURCE_TOKEN,
|
|
2931
|
+
AppliedSettingsRecorder,
|
|
2932
|
+
buildSettingsController,
|
|
2933
|
+
SettingsModule,
|
|
1859
2934
|
ENFORCEMENT_CHAIN_STATE_TOKEN,
|
|
1860
2935
|
EnforcementChainCheck,
|
|
2936
|
+
IssuerIdentityInspector,
|
|
2937
|
+
IssuerIdentityCheck,
|
|
2938
|
+
DB_CATALOG_MEMBERS,
|
|
1861
2939
|
headingSlug,
|
|
1862
2940
|
docsUrlFor,
|
|
1863
2941
|
PLATFORM_RULES,
|