@saasicat/nest 0.19.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/_entries.cjs +358 -391
- package/dist/admin/index.js +2 -1
- package/dist/{aggregation-o0LMenvl.d.ts → aggregation-DP5zfKbU.d.ts} +3 -3
- package/dist/{aggregation-SKfXu09x.d.cts → aggregation-mZ7WZROK.d.cts} +3 -3
- package/dist/billing/index.d.cts +13 -12
- package/dist/billing/index.d.ts +13 -12
- package/dist/billing/index.js +4 -3
- package/dist/catalog/index.d.cts +10 -10
- package/dist/catalog/index.d.ts +10 -10
- package/dist/catalog/index.js +2 -2
- package/dist/{catalog.module-BKObceo0.d.ts → catalog.module-CQr1FpkI.d.ts} +6 -6
- package/dist/{catalog.module-Dw2Hrbyu.d.cts → catalog.module-D-_pQDNA.d.cts} +6 -6
- package/dist/checkout-offer/index.js +1 -1
- package/dist/{chunk-EUU3B6NB.js → chunk-4O6R6ARQ.js} +12 -6
- package/dist/{chunk-LXUBCLRK.js → chunk-7X5MSAIL.js} +16 -15
- package/dist/{chunk-AFK2IE2L.js → chunk-7ZPFQXHM.js} +1 -1
- package/dist/chunk-EBJJNTW6.js +22 -0
- package/dist/{chunk-6WA4X5U3.js → chunk-MJ32VNZD.js} +41 -91
- package/dist/{chunk-F6DCLVBB.js → chunk-NNNXXUPT.js} +9 -3
- package/dist/{chunk-7JIHG53V.js → chunk-SN5UOC43.js} +7 -4
- package/dist/{chunk-KUDKD7ZG.js → chunk-UFS344JD.js} +26 -22
- package/dist/{chunk-I77VS3YH.js → chunk-UO64MK3Q.js} +18 -24
- package/dist/{chunk-QLJHIIOY.js → chunk-VQQWF7MW.js} +7 -3
- package/dist/{chunk-ORWIGSNN.js → chunk-XTVAFJ3P.js} +2 -1
- package/dist/{define-saasicat-D8c6Bs4T.d.cts → define-saasicat-ByPjtKPy.d.cts} +1 -1
- package/dist/{define-saasicat-CqkhnHn-.d.ts → define-saasicat-C99hWAUK.d.ts} +1 -1
- package/dist/discovery/index.d.cts +2 -2
- package/dist/discovery/index.d.ts +2 -2
- package/dist/{enforce-quota.interceptor-Dk0eEtzV.d.cts → enforce-quota.interceptor-BrsU1UYJ.d.cts} +1 -1
- package/dist/{enforce-quota.interceptor-Dp8wTYro.d.ts → enforce-quota.interceptor-reO-i71U.d.ts} +1 -1
- package/dist/entitlement/index.d.cts +1 -1
- package/dist/entitlement/index.d.ts +1 -1
- package/dist/entitlement/index.js +2 -1
- package/dist/index.d.cts +30 -11
- package/dist/index.d.ts +30 -11
- package/dist/index.js +14 -10
- package/dist/{module-BwD-wAor.d.ts → module-BE17XFLg.d.ts} +1 -1
- package/dist/{module-DsDkbawm.d.cts → module-CHMIyWlf.d.cts} +1 -1
- package/dist/{plan-versions.service-BB36Q1bg.d.ts → plan-versions.service-B-YoE4SZ.d.ts} +1 -1
- package/dist/{plan-versions.service-CUR2Whyr.d.cts → plan-versions.service-_saKzb50.d.cts} +1 -1
- package/dist/platform/index.d.cts +11 -11
- package/dist/platform/index.d.ts +11 -11
- package/dist/platform/index.js +9 -8
- package/dist/promo/index.d.cts +3 -3
- package/dist/promo/index.d.ts +3 -3
- package/dist/promo/index.js +3 -2
- package/dist/registration/index.d.cts +1 -1
- package/dist/registration/index.d.ts +1 -1
- package/dist/registration/index.js +2 -1
- package/dist/{saas-platform.module-BQmrDGVU.d.ts → saas-platform.module-DolE41YY.d.ts} +2 -2
- package/dist/{saas-platform.module-DducnQ9s.d.cts → saas-platform.module-HCDNZTiE.d.cts} +2 -2
- package/dist/{service-DPZ2W1E-.d.cts → service-D48clObR.d.cts} +1 -1
- package/dist/{service-DPZ2W1E-.d.ts → service-D48clObR.d.ts} +1 -1
- package/dist/{tenant-billing.controller-b1G1yB09.d.ts → tenant-billing.controller-7o4ObzGs.d.ts} +3 -3
- package/dist/{tenant-billing.controller-m42YQ_Ei.d.cts → tenant-billing.controller-DubP6-Md.d.cts} +3 -3
- package/dist/testing/index.d.cts +6 -6
- package/dist/testing/index.d.ts +6 -6
- package/dist/testing/index.js +10 -9
- package/package.json +3 -3
|
@@ -330,7 +330,7 @@ var BundlesService = class _BundlesService {
|
|
|
330
330
|
code: CATALOG_ERROR_CODES.BUNDLE_VERSION_NOT_FOUND,
|
|
331
331
|
message: `BundleVersion '${versionId}' not found`,
|
|
332
332
|
params: {
|
|
333
|
-
versionId
|
|
333
|
+
bundleVersionId: versionId
|
|
334
334
|
}
|
|
335
335
|
});
|
|
336
336
|
}
|
|
@@ -364,7 +364,7 @@ var BundlesService = class _BundlesService {
|
|
|
364
364
|
code: CATALOG_ERROR_CODES.BUNDLE_VERSION_NOT_FOUND,
|
|
365
365
|
message: `BundleVersion '${versionId}' not found`,
|
|
366
366
|
params: {
|
|
367
|
-
versionId
|
|
367
|
+
bundleVersionId: versionId
|
|
368
368
|
}
|
|
369
369
|
});
|
|
370
370
|
}
|
|
@@ -461,7 +461,7 @@ var BundlesService = class _BundlesService {
|
|
|
461
461
|
code: CATALOG_ERROR_CODES.BUNDLE_VERSION_NOT_FOUND,
|
|
462
462
|
message: `BundleVersion '${versionId}' not found`,
|
|
463
463
|
params: {
|
|
464
|
-
versionId
|
|
464
|
+
bundleVersionId: versionId
|
|
465
465
|
}
|
|
466
466
|
});
|
|
467
467
|
}
|
|
@@ -497,7 +497,7 @@ var BundlesService = class _BundlesService {
|
|
|
497
497
|
* Publishes a draft version atomically. Steps:
|
|
498
498
|
* 1. Strict check on the draft definition (warn-only or blocking)
|
|
499
499
|
* 2. Enforce validFrom requirement + ordering against the predecessor
|
|
500
|
-
* (
|
|
500
|
+
* (analogous to PlanVersion)
|
|
501
501
|
* 3. Compute the diff against the predecessor version (latest live) →
|
|
502
502
|
* `publishedChanges` + `nonRegressive` via `classifyBundleVersionDiff`
|
|
503
503
|
* 4. Call Repository.publishDraft with the diff result + validity dates
|
|
@@ -510,7 +510,7 @@ var BundlesService = class _BundlesService {
|
|
|
510
510
|
code: CATALOG_ERROR_CODES.BUNDLE_VERSION_NOT_FOUND,
|
|
511
511
|
message: `BundleVersion '${versionId}' not found`,
|
|
512
512
|
params: {
|
|
513
|
-
versionId
|
|
513
|
+
bundleVersionId: versionId
|
|
514
514
|
}
|
|
515
515
|
});
|
|
516
516
|
}
|
|
@@ -546,7 +546,7 @@ var BundlesService = class _BundlesService {
|
|
|
546
546
|
if (!validFromInput) {
|
|
547
547
|
throw new UnprocessableEntityException({
|
|
548
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)
|
|
549
|
+
message: "validFrom must be set when publishing (on the draft or the publish call).."
|
|
550
550
|
});
|
|
551
551
|
}
|
|
552
552
|
const validFrom = new Date(validFromInput);
|
|
@@ -750,9 +750,10 @@ var BundlesService = class _BundlesService {
|
|
|
750
750
|
throw new UnprocessableEntityException({
|
|
751
751
|
code,
|
|
752
752
|
message: `${field} '${input}' is not a valid date`,
|
|
753
|
+
// Keyed by the field name so these params match the publish
|
|
754
|
+
// path, where the same codes carry `validFrom` / `validUntil`.
|
|
753
755
|
params: {
|
|
754
|
-
field
|
|
755
|
-
value: input
|
|
756
|
+
[field]: input
|
|
756
757
|
}
|
|
757
758
|
});
|
|
758
759
|
}
|
|
@@ -2775,7 +2776,7 @@ function buildPublicMarketingCatalogController(guards, projectKey, currency, vat
|
|
|
2775
2776
|
this.service = service;
|
|
2776
2777
|
}
|
|
2777
2778
|
/**
|
|
2778
|
-
* `?lang=de|tr|en` — alias `locale` accepted
|
|
2779
|
+
* `?lang=de|tr|en` — alias `locale` accepted.
|
|
2779
2780
|
* `?asOf=YYYY-MM-DD` — preview of a future or past catalog variant.
|
|
2780
2781
|
* Default = today. Returns the PlanVersion whose `validFrom`/`validUntil`
|
|
2781
2782
|
* span `asOf` inclusively by day (the validUntil day still counts,
|
|
@@ -3286,7 +3287,7 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3286
3287
|
if (!validFromInput) {
|
|
3287
3288
|
throw new UnprocessableEntityException4({
|
|
3288
3289
|
code: CATALOG_ERROR_CODES6.PLAN_VERSION_VALID_FROM_REQUIRED,
|
|
3289
|
-
message: "validFrom must be set when publishing (on the draft or the publish call)
|
|
3290
|
+
message: "validFrom must be set when publishing (on the draft or the publish call).."
|
|
3290
3291
|
});
|
|
3291
3292
|
}
|
|
3292
3293
|
const validFrom = new Date(validFromInput);
|
|
@@ -3409,7 +3410,7 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3409
3410
|
}
|
|
3410
3411
|
if (existing.publishedAt === null) {
|
|
3411
3412
|
throw new UnprocessableEntityException4({
|
|
3412
|
-
code: CATALOG_ERROR_CODES6.
|
|
3413
|
+
code: CATALOG_ERROR_CODES6.PLAN_VERSION_NOT_PUBLISHED,
|
|
3413
3414
|
message: `PlanVersion '${versionId}' is not published and cannot be terminated.`,
|
|
3414
3415
|
params: {
|
|
3415
3416
|
versionId
|
|
@@ -3418,7 +3419,7 @@ var PlanVersionsService = class _PlanVersionsService {
|
|
|
3418
3419
|
}
|
|
3419
3420
|
if (existing.supersededAt !== null) {
|
|
3420
3421
|
throw new UnprocessableEntityException4({
|
|
3421
|
-
code: CATALOG_ERROR_CODES6.
|
|
3422
|
+
code: CATALOG_ERROR_CODES6.PLAN_VERSION_SUPERSEDED,
|
|
3422
3423
|
message: `PlanVersion '${versionId}' has already been superseded by a newer version (supersededAt) and cannot be terminated.`,
|
|
3423
3424
|
params: {
|
|
3424
3425
|
versionId
|
|
@@ -3800,7 +3801,7 @@ var PublishBundleVersionDto = class {
|
|
|
3800
3801
|
allowZeroPrice;
|
|
3801
3802
|
/**
|
|
3802
3803
|
* Required on publish if the draft carries no `validFrom`
|
|
3803
|
-
* (
|
|
3804
|
+
* (analogous to PublishPlanVersionDto).
|
|
3804
3805
|
* Service strictly checks > `validFrom` of the predecessor version.
|
|
3805
3806
|
*/
|
|
3806
3807
|
validFrom;
|
|
@@ -4582,7 +4583,7 @@ var CreatePlanVersionDraftDto = class {
|
|
|
4582
4583
|
marketed;
|
|
4583
4584
|
changeNote;
|
|
4584
4585
|
baseVersionId;
|
|
4585
|
-
/** Optional in the draft (required at publish). ISO date string
|
|
4586
|
+
/** Optional in the draft (required at publish). ISO date string.. */
|
|
4586
4587
|
validFrom;
|
|
4587
4588
|
/** Optional; null = unlimited. ISO date string. */
|
|
4588
4589
|
validUntil;
|
|
@@ -4757,7 +4758,7 @@ var PublishPlanVersionDto = class {
|
|
|
4757
4758
|
allowZeroPrice;
|
|
4758
4759
|
/**
|
|
4759
4760
|
* Required at publish (on the DTO or draft). The service strictly checks
|
|
4760
|
-
* `validFrom > predecessor.validFrom
|
|
4761
|
+
* `validFrom > predecessor.validFrom`..
|
|
4761
4762
|
*/
|
|
4762
4763
|
validFrom;
|
|
4763
4764
|
validUntil;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
__name
|
|
3
|
+
} from "./chunk-SHUYVCID.js";
|
|
4
|
+
|
|
5
|
+
// src/errors/coded-error.ts
|
|
6
|
+
import { ERROR_MESSAGES_EN, formatErrorMessage } from "@saasicat/types";
|
|
7
|
+
function codedError(code, params) {
|
|
8
|
+
const message = formatErrorMessage(ERROR_MESSAGES_EN[code], params ?? {});
|
|
9
|
+
return params ? {
|
|
10
|
+
code,
|
|
11
|
+
message,
|
|
12
|
+
params
|
|
13
|
+
} : {
|
|
14
|
+
code,
|
|
15
|
+
message
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
__name(codedError, "codedError");
|
|
19
|
+
|
|
20
|
+
export {
|
|
21
|
+
codedError
|
|
22
|
+
};
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import {
|
|
2
|
+
codedError
|
|
3
|
+
} from "./chunk-EBJJNTW6.js";
|
|
1
4
|
import {
|
|
2
5
|
asProvider
|
|
3
6
|
} from "./chunk-SABTXESR.js";
|
|
@@ -183,25 +186,17 @@ var PendingRegistrationService = class _PendingRegistrationService {
|
|
|
183
186
|
async selectPlan(input, context) {
|
|
184
187
|
const pending = await this.repo.findById(input.pendingRegistrationId);
|
|
185
188
|
if (!pending) {
|
|
186
|
-
throw new NotFoundException(
|
|
187
|
-
code: REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_NOT_FOUND
|
|
188
|
-
});
|
|
189
|
+
throw new NotFoundException(codedError(REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_NOT_FOUND));
|
|
189
190
|
}
|
|
190
191
|
if (pending.expiresAt.getTime() < Date.now()) {
|
|
191
|
-
throw new BadRequestException(
|
|
192
|
-
code: REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_EXPIRED
|
|
193
|
-
});
|
|
192
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_EXPIRED));
|
|
194
193
|
}
|
|
195
194
|
if (pending.status !== "EMAIL_VERIFIED" && pending.status !== "PLAN_SELECTED" && pending.status !== "CHECKOUT_STARTED") {
|
|
196
|
-
throw new BadRequestException(
|
|
197
|
-
code: REGISTRATION_ERROR_CODES.INVALID_REGISTRATION_STATE
|
|
198
|
-
});
|
|
195
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.INVALID_REGISTRATION_STATE));
|
|
199
196
|
}
|
|
200
197
|
const plan = await this.planCatalog.findPublicSignupPlan(input.planId);
|
|
201
198
|
if (!plan) {
|
|
202
|
-
throw new BadRequestException(
|
|
203
|
-
code: REGISTRATION_ERROR_CODES.PLAN_NOT_AVAILABLE
|
|
204
|
-
});
|
|
199
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.PLAN_NOT_AVAILABLE));
|
|
205
200
|
}
|
|
206
201
|
const now = /* @__PURE__ */ new Date();
|
|
207
202
|
const expiresAt = new Date(now.getTime() + PENDING_ONBOARDING_TTL_DAYS * 24 * 60 * 60 * 1e3);
|
|
@@ -224,30 +219,20 @@ var PendingRegistrationService = class _PendingRegistrationService {
|
|
|
224
219
|
async startCheckout(input, context) {
|
|
225
220
|
const pending = await this.repo.findById(input.pendingRegistrationId);
|
|
226
221
|
if (!pending) {
|
|
227
|
-
throw new NotFoundException(
|
|
228
|
-
code: REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_NOT_FOUND
|
|
229
|
-
});
|
|
222
|
+
throw new NotFoundException(codedError(REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_NOT_FOUND));
|
|
230
223
|
}
|
|
231
224
|
if (pending.expiresAt.getTime() < Date.now()) {
|
|
232
|
-
throw new BadRequestException(
|
|
233
|
-
code: REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_EXPIRED
|
|
234
|
-
});
|
|
225
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_EXPIRED));
|
|
235
226
|
}
|
|
236
227
|
if (pending.status !== "PLAN_SELECTED" && pending.status !== "CHECKOUT_STARTED") {
|
|
237
|
-
throw new BadRequestException(
|
|
238
|
-
code: REGISTRATION_ERROR_CODES.PLAN_NOT_SELECTED
|
|
239
|
-
});
|
|
228
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.PLAN_NOT_SELECTED));
|
|
240
229
|
}
|
|
241
230
|
if (!pending.selectedPlanId) {
|
|
242
|
-
throw new BadRequestException(
|
|
243
|
-
code: REGISTRATION_ERROR_CODES.PLAN_NOT_SELECTED
|
|
244
|
-
});
|
|
231
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.PLAN_NOT_SELECTED));
|
|
245
232
|
}
|
|
246
233
|
const plan = await this.planCatalog.findPublicSignupPlan(pending.selectedPlanId);
|
|
247
234
|
if (!plan) {
|
|
248
|
-
throw new BadRequestException(
|
|
249
|
-
code: REGISTRATION_ERROR_CODES.PLAN_NOT_AVAILABLE
|
|
250
|
-
});
|
|
235
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.PLAN_NOT_AVAILABLE));
|
|
251
236
|
}
|
|
252
237
|
const session = await this.paymentProvider.createCheckoutSession({
|
|
253
238
|
pendingRegistrationId: pending.id,
|
|
@@ -456,9 +441,7 @@ var PendingRegistrationService = class _PendingRegistrationService {
|
|
|
456
441
|
await this.record("OTP_VERIFY_FAILED", null, context, {
|
|
457
442
|
reason: "unknown_email"
|
|
458
443
|
});
|
|
459
|
-
throw new BadRequestException(
|
|
460
|
-
code: REGISTRATION_ERROR_CODES.OTP_INVALID
|
|
461
|
-
});
|
|
444
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.OTP_INVALID));
|
|
462
445
|
}
|
|
463
446
|
if (pending.status !== "PENDING_EMAIL_VERIFICATION") {
|
|
464
447
|
return {
|
|
@@ -471,43 +454,33 @@ var PendingRegistrationService = class _PendingRegistrationService {
|
|
|
471
454
|
await this.record("OTP_VERIFY_FAILED", pending.id, context, {
|
|
472
455
|
reason: "no_otp_set"
|
|
473
456
|
});
|
|
474
|
-
throw new BadRequestException(
|
|
475
|
-
code: REGISTRATION_ERROR_CODES.OTP_INVALID
|
|
476
|
-
});
|
|
457
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.OTP_INVALID));
|
|
477
458
|
}
|
|
478
459
|
if (pending.otpExpiresAt.getTime() < Date.now()) {
|
|
479
460
|
await this.record("OTP_VERIFY_FAILED", pending.id, context, {
|
|
480
461
|
reason: "expired"
|
|
481
462
|
});
|
|
482
|
-
throw new BadRequestException(
|
|
483
|
-
code: REGISTRATION_ERROR_CODES.OTP_EXPIRED
|
|
484
|
-
});
|
|
463
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.OTP_EXPIRED));
|
|
485
464
|
}
|
|
486
465
|
const maxAttempts = resolveOtpVerifyMaxAttempts();
|
|
487
466
|
if (pending.otpAttemptCount >= maxAttempts) {
|
|
488
467
|
await this.record("OTP_VERIFY_FAILED", pending.id, context, {
|
|
489
468
|
reason: "locked"
|
|
490
469
|
});
|
|
491
|
-
throw new BadRequestException(
|
|
492
|
-
code: REGISTRATION_ERROR_CODES.OTP_LOCKED
|
|
493
|
-
});
|
|
470
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.OTP_LOCKED));
|
|
494
471
|
}
|
|
495
472
|
const attemptCount = await this.repo.incrementOtpAttemptCount(pending.id);
|
|
496
473
|
if (attemptCount > maxAttempts) {
|
|
497
474
|
await this.record("OTP_VERIFY_FAILED", pending.id, context, {
|
|
498
475
|
reason: "locked"
|
|
499
476
|
});
|
|
500
|
-
throw new BadRequestException(
|
|
501
|
-
code: REGISTRATION_ERROR_CODES.OTP_LOCKED
|
|
502
|
-
});
|
|
477
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.OTP_LOCKED));
|
|
503
478
|
}
|
|
504
479
|
if (!verifyOtpCode(pending.otpHash, code)) {
|
|
505
480
|
await this.record("OTP_VERIFY_FAILED", pending.id, context, {
|
|
506
481
|
reason: "wrong_code"
|
|
507
482
|
});
|
|
508
|
-
throw new BadRequestException(
|
|
509
|
-
code: REGISTRATION_ERROR_CODES.OTP_INVALID
|
|
510
|
-
});
|
|
483
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.OTP_INVALID));
|
|
511
484
|
}
|
|
512
485
|
const now = /* @__PURE__ */ new Date();
|
|
513
486
|
const updated = await this.repo.update(pending.id, {
|
|
@@ -618,28 +591,20 @@ var PendingRegistrationService = class _PendingRegistrationService {
|
|
|
618
591
|
*/
|
|
619
592
|
async resumeWithToken(input, context) {
|
|
620
593
|
if (!this.resumeTokenSigner) {
|
|
621
|
-
throw new BadRequestException(
|
|
622
|
-
code: REGISTRATION_ERROR_CODES.RESUME_NOT_CONFIGURED
|
|
623
|
-
});
|
|
594
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.RESUME_NOT_CONFIGURED));
|
|
624
595
|
}
|
|
625
596
|
let decoded;
|
|
626
597
|
try {
|
|
627
598
|
decoded = await this.resumeTokenSigner.verify(input.token);
|
|
628
599
|
} catch {
|
|
629
|
-
throw new BadRequestException(
|
|
630
|
-
code: REGISTRATION_ERROR_CODES.RESUME_TOKEN_INVALID
|
|
631
|
-
});
|
|
600
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.RESUME_TOKEN_INVALID));
|
|
632
601
|
}
|
|
633
602
|
const pending = await this.repo.findById(decoded.pendingRegistrationId);
|
|
634
603
|
if (!pending) {
|
|
635
|
-
throw new BadRequestException(
|
|
636
|
-
code: REGISTRATION_ERROR_CODES.RESUME_TOKEN_INVALID
|
|
637
|
-
});
|
|
604
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.RESUME_TOKEN_INVALID));
|
|
638
605
|
}
|
|
639
606
|
if (pending.expiresAt.getTime() < Date.now()) {
|
|
640
|
-
throw new BadRequestException(
|
|
641
|
-
code: REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_EXPIRED
|
|
642
|
-
});
|
|
607
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_EXPIRED));
|
|
643
608
|
}
|
|
644
609
|
await this.record("REGISTRATION_NEUTRAL_REPLAY", pending.id, context, {
|
|
645
610
|
via: "resume_token",
|
|
@@ -667,9 +632,7 @@ var PendingRegistrationService = class _PendingRegistrationService {
|
|
|
667
632
|
/** Returns the (app-specific) configurator catalog. */
|
|
668
633
|
async getConfiguratorCatalog() {
|
|
669
634
|
if (!this.configuratorLookup) {
|
|
670
|
-
throw new BadRequestException(
|
|
671
|
-
code: REGISTRATION_ERROR_CODES.CONFIGURATOR_NOT_CONFIGURED
|
|
672
|
-
});
|
|
635
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.CONFIGURATOR_NOT_CONFIGURED));
|
|
673
636
|
}
|
|
674
637
|
return this.configuratorLookup.getCatalog();
|
|
675
638
|
}
|
|
@@ -686,32 +649,22 @@ var PendingRegistrationService = class _PendingRegistrationService {
|
|
|
686
649
|
*/
|
|
687
650
|
async saveConfiguration(input, context) {
|
|
688
651
|
if (!this.configuratorLookup) {
|
|
689
|
-
throw new BadRequestException(
|
|
690
|
-
code: REGISTRATION_ERROR_CODES.CONFIGURATOR_NOT_CONFIGURED
|
|
691
|
-
});
|
|
652
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.CONFIGURATOR_NOT_CONFIGURED));
|
|
692
653
|
}
|
|
693
654
|
const pending = await this.repo.findById(input.pendingRegistrationId);
|
|
694
655
|
if (!pending) {
|
|
695
|
-
throw new NotFoundException(
|
|
696
|
-
code: REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_NOT_FOUND
|
|
697
|
-
});
|
|
656
|
+
throw new NotFoundException(codedError(REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_NOT_FOUND));
|
|
698
657
|
}
|
|
699
658
|
if (pending.expiresAt.getTime() < Date.now()) {
|
|
700
|
-
throw new BadRequestException(
|
|
701
|
-
code: REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_EXPIRED
|
|
702
|
-
});
|
|
659
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_EXPIRED));
|
|
703
660
|
}
|
|
704
661
|
if (pending.status !== "EMAIL_VERIFIED" && pending.status !== "PLAN_SELECTED" && pending.status !== "CHECKOUT_STARTED") {
|
|
705
|
-
throw new BadRequestException(
|
|
706
|
-
code: REGISTRATION_ERROR_CODES.INVALID_REGISTRATION_STATE
|
|
707
|
-
});
|
|
662
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.INVALID_REGISTRATION_STATE));
|
|
708
663
|
}
|
|
709
664
|
const catalog = await this.configuratorLookup.getCatalog();
|
|
710
665
|
const model = catalog.models.find((m) => m.id === input.selection.modelId);
|
|
711
666
|
if (!model) {
|
|
712
|
-
throw new BadRequestException(
|
|
713
|
-
code: REGISTRATION_ERROR_CODES.MODEL_NOT_AVAILABLE
|
|
714
|
-
});
|
|
667
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.MODEL_NOT_AVAILABLE));
|
|
715
668
|
}
|
|
716
669
|
let promoEval;
|
|
717
670
|
if (input.selection.appliedPromoCode && this.promoPreview) {
|
|
@@ -775,28 +728,20 @@ var PendingRegistrationService = class _PendingRegistrationService {
|
|
|
775
728
|
*/
|
|
776
729
|
async previewConfigPromo(pendingRegistrationId, code) {
|
|
777
730
|
if (!this.configuratorLookup) {
|
|
778
|
-
throw new BadRequestException(
|
|
779
|
-
code: REGISTRATION_ERROR_CODES.CONFIGURATOR_NOT_CONFIGURED
|
|
780
|
-
});
|
|
731
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.CONFIGURATOR_NOT_CONFIGURED));
|
|
781
732
|
}
|
|
782
733
|
const pending = await this.repo.findById(pendingRegistrationId);
|
|
783
734
|
if (!pending) {
|
|
784
|
-
throw new NotFoundException(
|
|
785
|
-
code: REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_NOT_FOUND
|
|
786
|
-
});
|
|
735
|
+
throw new NotFoundException(codedError(REGISTRATION_ERROR_CODES.PENDING_REGISTRATION_NOT_FOUND));
|
|
787
736
|
}
|
|
788
737
|
const config = pending.configJson;
|
|
789
738
|
if (!config) {
|
|
790
|
-
throw new BadRequestException(
|
|
791
|
-
code: REGISTRATION_ERROR_CODES.CONFIG_NOT_SAVED
|
|
792
|
-
});
|
|
739
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.CONFIG_NOT_SAVED));
|
|
793
740
|
}
|
|
794
741
|
const catalog = await this.configuratorLookup.getCatalog();
|
|
795
742
|
const model = catalog.models.find((m) => m.id === config.modelId);
|
|
796
743
|
if (!model) {
|
|
797
|
-
throw new BadRequestException(
|
|
798
|
-
code: REGISTRATION_ERROR_CODES.MODEL_NOT_AVAILABLE
|
|
799
|
-
});
|
|
744
|
+
throw new BadRequestException(codedError(REGISTRATION_ERROR_CODES.MODEL_NOT_AVAILABLE));
|
|
800
745
|
}
|
|
801
746
|
let promoEval;
|
|
802
747
|
if (code && this.promoPreview) {
|
|
@@ -1030,9 +975,14 @@ var BaseIpRateLimitGuard = class {
|
|
|
1030
975
|
const key = ipFingerprint(req);
|
|
1031
976
|
if (this.exceeded(key)) {
|
|
1032
977
|
throw new HttpException({
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
978
|
+
...codedError(REGISTRATION_ERROR_CODES2.RATE_LIMITED, {
|
|
979
|
+
retryAfterSeconds: Math.ceil(this.options.windowMs / 1e3)
|
|
980
|
+
}),
|
|
981
|
+
retryAfterSeconds: Math.ceil(this.options.windowMs / 1e3),
|
|
982
|
+
// Superseded by `code`. Note this carries the limiter name,
|
|
983
|
+
// not the code — the two emitters of RATE_LIMITED disagree
|
|
984
|
+
// on what `reason` means, which is why it is going away.
|
|
985
|
+
reason: this.options.name
|
|
1036
986
|
}, HttpStatus.TOO_MANY_REQUESTS);
|
|
1037
987
|
}
|
|
1038
988
|
return true;
|
|
@@ -1455,7 +1405,7 @@ var SaveRegistrationConfigSelectionDto = class {
|
|
|
1455
1405
|
billingCycle;
|
|
1456
1406
|
appliedPromoCode;
|
|
1457
1407
|
/**
|
|
1458
|
-
*
|
|
1408
|
+
* — Bundle consistency. ID of the `CheckoutOffer` that was
|
|
1459
1409
|
* created on the pricing page (`<app>/register?offer=<id>`). Passed
|
|
1460
1410
|
* through unchanged in `configJson`; the ActivationOrchestrator freezes it
|
|
1461
1411
|
* as `packageSnapshot` when the subscription is created and sets the offer
|
|
@@ -5,6 +5,9 @@ import {
|
|
|
5
5
|
import {
|
|
6
6
|
SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN
|
|
7
7
|
} from "./chunk-NYLON2VC.js";
|
|
8
|
+
import {
|
|
9
|
+
codedError
|
|
10
|
+
} from "./chunk-EBJJNTW6.js";
|
|
8
11
|
import {
|
|
9
12
|
ENTITLEMENT_RESOLUTION_CONFIG_TOKEN,
|
|
10
13
|
ENTITLEMENT_SERVICE_TOKEN,
|
|
@@ -304,7 +307,7 @@ function isLimitExceededError(err) {
|
|
|
304
307
|
__name(isLimitExceededError, "isLimitExceededError");
|
|
305
308
|
|
|
306
309
|
// src/entitlement/service.ts
|
|
307
|
-
import { Inject, Injectable, NotFoundException, Optional } from "@nestjs/common";
|
|
310
|
+
import { Inject, Injectable, InternalServerErrorException, NotFoundException, Optional } from "@nestjs/common";
|
|
308
311
|
import { BILLING_ERROR_CODES } from "@saasicat/types";
|
|
309
312
|
|
|
310
313
|
// src/billing/subscription-bundles.tokens.ts
|
|
@@ -484,7 +487,10 @@ var EntitlementService = class {
|
|
|
484
487
|
const limits = await this.deriveLimits(sub, now, tx);
|
|
485
488
|
const max = limits.quotas[input.dimension];
|
|
486
489
|
if (max === void 0) {
|
|
487
|
-
throw new
|
|
490
|
+
throw new InternalServerErrorException(codedError(BILLING_ERROR_CODES.QUOTA_DIMENSION_UNKNOWN, {
|
|
491
|
+
dimension: input.dimension,
|
|
492
|
+
projectKey: this.catalog.projectKey
|
|
493
|
+
}));
|
|
488
494
|
}
|
|
489
495
|
const used = await input.currentUsage(tx, input.tenantId);
|
|
490
496
|
if (max !== -1 && used + delta > max) {
|
|
@@ -498,7 +504,7 @@ var EntitlementService = class {
|
|
|
498
504
|
// ---------------------------------------------------------------------
|
|
499
505
|
/**
|
|
500
506
|
* Plan fallback for TRIAL/PENDING_SALES: returns the PlanVersion active
|
|
501
|
-
* as of `asOf
|
|
507
|
+
* as of `asOf`. If the repo does not implement
|
|
502
508
|
* `findActive`, we fall back to `findLatestLive` (backward compat for
|
|
503
509
|
* adapters without `validFrom` columns).
|
|
504
510
|
*/
|
|
@@ -7,6 +7,9 @@ import {
|
|
|
7
7
|
import {
|
|
8
8
|
SaaSiCatPublicRoute
|
|
9
9
|
} from "./chunk-YSC46BGM.js";
|
|
10
|
+
import {
|
|
11
|
+
codedError
|
|
12
|
+
} from "./chunk-EBJJNTW6.js";
|
|
10
13
|
import {
|
|
11
14
|
asProvider
|
|
12
15
|
} from "./chunk-SABTXESR.js";
|
|
@@ -199,7 +202,7 @@ var MfaGuard = class _MfaGuard {
|
|
|
199
202
|
const user = request.user;
|
|
200
203
|
if (!user) {
|
|
201
204
|
throw new UnauthorizedException({
|
|
202
|
-
|
|
205
|
+
...codedError(AUTH_ERROR_CODES2.NOT_AUTHENTICATED),
|
|
203
206
|
// `reason` is superseded by `code` and will be removed.
|
|
204
207
|
reason: "NOT_AUTHENTICATED"
|
|
205
208
|
});
|
|
@@ -572,7 +575,7 @@ var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
|
|
|
572
575
|
enabled: true,
|
|
573
576
|
requiredCapability: "dashboard.read"
|
|
574
577
|
},
|
|
575
|
-
//
|
|
578
|
+
// — discovery snapshot of the running backend.
|
|
576
579
|
// Apps must set `discovery.read` in their manifest capabilities
|
|
577
580
|
// to `true` so that the NavBuilder wires the page into the
|
|
578
581
|
// sidebar; otherwise it is filtered out automatically.
|
|
@@ -580,7 +583,7 @@ var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
|
|
|
580
583
|
enabled: true,
|
|
581
584
|
requiredCapability: "discovery.read"
|
|
582
585
|
},
|
|
583
|
-
//
|
|
586
|
+
// — Bundle editor in SuperAdmin.
|
|
584
587
|
// Apps with a DB-driven catalog set `bundles.read = true`.
|
|
585
588
|
bundles: {
|
|
586
589
|
enabled: true,
|
|
@@ -590,7 +593,7 @@ var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
|
|
|
590
593
|
enabled: true,
|
|
591
594
|
requiredCapability: "plans.read"
|
|
592
595
|
},
|
|
593
|
-
//
|
|
596
|
+
// — marketing catalog (locale pivot).
|
|
594
597
|
// Apps that market a public catalog set
|
|
595
598
|
// `marketingProjections.read = true`.
|
|
596
599
|
marketingCatalog: {
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
PromoCodesModule
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-4O6R6ARQ.js";
|
|
4
4
|
import {
|
|
5
5
|
AdminManifestModule,
|
|
6
6
|
AdminManifestService,
|
|
@@ -9,22 +9,22 @@ import {
|
|
|
9
9
|
AdminStatsModule,
|
|
10
10
|
MfaService,
|
|
11
11
|
SuperAdminGuard
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-SN5UOC43.js";
|
|
13
13
|
import {
|
|
14
14
|
PublicCatalogModule,
|
|
15
15
|
REQUIRE_FEATURE_KEY,
|
|
16
16
|
SubscriptionBundleModule,
|
|
17
17
|
TenantBillingModule
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-UO64MK3Q.js";
|
|
19
19
|
import {
|
|
20
20
|
SubscriptionContractModule
|
|
21
21
|
} from "./chunk-B7NRY2LV.js";
|
|
22
22
|
import {
|
|
23
23
|
CatalogModule
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-7X5MSAIL.js";
|
|
25
25
|
import {
|
|
26
26
|
CheckoutOfferModule
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-XTVAFJ3P.js";
|
|
28
28
|
import {
|
|
29
29
|
SaaSiCatPublicRoute
|
|
30
30
|
} from "./chunk-YSC46BGM.js";
|
|
@@ -34,11 +34,14 @@ import {
|
|
|
34
34
|
import {
|
|
35
35
|
EntitlementModule,
|
|
36
36
|
LimitExceededError
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-NNNXXUPT.js";
|
|
38
38
|
import {
|
|
39
39
|
PLAN_CATALOG_TOKEN,
|
|
40
40
|
PlanCatalogModule
|
|
41
41
|
} from "./chunk-DQKCK7DX.js";
|
|
42
|
+
import {
|
|
43
|
+
codedError
|
|
44
|
+
} from "./chunk-EBJJNTW6.js";
|
|
42
45
|
import {
|
|
43
46
|
asProvider
|
|
44
47
|
} from "./chunk-SABTXESR.js";
|
|
@@ -105,9 +108,7 @@ var SetupService = class {
|
|
|
105
108
|
await this.assertNoSuperAdmin();
|
|
106
109
|
const email = req.email.trim().toLowerCase();
|
|
107
110
|
if (!email.includes("@")) {
|
|
108
|
-
throw new BadRequestException(
|
|
109
|
-
code: SETUP_ERROR_CODES.INVALID_EMAIL
|
|
110
|
-
});
|
|
111
|
+
throw new BadRequestException(codedError(SETUP_ERROR_CODES.INVALID_EMAIL));
|
|
111
112
|
}
|
|
112
113
|
const generated = !req.password;
|
|
113
114
|
const password = req.password ?? randomBytes(GENERATED_PASSWORD_BYTES).toString("base64url");
|
|
@@ -164,16 +165,12 @@ var SetupService = class {
|
|
|
164
165
|
});
|
|
165
166
|
}
|
|
166
167
|
if (!token || !timingSafeEqualStr(token, expected)) {
|
|
167
|
-
throw new UnauthorizedException(
|
|
168
|
-
code: SETUP_ERROR_CODES.INVALID_SETUP_TOKEN
|
|
169
|
-
});
|
|
168
|
+
throw new UnauthorizedException(codedError(SETUP_ERROR_CODES.INVALID_SETUP_TOKEN));
|
|
170
169
|
}
|
|
171
170
|
}
|
|
172
171
|
async assertNoSuperAdmin() {
|
|
173
172
|
if (await this.users.countSuperAdmins() > 0) {
|
|
174
|
-
throw new ConflictException(
|
|
175
|
-
code: SETUP_ERROR_CODES.SETUP_ALREADY_DONE
|
|
176
|
-
});
|
|
173
|
+
throw new ConflictException(codedError(SETUP_ERROR_CODES.SETUP_ALREADY_DONE));
|
|
177
174
|
}
|
|
178
175
|
}
|
|
179
176
|
};
|
|
@@ -508,13 +505,14 @@ var StaticFeatureGuard = class {
|
|
|
508
505
|
const snap = await this.entitlements.snapshot(tenantId);
|
|
509
506
|
const allowed = required.some((f) => snap.features.includes(f));
|
|
510
507
|
if (!allowed) {
|
|
511
|
-
|
|
508
|
+
const body = {
|
|
512
509
|
code: FEATURE_NOT_LICENSED,
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
}
|
|
517
|
-
}
|
|
510
|
+
featureKey: required[0],
|
|
511
|
+
featureKeys: required,
|
|
512
|
+
offers: [],
|
|
513
|
+
message: `Feature ${required.join(" / ")} is not included in the current plan.`
|
|
514
|
+
};
|
|
515
|
+
throw new ForbiddenException2(body);
|
|
518
516
|
}
|
|
519
517
|
return true;
|
|
520
518
|
}
|
|
@@ -785,7 +783,7 @@ function buildTenantManifestController(options) {
|
|
|
785
783
|
__name(buildTenantManifestController, "buildTenantManifestController");
|
|
786
784
|
|
|
787
785
|
// src/platform/saas-platform.module.ts
|
|
788
|
-
import { Module as Module2 } from "@nestjs/common";
|
|
786
|
+
import { Logger, Module as Module2 } from "@nestjs/common";
|
|
789
787
|
import { APP_GUARD, APP_INTERCEPTOR } from "@nestjs/core";
|
|
790
788
|
import { assertPersistenceCapabilities } from "@saasicat/types";
|
|
791
789
|
function _ts_decorate9(decorators, target, key, desc) {
|
|
@@ -795,6 +793,7 @@ function _ts_decorate9(decorators, target, key, desc) {
|
|
|
795
793
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
796
794
|
}
|
|
797
795
|
__name(_ts_decorate9, "_ts_decorate");
|
|
796
|
+
var PLATFORM_LOGGER = new Logger("SaaSiCat");
|
|
798
797
|
var PLATFORM_SUBSCRIPTION_REPOSITORY_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/PlatformSubscriptionRepository");
|
|
799
798
|
var STANDARD_MANIFEST_REGISTRATION_TOKEN = /* @__PURE__ */ Symbol.for("saas-platform-nest/StandardManifestRegistration");
|
|
800
799
|
function buildStandardManifestContribution(catalog, adminResources, promoCodes) {
|
|
@@ -1259,6 +1258,11 @@ var SaasPlatformModule = class _SaasPlatformModule {
|
|
|
1259
1258
|
useExisting: EnforceQuotaInterceptor
|
|
1260
1259
|
});
|
|
1261
1260
|
lightweightExports.push(PLAN_RESOLVER_PORT_TOKEN, StaticEntitlementService, StaticFeatureGuard, EnforceQuotaInterceptor, QUOTA_PROVIDERS_TOKEN);
|
|
1261
|
+
if (options.globalFeatureGuard === false) {
|
|
1262
|
+
PLATFORM_LOGGER.warn("globalFeatureGuard is off \u2014 the platform did NOT bind StaticFeatureGuard as an APP_GUARD. @RequireFeature only blocks on routes where you bound a feature guard yourself, behind your auth guard: @UseGuards(JwtAuthGuard, StaticFeatureGuard), or FeatureGuard from @saasicat/nest/billing on the V3 entitlement path. Any annotated route without one serves unlicensed traffic.");
|
|
1263
|
+
}
|
|
1264
|
+
} else {
|
|
1265
|
+
PLATFORM_LOGGER.warn("No plan resolver configured \u2014 @RequireFeature and @EnforceQuota are INERT. The platform registered neither StaticFeatureGuard nor EnforceQuotaInterceptor, so every annotated route serves unlicensed traffic and every quota is unlimited. Set one of: adapters.planResolver, defaultPlanId, or tenantBilling.");
|
|
1262
1266
|
}
|
|
1263
1267
|
const tenantControllers = [];
|
|
1264
1268
|
if (options.tenantManifest) {
|