@saasicat/nest 0.18.0 → 0.19.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/dist/_entries.cjs +1309 -519
  2. package/dist/admin/index.js +1 -1
  3. package/dist/billing/index.d.cts +7 -4
  4. package/dist/billing/index.d.ts +7 -4
  5. package/dist/billing/index.js +6 -6
  6. package/dist/catalog/index.js +4 -4
  7. package/dist/checkout-offer/index.js +2 -2
  8. package/dist/{chunk-Z3FWN3HJ.js → chunk-5ZXBXYXU.js} +3 -3
  9. package/dist/{chunk-RZVSKKXX.js → chunk-6WA4X5U3.js} +46 -45
  10. package/dist/{chunk-V34AA7C7.js → chunk-6WL6VQ7Z.js} +1 -1
  11. package/dist/{chunk-ABB2CRAS.js → chunk-74SCJ4HF.js} +1 -1
  12. package/dist/{chunk-PLJEAUFU.js → chunk-7JIHG53V.js} +76 -39
  13. package/dist/{chunk-7RRWI5LH.js → chunk-AFK2IE2L.js} +10 -10
  14. package/dist/{chunk-32EMXPU2.js → chunk-B7NRY2LV.js} +48 -17
  15. package/dist/{chunk-MDIZUVIK.js → chunk-BSK6YBLI.js} +1 -1
  16. package/dist/{chunk-R2QOSVAA.js → chunk-DQKCK7DX.js} +1 -1
  17. package/dist/{chunk-GLLCB4BP.js → chunk-EUU3B6NB.js} +17 -13
  18. package/dist/{chunk-EXOLOJFZ.js → chunk-F6DCLVBB.js} +29 -8
  19. package/dist/{chunk-5KH2O4SF.js → chunk-I77VS3YH.js} +287 -105
  20. package/dist/{chunk-YITTE646.js → chunk-KUDKD7ZG.js} +34 -14
  21. package/dist/{chunk-P3DP6GJE.js → chunk-LXUBCLRK.js} +486 -160
  22. package/dist/{chunk-GJBXOCRZ.js → chunk-ORWIGSNN.js} +45 -17
  23. package/dist/{chunk-E7AJ42AB.js → chunk-QLJHIIOY.js} +168 -28
  24. package/dist/{chunk-BOADBIGB.js → chunk-YYUXKLXK.js} +2 -2
  25. package/dist/{define-saasicat-Rc0rXrYG.d.ts → define-saasicat-CqkhnHn-.d.ts} +1 -1
  26. package/dist/{define-saasicat-CeDBX9ji.d.cts → define-saasicat-D8c6Bs4T.d.cts} +1 -1
  27. package/dist/discovery/index.d.cts +2 -2
  28. package/dist/discovery/index.d.ts +2 -2
  29. package/dist/discovery/index.js +4 -4
  30. package/dist/{enforce-quota.interceptor-DMw-bPyk.d.cts → enforce-quota.interceptor-Dk0eEtzV.d.cts} +1 -1
  31. package/dist/{enforce-quota.interceptor-CmbWX39L.d.ts → enforce-quota.interceptor-Dp8wTYro.d.ts} +1 -1
  32. package/dist/entitlement/index.js +2 -2
  33. package/dist/index.d.cts +6 -6
  34. package/dist/index.d.ts +6 -6
  35. package/dist/index.js +17 -17
  36. package/dist/{module-DgYJur6f.d.ts → module-BwD-wAor.d.ts} +1 -1
  37. package/dist/{module-CSN8j8Tp.d.cts → module-DsDkbawm.d.cts} +1 -1
  38. package/dist/platform/index.d.cts +8 -8
  39. package/dist/platform/index.d.ts +8 -8
  40. package/dist/platform/index.js +13 -13
  41. package/dist/promo/index.d.cts +3 -3
  42. package/dist/promo/index.d.ts +3 -3
  43. package/dist/promo/index.js +3 -3
  44. package/dist/registration/index.js +1 -1
  45. package/dist/{saas-platform.module-DLhoqKNA.d.ts → saas-platform.module-BQmrDGVU.d.ts} +1 -1
  46. package/dist/{saas-platform.module-D3rUTiho.d.cts → saas-platform.module-DducnQ9s.d.cts} +1 -1
  47. package/dist/{service-DX8KbGXl.d.cts → service-DPZ2W1E-.d.cts} +2 -2
  48. package/dist/{service-DX8KbGXl.d.ts → service-DPZ2W1E-.d.ts} +2 -2
  49. package/dist/subscription-contract/index.js +2 -2
  50. package/dist/{tenant-billing.controller-BwZRnJ3q.d.ts → tenant-billing.controller-b1G1yB09.d.ts} +2 -2
  51. package/dist/{tenant-billing.controller-CG4CaFUP.d.cts → tenant-billing.controller-m42YQ_Ei.d.cts} +2 -2
  52. package/dist/testing/index.d.cts +5 -5
  53. package/dist/testing/index.d.ts +5 -5
  54. package/dist/testing/index.js +33 -33
  55. package/package.json +3 -3
