@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,16 +1,20 @@
|
|
|
1
1
|
import {
|
|
2
|
-
AUDIT_CONTEXT_RESOLVER_TOKEN,
|
|
3
|
-
USER_EMAIL_RESOLVER_TOKEN,
|
|
4
|
-
USER_ID_RESOLVER_TOKEN,
|
|
5
2
|
classifyBundleVersionDiff,
|
|
6
3
|
classifyPlanDiff
|
|
7
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-GZTL64QK.js";
|
|
5
|
+
import {
|
|
6
|
+
EnforceMfa
|
|
7
|
+
} from "./chunk-34XVPR6P.js";
|
|
8
8
|
import {
|
|
9
9
|
AdminAuditService
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-OOTEXP47.js";
|
|
11
11
|
import {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
cyclesSoldFor,
|
|
13
|
+
resolveBundlePriceNet
|
|
14
|
+
} from "./chunk-KPEMTBOP.js";
|
|
15
|
+
import {
|
|
16
|
+
DiscoveryScanner
|
|
17
|
+
} from "./chunk-JVAEKTJ4.js";
|
|
14
18
|
import {
|
|
15
19
|
SUBSCRIPTION_REPOSITORY_TOKEN
|
|
16
20
|
} from "./chunk-OUDSQ3PU.js";
|
|
@@ -21,32 +25,129 @@ import {
|
|
|
21
25
|
CATALOG_SERVICE_CONFIG_TOKEN,
|
|
22
26
|
MARKETING_PROJECTION_REPOSITORY_TOKEN,
|
|
23
27
|
MARKETING_SETTINGS_REPOSITORY_TOKEN,
|
|
28
|
+
NEW_PAYMENT_METHODS_SOURCE_TOKEN,
|
|
24
29
|
PLAN_REPOSITORY_TOKEN,
|
|
25
30
|
PROMOTION_REPOSITORY_TOKEN
|
|
26
|
-
} from "./chunk-
|
|
27
|
-
import {
|
|
28
|
-
asProvider
|
|
29
|
-
} from "./chunk-SABTXESR.js";
|
|
30
|
-
import {
|
|
31
|
-
DiscoveryScanner
|
|
32
|
-
} from "./chunk-WXYJHZCN.js";
|
|
31
|
+
} from "./chunk-3QVGA6JX.js";
|
|
33
32
|
import {
|
|
34
33
|
DISCOVERY_SNAPSHOT_TOKEN
|
|
35
34
|
} from "./chunk-QUZU2M33.js";
|
|
35
|
+
import {
|
|
36
|
+
AUDIT_CONTEXT_RESOLVER_TOKEN,
|
|
37
|
+
USER_EMAIL_RESOLVER_TOKEN,
|
|
38
|
+
USER_ID_RESOLVER_TOKEN
|
|
39
|
+
} from "./chunk-GPQWGA6B.js";
|
|
40
|
+
import {
|
|
41
|
+
SaaSiCatPublicRoute
|
|
42
|
+
} from "./chunk-YSC46BGM.js";
|
|
43
|
+
import {
|
|
44
|
+
asProvider
|
|
45
|
+
} from "./chunk-J7NJRK3K.js";
|
|
36
46
|
import {
|
|
37
47
|
__name
|
|
38
48
|
} from "./chunk-SHUYVCID.js";
|
|
39
49
|
|
|
50
|
+
// src/catalog/validity-window.ts
|
|
51
|
+
import { UnprocessableEntityException } from "@nestjs/common";
|
|
52
|
+
import { CATALOG_ERROR_CODES } from "@saasicat/core";
|
|
53
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
54
|
+
var PLAN_VERSION_WINDOW_CODES = {
|
|
55
|
+
validFromRequired: CATALOG_ERROR_CODES.PLAN_VERSION_VALID_FROM_REQUIRED,
|
|
56
|
+
validFromInvalid: CATALOG_ERROR_CODES.PLAN_VERSION_VALID_FROM_INVALID,
|
|
57
|
+
validFromNotAfterPrevious: CATALOG_ERROR_CODES.PLAN_VERSION_VALID_FROM_NOT_AFTER_PREVIOUS,
|
|
58
|
+
validFromNotGapless: CATALOG_ERROR_CODES.PLAN_VERSION_VALID_FROM_NOT_GAPLESS,
|
|
59
|
+
validUntilInvalid: CATALOG_ERROR_CODES.PLAN_VERSION_VALID_UNTIL_INVALID,
|
|
60
|
+
validUntilBeforeFrom: CATALOG_ERROR_CODES.PLAN_VERSION_VALID_UNTIL_BEFORE_FROM
|
|
61
|
+
};
|
|
62
|
+
var BUNDLE_VERSION_WINDOW_CODES = {
|
|
63
|
+
validFromRequired: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_FROM_REQUIRED,
|
|
64
|
+
validFromInvalid: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_FROM_INVALID,
|
|
65
|
+
validFromNotAfterPrevious: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_FROM_NOT_AFTER_PREVIOUS,
|
|
66
|
+
validFromNotGapless: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_FROM_NOT_GAPLESS,
|
|
67
|
+
validUntilInvalid: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_UNTIL_INVALID,
|
|
68
|
+
validUntilBeforeFrom: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_UNTIL_BEFORE_FROM
|
|
69
|
+
};
|
|
70
|
+
function resolveValidityWindow(publishMeta, draft, previous, codes) {
|
|
71
|
+
const validFromInput = publishMeta.validFrom ?? draft.validFrom;
|
|
72
|
+
if (!validFromInput) {
|
|
73
|
+
throw new UnprocessableEntityException({
|
|
74
|
+
code: codes.validFromRequired,
|
|
75
|
+
message: "validFrom must be set when publishing (on the draft or the publish call).."
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
const validFrom = new Date(validFromInput);
|
|
79
|
+
if (Number.isNaN(validFrom.getTime())) {
|
|
80
|
+
throw new UnprocessableEntityException({
|
|
81
|
+
code: codes.validFromInvalid,
|
|
82
|
+
message: `validFrom '${validFromInput}' is not a valid date`,
|
|
83
|
+
params: {
|
|
84
|
+
validFrom: validFromInput
|
|
85
|
+
}
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
if (previous?.validFrom) {
|
|
89
|
+
const previousFrom = new Date(previous.validFrom);
|
|
90
|
+
if (validFrom <= previousFrom) {
|
|
91
|
+
throw new UnprocessableEntityException({
|
|
92
|
+
code: codes.validFromNotAfterPrevious,
|
|
93
|
+
message: `validFrom (${validFrom.toISOString()}) must be strictly after the validFrom of the previous version (${previous.validFrom}).`,
|
|
94
|
+
params: {
|
|
95
|
+
validFrom: validFrom.toISOString(),
|
|
96
|
+
previousValidFrom: previous.validFrom
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
if (previous.validUntil) {
|
|
101
|
+
const requiredStart = new Date(new Date(previous.validUntil).getTime() + DAY_MS);
|
|
102
|
+
if (validFrom.getTime() !== requiredStart.getTime()) {
|
|
103
|
+
throw new UnprocessableEntityException({
|
|
104
|
+
code: codes.validFromNotGapless,
|
|
105
|
+
message: `The predecessor has validUntil=${previous.validUntil.slice(0, 10)} \u2014 the successor must start seamlessly on the next day (${requiredStart.toISOString().slice(0, 10)}). Received: ${validFrom.toISOString().slice(0, 10)}.`,
|
|
106
|
+
params: {
|
|
107
|
+
received: validFrom.toISOString().slice(0, 10)
|
|
108
|
+
},
|
|
109
|
+
requiredValidFrom: requiredStart.toISOString().slice(0, 10),
|
|
110
|
+
previousValidUntil: previous.validUntil
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const validUntilInput = publishMeta.validUntil !== void 0 ? publishMeta.validUntil : draft.validUntil;
|
|
116
|
+
const validUntil = validUntilInput ? new Date(validUntilInput) : null;
|
|
117
|
+
if (validUntil && Number.isNaN(validUntil.getTime())) {
|
|
118
|
+
throw new UnprocessableEntityException({
|
|
119
|
+
code: codes.validUntilInvalid,
|
|
120
|
+
message: `validUntil '${validUntilInput}' is not a valid date`,
|
|
121
|
+
params: {
|
|
122
|
+
validUntil: validUntilInput
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
if (validUntil && validUntil <= validFrom) {
|
|
127
|
+
throw new UnprocessableEntityException({
|
|
128
|
+
code: codes.validUntilBeforeFrom,
|
|
129
|
+
message: `validUntil (${validUntil.toISOString()}) must be strictly after validFrom (${validFrom.toISOString()}).`,
|
|
130
|
+
params: {
|
|
131
|
+
validUntil: validUntil.toISOString(),
|
|
132
|
+
validFrom: validFrom.toISOString()
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return {
|
|
137
|
+
validFrom,
|
|
138
|
+
validUntil
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
__name(resolveValidityWindow, "resolveValidityWindow");
|
|
142
|
+
|
|
40
143
|
// src/catalog/approved-keys.ts
|
|
41
|
-
async function loadApprovedCatalogKeys(repo
|
|
144
|
+
async function loadApprovedCatalogKeys(repo) {
|
|
42
145
|
if (!repo) return null;
|
|
43
146
|
const [features, quotas] = await Promise.all([
|
|
44
147
|
repo.listFeatures({
|
|
45
|
-
projectKey,
|
|
46
148
|
discoveryStatus: "approved"
|
|
47
149
|
}),
|
|
48
150
|
repo.listQuotas({
|
|
49
|
-
projectKey,
|
|
50
151
|
discoveryStatus: "approved"
|
|
51
152
|
})
|
|
52
153
|
]);
|
|
@@ -191,8 +292,8 @@ function validatePlanDraft(draft, snapshot, marketedOnlyFeatures = /* @__PURE__
|
|
|
191
292
|
__name(validatePlanDraft, "validatePlanDraft");
|
|
192
293
|
|
|
193
294
|
// src/catalog/bundles.service.ts
|
|
194
|
-
import { Inject, Injectable, Logger, NotFoundException, Optional, UnprocessableEntityException } from "@nestjs/common";
|
|
195
|
-
import { CATALOG_ERROR_CODES, isVersionEditable } from "@saasicat/core";
|
|
295
|
+
import { Inject, Injectable, Logger, NotFoundException, Optional, UnprocessableEntityException as UnprocessableEntityException2 } from "@nestjs/common";
|
|
296
|
+
import { CATALOG_ERROR_CODES as CATALOG_ERROR_CODES2, isVersionEditable } from "@saasicat/core";
|
|
196
297
|
|
|
197
298
|
// src/core/discovery-snapshot-source.ts
|
|
198
299
|
function resolveDiscoverySnapshot(injected, scanner) {
|
|
@@ -254,9 +355,8 @@ var BundlesService = class _BundlesService {
|
|
|
254
355
|
// =========================================================================
|
|
255
356
|
// Master operations
|
|
256
357
|
// =========================================================================
|
|
257
|
-
listBundles(
|
|
358
|
+
listBundles() {
|
|
258
359
|
return this.repo.list({
|
|
259
|
-
projectKey,
|
|
260
360
|
excludeDeleted: true
|
|
261
361
|
});
|
|
262
362
|
}
|
|
@@ -264,7 +364,7 @@ var BundlesService = class _BundlesService {
|
|
|
264
364
|
const bundle = await this.repo.findById(bundleId);
|
|
265
365
|
if (!bundle) {
|
|
266
366
|
throw new NotFoundException({
|
|
267
|
-
code:
|
|
367
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_NOT_FOUND,
|
|
268
368
|
message: `Bundle '${bundleId}' not found`,
|
|
269
369
|
params: {
|
|
270
370
|
bundleId
|
|
@@ -278,14 +378,13 @@ var BundlesService = class _BundlesService {
|
|
|
278
378
|
};
|
|
279
379
|
}
|
|
280
380
|
async createBundle(data) {
|
|
281
|
-
const existing = await this.repo.findByKey(data.
|
|
381
|
+
const existing = await this.repo.findByKey(data.bundleKey);
|
|
282
382
|
if (existing) {
|
|
283
|
-
throw new
|
|
284
|
-
code:
|
|
285
|
-
message: `Bundle '${data.bundleKey}' already exists
|
|
383
|
+
throw new UnprocessableEntityException2({
|
|
384
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_ALREADY_EXISTS,
|
|
385
|
+
message: `Bundle '${data.bundleKey}' already exists`,
|
|
286
386
|
params: {
|
|
287
|
-
bundleKey: data.bundleKey
|
|
288
|
-
projectKey: data.projectKey
|
|
387
|
+
bundleKey: data.bundleKey
|
|
289
388
|
}
|
|
290
389
|
});
|
|
291
390
|
}
|
|
@@ -295,7 +394,7 @@ var BundlesService = class _BundlesService {
|
|
|
295
394
|
const existing = await this.repo.findById(bundleId);
|
|
296
395
|
if (!existing) {
|
|
297
396
|
throw new NotFoundException({
|
|
298
|
-
code:
|
|
397
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_NOT_FOUND,
|
|
299
398
|
message: `Bundle '${bundleId}' not found`,
|
|
300
399
|
params: {
|
|
301
400
|
bundleId
|
|
@@ -308,7 +407,7 @@ var BundlesService = class _BundlesService {
|
|
|
308
407
|
const existing = await this.repo.findById(bundleId);
|
|
309
408
|
if (!existing) {
|
|
310
409
|
throw new NotFoundException({
|
|
311
|
-
code:
|
|
410
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_NOT_FOUND,
|
|
312
411
|
message: `Bundle '${bundleId}' not found`,
|
|
313
412
|
params: {
|
|
314
413
|
bundleId
|
|
@@ -327,7 +426,7 @@ var BundlesService = class _BundlesService {
|
|
|
327
426
|
const existing = await this.repo.findVersionById(versionId);
|
|
328
427
|
if (!existing) {
|
|
329
428
|
throw new NotFoundException({
|
|
330
|
-
code:
|
|
429
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_NOT_FOUND,
|
|
331
430
|
message: `BundleVersion '${versionId}' not found`,
|
|
332
431
|
params: {
|
|
333
432
|
bundleVersionId: versionId
|
|
@@ -335,8 +434,8 @@ var BundlesService = class _BundlesService {
|
|
|
335
434
|
});
|
|
336
435
|
}
|
|
337
436
|
if (existing.publishedAt !== null) {
|
|
338
|
-
throw new
|
|
339
|
-
code:
|
|
437
|
+
throw new UnprocessableEntityException2({
|
|
438
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_ALREADY_PUBLISHED,
|
|
340
439
|
message: `BundleVersion '${versionId}' is already published and cannot be discarded.`,
|
|
341
440
|
params: {
|
|
342
441
|
versionId
|
|
@@ -344,8 +443,8 @@ var BundlesService = class _BundlesService {
|
|
|
344
443
|
});
|
|
345
444
|
}
|
|
346
445
|
if (typeof this.repo.deleteDraft !== "function") {
|
|
347
|
-
throw new
|
|
348
|
-
code:
|
|
446
|
+
throw new UnprocessableEntityException2({
|
|
447
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_DISCARD_NOT_IMPLEMENTED,
|
|
349
448
|
message: "Discard is not implemented in the current repository. Implement BundleRepository.deleteDraft."
|
|
350
449
|
});
|
|
351
450
|
}
|
|
@@ -361,7 +460,7 @@ var BundlesService = class _BundlesService {
|
|
|
361
460
|
const version = await this.repo.findVersionById(versionId);
|
|
362
461
|
if (!version) {
|
|
363
462
|
throw new NotFoundException({
|
|
364
|
-
code:
|
|
463
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_NOT_FOUND,
|
|
365
464
|
message: `BundleVersion '${versionId}' not found`,
|
|
366
465
|
params: {
|
|
367
466
|
bundleVersionId: versionId
|
|
@@ -409,7 +508,7 @@ var BundlesService = class _BundlesService {
|
|
|
409
508
|
const bundle = await this.repo.findById(data.bundleId);
|
|
410
509
|
if (!bundle) {
|
|
411
510
|
throw new NotFoundException({
|
|
412
|
-
code:
|
|
511
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_NOT_FOUND,
|
|
413
512
|
message: `Bundle '${data.bundleId}' not found`,
|
|
414
513
|
params: {
|
|
415
514
|
bundleId: data.bundleId
|
|
@@ -418,8 +517,8 @@ var BundlesService = class _BundlesService {
|
|
|
418
517
|
}
|
|
419
518
|
const existingDraft = await this.repo.findCurrentDraft(data.bundleId);
|
|
420
519
|
if (existingDraft) {
|
|
421
|
-
throw new
|
|
422
|
-
code:
|
|
520
|
+
throw new UnprocessableEntityException2({
|
|
521
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_DRAFT_ALREADY_EXISTS,
|
|
423
522
|
message: `Bundle '${bundle.bundleKey}' already has a draft version v${existingDraft.version}; publish or discard it first`,
|
|
424
523
|
params: {
|
|
425
524
|
bundleKey: bundle.bundleKey,
|
|
@@ -438,7 +537,7 @@ var BundlesService = class _BundlesService {
|
|
|
438
537
|
features: data.features,
|
|
439
538
|
quotas: data.quotas ?? {},
|
|
440
539
|
compatibility: data.compatibility
|
|
441
|
-
}
|
|
540
|
+
});
|
|
442
541
|
this.gateOrPass(warnings);
|
|
443
542
|
const bundleVersion = await this.repo.createDraft(data);
|
|
444
543
|
return {
|
|
@@ -458,7 +557,7 @@ var BundlesService = class _BundlesService {
|
|
|
458
557
|
const existing = await this.repo.findVersionById(versionId);
|
|
459
558
|
if (!existing) {
|
|
460
559
|
throw new NotFoundException({
|
|
461
|
-
code:
|
|
560
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_NOT_FOUND,
|
|
462
561
|
message: `BundleVersion '${versionId}' not found`,
|
|
463
562
|
params: {
|
|
464
563
|
bundleVersionId: versionId
|
|
@@ -470,8 +569,8 @@ var BundlesService = class _BundlesService {
|
|
|
470
569
|
]);
|
|
471
570
|
const { editable } = isVersionEditable(annotated);
|
|
472
571
|
if (!editable) {
|
|
473
|
-
throw new
|
|
474
|
-
code:
|
|
572
|
+
throw new UnprocessableEntityException2({
|
|
573
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_NOT_EDITABLE,
|
|
475
574
|
message: `BundleVersion '${versionId}' is not editable. Only drafts and published versions are editable that are latest-in-chain, bind no subscription yet, and whose validFrom lies in the future.`,
|
|
476
575
|
params: {
|
|
477
576
|
versionId
|
|
@@ -483,8 +582,7 @@ var BundlesService = class _BundlesService {
|
|
|
483
582
|
quotas: data.quotas ?? existing.quotas,
|
|
484
583
|
compatibility: data.compatibility ?? existing.compatibility
|
|
485
584
|
};
|
|
486
|
-
const
|
|
487
|
-
const warnings = await this.runStrictCheck(merged, bundle?.projectKey ?? null);
|
|
585
|
+
const warnings = await this.runStrictCheck(merged);
|
|
488
586
|
this.gateOrPass(warnings);
|
|
489
587
|
await this.assertBundleVersionWindowUpdate(existing, data);
|
|
490
588
|
const bundleVersion = await this.repo.updateDraft(versionId, data);
|
|
@@ -507,7 +605,7 @@ var BundlesService = class _BundlesService {
|
|
|
507
605
|
const draft = await this.repo.findVersionById(versionId);
|
|
508
606
|
if (!draft) {
|
|
509
607
|
throw new NotFoundException({
|
|
510
|
-
code:
|
|
608
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_NOT_FOUND,
|
|
511
609
|
message: `BundleVersion '${versionId}' not found`,
|
|
512
610
|
params: {
|
|
513
611
|
bundleVersionId: versionId
|
|
@@ -515,100 +613,44 @@ var BundlesService = class _BundlesService {
|
|
|
515
613
|
});
|
|
516
614
|
}
|
|
517
615
|
if (draft.publishedAt !== null) {
|
|
518
|
-
throw new
|
|
519
|
-
code:
|
|
616
|
+
throw new UnprocessableEntityException2({
|
|
617
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_ALREADY_PUBLISHED,
|
|
520
618
|
message: `BundleVersion '${versionId}' is already published`,
|
|
521
619
|
params: {
|
|
522
620
|
versionId
|
|
523
621
|
}
|
|
524
622
|
});
|
|
525
623
|
}
|
|
526
|
-
const bundle = await this.repo.findById(draft.bundleId);
|
|
527
624
|
const warnings = await this.runStrictCheck({
|
|
528
625
|
features: draft.features,
|
|
529
626
|
quotas: draft.quotas,
|
|
530
627
|
compatibility: draft.compatibility
|
|
531
|
-
}
|
|
628
|
+
});
|
|
532
629
|
this.gateOrPass(warnings);
|
|
630
|
+
const hasAnyPrice = draft.monthlyNet != null || draft.yearlyNet != null || (draft.pricingOverrides ?? []).some((o) => o.monthlyNet != null || o.yearlyNet != null);
|
|
631
|
+
if (!hasAnyPrice) {
|
|
632
|
+
throw new UnprocessableEntityException2({
|
|
633
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_NO_PRICE,
|
|
634
|
+
message: "A bundle version cannot be published without a price: neither a base price nor any plan override resolves one, so a tenant booking it would be charged nothing for what they receive.",
|
|
635
|
+
params: {
|
|
636
|
+
versionId
|
|
637
|
+
}
|
|
638
|
+
});
|
|
639
|
+
}
|
|
533
640
|
if (!publishMeta.allowZeroPrice) {
|
|
534
641
|
const explicitZero = /* @__PURE__ */ __name((v) => v !== null && v !== void 0 && Number.parseFloat(String(v)) <= 0, "explicitZero");
|
|
535
642
|
if (explicitZero(draft.monthlyNet) || explicitZero(draft.yearlyNet)) {
|
|
536
|
-
throw new
|
|
537
|
-
code:
|
|
643
|
+
throw new UnprocessableEntityException2({
|
|
644
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_ZERO_PRICE,
|
|
538
645
|
message: "A bundle version cannot be published with an explicit price of 0.00 (guard against seed placeholders). For free bundles leave it null, or set allowZeroPrice.",
|
|
539
646
|
monthlyNet: draft.monthlyNet,
|
|
540
647
|
yearlyNet: draft.yearlyNet
|
|
541
648
|
});
|
|
542
649
|
}
|
|
543
650
|
}
|
|
651
|
+
await this.refuseUnsellableToACompatiblePlan(draft, versionId);
|
|
544
652
|
const previous = await this.repo.findLatestLive(draft.bundleId);
|
|
545
|
-
const
|
|
546
|
-
if (!validFromInput) {
|
|
547
|
-
throw new UnprocessableEntityException({
|
|
548
|
-
code: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_FROM_REQUIRED,
|
|
549
|
-
message: "validFrom must be set when publishing (on the draft or the publish call).."
|
|
550
|
-
});
|
|
551
|
-
}
|
|
552
|
-
const validFrom = new Date(validFromInput);
|
|
553
|
-
if (Number.isNaN(validFrom.getTime())) {
|
|
554
|
-
throw new UnprocessableEntityException({
|
|
555
|
-
code: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_FROM_INVALID,
|
|
556
|
-
message: `validFrom '${validFromInput}' is not a valid date`,
|
|
557
|
-
params: {
|
|
558
|
-
validFrom: validFromInput
|
|
559
|
-
}
|
|
560
|
-
});
|
|
561
|
-
}
|
|
562
|
-
if (previous?.validFrom) {
|
|
563
|
-
const prevFrom = new Date(previous.validFrom);
|
|
564
|
-
if (validFrom <= prevFrom) {
|
|
565
|
-
throw new UnprocessableEntityException({
|
|
566
|
-
code: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_FROM_NOT_AFTER_PREVIOUS,
|
|
567
|
-
message: `validFrom (${validFrom.toISOString()}) must be strictly after the validFrom of the previous version (${previous.validFrom}).`,
|
|
568
|
-
params: {
|
|
569
|
-
validFrom: validFrom.toISOString(),
|
|
570
|
-
previousValidFrom: previous.validFrom
|
|
571
|
-
}
|
|
572
|
-
});
|
|
573
|
-
}
|
|
574
|
-
if (previous.validUntil) {
|
|
575
|
-
const prevUntil = new Date(previous.validUntil);
|
|
576
|
-
const dayMs = 24 * 60 * 60 * 1e3;
|
|
577
|
-
const requiredStart = new Date(prevUntil.getTime() + dayMs);
|
|
578
|
-
if (validFrom.getTime() !== requiredStart.getTime()) {
|
|
579
|
-
throw new UnprocessableEntityException({
|
|
580
|
-
code: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_FROM_NOT_GAPLESS,
|
|
581
|
-
message: `The predecessor has validUntil=${previous.validUntil.slice(0, 10)} \u2014 the successor must start seamlessly on the next day (${requiredStart.toISOString().slice(0, 10)}). Received: ${validFrom.toISOString().slice(0, 10)}.`,
|
|
582
|
-
params: {
|
|
583
|
-
received: validFrom.toISOString().slice(0, 10)
|
|
584
|
-
},
|
|
585
|
-
requiredValidFrom: requiredStart.toISOString().slice(0, 10),
|
|
586
|
-
previousValidUntil: previous.validUntil
|
|
587
|
-
});
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
const validUntilInput = publishMeta.validUntil !== void 0 ? publishMeta.validUntil : draft.validUntil;
|
|
592
|
-
const validUntil = validUntilInput ? new Date(validUntilInput) : null;
|
|
593
|
-
if (validUntil && Number.isNaN(validUntil.getTime())) {
|
|
594
|
-
throw new UnprocessableEntityException({
|
|
595
|
-
code: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_UNTIL_INVALID,
|
|
596
|
-
message: `validUntil '${validUntilInput}' is not a valid date`,
|
|
597
|
-
params: {
|
|
598
|
-
validUntil: validUntilInput
|
|
599
|
-
}
|
|
600
|
-
});
|
|
601
|
-
}
|
|
602
|
-
if (validUntil && validUntil <= validFrom) {
|
|
603
|
-
throw new UnprocessableEntityException({
|
|
604
|
-
code: CATALOG_ERROR_CODES.BUNDLE_VERSION_VALID_UNTIL_BEFORE_FROM,
|
|
605
|
-
message: `validUntil (${validUntil.toISOString()}) must be strictly after validFrom (${validFrom.toISOString()}).`,
|
|
606
|
-
params: {
|
|
607
|
-
validUntil: validUntil.toISOString(),
|
|
608
|
-
validFrom: validFrom.toISOString()
|
|
609
|
-
}
|
|
610
|
-
});
|
|
611
|
-
}
|
|
653
|
+
const { validFrom, validUntil } = resolveValidityWindow(publishMeta, draft, previous, BUNDLE_VERSION_WINDOW_CODES);
|
|
612
654
|
const diff = previous ? classifyBundleVersionDiff({
|
|
613
655
|
features: previous.features,
|
|
614
656
|
quotas: previous.quotas,
|
|
@@ -624,8 +666,8 @@ var BundlesService = class _BundlesService {
|
|
|
624
666
|
nonRegressive: true
|
|
625
667
|
};
|
|
626
668
|
if (!diff.nonRegressive && !publishMeta.forceRegressive) {
|
|
627
|
-
throw new
|
|
628
|
-
code:
|
|
669
|
+
throw new UnprocessableEntityException2({
|
|
670
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_REGRESSION,
|
|
629
671
|
message: "This bundle version is regressive (feature removed / quota lowered / price raised). Publishing requires an explicit `forceRegressive: true` (UI confirmation dialog with MFA).",
|
|
630
672
|
changes: diff.changes
|
|
631
673
|
});
|
|
@@ -645,27 +687,71 @@ var BundlesService = class _BundlesService {
|
|
|
645
687
|
// =========================================================================
|
|
646
688
|
// Helper
|
|
647
689
|
// =========================================================================
|
|
648
|
-
async runStrictCheck(draft
|
|
690
|
+
async runStrictCheck(draft) {
|
|
649
691
|
const snapshot = resolveDiscoverySnapshot(this.snapshot, this.scanner);
|
|
650
692
|
if (!snapshot) return [];
|
|
651
693
|
const planIds = draft.compatibility?.planIds ?? [];
|
|
652
694
|
let knownPlanKeys = null;
|
|
653
|
-
if (planIds.length > 0 && this.planRepo
|
|
654
|
-
const all = await this.planRepo.list({
|
|
655
|
-
projectKey
|
|
656
|
-
});
|
|
695
|
+
if (planIds.length > 0 && this.planRepo) {
|
|
696
|
+
const all = await this.planRepo.list({});
|
|
657
697
|
knownPlanKeys = new Set(all.map((p) => p.planKey));
|
|
658
698
|
}
|
|
659
|
-
const approved = await loadApprovedCatalogKeys(this.catalogEntries
|
|
699
|
+
const approved = await loadApprovedCatalogKeys(this.catalogEntries);
|
|
660
700
|
return validateBundleDraft(draft, snapshot, knownPlanKeys, this.marketedOnly, approved);
|
|
661
701
|
}
|
|
702
|
+
/**
|
|
703
|
+
* Refuses a version that a plan it is offered to could not actually buy.
|
|
704
|
+
*
|
|
705
|
+
* "Some price resolves" is not the same as "this plan, in the rhythm it is
|
|
706
|
+
* sold in, resolves one". A bundle priced monthly only, offered to a plan
|
|
707
|
+
* that is sold yearly, passes the first test and fails the second — and the
|
|
708
|
+
* failure surfaces at a tenant's checkout rather than at the operator's
|
|
709
|
+
* desk, which is the wrong end of the mistake.
|
|
710
|
+
*
|
|
711
|
+
* The expectation comes from the catalog, not from a list: the plans are
|
|
712
|
+
* whichever the compatibility declares (empty = every plan), and the cycles
|
|
713
|
+
* are whichever each plan version carries a price for. Add a third cycle to
|
|
714
|
+
* the catalog one day and this asks about it without being told.
|
|
715
|
+
*
|
|
716
|
+
* Skipped where no plan repository is bound — a consumer without one has no
|
|
717
|
+
* catalog to derive from, and guessing would refuse valid bundles.
|
|
718
|
+
*/
|
|
719
|
+
async refuseUnsellableToACompatiblePlan(draft, versionId) {
|
|
720
|
+
if (!this.planRepo?.listVersions) return;
|
|
721
|
+
const declared = draft.compatibility?.planIds ?? [];
|
|
722
|
+
const plans = await this.planRepo.list({
|
|
723
|
+
onlyPublished: true
|
|
724
|
+
});
|
|
725
|
+
const offeredTo = plans.filter((plan) => declared.length === 0 || declared.includes(plan.planKey));
|
|
726
|
+
for (const plan of offeredTo) {
|
|
727
|
+
const live = await this.liveVersionOf(plan.planKey);
|
|
728
|
+
if (!live) continue;
|
|
729
|
+
for (const cycle of cyclesSoldFor(live)) {
|
|
730
|
+
if (resolveBundlePriceNet(draft, plan.planKey, cycle) !== null) continue;
|
|
731
|
+
throw new UnprocessableEntityException2({
|
|
732
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_NOT_PRICED_FOR_PLAN,
|
|
733
|
+
message: `This bundle is offered to plan '${plan.planKey}', which is sold ${cycle.toLowerCase()}, and no ${cycle.toLowerCase()} price resolves for it \u2014 neither a base price nor a plan override. Price it, or take that plan out of its compatibility.`,
|
|
734
|
+
params: {
|
|
735
|
+
versionId,
|
|
736
|
+
planKey: plan.planKey,
|
|
737
|
+
billingCycle: cycle
|
|
738
|
+
}
|
|
739
|
+
});
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
}
|
|
743
|
+
/** The live version of a plan, or null where the port cannot answer. */
|
|
744
|
+
async liveVersionOf(planKey) {
|
|
745
|
+
const versions = await this.planRepo?.listVersions?.(planKey) ?? [];
|
|
746
|
+
return versions.find((version) => version.publishedAt && !version.supersededAt) ?? null;
|
|
747
|
+
}
|
|
662
748
|
gateOrPass(warnings) {
|
|
663
749
|
const blocking = blockingStrictModeWarnings(warnings);
|
|
664
750
|
if (blocking.length === 0) return;
|
|
665
751
|
const hasHardBlockingWarning = blocking.some((w) => w.code === "BUNDLE_PLAN_KEY_UNKNOWN");
|
|
666
752
|
if (this.mode === "blocking" || hasHardBlockingWarning) {
|
|
667
|
-
throw new
|
|
668
|
-
code:
|
|
753
|
+
throw new UnprocessableEntityException2({
|
|
754
|
+
code: CATALOG_ERROR_CODES2.STRICT_MODE_VIOLATIONS,
|
|
669
755
|
message: "The strict-mode check found drift against the discovery snapshot.",
|
|
670
756
|
warnings
|
|
671
757
|
});
|
|
@@ -678,8 +764,8 @@ var BundlesService = class _BundlesService {
|
|
|
678
764
|
const validUntil = this.parseOptionalBundleDate(validUntilInput, "validUntil", "BUNDLE_VERSION_VALID_UNTIL_INVALID");
|
|
679
765
|
if (!validFromInput) {
|
|
680
766
|
if (existing.publishedAt !== null) {
|
|
681
|
-
throw new
|
|
682
|
-
code:
|
|
767
|
+
throw new UnprocessableEntityException2({
|
|
768
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_VALID_FROM_REQUIRED,
|
|
683
769
|
message: "Published bundle versions must keep a validFrom. Set a new future date, or edit the draft before publishing."
|
|
684
770
|
});
|
|
685
771
|
}
|
|
@@ -687,8 +773,8 @@ var BundlesService = class _BundlesService {
|
|
|
687
773
|
}
|
|
688
774
|
const validFrom = this.parseRequiredBundleDate(validFromInput, "validFrom", "BUNDLE_VERSION_VALID_FROM_INVALID");
|
|
689
775
|
if (existing.publishedAt !== null && validFrom.getTime() <= Date.now()) {
|
|
690
|
-
throw new
|
|
691
|
-
code:
|
|
776
|
+
throw new UnprocessableEntityException2({
|
|
777
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_VALID_FROM_NOT_FUTURE,
|
|
692
778
|
message: `validFrom (${validFrom.toISOString()}) must, for a published-but-future bundle version, still lie in the future.`,
|
|
693
779
|
params: {
|
|
694
780
|
validFrom: validFrom.toISOString()
|
|
@@ -696,8 +782,8 @@ var BundlesService = class _BundlesService {
|
|
|
696
782
|
});
|
|
697
783
|
}
|
|
698
784
|
if (validUntil && validUntil <= validFrom) {
|
|
699
|
-
throw new
|
|
700
|
-
code:
|
|
785
|
+
throw new UnprocessableEntityException2({
|
|
786
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_VALID_UNTIL_BEFORE_FROM,
|
|
701
787
|
message: `validUntil (${validUntil.toISOString()}) must be strictly after validFrom (${validFrom.toISOString()}).`,
|
|
702
788
|
params: {
|
|
703
789
|
validUntil: validUntil.toISOString(),
|
|
@@ -710,8 +796,8 @@ var BundlesService = class _BundlesService {
|
|
|
710
796
|
const previousValidFrom = new Date(previous.validFrom);
|
|
711
797
|
if (Number.isNaN(previousValidFrom.getTime())) return;
|
|
712
798
|
if (validFrom <= previousValidFrom) {
|
|
713
|
-
throw new
|
|
714
|
-
code:
|
|
799
|
+
throw new UnprocessableEntityException2({
|
|
800
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_VALID_FROM_NOT_AFTER_PREVIOUS,
|
|
715
801
|
message: `validFrom (${validFrom.toISOString()}) must be strictly after the validFrom of the previous version (${previous.validFrom}).`,
|
|
716
802
|
params: {
|
|
717
803
|
validFrom: validFrom.toISOString(),
|
|
@@ -725,8 +811,8 @@ var BundlesService = class _BundlesService {
|
|
|
725
811
|
const dayMs = 24 * 60 * 60 * 1e3;
|
|
726
812
|
const requiredStart = new Date(previousValidUntil.getTime() + dayMs);
|
|
727
813
|
if (validFrom.getTime() !== requiredStart.getTime()) {
|
|
728
|
-
throw new
|
|
729
|
-
code:
|
|
814
|
+
throw new UnprocessableEntityException2({
|
|
815
|
+
code: CATALOG_ERROR_CODES2.BUNDLE_VERSION_VALID_FROM_NOT_GAPLESS,
|
|
730
816
|
message: `The predecessor has validUntil=${previous.validUntil.slice(0, 10)} \u2014 the successor must start seamlessly on the next day (${requiredStart.toISOString().slice(0, 10)}). Received: ${validFrom.toISOString().slice(0, 10)}.`,
|
|
731
817
|
params: {
|
|
732
818
|
received: validFrom.toISOString().slice(0, 10)
|
|
@@ -747,7 +833,7 @@ var BundlesService = class _BundlesService {
|
|
|
747
833
|
parseRequiredBundleDate(input, field, code) {
|
|
748
834
|
const date = new Date(input);
|
|
749
835
|
if (Number.isNaN(date.getTime())) {
|
|
750
|
-
throw new
|
|
836
|
+
throw new UnprocessableEntityException2({
|
|
751
837
|
code,
|
|
752
838
|
message: `${field} '${input}' is not a valid date`,
|
|
753
839
|
// Keyed by the field name so these params match the publish
|
|
@@ -789,7 +875,7 @@ BundlesService = _ts_decorate([
|
|
|
789
875
|
|
|
790
876
|
// src/catalog/marketing-projections.service.ts
|
|
791
877
|
import { ConflictException, Inject as Inject2, Injectable as Injectable2, NotFoundException as NotFoundException2 } from "@nestjs/common";
|
|
792
|
-
import { CATALOG_ERROR_CODES as
|
|
878
|
+
import { CATALOG_ERROR_CODES as CATALOG_ERROR_CODES3 } from "@saasicat/core";
|
|
793
879
|
function _ts_decorate2(decorators, target, key, desc) {
|
|
794
880
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
795
881
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -822,7 +908,7 @@ var MarketingProjectionsService = class {
|
|
|
822
908
|
const row = await this.repo.findById(id);
|
|
823
909
|
if (!row) {
|
|
824
910
|
throw new NotFoundException2({
|
|
825
|
-
code:
|
|
911
|
+
code: CATALOG_ERROR_CODES3.MARKETING_PROJECTION_NOT_FOUND,
|
|
826
912
|
message: `MarketingProjection '${id}' not found`,
|
|
827
913
|
params: {
|
|
828
914
|
projectionId: id
|
|
@@ -836,7 +922,7 @@ var MarketingProjectionsService = class {
|
|
|
836
922
|
const existing = await this.repo.findByTarget(data.targetType, data.targetVersionId, locale);
|
|
837
923
|
if (existing) {
|
|
838
924
|
throw new ConflictException({
|
|
839
|
-
code:
|
|
925
|
+
code: CATALOG_ERROR_CODES3.MARKETING_PROJECTION_ALREADY_EXISTS,
|
|
840
926
|
message: `Marketing projection for ${data.targetType}/${data.targetVersionId}/${locale} already exists \u2014 use PATCH to edit it`,
|
|
841
927
|
params: {
|
|
842
928
|
targetType: data.targetType,
|
|
@@ -854,7 +940,7 @@ var MarketingProjectionsService = class {
|
|
|
854
940
|
const existing = await this.repo.findById(id);
|
|
855
941
|
if (!existing) {
|
|
856
942
|
throw new NotFoundException2({
|
|
857
|
-
code:
|
|
943
|
+
code: CATALOG_ERROR_CODES3.MARKETING_PROJECTION_NOT_FOUND,
|
|
858
944
|
message: `MarketingProjection '${id}' not found`,
|
|
859
945
|
params: {
|
|
860
946
|
projectionId: id
|
|
@@ -867,7 +953,7 @@ var MarketingProjectionsService = class {
|
|
|
867
953
|
const existing = await this.repo.findById(id);
|
|
868
954
|
if (!existing) {
|
|
869
955
|
throw new NotFoundException2({
|
|
870
|
-
code:
|
|
956
|
+
code: CATALOG_ERROR_CODES3.MARKETING_PROJECTION_NOT_FOUND,
|
|
871
957
|
message: `MarketingProjection '${id}' not found`,
|
|
872
958
|
params: {
|
|
873
959
|
projectionId: id
|
|
@@ -909,8 +995,8 @@ function keyListSegment(name, keys) {
|
|
|
909
995
|
__name(keyListSegment, "keyListSegment");
|
|
910
996
|
|
|
911
997
|
// src/catalog/catalog-entries.service.ts
|
|
912
|
-
import { Inject as Inject3, Injectable as Injectable3, Logger as Logger2, NotFoundException as NotFoundException3, Optional as Optional2, UnprocessableEntityException as
|
|
913
|
-
import { CATALOG_ERROR_CODES as
|
|
998
|
+
import { Inject as Inject3, Injectable as Injectable3, Logger as Logger2, NotFoundException as NotFoundException3, Optional as Optional2, UnprocessableEntityException as UnprocessableEntityException3 } from "@nestjs/common";
|
|
999
|
+
import { CATALOG_ERROR_CODES as CATALOG_ERROR_CODES4 } from "@saasicat/core";
|
|
914
1000
|
function _ts_decorate3(decorators, target, key, desc) {
|
|
915
1001
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
916
1002
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1009,21 +1095,18 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1009
1095
|
this.logger.error(`Discovery auto-sync on boot failed (${snapshot.app.key}) \u2014 the catalog may be stale; re-sync manually via POST /admin/catalog/discovery/sync`, err instanceof Error ? err.stack : String(err));
|
|
1010
1096
|
}
|
|
1011
1097
|
}
|
|
1012
|
-
listCapabilities(
|
|
1098
|
+
listCapabilities(codeStatus) {
|
|
1013
1099
|
return this.repo.listCapabilities({
|
|
1014
|
-
projectKey,
|
|
1015
1100
|
codeStatus
|
|
1016
1101
|
});
|
|
1017
1102
|
}
|
|
1018
|
-
listFeatures(
|
|
1103
|
+
listFeatures(discoveryStatus) {
|
|
1019
1104
|
return this.repo.listFeatures({
|
|
1020
|
-
projectKey,
|
|
1021
1105
|
discoveryStatus
|
|
1022
1106
|
});
|
|
1023
1107
|
}
|
|
1024
|
-
listQuotas(
|
|
1108
|
+
listQuotas(discoveryStatus) {
|
|
1025
1109
|
return this.repo.listQuotas({
|
|
1026
|
-
projectKey,
|
|
1027
1110
|
discoveryStatus
|
|
1028
1111
|
});
|
|
1029
1112
|
}
|
|
@@ -1033,38 +1116,36 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1033
1116
|
* snapshot, `pending` (revoke/reactivate) deletes it, `outdated`/
|
|
1034
1117
|
* `obsolete` keep the last approval as history.
|
|
1035
1118
|
*/
|
|
1036
|
-
async reviewFeature(
|
|
1037
|
-
const existing = await this.repo.findFeature(
|
|
1119
|
+
async reviewFeature(featureKey, data, reviewedBy) {
|
|
1120
|
+
const existing = await this.repo.findFeature(featureKey);
|
|
1038
1121
|
if (!existing) {
|
|
1039
1122
|
throw new NotFoundException3({
|
|
1040
|
-
code:
|
|
1041
|
-
message: `Feature '${featureKey}' not found
|
|
1123
|
+
code: CATALOG_ERROR_CODES4.FEATURE_NOT_FOUND,
|
|
1124
|
+
message: `Feature '${featureKey}' not found`,
|
|
1042
1125
|
params: {
|
|
1043
|
-
featureKey
|
|
1044
|
-
projectKey
|
|
1126
|
+
featureKey
|
|
1045
1127
|
}
|
|
1046
1128
|
});
|
|
1047
1129
|
}
|
|
1048
|
-
return this.repo.setFeatureReview(
|
|
1130
|
+
return this.repo.setFeatureReview(featureKey, this.resolveReviewUpdate(existing, data.discoveryStatus, reviewedBy, (snapshot) => featureApprovalSignature(featureKey, snapshot)));
|
|
1049
1131
|
}
|
|
1050
1132
|
/** Approval transition of a quota — same state machine as features (#20). */
|
|
1051
|
-
async reviewQuota(
|
|
1052
|
-
const existing = await this.repo.findQuota(
|
|
1133
|
+
async reviewQuota(quotaKey, data, reviewedBy) {
|
|
1134
|
+
const existing = await this.repo.findQuota(quotaKey);
|
|
1053
1135
|
if (!existing) {
|
|
1054
1136
|
throw new NotFoundException3({
|
|
1055
|
-
code:
|
|
1056
|
-
message: `Quota '${quotaKey}' not found
|
|
1137
|
+
code: CATALOG_ERROR_CODES4.QUOTA_NOT_FOUND,
|
|
1138
|
+
message: `Quota '${quotaKey}' not found`,
|
|
1057
1139
|
params: {
|
|
1058
|
-
quotaKey
|
|
1059
|
-
projectKey
|
|
1140
|
+
quotaKey
|
|
1060
1141
|
}
|
|
1061
1142
|
});
|
|
1062
1143
|
}
|
|
1063
|
-
return this.repo.setQuotaReview(
|
|
1144
|
+
return this.repo.setQuotaReview(quotaKey, this.resolveReviewUpdate(existing, data.discoveryStatus, reviewedBy, (snapshot) => {
|
|
1064
1145
|
const discovered = snapshot.quotas.find((q) => q.quotaKey === quotaKey);
|
|
1065
1146
|
if (!discovered) {
|
|
1066
|
-
throw new
|
|
1067
|
-
code:
|
|
1147
|
+
throw new UnprocessableEntityException3({
|
|
1148
|
+
code: CATALOG_ERROR_CODES4.QUOTA_NOT_IN_DISCOVERY_SNAPSHOT,
|
|
1068
1149
|
message: `Quota '${quotaKey}' is not in the discovery snapshot \u2014 a quota that is not declared in code cannot be approved`,
|
|
1069
1150
|
params: {
|
|
1070
1151
|
quotaKey
|
|
@@ -1076,8 +1157,8 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1076
1157
|
}
|
|
1077
1158
|
resolveReviewUpdate(existing, target, reviewedBy, signatureOf) {
|
|
1078
1159
|
if (!REVIEW_TRANSITIONS[existing.discoveryStatus].includes(target)) {
|
|
1079
|
-
throw new
|
|
1080
|
-
code:
|
|
1160
|
+
throw new UnprocessableEntityException3({
|
|
1161
|
+
code: CATALOG_ERROR_CODES4.DISCOVERY_STATUS_TRANSITION_INVALID,
|
|
1081
1162
|
message: `Transition '${existing.discoveryStatus}' \u2192 '${target}' is not allowed`,
|
|
1082
1163
|
params: {
|
|
1083
1164
|
from: existing.discoveryStatus,
|
|
@@ -1088,8 +1169,8 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1088
1169
|
if (target === "approved") {
|
|
1089
1170
|
const snapshot = resolveDiscoverySnapshot(this.snapshot, this.scanner);
|
|
1090
1171
|
if (!snapshot) {
|
|
1091
|
-
throw new
|
|
1092
|
-
code:
|
|
1172
|
+
throw new UnprocessableEntityException3({
|
|
1173
|
+
code: CATALOG_ERROR_CODES4.DISCOVERY_NOT_INITIALIZED,
|
|
1093
1174
|
message: "Approval requires a discovery snapshot \u2014 discovery is not initialised (#25)"
|
|
1094
1175
|
});
|
|
1095
1176
|
}
|
|
@@ -1108,30 +1189,27 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1108
1189
|
approvedSignature: keepApproval ? existing.approvedSignature : null
|
|
1109
1190
|
};
|
|
1110
1191
|
}
|
|
1111
|
-
setFeatureI18n(
|
|
1112
|
-
return this.repo.setFeatureI18n(
|
|
1192
|
+
setFeatureI18n(featureKey, i18n) {
|
|
1193
|
+
return this.repo.setFeatureI18n(featureKey, i18n);
|
|
1113
1194
|
}
|
|
1114
|
-
setQuotaI18n(
|
|
1115
|
-
return this.repo.setQuotaI18n(
|
|
1195
|
+
setQuotaI18n(quotaKey, i18n) {
|
|
1196
|
+
return this.repo.setQuotaI18n(quotaKey, i18n);
|
|
1116
1197
|
}
|
|
1117
1198
|
/** Sets the editable default-locale label/description of a feature. */
|
|
1118
|
-
setFeatureBase(
|
|
1119
|
-
return this.repo.setFeatureBase(
|
|
1199
|
+
setFeatureBase(featureKey, data) {
|
|
1200
|
+
return this.repo.setFeatureBase(featureKey, data);
|
|
1120
1201
|
}
|
|
1121
1202
|
/** Sets the editable default-locale label/description of a quota. */
|
|
1122
|
-
setQuotaBase(
|
|
1123
|
-
return this.repo.setQuotaBase(
|
|
1203
|
+
setQuotaBase(quotaKey, data) {
|
|
1204
|
+
return this.repo.setQuotaBase(quotaKey, data);
|
|
1124
1205
|
}
|
|
1125
1206
|
/**
|
|
1126
1207
|
* Synchronizes the catalog entries from a discovery snapshot.
|
|
1127
1208
|
* Idempotent — can run against the same snapshot any number of times.
|
|
1128
1209
|
*/
|
|
1129
1210
|
async syncFromSnapshot(snapshot) {
|
|
1130
|
-
const projectKey = snapshot.app.key;
|
|
1131
1211
|
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
|
|
1132
|
-
const existingCaps = await this.repo.listCapabilities({
|
|
1133
|
-
projectKey
|
|
1134
|
-
});
|
|
1212
|
+
const existingCaps = await this.repo.listCapabilities({});
|
|
1135
1213
|
const capByKey = new Map(existingCaps.map((c) => [
|
|
1136
1214
|
c.capabilityKey,
|
|
1137
1215
|
c
|
|
@@ -1144,7 +1222,6 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1144
1222
|
const existing = capByKey.get(cap.capabilityKey);
|
|
1145
1223
|
if (!existing || existing.codeStatus === "retired") capDiscovered++;
|
|
1146
1224
|
await this.repo.upsertCapability({
|
|
1147
|
-
projectKey,
|
|
1148
1225
|
capabilityKey: cap.capabilityKey,
|
|
1149
1226
|
label: cap.label ?? cap.capabilityKey,
|
|
1150
1227
|
description: existing?.description ?? null,
|
|
@@ -1159,10 +1236,8 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1159
1236
|
reason: cap.reason ?? existing?.reason ?? null
|
|
1160
1237
|
});
|
|
1161
1238
|
}
|
|
1162
|
-
const capRetired = await this.repo.retireMissing(
|
|
1163
|
-
const existingFeatures = await this.repo.listFeatures({
|
|
1164
|
-
projectKey
|
|
1165
|
-
});
|
|
1239
|
+
const capRetired = await this.repo.retireMissing("capability", presentCaps);
|
|
1240
|
+
const existingFeatures = await this.repo.listFeatures({});
|
|
1166
1241
|
const featureByKey = new Map(existingFeatures.map((f) => [
|
|
1167
1242
|
f.featureKey,
|
|
1168
1243
|
f
|
|
@@ -1181,7 +1256,6 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1181
1256
|
const meta = this.featureUiRegistry?.[feature.featureKey];
|
|
1182
1257
|
const hasCuratedLabel = existing != null && existing.label !== feature.featureKey;
|
|
1183
1258
|
await this.repo.upsertFeature({
|
|
1184
|
-
projectKey,
|
|
1185
1259
|
featureKey: feature.featureKey,
|
|
1186
1260
|
label: hasCuratedLabel ? existing.label : meta?.label ?? feature.featureKey,
|
|
1187
1261
|
description: existing?.description ?? meta?.description ?? null,
|
|
@@ -1191,22 +1265,20 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1191
1265
|
core: meta?.core ?? false
|
|
1192
1266
|
});
|
|
1193
1267
|
if (meta?.icon && !existing?.icon) {
|
|
1194
|
-
await this.repo.setFeatureBase(
|
|
1268
|
+
await this.repo.setFeatureBase(feature.featureKey, {
|
|
1195
1269
|
icon: meta.icon
|
|
1196
1270
|
});
|
|
1197
1271
|
}
|
|
1198
1272
|
}
|
|
1199
|
-
const featureRetired = await this.repo.retireMissing(
|
|
1200
|
-
const featureReplaced = await this.applySuccessorPointers("feature",
|
|
1273
|
+
const featureRetired = await this.repo.retireMissing("feature", presentFeatures);
|
|
1274
|
+
const featureReplaced = await this.applySuccessorPointers("feature", existingFeatures.map((f) => ({
|
|
1201
1275
|
key: f.featureKey,
|
|
1202
1276
|
successorKey: f.successorKey
|
|
1203
1277
|
})), new Set(presentFeatures), buildSuccessorIndex(snapshot.features.map((f) => ({
|
|
1204
1278
|
key: f.featureKey,
|
|
1205
1279
|
replaces: f.replaces
|
|
1206
1280
|
}))));
|
|
1207
|
-
const existingQuotas = await this.repo.listQuotas({
|
|
1208
|
-
projectKey
|
|
1209
|
-
});
|
|
1281
|
+
const existingQuotas = await this.repo.listQuotas({});
|
|
1210
1282
|
const quotaByKey = new Map(existingQuotas.map((q) => [
|
|
1211
1283
|
q.quotaKey,
|
|
1212
1284
|
q
|
|
@@ -1223,7 +1295,6 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1223
1295
|
quotaOutdated++;
|
|
1224
1296
|
}
|
|
1225
1297
|
await this.repo.upsertQuota({
|
|
1226
|
-
projectKey,
|
|
1227
1298
|
quotaKey: quota.quotaKey,
|
|
1228
1299
|
label: quota.label,
|
|
1229
1300
|
description: existing?.description ?? null,
|
|
@@ -1235,8 +1306,8 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1235
1306
|
replaces: quota.replaces ?? []
|
|
1236
1307
|
});
|
|
1237
1308
|
}
|
|
1238
|
-
const quotaRetired = await this.repo.retireMissing(
|
|
1239
|
-
const quotaReplaced = await this.applySuccessorPointers("quota",
|
|
1309
|
+
const quotaRetired = await this.repo.retireMissing("quota", presentQuotas);
|
|
1310
|
+
const quotaReplaced = await this.applySuccessorPointers("quota", existingQuotas.map((q) => ({
|
|
1240
1311
|
key: q.quotaKey,
|
|
1241
1312
|
successorKey: q.successorKey
|
|
1242
1313
|
})), new Set(presentQuotas), buildSuccessorIndex(snapshot.quotas.map((q) => ({
|
|
@@ -1273,7 +1344,7 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1273
1344
|
* the code, an existing pointer is deleted. A missing key
|
|
1274
1345
|
* without a claimant remains untouched (deleted without replacement).
|
|
1275
1346
|
*/
|
|
1276
|
-
async applySuccessorPointers(type,
|
|
1347
|
+
async applySuccessorPointers(type, existing, presentKeys, claimantsByOldKey) {
|
|
1277
1348
|
const changes = [];
|
|
1278
1349
|
for (const entry of existing) {
|
|
1279
1350
|
const current = entry.successorKey ?? null;
|
|
@@ -1302,7 +1373,7 @@ var CatalogEntriesService = class _CatalogEntriesService {
|
|
|
1302
1373
|
}
|
|
1303
1374
|
let replaced = 0;
|
|
1304
1375
|
for (const change of changes) {
|
|
1305
|
-
await setSuccessor(
|
|
1376
|
+
await setSuccessor(change.key, change.successorKey);
|
|
1306
1377
|
if (change.successorKey !== null) replaced++;
|
|
1307
1378
|
}
|
|
1308
1379
|
return replaced;
|
|
@@ -1330,7 +1401,7 @@ CatalogEntriesService = _ts_decorate3([
|
|
|
1330
1401
|
], CatalogEntriesService);
|
|
1331
1402
|
|
|
1332
1403
|
// src/catalog/dto/catalog-entries.dto.ts
|
|
1333
|
-
import { IsIn, IsObject, IsOptional, IsString,
|
|
1404
|
+
import { IsIn, IsObject, IsOptional, IsString, MaxLength } from "class-validator";
|
|
1334
1405
|
function _ts_decorate4(decorators, target, key, desc) {
|
|
1335
1406
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1336
1407
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1342,7 +1413,6 @@ function _ts_metadata4(k, v) {
|
|
|
1342
1413
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1343
1414
|
}
|
|
1344
1415
|
__name(_ts_metadata4, "_ts_metadata");
|
|
1345
|
-
var PROJECT_KEY_PATTERN = /^[a-z][a-z0-9-]*$/;
|
|
1346
1416
|
var REVIEW_STATUSES = [
|
|
1347
1417
|
"pending",
|
|
1348
1418
|
"approved",
|
|
@@ -1359,19 +1429,11 @@ var ListCatalogEntriesQueryDto = class {
|
|
|
1359
1429
|
static {
|
|
1360
1430
|
__name(this, "ListCatalogEntriesQueryDto");
|
|
1361
1431
|
}
|
|
1362
|
-
projectKey;
|
|
1363
1432
|
/** Filter for features/quotas (approval lifecycle). */
|
|
1364
1433
|
discoveryStatus;
|
|
1365
1434
|
/** Filter for capabilities (read-only code facts). */
|
|
1366
1435
|
codeStatus;
|
|
1367
1436
|
};
|
|
1368
|
-
_ts_decorate4([
|
|
1369
|
-
IsString(),
|
|
1370
|
-
Matches(PROJECT_KEY_PATTERN, {
|
|
1371
|
-
message: "projectKey must be kebab-case"
|
|
1372
|
-
}),
|
|
1373
|
-
_ts_metadata4("design:type", String)
|
|
1374
|
-
], ListCatalogEntriesQueryDto.prototype, "projectKey", void 0);
|
|
1375
1437
|
_ts_decorate4([
|
|
1376
1438
|
IsOptional(),
|
|
1377
1439
|
IsString(),
|
|
@@ -1476,6 +1538,21 @@ function _ts_param4(paramIndex, decorator) {
|
|
|
1476
1538
|
}
|
|
1477
1539
|
__name(_ts_param4, "_ts_param");
|
|
1478
1540
|
var DEFAULT_CONTEXT = "admin";
|
|
1541
|
+
function defaultActorFromRequest(req) {
|
|
1542
|
+
const request = req;
|
|
1543
|
+
const sid = request.headers?.["x-session-id"];
|
|
1544
|
+
return {
|
|
1545
|
+
userId: request.user?.sub ?? request.user?.id ?? "unknown",
|
|
1546
|
+
email: request.user?.email ?? "unknown",
|
|
1547
|
+
source: "web",
|
|
1548
|
+
context: (Array.isArray(sid) ? sid[0] : sid) ?? DEFAULT_CONTEXT
|
|
1549
|
+
};
|
|
1550
|
+
}
|
|
1551
|
+
__name(defaultActorFromRequest, "defaultActorFromRequest");
|
|
1552
|
+
function actorTagOf(actor) {
|
|
1553
|
+
return `${actor.source}:${actor.email}:${actor.context}`;
|
|
1554
|
+
}
|
|
1555
|
+
__name(actorTagOf, "actorTagOf");
|
|
1479
1556
|
var WebAuditLogger = class {
|
|
1480
1557
|
static {
|
|
1481
1558
|
__name(this, "WebAuditLogger");
|
|
@@ -1512,6 +1589,15 @@ var WebAuditLogger = class {
|
|
|
1512
1589
|
context
|
|
1513
1590
|
};
|
|
1514
1591
|
}
|
|
1592
|
+
/**
|
|
1593
|
+
* The actor behind a request as the audit log writes it —
|
|
1594
|
+
* `web:<email>:<context>` — for a column that records who did something.
|
|
1595
|
+
* The same derivation as the log line, so the two never disagree about who
|
|
1596
|
+
* it was.
|
|
1597
|
+
*/
|
|
1598
|
+
actorTagFromRequest(req) {
|
|
1599
|
+
return actorTagOf(this.buildActor(req));
|
|
1600
|
+
}
|
|
1515
1601
|
/**
|
|
1516
1602
|
* Best-effort audit log from a web request. Writes nothing and never
|
|
1517
1603
|
* throws when no AdminAuditService is injected; write errors are
|
|
@@ -1580,45 +1666,45 @@ function buildCatalogEntriesController(guards) {
|
|
|
1580
1666
|
this.audit = audit;
|
|
1581
1667
|
}
|
|
1582
1668
|
listCapabilities(query) {
|
|
1583
|
-
return this.service.listCapabilities(query.
|
|
1669
|
+
return this.service.listCapabilities(query.codeStatus);
|
|
1584
1670
|
}
|
|
1585
1671
|
listFeatures(query) {
|
|
1586
|
-
return this.service.listFeatures(query.
|
|
1672
|
+
return this.service.listFeatures(query.discoveryStatus);
|
|
1587
1673
|
}
|
|
1588
1674
|
listQuotas(query) {
|
|
1589
|
-
return this.service.listQuotas(query.
|
|
1675
|
+
return this.service.listQuotas(query.discoveryStatus);
|
|
1590
1676
|
}
|
|
1591
|
-
async reviewFeature(req, featureKey,
|
|
1592
|
-
const row = await this.service.reviewFeature(
|
|
1593
|
-
await this.audit?.logFromRequest(req, "CatalogFeatureEntry",
|
|
1677
|
+
async reviewFeature(req, featureKey, dto) {
|
|
1678
|
+
const row = await this.service.reviewFeature(featureKey, dto, this.audit?.resolveUserId(req) ?? null);
|
|
1679
|
+
await this.audit?.logFromRequest(req, "CatalogFeatureEntry", featureKey, "REVIEW_FEATURE", {
|
|
1594
1680
|
discoveryStatus: dto.discoveryStatus
|
|
1595
1681
|
});
|
|
1596
1682
|
return row;
|
|
1597
1683
|
}
|
|
1598
|
-
async reviewQuota(req, quotaKey,
|
|
1599
|
-
const row = await this.service.reviewQuota(
|
|
1600
|
-
await this.audit?.logFromRequest(req, "CatalogQuotaEntry",
|
|
1684
|
+
async reviewQuota(req, quotaKey, dto) {
|
|
1685
|
+
const row = await this.service.reviewQuota(quotaKey, dto, this.audit?.resolveUserId(req) ?? null);
|
|
1686
|
+
await this.audit?.logFromRequest(req, "CatalogQuotaEntry", quotaKey, "REVIEW_QUOTA", {
|
|
1601
1687
|
discoveryStatus: dto.discoveryStatus
|
|
1602
1688
|
});
|
|
1603
1689
|
return row;
|
|
1604
1690
|
}
|
|
1605
|
-
async setFeatureI18n(req, featureKey,
|
|
1606
|
-
const row = await this.service.setFeatureI18n(
|
|
1607
|
-
await this.audit?.logFromRequest(req, "CatalogFeatureEntry",
|
|
1691
|
+
async setFeatureI18n(req, featureKey, dto) {
|
|
1692
|
+
const row = await this.service.setFeatureI18n(featureKey, dto.i18n);
|
|
1693
|
+
await this.audit?.logFromRequest(req, "CatalogFeatureEntry", featureKey, "SET_FEATURE_I18N", {
|
|
1608
1694
|
locales: Object.keys(dto.i18n ?? {})
|
|
1609
1695
|
});
|
|
1610
1696
|
return row;
|
|
1611
1697
|
}
|
|
1612
|
-
async setQuotaI18n(req, quotaKey,
|
|
1613
|
-
const row = await this.service.setQuotaI18n(
|
|
1614
|
-
await this.audit?.logFromRequest(req, "CatalogQuotaEntry",
|
|
1698
|
+
async setQuotaI18n(req, quotaKey, dto) {
|
|
1699
|
+
const row = await this.service.setQuotaI18n(quotaKey, dto.i18n);
|
|
1700
|
+
await this.audit?.logFromRequest(req, "CatalogQuotaEntry", quotaKey, "SET_QUOTA_I18N", {
|
|
1615
1701
|
locales: Object.keys(dto.i18n ?? {})
|
|
1616
1702
|
});
|
|
1617
1703
|
return row;
|
|
1618
1704
|
}
|
|
1619
|
-
async setFeatureBase(req, featureKey,
|
|
1620
|
-
const row = await this.service.setFeatureBase(
|
|
1621
|
-
await this.audit?.logFromRequest(req, "CatalogFeatureEntry",
|
|
1705
|
+
async setFeatureBase(req, featureKey, dto) {
|
|
1706
|
+
const row = await this.service.setFeatureBase(featureKey, dto);
|
|
1707
|
+
await this.audit?.logFromRequest(req, "CatalogFeatureEntry", featureKey, "SET_FEATURE_BASE", {
|
|
1622
1708
|
label: dto.label,
|
|
1623
1709
|
description: dto.description,
|
|
1624
1710
|
icon: dto.icon,
|
|
@@ -1626,9 +1712,9 @@ function buildCatalogEntriesController(guards) {
|
|
|
1626
1712
|
});
|
|
1627
1713
|
return row;
|
|
1628
1714
|
}
|
|
1629
|
-
async setQuotaBase(req, quotaKey,
|
|
1630
|
-
const row = await this.service.setQuotaBase(
|
|
1631
|
-
await this.audit?.logFromRequest(req, "CatalogQuotaEntry",
|
|
1715
|
+
async setQuotaBase(req, quotaKey, dto) {
|
|
1716
|
+
const row = await this.service.setQuotaBase(quotaKey, dto);
|
|
1717
|
+
await this.audit?.logFromRequest(req, "CatalogQuotaEntry", quotaKey, "SET_QUOTA_BASE", {
|
|
1632
1718
|
label: dto.label,
|
|
1633
1719
|
description: dto.description
|
|
1634
1720
|
});
|
|
@@ -1676,13 +1762,11 @@ function buildCatalogEntriesController(guards) {
|
|
|
1676
1762
|
Patch("features/:key/review"),
|
|
1677
1763
|
_ts_param5(0, Req()),
|
|
1678
1764
|
_ts_param5(1, Param("key")),
|
|
1679
|
-
_ts_param5(2,
|
|
1680
|
-
_ts_param5(3, Body()),
|
|
1765
|
+
_ts_param5(2, Body()),
|
|
1681
1766
|
_ts_metadata6("design:type", Function),
|
|
1682
1767
|
_ts_metadata6("design:paramtypes", [
|
|
1683
1768
|
Object,
|
|
1684
1769
|
String,
|
|
1685
|
-
String,
|
|
1686
1770
|
typeof ReviewCatalogEntryDto === "undefined" ? Object : ReviewCatalogEntryDto
|
|
1687
1771
|
]),
|
|
1688
1772
|
_ts_metadata6("design:returntype", Promise)
|
|
@@ -1691,13 +1775,11 @@ function buildCatalogEntriesController(guards) {
|
|
|
1691
1775
|
Patch("quotas/:key/review"),
|
|
1692
1776
|
_ts_param5(0, Req()),
|
|
1693
1777
|
_ts_param5(1, Param("key")),
|
|
1694
|
-
_ts_param5(2,
|
|
1695
|
-
_ts_param5(3, Body()),
|
|
1778
|
+
_ts_param5(2, Body()),
|
|
1696
1779
|
_ts_metadata6("design:type", Function),
|
|
1697
1780
|
_ts_metadata6("design:paramtypes", [
|
|
1698
1781
|
Object,
|
|
1699
1782
|
String,
|
|
1700
|
-
String,
|
|
1701
1783
|
typeof ReviewCatalogEntryDto === "undefined" ? Object : ReviewCatalogEntryDto
|
|
1702
1784
|
]),
|
|
1703
1785
|
_ts_metadata6("design:returntype", Promise)
|
|
@@ -1706,13 +1788,11 @@ function buildCatalogEntriesController(guards) {
|
|
|
1706
1788
|
Patch("features/:key/i18n"),
|
|
1707
1789
|
_ts_param5(0, Req()),
|
|
1708
1790
|
_ts_param5(1, Param("key")),
|
|
1709
|
-
_ts_param5(2,
|
|
1710
|
-
_ts_param5(3, Body()),
|
|
1791
|
+
_ts_param5(2, Body()),
|
|
1711
1792
|
_ts_metadata6("design:type", Function),
|
|
1712
1793
|
_ts_metadata6("design:paramtypes", [
|
|
1713
1794
|
Object,
|
|
1714
1795
|
String,
|
|
1715
|
-
String,
|
|
1716
1796
|
typeof UpdateCatalogEntryI18nDto === "undefined" ? Object : UpdateCatalogEntryI18nDto
|
|
1717
1797
|
]),
|
|
1718
1798
|
_ts_metadata6("design:returntype", Promise)
|
|
@@ -1721,13 +1801,11 @@ function buildCatalogEntriesController(guards) {
|
|
|
1721
1801
|
Patch("quotas/:key/i18n"),
|
|
1722
1802
|
_ts_param5(0, Req()),
|
|
1723
1803
|
_ts_param5(1, Param("key")),
|
|
1724
|
-
_ts_param5(2,
|
|
1725
|
-
_ts_param5(3, Body()),
|
|
1804
|
+
_ts_param5(2, Body()),
|
|
1726
1805
|
_ts_metadata6("design:type", Function),
|
|
1727
1806
|
_ts_metadata6("design:paramtypes", [
|
|
1728
1807
|
Object,
|
|
1729
1808
|
String,
|
|
1730
|
-
String,
|
|
1731
1809
|
typeof UpdateCatalogEntryI18nDto === "undefined" ? Object : UpdateCatalogEntryI18nDto
|
|
1732
1810
|
]),
|
|
1733
1811
|
_ts_metadata6("design:returntype", Promise)
|
|
@@ -1736,13 +1814,11 @@ function buildCatalogEntriesController(guards) {
|
|
|
1736
1814
|
Patch("features/:key"),
|
|
1737
1815
|
_ts_param5(0, Req()),
|
|
1738
1816
|
_ts_param5(1, Param("key")),
|
|
1739
|
-
_ts_param5(2,
|
|
1740
|
-
_ts_param5(3, Body()),
|
|
1817
|
+
_ts_param5(2, Body()),
|
|
1741
1818
|
_ts_metadata6("design:type", Function),
|
|
1742
1819
|
_ts_metadata6("design:paramtypes", [
|
|
1743
1820
|
Object,
|
|
1744
1821
|
String,
|
|
1745
|
-
String,
|
|
1746
1822
|
typeof UpdateCatalogEntryBaseDto === "undefined" ? Object : UpdateCatalogEntryBaseDto
|
|
1747
1823
|
]),
|
|
1748
1824
|
_ts_metadata6("design:returntype", Promise)
|
|
@@ -1751,13 +1827,11 @@ function buildCatalogEntriesController(guards) {
|
|
|
1751
1827
|
Patch("quotas/:key"),
|
|
1752
1828
|
_ts_param5(0, Req()),
|
|
1753
1829
|
_ts_param5(1, Param("key")),
|
|
1754
|
-
_ts_param5(2,
|
|
1755
|
-
_ts_param5(3, Body()),
|
|
1830
|
+
_ts_param5(2, Body()),
|
|
1756
1831
|
_ts_metadata6("design:type", Function),
|
|
1757
1832
|
_ts_metadata6("design:paramtypes", [
|
|
1758
1833
|
Object,
|
|
1759
1834
|
String,
|
|
1760
|
-
String,
|
|
1761
1835
|
typeof UpdateCatalogEntryBaseDto === "undefined" ? Object : UpdateCatalogEntryBaseDto
|
|
1762
1836
|
]),
|
|
1763
1837
|
_ts_metadata6("design:returntype", Promise)
|
|
@@ -1791,7 +1865,47 @@ __name(buildCatalogEntriesController, "buildCatalogEntriesController");
|
|
|
1791
1865
|
|
|
1792
1866
|
// src/catalog/promotions.service.ts
|
|
1793
1867
|
import { Inject as Inject6, Injectable as Injectable5, NotFoundException as NotFoundException4 } from "@nestjs/common";
|
|
1794
|
-
import { CATALOG_ERROR_CODES as
|
|
1868
|
+
import { CATALOG_ERROR_CODES as CATALOG_ERROR_CODES6 } from "@saasicat/core";
|
|
1869
|
+
|
|
1870
|
+
// src/catalog/promotion-value.ts
|
|
1871
|
+
import { UnprocessableEntityException as UnprocessableEntityException4 } from "@nestjs/common";
|
|
1872
|
+
import { CATALOG_ERROR_CODES as CATALOG_ERROR_CODES5 } from "@saasicat/core";
|
|
1873
|
+
function assertPromotionValue(type, value) {
|
|
1874
|
+
if (takesValue(type, value)) return;
|
|
1875
|
+
throw new UnprocessableEntityException4({
|
|
1876
|
+
code: CATALOG_ERROR_CODES5.PROMOTION_VALUE_INVALID,
|
|
1877
|
+
message: `The value of a '${type}' promotion is not one its type takes: a percentage above 0 and at most 100, an amount above 0, an intro price of at least 0 for a whole number of months, or a whole number of free months.`,
|
|
1878
|
+
params: {
|
|
1879
|
+
type
|
|
1880
|
+
}
|
|
1881
|
+
});
|
|
1882
|
+
}
|
|
1883
|
+
__name(assertPromotionValue, "assertPromotionValue");
|
|
1884
|
+
function takesValue(type, value) {
|
|
1885
|
+
switch (type) {
|
|
1886
|
+
case "percent":
|
|
1887
|
+
return isAmount(value) && value > 0 && value <= 100;
|
|
1888
|
+
case "amount":
|
|
1889
|
+
return isAmount(value) && value > 0;
|
|
1890
|
+
case "intro":
|
|
1891
|
+
return typeof value === "object" && value !== null && isAmount(value.price) && value.price >= 0 && isMonths(value.months);
|
|
1892
|
+
case "freeMonths":
|
|
1893
|
+
return isMonths(value);
|
|
1894
|
+
default:
|
|
1895
|
+
return false;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
__name(takesValue, "takesValue");
|
|
1899
|
+
function isAmount(value) {
|
|
1900
|
+
return typeof value === "number" && Number.isFinite(value);
|
|
1901
|
+
}
|
|
1902
|
+
__name(isAmount, "isAmount");
|
|
1903
|
+
function isMonths(value) {
|
|
1904
|
+
return Number.isInteger(value) && value >= 1;
|
|
1905
|
+
}
|
|
1906
|
+
__name(isMonths, "isMonths");
|
|
1907
|
+
|
|
1908
|
+
// src/catalog/promotions.service.ts
|
|
1795
1909
|
function _ts_decorate7(decorators, target, key, desc) {
|
|
1796
1910
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1797
1911
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1817,16 +1931,14 @@ var PromotionsService = class {
|
|
|
1817
1931
|
constructor(repo) {
|
|
1818
1932
|
this.repo = repo;
|
|
1819
1933
|
}
|
|
1820
|
-
list(
|
|
1821
|
-
return this.repo.list(
|
|
1822
|
-
projectKey
|
|
1823
|
-
});
|
|
1934
|
+
list() {
|
|
1935
|
+
return this.repo.list();
|
|
1824
1936
|
}
|
|
1825
1937
|
async getById(id) {
|
|
1826
1938
|
const row = await this.repo.findById(id);
|
|
1827
1939
|
if (!row) {
|
|
1828
1940
|
throw new NotFoundException4({
|
|
1829
|
-
code:
|
|
1941
|
+
code: CATALOG_ERROR_CODES6.PROMOTION_NOT_FOUND,
|
|
1830
1942
|
message: `Promotion '${id}' not found`,
|
|
1831
1943
|
params: {
|
|
1832
1944
|
promotionId: id
|
|
@@ -1835,27 +1947,29 @@ var PromotionsService = class {
|
|
|
1835
1947
|
}
|
|
1836
1948
|
return row;
|
|
1837
1949
|
}
|
|
1838
|
-
create(data) {
|
|
1950
|
+
async create(data) {
|
|
1951
|
+
assertPromotionValue(data.type, data.value);
|
|
1839
1952
|
return this.repo.create(data);
|
|
1840
1953
|
}
|
|
1841
1954
|
async update(id, data) {
|
|
1842
1955
|
const existing = await this.repo.findById(id);
|
|
1843
1956
|
if (!existing) {
|
|
1844
1957
|
throw new NotFoundException4({
|
|
1845
|
-
code:
|
|
1958
|
+
code: CATALOG_ERROR_CODES6.PROMOTION_NOT_FOUND,
|
|
1846
1959
|
message: `Promotion '${id}' not found`,
|
|
1847
1960
|
params: {
|
|
1848
1961
|
promotionId: id
|
|
1849
1962
|
}
|
|
1850
1963
|
});
|
|
1851
1964
|
}
|
|
1965
|
+
assertPromotionValue(data.type === void 0 ? existing.type : data.type, data.value === void 0 ? existing.value : data.value);
|
|
1852
1966
|
return this.repo.update(id, data);
|
|
1853
1967
|
}
|
|
1854
1968
|
async delete(id) {
|
|
1855
1969
|
const existing = await this.repo.findById(id);
|
|
1856
1970
|
if (!existing) {
|
|
1857
1971
|
throw new NotFoundException4({
|
|
1858
|
-
code:
|
|
1972
|
+
code: CATALOG_ERROR_CODES6.PROMOTION_NOT_FOUND,
|
|
1859
1973
|
message: `Promotion '${id}' not found`,
|
|
1860
1974
|
params: {
|
|
1861
1975
|
promotionId: id
|
|
@@ -1875,7 +1989,7 @@ PromotionsService = _ts_decorate7([
|
|
|
1875
1989
|
], PromotionsService);
|
|
1876
1990
|
|
|
1877
1991
|
// src/catalog/dto/promotions.dto.ts
|
|
1878
|
-
import { IsArray, IsBoolean, IsDefined, IsIn as IsIn2, IsInt, IsObject as IsObject2, IsOptional as IsOptional2, IsString as IsString2, Matches
|
|
1992
|
+
import { IsArray, IsBoolean, IsDefined, IsIn as IsIn2, IsInt, IsObject as IsObject2, IsOptional as IsOptional2, IsString as IsString2, Matches, MaxLength as MaxLength2 } from "class-validator";
|
|
1879
1993
|
function _ts_decorate8(decorators, target, key, desc) {
|
|
1880
1994
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
1881
1995
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -1887,7 +2001,6 @@ function _ts_metadata8(k, v) {
|
|
|
1887
2001
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1888
2002
|
}
|
|
1889
2003
|
__name(_ts_metadata8, "_ts_metadata");
|
|
1890
|
-
var PROJECT_KEY_PATTERN2 = /^[a-z][a-z0-9-]*$/;
|
|
1891
2004
|
var ISO_DATE_PATTERN = /^\d{4}-\d{2}-\d{2}/;
|
|
1892
2005
|
var TYPES = [
|
|
1893
2006
|
"percent",
|
|
@@ -1905,24 +2018,10 @@ var TARGET_TYPES = [
|
|
|
1905
2018
|
"BUNDLE",
|
|
1906
2019
|
"OFFER"
|
|
1907
2020
|
];
|
|
1908
|
-
var ListPromotionsQueryDto = class {
|
|
1909
|
-
static {
|
|
1910
|
-
__name(this, "ListPromotionsQueryDto");
|
|
1911
|
-
}
|
|
1912
|
-
projectKey;
|
|
1913
|
-
};
|
|
1914
|
-
_ts_decorate8([
|
|
1915
|
-
IsString2(),
|
|
1916
|
-
Matches2(PROJECT_KEY_PATTERN2, {
|
|
1917
|
-
message: "projectKey must be kebab-case"
|
|
1918
|
-
}),
|
|
1919
|
-
_ts_metadata8("design:type", String)
|
|
1920
|
-
], ListPromotionsQueryDto.prototype, "projectKey", void 0);
|
|
1921
2021
|
var CreatePromotionDto = class {
|
|
1922
2022
|
static {
|
|
1923
2023
|
__name(this, "CreatePromotionDto");
|
|
1924
2024
|
}
|
|
1925
|
-
projectKey;
|
|
1926
2025
|
internalLabel;
|
|
1927
2026
|
type;
|
|
1928
2027
|
/** Type-dependent: number | { price, months }. */
|
|
@@ -1939,12 +2038,6 @@ var CreatePromotionDto = class {
|
|
|
1939
2038
|
color;
|
|
1940
2039
|
i18n;
|
|
1941
2040
|
};
|
|
1942
|
-
_ts_decorate8([
|
|
1943
|
-
IsString2(),
|
|
1944
|
-
Matches2(PROJECT_KEY_PATTERN2),
|
|
1945
|
-
MaxLength2(64),
|
|
1946
|
-
_ts_metadata8("design:type", String)
|
|
1947
|
-
], CreatePromotionDto.prototype, "projectKey", void 0);
|
|
1948
2041
|
_ts_decorate8([
|
|
1949
2042
|
IsString2(),
|
|
1950
2043
|
MaxLength2(120),
|
|
@@ -1981,14 +2074,14 @@ _ts_decorate8([
|
|
|
1981
2074
|
], CreatePromotionDto.prototype, "billingCycle", void 0);
|
|
1982
2075
|
_ts_decorate8([
|
|
1983
2076
|
IsString2(),
|
|
1984
|
-
|
|
2077
|
+
Matches(ISO_DATE_PATTERN, {
|
|
1985
2078
|
message: "validFrom must be an ISO date"
|
|
1986
2079
|
}),
|
|
1987
2080
|
_ts_metadata8("design:type", String)
|
|
1988
2081
|
], CreatePromotionDto.prototype, "validFrom", void 0);
|
|
1989
2082
|
_ts_decorate8([
|
|
1990
2083
|
IsString2(),
|
|
1991
|
-
|
|
2084
|
+
Matches(ISO_DATE_PATTERN, {
|
|
1992
2085
|
message: "validTo must be an ISO date"
|
|
1993
2086
|
}),
|
|
1994
2087
|
_ts_metadata8("design:type", String)
|
|
@@ -2088,13 +2181,13 @@ _ts_decorate8([
|
|
|
2088
2181
|
_ts_decorate8([
|
|
2089
2182
|
IsOptional2(),
|
|
2090
2183
|
IsString2(),
|
|
2091
|
-
|
|
2184
|
+
Matches(ISO_DATE_PATTERN),
|
|
2092
2185
|
_ts_metadata8("design:type", String)
|
|
2093
2186
|
], UpdatePromotionDto.prototype, "validFrom", void 0);
|
|
2094
2187
|
_ts_decorate8([
|
|
2095
2188
|
IsOptional2(),
|
|
2096
2189
|
IsString2(),
|
|
2097
|
-
|
|
2190
|
+
Matches(ISO_DATE_PATTERN),
|
|
2098
2191
|
_ts_metadata8("design:type", String)
|
|
2099
2192
|
], UpdatePromotionDto.prototype, "validTo", void 0);
|
|
2100
2193
|
_ts_decorate8([
|
|
@@ -2136,7 +2229,7 @@ _ts_decorate8([
|
|
|
2136
2229
|
], UpdatePromotionDto.prototype, "i18n", void 0);
|
|
2137
2230
|
|
|
2138
2231
|
// src/catalog/promotions.controller.ts
|
|
2139
|
-
import { Body as Body2, Controller as Controller2, Delete, Get as Get2, HttpCode, HttpStatus, Inject as Inject7, Param as Param2, ParseUUIDPipe, Patch as Patch2, Post as Post2,
|
|
2232
|
+
import { Body as Body2, Controller as Controller2, Delete, Get as Get2, HttpCode, HttpStatus, Inject as Inject7, Param as Param2, ParseUUIDPipe, Patch as Patch2, Post as Post2, UseGuards as UseGuards2 } from "@nestjs/common";
|
|
2140
2233
|
function _ts_decorate9(decorators, target, key, desc) {
|
|
2141
2234
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2142
2235
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -2163,8 +2256,8 @@ function buildPromotionsController(guards) {
|
|
|
2163
2256
|
constructor(service) {
|
|
2164
2257
|
this.service = service;
|
|
2165
2258
|
}
|
|
2166
|
-
list(
|
|
2167
|
-
return this.service.list(
|
|
2259
|
+
list() {
|
|
2260
|
+
return this.service.list();
|
|
2168
2261
|
}
|
|
2169
2262
|
getById(id) {
|
|
2170
2263
|
return this.service.getById(id);
|
|
@@ -2181,11 +2274,8 @@ function buildPromotionsController(guards) {
|
|
|
2181
2274
|
};
|
|
2182
2275
|
_ts_decorate9([
|
|
2183
2276
|
Get2(),
|
|
2184
|
-
_ts_param7(0, Query2()),
|
|
2185
2277
|
_ts_metadata9("design:type", Function),
|
|
2186
|
-
_ts_metadata9("design:paramtypes", [
|
|
2187
|
-
typeof ListPromotionsQueryDto === "undefined" ? Object : ListPromotionsQueryDto
|
|
2188
|
-
]),
|
|
2278
|
+
_ts_metadata9("design:paramtypes", []),
|
|
2189
2279
|
_ts_metadata9("design:returntype", void 0)
|
|
2190
2280
|
], GeneratedPromotionsController.prototype, "list", null);
|
|
2191
2281
|
_ts_decorate9([
|
|
@@ -2268,11 +2358,11 @@ var MarketingSettingsService = class {
|
|
|
2268
2358
|
this.repo = repo;
|
|
2269
2359
|
}
|
|
2270
2360
|
/** Returns the stored config or `null` (in which case the full pool applies). */
|
|
2271
|
-
get(
|
|
2272
|
-
return this.repo.get(
|
|
2361
|
+
get() {
|
|
2362
|
+
return this.repo.get();
|
|
2273
2363
|
}
|
|
2274
|
-
upsert(
|
|
2275
|
-
return this.repo.upsert(
|
|
2364
|
+
upsert(data) {
|
|
2365
|
+
return this.repo.upsert(data);
|
|
2276
2366
|
}
|
|
2277
2367
|
};
|
|
2278
2368
|
MarketingSettingsService = _ts_decorate10([
|
|
@@ -2285,7 +2375,7 @@ MarketingSettingsService = _ts_decorate10([
|
|
|
2285
2375
|
], MarketingSettingsService);
|
|
2286
2376
|
|
|
2287
2377
|
// src/catalog/dto/marketing-settings.dto.ts
|
|
2288
|
-
import { ArrayUnique, IsArray as IsArray2, IsString as IsString3, Matches as
|
|
2378
|
+
import { ArrayUnique, IsArray as IsArray2, IsString as IsString3, Matches as Matches2 } from "class-validator";
|
|
2289
2379
|
function _ts_decorate11(decorators, target, key, desc) {
|
|
2290
2380
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2291
2381
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -2297,42 +2387,20 @@ function _ts_metadata11(k, v) {
|
|
|
2297
2387
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2298
2388
|
}
|
|
2299
2389
|
__name(_ts_metadata11, "_ts_metadata");
|
|
2300
|
-
var PROJECT_KEY_PATTERN3 = /^[a-z][a-z0-9-]*$/;
|
|
2301
2390
|
var LOCALE_PATTERN = /^[a-z]{2}(-[A-Z]{2})?$/;
|
|
2302
|
-
var ListMarketingSettingsQueryDto = class {
|
|
2303
|
-
static {
|
|
2304
|
-
__name(this, "ListMarketingSettingsQueryDto");
|
|
2305
|
-
}
|
|
2306
|
-
projectKey;
|
|
2307
|
-
};
|
|
2308
|
-
_ts_decorate11([
|
|
2309
|
-
IsString3(),
|
|
2310
|
-
Matches3(PROJECT_KEY_PATTERN3, {
|
|
2311
|
-
message: "projectKey must be kebab-case"
|
|
2312
|
-
}),
|
|
2313
|
-
_ts_metadata11("design:type", String)
|
|
2314
|
-
], ListMarketingSettingsQueryDto.prototype, "projectKey", void 0);
|
|
2315
2391
|
var UpdateMarketingSettingsDto = class {
|
|
2316
2392
|
static {
|
|
2317
2393
|
__name(this, "UpdateMarketingSettingsDto");
|
|
2318
2394
|
}
|
|
2319
|
-
projectKey;
|
|
2320
2395
|
activeLocales;
|
|
2321
2396
|
};
|
|
2322
|
-
_ts_decorate11([
|
|
2323
|
-
IsString3(),
|
|
2324
|
-
Matches3(PROJECT_KEY_PATTERN3, {
|
|
2325
|
-
message: "projectKey must be kebab-case"
|
|
2326
|
-
}),
|
|
2327
|
-
_ts_metadata11("design:type", String)
|
|
2328
|
-
], UpdateMarketingSettingsDto.prototype, "projectKey", void 0);
|
|
2329
2397
|
_ts_decorate11([
|
|
2330
2398
|
IsArray2(),
|
|
2331
2399
|
ArrayUnique(),
|
|
2332
2400
|
IsString3({
|
|
2333
2401
|
each: true
|
|
2334
2402
|
}),
|
|
2335
|
-
|
|
2403
|
+
Matches2(LOCALE_PATTERN, {
|
|
2336
2404
|
each: true,
|
|
2337
2405
|
message: "Locale must be ISO-639-1"
|
|
2338
2406
|
}),
|
|
@@ -2340,7 +2408,7 @@ _ts_decorate11([
|
|
|
2340
2408
|
], UpdateMarketingSettingsDto.prototype, "activeLocales", void 0);
|
|
2341
2409
|
|
|
2342
2410
|
// src/catalog/marketing-settings.controller.ts
|
|
2343
|
-
import { Body as Body3, Controller as Controller3, Get as Get3, Inject as Inject9, Put,
|
|
2411
|
+
import { Body as Body3, Controller as Controller3, Get as Get3, Inject as Inject9, Put, UseGuards as UseGuards3 } from "@nestjs/common";
|
|
2344
2412
|
function _ts_decorate12(decorators, target, key, desc) {
|
|
2345
2413
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2346
2414
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -2368,22 +2436,19 @@ function buildMarketingSettingsController(guards) {
|
|
|
2368
2436
|
this.service = service;
|
|
2369
2437
|
}
|
|
2370
2438
|
/** Returns the stored config or `null` (full pool active). */
|
|
2371
|
-
get(
|
|
2372
|
-
return this.service.get(
|
|
2439
|
+
get() {
|
|
2440
|
+
return this.service.get();
|
|
2373
2441
|
}
|
|
2374
2442
|
update(dto) {
|
|
2375
|
-
return this.service.upsert(
|
|
2443
|
+
return this.service.upsert({
|
|
2376
2444
|
activeLocales: dto.activeLocales
|
|
2377
2445
|
});
|
|
2378
2446
|
}
|
|
2379
2447
|
};
|
|
2380
2448
|
_ts_decorate12([
|
|
2381
2449
|
Get3(),
|
|
2382
|
-
_ts_param9(0, Query3()),
|
|
2383
2450
|
_ts_metadata12("design:type", Function),
|
|
2384
|
-
_ts_metadata12("design:paramtypes", [
|
|
2385
|
-
typeof ListMarketingSettingsQueryDto === "undefined" ? Object : ListMarketingSettingsQueryDto
|
|
2386
|
-
]),
|
|
2451
|
+
_ts_metadata12("design:paramtypes", []),
|
|
2387
2452
|
_ts_metadata12("design:returntype", void 0)
|
|
2388
2453
|
], GeneratedMarketingSettingsController.prototype, "get", null);
|
|
2389
2454
|
_ts_decorate12([
|
|
@@ -2410,7 +2475,7 @@ __name(buildMarketingSettingsController, "buildMarketingSettingsController");
|
|
|
2410
2475
|
|
|
2411
2476
|
// src/catalog/public-marketing-catalog.service.ts
|
|
2412
2477
|
import { Inject as Inject10, Injectable as Injectable7, Optional as Optional5 } from "@nestjs/common";
|
|
2413
|
-
import {
|
|
2478
|
+
import { buildFeatureRequiresIndex, collectUnsatisfiedRequires, promotionOnPrice, keepOneRecommended } from "@saasicat/core";
|
|
2414
2479
|
function _ts_decorate13(decorators, target, key, desc) {
|
|
2415
2480
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2416
2481
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -2444,14 +2509,30 @@ var PublicMarketingCatalogService = class {
|
|
|
2444
2509
|
promotionRepo;
|
|
2445
2510
|
catalogEntryRepo;
|
|
2446
2511
|
bundleRepo;
|
|
2447
|
-
|
|
2512
|
+
newPaymentMethodsSource;
|
|
2513
|
+
constructor(planRepo, marketingRepo, promotionRepo, catalogEntryRepo = null, bundleRepo = null, newPaymentMethodsSource) {
|
|
2448
2514
|
this.planRepo = planRepo;
|
|
2449
2515
|
this.marketingRepo = marketingRepo;
|
|
2450
2516
|
this.promotionRepo = promotionRepo;
|
|
2451
2517
|
this.catalogEntryRepo = catalogEntryRepo;
|
|
2452
2518
|
this.bundleRepo = bundleRepo;
|
|
2519
|
+
this.newPaymentMethodsSource = newPaymentMethodsSource;
|
|
2453
2520
|
}
|
|
2454
|
-
|
|
2521
|
+
/** What a new payment method is taken with here, for the page that offers the plans. */
|
|
2522
|
+
newPaymentMethods() {
|
|
2523
|
+
const account = this.newPaymentMethodsSource?.forNewPaymentMethods() ?? null;
|
|
2524
|
+
if (!account) return {
|
|
2525
|
+
taken: false,
|
|
2526
|
+
methods: []
|
|
2527
|
+
};
|
|
2528
|
+
return {
|
|
2529
|
+
taken: true,
|
|
2530
|
+
methods: [
|
|
2531
|
+
...account.methods
|
|
2532
|
+
]
|
|
2533
|
+
};
|
|
2534
|
+
}
|
|
2535
|
+
async getCatalog(locale, currency, vatRate, asOf = /* @__PURE__ */ new Date()) {
|
|
2455
2536
|
const empty = {
|
|
2456
2537
|
features: [],
|
|
2457
2538
|
quotas: []
|
|
@@ -2460,10 +2541,10 @@ var PublicMarketingCatalogService = class {
|
|
|
2460
2541
|
const findLatest = this.planRepo.findLatestLivePlanVersion?.bind(this.planRepo);
|
|
2461
2542
|
if (!findActive && !findLatest) {
|
|
2462
2543
|
return {
|
|
2463
|
-
projectKey,
|
|
2464
2544
|
locale,
|
|
2465
2545
|
currency,
|
|
2466
2546
|
vatRate,
|
|
2547
|
+
newPaymentMethods: this.newPaymentMethods(),
|
|
2467
2548
|
plans: [],
|
|
2468
2549
|
bundles: [],
|
|
2469
2550
|
comparison: empty
|
|
@@ -2471,21 +2552,20 @@ var PublicMarketingCatalogService = class {
|
|
|
2471
2552
|
}
|
|
2472
2553
|
const resolveVersion = findActive ? (planKey) => findActive(planKey, asOf) : (planKey) => findLatest(planKey);
|
|
2473
2554
|
const [plans, promotions, labelMeta, marketedBundles] = await Promise.all([
|
|
2474
|
-
this.planRepo.list({
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
this.
|
|
2478
|
-
projectKey
|
|
2479
|
-
}),
|
|
2480
|
-
this.loadLabelMeta(projectKey, locale),
|
|
2481
|
-
this.loadMarketedBundles(projectKey)
|
|
2555
|
+
this.planRepo.list({}),
|
|
2556
|
+
this.promotionRepo.list(),
|
|
2557
|
+
this.loadLabelMeta(locale),
|
|
2558
|
+
this.loadMarketedBundles()
|
|
2482
2559
|
]);
|
|
2483
2560
|
const out = [];
|
|
2561
|
+
const inRequestedLocale = /* @__PURE__ */ new Set();
|
|
2484
2562
|
for (const plan of plans) {
|
|
2485
2563
|
const live = await resolveVersion(plan.planKey);
|
|
2486
2564
|
if (!live) continue;
|
|
2487
|
-
const
|
|
2565
|
+
const own = await this.marketingRepo.findByTarget("PLAN", live.id, locale);
|
|
2566
|
+
const marketing = own ?? (locale === DEFAULT_LOCALE ? null : await this.marketingRepo.findByTarget("PLAN", live.id, DEFAULT_LOCALE));
|
|
2488
2567
|
if (!marketing || marketing.visible === false) continue;
|
|
2568
|
+
if (own) inRequestedLocale.add(plan.planKey);
|
|
2489
2569
|
const monthlyNet = toNumber(live.monthlyNet);
|
|
2490
2570
|
const yearlyNet = toNumber(live.yearlyNet);
|
|
2491
2571
|
out.push({
|
|
@@ -2513,6 +2593,7 @@ var PublicMarketingCatalogService = class {
|
|
|
2513
2593
|
});
|
|
2514
2594
|
}
|
|
2515
2595
|
out.sort((a, b) => b.priority - a.priority);
|
|
2596
|
+
keepOneRecommended(out, inRequestedLocale);
|
|
2516
2597
|
const requiresIndex = buildFeatureRequiresIndex([
|
|
2517
2598
|
...labelMeta.features
|
|
2518
2599
|
].map(([featureKey, meta]) => ({
|
|
@@ -2521,25 +2602,24 @@ var PublicMarketingCatalogService = class {
|
|
|
2521
2602
|
})));
|
|
2522
2603
|
const publicBundles = (await Promise.all(marketedBundles.map((bv) => this.toPublicBundle(bv, promotions, locale, asOf, requiresIndex, labelMeta)))).filter((bundle) => bundle !== null);
|
|
2523
2604
|
return {
|
|
2524
|
-
projectKey,
|
|
2525
2605
|
locale,
|
|
2526
2606
|
currency,
|
|
2527
2607
|
vatRate,
|
|
2608
|
+
newPaymentMethods: this.newPaymentMethods(),
|
|
2528
2609
|
plans: out,
|
|
2529
2610
|
bundles: publicBundles,
|
|
2530
2611
|
comparison: this.buildComparison(out, labelMeta)
|
|
2531
2612
|
};
|
|
2532
2613
|
}
|
|
2533
2614
|
/**
|
|
2534
|
-
* Loads all published-and-live bundle versions
|
|
2535
|
-
*
|
|
2536
|
-
*
|
|
2537
|
-
*
|
|
2615
|
+
* Loads all published-and-live bundle versions and filters on
|
|
2616
|
+
* `marketed === true`. Without a registered `BundleRepository` (apps
|
|
2617
|
+
* without the SuperAdmin bundle editor) the list is empty — the public
|
|
2618
|
+
* catalog stays plan-only.
|
|
2538
2619
|
*/
|
|
2539
|
-
async loadMarketedBundles(
|
|
2620
|
+
async loadMarketedBundles() {
|
|
2540
2621
|
if (!this.bundleRepo) return [];
|
|
2541
2622
|
const bundles = await this.bundleRepo.list({
|
|
2542
|
-
projectKey,
|
|
2543
2623
|
excludeDeleted: true
|
|
2544
2624
|
});
|
|
2545
2625
|
const out = [];
|
|
@@ -2607,7 +2687,7 @@ var PublicMarketingCatalogService = class {
|
|
|
2607
2687
|
return labels;
|
|
2608
2688
|
}
|
|
2609
2689
|
/** Loads the translated feature/quota labels (+ sortOrder) for a locale. */
|
|
2610
|
-
async loadLabelMeta(
|
|
2690
|
+
async loadLabelMeta(locale) {
|
|
2611
2691
|
const features = /* @__PURE__ */ new Map();
|
|
2612
2692
|
const quotas = /* @__PURE__ */ new Map();
|
|
2613
2693
|
if (!this.catalogEntryRepo) return {
|
|
@@ -2615,12 +2695,8 @@ var PublicMarketingCatalogService = class {
|
|
|
2615
2695
|
quotas
|
|
2616
2696
|
};
|
|
2617
2697
|
const [featureRows, quotaRows] = await Promise.all([
|
|
2618
|
-
this.catalogEntryRepo.listFeatures({
|
|
2619
|
-
|
|
2620
|
-
}),
|
|
2621
|
-
this.catalogEntryRepo.listQuotas({
|
|
2622
|
-
projectKey
|
|
2623
|
-
})
|
|
2698
|
+
this.catalogEntryRepo.listFeatures({}),
|
|
2699
|
+
this.catalogEntryRepo.listQuotas({})
|
|
2624
2700
|
]);
|
|
2625
2701
|
for (const f of featureRows) {
|
|
2626
2702
|
features.set(f.featureKey, {
|
|
@@ -2711,20 +2787,18 @@ var PublicMarketingCatalogService = class {
|
|
|
2711
2787
|
};
|
|
2712
2788
|
}
|
|
2713
2789
|
resolvePromo(promotions, targetKey, targetType, locale, monthlyNet, yearlyNet, asOf) {
|
|
2714
|
-
const
|
|
2715
|
-
const
|
|
2716
|
-
const promo =
|
|
2790
|
+
const monthly = promotionOnPrice(promotions, targetKey, locale, "monthly", monthlyNet, asOf, targetType);
|
|
2791
|
+
const yearly = promotionOnPrice(promotions, targetKey, locale, "yearly", yearlyNet, asOf, targetType);
|
|
2792
|
+
const promo = (monthly ?? yearly)?.promotion;
|
|
2717
2793
|
if (!promo) return null;
|
|
2718
|
-
const monthlyResult = applyPromo(monthlyPromo, monthlyNet);
|
|
2719
|
-
const yearlyResult = applyPromo(yearlyPromo, yearlyNet);
|
|
2720
2794
|
const i18n = promo.i18n?.[locale] ?? promo.i18n?.[DEFAULT_LOCALE] ?? {};
|
|
2721
2795
|
return {
|
|
2722
2796
|
type: promo.type,
|
|
2723
2797
|
badge: i18n.badge ?? "",
|
|
2724
2798
|
fineprint: i18n.fineprint ?? "",
|
|
2725
2799
|
color: promo.color,
|
|
2726
|
-
discountedMonthlyNet:
|
|
2727
|
-
discountedYearlyNet:
|
|
2800
|
+
discountedMonthlyNet: monthly ? monthly.result.discounted : null,
|
|
2801
|
+
discountedYearlyNet: yearly ? yearly.result.discounted : null
|
|
2728
2802
|
};
|
|
2729
2803
|
}
|
|
2730
2804
|
};
|
|
@@ -2737,18 +2811,20 @@ PublicMarketingCatalogService = _ts_decorate13([
|
|
|
2737
2811
|
_ts_param10(3, Inject10(CATALOG_ENTRY_REPOSITORY_TOKEN)),
|
|
2738
2812
|
_ts_param10(4, Optional5()),
|
|
2739
2813
|
_ts_param10(4, Inject10(BUNDLE_REPOSITORY_TOKEN)),
|
|
2814
|
+
_ts_param10(5, Inject10(NEW_PAYMENT_METHODS_SOURCE_TOKEN)),
|
|
2740
2815
|
_ts_metadata13("design:type", Function),
|
|
2741
2816
|
_ts_metadata13("design:paramtypes", [
|
|
2742
2817
|
typeof PlanRepository === "undefined" ? Object : PlanRepository,
|
|
2743
2818
|
typeof MarketingProjectionRepository === "undefined" ? Object : MarketingProjectionRepository,
|
|
2744
2819
|
typeof PromotionRepository === "undefined" ? Object : PromotionRepository,
|
|
2745
2820
|
Object,
|
|
2821
|
+
Object,
|
|
2746
2822
|
Object
|
|
2747
2823
|
])
|
|
2748
2824
|
], PublicMarketingCatalogService);
|
|
2749
2825
|
|
|
2750
2826
|
// src/catalog/public-marketing-catalog.controller.ts
|
|
2751
|
-
import { Controller as Controller4, Get as Get4, Inject as Inject11, Query as
|
|
2827
|
+
import { Controller as Controller4, Get as Get4, Inject as Inject11, Query as Query2, UseGuards as UseGuards4 } from "@nestjs/common";
|
|
2752
2828
|
function _ts_decorate14(decorators, target, key, desc) {
|
|
2753
2829
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2754
2830
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -2766,7 +2842,7 @@ function _ts_param11(paramIndex, decorator) {
|
|
|
2766
2842
|
};
|
|
2767
2843
|
}
|
|
2768
2844
|
__name(_ts_param11, "_ts_param");
|
|
2769
|
-
function buildPublicMarketingCatalogController(guards,
|
|
2845
|
+
function buildPublicMarketingCatalogController(guards, currency, vatRate) {
|
|
2770
2846
|
let GeneratedPublicMarketingCatalogController = class GeneratedPublicMarketingCatalogController {
|
|
2771
2847
|
static {
|
|
2772
2848
|
__name(this, "GeneratedPublicMarketingCatalogController");
|
|
@@ -2785,14 +2861,14 @@ function buildPublicMarketingCatalogController(guards, projectKey, currency, vat
|
|
|
2785
2861
|
getCatalog(lang, localeParam, asOfParam) {
|
|
2786
2862
|
const locale = lang || localeParam || "de";
|
|
2787
2863
|
const asOf = parseAsOf(asOfParam);
|
|
2788
|
-
return this.service.getCatalog(
|
|
2864
|
+
return this.service.getCatalog(locale, currency, vatRate, asOf);
|
|
2789
2865
|
}
|
|
2790
2866
|
};
|
|
2791
2867
|
_ts_decorate14([
|
|
2792
2868
|
Get4(),
|
|
2793
|
-
_ts_param11(0,
|
|
2794
|
-
_ts_param11(1,
|
|
2795
|
-
_ts_param11(2,
|
|
2869
|
+
_ts_param11(0, Query2("lang")),
|
|
2870
|
+
_ts_param11(1, Query2("locale")),
|
|
2871
|
+
_ts_param11(2, Query2("asOf")),
|
|
2796
2872
|
_ts_metadata14("design:type", Function),
|
|
2797
2873
|
_ts_metadata14("design:paramtypes", [
|
|
2798
2874
|
String,
|
|
@@ -2822,8 +2898,8 @@ function parseAsOf(raw) {
|
|
|
2822
2898
|
__name(parseAsOf, "parseAsOf");
|
|
2823
2899
|
|
|
2824
2900
|
// src/catalog/plans.service.ts
|
|
2825
|
-
import { Inject as Inject12, Injectable as Injectable8, NotFoundException as NotFoundException5, Optional as Optional6, UnprocessableEntityException as
|
|
2826
|
-
import { CATALOG_ERROR_CODES as
|
|
2901
|
+
import { Inject as Inject12, Injectable as Injectable8, NotFoundException as NotFoundException5, Optional as Optional6, UnprocessableEntityException as UnprocessableEntityException5 } from "@nestjs/common";
|
|
2902
|
+
import { CATALOG_ERROR_CODES as CATALOG_ERROR_CODES7 } from "@saasicat/core";
|
|
2827
2903
|
function _ts_decorate15(decorators, target, key, desc) {
|
|
2828
2904
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2829
2905
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -2858,12 +2934,11 @@ var PlansService = class {
|
|
|
2858
2934
|
* `SubscriptionRepository` is registered or it does not implement
|
|
2859
2935
|
* `countActiveByPlanKey`.
|
|
2860
2936
|
*/
|
|
2861
|
-
async getTenantCounts(
|
|
2862
|
-
return await this.subscriptions?.countActiveByPlanKey?.(
|
|
2937
|
+
async getTenantCounts() {
|
|
2938
|
+
return await this.subscriptions?.countActiveByPlanKey?.() ?? {};
|
|
2863
2939
|
}
|
|
2864
|
-
listPlans(
|
|
2940
|
+
listPlans(opts = {}) {
|
|
2865
2941
|
return this.repo.list({
|
|
2866
|
-
projectKey,
|
|
2867
2942
|
excludeDeleted: true,
|
|
2868
2943
|
onlyPublished: opts.onlyPublished ?? false
|
|
2869
2944
|
});
|
|
@@ -2872,7 +2947,7 @@ var PlansService = class {
|
|
|
2872
2947
|
const plan = await this.repo.findById(planId);
|
|
2873
2948
|
if (!plan) {
|
|
2874
2949
|
throw new NotFoundException5({
|
|
2875
|
-
code:
|
|
2950
|
+
code: CATALOG_ERROR_CODES7.PLAN_NOT_FOUND,
|
|
2876
2951
|
message: `Plan '${planId}' not found`,
|
|
2877
2952
|
params: {
|
|
2878
2953
|
planId
|
|
@@ -2882,14 +2957,13 @@ var PlansService = class {
|
|
|
2882
2957
|
return plan;
|
|
2883
2958
|
}
|
|
2884
2959
|
async createPlan(data) {
|
|
2885
|
-
const existing = await this.repo.findByKey(data.
|
|
2960
|
+
const existing = await this.repo.findByKey(data.planKey);
|
|
2886
2961
|
if (existing) {
|
|
2887
|
-
throw new
|
|
2888
|
-
code:
|
|
2889
|
-
message: `Plan '${data.planKey}' already exists
|
|
2962
|
+
throw new UnprocessableEntityException5({
|
|
2963
|
+
code: CATALOG_ERROR_CODES7.PLAN_ALREADY_EXISTS,
|
|
2964
|
+
message: `Plan '${data.planKey}' already exists`,
|
|
2890
2965
|
params: {
|
|
2891
|
-
planKey: data.planKey
|
|
2892
|
-
projectKey: data.projectKey
|
|
2966
|
+
planKey: data.planKey
|
|
2893
2967
|
}
|
|
2894
2968
|
});
|
|
2895
2969
|
}
|
|
@@ -2899,7 +2973,7 @@ var PlansService = class {
|
|
|
2899
2973
|
const existing = await this.repo.findById(planId);
|
|
2900
2974
|
if (!existing) {
|
|
2901
2975
|
throw new NotFoundException5({
|
|
2902
|
-
code:
|
|
2976
|
+
code: CATALOG_ERROR_CODES7.PLAN_NOT_FOUND,
|
|
2903
2977
|
message: `Plan '${planId}' not found`,
|
|
2904
2978
|
params: {
|
|
2905
2979
|
planId
|
|
@@ -2912,7 +2986,7 @@ var PlansService = class {
|
|
|
2912
2986
|
const existing = await this.repo.findById(planId);
|
|
2913
2987
|
if (!existing) {
|
|
2914
2988
|
throw new NotFoundException5({
|
|
2915
|
-
code:
|
|
2989
|
+
code: CATALOG_ERROR_CODES7.PLAN_NOT_FOUND,
|
|
2916
2990
|
message: `Plan '${planId}' not found`,
|
|
2917
2991
|
params: {
|
|
2918
2992
|
planId
|
|
@@ -2937,7 +3011,7 @@ var PlansService = class {
|
|
|
2937
3011
|
const existing = await this.repo.findById(planId);
|
|
2938
3012
|
if (!existing) {
|
|
2939
3013
|
throw new NotFoundException5({
|
|
2940
|
-
code:
|
|
3014
|
+
code: CATALOG_ERROR_CODES7.PLAN_NOT_FOUND,
|
|
2941
3015
|
message: `Plan '${planId}' not found`,
|
|
2942
3016
|
params: {
|
|
2943
3017
|
planId
|
|
@@ -2947,8 +3021,8 @@ var PlansService = class {
|
|
|
2947
3021
|
await this.assertNoPublishedVersions(existing.planKey, "hard-delete");
|
|
2948
3022
|
await this.assertNoDrafts(existing.planKey);
|
|
2949
3023
|
if (typeof this.repo.hardDelete !== "function") {
|
|
2950
|
-
throw new
|
|
2951
|
-
code:
|
|
3024
|
+
throw new UnprocessableEntityException5({
|
|
3025
|
+
code: CATALOG_ERROR_CODES7.PLAN_HARD_DELETE_NOT_IMPLEMENTED,
|
|
2952
3026
|
message: "Hard delete is not implemented in the current repository. Implement PlanRepository.hardDelete."
|
|
2953
3027
|
});
|
|
2954
3028
|
}
|
|
@@ -2968,8 +3042,8 @@ var PlansService = class {
|
|
|
2968
3042
|
if (published.length === 0) return;
|
|
2969
3043
|
const live = published.filter((v) => v.supersededAt === null).length;
|
|
2970
3044
|
const superseded = published.length - live;
|
|
2971
|
-
throw new
|
|
2972
|
-
code:
|
|
3045
|
+
throw new UnprocessableEntityException5({
|
|
3046
|
+
code: CATALOG_ERROR_CODES7.PLAN_HAS_PUBLISHED_VERSIONS,
|
|
2973
3047
|
message: `Plan '${planKey}' cannot be ${op === "hard-delete" ? "deleted" : "archived"} \u2014 it has ${published.length} published version(s) (${live} live, ${superseded} superseded). Existing subscriptions reference those versions (contract protection P1), so the plan record must be preserved.`,
|
|
2974
3048
|
params: {
|
|
2975
3049
|
planKey,
|
|
@@ -2985,8 +3059,8 @@ var PlansService = class {
|
|
|
2985
3059
|
const versions = await this.repo.listVersions(planKey);
|
|
2986
3060
|
const drafts = versions.filter((v) => v.publishedAt === null);
|
|
2987
3061
|
if (drafts.length === 0) return;
|
|
2988
|
-
throw new
|
|
2989
|
-
code:
|
|
3062
|
+
throw new UnprocessableEntityException5({
|
|
3063
|
+
code: CATALOG_ERROR_CODES7.PLAN_HAS_DRAFTS,
|
|
2990
3064
|
message: `Plan '${planKey}' still has ${drafts.length} open draft version(s). Discard them first (DELETE /admin/catalog/plan-versions/:id) or publish them.`,
|
|
2991
3065
|
params: {
|
|
2992
3066
|
planKey
|
|
@@ -3008,8 +3082,8 @@ PlansService = _ts_decorate15([
|
|
|
3008
3082
|
], PlansService);
|
|
3009
3083
|
|
|
3010
3084
|
// src/catalog/plan-versions.service.ts
|
|
3011
|
-
import { Inject as Inject13, Injectable as Injectable9, Logger as Logger3, NotFoundException as NotFoundException6, Optional as Optional7, UnprocessableEntityException as
|
|
3012
|
-
import { CATALOG_ERROR_CODES as
|
|
3085
|
+
import { Inject as Inject13, Injectable as Injectable9, Logger as Logger3, NotFoundException as NotFoundException6, Optional as Optional7, UnprocessableEntityException as UnprocessableEntityException6 } from "@nestjs/common";
|
|
3086
|
+
import { CATALOG_ERROR_CODES as CATALOG_ERROR_CODES8, isVersionEditable as isVersionEditable2 } from "@saasicat/core";
|
|
3013
3087
|
function _ts_decorate16(decorators, target, key, desc) {
|
|
3014
3088
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3015
3089
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -3079,7 +3153,7 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3079
3153
|
const existing = await this.repo.findVersionById(versionId);
|
|
3080
3154
|
if (!existing) {
|
|
3081
3155
|
throw new NotFoundException6({
|
|
3082
|
-
code:
|
|
3156
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_NOT_FOUND,
|
|
3083
3157
|
message: `PlanVersion '${versionId}' not found`,
|
|
3084
3158
|
params: {
|
|
3085
3159
|
versionId
|
|
@@ -3087,8 +3161,8 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3087
3161
|
});
|
|
3088
3162
|
}
|
|
3089
3163
|
if (existing.publishedAt !== null) {
|
|
3090
|
-
throw new
|
|
3091
|
-
code:
|
|
3164
|
+
throw new UnprocessableEntityException6({
|
|
3165
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_ALREADY_PUBLISHED,
|
|
3092
3166
|
message: `PlanVersion '${versionId}' is already published and cannot be discarded.`,
|
|
3093
3167
|
params: {
|
|
3094
3168
|
versionId
|
|
@@ -3096,8 +3170,8 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3096
3170
|
});
|
|
3097
3171
|
}
|
|
3098
3172
|
if (typeof this.repo.deletePlanVersionDraft !== "function") {
|
|
3099
|
-
throw new
|
|
3100
|
-
code:
|
|
3173
|
+
throw new UnprocessableEntityException6({
|
|
3174
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_DISCARD_NOT_IMPLEMENTED,
|
|
3101
3175
|
message: "Discard is not implemented in the current repository. Implement PlanRepository.deletePlanVersionDraft."
|
|
3102
3176
|
});
|
|
3103
3177
|
}
|
|
@@ -3112,7 +3186,7 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3112
3186
|
const plan = await this.repo.findById(planUuid);
|
|
3113
3187
|
if (!plan) {
|
|
3114
3188
|
throw new NotFoundException6({
|
|
3115
|
-
code:
|
|
3189
|
+
code: CATALOG_ERROR_CODES8.PLAN_NOT_FOUND,
|
|
3116
3190
|
message: `Plan '${planUuid}' not found`,
|
|
3117
3191
|
params: {
|
|
3118
3192
|
planId: planUuid
|
|
@@ -3126,7 +3200,7 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3126
3200
|
const version = await this.repo.findVersionById(versionId);
|
|
3127
3201
|
if (!version) {
|
|
3128
3202
|
throw new NotFoundException6({
|
|
3129
|
-
code:
|
|
3203
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_NOT_FOUND,
|
|
3130
3204
|
message: `PlanVersion '${versionId}' not found`,
|
|
3131
3205
|
params: {
|
|
3132
3206
|
versionId
|
|
@@ -3168,9 +3242,9 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3168
3242
|
*/
|
|
3169
3243
|
async createPlanDraft(data) {
|
|
3170
3244
|
const plan = await this.repo.findById(data.planId);
|
|
3171
|
-
if (!plan) {
|
|
3245
|
+
if (!plan || plan.deletedAt !== null) {
|
|
3172
3246
|
throw new NotFoundException6({
|
|
3173
|
-
code:
|
|
3247
|
+
code: CATALOG_ERROR_CODES8.PLAN_NOT_FOUND,
|
|
3174
3248
|
message: `Plan '${data.planId}' not found`,
|
|
3175
3249
|
params: {
|
|
3176
3250
|
planId: data.planId
|
|
@@ -3180,8 +3254,8 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3180
3254
|
const planKey = plan.planKey;
|
|
3181
3255
|
const existingDraft = await this.repo.findCurrentDraft(planKey);
|
|
3182
3256
|
if (existingDraft) {
|
|
3183
|
-
throw new
|
|
3184
|
-
code:
|
|
3257
|
+
throw new UnprocessableEntityException6({
|
|
3258
|
+
code: CATALOG_ERROR_CODES8.PLAN_DRAFT_ALREADY_EXISTS,
|
|
3185
3259
|
message: `Plan '${planKey}' already has a draft version v${existingDraft.version}; publish or discard it first`,
|
|
3186
3260
|
params: {
|
|
3187
3261
|
planKey,
|
|
@@ -3213,7 +3287,7 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3213
3287
|
const existing = await this.repo.findVersionById(versionId);
|
|
3214
3288
|
if (!existing) {
|
|
3215
3289
|
throw new NotFoundException6({
|
|
3216
|
-
code:
|
|
3290
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_NOT_FOUND,
|
|
3217
3291
|
message: `PlanVersion '${versionId}' not found`,
|
|
3218
3292
|
params: {
|
|
3219
3293
|
versionId
|
|
@@ -3225,8 +3299,8 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3225
3299
|
]);
|
|
3226
3300
|
const { editable } = isVersionEditable2(annotated);
|
|
3227
3301
|
if (!editable) {
|
|
3228
|
-
throw new
|
|
3229
|
-
code:
|
|
3302
|
+
throw new UnprocessableEntityException6({
|
|
3303
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_NOT_EDITABLE,
|
|
3230
3304
|
message: `PlanVersion '${versionId}' is not editable. Only drafts and published versions are editable that are latest-in-chain, bind no subscription yet, and whose validFrom lies in the future.`,
|
|
3231
3305
|
params: {
|
|
3232
3306
|
versionId
|
|
@@ -3249,7 +3323,7 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3249
3323
|
const draft = await this.repo.findVersionById(versionId);
|
|
3250
3324
|
if (!draft) {
|
|
3251
3325
|
throw new NotFoundException6({
|
|
3252
|
-
code:
|
|
3326
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_NOT_FOUND,
|
|
3253
3327
|
message: `PlanVersion '${versionId}' not found`,
|
|
3254
3328
|
params: {
|
|
3255
3329
|
versionId
|
|
@@ -3257,14 +3331,24 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3257
3331
|
});
|
|
3258
3332
|
}
|
|
3259
3333
|
if (draft.publishedAt !== null) {
|
|
3260
|
-
throw new
|
|
3261
|
-
code:
|
|
3334
|
+
throw new UnprocessableEntityException6({
|
|
3335
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_ALREADY_PUBLISHED,
|
|
3262
3336
|
message: `PlanVersion '${versionId}' is already published`,
|
|
3263
3337
|
params: {
|
|
3264
3338
|
versionId
|
|
3265
3339
|
}
|
|
3266
3340
|
});
|
|
3267
3341
|
}
|
|
3342
|
+
const plan = await this.repo.findByKey(draft.planId);
|
|
3343
|
+
if (!plan || plan.deletedAt !== null) {
|
|
3344
|
+
throw new NotFoundException6({
|
|
3345
|
+
code: CATALOG_ERROR_CODES8.PLAN_NOT_FOUND,
|
|
3346
|
+
message: `Plan '${draft.planId}' not found`,
|
|
3347
|
+
params: {
|
|
3348
|
+
planId: draft.planId
|
|
3349
|
+
}
|
|
3350
|
+
});
|
|
3351
|
+
}
|
|
3268
3352
|
const warnings = await this.runStrictCheck({
|
|
3269
3353
|
features: draft.features,
|
|
3270
3354
|
quotas: draft.quotas ?? {}
|
|
@@ -3274,8 +3358,8 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3274
3358
|
const monthly = Number.parseFloat(String(draft.monthlyNet ?? "0"));
|
|
3275
3359
|
const yearly = Number.parseFloat(String(draft.yearlyNet ?? "0"));
|
|
3276
3360
|
if (monthly <= 0 || yearly <= 0) {
|
|
3277
|
-
throw new
|
|
3278
|
-
code:
|
|
3361
|
+
throw new UnprocessableEntityException6({
|
|
3362
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_ZERO_PRICE,
|
|
3279
3363
|
message: "A plan version cannot be published with a price of 0.00 (guard against seed placeholder). For deliberately free special contracts, set allowZeroPrice.",
|
|
3280
3364
|
monthlyNet: String(draft.monthlyNet),
|
|
3281
3365
|
yearlyNet: String(draft.yearlyNet)
|
|
@@ -3283,78 +3367,10 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3283
3367
|
}
|
|
3284
3368
|
}
|
|
3285
3369
|
const previous = await this.repo.findLatestLivePlanVersion(draft.planId);
|
|
3286
|
-
const
|
|
3287
|
-
if (!validFromInput) {
|
|
3288
|
-
throw new UnprocessableEntityException4({
|
|
3289
|
-
code: CATALOG_ERROR_CODES6.PLAN_VERSION_VALID_FROM_REQUIRED,
|
|
3290
|
-
message: "validFrom must be set when publishing (on the draft or the publish call).."
|
|
3291
|
-
});
|
|
3292
|
-
}
|
|
3293
|
-
const validFrom = new Date(validFromInput);
|
|
3294
|
-
if (Number.isNaN(validFrom.getTime())) {
|
|
3295
|
-
throw new UnprocessableEntityException4({
|
|
3296
|
-
code: CATALOG_ERROR_CODES6.PLAN_VERSION_VALID_FROM_INVALID,
|
|
3297
|
-
message: `validFrom '${validFromInput}' is not a valid date`,
|
|
3298
|
-
params: {
|
|
3299
|
-
validFrom: validFromInput
|
|
3300
|
-
}
|
|
3301
|
-
});
|
|
3302
|
-
}
|
|
3303
|
-
if (previous?.validFrom) {
|
|
3304
|
-
const prevFrom = new Date(previous.validFrom);
|
|
3305
|
-
if (validFrom <= prevFrom) {
|
|
3306
|
-
throw new UnprocessableEntityException4({
|
|
3307
|
-
code: CATALOG_ERROR_CODES6.PLAN_VERSION_VALID_FROM_NOT_AFTER_PREVIOUS,
|
|
3308
|
-
message: `validFrom (${validFrom.toISOString()}) must be strictly after the validFrom of the previous version (${previous.validFrom}).`,
|
|
3309
|
-
params: {
|
|
3310
|
-
validFrom: validFrom.toISOString(),
|
|
3311
|
-
previousValidFrom: previous.validFrom
|
|
3312
|
-
}
|
|
3313
|
-
});
|
|
3314
|
-
}
|
|
3315
|
-
if (previous.validUntil) {
|
|
3316
|
-
const prevUntil = new Date(previous.validUntil);
|
|
3317
|
-
const dayMs = 24 * 60 * 60 * 1e3;
|
|
3318
|
-
const requiredStart = new Date(prevUntil.getTime() + dayMs);
|
|
3319
|
-
if (validFrom.getTime() !== requiredStart.getTime()) {
|
|
3320
|
-
throw new UnprocessableEntityException4({
|
|
3321
|
-
code: CATALOG_ERROR_CODES6.PLAN_VERSION_VALID_FROM_NOT_GAPLESS,
|
|
3322
|
-
message: `The predecessor has validUntil=${previous.validUntil.slice(0, 10)} \u2014 the successor must start seamlessly on the next day (${requiredStart.toISOString().slice(0, 10)}). Received: ${validFrom.toISOString().slice(0, 10)}.`,
|
|
3323
|
-
params: {
|
|
3324
|
-
received: validFrom.toISOString().slice(0, 10)
|
|
3325
|
-
},
|
|
3326
|
-
requiredValidFrom: requiredStart.toISOString().slice(0, 10),
|
|
3327
|
-
previousValidUntil: previous.validUntil
|
|
3328
|
-
});
|
|
3329
|
-
}
|
|
3330
|
-
}
|
|
3331
|
-
}
|
|
3332
|
-
const validUntilInput = publishMeta.validUntil !== void 0 ? publishMeta.validUntil : draft.validUntil;
|
|
3333
|
-
const validUntil = validUntilInput ? new Date(validUntilInput) : null;
|
|
3334
|
-
if (validUntil && Number.isNaN(validUntil.getTime())) {
|
|
3335
|
-
throw new UnprocessableEntityException4({
|
|
3336
|
-
code: CATALOG_ERROR_CODES6.PLAN_VERSION_VALID_UNTIL_INVALID,
|
|
3337
|
-
message: `validUntil '${validUntilInput}' is not a valid date`,
|
|
3338
|
-
params: {
|
|
3339
|
-
validUntil: validUntilInput
|
|
3340
|
-
}
|
|
3341
|
-
});
|
|
3342
|
-
}
|
|
3343
|
-
if (validUntil && validUntil <= validFrom) {
|
|
3344
|
-
throw new UnprocessableEntityException4({
|
|
3345
|
-
code: CATALOG_ERROR_CODES6.PLAN_VERSION_VALID_UNTIL_BEFORE_FROM,
|
|
3346
|
-
message: `validUntil (${validUntil.toISOString()}) must be strictly after validFrom (${validFrom.toISOString()}).`,
|
|
3347
|
-
params: {
|
|
3348
|
-
validUntil: validUntil.toISOString(),
|
|
3349
|
-
validFrom: validFrom.toISOString()
|
|
3350
|
-
}
|
|
3351
|
-
});
|
|
3352
|
-
}
|
|
3370
|
+
const { validFrom, validUntil } = resolveValidityWindow(publishMeta, draft, previous, PLAN_VERSION_WINDOW_CODES);
|
|
3353
3371
|
const fieldsFor = /* @__PURE__ */ __name((v) => ({
|
|
3354
3372
|
features: v.features,
|
|
3355
|
-
|
|
3356
|
-
maxVehicles: v.quotas?.["vehicles"] ?? 0,
|
|
3357
|
-
maxStorageGb: v.quotas?.["storageGb"] ?? 0,
|
|
3373
|
+
quotas: v.quotas ?? {},
|
|
3358
3374
|
monthlyNet: v.monthlyNet,
|
|
3359
3375
|
yearlyNet: v.yearlyNet
|
|
3360
3376
|
}), "fieldsFor");
|
|
@@ -3363,8 +3379,8 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3363
3379
|
nonRegressive: true
|
|
3364
3380
|
};
|
|
3365
3381
|
if (!diff.nonRegressive && !publishMeta.forceRegressive) {
|
|
3366
|
-
throw new
|
|
3367
|
-
code:
|
|
3382
|
+
throw new UnprocessableEntityException6({
|
|
3383
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_REGRESSION,
|
|
3368
3384
|
message: "This plan version is regressive (feature removed / quota lowered / price raised). Publishing requires an explicit `forceRegressive: true` (UI confirmation dialog with MFA).",
|
|
3369
3385
|
changes: diff.changes
|
|
3370
3386
|
});
|
|
@@ -3401,7 +3417,7 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3401
3417
|
const existing = await this.repo.findVersionById(versionId);
|
|
3402
3418
|
if (!existing) {
|
|
3403
3419
|
throw new NotFoundException6({
|
|
3404
|
-
code:
|
|
3420
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_NOT_FOUND,
|
|
3405
3421
|
message: `PlanVersion '${versionId}' not found`,
|
|
3406
3422
|
params: {
|
|
3407
3423
|
versionId
|
|
@@ -3409,8 +3425,8 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3409
3425
|
});
|
|
3410
3426
|
}
|
|
3411
3427
|
if (existing.publishedAt === null) {
|
|
3412
|
-
throw new
|
|
3413
|
-
code:
|
|
3428
|
+
throw new UnprocessableEntityException6({
|
|
3429
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_NOT_PUBLISHED,
|
|
3414
3430
|
message: `PlanVersion '${versionId}' is not published and cannot be terminated.`,
|
|
3415
3431
|
params: {
|
|
3416
3432
|
versionId
|
|
@@ -3418,8 +3434,8 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3418
3434
|
});
|
|
3419
3435
|
}
|
|
3420
3436
|
if (existing.supersededAt !== null) {
|
|
3421
|
-
throw new
|
|
3422
|
-
code:
|
|
3437
|
+
throw new UnprocessableEntityException6({
|
|
3438
|
+
code: CATALOG_ERROR_CODES8.PLAN_VERSION_SUPERSEDED,
|
|
3423
3439
|
message: `PlanVersion '${versionId}' has already been superseded by a newer version (supersededAt) and cannot be terminated.`,
|
|
3424
3440
|
params: {
|
|
3425
3441
|
versionId
|
|
@@ -3427,14 +3443,14 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3427
3443
|
});
|
|
3428
3444
|
}
|
|
3429
3445
|
if (Number.isNaN(endsAt.getTime())) {
|
|
3430
|
-
throw new
|
|
3431
|
-
code:
|
|
3446
|
+
throw new UnprocessableEntityException6({
|
|
3447
|
+
code: CATALOG_ERROR_CODES8.PLAN_TERMINATE_INVALID_DATE,
|
|
3432
3448
|
message: "endsAt is not a valid date."
|
|
3433
3449
|
});
|
|
3434
3450
|
}
|
|
3435
3451
|
if (endsAt.getTime() <= Date.now()) {
|
|
3436
|
-
throw new
|
|
3437
|
-
code:
|
|
3452
|
+
throw new UnprocessableEntityException6({
|
|
3453
|
+
code: CATALOG_ERROR_CODES8.PLAN_TERMINATE_DATE_NOT_FUTURE,
|
|
3438
3454
|
message: `endsAt (${endsAt.toISOString()}) must lie strictly in the future.`,
|
|
3439
3455
|
params: {
|
|
3440
3456
|
endsAt: endsAt.toISOString()
|
|
@@ -3442,8 +3458,8 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3442
3458
|
});
|
|
3443
3459
|
}
|
|
3444
3460
|
if (typeof this.repo.terminate !== "function") {
|
|
3445
|
-
throw new
|
|
3446
|
-
code:
|
|
3461
|
+
throw new UnprocessableEntityException6({
|
|
3462
|
+
code: CATALOG_ERROR_CODES8.PLAN_TERMINATE_NOT_IMPLEMENTED,
|
|
3447
3463
|
message: "Terminate is not implemented in the current repository. Implement PlanRepository.terminate."
|
|
3448
3464
|
});
|
|
3449
3465
|
}
|
|
@@ -3455,14 +3471,14 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3455
3471
|
async runStrictCheck(draft) {
|
|
3456
3472
|
const snapshot = resolveDiscoverySnapshot(this.snapshot, this.scanner);
|
|
3457
3473
|
if (!snapshot) return [];
|
|
3458
|
-
const approved = await loadApprovedCatalogKeys(this.catalogEntries
|
|
3474
|
+
const approved = await loadApprovedCatalogKeys(this.catalogEntries);
|
|
3459
3475
|
return validatePlanDraft(draft, snapshot, this.marketedOnly, approved);
|
|
3460
3476
|
}
|
|
3461
3477
|
gateOrPass(warnings) {
|
|
3462
3478
|
if (blockingStrictModeWarnings(warnings).length === 0) return;
|
|
3463
3479
|
if (this.mode === "blocking") {
|
|
3464
|
-
throw new
|
|
3465
|
-
code:
|
|
3480
|
+
throw new UnprocessableEntityException6({
|
|
3481
|
+
code: CATALOG_ERROR_CODES8.STRICT_MODE_VIOLATIONS,
|
|
3466
3482
|
message: "The strict-mode check found drift against the discovery snapshot.",
|
|
3467
3483
|
warnings
|
|
3468
3484
|
});
|
|
@@ -3494,7 +3510,49 @@ PlanVersionsService = _ts_decorate16([
|
|
|
3494
3510
|
], PlanVersionsService);
|
|
3495
3511
|
|
|
3496
3512
|
// src/catalog/dto/bundles.dto.ts
|
|
3497
|
-
import {
|
|
3513
|
+
import { Type } from "class-transformer";
|
|
3514
|
+
import { IsArray as IsArray3, IsBoolean as IsBoolean2, IsInt as IsInt2, IsNumberString, IsObject as IsObject3, IsOptional as IsOptional3, IsString as IsString4, IsUUID, Matches as Matches3, Max, MaxLength as MaxLength3, Min, MinLength, ValidateIf, ValidateNested } from "class-validator";
|
|
3515
|
+
|
|
3516
|
+
// src/catalog/dto/quota-record.validator.ts
|
|
3517
|
+
import { registerDecorator } from "class-validator";
|
|
3518
|
+
function isQuotaValue(value) {
|
|
3519
|
+
return typeof value === "number" && Number.isInteger(value) && value >= -1;
|
|
3520
|
+
}
|
|
3521
|
+
__name(isQuotaValue, "isQuotaValue");
|
|
3522
|
+
function offendingQuotaKeys(value) {
|
|
3523
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) return [];
|
|
3524
|
+
return Object.entries(value).filter(([, quota]) => !isQuotaValue(quota)).map(([key]) => key).sort();
|
|
3525
|
+
}
|
|
3526
|
+
__name(offendingQuotaKeys, "offendingQuotaKeys");
|
|
3527
|
+
function IsQuotaRecord(options) {
|
|
3528
|
+
return /* @__PURE__ */ __name(function register(target, propertyName) {
|
|
3529
|
+
registerDecorator({
|
|
3530
|
+
name: "isQuotaRecord",
|
|
3531
|
+
target: target.constructor,
|
|
3532
|
+
propertyName,
|
|
3533
|
+
options,
|
|
3534
|
+
validator: {
|
|
3535
|
+
validate(value) {
|
|
3536
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3537
|
+
return false;
|
|
3538
|
+
}
|
|
3539
|
+
return offendingQuotaKeys(value).length === 0;
|
|
3540
|
+
},
|
|
3541
|
+
defaultMessage(args) {
|
|
3542
|
+
const bad = offendingQuotaKeys(args.value);
|
|
3543
|
+
if (bad.length === 0) {
|
|
3544
|
+
return `${args.property} must be an object of quota keys to integers`;
|
|
3545
|
+
}
|
|
3546
|
+
return `${args.property} must map every key to an integer of at least -1 (-1 = unlimited); not a number: ${bad.join(", ")}`;
|
|
3547
|
+
}
|
|
3548
|
+
}
|
|
3549
|
+
});
|
|
3550
|
+
}, "register");
|
|
3551
|
+
}
|
|
3552
|
+
__name(IsQuotaRecord, "IsQuotaRecord");
|
|
3553
|
+
|
|
3554
|
+
// src/catalog/dto/bundles.dto.ts
|
|
3555
|
+
import { applyDecorators } from "@nestjs/common";
|
|
3498
3556
|
function _ts_decorate17(decorators, target, key, desc) {
|
|
3499
3557
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3500
3558
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -3506,16 +3564,30 @@ function _ts_metadata17(k, v) {
|
|
|
3506
3564
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
3507
3565
|
}
|
|
3508
3566
|
__name(_ts_metadata17, "_ts_metadata");
|
|
3567
|
+
var IsBoundedText = /* @__PURE__ */ __name((max) => applyDecorators(IsString4(), MaxLength3(max)), "IsBoundedText");
|
|
3568
|
+
var IsFeatureKeyList = /* @__PURE__ */ __name(() => applyDecorators(IsArray3(), IsString4({
|
|
3569
|
+
each: true
|
|
3570
|
+
}), Matches3(FEATURE_KEY_PATTERN, {
|
|
3571
|
+
each: true,
|
|
3572
|
+
message: "features entries must be SCREAMING_SNAKE_CASE"
|
|
3573
|
+
})), "IsFeatureKeyList");
|
|
3574
|
+
var IsDecimalAmountOrNull = /* @__PURE__ */ __name((field) => applyDecorators(ValidateIf((_o, value) => value !== null), IsNumberString({
|
|
3575
|
+
no_symbols: false
|
|
3576
|
+
}), Matches3(DECIMAL_PATTERN, {
|
|
3577
|
+
message: `${field} must be a decimal with at most 2 fraction digits (e.g. "9.90")`
|
|
3578
|
+
})), "IsDecimalAmountOrNull");
|
|
3579
|
+
var IsIsoDateOrNull = /* @__PURE__ */ __name((field) => applyDecorators(ValidateIf((_o, value) => value !== null), Matches3(ISO_DATE_PATTERN2, {
|
|
3580
|
+
message: `${field} must be an ISO date (YYYY-MM-DD)`
|
|
3581
|
+
})), "IsIsoDateOrNull");
|
|
3582
|
+
var IsSortOrder = /* @__PURE__ */ __name(() => applyDecorators(IsInt2(), Min(0), Max(1e4)), "IsSortOrder");
|
|
3509
3583
|
var KEY_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
3510
3584
|
var FEATURE_KEY_PATTERN = /^[A-Z][A-Z0-9_]*$/;
|
|
3511
|
-
var PROJECT_KEY_PATTERN4 = /^[a-z][a-z0-9-]*$/;
|
|
3512
3585
|
var DECIMAL_PATTERN = /^\d+(\.\d{1,2})?$/;
|
|
3513
3586
|
var ISO_DATE_PATTERN2 = /^\d{4}-\d{2}-\d{2}(T.*)?$/;
|
|
3514
3587
|
var CreateBundleDto = class {
|
|
3515
3588
|
static {
|
|
3516
3589
|
__name(this, "CreateBundleDto");
|
|
3517
3590
|
}
|
|
3518
|
-
projectKey;
|
|
3519
3591
|
bundleKey;
|
|
3520
3592
|
label;
|
|
3521
3593
|
description;
|
|
@@ -3526,15 +3598,7 @@ var CreateBundleDto = class {
|
|
|
3526
3598
|
};
|
|
3527
3599
|
_ts_decorate17([
|
|
3528
3600
|
IsString4(),
|
|
3529
|
-
|
|
3530
|
-
message: 'projectKey must be kebab-case (e.g. "my-app")'
|
|
3531
|
-
}),
|
|
3532
|
-
MaxLength3(64),
|
|
3533
|
-
_ts_metadata17("design:type", String)
|
|
3534
|
-
], CreateBundleDto.prototype, "projectKey", void 0);
|
|
3535
|
-
_ts_decorate17([
|
|
3536
|
-
IsString4(),
|
|
3537
|
-
Matches4(KEY_PATTERN, {
|
|
3601
|
+
Matches3(KEY_PATTERN, {
|
|
3538
3602
|
message: 'bundleKey must be SCREAMING_SNAKE_CASE (e.g. "BANKING")'
|
|
3539
3603
|
}),
|
|
3540
3604
|
MaxLength3(64),
|
|
@@ -3548,21 +3612,17 @@ _ts_decorate17([
|
|
|
3548
3612
|
], CreateBundleDto.prototype, "label", void 0);
|
|
3549
3613
|
_ts_decorate17([
|
|
3550
3614
|
IsOptional3(),
|
|
3551
|
-
|
|
3552
|
-
MaxLength3(2e3),
|
|
3615
|
+
IsBoundedText(2e3),
|
|
3553
3616
|
_ts_metadata17("design:type", String)
|
|
3554
3617
|
], CreateBundleDto.prototype, "description", void 0);
|
|
3555
3618
|
_ts_decorate17([
|
|
3556
3619
|
IsOptional3(),
|
|
3557
|
-
|
|
3558
|
-
MaxLength3(64),
|
|
3620
|
+
IsBoundedText(64),
|
|
3559
3621
|
_ts_metadata17("design:type", String)
|
|
3560
3622
|
], CreateBundleDto.prototype, "icon", void 0);
|
|
3561
3623
|
_ts_decorate17([
|
|
3562
3624
|
IsOptional3(),
|
|
3563
|
-
|
|
3564
|
-
Min(0),
|
|
3565
|
-
Max(1e4),
|
|
3625
|
+
IsSortOrder(),
|
|
3566
3626
|
_ts_metadata17("design:type", Number)
|
|
3567
3627
|
], CreateBundleDto.prototype, "sortOrder", void 0);
|
|
3568
3628
|
_ts_decorate17([
|
|
@@ -3590,21 +3650,17 @@ _ts_decorate17([
|
|
|
3590
3650
|
], UpdateBundleDto.prototype, "label", void 0);
|
|
3591
3651
|
_ts_decorate17([
|
|
3592
3652
|
IsOptional3(),
|
|
3593
|
-
|
|
3594
|
-
MaxLength3(2e3),
|
|
3653
|
+
IsBoundedText(2e3),
|
|
3595
3654
|
_ts_metadata17("design:type", Object)
|
|
3596
3655
|
], UpdateBundleDto.prototype, "description", void 0);
|
|
3597
3656
|
_ts_decorate17([
|
|
3598
3657
|
IsOptional3(),
|
|
3599
|
-
|
|
3600
|
-
MaxLength3(64),
|
|
3658
|
+
IsBoundedText(64),
|
|
3601
3659
|
_ts_metadata17("design:type", Object)
|
|
3602
3660
|
], UpdateBundleDto.prototype, "icon", void 0);
|
|
3603
3661
|
_ts_decorate17([
|
|
3604
3662
|
IsOptional3(),
|
|
3605
|
-
|
|
3606
|
-
Min(0),
|
|
3607
|
-
Max(1e4),
|
|
3663
|
+
IsSortOrder(),
|
|
3608
3664
|
_ts_metadata17("design:type", Number)
|
|
3609
3665
|
], UpdateBundleDto.prototype, "sortOrder", void 0);
|
|
3610
3666
|
_ts_decorate17([
|
|
@@ -3612,107 +3668,33 @@ _ts_decorate17([
|
|
|
3612
3668
|
IsObject3(),
|
|
3613
3669
|
_ts_metadata17("design:type", typeof Record === "undefined" ? Object : Record)
|
|
3614
3670
|
], UpdateBundleDto.prototype, "i18n", void 0);
|
|
3615
|
-
var
|
|
3671
|
+
var BundlePricingOverrideDto = class {
|
|
3616
3672
|
static {
|
|
3617
|
-
__name(this, "
|
|
3673
|
+
__name(this, "BundlePricingOverrideDto");
|
|
3618
3674
|
}
|
|
3619
|
-
|
|
3620
|
-
quotas;
|
|
3621
|
-
compatibility;
|
|
3622
|
-
pricingOverrides;
|
|
3675
|
+
planId;
|
|
3623
3676
|
monthlyNet;
|
|
3624
3677
|
yearlyNet;
|
|
3625
|
-
marketed;
|
|
3626
|
-
changeNote;
|
|
3627
|
-
baseVersionId;
|
|
3628
|
-
validFrom;
|
|
3629
|
-
validUntil;
|
|
3630
3678
|
};
|
|
3631
|
-
_ts_decorate17([
|
|
3632
|
-
IsArray3(),
|
|
3633
|
-
IsString4({
|
|
3634
|
-
each: true
|
|
3635
|
-
}),
|
|
3636
|
-
Matches4(FEATURE_KEY_PATTERN, {
|
|
3637
|
-
each: true,
|
|
3638
|
-
message: "features entries must be SCREAMING_SNAKE_CASE"
|
|
3639
|
-
}),
|
|
3640
|
-
_ts_metadata17("design:type", Array)
|
|
3641
|
-
], CreateBundleVersionDraftDto.prototype, "features", void 0);
|
|
3642
|
-
_ts_decorate17([
|
|
3643
|
-
IsOptional3(),
|
|
3644
|
-
IsObject3(),
|
|
3645
|
-
_ts_metadata17("design:type", typeof Record === "undefined" ? Object : Record)
|
|
3646
|
-
], CreateBundleVersionDraftDto.prototype, "quotas", void 0);
|
|
3647
|
-
_ts_decorate17([
|
|
3648
|
-
IsOptional3(),
|
|
3649
|
-
IsObject3(),
|
|
3650
|
-
_ts_metadata17("design:type", Object)
|
|
3651
|
-
], CreateBundleVersionDraftDto.prototype, "compatibility", void 0);
|
|
3652
|
-
_ts_decorate17([
|
|
3653
|
-
IsOptional3(),
|
|
3654
|
-
IsArray3(),
|
|
3655
|
-
_ts_metadata17("design:type", typeof Array === "undefined" ? Object : Array)
|
|
3656
|
-
], CreateBundleVersionDraftDto.prototype, "pricingOverrides", void 0);
|
|
3657
|
-
_ts_decorate17([
|
|
3658
|
-
IsOptional3(),
|
|
3659
|
-
ValidateIf((_o, value) => value !== null),
|
|
3660
|
-
IsNumberString({
|
|
3661
|
-
no_symbols: false
|
|
3662
|
-
}),
|
|
3663
|
-
Matches4(DECIMAL_PATTERN, {
|
|
3664
|
-
message: 'monthlyNet must be a decimal with at most 2 fraction digits (e.g. "9.90")'
|
|
3665
|
-
}),
|
|
3666
|
-
_ts_metadata17("design:type", Object)
|
|
3667
|
-
], CreateBundleVersionDraftDto.prototype, "monthlyNet", void 0);
|
|
3668
|
-
_ts_decorate17([
|
|
3669
|
-
IsOptional3(),
|
|
3670
|
-
ValidateIf((_o, value) => value !== null),
|
|
3671
|
-
IsNumberString({
|
|
3672
|
-
no_symbols: false
|
|
3673
|
-
}),
|
|
3674
|
-
Matches4(DECIMAL_PATTERN, {
|
|
3675
|
-
message: "yearlyNet must be a decimal with at most 2 fraction digits"
|
|
3676
|
-
}),
|
|
3677
|
-
_ts_metadata17("design:type", Object)
|
|
3678
|
-
], CreateBundleVersionDraftDto.prototype, "yearlyNet", void 0);
|
|
3679
|
-
_ts_decorate17([
|
|
3680
|
-
IsOptional3(),
|
|
3681
|
-
IsBoolean2(),
|
|
3682
|
-
_ts_metadata17("design:type", Boolean)
|
|
3683
|
-
], CreateBundleVersionDraftDto.prototype, "marketed", void 0);
|
|
3684
3679
|
_ts_decorate17([
|
|
3685
3680
|
IsOptional3(),
|
|
3686
3681
|
IsString4(),
|
|
3687
|
-
MaxLength3(2e3),
|
|
3688
3682
|
_ts_metadata17("design:type", String)
|
|
3689
|
-
],
|
|
3690
|
-
_ts_decorate17([
|
|
3691
|
-
IsOptional3(),
|
|
3692
|
-
IsUUID(),
|
|
3693
|
-
_ts_metadata17("design:type", Object)
|
|
3694
|
-
], CreateBundleVersionDraftDto.prototype, "baseVersionId", void 0);
|
|
3683
|
+
], BundlePricingOverrideDto.prototype, "planId", void 0);
|
|
3695
3684
|
_ts_decorate17([
|
|
3696
3685
|
IsOptional3(),
|
|
3697
|
-
|
|
3698
|
-
Matches4(ISO_DATE_PATTERN2, {
|
|
3699
|
-
message: "validFrom must be an ISO date (YYYY-MM-DD)"
|
|
3700
|
-
}),
|
|
3686
|
+
IsDecimalAmountOrNull("pricingOverrides.monthlyNet"),
|
|
3701
3687
|
_ts_metadata17("design:type", Object)
|
|
3702
|
-
],
|
|
3688
|
+
], BundlePricingOverrideDto.prototype, "monthlyNet", void 0);
|
|
3703
3689
|
_ts_decorate17([
|
|
3704
3690
|
IsOptional3(),
|
|
3705
|
-
|
|
3706
|
-
Matches4(ISO_DATE_PATTERN2, {
|
|
3707
|
-
message: "validUntil must be an ISO date (YYYY-MM-DD)"
|
|
3708
|
-
}),
|
|
3691
|
+
IsDecimalAmountOrNull("pricingOverrides.yearlyNet"),
|
|
3709
3692
|
_ts_metadata17("design:type", Object)
|
|
3710
|
-
],
|
|
3711
|
-
var
|
|
3693
|
+
], BundlePricingOverrideDto.prototype, "yearlyNet", void 0);
|
|
3694
|
+
var BundleVersionDraftFieldsDto = class {
|
|
3712
3695
|
static {
|
|
3713
|
-
__name(this, "
|
|
3696
|
+
__name(this, "BundleVersionDraftFieldsDto");
|
|
3714
3697
|
}
|
|
3715
|
-
features;
|
|
3716
3698
|
quotas;
|
|
3717
3699
|
compatibility;
|
|
3718
3700
|
pricingOverrides;
|
|
@@ -3720,74 +3702,86 @@ var UpdateBundleVersionDraftDto = class {
|
|
|
3720
3702
|
yearlyNet;
|
|
3721
3703
|
marketed;
|
|
3722
3704
|
changeNote;
|
|
3705
|
+
baseVersionId;
|
|
3723
3706
|
validFrom;
|
|
3724
3707
|
validUntil;
|
|
3725
3708
|
};
|
|
3726
|
-
_ts_decorate17([
|
|
3727
|
-
IsOptional3(),
|
|
3728
|
-
IsArray3(),
|
|
3729
|
-
IsString4({
|
|
3730
|
-
each: true
|
|
3731
|
-
}),
|
|
3732
|
-
Matches4(FEATURE_KEY_PATTERN, {
|
|
3733
|
-
each: true
|
|
3734
|
-
}),
|
|
3735
|
-
_ts_metadata17("design:type", Array)
|
|
3736
|
-
], UpdateBundleVersionDraftDto.prototype, "features", void 0);
|
|
3737
3709
|
_ts_decorate17([
|
|
3738
3710
|
IsOptional3(),
|
|
3739
3711
|
IsObject3(),
|
|
3712
|
+
IsQuotaRecord(),
|
|
3740
3713
|
_ts_metadata17("design:type", typeof Record === "undefined" ? Object : Record)
|
|
3741
|
-
],
|
|
3714
|
+
], BundleVersionDraftFieldsDto.prototype, "quotas", void 0);
|
|
3742
3715
|
_ts_decorate17([
|
|
3743
3716
|
IsOptional3(),
|
|
3744
3717
|
IsObject3(),
|
|
3745
3718
|
_ts_metadata17("design:type", Object)
|
|
3746
|
-
],
|
|
3719
|
+
], BundleVersionDraftFieldsDto.prototype, "compatibility", void 0);
|
|
3747
3720
|
_ts_decorate17([
|
|
3748
3721
|
IsOptional3(),
|
|
3749
3722
|
IsArray3(),
|
|
3750
|
-
|
|
3751
|
-
|
|
3723
|
+
ValidateNested({
|
|
3724
|
+
each: true
|
|
3725
|
+
}),
|
|
3726
|
+
Type(() => BundlePricingOverrideDto),
|
|
3727
|
+
_ts_metadata17("design:type", Array)
|
|
3728
|
+
], BundleVersionDraftFieldsDto.prototype, "pricingOverrides", void 0);
|
|
3752
3729
|
_ts_decorate17([
|
|
3753
3730
|
IsOptional3(),
|
|
3754
|
-
|
|
3755
|
-
Matches4(DECIMAL_PATTERN),
|
|
3731
|
+
IsDecimalAmountOrNull("monthlyNet"),
|
|
3756
3732
|
_ts_metadata17("design:type", Object)
|
|
3757
|
-
],
|
|
3733
|
+
], BundleVersionDraftFieldsDto.prototype, "monthlyNet", void 0);
|
|
3758
3734
|
_ts_decorate17([
|
|
3759
3735
|
IsOptional3(),
|
|
3760
|
-
|
|
3761
|
-
Matches4(DECIMAL_PATTERN),
|
|
3736
|
+
IsDecimalAmountOrNull("yearlyNet"),
|
|
3762
3737
|
_ts_metadata17("design:type", Object)
|
|
3763
|
-
],
|
|
3738
|
+
], BundleVersionDraftFieldsDto.prototype, "yearlyNet", void 0);
|
|
3764
3739
|
_ts_decorate17([
|
|
3765
3740
|
IsOptional3(),
|
|
3766
3741
|
IsBoolean2(),
|
|
3767
3742
|
_ts_metadata17("design:type", Boolean)
|
|
3768
|
-
],
|
|
3743
|
+
], BundleVersionDraftFieldsDto.prototype, "marketed", void 0);
|
|
3769
3744
|
_ts_decorate17([
|
|
3770
3745
|
IsOptional3(),
|
|
3771
|
-
|
|
3772
|
-
MaxLength3(2e3),
|
|
3746
|
+
IsBoundedText(2e3),
|
|
3773
3747
|
_ts_metadata17("design:type", String)
|
|
3774
|
-
],
|
|
3748
|
+
], BundleVersionDraftFieldsDto.prototype, "changeNote", void 0);
|
|
3775
3749
|
_ts_decorate17([
|
|
3776
3750
|
IsOptional3(),
|
|
3777
|
-
|
|
3778
|
-
Matches4(ISO_DATE_PATTERN2, {
|
|
3779
|
-
message: "validFrom must be an ISO date (YYYY-MM-DD)"
|
|
3780
|
-
}),
|
|
3751
|
+
IsUUID(),
|
|
3781
3752
|
_ts_metadata17("design:type", Object)
|
|
3782
|
-
],
|
|
3753
|
+
], BundleVersionDraftFieldsDto.prototype, "baseVersionId", void 0);
|
|
3783
3754
|
_ts_decorate17([
|
|
3784
3755
|
IsOptional3(),
|
|
3785
|
-
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
|
|
3756
|
+
IsIsoDateOrNull("validFrom"),
|
|
3757
|
+
_ts_metadata17("design:type", Object)
|
|
3758
|
+
], BundleVersionDraftFieldsDto.prototype, "validFrom", void 0);
|
|
3759
|
+
_ts_decorate17([
|
|
3760
|
+
IsOptional3(),
|
|
3761
|
+
IsIsoDateOrNull("validUntil"),
|
|
3789
3762
|
_ts_metadata17("design:type", Object)
|
|
3790
|
-
],
|
|
3763
|
+
], BundleVersionDraftFieldsDto.prototype, "validUntil", void 0);
|
|
3764
|
+
var CreateBundleVersionDraftDto = class extends BundleVersionDraftFieldsDto {
|
|
3765
|
+
static {
|
|
3766
|
+
__name(this, "CreateBundleVersionDraftDto");
|
|
3767
|
+
}
|
|
3768
|
+
features;
|
|
3769
|
+
};
|
|
3770
|
+
_ts_decorate17([
|
|
3771
|
+
IsFeatureKeyList(),
|
|
3772
|
+
_ts_metadata17("design:type", Array)
|
|
3773
|
+
], CreateBundleVersionDraftDto.prototype, "features", void 0);
|
|
3774
|
+
var UpdateBundleVersionDraftDto = class extends BundleVersionDraftFieldsDto {
|
|
3775
|
+
static {
|
|
3776
|
+
__name(this, "UpdateBundleVersionDraftDto");
|
|
3777
|
+
}
|
|
3778
|
+
features;
|
|
3779
|
+
};
|
|
3780
|
+
_ts_decorate17([
|
|
3781
|
+
IsOptional3(),
|
|
3782
|
+
IsFeatureKeyList(),
|
|
3783
|
+
_ts_metadata17("design:type", Array)
|
|
3784
|
+
], UpdateBundleVersionDraftDto.prototype, "features", void 0);
|
|
3791
3785
|
var PublishBundleVersionDto = class {
|
|
3792
3786
|
static {
|
|
3793
3787
|
__name(this, "PublishBundleVersionDto");
|
|
@@ -3819,23 +3813,17 @@ _ts_decorate17([
|
|
|
3819
3813
|
], PublishBundleVersionDto.prototype, "allowZeroPrice", void 0);
|
|
3820
3814
|
_ts_decorate17([
|
|
3821
3815
|
IsOptional3(),
|
|
3822
|
-
|
|
3823
|
-
Matches4(ISO_DATE_PATTERN2, {
|
|
3824
|
-
message: "validFrom must be an ISO date (YYYY-MM-DD)"
|
|
3825
|
-
}),
|
|
3816
|
+
IsIsoDateOrNull("validFrom"),
|
|
3826
3817
|
_ts_metadata17("design:type", Object)
|
|
3827
3818
|
], PublishBundleVersionDto.prototype, "validFrom", void 0);
|
|
3828
3819
|
_ts_decorate17([
|
|
3829
3820
|
IsOptional3(),
|
|
3830
|
-
|
|
3831
|
-
Matches4(ISO_DATE_PATTERN2, {
|
|
3832
|
-
message: "validUntil must be an ISO date (YYYY-MM-DD)"
|
|
3833
|
-
}),
|
|
3821
|
+
IsIsoDateOrNull("validUntil"),
|
|
3834
3822
|
_ts_metadata17("design:type", Object)
|
|
3835
3823
|
], PublishBundleVersionDto.prototype, "validUntil", void 0);
|
|
3836
3824
|
|
|
3837
3825
|
// src/catalog/bundles.controller.ts
|
|
3838
|
-
import { Body as Body4, Controller as Controller5, Delete as Delete2, Get as Get5, HttpCode as HttpCode2, HttpStatus as HttpStatus2, Inject as Inject14, Param as Param3, ParseUUIDPipe as ParseUUIDPipe2, Patch as Patch3, Post as Post3,
|
|
3826
|
+
import { Body as Body4, Controller as Controller5, Delete as Delete2, Get as Get5, HttpCode as HttpCode2, HttpStatus as HttpStatus2, Inject as Inject14, Param as Param3, ParseUUIDPipe as ParseUUIDPipe2, Patch as Patch3, Post as Post3, UseGuards as UseGuards5 } from "@nestjs/common";
|
|
3839
3827
|
function _ts_decorate18(decorators, target, key, desc) {
|
|
3840
3828
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3841
3829
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -3863,8 +3851,8 @@ function buildBundlesController(guards) {
|
|
|
3863
3851
|
this.service = service;
|
|
3864
3852
|
}
|
|
3865
3853
|
// ─── Root operations ───
|
|
3866
|
-
listBundles(
|
|
3867
|
-
return this.service.listBundles(
|
|
3854
|
+
listBundles() {
|
|
3855
|
+
return this.service.listBundles();
|
|
3868
3856
|
}
|
|
3869
3857
|
getBundle(bundleId) {
|
|
3870
3858
|
return this.service.getBundle(bundleId);
|
|
@@ -3901,11 +3889,8 @@ function buildBundlesController(guards) {
|
|
|
3901
3889
|
};
|
|
3902
3890
|
_ts_decorate18([
|
|
3903
3891
|
Get5(),
|
|
3904
|
-
_ts_param14(0, Query5("projectKey")),
|
|
3905
3892
|
_ts_metadata18("design:type", Function),
|
|
3906
|
-
_ts_metadata18("design:paramtypes", [
|
|
3907
|
-
String
|
|
3908
|
-
]),
|
|
3893
|
+
_ts_metadata18("design:paramtypes", []),
|
|
3909
3894
|
_ts_metadata18("design:returntype", void 0)
|
|
3910
3895
|
], GeneratedBundlesController.prototype, "listBundles", null);
|
|
3911
3896
|
_ts_decorate18([
|
|
@@ -4039,6 +4024,7 @@ function buildBundleVersionsController(guards) {
|
|
|
4039
4024
|
], GeneratedBundleVersionsController.prototype, "discardDraft", null);
|
|
4040
4025
|
_ts_decorate18([
|
|
4041
4026
|
Post3(":id/publish"),
|
|
4027
|
+
EnforceMfa(),
|
|
4042
4028
|
_ts_param14(0, Param3("id", new ParseUUIDPipe2())),
|
|
4043
4029
|
_ts_param14(1, Body4()),
|
|
4044
4030
|
_ts_metadata18("design:type", Function),
|
|
@@ -4062,8 +4048,9 @@ function buildBundleVersionsController(guards) {
|
|
|
4062
4048
|
__name(buildBundleVersionsController, "buildBundleVersionsController");
|
|
4063
4049
|
|
|
4064
4050
|
// src/catalog/dto/marketing-projections.dto.ts
|
|
4065
|
-
import { IsArray as IsArray4, IsBoolean as IsBoolean3, IsIn as IsIn3, IsInt as IsInt3, IsOptional as IsOptional4, IsString as IsString5, IsUUID as IsUUID2, Matches as
|
|
4066
|
-
import { Type } from "class-transformer";
|
|
4051
|
+
import { IsArray as IsArray4, IsBoolean as IsBoolean3, IsIn as IsIn3, IsInt as IsInt3, IsOptional as IsOptional4, IsString as IsString5, IsUUID as IsUUID2, Matches as Matches4, Max as Max2, MaxLength as MaxLength4, Min as Min2, MinLength as MinLength2, ValidateNested as ValidateNested2 } from "class-validator";
|
|
4052
|
+
import { Type as Type2 } from "class-transformer";
|
|
4053
|
+
import { MARKETING_PRIORITY_MAX, MARKETING_PRIORITY_MIN } from "@saasicat/core";
|
|
4067
4054
|
function _ts_decorate19(decorators, target, key, desc) {
|
|
4068
4055
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4069
4056
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -4075,7 +4062,6 @@ function _ts_metadata19(k, v) {
|
|
|
4075
4062
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
4076
4063
|
}
|
|
4077
4064
|
__name(_ts_metadata19, "_ts_metadata");
|
|
4078
|
-
var PROJECT_KEY_PATTERN5 = /^[a-z][a-z0-9-]*$/;
|
|
4079
4065
|
var LOCALE_PATTERN2 = /^[a-z]{2}(-[A-Z]{2})?$/;
|
|
4080
4066
|
var TARGET_TYPES2 = [
|
|
4081
4067
|
"PLAN",
|
|
@@ -4112,7 +4098,6 @@ var CreateMarketingProjectionDto = class {
|
|
|
4112
4098
|
static {
|
|
4113
4099
|
__name(this, "CreateMarketingProjectionDto");
|
|
4114
4100
|
}
|
|
4115
|
-
projectKey;
|
|
4116
4101
|
targetType;
|
|
4117
4102
|
targetVersionId;
|
|
4118
4103
|
locale;
|
|
@@ -4128,14 +4113,6 @@ var CreateMarketingProjectionDto = class {
|
|
|
4128
4113
|
priority;
|
|
4129
4114
|
highlight;
|
|
4130
4115
|
};
|
|
4131
|
-
_ts_decorate19([
|
|
4132
|
-
IsString5(),
|
|
4133
|
-
Matches5(PROJECT_KEY_PATTERN5, {
|
|
4134
|
-
message: "projectKey must be kebab-case"
|
|
4135
|
-
}),
|
|
4136
|
-
MaxLength4(64),
|
|
4137
|
-
_ts_metadata19("design:type", String)
|
|
4138
|
-
], CreateMarketingProjectionDto.prototype, "projectKey", void 0);
|
|
4139
4116
|
_ts_decorate19([
|
|
4140
4117
|
IsString5(),
|
|
4141
4118
|
IsIn3(TARGET_TYPES2),
|
|
@@ -4148,7 +4125,7 @@ _ts_decorate19([
|
|
|
4148
4125
|
_ts_decorate19([
|
|
4149
4126
|
IsOptional4(),
|
|
4150
4127
|
IsString5(),
|
|
4151
|
-
|
|
4128
|
+
Matches4(LOCALE_PATTERN2, {
|
|
4152
4129
|
message: 'locale must be ISO-639-1 (e.g. "de", "en", "de-AT")'
|
|
4153
4130
|
}),
|
|
4154
4131
|
_ts_metadata19("design:type", String)
|
|
@@ -4179,10 +4156,10 @@ _ts_decorate19([
|
|
|
4179
4156
|
_ts_decorate19([
|
|
4180
4157
|
IsOptional4(),
|
|
4181
4158
|
IsArray4(),
|
|
4182
|
-
|
|
4159
|
+
ValidateNested2({
|
|
4183
4160
|
each: true
|
|
4184
4161
|
}),
|
|
4185
|
-
|
|
4162
|
+
Type2(() => MarketingTopFeatureDto),
|
|
4186
4163
|
_ts_metadata19("design:type", Array)
|
|
4187
4164
|
], CreateMarketingProjectionDto.prototype, "topFeatures", void 0);
|
|
4188
4165
|
_ts_decorate19([
|
|
@@ -4212,8 +4189,8 @@ _ts_decorate19([
|
|
|
4212
4189
|
_ts_decorate19([
|
|
4213
4190
|
IsOptional4(),
|
|
4214
4191
|
IsInt3(),
|
|
4215
|
-
Min2(
|
|
4216
|
-
Max2(
|
|
4192
|
+
Min2(MARKETING_PRIORITY_MIN),
|
|
4193
|
+
Max2(MARKETING_PRIORITY_MAX),
|
|
4217
4194
|
_ts_metadata19("design:type", Number)
|
|
4218
4195
|
], CreateMarketingProjectionDto.prototype, "priority", void 0);
|
|
4219
4196
|
_ts_decorate19([
|
|
@@ -4265,10 +4242,10 @@ _ts_decorate19([
|
|
|
4265
4242
|
_ts_decorate19([
|
|
4266
4243
|
IsOptional4(),
|
|
4267
4244
|
IsArray4(),
|
|
4268
|
-
|
|
4245
|
+
ValidateNested2({
|
|
4269
4246
|
each: true
|
|
4270
4247
|
}),
|
|
4271
|
-
|
|
4248
|
+
Type2(() => MarketingTopFeatureDto),
|
|
4272
4249
|
_ts_metadata19("design:type", Array)
|
|
4273
4250
|
], UpdateMarketingProjectionDto.prototype, "topFeatures", void 0);
|
|
4274
4251
|
_ts_decorate19([
|
|
@@ -4298,8 +4275,8 @@ _ts_decorate19([
|
|
|
4298
4275
|
_ts_decorate19([
|
|
4299
4276
|
IsOptional4(),
|
|
4300
4277
|
IsInt3(),
|
|
4301
|
-
Min2(
|
|
4302
|
-
Max2(
|
|
4278
|
+
Min2(MARKETING_PRIORITY_MIN),
|
|
4279
|
+
Max2(MARKETING_PRIORITY_MAX),
|
|
4303
4280
|
_ts_metadata19("design:type", Number)
|
|
4304
4281
|
], UpdateMarketingProjectionDto.prototype, "priority", void 0);
|
|
4305
4282
|
_ts_decorate19([
|
|
@@ -4311,16 +4288,10 @@ var ListMarketingProjectionsQueryDto = class {
|
|
|
4311
4288
|
static {
|
|
4312
4289
|
__name(this, "ListMarketingProjectionsQueryDto");
|
|
4313
4290
|
}
|
|
4314
|
-
projectKey;
|
|
4315
4291
|
targetType;
|
|
4316
4292
|
targetVersionId;
|
|
4317
4293
|
locale;
|
|
4318
4294
|
};
|
|
4319
|
-
_ts_decorate19([
|
|
4320
|
-
IsString5(),
|
|
4321
|
-
Matches5(PROJECT_KEY_PATTERN5),
|
|
4322
|
-
_ts_metadata19("design:type", String)
|
|
4323
|
-
], ListMarketingProjectionsQueryDto.prototype, "projectKey", void 0);
|
|
4324
4295
|
_ts_decorate19([
|
|
4325
4296
|
IsOptional4(),
|
|
4326
4297
|
IsString5(),
|
|
@@ -4335,12 +4306,12 @@ _ts_decorate19([
|
|
|
4335
4306
|
_ts_decorate19([
|
|
4336
4307
|
IsOptional4(),
|
|
4337
4308
|
IsString5(),
|
|
4338
|
-
|
|
4309
|
+
Matches4(LOCALE_PATTERN2),
|
|
4339
4310
|
_ts_metadata19("design:type", String)
|
|
4340
4311
|
], ListMarketingProjectionsQueryDto.prototype, "locale", void 0);
|
|
4341
4312
|
|
|
4342
4313
|
// src/catalog/marketing-projections.controller.ts
|
|
4343
|
-
import { Body as Body5, Controller as Controller6, Delete as Delete3, Get as Get6, HttpCode as HttpCode3, HttpStatus as HttpStatus3, Inject as Inject15, Param as Param4, ParseUUIDPipe as ParseUUIDPipe3, Patch as Patch4, Post as Post4, Query as
|
|
4314
|
+
import { Body as Body5, Controller as Controller6, Delete as Delete3, Get as Get6, HttpCode as HttpCode3, HttpStatus as HttpStatus3, Inject as Inject15, Param as Param4, ParseUUIDPipe as ParseUUIDPipe3, Patch as Patch4, Post as Post4, Query as Query3, UseGuards as UseGuards6 } from "@nestjs/common";
|
|
4344
4315
|
function _ts_decorate20(decorators, target, key, desc) {
|
|
4345
4316
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4346
4317
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -4369,7 +4340,6 @@ function buildMarketingProjectionsController(guards) {
|
|
|
4369
4340
|
}
|
|
4370
4341
|
list(query) {
|
|
4371
4342
|
return this.service.list({
|
|
4372
|
-
projectKey: query.projectKey,
|
|
4373
4343
|
targetType: query.targetType,
|
|
4374
4344
|
targetVersionId: query.targetVersionId,
|
|
4375
4345
|
locale: query.locale
|
|
@@ -4390,7 +4360,7 @@ function buildMarketingProjectionsController(guards) {
|
|
|
4390
4360
|
};
|
|
4391
4361
|
_ts_decorate20([
|
|
4392
4362
|
Get6(),
|
|
4393
|
-
_ts_param15(0,
|
|
4363
|
+
_ts_param15(0, Query3()),
|
|
4394
4364
|
_ts_metadata20("design:type", Function),
|
|
4395
4365
|
_ts_metadata20("design:paramtypes", [
|
|
4396
4366
|
typeof ListMarketingProjectionsQueryDto === "undefined" ? Object : ListMarketingProjectionsQueryDto
|
|
@@ -4450,7 +4420,7 @@ function buildMarketingProjectionsController(guards) {
|
|
|
4450
4420
|
__name(buildMarketingProjectionsController, "buildMarketingProjectionsController");
|
|
4451
4421
|
|
|
4452
4422
|
// src/catalog/dto/plans.dto.ts
|
|
4453
|
-
import { IsInt as IsInt4, IsOptional as IsOptional5, IsString as IsString6, Matches as
|
|
4423
|
+
import { IsInt as IsInt4, IsOptional as IsOptional5, IsString as IsString6, Matches as Matches5, Max as Max3, MaxLength as MaxLength5, Min as Min3, MinLength as MinLength3 } from "class-validator";
|
|
4454
4424
|
function _ts_decorate21(decorators, target, key, desc) {
|
|
4455
4425
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4456
4426
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -4463,12 +4433,10 @@ function _ts_metadata21(k, v) {
|
|
|
4463
4433
|
}
|
|
4464
4434
|
__name(_ts_metadata21, "_ts_metadata");
|
|
4465
4435
|
var KEY_PATTERN2 = /^[A-Z][A-Z0-9_]*$/;
|
|
4466
|
-
var PROJECT_KEY_PATTERN6 = /^[a-z][a-z0-9-]*$/;
|
|
4467
4436
|
var CreatePlanDto = class {
|
|
4468
4437
|
static {
|
|
4469
4438
|
__name(this, "CreatePlanDto");
|
|
4470
4439
|
}
|
|
4471
|
-
projectKey;
|
|
4472
4440
|
planKey;
|
|
4473
4441
|
label;
|
|
4474
4442
|
description;
|
|
@@ -4477,15 +4445,7 @@ var CreatePlanDto = class {
|
|
|
4477
4445
|
};
|
|
4478
4446
|
_ts_decorate21([
|
|
4479
4447
|
IsString6(),
|
|
4480
|
-
|
|
4481
|
-
message: 'projectKey must be kebab-case (e.g. "my-app")'
|
|
4482
|
-
}),
|
|
4483
|
-
MaxLength5(64),
|
|
4484
|
-
_ts_metadata21("design:type", String)
|
|
4485
|
-
], CreatePlanDto.prototype, "projectKey", void 0);
|
|
4486
|
-
_ts_decorate21([
|
|
4487
|
-
IsString6(),
|
|
4488
|
-
Matches6(KEY_PATTERN2, {
|
|
4448
|
+
Matches5(KEY_PATTERN2, {
|
|
4489
4449
|
message: 'planKey must be SCREAMING_SNAKE_CASE (e.g. "STARTER")'
|
|
4490
4450
|
}),
|
|
4491
4451
|
MaxLength5(64),
|
|
@@ -4553,7 +4513,7 @@ _ts_decorate21([
|
|
|
4553
4513
|
], UpdatePlanDto.prototype, "sortOrder", void 0);
|
|
4554
4514
|
|
|
4555
4515
|
// src/catalog/dto/plan-versions.dto.ts
|
|
4556
|
-
import { IsArray as IsArray5, IsBoolean as IsBoolean4, IsISO8601, IsObject as IsObject4, IsOptional as IsOptional6, IsString as IsString7, IsUUID as IsUUID3, Matches as
|
|
4516
|
+
import { IsArray as IsArray5, IsBoolean as IsBoolean4, IsISO8601, IsObject as IsObject4, IsOptional as IsOptional6, IsString as IsString7, IsUUID as IsUUID3, Matches as Matches6, MaxLength as MaxLength6, ValidateIf as ValidateIf2 } from "class-validator";
|
|
4557
4517
|
function _ts_decorate22(decorators, target, key, desc) {
|
|
4558
4518
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4559
4519
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -4593,7 +4553,7 @@ _ts_decorate22([
|
|
|
4593
4553
|
IsString7({
|
|
4594
4554
|
each: true
|
|
4595
4555
|
}),
|
|
4596
|
-
|
|
4556
|
+
Matches6(FEATURE_KEY_PATTERN2, {
|
|
4597
4557
|
each: true,
|
|
4598
4558
|
message: "features entries must be SCREAMING_SNAKE_CASE"
|
|
4599
4559
|
}),
|
|
@@ -4605,7 +4565,7 @@ _ts_decorate22([
|
|
|
4605
4565
|
IsString7({
|
|
4606
4566
|
each: true
|
|
4607
4567
|
}),
|
|
4608
|
-
|
|
4568
|
+
Matches6(FEATURE_KEY_PATTERN2, {
|
|
4609
4569
|
each: true,
|
|
4610
4570
|
message: "bundles entries must be SCREAMING_SNAKE_CASE"
|
|
4611
4571
|
}),
|
|
@@ -4613,16 +4573,17 @@ _ts_decorate22([
|
|
|
4613
4573
|
], CreatePlanVersionDraftDto.prototype, "bundles", void 0);
|
|
4614
4574
|
_ts_decorate22([
|
|
4615
4575
|
IsObject4(),
|
|
4576
|
+
IsQuotaRecord(),
|
|
4616
4577
|
_ts_metadata22("design:type", typeof Record === "undefined" ? Object : Record)
|
|
4617
4578
|
], CreatePlanVersionDraftDto.prototype, "quotas", void 0);
|
|
4618
4579
|
_ts_decorate22([
|
|
4619
|
-
|
|
4580
|
+
Matches6(DECIMAL_PATTERN2, {
|
|
4620
4581
|
message: 'monthlyNet must be a decimal with at most 2 fraction digits (e.g. "9.90")'
|
|
4621
4582
|
}),
|
|
4622
4583
|
_ts_metadata22("design:type", String)
|
|
4623
4584
|
], CreatePlanVersionDraftDto.prototype, "monthlyNet", void 0);
|
|
4624
4585
|
_ts_decorate22([
|
|
4625
|
-
|
|
4586
|
+
Matches6(DECIMAL_PATTERN2, {
|
|
4626
4587
|
message: "yearlyNet must be a decimal with at most 2 fraction digits"
|
|
4627
4588
|
}),
|
|
4628
4589
|
_ts_metadata22("design:type", String)
|
|
@@ -4677,7 +4638,7 @@ _ts_decorate22([
|
|
|
4677
4638
|
IsString7({
|
|
4678
4639
|
each: true
|
|
4679
4640
|
}),
|
|
4680
|
-
|
|
4641
|
+
Matches6(FEATURE_KEY_PATTERN2, {
|
|
4681
4642
|
each: true
|
|
4682
4643
|
}),
|
|
4683
4644
|
_ts_metadata22("design:type", Array)
|
|
@@ -4688,7 +4649,7 @@ _ts_decorate22([
|
|
|
4688
4649
|
IsString7({
|
|
4689
4650
|
each: true
|
|
4690
4651
|
}),
|
|
4691
|
-
|
|
4652
|
+
Matches6(FEATURE_KEY_PATTERN2, {
|
|
4692
4653
|
each: true
|
|
4693
4654
|
}),
|
|
4694
4655
|
_ts_metadata22("design:type", Array)
|
|
@@ -4696,16 +4657,17 @@ _ts_decorate22([
|
|
|
4696
4657
|
_ts_decorate22([
|
|
4697
4658
|
IsOptional6(),
|
|
4698
4659
|
IsObject4(),
|
|
4660
|
+
IsQuotaRecord(),
|
|
4699
4661
|
_ts_metadata22("design:type", typeof Record === "undefined" ? Object : Record)
|
|
4700
4662
|
], UpdatePlanVersionDraftDto.prototype, "quotas", void 0);
|
|
4701
4663
|
_ts_decorate22([
|
|
4702
4664
|
IsOptional6(),
|
|
4703
|
-
|
|
4665
|
+
Matches6(DECIMAL_PATTERN2),
|
|
4704
4666
|
_ts_metadata22("design:type", String)
|
|
4705
4667
|
], UpdatePlanVersionDraftDto.prototype, "monthlyNet", void 0);
|
|
4706
4668
|
_ts_decorate22([
|
|
4707
4669
|
IsOptional6(),
|
|
4708
|
-
|
|
4670
|
+
Matches6(DECIMAL_PATTERN2),
|
|
4709
4671
|
_ts_metadata22("design:type", String)
|
|
4710
4672
|
], UpdatePlanVersionDraftDto.prototype, "yearlyNet", void 0);
|
|
4711
4673
|
_ts_decorate22([
|
|
@@ -4787,7 +4749,7 @@ _ts_decorate22([
|
|
|
4787
4749
|
], PublishPlanVersionDto.prototype, "validUntil", void 0);
|
|
4788
4750
|
|
|
4789
4751
|
// src/catalog/plans.controller.ts
|
|
4790
|
-
import { Body as Body6, Controller as Controller7, Delete as Delete4, Get as Get7, HttpCode as HttpCode4, HttpStatus as HttpStatus4, Inject as Inject16, Optional as Optional8, Param as Param5, ParseUUIDPipe as ParseUUIDPipe4, Patch as Patch5, Post as Post5, Query as
|
|
4752
|
+
import { Body as Body6, Controller as Controller7, Delete as Delete4, Get as Get7, HttpCode as HttpCode4, HttpStatus as HttpStatus4, Inject as Inject16, Optional as Optional8, Param as Param5, ParseUUIDPipe as ParseUUIDPipe4, Patch as Patch5, Post as Post5, Query as Query4, UseGuards as UseGuards7 } from "@nestjs/common";
|
|
4791
4753
|
function _ts_decorate23(decorators, target, key, desc) {
|
|
4792
4754
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
4793
4755
|
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
@@ -4817,15 +4779,15 @@ function buildPlansController(guards) {
|
|
|
4817
4779
|
this.versionsService = versionsService;
|
|
4818
4780
|
}
|
|
4819
4781
|
// ─── Master operations ───
|
|
4820
|
-
listPlans(
|
|
4821
|
-
return this.service.listPlans(
|
|
4782
|
+
listPlans(onlyPublished) {
|
|
4783
|
+
return this.service.listPlans({
|
|
4822
4784
|
onlyPublished: onlyPublished === "true"
|
|
4823
4785
|
});
|
|
4824
4786
|
}
|
|
4825
4787
|
// Static route BEFORE `@Get(':id')` — Fastify prioritizes static over
|
|
4826
4788
|
// parametric; this makes the ordering additionally explicit.
|
|
4827
|
-
getTenantCounts(
|
|
4828
|
-
return this.service.getTenantCounts(
|
|
4789
|
+
getTenantCounts() {
|
|
4790
|
+
return this.service.getTenantCounts();
|
|
4829
4791
|
}
|
|
4830
4792
|
getPlan(planId) {
|
|
4831
4793
|
return this.service.getPlan(planId);
|
|
@@ -4874,22 +4836,17 @@ function buildPlansController(guards) {
|
|
|
4874
4836
|
};
|
|
4875
4837
|
_ts_decorate23([
|
|
4876
4838
|
Get7(),
|
|
4877
|
-
_ts_param16(0,
|
|
4878
|
-
_ts_param16(1, Query7("onlyPublished")),
|
|
4839
|
+
_ts_param16(0, Query4("onlyPublished")),
|
|
4879
4840
|
_ts_metadata23("design:type", Function),
|
|
4880
4841
|
_ts_metadata23("design:paramtypes", [
|
|
4881
|
-
String,
|
|
4882
4842
|
String
|
|
4883
4843
|
]),
|
|
4884
4844
|
_ts_metadata23("design:returntype", void 0)
|
|
4885
4845
|
], GeneratedPlansController.prototype, "listPlans", null);
|
|
4886
4846
|
_ts_decorate23([
|
|
4887
4847
|
Get7("tenant-counts"),
|
|
4888
|
-
_ts_param16(0, Query7("projectKey")),
|
|
4889
4848
|
_ts_metadata23("design:type", Function),
|
|
4890
|
-
_ts_metadata23("design:paramtypes", [
|
|
4891
|
-
String
|
|
4892
|
-
]),
|
|
4849
|
+
_ts_metadata23("design:paramtypes", []),
|
|
4893
4850
|
_ts_metadata23("design:returntype", void 0)
|
|
4894
4851
|
], GeneratedPlansController.prototype, "getTenantCounts", null);
|
|
4895
4852
|
_ts_decorate23([
|
|
@@ -4933,6 +4890,7 @@ function buildPlansController(guards) {
|
|
|
4933
4890
|
], GeneratedPlansController.prototype, "softDeletePlan", null);
|
|
4934
4891
|
_ts_decorate23([
|
|
4935
4892
|
Delete4(":id/purge"),
|
|
4893
|
+
EnforceMfa(),
|
|
4936
4894
|
HttpCode4(HttpStatus4.NO_CONTENT),
|
|
4937
4895
|
_ts_param16(0, Param5("id", new ParseUUIDPipe4())),
|
|
4938
4896
|
_ts_metadata23("design:type", Function),
|
|
@@ -5029,6 +4987,7 @@ function buildPlanVersionsController(guards) {
|
|
|
5029
4987
|
], GeneratedPlanVersionsController.prototype, "updateDraft", null);
|
|
5030
4988
|
_ts_decorate23([
|
|
5031
4989
|
Post5(":id/publish"),
|
|
4990
|
+
EnforceMfa(),
|
|
5032
4991
|
_ts_param16(0, Param5("id", new ParseUUIDPipe4())),
|
|
5033
4992
|
_ts_param16(1, Body6()),
|
|
5034
4993
|
_ts_metadata23("design:type", Function),
|
|
@@ -5050,6 +5009,7 @@ function buildPlanVersionsController(guards) {
|
|
|
5050
5009
|
], GeneratedPlanVersionsController.prototype, "discardDraft", null);
|
|
5051
5010
|
_ts_decorate23([
|
|
5052
5011
|
Post5(":id/terminate"),
|
|
5012
|
+
EnforceMfa(),
|
|
5053
5013
|
_ts_param16(0, Param5("id", new ParseUUIDPipe4())),
|
|
5054
5014
|
_ts_param16(1, Body6()),
|
|
5055
5015
|
_ts_metadata23("design:type", Function),
|
|
@@ -5114,9 +5074,22 @@ var CatalogModule = class _CatalogModule {
|
|
|
5114
5074
|
}
|
|
5115
5075
|
}
|
|
5116
5076
|
if (hasPublicMarketingCatalog && options.publicMarketingCatalog) {
|
|
5117
|
-
controllers.push(buildPublicMarketingCatalogController(options.publicMarketingCatalog.guards, options.publicMarketingCatalog.
|
|
5077
|
+
controllers.push(buildPublicMarketingCatalogController(options.publicMarketingCatalog.guards, options.publicMarketingCatalog.currency, options.publicMarketingCatalog.vatRate));
|
|
5118
5078
|
}
|
|
5119
5079
|
const providers = [
|
|
5080
|
+
// Always provided, `null` included: the service asks for it without
|
|
5081
|
+
// `@Optional()`, so a catalogue that publishes the answer cannot be
|
|
5082
|
+
// wired without one. `useExisting` rather than an instance, because
|
|
5083
|
+
// the source is a provider of the module the application wired for
|
|
5084
|
+
// payments — and where that module is out of scope, Nest says so at
|
|
5085
|
+
// boot instead of answering "no payment method is taken".
|
|
5086
|
+
options.publicMarketingCatalog?.newPaymentMethodsFrom ? {
|
|
5087
|
+
provide: NEW_PAYMENT_METHODS_SOURCE_TOKEN,
|
|
5088
|
+
useExisting: options.publicMarketingCatalog.newPaymentMethodsFrom
|
|
5089
|
+
} : {
|
|
5090
|
+
provide: NEW_PAYMENT_METHODS_SOURCE_TOKEN,
|
|
5091
|
+
useValue: null
|
|
5092
|
+
},
|
|
5120
5093
|
asProvider(BUNDLE_REPOSITORY_TOKEN, options.bundleRepository),
|
|
5121
5094
|
{
|
|
5122
5095
|
provide: CATALOG_SERVICE_CONFIG_TOKEN,
|
|
@@ -5179,6 +5152,9 @@ CatalogModule = _ts_decorate24([
|
|
|
5179
5152
|
], CatalogModule);
|
|
5180
5153
|
|
|
5181
5154
|
export {
|
|
5155
|
+
PLAN_VERSION_WINDOW_CODES,
|
|
5156
|
+
BUNDLE_VERSION_WINDOW_CODES,
|
|
5157
|
+
resolveValidityWindow,
|
|
5182
5158
|
loadApprovedCatalogKeys,
|
|
5183
5159
|
ADVISORY_STRICT_MODE_CODES,
|
|
5184
5160
|
blockingStrictModeWarnings,
|
|
@@ -5189,18 +5165,19 @@ export {
|
|
|
5189
5165
|
featureApprovalSignature,
|
|
5190
5166
|
quotaApprovalSignature,
|
|
5191
5167
|
CatalogEntriesService,
|
|
5168
|
+
defaultActorFromRequest,
|
|
5169
|
+
actorTagOf,
|
|
5170
|
+
WebAuditLogger,
|
|
5192
5171
|
ListCatalogEntriesQueryDto,
|
|
5193
5172
|
ReviewCatalogEntryDto,
|
|
5194
5173
|
UpdateCatalogEntryI18nDto,
|
|
5195
5174
|
SyncDiscoveryDto,
|
|
5196
5175
|
buildCatalogEntriesController,
|
|
5197
5176
|
PromotionsService,
|
|
5198
|
-
ListPromotionsQueryDto,
|
|
5199
5177
|
CreatePromotionDto,
|
|
5200
5178
|
UpdatePromotionDto,
|
|
5201
5179
|
buildPromotionsController,
|
|
5202
5180
|
MarketingSettingsService,
|
|
5203
|
-
ListMarketingSettingsQueryDto,
|
|
5204
5181
|
UpdateMarketingSettingsDto,
|
|
5205
5182
|
buildMarketingSettingsController,
|
|
5206
5183
|
PublicMarketingCatalogService,
|