@@ -16,6 +16,7 @@ import {
16
16
 
17
17
  // src/admin/super-admin.guard.ts
18
18
  import { ForbiddenException, Injectable } from "@nestjs/common";
19
+ import { AUTH_ERROR_CODES } from "@saasicat/types";
19
20
  function _ts_decorate(decorators, target, key, desc) {
20
21
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
21
22
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -29,10 +30,18 @@ var SuperAdminGuard = class {
29
30
  }
30
31
  canActivate(context) {
31
32
  const request = context.switchToHttp().getRequest();
32
- if (!request.user) throw new ForbiddenException("Nicht authentifiziert");
33
+ if (!request.user) {
34
+ throw new ForbiddenException({
35
+ code: AUTH_ERROR_CODES.NOT_AUTHENTICATED,
36
+ message: "Not authenticated"
37
+ });
38
+ }
33
39
  const role = request.user.platformRole ?? request.user.role;
34
40
  if (role !== "SUPER_ADMIN") {
35
- throw new ForbiddenException("Nur SUPER_ADMIN-Rolle erlaubt");
41
+ throw new ForbiddenException({
42
+ code: AUTH_ERROR_CODES.SUPER_ADMIN_REQUIRED,
43
+ message: "Only the SUPER_ADMIN role is allowed"
44
+ });
36
45
  }
37
46
  return true;
38
47
  }
@@ -119,7 +128,7 @@ var MfaService = class _MfaService {
119
128
  return false;
120
129
  }
121
130
  const message = error instanceof Error ? error.message : String(error);
122
- this.logger.warn(`TOTP-Verify f\xFCr User ${input.userId} unerwartet fehlgeschlagen: ${message}`);
131
+ this.logger.warn(`TOTP verification for user ${input.userId} failed unexpectedly: ${message}`);
123
132
  return false;
124
133
  }
125
134
  }
@@ -146,6 +155,7 @@ MfaService = _ts_decorate2([
146
155
  // src/admin/mfa.guard.ts
147
156
  import { Inject as Inject2, Injectable as Injectable3, Logger as Logger2, SetMetadata, UnauthorizedException } from "@nestjs/common";
148
157
  import { Reflector } from "@nestjs/core";
158
+ import { AUTH_ERROR_CODES as AUTH_ERROR_CODES2 } from "@saasicat/types";
149
159
  function _ts_decorate3(decorators, target, key, desc) {
150
160
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
151
161
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -187,22 +197,30 @@ var MfaGuard = class _MfaGuard {
187
197
  }
188
198
  const request = context.switchToHttp().getRequest();
189
199
  const user = request.user;
190
- if (!user) throw new UnauthorizedException({
191
- reason: "NOT_AUTHENTICATED"
192
- });
200
+ if (!user) {
201
+ throw new UnauthorizedException({
202
+ code: AUTH_ERROR_CODES2.NOT_AUTHENTICATED,
203
+ // `reason` is superseded by `code` and will be removed.
204
+ reason: "NOT_AUTHENTICATED"
205
+ });
206
+ }
193
207
  const enabled = await this.mfaService.isEnabled(user.id);
194
208
  if (!enabled) {
195
209
  throw new UnauthorizedException({
210
+ code: AUTH_ERROR_CODES2.MFA_NOT_SET_UP,
211
+ // `reason` is superseded by `code` and will be removed.
196
212
  reason: "MFA_NOT_SET_UP",
197
- message: "Bitte zuerst MFA-Setup \xFCber das CLI ausf\xFChren."
213
+ message: "Run the MFA setup via the CLI first."
198
214
  });
199
215
  }
200
216
  const headerVal = request.headers?.["x-mfa-code"];
201
217
  const code = Array.isArray(headerVal) ? headerVal[0] : headerVal;
202
218
  if (!code) {
203
219
  throw new UnauthorizedException({
220
+ code: AUTH_ERROR_CODES2.MFA_REQUIRED,
221
+ // `reason` is superseded by `code` and will be removed.
204
222
  reason: "MFA_REQUIRED",
205
- message: "TOTP-Code im Header X-Mfa-Code erforderlich."
223
+ message: "TOTP code required in the X-Mfa-Code header."
206
224
  });
207
225
  }
208
226
  const valid = await this.mfaService.verify({
@@ -211,8 +229,10 @@ var MfaGuard = class _MfaGuard {
211
229
  });
212
230
  if (!valid) {
213
231
  throw new UnauthorizedException({
232
+ code: AUTH_ERROR_CODES2.MFA_FAILED,
233
+ // `reason` is superseded by `code` and will be removed.
214
234
  reason: "MFA_FAILED",
215
- message: "TOTP-Code ung\xFCltig."
235
+ message: "Invalid TOTP code."
216
236
  });
217
237
  }
218
238
  return true;
@@ -615,7 +635,7 @@ var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
615
635
  actions: [
616
636
  {
617
637
  id: "platform.tenants.suspend",
618
- label: "Mandant suspendieren",
638
+ label: "Suspend tenant",
619
639
  actionKey: "tenants.suspend",
620
640
  requiredCapability: "tenants.suspend",
621
641
  requiresMfa: true,
@@ -623,7 +643,7 @@ var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
623
643
  },
624
644
  {
625
645
  id: "platform.tenants.reactivate",
626
- label: "Mandant reaktivieren",
646
+ label: "Reactivate tenant",
627
647
  actionKey: "tenants.reactivate",
628
648
  requiredCapability: "tenants.reactivate",
629
649
  requiresMfa: true,
@@ -639,7 +659,7 @@ var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
639
659
  },
640
660
  {
641
661
  id: "platform.tenants.export",
642
- label: "DSGVO-Export",
662
+ label: "GDPR export",
643
663
  actionKey: "tenants.export",
644
664
  requiredCapability: "tenants.export",
645
665
  requiresMfa: true,
@@ -647,7 +667,7 @@ var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
647
667
  },
648
668
  {
649
669
  id: "platform.subscriptions.cancel",
650
- label: "Subscription k\xFCndigen",
670
+ label: "Cancel subscription",
651
671
  actionKey: "subscriptions.cancel",
652
672
  requiredCapability: "subscriptions.cancel",
653
673
  requiresMfa: true,
@@ -655,7 +675,7 @@ var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
655
675
  },
656
676
  {
657
677
  id: "platform.pilots.grant",
658
- label: "Pilot-Status setzen",
678
+ label: "Set pilot status",
659
679
  actionKey: "pilots.grant",
660
680
  requiredCapability: "pilots.grant",
661
681
  requiresMfa: true,
@@ -663,7 +683,7 @@ var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
663
683
  },
664
684
  {
665
685
  id: "platform.pilots.revoke",
666
- label: "Pilot-Status entziehen",
686
+ label: "Revoke pilot status",
667
687
  actionKey: "pilots.revoke",
668
688
  requiredCapability: "pilots.revoke",
669
689
  requiresMfa: true,
@@ -671,7 +691,7 @@ var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
671
691
  },
672
692
  {
673
693
  id: "platform.pilots.extend",
674
- label: "Pilot verl\xE4ngern",
694
+ label: "Extend pilot",
675
695
  actionKey: "pilots.extend",
676
696
  requiredCapability: "pilots.extend",
677
697
  requiresMfa: true,
@@ -683,102 +703,102 @@ var PLATFORM_CORE_MANIFEST_CONTRIBUTION = {
683
703
  actions: [
684
704
  {
685
705
  key: "TENANT_SUSPEND",
686
- label: "Mandant suspendiert",
706
+ label: "Tenant suspended",
687
707
  severity: "high"
688
708
  },
689
709
  {
690
710
  key: "TENANT_REACTIVATE",
691
- label: "Mandant reaktiviert",
711
+ label: "Tenant reactivated",
692
712
  severity: "medium"
693
713
  },
694
714
  {
695
715
  key: "TENANT_IMPERSONATE",
696
- label: "Impersonation gestartet",
716
+ label: "Impersonation started",
697
717
  severity: "high"
698
718
  },
699
719
  {
700
720
  key: "TENANT_EXPORT",
701
- label: "DSGVO-Export ausgel\xF6st",
721
+ label: "GDPR export triggered",
702
722
  severity: "medium"
703
723
  },
704
724
  {
705
725
  key: "PILOT_CREATE",
706
- label: "Pilot-Tenant angelegt",
726
+ label: "Pilot tenant created",
707
727
  severity: "medium"
708
728
  },
709
729
  {
710
730
  key: "PILOT_GRANT",
711
- label: "Pilot-Status gesetzt",
731
+ label: "Pilot status set",
712
732
  severity: "medium"
713
733
  },
714
734
  {
715
735
  key: "PILOT_REVOKE",
716
- label: "Pilot-Status entzogen",
736
+ label: "Pilot status revoked",
717
737
  severity: "medium"
718
738
  },
719
739
  {
720
740
  key: "PILOT_EXTEND",
721
- label: "Pilot verl\xE4ngert",
741
+ label: "Pilot extended",
722
742
  severity: "low"
723
743
  },
724
744
  {
725
745
  key: "PLAN_VERSION_PUBLISH",
726
- label: "Plan-Version freigegeben",
746
+ label: "Plan version approved",
727
747
  severity: "high"
728
748
  },
729
749
  {
730
750
  key: "PLAN_VERSION_TERMINATE",
731
- label: "Plan-Version terminiert",
751
+ label: "Plan version terminated",
732
752
  severity: "high"
733
753
  },
734
754
  {
735
755
  key: "SUBSCRIPTION_CANCEL",
736
- label: "Subscription gek\xFCndigt",
756
+ label: "Subscription cancelled",
737
757
  severity: "high"
738
758
  },
739
759
  {
740
760
  key: "USER_RESET_PASSWORD",
741
- label: "Passwort-Reset ausgel\xF6st",
761
+ label: "Password reset triggered",
742
762
  severity: "medium"
743
763
  },
744
764
  {
745
765
  key: "USER_DEACTIVATE",
746
- label: "User deaktiviert",
766
+ label: "User deactivated",
747
767
  severity: "high"
748
768
  },
749
769
  {
750
770
  key: "USER_REASSIGN_ADMIN",
751
- label: "Admin-Rolle \xFCbertragen",
771
+ label: "Admin role transferred",
752
772
  severity: "high"
753
773
  },
754
774
  {
755
775
  key: "PLATFORM_EMAIL_PROVIDER_CREATE",
756
- label: "Plattform-Absender angelegt",
776
+ label: "Platform sender created",
757
777
  severity: "medium"
758
778
  },
759
779
  {
760
780
  key: "PLATFORM_EMAIL_PROVIDER_UPDATE",
761
- label: "Plattform-Absender ge\xE4ndert",
781
+ label: "Platform sender changed",
762
782
  severity: "medium"
763
783
  },
764
784
  {
765
785
  key: "PLATFORM_EMAIL_PROVIDER_DELETE",
766
- label: "Plattform-Absender gel\xF6scht",
786
+ label: "Platform sender deleted",
767
787
  severity: "medium"
768
788
  },
769
789
  {
770
790
  key: "PLATFORM_EMAIL_PROVIDER_TEST",
771
- label: "Plattform-Absender Test-Versand",
791
+ label: "Platform sender test send",
772
792
  severity: "low"
773
793
  },
774
794
  {
775
795
  key: "EMAIL_HISTORY_DELETE",
776
- label: "Plattform-E-Mail aus Verlauf entfernt",
796
+ label: "Platform email removed from history",
777
797
  severity: "medium"
778
798
  },
779
799
  {
780
800
  key: "EMAIL_HISTORY_RESEND",
781
- label: "Plattform-E-Mail erneut versendet",
801
+ label: "Platform email resent",
782
802
  severity: "low"
783
803
  }
784
804
  ]
@@ -898,7 +918,7 @@ var AdminManifestModule = class _AdminManifestModule {
898
918
  ];
899
919
  if (includeManifestController) {
900
920
  if (options.guards === void 0) {
901
- throw new Error("AdminManifestModule.forRoot: `guards` ist Pflicht, wenn `includeManifestController` aktiv ist (Default `true`). Setze `guards: [JwtAuthGuard, SuperAdminGuard]` (oder analog) \u2014 oder `guards: []`, um den Endpoint absichtlich auth-frei zu betreiben. Alternative: `includeManifestController: false` und einen eigenen Wrapping-Controller registrieren.");
921
+ throw new Error("AdminManifestModule.forRoot: `guards` is required when `includeManifestController` is enabled (default `true`). Set `guards: [JwtAuthGuard, SuperAdminGuard]` (or equivalent) \u2014 or `guards: []` to deliberately leave the endpoint unauthenticated. Alternative: `includeManifestController: false` plus registering your own wrapping controller.");
902
922
  }
903
923
  controllers.push(buildManifestController(options.guards, options.reloadGuards ?? []));
904
924
  }
@@ -1135,6 +1155,7 @@ AdminStatsModule = _ts_decorate11([
1135
1155
  // src/admin/admin-resources.module.ts
1136
1156
  import { Body, Controller as Controller4, Get as Get4, Inject as Inject9, Injectable as Injectable7, Module as Module4, NotFoundException, Param, Post as Post2, Query, Req, UseGuards as UseGuards3 } from "@nestjs/common";
1137
1157
  import { IsOptional, IsString, MaxLength } from "class-validator";
1158
+ import { BILLING_ERROR_CODES } from "@saasicat/types";
1138
1159
  function _ts_decorate12(decorators, target, key, desc) {
1139
1160
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
1140
1161
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -1180,7 +1201,15 @@ var AdminResourcesService = class {
1180
1201
  }
1181
1202
  async getTenantDetail(slug) {
1182
1203
  const tenant = await this.resources.getTenantDetail(slug);
1183
- if (!tenant) throw new NotFoundException(`Tenant ${slug} not found`);
1204
+ if (!tenant) {
1205
+ throw new NotFoundException({
1206
+ code: BILLING_ERROR_CODES.TENANT_NOT_FOUND,
1207
+ message: `Tenant ${slug} not found`,
1208
+ params: {
1209
+ slug
1210
+ }
1211
+ });
1212
+ }
1184
1213
  return tenant;
1185
1214
  }
1186
1215
  listUsers(filter) {
@@ -1206,7 +1235,15 @@ var AdminResourcesService = class {
1206
1235
  }
1207
1236
  async setTenantActive(slug, active, subscriptionStatus, actor, audit) {
1208
1237
  const result = await this.resources.setTenantActive(slug, active, subscriptionStatus);
1209
- if (!result) throw new NotFoundException(`Tenant ${slug} not found`);
1238
+ if (!result) {
1239
+ throw new NotFoundException({
1240
+ code: BILLING_ERROR_CODES.TENANT_NOT_FOUND,
1241
+ message: `Tenant ${slug} not found`,
1242
+ params: {
1243
+ slug
1244
+ }
1245
+ });
1246
+ }
1210
1247
  await this.audit.log({
1211
1248
  actor,
1212
1249
  entity: "Tenant",
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  DiscoverySnapshotNotFoundError,
3
3
  loadDiscoverySnapshotFromFile
4
- } from "./chunk-ABB2CRAS.js";
4
+ } from "./chunk-74SCJ4HF.js";
5
5
  import {
6
6
  ADVISORY_STRICT_MODE_CODES,
7
7
  validateBundleDraft,
8
8
  validatePlanDraft
9
- } from "./chunk-P3DP6GJE.js";
9
+ } from "./chunk-LXUBCLRK.js";
10
10
  import {
11
11
  __name
12
12
  } from "./chunk-SHUYVCID.js";
@@ -66,9 +66,9 @@ function runPreflight(input) {
66
66
  __name(runPreflight, "runPreflight");
67
67
  function formatPreflightReport(report) {
68
68
  const lines = [];
69
- lines.push(`Preflight (Status: ${report.overall.toUpperCase()})`, ` Plans: ${report.counts.planFindings} \xB7 Bundles: ${report.counts.bundleFindings} \xB7 Gesamt: ${report.counts.total}`, "");
69
+ lines.push(`Preflight (Status: ${report.overall.toUpperCase()})`, ` Plans: ${report.counts.planFindings} \xB7 Bundles: ${report.counts.bundleFindings} \xB7 Total: ${report.counts.total}`, "");
70
70
  if (report.findings.length === 0) {
71
- lines.push(" \u2713 Keine Strict-Mode-Verst\xF6\xDFe gefunden.");
71
+ lines.push(" \u2713 No strict-mode violations found.");
72
72
  return lines.join("\n");
73
73
  }
74
74
  let lastEntity = "";
@@ -138,9 +138,9 @@ function validateSeedAgainstSnapshot(input) {
138
138
  __name(validateSeedAgainstSnapshot, "validateSeedAgainstSnapshot");
139
139
  function formatSeedGateReport(report) {
140
140
  const lines = [];
141
- lines.push(`Seed-Gate (Status: ${report.overall.toUpperCase()})`, ` Plans: ${report.counts.planFindings} \xB7 Bundles: ${report.counts.bundleFindings} \xB7 Gesamt: ${report.counts.total}`, "");
141
+ lines.push(`Seed-Gate (Status: ${report.overall.toUpperCase()})`, ` Plans: ${report.counts.planFindings} \xB7 Bundles: ${report.counts.bundleFindings} \xB7 Total: ${report.counts.total}`, "");
142
142
  if (report.findings.length === 0) {
143
- lines.push(" \u2713 Alle geseedeten Plan-/Bundle-Features sind discovered (und, sofern gepr\xFCft, approved).");
143
+ lines.push(" \u2713 Every seeded plan/bundle feature is discovered (and approved where checked).");
144
144
  return lines.join("\n");
145
145
  }
146
146
  let lastEntity = "";
@@ -174,10 +174,10 @@ function runSeedGateFromFile(options) {
174
174
  } catch (err) {
175
175
  if (err instanceof DiscoverySnapshotNotFoundError) {
176
176
  if (mode === "blocking") {
177
- error(`[seed-gate] FEHLER: Kein Discovery-Snapshot unter ${options.snapshotPath} \u2014 im blocking-Modus Pflicht (Headless-Scan vor dem Seed ausf\xFChren, #23).`);
177
+ error(`[seed-gate] ERROR: no discovery snapshot at ${options.snapshotPath} \u2014 required in blocking mode (run the headless scan before seeding, #23).`);
178
178
  return exit(4);
179
179
  }
180
- warn(`[seed-gate] Kein Discovery-Snapshot unter ${options.snapshotPath} \u2014 \xFCbersprungen (report-only).`);
180
+ warn(`[seed-gate] No discovery snapshot at ${options.snapshotPath} \u2014 skipped (report-only).`);
181
181
  return null;
182
182
  }
183
183
  throw err;
@@ -191,10 +191,10 @@ function runSeedGateFromFile(options) {
191
191
  log(formatSeedGateReport(report));
192
192
  if (report.overall === "error") {
193
193
  if (mode === "blocking") {
194
- error("[seed-gate] blocking: Verst\xF6\xDFe gefunden \u2014 Seed wird abgebrochen (#23).");
194
+ error("[seed-gate] blocking: violations found \u2014 aborting the seed (#23).");
195
195
  return exit(seedGateExitCode(report));
196
196
  }
197
- warn("[seed-gate] report-only: Verst\xF6\xDFe gefunden, Seed l\xE4uft trotzdem weiter.");
197
+ warn("[seed-gate] report-only: violations found, seeding continues anyway.");
198
198
  }
199
199
  return report;
200
200
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  appendImplicitDiscountLineItem
3
- } from "./chunk-MDIZUVIK.js";
3
+ } from "./chunk-BSK6YBLI.js";
4
4
  import {
5
5
  SUBSCRIPTION_CONTRACT_REPOSITORY_TOKEN
6
6
  } from "./chunk-NYLON2VC.js";
@@ -13,6 +13,7 @@ import {
13
13
 
14
14
  // src/subscription-contract/subscription-contract.service.ts
15
15
  import { ConflictException, Inject, Injectable, NotFoundException, UnprocessableEntityException } from "@nestjs/common";
16
+ import { CONTRACT_ERROR_CODES } from "@saasicat/types";
16
17
  function _ts_decorate(decorators, target, key, desc) {
17
18
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
18
19
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -44,7 +45,13 @@ var SubscriptionContractService = class {
44
45
  async getById(contractId) {
45
46
  const row = await this.repo.findById(contractId);
46
47
  if (!row) {
47
- throw new NotFoundException(`SubscriptionContract '${contractId}' nicht gefunden`);
48
+ throw new NotFoundException({
49
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_NOT_FOUND,
50
+ message: `SubscriptionContract '${contractId}' not found`,
51
+ params: {
52
+ contractId
53
+ }
54
+ });
48
55
  }
49
56
  return row;
50
57
  }
@@ -54,7 +61,14 @@ var SubscriptionContractService = class {
54
61
  async getActiveInvoiceSnapshotForTenant(tenantId, asOf = /* @__PURE__ */ new Date()) {
55
62
  const contract = await this.repo.findActiveByTenantId(tenantId, asOf);
56
63
  if (!contract) {
57
- throw new NotFoundException(`Kein aktiver SubscriptionContract f\xFCr Tenant ${tenantId}`);
64
+ throw new NotFoundException({
65
+ code: CONTRACT_ERROR_CODES.NO_ACTIVE_SUBSCRIPTION_CONTRACT,
66
+ message: `No active subscription contract for tenant ${tenantId}`,
67
+ params: {
68
+ tenantId,
69
+ asOf: asOf.toISOString()
70
+ }
71
+ });
58
72
  }
59
73
  return subscriptionContractToInvoiceSnapshot(contract);
60
74
  }
@@ -87,7 +101,14 @@ var SubscriptionContractService = class {
87
101
  }
88
102
  createDataFromOffer(offer, options) {
89
103
  if (offer.status !== "consumed") {
90
- throw new ConflictException(`CheckoutOffer '${offer.id}' muss vor Contract-Erstellung consumed sein`);
104
+ throw new ConflictException({
105
+ code: CONTRACT_ERROR_CODES.CHECKOUT_OFFER_NOT_CONSUMED,
106
+ message: `CheckoutOffer '${offer.id}' must be consumed before the contract is created`,
107
+ params: {
108
+ offerId: offer.id,
109
+ status: offer.status
110
+ }
111
+ });
91
112
  }
92
113
  const lineItems = this.lineItemsFromOffer(offer);
93
114
  return {
@@ -120,8 +141,8 @@ var SubscriptionContractService = class {
120
141
  const source = offer.lineItems ?? [];
121
142
  if (source.length === 0) {
122
143
  throw new UnprocessableEntityException({
123
- code: "CHECKOUT_OFFER_LINE_ITEMS_REQUIRED",
124
- message: "Aus einem CheckoutOffer kann nur ein Contract entstehen, wenn LineItems eingefroren sind."
144
+ code: CONTRACT_ERROR_CODES.CHECKOUT_OFFER_LINE_ITEMS_REQUIRED,
145
+ message: "A checkout offer can yield only one contract, and only once its line items are frozen."
125
146
  });
126
147
  }
127
148
  return appendImplicitDiscountLineItem({
@@ -159,8 +180,11 @@ var SubscriptionContractService = class {
159
180
  const date = value instanceof Date ? new Date(value) : new Date(value);
160
181
  if (Number.isNaN(date.getTime())) {
161
182
  throw new UnprocessableEntityException({
162
- code: "SUBSCRIPTION_CONTRACT_INVALID_DATE",
163
- message: `${field} muss ein g\xFCltiges Datum sein.`
183
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_INVALID_DATE,
184
+ message: `${field} must be a valid date.`,
185
+ params: {
186
+ field
187
+ }
164
188
  });
165
189
  }
166
190
  return date;
@@ -180,32 +204,39 @@ var SubscriptionContractService = class {
180
204
  assertCreateData(data) {
181
205
  if (data.lineItems.length === 0) {
182
206
  throw new UnprocessableEntityException({
183
- code: "SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED",
184
- message: "SubscriptionContract ben\xF6tigt mindestens eine LineItem."
207
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_LINE_ITEMS_REQUIRED,
208
+ message: "A subscription contract requires at least one line item."
185
209
  });
186
210
  }
187
211
  if (data.effectiveUntil && data.effectiveUntil <= data.effectiveFrom) {
188
212
  throw new UnprocessableEntityException({
189
- code: "SUBSCRIPTION_CONTRACT_INVALID_WINDOW",
190
- message: "effectiveUntil muss nach effectiveFrom liegen."
213
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_INVALID_WINDOW,
214
+ message: "effectiveUntil must be after effectiveFrom."
191
215
  });
192
216
  }
193
217
  const planLineItems = data.lineItems.filter((item) => item.kind === "plan");
194
218
  if (planLineItems.length !== 1) {
195
219
  throw new UnprocessableEntityException({
196
- code: "SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED",
197
- message: "SubscriptionContract ben\xF6tigt genau eine Plan-Grundposition."
220
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_PLAN_LINE_ITEM_REQUIRED,
221
+ message: "A subscription contract requires exactly one plan base item."
198
222
  });
199
223
  }
200
224
  }
201
225
  assertTerminable(existing, data) {
202
226
  if (existing.status === "terminated" || existing.status === "superseded") {
203
- throw new ConflictException(`SubscriptionContract '${existing.id}' ist bereits geschlossen`);
227
+ throw new ConflictException({
228
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_ALREADY_CLOSED,
229
+ message: `SubscriptionContract '${existing.id}' is already closed`,
230
+ params: {
231
+ contractId: existing.id,
232
+ status: existing.status
233
+ }
234
+ });
204
235
  }
205
236
  if (data.effectiveUntil <= existing.effectiveFrom) {
206
237
  throw new UnprocessableEntityException({
207
- code: "SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START",
208
- message: "effectiveUntil muss nach effectiveFrom des Contracts liegen."
238
+ code: CONTRACT_ERROR_CODES.SUBSCRIPTION_CONTRACT_TERMINATION_BEFORE_START,
239
+ message: "effectiveUntil must be after the effectiveFrom of the contract."
209
240
  });
210
241
  }
211
242
  }
@@ -40,7 +40,7 @@ function createDiscountLineItem(input, discountNet) {
40
40
  kind: "discount",
41
41
  sourceKey: promoCode?.code ?? firstPromotion?.id ?? "price-discount",
42
42
  sourceVersionId: null,
43
- titleSnapshot: promoCode?.label ?? firstPromotion?.label ?? "Rabatt",
43
+ titleSnapshot: promoCode?.label ?? firstPromotion?.label ?? "Discount",
44
44
  descriptionSnapshot: promoCode ? `Promo-Code ${promoCode.code}` : null,
45
45
  quantity: 1,
46
46
  unit: null,
@@ -152,7 +152,7 @@ __name(findPlan, "findPlan");
152
152
  function getPlanOrThrow(catalog, planId) {
153
153
  const plan = findPlan(catalog, planId);
154
154
  if (!plan) {
155
- throw new Error(`Plan "${planId}" nicht im Catalog (${catalog.projectKey}) gefunden`);
155
+ throw new Error(`Plan "${planId}" not found in the catalog (${catalog.projectKey})`);
156
156
  }
157
157
  return plan;
158
158
  }
@@ -8,7 +8,7 @@ import {
8
8
  PROMO_SUBSCRIPTION_LOOKUP_TOKEN,
9
9
  PROMO_TRANSACTION_RUNNER_TOKEN,
10
10
  PromoCodesService
11
- } from "./chunk-E7AJ42AB.js";
11
+ } from "./chunk-QLJHIIOY.js";
12
12
  import {
13
13
  AdminAuditService
14
14
  } from "./chunk-E56W4U2P.js";
@@ -58,7 +58,7 @@ var PromoCodeExpirer = class _PromoCodeExpirer {
58
58
  const codes = await this.promoRepo.expireDueCodes(now);
59
59
  const redemptions = await this.redemptionRepo.expireDueRedemptions(now);
60
60
  if (codes > 0 || redemptions > 0) {
61
- this.logger.log(`PromoCodeExpirer: ${codes} Codes, ${redemptions} Redemptions abgelaufen.`);
61
+ this.logger.log(`PromoCodeExpirer: ${codes} Codes, ${redemptions} redemptions expired.`);
62
62
  }
63
63
  }
64
64
  };
@@ -84,6 +84,7 @@ PromoCodeExpirer = _ts_decorate([
84
84
 
85
85
  // src/promo/rate-limit.guard.ts
86
86
  import { HttpException, HttpStatus, Injectable as Injectable2 } from "@nestjs/common";
87
+ import { REGISTRATION_ERROR_CODES } from "@saasicat/types";
87
88
  function _ts_decorate2(decorators, target, key, desc) {
88
89
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
89
90
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
@@ -95,6 +96,15 @@ var IP_WINDOW_MS = 6e4;
95
96
  var IP_LIMIT = 20;
96
97
  var SESSION_WINDOW_MS = 60 * 6e4;
97
98
  var SESSION_LIMIT = 50;
99
+ function rateLimited() {
100
+ return new HttpException({
101
+ valid: false,
102
+ // Superseded by `code` — kept until all consumers read `code`.
103
+ reason: "RATE_LIMITED",
104
+ code: REGISTRATION_ERROR_CODES.RATE_LIMITED
105
+ }, HttpStatus.TOO_MANY_REQUESTS);
106
+ }
107
+ __name(rateLimited, "rateLimited");
98
108
  var PromoCodeRateLimitGuard = class {
99
109
  static {
100
110
  __name(this, "PromoCodeRateLimitGuard");
@@ -106,16 +116,10 @@ var PromoCodeRateLimitGuard = class {
106
116
  const ipKey = ipFingerprint(req);
107
117
  const sessionKey = req.user?.id ?? null;
108
118
  if (this.exceeded(this.ipBuckets, ipKey, IP_WINDOW_MS, IP_LIMIT)) {
109
- throw new HttpException({
110
- valid: false,
111
- reason: "RATE_LIMITED"
112
- }, HttpStatus.TOO_MANY_REQUESTS);
119
+ throw rateLimited();
113
120
  }
114
121
  if (sessionKey && this.exceeded(this.sessionBuckets, sessionKey, SESSION_WINDOW_MS, SESSION_LIMIT)) {
115
- throw new HttpException({
116
- valid: false,
117
- reason: "RATE_LIMITED"
118
- }, HttpStatus.TOO_MANY_REQUESTS);
122
+ throw rateLimited();
119
123
  }
120
124
  return true;
121
125
  }
@@ -179,21 +183,21 @@ var PreviewPromoCodeDto = class {
179
183
  _ts_decorate3([
180
184
  IsString(),
181
185
  Matches(CODE_PATTERN, {
182
- message: 'code muss aus A\u2013Z, 0\u20139, "-" und "_" bestehen (4\u201332 Zeichen)'
186
+ message: 'code must consist of A\u2013Z, 0\u20139, "-" and "_" (4\u201332 characters)'
183
187
  }),
184
188
  _ts_metadata2("design:type", String)
185
189
  ], PreviewPromoCodeDto.prototype, "code", void 0);
186
190
  _ts_decorate3([
187
191
  IsString(),
188
192
  Matches(PLAN_OR_CYCLE_PATTERN, {
189
- message: "plan muss SCREAMING_SNAKE_CASE sein"
193
+ message: "plan must be SCREAMING_SNAKE_CASE"
190
194
  }),
191
195
  _ts_metadata2("design:type", String)
192
196
  ], PreviewPromoCodeDto.prototype, "plan", void 0);
193
197
  _ts_decorate3([
194
198
  IsString(),
195
199
  Matches(PLAN_OR_CYCLE_PATTERN, {
196
- message: "billingCycle muss SCREAMING_SNAKE_CASE sein (z. B. MONTHLY, YEARLY)"
200
+ message: "billingCycle must be SCREAMING_SNAKE_CASE (e.g. MONTHLY, YEARLY)"
197
201
  }),
198
202
  _ts_metadata2("design:type", String)
199
203
  ], PreviewPromoCodeDto.prototype, "billingCycle", void 0);