@saasicat/adapter-drizzle 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 +23 -3
- package/dist/.build-stamp +1 -1
- package/dist/index.cjs +2658 -498
- package/dist/index.d.cts +4458 -776
- package/dist/index.d.ts +4458 -776
- package/dist/index.js +2639 -490
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -6,16 +6,14 @@ var __export = (target, all) => {
|
|
|
6
6
|
};
|
|
7
7
|
|
|
8
8
|
// src/client.ts
|
|
9
|
+
import { readQuotaRecord } from "@saasicat/core";
|
|
9
10
|
var DRIZZLE_DB_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/adapter-drizzle/DrizzleDb");
|
|
10
11
|
function resolveDb(client, tx) {
|
|
11
12
|
return tx ?? client;
|
|
12
13
|
}
|
|
13
14
|
__name(resolveDb, "resolveDb");
|
|
14
15
|
function toQuotaMap(value) {
|
|
15
|
-
|
|
16
|
-
return value;
|
|
17
|
-
}
|
|
18
|
-
return {};
|
|
16
|
+
return readQuotaRecord(value);
|
|
19
17
|
}
|
|
20
18
|
__name(toQuotaMap, "toQuotaMap");
|
|
21
19
|
function toStringArray(value) {
|
|
@@ -30,9 +28,9 @@ __name(escapeLikePattern, "escapeLikePattern");
|
|
|
30
28
|
// src/async-local-rls-bypass.adapter.ts
|
|
31
29
|
import { Injectable } from "@nestjs/common";
|
|
32
30
|
import { AsyncLocalStorage } from "node:async_hooks";
|
|
33
|
-
function _ts_decorate(decorators, target, key,
|
|
34
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
35
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
31
|
+
function _ts_decorate(decorators, target, key, desc10) {
|
|
32
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
33
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
36
34
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
37
35
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
38
36
|
}
|
|
@@ -56,25 +54,40 @@ AsyncLocalRlsBypassAdapter = _ts_decorate([
|
|
|
56
54
|
Injectable()
|
|
57
55
|
], AsyncLocalRlsBypassAdapter);
|
|
58
56
|
|
|
59
|
-
// src/drizzle-
|
|
57
|
+
// src/drizzle-applied-settings.repository.ts
|
|
60
58
|
import { randomUUID } from "node:crypto";
|
|
61
59
|
import { Inject, Injectable as Injectable2 } from "@nestjs/common";
|
|
60
|
+
import { and, desc, eq, isNotNull, isNull } from "drizzle-orm";
|
|
62
61
|
|
|
63
62
|
// src/schema.ts
|
|
64
63
|
var schema_exports = {};
|
|
65
64
|
__export(schema_exports, {
|
|
65
|
+
appliedSettings: () => appliedSettings,
|
|
66
66
|
auditLogs: () => auditLogs,
|
|
67
|
+
bundleVersions: () => bundleVersions,
|
|
68
|
+
bundles: () => bundles,
|
|
69
|
+
contractLineItems: () => contractLineItems,
|
|
67
70
|
featureCatalogEntries: () => featureCatalogEntries,
|
|
71
|
+
paymentEventLog: () => paymentEventLog,
|
|
68
72
|
planVersions: () => planVersions,
|
|
69
73
|
plans: () => plans,
|
|
74
|
+
promoCodeHolds: () => promoCodeHolds,
|
|
70
75
|
promoCodeRedemptions: () => promoCodeRedemptions,
|
|
71
76
|
promoCodeValidationLogs: () => promoCodeValidationLogs,
|
|
72
77
|
promoCodes: () => promoCodes,
|
|
78
|
+
settingsChanges: () => settingsChanges,
|
|
79
|
+
subscriberCorrections: () => subscriberCorrections,
|
|
80
|
+
subscriberPaymentMethodSetups: () => subscriberPaymentMethodSetups,
|
|
81
|
+
subscriberPaymentMethods: () => subscriberPaymentMethods,
|
|
82
|
+
subscriberTenants: () => subscriberTenants,
|
|
83
|
+
subscribers: () => subscribers,
|
|
84
|
+
subscriptionBundles: () => subscriptionBundles,
|
|
85
|
+
subscriptionContracts: () => subscriptionContracts,
|
|
73
86
|
subscriptions: () => subscriptions,
|
|
74
87
|
superAdminMfa: () => superAdminMfa,
|
|
75
88
|
superAdminUsers: () => superAdminUsers
|
|
76
89
|
});
|
|
77
|
-
import { boolean, integer, jsonb, numeric, pgTable, text, timestamp } from "drizzle-orm/pg-core";
|
|
90
|
+
import { bigint, boolean, integer, jsonb, numeric, pgTable, serial, text, timestamp } from "drizzle-orm/pg-core";
|
|
78
91
|
var ts = /* @__PURE__ */ __name((name) => timestamp(name, {
|
|
79
92
|
precision: 3,
|
|
80
93
|
mode: "date"
|
|
@@ -93,6 +106,42 @@ var subscriptions = pgTable("subscriptions", {
|
|
|
93
106
|
pendingPlanVersionId: text("pendingPlanVersionId"),
|
|
94
107
|
isPilot: boolean("isPilot").notNull().default(false),
|
|
95
108
|
startedAt: ts("startedAt"),
|
|
109
|
+
// Entitlement resolution ends a subscription by reading these two, so the
|
|
110
|
+
// query map has to describe them: a column this file omits is a column the
|
|
111
|
+
// repository cannot return, and the platform then treats a subscription
|
|
112
|
+
// that ended last January as running.
|
|
113
|
+
canceledAt: ts("canceledAt"),
|
|
114
|
+
canceledEffectiveAt: ts("canceledEffectiveAt"),
|
|
115
|
+
// The billing window, the commitment and the day it is billed on. Omitted
|
|
116
|
+
// until 2026-08-27, and a column this file omits is one the repositories
|
|
117
|
+
// cannot return — which is why the renewal, plan-change and cancellation
|
|
118
|
+
// paths could not be served from this adapter at all.
|
|
119
|
+
trialEndsAt: ts("trialEndsAt"),
|
|
120
|
+
currentPeriodStart: ts("currentPeriodStart"),
|
|
121
|
+
currentPeriodEnd: ts("currentPeriodEnd"),
|
|
122
|
+
minimumTermUntil: ts("minimumTermUntil"),
|
|
123
|
+
billingAnchorDay: integer("billingAnchorDay"),
|
|
124
|
+
// A pending version change and how far it has got: announced, reminded
|
|
125
|
+
// about, accepted, and the date it lands.
|
|
126
|
+
pendingPlanVersionEffectiveAt: ts("pendingPlanVersionEffectiveAt"),
|
|
127
|
+
pendingPlanVersionAccepted: boolean("pendingPlanVersionAccepted").notNull().default(false),
|
|
128
|
+
pendingPlanVersionAcceptedAt: ts("pendingPlanVersionAcceptedAt"),
|
|
129
|
+
pendingPlanVersionAcceptedByUserId: text("pendingPlanVersionAcceptedByUserId"),
|
|
130
|
+
pendingPlanVersionNotifiedAt: ts("pendingPlanVersionNotifiedAt"),
|
|
131
|
+
pendingPlanVersionReminderSentAt: ts("pendingPlanVersionReminderSentAt"),
|
|
132
|
+
postTrialPlan: text("postTrialPlan"),
|
|
133
|
+
pendingBillingCycle: text("pendingBillingCycle"),
|
|
134
|
+
// A negotiated price and the note that explains it, a pilot window, the
|
|
135
|
+
// offer this subscription came from, and the package as it was sold.
|
|
136
|
+
customMonthlyNet: numeric("customMonthlyNet", {
|
|
137
|
+
precision: 10,
|
|
138
|
+
scale: 2
|
|
139
|
+
}),
|
|
140
|
+
customNote: text("customNote"),
|
|
141
|
+
pilotEndsAt: ts("pilotEndsAt"),
|
|
142
|
+
pilotNote: text("pilotNote"),
|
|
143
|
+
checkoutOfferId: text("checkoutOfferId"),
|
|
144
|
+
packageSnapshot: jsonb("packageSnapshot"),
|
|
96
145
|
createdAt: ts("createdAt").notNull().defaultNow(),
|
|
97
146
|
updatedAt: ts("updatedAt").notNull()
|
|
98
147
|
});
|
|
@@ -117,6 +166,12 @@ var planVersions = pgTable("plan_versions", {
|
|
|
117
166
|
publishedChanges: jsonb("publishedChanges"),
|
|
118
167
|
changeNote: text("changeNote").notNull(),
|
|
119
168
|
nonRegressive: boolean("nonRegressive").notNull().default(true),
|
|
169
|
+
// The booking window for new subscriptions, and the day the plan itself
|
|
170
|
+
// stops. All three nullable, so a schema that predates them stays valid and
|
|
171
|
+
// an adapter that does not maintain them says so rather than writing them.
|
|
172
|
+
validFrom: ts("validFrom"),
|
|
173
|
+
validUntil: ts("validUntil"),
|
|
174
|
+
endsAt: ts("endsAt"),
|
|
120
175
|
createdByUserId: text("createdByUserId"),
|
|
121
176
|
publishedByUserId: text("publishedByUserId"),
|
|
122
177
|
createdAt: ts("createdAt").notNull().defaultNow(),
|
|
@@ -124,7 +179,6 @@ var planVersions = pgTable("plan_versions", {
|
|
|
124
179
|
});
|
|
125
180
|
var plans = pgTable("plans", {
|
|
126
181
|
id: text("id").primaryKey(),
|
|
127
|
-
projectKey: text("projectKey").notNull(),
|
|
128
182
|
planKey: text("planKey").notNull(),
|
|
129
183
|
label: text("label").notNull(),
|
|
130
184
|
description: text("description"),
|
|
@@ -134,9 +188,66 @@ var plans = pgTable("plans", {
|
|
|
134
188
|
updatedAt: ts("updatedAt").notNull(),
|
|
135
189
|
deletedAt: ts("deletedAt")
|
|
136
190
|
});
|
|
191
|
+
var subscriptionContracts = pgTable("subscription_contracts", {
|
|
192
|
+
id: text("id").primaryKey(),
|
|
193
|
+
tenantId: text("tenantId").notNull(),
|
|
194
|
+
// Postgres enum, declared as text: parameterized values are coerced and
|
|
195
|
+
// reads come back as strings, which is what the records expect.
|
|
196
|
+
status: text("status").notNull().default("active"),
|
|
197
|
+
effectiveFrom: ts("effectiveFrom").notNull(),
|
|
198
|
+
effectiveUntil: ts("effectiveUntil"),
|
|
199
|
+
originalOfferId: text("originalOfferId"),
|
|
200
|
+
originalPlanVersionId: text("originalPlanVersionId"),
|
|
201
|
+
originalBundleVersionIds: jsonb("originalBundleVersionIds").notNull(),
|
|
202
|
+
entitlementSnapshot: jsonb("entitlementSnapshot"),
|
|
203
|
+
priceSnapshot: jsonb("priceSnapshot").notNull(),
|
|
204
|
+
promotionSnapshots: jsonb("promotionSnapshots").notNull(),
|
|
205
|
+
promoCodeSnapshots: jsonb("promoCodeSnapshots").notNull(),
|
|
206
|
+
termsSnapshot: jsonb("termsSnapshot"),
|
|
207
|
+
// Who the contract is between, copied when it was concluded.
|
|
208
|
+
subscriberId: text("subscriberId").notNull(),
|
|
209
|
+
subscriberSnapshot: jsonb("subscriberSnapshot").notNull(),
|
|
210
|
+
issuerSnapshot: jsonb("issuerSnapshot"),
|
|
211
|
+
partiesMigrated: boolean("partiesMigrated").notNull().default(false),
|
|
212
|
+
createdAt: ts("createdAt").notNull().defaultNow(),
|
|
213
|
+
updatedAt: ts("updatedAt").notNull()
|
|
214
|
+
});
|
|
215
|
+
var contractLineItems = pgTable("contract_line_items", {
|
|
216
|
+
id: text("id").primaryKey(),
|
|
217
|
+
contractId: text("contractId").notNull(),
|
|
218
|
+
kind: text("kind").notNull(),
|
|
219
|
+
sourceKey: text("sourceKey").notNull(),
|
|
220
|
+
sourceVersionId: text("sourceVersionId"),
|
|
221
|
+
titleSnapshot: text("titleSnapshot").notNull(),
|
|
222
|
+
descriptionSnapshot: text("descriptionSnapshot"),
|
|
223
|
+
quantity: integer("quantity").notNull().default(1),
|
|
224
|
+
unit: text("unit"),
|
|
225
|
+
priceNet: numeric("priceNet", {
|
|
226
|
+
precision: 10,
|
|
227
|
+
scale: 2
|
|
228
|
+
}).notNull(),
|
|
229
|
+
priceGross: numeric("priceGross", {
|
|
230
|
+
precision: 10,
|
|
231
|
+
scale: 2
|
|
232
|
+
}).notNull(),
|
|
233
|
+
billingCycle: text("billingCycle").notNull(),
|
|
234
|
+
currency: text("currency").notNull(),
|
|
235
|
+
taxRate: numeric("taxRate", {
|
|
236
|
+
precision: 5,
|
|
237
|
+
scale: 2
|
|
238
|
+
}).notNull(),
|
|
239
|
+
taxAmount: numeric("taxAmount", {
|
|
240
|
+
precision: 10,
|
|
241
|
+
scale: 2
|
|
242
|
+
}).notNull(),
|
|
243
|
+
minimumTermUntil: ts("minimumTermUntil"),
|
|
244
|
+
featuresSnapshot: jsonb("featuresSnapshot").notNull(),
|
|
245
|
+
quotaEffectsSnapshot: jsonb("quotaEffectsSnapshot").notNull(),
|
|
246
|
+
metadata: jsonb("metadata"),
|
|
247
|
+
createdAt: ts("createdAt").notNull().defaultNow()
|
|
248
|
+
});
|
|
137
249
|
var featureCatalogEntries = pgTable("feature_catalog_entries", {
|
|
138
250
|
id: text("id").primaryKey(),
|
|
139
|
-
projectKey: text("projectKey").notNull(),
|
|
140
251
|
featureKey: text("featureKey").notNull(),
|
|
141
252
|
label: text("label").notNull(),
|
|
142
253
|
description: text("description"),
|
|
@@ -173,6 +284,7 @@ var promoCodes = pgTable("promo_codes", {
|
|
|
173
284
|
validUntil: ts("validUntil"),
|
|
174
285
|
maxRedemptions: integer("maxRedemptions"),
|
|
175
286
|
redemptionsCount: integer("redemptionsCount").notNull().default(0),
|
|
287
|
+
heldCount: integer("heldCount").notNull().default(0),
|
|
176
288
|
appliesToPlans: text("appliesToPlans").array(),
|
|
177
289
|
appliesToBilling: text("appliesToBilling"),
|
|
178
290
|
firstTimeCustomersOnly: boolean("firstTimeCustomersOnly").notNull().default(true),
|
|
@@ -208,6 +320,16 @@ var promoCodeRedemptions = pgTable("promo_code_redemptions", {
|
|
|
208
320
|
redeemedAt: ts("redeemedAt").notNull().defaultNow(),
|
|
209
321
|
reversedAt: ts("reversedAt")
|
|
210
322
|
});
|
|
323
|
+
var promoCodeHolds = pgTable("promo_code_holds", {
|
|
324
|
+
id: text("id").primaryKey(),
|
|
325
|
+
promoCodeId: text("promoCodeId").notNull(),
|
|
326
|
+
checkoutOfferId: text("checkoutOfferId").notNull(),
|
|
327
|
+
expiresAt: ts("expiresAt").notNull(),
|
|
328
|
+
handedOverTx: bigint("handedOverTx", {
|
|
329
|
+
mode: "bigint"
|
|
330
|
+
}),
|
|
331
|
+
createdAt: ts("createdAt").notNull().defaultNow()
|
|
332
|
+
});
|
|
211
333
|
var promoCodeValidationLogs = pgTable("promo_code_validation_logs", {
|
|
212
334
|
id: text("id").primaryKey(),
|
|
213
335
|
promoCodeId: text("promoCodeId"),
|
|
@@ -249,11 +371,166 @@ var superAdminMfa = pgTable("super_admin_mfa", {
|
|
|
249
371
|
enabledAt: ts("enabledAt"),
|
|
250
372
|
updatedAt: ts("updatedAt").notNull()
|
|
251
373
|
});
|
|
374
|
+
var bundles = pgTable("bundles", {
|
|
375
|
+
id: text("id").primaryKey(),
|
|
376
|
+
bundleKey: text("bundleKey").notNull(),
|
|
377
|
+
label: text("label").notNull(),
|
|
378
|
+
description: text("description"),
|
|
379
|
+
icon: text("icon"),
|
|
380
|
+
sortOrder: integer("sortOrder").notNull().default(0),
|
|
381
|
+
i18n: jsonb("i18n").notNull(),
|
|
382
|
+
createdAt: ts("createdAt").notNull().defaultNow(),
|
|
383
|
+
updatedAt: ts("updatedAt").notNull(),
|
|
384
|
+
deletedAt: ts("deletedAt")
|
|
385
|
+
});
|
|
386
|
+
var bundleVersions = pgTable("bundle_versions", {
|
|
387
|
+
id: text("id").primaryKey(),
|
|
388
|
+
bundleId: text("bundleId").notNull(),
|
|
389
|
+
version: integer("version").notNull(),
|
|
390
|
+
baseVersionId: text("baseVersionId"),
|
|
391
|
+
features: jsonb("features").notNull(),
|
|
392
|
+
quotas: jsonb("quotas").notNull(),
|
|
393
|
+
compatibility: jsonb("compatibility").notNull(),
|
|
394
|
+
pricingOverrides: jsonb("pricingOverrides").notNull(),
|
|
395
|
+
// Nullable, unlike a plan version's: a bundle may carry no base price and
|
|
396
|
+
// resolve one per plan through `pricingOverrides` instead.
|
|
397
|
+
monthlyNet: numeric("monthlyNet", {
|
|
398
|
+
precision: 10,
|
|
399
|
+
scale: 2
|
|
400
|
+
}),
|
|
401
|
+
yearlyNet: numeric("yearlyNet", {
|
|
402
|
+
precision: 10,
|
|
403
|
+
scale: 2
|
|
404
|
+
}),
|
|
405
|
+
marketed: boolean("marketed").notNull().default(true),
|
|
406
|
+
publishedAt: ts("publishedAt"),
|
|
407
|
+
supersededAt: ts("supersededAt"),
|
|
408
|
+
publishedChanges: jsonb("publishedChanges"),
|
|
409
|
+
changeNote: text("changeNote").notNull(),
|
|
410
|
+
nonRegressive: boolean("nonRegressive").notNull().default(true),
|
|
411
|
+
validFrom: ts("validFrom"),
|
|
412
|
+
validUntil: ts("validUntil"),
|
|
413
|
+
createdByUserId: text("createdByUserId"),
|
|
414
|
+
publishedByUserId: text("publishedByUserId"),
|
|
415
|
+
createdAt: ts("createdAt").notNull().defaultNow(),
|
|
416
|
+
updatedAt: ts("updatedAt").notNull()
|
|
417
|
+
});
|
|
418
|
+
var subscriptionBundles = pgTable("subscription_bundles", {
|
|
419
|
+
id: text("id").primaryKey(),
|
|
420
|
+
subscriptionId: text("subscriptionId").notNull(),
|
|
421
|
+
bundleVersionId: text("bundleVersionId").notNull(),
|
|
422
|
+
startedAt: ts("startedAt").notNull(),
|
|
423
|
+
minimumTermEndsAt: ts("minimumTermEndsAt"),
|
|
424
|
+
// The rhythm this booking is billed in and the window it is billed for. A
|
|
425
|
+
// bundle's periods end on the day the plan's do; all three are null on a
|
|
426
|
+
// booking made before these columns existed, and on one whose plan had no
|
|
427
|
+
// period to align to — a trial, or a subscription awaiting sales.
|
|
428
|
+
billingCycle: text("billingCycle"),
|
|
429
|
+
currentPeriodStart: ts("currentPeriodStart"),
|
|
430
|
+
currentPeriodEnd: ts("currentPeriodEnd"),
|
|
431
|
+
canceledAt: ts("canceledAt"),
|
|
432
|
+
canceledEffectiveAt: ts("canceledEffectiveAt"),
|
|
433
|
+
createdAt: ts("createdAt").notNull().defaultNow(),
|
|
434
|
+
updatedAt: ts("updatedAt").notNull()
|
|
435
|
+
});
|
|
436
|
+
var appliedSettings = pgTable("applied_settings", {
|
|
437
|
+
id: text("id").primaryKey().default("installation"),
|
|
438
|
+
fingerprint: text("fingerprint").notNull(),
|
|
439
|
+
settings: jsonb("settings").notNull(),
|
|
440
|
+
source: text("source").notNull(),
|
|
441
|
+
appliedAt: ts("appliedAt").notNull()
|
|
442
|
+
});
|
|
443
|
+
var settingsChanges = pgTable("settings_changes", {
|
|
444
|
+
id: text("id").primaryKey(),
|
|
445
|
+
// Numbered by the database at the write that recorded the change, and the
|
|
446
|
+
// order the list is read in. Never set by the adapter.
|
|
447
|
+
seq: serial("seq").notNull(),
|
|
448
|
+
noticedAt: ts("noticedAt").notNull(),
|
|
449
|
+
source: text("source").notNull(),
|
|
450
|
+
previous: jsonb("previous").notNull(),
|
|
451
|
+
current: jsonb("current").notNull(),
|
|
452
|
+
acknowledgedAt: ts("acknowledgedAt"),
|
|
453
|
+
acknowledgedBy: text("acknowledgedBy")
|
|
454
|
+
});
|
|
455
|
+
var subscribers = pgTable("subscribers", {
|
|
456
|
+
id: text("id").primaryKey(),
|
|
457
|
+
// Counted by the database, from where `constraints.postgres.sql` starts it.
|
|
458
|
+
// Never set by the adapter.
|
|
459
|
+
customerSequence: serial("customerSequence").notNull(),
|
|
460
|
+
customerNumberPrefix: text("customerNumberPrefix").notNull().default(""),
|
|
461
|
+
legalName: text("legalName").notNull(),
|
|
462
|
+
vatId: text("vatId"),
|
|
463
|
+
taxNumber: text("taxNumber"),
|
|
464
|
+
addressLine1: text("addressLine1"),
|
|
465
|
+
addressLine2: text("addressLine2"),
|
|
466
|
+
postalCode: text("postalCode"),
|
|
467
|
+
city: text("city"),
|
|
468
|
+
country: text("country"),
|
|
469
|
+
invoiceEmail: text("invoiceEmail"),
|
|
470
|
+
migrated: boolean("migrated").notNull().default(false),
|
|
471
|
+
createdAt: ts("createdAt").notNull().defaultNow(),
|
|
472
|
+
updatedAt: ts("updatedAt").notNull()
|
|
473
|
+
});
|
|
474
|
+
var subscriberTenants = pgTable("subscriber_tenants", {
|
|
475
|
+
id: text("id").primaryKey(),
|
|
476
|
+
subscriberId: text("subscriberId").notNull(),
|
|
477
|
+
tenantId: text("tenantId").notNull(),
|
|
478
|
+
linkedAt: ts("linkedAt").notNull().defaultNow(),
|
|
479
|
+
unlinkedAt: ts("unlinkedAt")
|
|
480
|
+
});
|
|
481
|
+
var subscriberCorrections = pgTable("subscriber_corrections", {
|
|
482
|
+
id: text("id").primaryKey(),
|
|
483
|
+
subscriberId: text("subscriberId").notNull(),
|
|
484
|
+
previous: jsonb("previous").notNull(),
|
|
485
|
+
corrected: jsonb("corrected").notNull(),
|
|
486
|
+
reason: text("reason").notNull(),
|
|
487
|
+
correctedBy: text("correctedBy").notNull(),
|
|
488
|
+
correctedAt: ts("correctedAt").notNull()
|
|
489
|
+
});
|
|
490
|
+
var subscriberPaymentMethodSetups = pgTable("subscriber_payment_method_setups", {
|
|
491
|
+
id: text("id").primaryKey(),
|
|
492
|
+
subscriberId: text("subscriberId").notNull(),
|
|
493
|
+
gatewayAccount: text("gatewayAccount").notNull(),
|
|
494
|
+
sessionRef: text("sessionRef").notNull(),
|
|
495
|
+
customerRef: text("customerRef").notNull(),
|
|
496
|
+
startedAt: ts("startedAt").notNull().defaultNow(),
|
|
497
|
+
completedAt: ts("completedAt")
|
|
498
|
+
});
|
|
499
|
+
var paymentEventLog = pgTable("PaymentEventLog", {
|
|
500
|
+
id: text("id").primaryKey(),
|
|
501
|
+
gatewayAccount: text("gatewayAccount").notNull(),
|
|
502
|
+
eventId: text("eventId").notNull(),
|
|
503
|
+
provider: text("provider").notNull(),
|
|
504
|
+
sessionId: text("sessionId"),
|
|
505
|
+
status: text("status").notNull(),
|
|
506
|
+
payload: jsonb("payload"),
|
|
507
|
+
processedAt: ts("processedAt").notNull().defaultNow()
|
|
508
|
+
});
|
|
509
|
+
var subscriberPaymentMethods = pgTable("subscriber_payment_methods", {
|
|
510
|
+
id: text("id").primaryKey(),
|
|
511
|
+
subscriberId: text("subscriberId").notNull(),
|
|
512
|
+
gatewayAccount: text("gatewayAccount").notNull(),
|
|
513
|
+
provider: text("provider").notNull(),
|
|
514
|
+
customerRef: text("customerRef").notNull(),
|
|
515
|
+
paymentMethodRef: text("paymentMethodRef").notNull(),
|
|
516
|
+
type: text("type").notNull(),
|
|
517
|
+
brand: text("brand"),
|
|
518
|
+
last4: text("last4").notNull(),
|
|
519
|
+
expiryMonth: integer("expiryMonth"),
|
|
520
|
+
expiryYear: integer("expiryYear"),
|
|
521
|
+
country: text("country"),
|
|
522
|
+
bankCode: text("bankCode"),
|
|
523
|
+
mandateReference: text("mandateReference"),
|
|
524
|
+
status: text("status").notNull().default("ACTIVE"),
|
|
525
|
+
confirmedAt: ts("confirmedAt").notNull(),
|
|
526
|
+
replacedAt: ts("replacedAt"),
|
|
527
|
+
createdAt: ts("createdAt").notNull().defaultNow()
|
|
528
|
+
});
|
|
252
529
|
|
|
253
|
-
// src/drizzle-
|
|
254
|
-
function _ts_decorate2(decorators, target, key,
|
|
255
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
256
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
530
|
+
// src/drizzle-applied-settings.repository.ts
|
|
531
|
+
function _ts_decorate2(decorators, target, key, desc10) {
|
|
532
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
533
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
257
534
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
258
535
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
259
536
|
}
|
|
@@ -268,6 +545,126 @@ function _ts_param(paramIndex, decorator) {
|
|
|
268
545
|
};
|
|
269
546
|
}
|
|
270
547
|
__name(_ts_param, "_ts_param");
|
|
548
|
+
var INSTALLATION_ROW_ID = "installation";
|
|
549
|
+
var DrizzleAppliedSettingsRepository = class {
|
|
550
|
+
static {
|
|
551
|
+
__name(this, "DrizzleAppliedSettingsRepository");
|
|
552
|
+
}
|
|
553
|
+
db;
|
|
554
|
+
constructor(db) {
|
|
555
|
+
this.db = db;
|
|
556
|
+
}
|
|
557
|
+
async readApplied() {
|
|
558
|
+
const rows = await this.db.select().from(appliedSettings).where(eq(appliedSettings.id, INSTALLATION_ROW_ID)).limit(1);
|
|
559
|
+
const row = rows[0];
|
|
560
|
+
if (!row) return null;
|
|
561
|
+
return {
|
|
562
|
+
fingerprint: row.fingerprint,
|
|
563
|
+
settings: toValues(row.settings),
|
|
564
|
+
source: row.source,
|
|
565
|
+
appliedAt: row.appliedAt
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
async writeApplied(record, expectedFingerprint) {
|
|
569
|
+
return replaceRecord(this.db, record, expectedFingerprint);
|
|
570
|
+
}
|
|
571
|
+
async recordChange(change, record, expectedFingerprint) {
|
|
572
|
+
return this.db.transaction(async (tx) => {
|
|
573
|
+
const db = tx;
|
|
574
|
+
if (!await replaceRecord(db, record, expectedFingerprint)) return null;
|
|
575
|
+
const row = {
|
|
576
|
+
id: randomUUID(),
|
|
577
|
+
noticedAt: change.noticedAt,
|
|
578
|
+
source: change.source,
|
|
579
|
+
previous: change.previous,
|
|
580
|
+
current: change.current,
|
|
581
|
+
acknowledgedAt: null,
|
|
582
|
+
acknowledgedBy: null
|
|
583
|
+
};
|
|
584
|
+
await db.insert(settingsChanges).values(row);
|
|
585
|
+
return toChangeRecord(row);
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
async listChanges(filter = {}) {
|
|
589
|
+
const acknowledgement = filter.acknowledged === void 0 ? void 0 : filter.acknowledged ? isNotNull(settingsChanges.acknowledgedAt) : isNull(settingsChanges.acknowledgedAt);
|
|
590
|
+
const query = this.db.select().from(settingsChanges).where(acknowledgement).orderBy(desc(settingsChanges.seq));
|
|
591
|
+
const rows = filter.limit === void 0 ? await query : await query.limit(filter.limit);
|
|
592
|
+
return rows.map(toChangeRecord);
|
|
593
|
+
}
|
|
594
|
+
async acknowledgeChange(id, acknowledgedBy, acknowledgedAt) {
|
|
595
|
+
await this.db.update(settingsChanges).set({
|
|
596
|
+
acknowledgedAt,
|
|
597
|
+
acknowledgedBy
|
|
598
|
+
}).where(and(eq(settingsChanges.id, id), isNull(settingsChanges.acknowledgedAt)));
|
|
599
|
+
const rows = await this.db.select().from(settingsChanges).where(eq(settingsChanges.id, id)).limit(1);
|
|
600
|
+
return rows[0] ? toChangeRecord(rows[0]) : null;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
DrizzleAppliedSettingsRepository = _ts_decorate2([
|
|
604
|
+
Injectable2(),
|
|
605
|
+
_ts_param(0, Inject(DRIZZLE_DB_TOKEN)),
|
|
606
|
+
_ts_metadata("design:type", Function),
|
|
607
|
+
_ts_metadata("design:paramtypes", [
|
|
608
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
609
|
+
])
|
|
610
|
+
], DrizzleAppliedSettingsRepository);
|
|
611
|
+
async function replaceRecord(db, record, expectedFingerprint) {
|
|
612
|
+
const values = {
|
|
613
|
+
fingerprint: record.fingerprint,
|
|
614
|
+
settings: record.settings,
|
|
615
|
+
source: record.source,
|
|
616
|
+
appliedAt: record.appliedAt
|
|
617
|
+
};
|
|
618
|
+
const written = expectedFingerprint === null ? await db.insert(appliedSettings).values({
|
|
619
|
+
id: INSTALLATION_ROW_ID,
|
|
620
|
+
...values
|
|
621
|
+
}).onConflictDoNothing({
|
|
622
|
+
target: appliedSettings.id
|
|
623
|
+
}).returning({
|
|
624
|
+
id: appliedSettings.id
|
|
625
|
+
}) : await db.update(appliedSettings).set(values).where(and(eq(appliedSettings.id, INSTALLATION_ROW_ID), eq(appliedSettings.fingerprint, expectedFingerprint))).returning({
|
|
626
|
+
id: appliedSettings.id
|
|
627
|
+
});
|
|
628
|
+
return written.length === 1;
|
|
629
|
+
}
|
|
630
|
+
__name(replaceRecord, "replaceRecord");
|
|
631
|
+
function toChangeRecord(row) {
|
|
632
|
+
return {
|
|
633
|
+
id: row.id,
|
|
634
|
+
noticedAt: row.noticedAt,
|
|
635
|
+
source: row.source,
|
|
636
|
+
previous: toValues(row.previous),
|
|
637
|
+
current: toValues(row.current),
|
|
638
|
+
acknowledgedAt: row.acknowledgedAt,
|
|
639
|
+
acknowledgedBy: row.acknowledgedBy
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
__name(toChangeRecord, "toChangeRecord");
|
|
643
|
+
function toValues(value) {
|
|
644
|
+
return value !== null && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
645
|
+
}
|
|
646
|
+
__name(toValues, "toValues");
|
|
647
|
+
|
|
648
|
+
// src/drizzle-audit.adapter.ts
|
|
649
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
650
|
+
import { Inject as Inject2, Injectable as Injectable3 } from "@nestjs/common";
|
|
651
|
+
function _ts_decorate3(decorators, target, key, desc10) {
|
|
652
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
653
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
654
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
655
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
656
|
+
}
|
|
657
|
+
__name(_ts_decorate3, "_ts_decorate");
|
|
658
|
+
function _ts_metadata2(k, v) {
|
|
659
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
660
|
+
}
|
|
661
|
+
__name(_ts_metadata2, "_ts_metadata");
|
|
662
|
+
function _ts_param2(paramIndex, decorator) {
|
|
663
|
+
return function(target, key) {
|
|
664
|
+
decorator(target, key, paramIndex);
|
|
665
|
+
};
|
|
666
|
+
}
|
|
667
|
+
__name(_ts_param2, "_ts_param");
|
|
271
668
|
function buildActorTag(actor) {
|
|
272
669
|
return `${actor.source}:${actor.email}:${actor.context}`;
|
|
273
670
|
}
|
|
@@ -282,7 +679,7 @@ var DrizzleAuditAdapter = class {
|
|
|
282
679
|
}
|
|
283
680
|
async write(input) {
|
|
284
681
|
await this.db.insert(auditLogs).values({
|
|
285
|
-
id:
|
|
682
|
+
id: randomUUID2(),
|
|
286
683
|
tenantId: null,
|
|
287
684
|
userId: input.actor.userId,
|
|
288
685
|
entity: input.entity,
|
|
@@ -293,35 +690,35 @@ var DrizzleAuditAdapter = class {
|
|
|
293
690
|
});
|
|
294
691
|
}
|
|
295
692
|
};
|
|
296
|
-
DrizzleAuditAdapter =
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
693
|
+
DrizzleAuditAdapter = _ts_decorate3([
|
|
694
|
+
Injectable3(),
|
|
695
|
+
_ts_param2(0, Inject2(DRIZZLE_DB_TOKEN)),
|
|
696
|
+
_ts_metadata2("design:type", Function),
|
|
697
|
+
_ts_metadata2("design:paramtypes", [
|
|
301
698
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
302
699
|
])
|
|
303
700
|
], DrizzleAuditAdapter);
|
|
304
701
|
|
|
305
702
|
// src/drizzle-audit-query.adapter.ts
|
|
306
|
-
import { Inject as
|
|
307
|
-
import { and, desc, eq, gte, like, lte } from "drizzle-orm";
|
|
308
|
-
function
|
|
309
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
310
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
703
|
+
import { Inject as Inject3, Injectable as Injectable4 } from "@nestjs/common";
|
|
704
|
+
import { and as and2, desc as desc2, eq as eq2, gte, like, lte } from "drizzle-orm";
|
|
705
|
+
function _ts_decorate4(decorators, target, key, desc10) {
|
|
706
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
707
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
311
708
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
312
709
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
313
710
|
}
|
|
314
|
-
__name(
|
|
315
|
-
function
|
|
711
|
+
__name(_ts_decorate4, "_ts_decorate");
|
|
712
|
+
function _ts_metadata3(k, v) {
|
|
316
713
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
317
714
|
}
|
|
318
|
-
__name(
|
|
319
|
-
function
|
|
715
|
+
__name(_ts_metadata3, "_ts_metadata");
|
|
716
|
+
function _ts_param3(paramIndex, decorator) {
|
|
320
717
|
return function(target, key) {
|
|
321
718
|
decorator(target, key, paramIndex);
|
|
322
719
|
};
|
|
323
720
|
}
|
|
324
|
-
__name(
|
|
721
|
+
__name(_ts_param3, "_ts_param");
|
|
325
722
|
var DEFAULT_PAGE_SIZE = 50;
|
|
326
723
|
var MAX_PAGE_SIZE = 200;
|
|
327
724
|
var DrizzleAuditQueryAdapter = class {
|
|
@@ -336,24 +733,24 @@ var DrizzleAuditQueryAdapter = class {
|
|
|
336
733
|
const page = Math.max(filter.page ?? 1, 1);
|
|
337
734
|
const pageSize = Math.min(Math.max(filter.pageSize ?? DEFAULT_PAGE_SIZE, 1), MAX_PAGE_SIZE);
|
|
338
735
|
const conditions = [
|
|
339
|
-
filter.tenantId ?
|
|
340
|
-
filter.userId ?
|
|
341
|
-
filter.entity ?
|
|
342
|
-
filter.entityId ?
|
|
343
|
-
filter.action ?
|
|
736
|
+
filter.tenantId ? eq2(auditLogs.tenantId, filter.tenantId) : void 0,
|
|
737
|
+
filter.userId ? eq2(auditLogs.userId, filter.userId) : void 0,
|
|
738
|
+
filter.entity ? eq2(auditLogs.entity, filter.entity) : void 0,
|
|
739
|
+
filter.entityId ? eq2(auditLogs.entityId, filter.entityId) : void 0,
|
|
740
|
+
filter.action ? eq2(auditLogs.action, filter.action) : void 0,
|
|
344
741
|
toActorTagCondition(filter.actorTag),
|
|
345
742
|
filter.from ? gte(auditLogs.createdAt, new Date(filter.from)) : void 0,
|
|
346
743
|
filter.to ? lte(auditLogs.createdAt, new Date(filter.to)) : void 0
|
|
347
744
|
];
|
|
348
|
-
const rows = await this.db.select().from(auditLogs).where(
|
|
745
|
+
const rows = await this.db.select().from(auditLogs).where(and2(...conditions)).orderBy(desc2(auditLogs.createdAt)).limit(pageSize).offset((page - 1) * pageSize);
|
|
349
746
|
return rows.map(toAuditEntry);
|
|
350
747
|
}
|
|
351
748
|
};
|
|
352
|
-
DrizzleAuditQueryAdapter =
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
749
|
+
DrizzleAuditQueryAdapter = _ts_decorate4([
|
|
750
|
+
Injectable4(),
|
|
751
|
+
_ts_param3(0, Inject3(DRIZZLE_DB_TOKEN)),
|
|
752
|
+
_ts_metadata3("design:type", Function),
|
|
753
|
+
_ts_metadata3("design:paramtypes", [
|
|
357
754
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
358
755
|
])
|
|
359
756
|
], DrizzleAuditQueryAdapter);
|
|
@@ -362,7 +759,7 @@ function toActorTagCondition(actorTag) {
|
|
|
362
759
|
if (actorTag.endsWith("*")) {
|
|
363
760
|
return like(auditLogs.actorTag, `${escapeLikePattern(actorTag.slice(0, -1))}%`);
|
|
364
761
|
}
|
|
365
|
-
return
|
|
762
|
+
return eq2(auditLogs.actorTag, actorTag);
|
|
366
763
|
}
|
|
367
764
|
__name(toActorTagCondition, "toActorTagCondition");
|
|
368
765
|
function toAuditEntry(row) {
|
|
@@ -385,25 +782,25 @@ function toAuditEntry(row) {
|
|
|
385
782
|
__name(toAuditEntry, "toAuditEntry");
|
|
386
783
|
|
|
387
784
|
// src/drizzle-audit-stats.adapter.ts
|
|
388
|
-
import { Inject as
|
|
785
|
+
import { Inject as Inject4, Injectable as Injectable5 } from "@nestjs/common";
|
|
389
786
|
import { count, gte as gte2 } from "drizzle-orm";
|
|
390
|
-
function
|
|
391
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
392
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
787
|
+
function _ts_decorate5(decorators, target, key, desc10) {
|
|
788
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
789
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
393
790
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
394
791
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
395
792
|
}
|
|
396
|
-
__name(
|
|
397
|
-
function
|
|
793
|
+
__name(_ts_decorate5, "_ts_decorate");
|
|
794
|
+
function _ts_metadata4(k, v) {
|
|
398
795
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
399
796
|
}
|
|
400
|
-
__name(
|
|
401
|
-
function
|
|
797
|
+
__name(_ts_metadata4, "_ts_metadata");
|
|
798
|
+
function _ts_param4(paramIndex, decorator) {
|
|
402
799
|
return function(target, key) {
|
|
403
800
|
decorator(target, key, paramIndex);
|
|
404
801
|
};
|
|
405
802
|
}
|
|
406
|
-
__name(
|
|
803
|
+
__name(_ts_param4, "_ts_param");
|
|
407
804
|
var DrizzleAuditStatsAdapter = class {
|
|
408
805
|
static {
|
|
409
806
|
__name(this, "DrizzleAuditStatsAdapter");
|
|
@@ -419,35 +816,35 @@ var DrizzleAuditStatsAdapter = class {
|
|
|
419
816
|
return rows[0]?.value ?? 0;
|
|
420
817
|
}
|
|
421
818
|
};
|
|
422
|
-
DrizzleAuditStatsAdapter =
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
819
|
+
DrizzleAuditStatsAdapter = _ts_decorate5([
|
|
820
|
+
Injectable5(),
|
|
821
|
+
_ts_param4(0, Inject4(DRIZZLE_DB_TOKEN)),
|
|
822
|
+
_ts_metadata4("design:type", Function),
|
|
823
|
+
_ts_metadata4("design:paramtypes", [
|
|
427
824
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
428
825
|
])
|
|
429
826
|
], DrizzleAuditStatsAdapter);
|
|
430
827
|
|
|
431
828
|
// src/drizzle-mfa.adapter.ts
|
|
432
|
-
import { Inject as
|
|
433
|
-
import { eq as
|
|
434
|
-
function
|
|
435
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
436
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
829
|
+
import { Inject as Inject5, Injectable as Injectable6 } from "@nestjs/common";
|
|
830
|
+
import { eq as eq3 } from "drizzle-orm";
|
|
831
|
+
function _ts_decorate6(decorators, target, key, desc10) {
|
|
832
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
833
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
437
834
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
438
835
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
439
836
|
}
|
|
440
|
-
__name(
|
|
441
|
-
function
|
|
837
|
+
__name(_ts_decorate6, "_ts_decorate");
|
|
838
|
+
function _ts_metadata5(k, v) {
|
|
442
839
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
443
840
|
}
|
|
444
|
-
__name(
|
|
445
|
-
function
|
|
841
|
+
__name(_ts_metadata5, "_ts_metadata");
|
|
842
|
+
function _ts_param5(paramIndex, decorator) {
|
|
446
843
|
return function(target, key) {
|
|
447
844
|
decorator(target, key, paramIndex);
|
|
448
845
|
};
|
|
449
846
|
}
|
|
450
|
-
__name(
|
|
847
|
+
__name(_ts_param5, "_ts_param");
|
|
451
848
|
var DrizzleMfaAdapter = class {
|
|
452
849
|
static {
|
|
453
850
|
__name(this, "DrizzleMfaAdapter");
|
|
@@ -457,7 +854,7 @@ var DrizzleMfaAdapter = class {
|
|
|
457
854
|
this.db = db;
|
|
458
855
|
}
|
|
459
856
|
async getSecret(userId) {
|
|
460
|
-
const rows = await this.db.select().from(superAdminMfa).where(
|
|
857
|
+
const rows = await this.db.select().from(superAdminMfa).where(eq3(superAdminMfa.userId, userId)).limit(1);
|
|
461
858
|
return rows[0]?.secret ?? null;
|
|
462
859
|
}
|
|
463
860
|
async setSecret(userId, secret) {
|
|
@@ -477,41 +874,41 @@ var DrizzleMfaAdapter = class {
|
|
|
477
874
|
});
|
|
478
875
|
}
|
|
479
876
|
async isEnabled(userId) {
|
|
480
|
-
const rows = await this.db.select().from(superAdminMfa).where(
|
|
877
|
+
const rows = await this.db.select().from(superAdminMfa).where(eq3(superAdminMfa.userId, userId)).limit(1);
|
|
481
878
|
const row = rows[0];
|
|
482
879
|
return !!row?.secret && !!row?.enabledAt;
|
|
483
880
|
}
|
|
484
881
|
};
|
|
485
|
-
DrizzleMfaAdapter =
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
882
|
+
DrizzleMfaAdapter = _ts_decorate6([
|
|
883
|
+
Injectable6(),
|
|
884
|
+
_ts_param5(0, Inject5(DRIZZLE_DB_TOKEN)),
|
|
885
|
+
_ts_metadata5("design:type", Function),
|
|
886
|
+
_ts_metadata5("design:paramtypes", [
|
|
490
887
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
491
888
|
])
|
|
492
889
|
], DrizzleMfaAdapter);
|
|
493
890
|
|
|
494
891
|
// src/drizzle-plan-catalog-import-sink.adapter.ts
|
|
495
|
-
import { randomUUID as
|
|
496
|
-
import { Inject as
|
|
497
|
-
import { and as
|
|
498
|
-
function
|
|
499
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
500
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
892
|
+
import { randomUUID as randomUUID3 } from "node:crypto";
|
|
893
|
+
import { Inject as Inject6, Injectable as Injectable7 } from "@nestjs/common";
|
|
894
|
+
import { and as and3, eq as eq4, isNotNull as isNotNull2, isNull as isNull2, lt } from "drizzle-orm";
|
|
895
|
+
function _ts_decorate7(decorators, target, key, desc10) {
|
|
896
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
897
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
501
898
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
502
899
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
503
900
|
}
|
|
504
|
-
__name(
|
|
505
|
-
function
|
|
901
|
+
__name(_ts_decorate7, "_ts_decorate");
|
|
902
|
+
function _ts_metadata6(k, v) {
|
|
506
903
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
507
904
|
}
|
|
508
|
-
__name(
|
|
509
|
-
function
|
|
905
|
+
__name(_ts_metadata6, "_ts_metadata");
|
|
906
|
+
function _ts_param6(paramIndex, decorator) {
|
|
510
907
|
return function(target, key) {
|
|
511
908
|
decorator(target, key, paramIndex);
|
|
512
909
|
};
|
|
513
910
|
}
|
|
514
|
-
__name(
|
|
911
|
+
__name(_ts_param6, "_ts_param");
|
|
515
912
|
var DrizzlePlanCatalogImportSink = class {
|
|
516
913
|
static {
|
|
517
914
|
__name(this, "DrizzlePlanCatalogImportSink");
|
|
@@ -523,14 +920,13 @@ var DrizzlePlanCatalogImportSink = class {
|
|
|
523
920
|
async upsertPlan(input) {
|
|
524
921
|
const existing = await this.db.select({
|
|
525
922
|
id: plans.id
|
|
526
|
-
}).from(plans).where(
|
|
923
|
+
}).from(plans).where(eq4(plans.planKey, input.planKey)).limit(1);
|
|
527
924
|
if (existing[0]) return {
|
|
528
925
|
created: false,
|
|
529
926
|
skipReason: "exists"
|
|
530
927
|
};
|
|
531
928
|
await this.db.insert(plans).values({
|
|
532
|
-
id:
|
|
533
|
-
projectKey: input.projectKey,
|
|
929
|
+
id: randomUUID3(),
|
|
534
930
|
planKey: input.planKey,
|
|
535
931
|
label: input.label,
|
|
536
932
|
description: input.description ?? null,
|
|
@@ -544,7 +940,7 @@ var DrizzlePlanCatalogImportSink = class {
|
|
|
544
940
|
async upsertPlanVersion(input) {
|
|
545
941
|
const existing = await this.db.select({
|
|
546
942
|
id: planVersions.id
|
|
547
|
-
}).from(planVersions).where(
|
|
943
|
+
}).from(planVersions).where(and3(eq4(planVersions.planId, input.planKey), eq4(planVersions.version, input.version))).limit(1);
|
|
548
944
|
if (existing[0]) return {
|
|
549
945
|
created: false,
|
|
550
946
|
skipReason: "exists"
|
|
@@ -554,10 +950,10 @@ var DrizzlePlanCatalogImportSink = class {
|
|
|
554
950
|
await this.db.update(planVersions).set({
|
|
555
951
|
supersededAt: now,
|
|
556
952
|
updatedAt: now
|
|
557
|
-
}).where(
|
|
953
|
+
}).where(and3(eq4(planVersions.planId, input.planKey), isNotNull2(planVersions.publishedAt), isNull2(planVersions.supersededAt), lt(planVersions.version, input.version)));
|
|
558
954
|
}
|
|
559
955
|
await this.db.insert(planVersions).values({
|
|
560
|
-
id:
|
|
956
|
+
id: randomUUID3(),
|
|
561
957
|
planId: input.planKey,
|
|
562
958
|
version: input.version,
|
|
563
959
|
features: input.features,
|
|
@@ -576,14 +972,13 @@ var DrizzlePlanCatalogImportSink = class {
|
|
|
576
972
|
async upsertFeatureCatalogEntry(input) {
|
|
577
973
|
const existing = await this.db.select({
|
|
578
974
|
id: featureCatalogEntries.id
|
|
579
|
-
}).from(featureCatalogEntries).where(
|
|
975
|
+
}).from(featureCatalogEntries).where(and3(eq4(featureCatalogEntries.featureKey, input.featureKey))).limit(1);
|
|
580
976
|
if (existing[0]) return {
|
|
581
977
|
created: false,
|
|
582
978
|
skipReason: "exists"
|
|
583
979
|
};
|
|
584
980
|
await this.db.insert(featureCatalogEntries).values({
|
|
585
|
-
id:
|
|
586
|
-
projectKey: input.projectKey,
|
|
981
|
+
id: randomUUID3(),
|
|
587
982
|
featureKey: input.featureKey,
|
|
588
983
|
label: input.label ?? input.featureKey,
|
|
589
984
|
icon: input.icon ?? null,
|
|
@@ -597,35 +992,40 @@ var DrizzlePlanCatalogImportSink = class {
|
|
|
597
992
|
};
|
|
598
993
|
}
|
|
599
994
|
};
|
|
600
|
-
DrizzlePlanCatalogImportSink =
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
995
|
+
DrizzlePlanCatalogImportSink = _ts_decorate7([
|
|
996
|
+
Injectable7(),
|
|
997
|
+
_ts_param6(0, Inject6(DRIZZLE_DB_TOKEN)),
|
|
998
|
+
_ts_metadata6("design:type", Function),
|
|
999
|
+
_ts_metadata6("design:paramtypes", [
|
|
605
1000
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
606
1001
|
])
|
|
607
1002
|
], DrizzlePlanCatalogImportSink);
|
|
608
1003
|
|
|
609
1004
|
// src/drizzle-plan-catalog-read-sink.adapter.ts
|
|
610
|
-
import { Inject as
|
|
611
|
-
import { and as
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
1005
|
+
import { Inject as Inject7, Injectable as Injectable8 } from "@nestjs/common";
|
|
1006
|
+
import { and as and4, asc, inArray, isNotNull as isNotNull3, isNull as isNull3 } from "drizzle-orm";
|
|
1007
|
+
import { toPlanRow, toPlanVersionRow } from "@saasicat/core";
|
|
1008
|
+
function _ts_decorate8(decorators, target, key, desc10) {
|
|
1009
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
1010
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
615
1011
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
616
1012
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
617
1013
|
}
|
|
618
|
-
__name(
|
|
619
|
-
function
|
|
1014
|
+
__name(_ts_decorate8, "_ts_decorate");
|
|
1015
|
+
function _ts_metadata7(k, v) {
|
|
620
1016
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
621
1017
|
}
|
|
622
|
-
__name(
|
|
623
|
-
function
|
|
1018
|
+
__name(_ts_metadata7, "_ts_metadata");
|
|
1019
|
+
function _ts_param7(paramIndex, decorator) {
|
|
624
1020
|
return function(target, key) {
|
|
625
1021
|
decorator(target, key, paramIndex);
|
|
626
1022
|
};
|
|
627
1023
|
}
|
|
628
|
-
__name(
|
|
1024
|
+
__name(_ts_param7, "_ts_param");
|
|
1025
|
+
var CATALOG_VERSION_FIELDS = {
|
|
1026
|
+
validityWindows: false,
|
|
1027
|
+
endsAt: false
|
|
1028
|
+
};
|
|
629
1029
|
var DrizzlePlanCatalogReadSink = class {
|
|
630
1030
|
static {
|
|
631
1031
|
__name(this, "DrizzlePlanCatalogReadSink");
|
|
@@ -634,70 +1034,29 @@ var DrizzlePlanCatalogReadSink = class {
|
|
|
634
1034
|
constructor(db) {
|
|
635
1035
|
this.db = db;
|
|
636
1036
|
}
|
|
637
|
-
async loadSnapshot(
|
|
638
|
-
const planRows = await this.db.select().from(plans).where(
|
|
1037
|
+
async loadSnapshot() {
|
|
1038
|
+
const planRows = await this.db.select().from(plans).where(isNull3(plans.deletedAt)).orderBy(asc(plans.sortOrder));
|
|
639
1039
|
const planKeys = planRows.map((plan) => plan.planKey);
|
|
640
|
-
const liveVersionRows = planKeys.length === 0 ? [] : await this.db.select().from(planVersions).where(
|
|
641
|
-
const featureRows = await this.db.select().from(featureCatalogEntries).where(
|
|
1040
|
+
const liveVersionRows = planKeys.length === 0 ? [] : await this.db.select().from(planVersions).where(and4(inArray(planVersions.planId, planKeys), isNotNull3(planVersions.publishedAt), isNull3(planVersions.supersededAt)));
|
|
1041
|
+
const featureRows = await this.db.select().from(featureCatalogEntries).where(isNull3(featureCatalogEntries.deletedAt)).orderBy(asc(featureCatalogEntries.sortOrder));
|
|
642
1042
|
return {
|
|
643
|
-
plans: planRows.map(toPlanRow),
|
|
644
|
-
livePlanVersions: liveVersionRows.map(toPlanVersionRow),
|
|
1043
|
+
plans: planRows.map((row) => toPlanRow(row)),
|
|
1044
|
+
livePlanVersions: liveVersionRows.map((row) => toPlanVersionRow(row, row.planId, CATALOG_VERSION_FIELDS)),
|
|
645
1045
|
featureEntries: featureRows.map(toFeatureCatalogEntryRow)
|
|
646
1046
|
};
|
|
647
1047
|
}
|
|
648
1048
|
};
|
|
649
|
-
DrizzlePlanCatalogReadSink =
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
1049
|
+
DrizzlePlanCatalogReadSink = _ts_decorate8([
|
|
1050
|
+
Injectable8(),
|
|
1051
|
+
_ts_param7(0, Inject7(DRIZZLE_DB_TOKEN)),
|
|
1052
|
+
_ts_metadata7("design:type", Function),
|
|
1053
|
+
_ts_metadata7("design:paramtypes", [
|
|
1054
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
655
1055
|
])
|
|
656
1056
|
], DrizzlePlanCatalogReadSink);
|
|
657
|
-
function toPlanRow(row) {
|
|
658
|
-
return {
|
|
659
|
-
id: row.id,
|
|
660
|
-
projectKey: row.projectKey,
|
|
661
|
-
planKey: row.planKey,
|
|
662
|
-
label: row.label,
|
|
663
|
-
description: row.description,
|
|
664
|
-
icon: row.icon,
|
|
665
|
-
sortOrder: row.sortOrder,
|
|
666
|
-
createdAt: row.createdAt.toISOString(),
|
|
667
|
-
updatedAt: row.updatedAt.toISOString(),
|
|
668
|
-
deletedAt: row.deletedAt ? row.deletedAt.toISOString() : null
|
|
669
|
-
};
|
|
670
|
-
}
|
|
671
|
-
__name(toPlanRow, "toPlanRow");
|
|
672
|
-
function toPlanVersionRow(row) {
|
|
673
|
-
return {
|
|
674
|
-
id: row.id,
|
|
675
|
-
planId: row.planId,
|
|
676
|
-
version: row.version,
|
|
677
|
-
baseVersionId: row.baseVersionId,
|
|
678
|
-
publishedAt: row.publishedAt ? row.publishedAt.toISOString() : null,
|
|
679
|
-
supersededAt: row.supersededAt ? row.supersededAt.toISOString() : null,
|
|
680
|
-
publishedChanges: row.publishedChanges ?? null,
|
|
681
|
-
changeNote: row.changeNote,
|
|
682
|
-
nonRegressive: row.nonRegressive,
|
|
683
|
-
validFrom: null,
|
|
684
|
-
validUntil: null,
|
|
685
|
-
createdByUserId: row.createdByUserId,
|
|
686
|
-
publishedByUserId: row.publishedByUserId,
|
|
687
|
-
createdAt: row.createdAt.toISOString(),
|
|
688
|
-
updatedAt: row.updatedAt.toISOString(),
|
|
689
|
-
features: toStringArray(row.features),
|
|
690
|
-
quotas: toQuotaMap(row.quotas),
|
|
691
|
-
monthlyNet: String(row.monthlyNet),
|
|
692
|
-
yearlyNet: String(row.yearlyNet),
|
|
693
|
-
marketed: row.marketed
|
|
694
|
-
};
|
|
695
|
-
}
|
|
696
|
-
__name(toPlanVersionRow, "toPlanVersionRow");
|
|
697
1057
|
function toFeatureCatalogEntryRow(row) {
|
|
698
1058
|
return {
|
|
699
1059
|
id: row.id,
|
|
700
|
-
projectKey: row.projectKey,
|
|
701
1060
|
featureKey: row.featureKey,
|
|
702
1061
|
label: row.label,
|
|
703
1062
|
description: row.description,
|
|
@@ -723,26 +1082,84 @@ function toFeatureCatalogEntryRow(row) {
|
|
|
723
1082
|
}
|
|
724
1083
|
__name(toFeatureCatalogEntryRow, "toFeatureCatalogEntryRow");
|
|
725
1084
|
|
|
1085
|
+
// src/drizzle-payment-event-log.adapter.ts
|
|
1086
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
1087
|
+
import { and as and5, eq as eq5 } from "drizzle-orm";
|
|
1088
|
+
import { Inject as Inject8, Injectable as Injectable9 } from "@nestjs/common";
|
|
1089
|
+
function _ts_decorate9(decorators, target, key, desc10) {
|
|
1090
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
1091
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
1092
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1093
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1094
|
+
}
|
|
1095
|
+
__name(_ts_decorate9, "_ts_decorate");
|
|
1096
|
+
function _ts_metadata8(k, v) {
|
|
1097
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1098
|
+
}
|
|
1099
|
+
__name(_ts_metadata8, "_ts_metadata");
|
|
1100
|
+
function _ts_param8(paramIndex, decorator) {
|
|
1101
|
+
return function(target, key) {
|
|
1102
|
+
decorator(target, key, paramIndex);
|
|
1103
|
+
};
|
|
1104
|
+
}
|
|
1105
|
+
__name(_ts_param8, "_ts_param");
|
|
1106
|
+
var DrizzlePaymentEventLog = class {
|
|
1107
|
+
static {
|
|
1108
|
+
__name(this, "DrizzlePaymentEventLog");
|
|
1109
|
+
}
|
|
1110
|
+
db;
|
|
1111
|
+
constructor(db) {
|
|
1112
|
+
this.db = db;
|
|
1113
|
+
}
|
|
1114
|
+
async claim(claim, tx) {
|
|
1115
|
+
const inserted = await resolveDb(this.db, tx).insert(paymentEventLog).values({
|
|
1116
|
+
id: randomUUID4(),
|
|
1117
|
+
gatewayAccount: claim.gatewayAccount,
|
|
1118
|
+
eventId: claim.eventId,
|
|
1119
|
+
provider: claim.provider,
|
|
1120
|
+
sessionId: claim.sessionId,
|
|
1121
|
+
status: claim.kind,
|
|
1122
|
+
payload: claim.summary
|
|
1123
|
+
}).onConflictDoNothing().returning({
|
|
1124
|
+
id: paymentEventLog.id
|
|
1125
|
+
});
|
|
1126
|
+
return inserted.length === 1;
|
|
1127
|
+
}
|
|
1128
|
+
async releaseSession(gatewayAccount, eventId, tx) {
|
|
1129
|
+
await resolveDb(this.db, tx).update(paymentEventLog).set({
|
|
1130
|
+
sessionId: null
|
|
1131
|
+
}).where(and5(eq5(paymentEventLog.gatewayAccount, gatewayAccount), eq5(paymentEventLog.eventId, eventId)));
|
|
1132
|
+
}
|
|
1133
|
+
};
|
|
1134
|
+
DrizzlePaymentEventLog = _ts_decorate9([
|
|
1135
|
+
Injectable9(),
|
|
1136
|
+
_ts_param8(0, Inject8(DRIZZLE_DB_TOKEN)),
|
|
1137
|
+
_ts_metadata8("design:type", Function),
|
|
1138
|
+
_ts_metadata8("design:paramtypes", [
|
|
1139
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
1140
|
+
])
|
|
1141
|
+
], DrizzlePaymentEventLog);
|
|
1142
|
+
|
|
726
1143
|
// src/drizzle-plan-version.repository.ts
|
|
727
|
-
import { Inject as
|
|
728
|
-
import { and as
|
|
729
|
-
function
|
|
730
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
731
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
1144
|
+
import { Inject as Inject9, Injectable as Injectable10 } from "@nestjs/common";
|
|
1145
|
+
import { and as and6, desc as desc3, eq as eq6, isNotNull as isNotNull4, isNull as isNull4 } from "drizzle-orm";
|
|
1146
|
+
function _ts_decorate10(decorators, target, key, desc10) {
|
|
1147
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
1148
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
732
1149
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
733
1150
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
734
1151
|
}
|
|
735
|
-
__name(
|
|
736
|
-
function
|
|
1152
|
+
__name(_ts_decorate10, "_ts_decorate");
|
|
1153
|
+
function _ts_metadata9(k, v) {
|
|
737
1154
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
738
1155
|
}
|
|
739
|
-
__name(
|
|
740
|
-
function
|
|
1156
|
+
__name(_ts_metadata9, "_ts_metadata");
|
|
1157
|
+
function _ts_param9(paramIndex, decorator) {
|
|
741
1158
|
return function(target, key) {
|
|
742
1159
|
decorator(target, key, paramIndex);
|
|
743
1160
|
};
|
|
744
1161
|
}
|
|
745
|
-
__name(
|
|
1162
|
+
__name(_ts_param9, "_ts_param");
|
|
746
1163
|
var DrizzlePlanVersionRepository = class {
|
|
747
1164
|
static {
|
|
748
1165
|
__name(this, "DrizzlePlanVersionRepository");
|
|
@@ -753,7 +1170,7 @@ var DrizzlePlanVersionRepository = class {
|
|
|
753
1170
|
}
|
|
754
1171
|
async findLatestLive(planId, tx) {
|
|
755
1172
|
const db = resolveDb(this.db, tx);
|
|
756
|
-
const rows = await db.select().from(planVersions).where(
|
|
1173
|
+
const rows = await db.select().from(planVersions).where(and6(eq6(planVersions.planId, planId), isNotNull4(planVersions.publishedAt), isNull4(planVersions.supersededAt))).orderBy(desc3(planVersions.version)).limit(1);
|
|
757
1174
|
const row = rows[0];
|
|
758
1175
|
if (!row) return null;
|
|
759
1176
|
return {
|
|
@@ -763,319 +1180,1992 @@ var DrizzlePlanVersionRepository = class {
|
|
|
763
1180
|
};
|
|
764
1181
|
}
|
|
765
1182
|
};
|
|
766
|
-
DrizzlePlanVersionRepository =
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
1183
|
+
DrizzlePlanVersionRepository = _ts_decorate10([
|
|
1184
|
+
Injectable10(),
|
|
1185
|
+
_ts_param9(0, Inject9(DRIZZLE_DB_TOKEN)),
|
|
1186
|
+
_ts_metadata9("design:type", Function),
|
|
1187
|
+
_ts_metadata9("design:paramtypes", [
|
|
771
1188
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
772
1189
|
])
|
|
773
1190
|
], DrizzlePlanVersionRepository);
|
|
774
1191
|
|
|
775
|
-
// src/drizzle-promo-code-
|
|
776
|
-
import { randomUUID as
|
|
777
|
-
import { Inject as
|
|
778
|
-
import { and as
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
1192
|
+
// src/drizzle-promo-code-hold.repository.ts
|
|
1193
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
1194
|
+
import { Inject as Inject11, Injectable as Injectable12 } from "@nestjs/common";
|
|
1195
|
+
import { and as and8, eq as eq8, gt, lte as lte2, sql as sql2 } from "drizzle-orm";
|
|
1196
|
+
|
|
1197
|
+
// src/drizzle-promo-code.repository.ts
|
|
1198
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
1199
|
+
import { Inject as Inject10, Injectable as Injectable11 } from "@nestjs/common";
|
|
1200
|
+
import { and as and7, eq as eq7, gte as gte3, inArray as inArray2, isNotNull as isNotNull5, isNull as isNull5, like as like2, lt as lt2, or, sql } from "drizzle-orm";
|
|
1201
|
+
function _ts_decorate11(decorators, target, key, desc10) {
|
|
1202
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
1203
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
782
1204
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
783
1205
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
784
1206
|
}
|
|
785
|
-
__name(
|
|
786
|
-
function
|
|
1207
|
+
__name(_ts_decorate11, "_ts_decorate");
|
|
1208
|
+
function _ts_metadata10(k, v) {
|
|
787
1209
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
788
1210
|
}
|
|
789
|
-
__name(
|
|
790
|
-
function
|
|
1211
|
+
__name(_ts_metadata10, "_ts_metadata");
|
|
1212
|
+
function _ts_param10(paramIndex, decorator) {
|
|
791
1213
|
return function(target, key) {
|
|
792
1214
|
decorator(target, key, paramIndex);
|
|
793
1215
|
};
|
|
794
1216
|
}
|
|
795
|
-
__name(
|
|
796
|
-
var
|
|
1217
|
+
__name(_ts_param10, "_ts_param");
|
|
1218
|
+
var DrizzlePromoCodeRepository = class {
|
|
797
1219
|
static {
|
|
798
|
-
__name(this, "
|
|
1220
|
+
__name(this, "DrizzlePromoCodeRepository");
|
|
799
1221
|
}
|
|
800
1222
|
db;
|
|
801
1223
|
constructor(db) {
|
|
802
1224
|
this.db = db;
|
|
803
1225
|
}
|
|
804
|
-
async
|
|
805
|
-
const
|
|
806
|
-
const rows = await db.select().from(promoCodeRedemptions).where(eq6(promoCodeRedemptions.subscriptionId, subscriptionId)).limit(1);
|
|
1226
|
+
async findById(id) {
|
|
1227
|
+
const rows = await this.db.select().from(promoCodes).where(eq7(promoCodes.id, id)).limit(1);
|
|
807
1228
|
const row = rows[0];
|
|
808
1229
|
return row ? toRecord(row) : null;
|
|
809
1230
|
}
|
|
810
|
-
async
|
|
1231
|
+
async findByCode(code, tx) {
|
|
811
1232
|
const db = resolveDb(this.db, tx);
|
|
812
|
-
const rows = await db.
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
1233
|
+
const rows = await db.select().from(promoCodes).where(eq7(promoCodes.code, normalizeCode(code))).limit(1);
|
|
1234
|
+
const row = rows[0];
|
|
1235
|
+
if (!row || row.deletedAt) return null;
|
|
1236
|
+
return toRecord(row);
|
|
1237
|
+
}
|
|
1238
|
+
async findMany(filter) {
|
|
1239
|
+
const conditions = [
|
|
1240
|
+
isNull5(promoCodes.deletedAt),
|
|
1241
|
+
filter.status ? eq7(promoCodes.status, filter.status) : void 0,
|
|
1242
|
+
filter.campaignTag ? eq7(promoCodes.campaignTag, filter.campaignTag) : void 0,
|
|
1243
|
+
filter.search ? like2(promoCodes.code, `%${escapeLikePattern(normalizeCode(filter.search))}%`) : void 0
|
|
1244
|
+
];
|
|
1245
|
+
const rows = await this.db.select().from(promoCodes).where(and7(...conditions)).orderBy(sql`${promoCodes.createdAt} DESC`);
|
|
1246
|
+
return rows.map(toRecord);
|
|
1247
|
+
}
|
|
1248
|
+
async create(data) {
|
|
1249
|
+
const rows = await this.db.insert(promoCodes).values({
|
|
1250
|
+
id: randomUUID5(),
|
|
1251
|
+
code: normalizeCode(data.code),
|
|
1252
|
+
valueType: data.valueType,
|
|
1253
|
+
value: data.value.toFixed(2),
|
|
1254
|
+
durationType: data.durationType,
|
|
1255
|
+
durationValue: data.durationValue ?? null,
|
|
1256
|
+
validFrom: data.validFrom ?? null,
|
|
1257
|
+
validUntil: data.validUntil ?? null,
|
|
1258
|
+
maxRedemptions: data.maxRedemptions ?? null,
|
|
1259
|
+
appliesToPlans: data.appliesToPlans ?? [],
|
|
1260
|
+
appliesToBilling: data.appliesToBilling ?? null,
|
|
1261
|
+
firstTimeCustomersOnly: data.firstTimeCustomersOnly ?? true,
|
|
1262
|
+
minimumPlanAmountGross: data.minimumPlanAmountGross?.toFixed(2) ?? null,
|
|
1263
|
+
allowZeroInvoice: data.allowZeroInvoice ?? false,
|
|
1264
|
+
description: data.description ?? null,
|
|
1265
|
+
campaignTag: data.campaignTag ?? null,
|
|
1266
|
+
revenueDeductionAccount: data.revenueDeductionAccount ?? null,
|
|
1267
|
+
createdById: data.createdById,
|
|
1268
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
823
1269
|
}).returning();
|
|
824
1270
|
return toRecord(rows[0]);
|
|
825
1271
|
}
|
|
826
|
-
async
|
|
827
|
-
const
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
1272
|
+
async update(id, data) {
|
|
1273
|
+
const rows = await this.db.update(promoCodes).set({
|
|
1274
|
+
status: data.status,
|
|
1275
|
+
description: data.description,
|
|
1276
|
+
validUntil: data.validUntil,
|
|
1277
|
+
maxRedemptions: data.maxRedemptions,
|
|
1278
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1279
|
+
}).where(eq7(promoCodes.id, id)).returning();
|
|
1280
|
+
const row = rows[0];
|
|
1281
|
+
if (!row) {
|
|
1282
|
+
throw new Error(`PromoCode ${id} not found.`);
|
|
1283
|
+
}
|
|
1284
|
+
return toRecord(row);
|
|
1285
|
+
}
|
|
1286
|
+
async softDelete(id) {
|
|
1287
|
+
await this.db.update(promoCodes).set({
|
|
1288
|
+
deletedAt: /* @__PURE__ */ new Date(),
|
|
1289
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1290
|
+
}).where(eq7(promoCodes.id, id));
|
|
1291
|
+
}
|
|
1292
|
+
async claimSlot(id, tx) {
|
|
1293
|
+
const db = resolveDb(this.db, tx);
|
|
1294
|
+
const claimed = await db.update(promoCodes).set({
|
|
1295
|
+
redemptionsCount: sql`${promoCodes.redemptionsCount} + 1`,
|
|
1296
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1297
|
+
}).where(hasFreeSlot(id)).returning({
|
|
1298
|
+
id: promoCodes.id
|
|
1299
|
+
});
|
|
1300
|
+
return claimed.length === 1;
|
|
1301
|
+
}
|
|
1302
|
+
async markExhaustedIfFull(id, tx) {
|
|
1303
|
+
const db = resolveDb(this.db, tx);
|
|
1304
|
+
await db.update(promoCodes).set({
|
|
1305
|
+
status: "EXHAUSTED",
|
|
1306
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1307
|
+
}).where(and7(eq7(promoCodes.id, id), eq7(promoCodes.status, "ACTIVE"), isNotNull5(promoCodes.maxRedemptions), gte3(promoCodes.redemptionsCount, promoCodes.maxRedemptions)));
|
|
1308
|
+
}
|
|
1309
|
+
async releaseSlot(id, tx) {
|
|
1310
|
+
const db = resolveDb(this.db, tx);
|
|
1311
|
+
await db.update(promoCodes).set({
|
|
1312
|
+
redemptionsCount: sql`GREATEST(${promoCodes.redemptionsCount} - 1, 0)`,
|
|
1313
|
+
status: sql`CASE WHEN ${promoCodes.status} = 'EXHAUSTED' THEN 'ACTIVE' ELSE ${promoCodes.status} END`,
|
|
1314
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1315
|
+
}).where(eq7(promoCodes.id, id));
|
|
1316
|
+
}
|
|
1317
|
+
async expireDueCodes(now) {
|
|
1318
|
+
const expired = await this.db.update(promoCodes).set({
|
|
1319
|
+
status: "EXPIRED",
|
|
1320
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1321
|
+
}).where(and7(inArray2(promoCodes.status, [
|
|
1322
|
+
"ACTIVE",
|
|
1323
|
+
"PAUSED"
|
|
1324
|
+
]), lt2(promoCodes.validUntil, now))).returning({
|
|
1325
|
+
id: promoCodes.id
|
|
1326
|
+
});
|
|
1327
|
+
return expired.length;
|
|
1328
|
+
}
|
|
1329
|
+
};
|
|
1330
|
+
DrizzlePromoCodeRepository = _ts_decorate11([
|
|
1331
|
+
Injectable11(),
|
|
1332
|
+
_ts_param10(0, Inject10(DRIZZLE_DB_TOKEN)),
|
|
1333
|
+
_ts_metadata10("design:type", Function),
|
|
1334
|
+
_ts_metadata10("design:paramtypes", [
|
|
1335
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
1336
|
+
])
|
|
1337
|
+
], DrizzlePromoCodeRepository);
|
|
1338
|
+
function hasFreeSlot(id) {
|
|
1339
|
+
return and7(eq7(promoCodes.id, id), eq7(promoCodes.status, "ACTIVE"), isNull5(promoCodes.deletedAt), or(isNull5(promoCodes.maxRedemptions), sql`${promoCodes.redemptionsCount} + ${promoCodes.heldCount} < ${promoCodes.maxRedemptions}`));
|
|
1340
|
+
}
|
|
1341
|
+
__name(hasFreeSlot, "hasFreeSlot");
|
|
1342
|
+
function normalizeCode(code) {
|
|
1343
|
+
return code.trim().toUpperCase();
|
|
1344
|
+
}
|
|
1345
|
+
__name(normalizeCode, "normalizeCode");
|
|
1346
|
+
function toRecord(row) {
|
|
1347
|
+
return {
|
|
1348
|
+
id: row.id,
|
|
1349
|
+
code: row.code,
|
|
1350
|
+
valueType: row.valueType,
|
|
1351
|
+
value: String(row.value),
|
|
1352
|
+
durationType: row.durationType,
|
|
1353
|
+
durationValue: row.durationValue,
|
|
1354
|
+
validFrom: row.validFrom,
|
|
1355
|
+
validUntil: row.validUntil,
|
|
1356
|
+
maxRedemptions: row.maxRedemptions,
|
|
1357
|
+
redemptionsCount: row.redemptionsCount,
|
|
1358
|
+
heldCount: row.heldCount,
|
|
1359
|
+
appliesToPlans: row.appliesToPlans ?? [],
|
|
1360
|
+
appliesToBilling: row.appliesToBilling ?? null,
|
|
1361
|
+
firstTimeCustomersOnly: row.firstTimeCustomersOnly,
|
|
1362
|
+
minimumPlanAmountGross: row.minimumPlanAmountGross === null ? null : String(row.minimumPlanAmountGross),
|
|
1363
|
+
allowZeroInvoice: row.allowZeroInvoice,
|
|
1364
|
+
status: row.status,
|
|
1365
|
+
description: row.description,
|
|
1366
|
+
campaignTag: row.campaignTag,
|
|
1367
|
+
revenueDeductionAccount: row.revenueDeductionAccount,
|
|
1368
|
+
createdById: row.createdById,
|
|
1369
|
+
createdAt: row.createdAt,
|
|
1370
|
+
updatedAt: row.updatedAt,
|
|
1371
|
+
deletedAt: row.deletedAt
|
|
1372
|
+
};
|
|
1373
|
+
}
|
|
1374
|
+
__name(toRecord, "toRecord");
|
|
1375
|
+
|
|
1376
|
+
// src/drizzle-promo-code-hold.repository.ts
|
|
1377
|
+
function _ts_decorate12(decorators, target, key, desc10) {
|
|
1378
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
1379
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
1380
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1381
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1382
|
+
}
|
|
1383
|
+
__name(_ts_decorate12, "_ts_decorate");
|
|
1384
|
+
function _ts_metadata11(k, v) {
|
|
1385
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1386
|
+
}
|
|
1387
|
+
__name(_ts_metadata11, "_ts_metadata");
|
|
1388
|
+
function _ts_param11(paramIndex, decorator) {
|
|
1389
|
+
return function(target, key) {
|
|
1390
|
+
decorator(target, key, paramIndex);
|
|
1391
|
+
};
|
|
1392
|
+
}
|
|
1393
|
+
__name(_ts_param11, "_ts_param");
|
|
1394
|
+
var HOLD_FIELDS = {
|
|
1395
|
+
id: promoCodeHolds.id,
|
|
1396
|
+
promoCodeId: promoCodeHolds.promoCodeId,
|
|
1397
|
+
checkoutOfferId: promoCodeHolds.checkoutOfferId,
|
|
1398
|
+
expiresAt: promoCodeHolds.expiresAt,
|
|
1399
|
+
createdAt: promoCodeHolds.createdAt
|
|
1400
|
+
};
|
|
1401
|
+
var CURRENT_TRANSACTION = sql2`txid_current()`;
|
|
1402
|
+
var atExpiry = /* @__PURE__ */ __name((expiresAt) => sql2.param(expiresAt, promoCodeHolds.expiresAt), "atExpiry");
|
|
1403
|
+
var DrizzlePromoCodeHoldRepository = class {
|
|
1404
|
+
static {
|
|
1405
|
+
__name(this, "DrizzlePromoCodeHoldRepository");
|
|
1406
|
+
}
|
|
1407
|
+
db;
|
|
1408
|
+
constructor(db) {
|
|
1409
|
+
this.db = db;
|
|
1410
|
+
}
|
|
1411
|
+
async findByCheckoutOffer(checkoutOfferId, tx) {
|
|
1412
|
+
const rows = await resolveDb(this.db, tx).select(HOLD_FIELDS).from(promoCodeHolds).where(eq8(promoCodeHolds.checkoutOfferId, checkoutOfferId)).limit(1);
|
|
1413
|
+
return rows[0] ?? null;
|
|
1414
|
+
}
|
|
1415
|
+
async take(hold) {
|
|
1416
|
+
return this.onTransaction(void 0, async (db) => {
|
|
1417
|
+
const code = await db.select({
|
|
1418
|
+
id: promoCodes.id
|
|
1419
|
+
}).from(promoCodes).where(eq8(promoCodes.id, hold.promoCodeId)).for("update");
|
|
1420
|
+
if (code.length === 0) return {
|
|
1421
|
+
outcome: "no-slot"
|
|
1422
|
+
};
|
|
1423
|
+
const held = await db.select({
|
|
1424
|
+
id: promoCodeHolds.id
|
|
1425
|
+
}).from(promoCodeHolds).where(eq8(promoCodeHolds.checkoutOfferId, hold.checkoutOfferId)).limit(1);
|
|
1426
|
+
if (held.length > 0) return {
|
|
1427
|
+
outcome: "offer-holds-one"
|
|
1428
|
+
};
|
|
1429
|
+
const counted = await db.update(promoCodes).set({
|
|
1430
|
+
heldCount: sql2`${promoCodes.heldCount} + 1`,
|
|
1431
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1432
|
+
}).where(hasFreeSlot(hold.promoCodeId)).returning({
|
|
1433
|
+
id: promoCodes.id
|
|
1434
|
+
});
|
|
1435
|
+
if (counted.length === 0) return {
|
|
1436
|
+
outcome: "no-slot"
|
|
1437
|
+
};
|
|
1438
|
+
const [created] = await db.insert(promoCodeHolds).values({
|
|
1439
|
+
id: randomUUID6(),
|
|
1440
|
+
...hold
|
|
1441
|
+
}).returning(HOLD_FIELDS);
|
|
1442
|
+
return {
|
|
1443
|
+
outcome: "taken",
|
|
1444
|
+
hold: created
|
|
1445
|
+
};
|
|
1446
|
+
});
|
|
1447
|
+
}
|
|
1448
|
+
async extend(checkoutOfferId, promoCodeId, expiresAt) {
|
|
1449
|
+
const extended = await this.db.update(promoCodeHolds).set({
|
|
1450
|
+
expiresAt: sql2`GREATEST(${promoCodeHolds.expiresAt}, ${atExpiry(expiresAt)})`
|
|
1451
|
+
}).where(and8(eq8(promoCodeHolds.checkoutOfferId, checkoutOfferId), eq8(promoCodeHolds.promoCodeId, promoCodeId))).returning({
|
|
1452
|
+
id: promoCodeHolds.id
|
|
1453
|
+
});
|
|
1454
|
+
return extended.length === 1;
|
|
1455
|
+
}
|
|
1456
|
+
async release(checkoutOfferId, tx) {
|
|
1457
|
+
return this.releaseWhere(eq8(promoCodeHolds.checkoutOfferId, checkoutOfferId), tx);
|
|
1458
|
+
}
|
|
1459
|
+
async releaseIfUnmoved(checkoutOfferId, expiresAt) {
|
|
1460
|
+
return this.releaseWhere(and8(eq8(promoCodeHolds.checkoutOfferId, checkoutOfferId), eq8(promoCodeHolds.expiresAt, expiresAt)));
|
|
1461
|
+
}
|
|
1462
|
+
async handOver(checkoutOfferId, now, tx) {
|
|
1463
|
+
const marked = await resolveDb(this.db, tx).update(promoCodeHolds).set({
|
|
1464
|
+
handedOverTx: CURRENT_TRANSACTION
|
|
1465
|
+
}).where(and8(eq8(promoCodeHolds.checkoutOfferId, checkoutOfferId), gt(promoCodeHolds.expiresAt, now))).returning({
|
|
1466
|
+
id: promoCodeHolds.id
|
|
1467
|
+
});
|
|
1468
|
+
return marked.length === 1;
|
|
1469
|
+
}
|
|
1470
|
+
async convertHandedOver(promoCodeId, tx) {
|
|
1471
|
+
const db = resolveDb(this.db, tx);
|
|
1472
|
+
const handedOver = await db.select({
|
|
1473
|
+
id: promoCodeHolds.id
|
|
1474
|
+
}).from(promoCodeHolds).where(and8(eq8(promoCodeHolds.promoCodeId, promoCodeId), eq8(promoCodeHolds.handedOverTx, CURRENT_TRANSACTION))).limit(1);
|
|
1475
|
+
if (handedOver.length === 0) return false;
|
|
1476
|
+
const gone = await db.delete(promoCodeHolds).where(eq8(promoCodeHolds.id, handedOver[0].id)).returning({
|
|
1477
|
+
promoCodeId: promoCodeHolds.promoCodeId
|
|
1478
|
+
});
|
|
1479
|
+
await giveBack(db, gone, {
|
|
1480
|
+
redeemed: true
|
|
1481
|
+
});
|
|
1482
|
+
return gone.length === 1;
|
|
1483
|
+
}
|
|
1484
|
+
async expireDue(now, promoCodeId, tx) {
|
|
1485
|
+
return this.onTransaction(tx, async (db) => {
|
|
1486
|
+
const gone = await db.delete(promoCodeHolds).where(and8(lte2(promoCodeHolds.expiresAt, now), sql2`${promoCodeHolds.handedOverTx} IS DISTINCT FROM ${CURRENT_TRANSACTION}`, promoCodeId ? eq8(promoCodeHolds.promoCodeId, promoCodeId) : void 0)).returning({
|
|
1487
|
+
promoCodeId: promoCodeHolds.promoCodeId
|
|
1488
|
+
});
|
|
1489
|
+
await giveBack(db, gone, {
|
|
1490
|
+
redeemed: false
|
|
1491
|
+
});
|
|
1492
|
+
return gone.length;
|
|
1493
|
+
});
|
|
1494
|
+
}
|
|
1495
|
+
/** Deletes the holds `where` matches and gives their slots back. */
|
|
1496
|
+
releaseWhere(where, tx) {
|
|
1497
|
+
return this.onTransaction(tx, async (db) => {
|
|
1498
|
+
const gone = await db.delete(promoCodeHolds).where(where).returning({
|
|
1499
|
+
promoCodeId: promoCodeHolds.promoCodeId
|
|
1500
|
+
});
|
|
1501
|
+
await giveBack(db, gone, {
|
|
1502
|
+
redeemed: false
|
|
1503
|
+
});
|
|
1504
|
+
return gone.length === 1;
|
|
1505
|
+
});
|
|
1506
|
+
}
|
|
1507
|
+
/** Runs `work` on a transaction of its own, a savepoint where the caller has one. */
|
|
1508
|
+
onTransaction(tx, work) {
|
|
1509
|
+
return resolveDb(this.db, tx).transaction((transaction) => work(transaction));
|
|
1510
|
+
}
|
|
1511
|
+
};
|
|
1512
|
+
DrizzlePromoCodeHoldRepository = _ts_decorate12([
|
|
1513
|
+
Injectable12(),
|
|
1514
|
+
_ts_param11(0, Inject11(DRIZZLE_DB_TOKEN)),
|
|
1515
|
+
_ts_metadata11("design:type", Function),
|
|
1516
|
+
_ts_metadata11("design:paramtypes", [
|
|
1517
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
1518
|
+
])
|
|
1519
|
+
], DrizzlePromoCodeHoldRepository);
|
|
1520
|
+
async function giveBack(db, ended, { redeemed }) {
|
|
1521
|
+
const perCode = /* @__PURE__ */ new Map();
|
|
1522
|
+
for (const { promoCodeId } of ended) {
|
|
1523
|
+
perCode.set(promoCodeId, (perCode.get(promoCodeId) ?? 0) + 1);
|
|
1524
|
+
}
|
|
1525
|
+
for (const promoCodeId of [
|
|
1526
|
+
...perCode.keys()
|
|
1527
|
+
].sort()) {
|
|
1528
|
+
const count4 = perCode.get(promoCodeId) ?? 0;
|
|
1529
|
+
await db.update(promoCodes).set({
|
|
1530
|
+
heldCount: sql2`GREATEST(${promoCodes.heldCount} - ${count4}, 0)`,
|
|
1531
|
+
...redeemed ? {
|
|
1532
|
+
redemptionsCount: sql2`${promoCodes.redemptionsCount} + ${count4}`
|
|
1533
|
+
} : {},
|
|
1534
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1535
|
+
}).where(eq8(promoCodes.id, promoCodeId));
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
__name(giveBack, "giveBack");
|
|
1539
|
+
|
|
1540
|
+
// src/drizzle-promo-code-redemption.repository.ts
|
|
1541
|
+
import { randomUUID as randomUUID7 } from "node:crypto";
|
|
1542
|
+
import { Inject as Inject12, Injectable as Injectable13 } from "@nestjs/common";
|
|
1543
|
+
import { and as and9, desc as desc4, eq as eq9, lt as lt3 } from "drizzle-orm";
|
|
1544
|
+
function _ts_decorate13(decorators, target, key, desc10) {
|
|
1545
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
1546
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
1547
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1548
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1549
|
+
}
|
|
1550
|
+
__name(_ts_decorate13, "_ts_decorate");
|
|
1551
|
+
function _ts_metadata12(k, v) {
|
|
1552
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1553
|
+
}
|
|
1554
|
+
__name(_ts_metadata12, "_ts_metadata");
|
|
1555
|
+
function _ts_param12(paramIndex, decorator) {
|
|
1556
|
+
return function(target, key) {
|
|
1557
|
+
decorator(target, key, paramIndex);
|
|
1558
|
+
};
|
|
1559
|
+
}
|
|
1560
|
+
__name(_ts_param12, "_ts_param");
|
|
1561
|
+
var DrizzlePromoCodeRedemptionRepository = class {
|
|
1562
|
+
static {
|
|
1563
|
+
__name(this, "DrizzlePromoCodeRedemptionRepository");
|
|
1564
|
+
}
|
|
1565
|
+
db;
|
|
1566
|
+
constructor(db) {
|
|
1567
|
+
this.db = db;
|
|
1568
|
+
}
|
|
1569
|
+
async findBySubscription(subscriptionId, tx) {
|
|
1570
|
+
const db = resolveDb(this.db, tx);
|
|
1571
|
+
const rows = await db.select().from(promoCodeRedemptions).where(eq9(promoCodeRedemptions.subscriptionId, subscriptionId)).limit(1);
|
|
1572
|
+
const row = rows[0];
|
|
1573
|
+
return row ? toRecord2(row) : null;
|
|
1574
|
+
}
|
|
1575
|
+
async create(data, tx) {
|
|
1576
|
+
const db = resolveDb(this.db, tx);
|
|
1577
|
+
const rows = await db.insert(promoCodeRedemptions).values({
|
|
1578
|
+
id: randomUUID7(),
|
|
1579
|
+
promoCodeId: data.promoCodeId,
|
|
1580
|
+
subscriptionId: data.subscriptionId,
|
|
1581
|
+
tenantId: data.tenantId,
|
|
1582
|
+
appliedValueType: data.appliedValueType,
|
|
1583
|
+
appliedValue: data.appliedValue,
|
|
1584
|
+
appliedDurationType: data.appliedDurationType,
|
|
1585
|
+
appliedDurationValue: data.appliedDurationValue,
|
|
1586
|
+
startsAt: data.startsAt,
|
|
1587
|
+
endsAt: data.endsAt
|
|
1588
|
+
}).returning();
|
|
1589
|
+
return toRecord2(rows[0]);
|
|
1590
|
+
}
|
|
1591
|
+
async setReversed(id, tx) {
|
|
1592
|
+
const db = resolveDb(this.db, tx);
|
|
1593
|
+
const rows = await db.update(promoCodeRedemptions).set({
|
|
1594
|
+
status: "REVERSED",
|
|
1595
|
+
reversedAt: /* @__PURE__ */ new Date()
|
|
1596
|
+
}).where(eq9(promoCodeRedemptions.id, id)).returning();
|
|
1597
|
+
const row = rows[0];
|
|
1598
|
+
if (!row) {
|
|
1599
|
+
throw new Error(`PromoCodeRedemption ${id} not found.`);
|
|
1600
|
+
}
|
|
1601
|
+
return toRecord2(row);
|
|
1602
|
+
}
|
|
1603
|
+
async countByPromoCode(promoCodeId, status) {
|
|
1604
|
+
const rows = await this.db.select({
|
|
1605
|
+
id: promoCodeRedemptions.id
|
|
1606
|
+
}).from(promoCodeRedemptions).where(and9(eq9(promoCodeRedemptions.promoCodeId, promoCodeId), status ? eq9(promoCodeRedemptions.status, status) : void 0));
|
|
1607
|
+
return rows.length;
|
|
1608
|
+
}
|
|
1609
|
+
async listByPromoCode(promoCodeId) {
|
|
1610
|
+
const rows = await this.db.select().from(promoCodeRedemptions).where(eq9(promoCodeRedemptions.promoCodeId, promoCodeId)).orderBy(desc4(promoCodeRedemptions.redeemedAt));
|
|
1611
|
+
return rows.map(toRecord2);
|
|
1612
|
+
}
|
|
1613
|
+
async expireDueRedemptions(now) {
|
|
1614
|
+
const expired = await this.db.update(promoCodeRedemptions).set({
|
|
1615
|
+
status: "EXPIRED"
|
|
1616
|
+
}).where(and9(eq9(promoCodeRedemptions.status, "ACTIVE"), lt3(promoCodeRedemptions.endsAt, now))).returning({
|
|
1617
|
+
id: promoCodeRedemptions.id
|
|
1618
|
+
});
|
|
1619
|
+
return expired.length;
|
|
1620
|
+
}
|
|
1621
|
+
};
|
|
1622
|
+
DrizzlePromoCodeRedemptionRepository = _ts_decorate13([
|
|
1623
|
+
Injectable13(),
|
|
1624
|
+
_ts_param12(0, Inject12(DRIZZLE_DB_TOKEN)),
|
|
1625
|
+
_ts_metadata12("design:type", Function),
|
|
1626
|
+
_ts_metadata12("design:paramtypes", [
|
|
1627
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
1628
|
+
])
|
|
1629
|
+
], DrizzlePromoCodeRedemptionRepository);
|
|
1630
|
+
function toRecord2(row) {
|
|
1631
|
+
return {
|
|
1632
|
+
id: row.id,
|
|
1633
|
+
promoCodeId: row.promoCodeId,
|
|
1634
|
+
subscriptionId: row.subscriptionId,
|
|
1635
|
+
tenantId: row.tenantId,
|
|
1636
|
+
appliedValueType: row.appliedValueType,
|
|
1637
|
+
appliedValue: String(row.appliedValue),
|
|
1638
|
+
appliedDurationType: row.appliedDurationType,
|
|
1639
|
+
appliedDurationValue: row.appliedDurationValue,
|
|
1640
|
+
startsAt: row.startsAt,
|
|
1641
|
+
endsAt: row.endsAt,
|
|
1642
|
+
status: row.status,
|
|
1643
|
+
redeemedAt: row.redeemedAt,
|
|
1644
|
+
reversedAt: row.reversedAt
|
|
1645
|
+
};
|
|
1646
|
+
}
|
|
1647
|
+
__name(toRecord2, "toRecord");
|
|
1648
|
+
|
|
1649
|
+
// src/drizzle-bundle.repository.ts
|
|
1650
|
+
import { randomUUID as randomUUID8 } from "node:crypto";
|
|
1651
|
+
import { Inject as Inject13, Injectable as Injectable14, Optional } from "@nestjs/common";
|
|
1652
|
+
import { and as and10, asc as asc2, desc as desc5, eq as eq10, gte as gte4, isNull as isNull6, lte as lte3, ne, or as or2, sql as sql3 } from "drizzle-orm";
|
|
1653
|
+
import { bundleDraftDefaults, bundleStemDefaults, previousUtcDay, toBundleStemRow } from "@saasicat/core";
|
|
1654
|
+
function _ts_decorate14(decorators, target, key, desc10) {
|
|
1655
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
1656
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
1657
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1658
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1659
|
+
}
|
|
1660
|
+
__name(_ts_decorate14, "_ts_decorate");
|
|
1661
|
+
function _ts_metadata13(k, v) {
|
|
1662
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1663
|
+
}
|
|
1664
|
+
__name(_ts_metadata13, "_ts_metadata");
|
|
1665
|
+
function _ts_param13(paramIndex, decorator) {
|
|
1666
|
+
return function(target, key) {
|
|
1667
|
+
decorator(target, key, paramIndex);
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
__name(_ts_param13, "_ts_param");
|
|
1671
|
+
var DrizzleBundleRepository = class {
|
|
1672
|
+
static {
|
|
1673
|
+
__name(this, "DrizzleBundleRepository");
|
|
1674
|
+
}
|
|
1675
|
+
db;
|
|
1676
|
+
validityWindows;
|
|
1677
|
+
constructor(db, options = {}) {
|
|
1678
|
+
this.db = db;
|
|
1679
|
+
this.validityWindows = options.validityWindows ?? false;
|
|
1680
|
+
if (this.validityWindows) {
|
|
1681
|
+
this.findActiveBundleVersion = (bundleId, asOf, tx) => this.activeVersionAt(bundleId, asOf ?? /* @__PURE__ */ new Date(), tx);
|
|
1682
|
+
}
|
|
1683
|
+
}
|
|
1684
|
+
findActiveBundleVersion;
|
|
1685
|
+
// ─── Stems ───
|
|
1686
|
+
async list(filter) {
|
|
1687
|
+
const excludeDeleted = filter.excludeDeleted ?? true;
|
|
1688
|
+
const rows = await this.db.select().from(bundles).where(excludeDeleted ? isNull6(bundles.deletedAt) : void 0).orderBy(asc2(bundles.sortOrder), asc2(bundles.bundleKey));
|
|
1689
|
+
return rows.map(toBundleStemRow);
|
|
1690
|
+
}
|
|
1691
|
+
async findById(bundleId) {
|
|
1692
|
+
const rows = await this.db.select().from(bundles).where(eq10(bundles.id, bundleId)).limit(1);
|
|
1693
|
+
return rows[0] ? toBundleStemRow(rows[0]) : null;
|
|
1694
|
+
}
|
|
1695
|
+
/**
|
|
1696
|
+
* Whether this project already uses this bundle key — **including retired
|
|
1697
|
+
* bundles**.
|
|
1698
|
+
*
|
|
1699
|
+
* Its one caller is the duplicate check in `createBundle`, and the question
|
|
1700
|
+
* it asks is the database's: `bundles_bundleKey_key` is an
|
|
1701
|
+
* unconditional unique index, so a soft-deleted bundle still occupies its
|
|
1702
|
+
* key. Excluding retired rows here makes the check pass and the insert then
|
|
1703
|
+
* fail on the constraint — a 500 where the service had
|
|
1704
|
+
* `BUNDLE_ALREADY_EXISTS` ready.
|
|
1705
|
+
*
|
|
1706
|
+
* Two review rounds landed on this line from opposite sides, which is what
|
|
1707
|
+
* finally settled it: there is no caller that wants the active-catalogue
|
|
1708
|
+
* reading. `list` is that lookup, and it excludes retired rows by default.
|
|
1709
|
+
*/
|
|
1710
|
+
async findByKey(bundleKey) {
|
|
1711
|
+
const rows = await this.db.select().from(bundles).where(eq10(bundles.bundleKey, bundleKey)).limit(1);
|
|
1712
|
+
return rows[0] ? toBundleStemRow(rows[0]) : null;
|
|
1713
|
+
}
|
|
1714
|
+
async create(data) {
|
|
1715
|
+
const now = /* @__PURE__ */ new Date();
|
|
1716
|
+
const rows = await this.db.insert(bundles).values({
|
|
1717
|
+
id: randomUUID8(),
|
|
1718
|
+
...bundleStemDefaults(data),
|
|
1719
|
+
updatedAt: now
|
|
1720
|
+
}).returning();
|
|
1721
|
+
return toBundleStemRow(rows[0]);
|
|
1722
|
+
}
|
|
1723
|
+
async update(bundleId, data) {
|
|
1724
|
+
const patch = {
|
|
1725
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1726
|
+
};
|
|
1727
|
+
if (data.label !== void 0) patch.label = data.label;
|
|
1728
|
+
if (data.description !== void 0) patch.description = data.description;
|
|
1729
|
+
if (data.icon !== void 0) patch.icon = data.icon;
|
|
1730
|
+
if (data.sortOrder !== void 0) patch.sortOrder = data.sortOrder;
|
|
1731
|
+
if (data.i18n !== void 0) patch.i18n = data.i18n;
|
|
1732
|
+
const rows = await this.db.update(bundles).set(patch).where(eq10(bundles.id, bundleId)).returning();
|
|
1733
|
+
if (!rows[0]) throw new Error(`Bundle '${bundleId}' not found.`);
|
|
1734
|
+
return toBundleStemRow(rows[0]);
|
|
1735
|
+
}
|
|
1736
|
+
async softDelete(bundleId) {
|
|
1737
|
+
await this.db.update(bundles).set({
|
|
1738
|
+
deletedAt: /* @__PURE__ */ new Date(),
|
|
1739
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1740
|
+
}).where(eq10(bundles.id, bundleId));
|
|
1741
|
+
}
|
|
1742
|
+
// ─── Versions ───
|
|
1743
|
+
async listVersions(bundleId) {
|
|
1744
|
+
const rows = await this.db.select().from(bundleVersions).where(eq10(bundleVersions.bundleId, bundleId)).orderBy(asc2(bundleVersions.version));
|
|
1745
|
+
const stem = await this.findById(bundleId);
|
|
1746
|
+
return rows.map((row) => this.toVersionRow(row, stem));
|
|
1747
|
+
}
|
|
1748
|
+
async findVersionById(versionId, tx) {
|
|
1749
|
+
const rows = await resolveDb(this.db, tx).select().from(bundleVersions).where(eq10(bundleVersions.id, versionId)).limit(1);
|
|
1750
|
+
if (!rows[0]) return null;
|
|
1751
|
+
return this.toVersionRow(rows[0], await this.stemOn(resolveDb(this.db, tx), rows[0].bundleId));
|
|
1752
|
+
}
|
|
1753
|
+
async findCurrentDraft(bundleId) {
|
|
1754
|
+
const rows = await this.db.select().from(bundleVersions).where(and10(eq10(bundleVersions.bundleId, bundleId), isNull6(bundleVersions.publishedAt))).limit(1);
|
|
1755
|
+
if (!rows[0]) return null;
|
|
1756
|
+
return this.toVersionRow(rows[0], await this.findById(bundleId));
|
|
1757
|
+
}
|
|
1758
|
+
async findLatestLive(bundleId, tx) {
|
|
1759
|
+
const rows = await resolveDb(this.db, tx).select().from(bundleVersions).where(and10(eq10(bundleVersions.bundleId, bundleId), sql3`${bundleVersions.publishedAt} IS NOT NULL`, isNull6(bundleVersions.supersededAt))).orderBy(desc5(bundleVersions.version)).limit(1);
|
|
1760
|
+
return this.withStem(rows[0], bundleId, tx);
|
|
1761
|
+
}
|
|
1762
|
+
/**
|
|
1763
|
+
* The version bookable at `asOf`: published, inside its validity window.
|
|
1764
|
+
*
|
|
1765
|
+
* Deliberately not filtered by `supersededAt` — a superseded predecessor is
|
|
1766
|
+
* still the bookable one until its window closes, which is the whole point
|
|
1767
|
+
* of auto-succession. `validUntil` is day-inclusive, so a version is active
|
|
1768
|
+
* throughout its last day.
|
|
1769
|
+
*/
|
|
1770
|
+
async activeVersionAt(bundleId, asOf, tx) {
|
|
1771
|
+
const dayStart = new Date(asOf);
|
|
1772
|
+
dayStart.setUTCHours(0, 0, 0, 0);
|
|
1773
|
+
const rows = await resolveDb(this.db, tx).select().from(bundleVersions).where(and10(eq10(bundleVersions.bundleId, bundleId), sql3`${bundleVersions.publishedAt} IS NOT NULL`, or2(isNull6(bundleVersions.validFrom), lte3(bundleVersions.validFrom, asOf)), or2(isNull6(bundleVersions.validUntil), gte4(bundleVersions.validUntil, dayStart)))).orderBy(sql3`"validFrom" DESC NULLS LAST`, desc5(bundleVersions.version)).limit(1);
|
|
1774
|
+
return this.withStem(rows[0], bundleId, tx);
|
|
1775
|
+
}
|
|
1776
|
+
async createDraft(data) {
|
|
1777
|
+
const existingDraft = await this.findCurrentDraft(data.bundleId);
|
|
1778
|
+
if (existingDraft) {
|
|
1779
|
+
throw new Error(`Bundle '${data.bundleId}' already has a draft version (v${existingDraft.version}); only one draft per bundle is allowed.`);
|
|
1780
|
+
}
|
|
1781
|
+
const latest = await this.db.select({
|
|
1782
|
+
version: bundleVersions.version
|
|
1783
|
+
}).from(bundleVersions).where(eq10(bundleVersions.bundleId, data.bundleId)).orderBy(desc5(bundleVersions.version)).limit(1);
|
|
1784
|
+
const now = /* @__PURE__ */ new Date();
|
|
1785
|
+
const rows = await this.db.insert(bundleVersions).values({
|
|
1786
|
+
id: randomUUID8(),
|
|
1787
|
+
bundleId: data.bundleId,
|
|
1788
|
+
version: latest[0] ? latest[0].version + 1 : 1,
|
|
1789
|
+
...bundleDraftDefaults(data),
|
|
1790
|
+
validFrom: this.validityWindows ? toNullableDate(data.validFrom) : null,
|
|
1791
|
+
validUntil: this.validityWindows ? toNullableDate(data.validUntil) : null,
|
|
1792
|
+
updatedAt: now
|
|
1793
|
+
}).returning();
|
|
1794
|
+
return this.toVersionRow(rows[0], await this.findById(data.bundleId));
|
|
1795
|
+
}
|
|
1796
|
+
async updateDraft(versionId, data) {
|
|
1797
|
+
const patch = {
|
|
1798
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
1799
|
+
};
|
|
1800
|
+
if (data.features !== void 0) patch.features = data.features;
|
|
1801
|
+
if (data.quotas !== void 0) patch.quotas = data.quotas;
|
|
1802
|
+
if (data.compatibility !== void 0) patch.compatibility = data.compatibility;
|
|
1803
|
+
if (data.pricingOverrides !== void 0) patch.pricingOverrides = data.pricingOverrides;
|
|
1804
|
+
if (data.monthlyNet !== void 0) patch.monthlyNet = data.monthlyNet;
|
|
1805
|
+
if (data.yearlyNet !== void 0) patch.yearlyNet = data.yearlyNet;
|
|
1806
|
+
if (data.marketed !== void 0) patch.marketed = data.marketed;
|
|
1807
|
+
if (data.changeNote !== void 0) patch.changeNote = data.changeNote;
|
|
1808
|
+
if (this.validityWindows) {
|
|
1809
|
+
if (data.validFrom !== void 0) patch.validFrom = toNullableDate(data.validFrom);
|
|
1810
|
+
if (data.validUntil !== void 0) patch.validUntil = toNullableDate(data.validUntil);
|
|
1811
|
+
}
|
|
1812
|
+
const rows = await this.db.update(bundleVersions).set(patch).where(eq10(bundleVersions.id, versionId)).returning();
|
|
1813
|
+
if (!rows[0]) throw new Error(`BundleVersion '${versionId}' not found.`);
|
|
1814
|
+
return this.toVersionRow(rows[0], await this.findById(rows[0].bundleId));
|
|
1815
|
+
}
|
|
1816
|
+
async publishDraft(versionId, publishMeta, tx) {
|
|
1817
|
+
if (tx !== void 0) return this.publishWithin(resolveDb(this.db, tx), versionId, publishMeta);
|
|
1818
|
+
return this.db.transaction((transaction) => this.publishWithin(transaction, versionId, publishMeta));
|
|
1819
|
+
}
|
|
1820
|
+
async publishWithin(db, versionId, publishMeta) {
|
|
1821
|
+
const now = /* @__PURE__ */ new Date();
|
|
1822
|
+
const publishedRows = await db.update(bundleVersions).set({
|
|
1823
|
+
publishedAt: now,
|
|
1824
|
+
publishedByUserId: publishMeta.publishedByUserId,
|
|
1825
|
+
publishedChanges: publishMeta.publishedChanges,
|
|
1826
|
+
nonRegressive: publishMeta.nonRegressive,
|
|
1827
|
+
updatedAt: now,
|
|
1828
|
+
...this.validityWindows ? {
|
|
1829
|
+
validFrom: publishMeta.validFrom,
|
|
1830
|
+
validUntil: publishMeta.validUntil
|
|
1831
|
+
} : {}
|
|
1832
|
+
}).where(and10(eq10(bundleVersions.id, versionId), isNull6(bundleVersions.publishedAt))).returning();
|
|
1833
|
+
if (!publishedRows[0]) {
|
|
1834
|
+
throw new Error(`BundleVersion '${versionId}' not found or already published.`);
|
|
1835
|
+
}
|
|
1836
|
+
const draft = publishedRows[0];
|
|
1837
|
+
await db.update(bundleVersions).set(this.validityWindows ? {
|
|
1838
|
+
supersededAt: now,
|
|
1839
|
+
validUntil: previousUtcDay(publishMeta.validFrom)
|
|
1840
|
+
} : {
|
|
1841
|
+
supersededAt: now
|
|
1842
|
+
}).where(and10(eq10(bundleVersions.bundleId, draft.bundleId), sql3`${bundleVersions.publishedAt} IS NOT NULL`, isNull6(bundleVersions.supersededAt), ne(bundleVersions.id, versionId)));
|
|
1843
|
+
const stemRows = await db.select().from(bundles).where(eq10(bundles.id, draft.bundleId)).limit(1);
|
|
1844
|
+
return this.toVersionRow(publishedRows[0], stemRows[0] ? toBundleStemRow(stemRows[0]) : null);
|
|
1845
|
+
}
|
|
1846
|
+
async deleteDraft(versionId) {
|
|
1847
|
+
const deleted = await this.db.delete(bundleVersions).where(and10(eq10(bundleVersions.id, versionId), isNull6(bundleVersions.publishedAt))).returning({
|
|
1848
|
+
id: bundleVersions.id
|
|
1849
|
+
});
|
|
1850
|
+
if (deleted.length > 0) return;
|
|
1851
|
+
const remaining = await this.db.select({
|
|
1852
|
+
publishedAt: bundleVersions.publishedAt
|
|
1853
|
+
}).from(bundleVersions).where(eq10(bundleVersions.id, versionId)).limit(1);
|
|
1854
|
+
if (!remaining[0]) return;
|
|
1855
|
+
throw new Error(`BundleVersion '${versionId}' is already published and cannot be discarded (published versions are immutable \u2014 contract protection P1).`);
|
|
1856
|
+
}
|
|
1857
|
+
/**
|
|
1858
|
+
* One version row plus its stem, or null — the tail every version lookup
|
|
1859
|
+
* ends with, and the reason it is here rather than written out each time:
|
|
1860
|
+
* the stem has to be read on the caller's connection, and a copy that
|
|
1861
|
+
* forgets that is the deadlock this class already had once.
|
|
1862
|
+
*/
|
|
1863
|
+
async withStem(row, bundleId, tx) {
|
|
1864
|
+
if (!row) return null;
|
|
1865
|
+
return this.toVersionRow(row, await this.stemOn(resolveDb(this.db, tx), bundleId));
|
|
1866
|
+
}
|
|
1867
|
+
/**
|
|
1868
|
+
* The stem, read on the connection the caller is already holding.
|
|
1869
|
+
*
|
|
1870
|
+
* `this.db` would take a second one from the pool. Inside a transaction —
|
|
1871
|
+
* which is where `enforceLimit()` calls this, holding a row lock — that
|
|
1872
|
+
* second connection can only be released when the transaction ends, and the
|
|
1873
|
+
* transaction cannot end until this query returns. With a small pool, or
|
|
1874
|
+
* enough concurrent checks to occupy it, entitlement resolution stops
|
|
1875
|
+
* dead. A version query that honours `tx` and a stem query beside it that
|
|
1876
|
+
* does not is the shape that hides it: the deadlock needs load to appear.
|
|
1877
|
+
*/
|
|
1878
|
+
async stemOn(db, bundleId) {
|
|
1879
|
+
const rows = await db.select().from(bundles).where(eq10(bundles.id, bundleId)).limit(1);
|
|
1880
|
+
return rows[0] ? toBundleStemRow(rows[0]) : null;
|
|
1881
|
+
}
|
|
1882
|
+
toVersionRow(row, stem) {
|
|
1883
|
+
return {
|
|
1884
|
+
id: row.id,
|
|
1885
|
+
bundleId: row.bundleId,
|
|
1886
|
+
bundleKey: stem?.bundleKey ?? "",
|
|
1887
|
+
label: stem?.label ?? "",
|
|
1888
|
+
version: row.version,
|
|
1889
|
+
baseVersionId: row.baseVersionId,
|
|
1890
|
+
features: row.features ?? [],
|
|
1891
|
+
quotas: row.quotas ?? {},
|
|
1892
|
+
compatibility: row.compatibility ?? {},
|
|
1893
|
+
pricingOverrides: row.pricingOverrides ?? [],
|
|
1894
|
+
monthlyNet: row.monthlyNet,
|
|
1895
|
+
yearlyNet: row.yearlyNet,
|
|
1896
|
+
marketed: row.marketed,
|
|
1897
|
+
publishedAt: toIso(row.publishedAt),
|
|
1898
|
+
supersededAt: toIso(row.supersededAt),
|
|
1899
|
+
publishedChanges: Array.isArray(row.publishedChanges) ? row.publishedChanges : null,
|
|
1900
|
+
changeNote: row.changeNote,
|
|
1901
|
+
nonRegressive: row.nonRegressive,
|
|
1902
|
+
// Null rather than the stored value where the adapter does not
|
|
1903
|
+
// promise windows: returning a date it does not maintain would
|
|
1904
|
+
// invite a caller to trust it.
|
|
1905
|
+
validFrom: this.validityWindows ? toIso(row.validFrom) : null,
|
|
1906
|
+
validUntil: this.validityWindows ? toIso(row.validUntil) : null,
|
|
1907
|
+
createdByUserId: row.createdByUserId,
|
|
1908
|
+
publishedByUserId: row.publishedByUserId,
|
|
1909
|
+
createdAt: row.createdAt.toISOString(),
|
|
1910
|
+
updatedAt: row.updatedAt.toISOString()
|
|
1911
|
+
};
|
|
1912
|
+
}
|
|
1913
|
+
};
|
|
1914
|
+
DrizzleBundleRepository = _ts_decorate14([
|
|
1915
|
+
Injectable14(),
|
|
1916
|
+
_ts_param13(0, Inject13(DRIZZLE_DB_TOKEN)),
|
|
1917
|
+
_ts_param13(1, Optional()),
|
|
1918
|
+
_ts_metadata13("design:type", Function),
|
|
1919
|
+
_ts_metadata13("design:paramtypes", [
|
|
1920
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient,
|
|
1921
|
+
typeof DrizzleBundleRepositoryOptions === "undefined" ? Object : DrizzleBundleRepositoryOptions
|
|
1922
|
+
])
|
|
1923
|
+
], DrizzleBundleRepository);
|
|
1924
|
+
function toIso(value) {
|
|
1925
|
+
return value ? value.toISOString() : null;
|
|
1926
|
+
}
|
|
1927
|
+
__name(toIso, "toIso");
|
|
1928
|
+
function toNullableDate(value) {
|
|
1929
|
+
return value ? new Date(value) : null;
|
|
1930
|
+
}
|
|
1931
|
+
__name(toNullableDate, "toNullableDate");
|
|
1932
|
+
|
|
1933
|
+
// src/drizzle-plan.repository.ts
|
|
1934
|
+
import { randomUUID as randomUUID9 } from "node:crypto";
|
|
1935
|
+
import { Inject as Inject14, Injectable as Injectable15, Optional as Optional2 } from "@nestjs/common";
|
|
1936
|
+
import { and as and11, asc as asc3, desc as desc6, eq as eq11, gte as gte5, inArray as inArray3, isNull as isNull7, lte as lte4, ne as ne2, or as or3, sql as sql4 } from "drizzle-orm";
|
|
1937
|
+
import { previousUtcDay as previousUtcDay2, startOfUtcDay, toPlanRow as toPlanRow2, toPlanVersionRow as toPlanVersionRow2 } from "@saasicat/core";
|
|
1938
|
+
function _ts_decorate15(decorators, target, key, desc10) {
|
|
1939
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
1940
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
1941
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1942
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1943
|
+
}
|
|
1944
|
+
__name(_ts_decorate15, "_ts_decorate");
|
|
1945
|
+
function _ts_metadata14(k, v) {
|
|
1946
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1947
|
+
}
|
|
1948
|
+
__name(_ts_metadata14, "_ts_metadata");
|
|
1949
|
+
function _ts_param14(paramIndex, decorator) {
|
|
1950
|
+
return function(target, key) {
|
|
1951
|
+
decorator(target, key, paramIndex);
|
|
1952
|
+
};
|
|
1953
|
+
}
|
|
1954
|
+
__name(_ts_param14, "_ts_param");
|
|
1955
|
+
var DrizzlePlanRepository = class {
|
|
1956
|
+
static {
|
|
1957
|
+
__name(this, "DrizzlePlanRepository");
|
|
1958
|
+
}
|
|
1959
|
+
db;
|
|
1960
|
+
validityWindows;
|
|
1961
|
+
constructor(db, options = {}) {
|
|
1962
|
+
this.db = db;
|
|
1963
|
+
this.validityWindows = options.validityWindows ?? false;
|
|
1964
|
+
if (this.validityWindows) {
|
|
1965
|
+
this.findActivePlanVersion = (planKey, asOf, tx) => this.activeVersionAt(planKey, asOf ?? /* @__PURE__ */ new Date(), tx);
|
|
1966
|
+
}
|
|
1967
|
+
}
|
|
1968
|
+
findActivePlanVersion;
|
|
1969
|
+
// ─── Stem operations ───
|
|
1970
|
+
async list(filter) {
|
|
1971
|
+
const excludeDeleted = filter.excludeDeleted ?? true;
|
|
1972
|
+
const stems = await this.db.select().from(plans).where(and11(...excludeDeleted ? [
|
|
1973
|
+
isNull7(plans.deletedAt)
|
|
1974
|
+
] : [])).orderBy(asc3(plans.sortOrder), asc3(plans.planKey));
|
|
1975
|
+
if (!filter.onlyPublished) return stems.map(toPlanRow2);
|
|
1976
|
+
if (stems.length === 0) return [];
|
|
1977
|
+
const candidateKeys = [
|
|
1978
|
+
...new Set(stems.map((stem) => stem.planKey))
|
|
1979
|
+
];
|
|
1980
|
+
const live = await this.db.select({
|
|
1981
|
+
planId: planVersions.planId
|
|
1982
|
+
}).from(planVersions).where(and11(inArray3(planVersions.planId, candidateKeys), sql4`${planVersions.publishedAt} IS NOT NULL`, isNull7(planVersions.supersededAt)));
|
|
1983
|
+
const publishedKeys = new Set(live.map((row) => row.planId));
|
|
1984
|
+
return stems.filter((stem) => publishedKeys.has(stem.planKey)).map(toPlanRow2);
|
|
1985
|
+
}
|
|
1986
|
+
async findById(planId) {
|
|
1987
|
+
const rows = await this.db.select().from(plans).where(eq11(plans.id, planId)).limit(1);
|
|
1988
|
+
return rows[0] ? toPlanRow2(rows[0]) : null;
|
|
1989
|
+
}
|
|
1990
|
+
/**
|
|
1991
|
+
* Whether this installation already uses this plan key — **including
|
|
1992
|
+
* retired plans**.
|
|
1993
|
+
*
|
|
1994
|
+
* `plans_planKey_key` is unconditional, so a soft delete does not free the
|
|
1995
|
+
* key, and the duplicate check in `createPlan` needs the database's answer
|
|
1996
|
+
* rather than the active catalogue's. `list` is that other lookup.
|
|
1997
|
+
*/
|
|
1998
|
+
async findByKey(planKey) {
|
|
1999
|
+
const rows = await this.db.select().from(plans).where(eq11(plans.planKey, planKey)).limit(1);
|
|
2000
|
+
return rows[0] ? toPlanRow2(rows[0]) : null;
|
|
2001
|
+
}
|
|
2002
|
+
async create(data) {
|
|
2003
|
+
const now = /* @__PURE__ */ new Date();
|
|
2004
|
+
const rows = await this.db.insert(plans).values({
|
|
2005
|
+
id: randomUUID9(),
|
|
2006
|
+
planKey: data.planKey,
|
|
2007
|
+
label: data.label,
|
|
2008
|
+
description: data.description ?? null,
|
|
2009
|
+
icon: data.icon ?? null,
|
|
2010
|
+
sortOrder: data.sortOrder ?? 0,
|
|
2011
|
+
createdAt: now,
|
|
2012
|
+
updatedAt: now
|
|
2013
|
+
}).returning();
|
|
2014
|
+
return toPlanRow2(rows[0]);
|
|
2015
|
+
}
|
|
2016
|
+
async update(planId, data) {
|
|
2017
|
+
const rows = await this.db.update(plans).set({
|
|
2018
|
+
...data.label !== void 0 ? {
|
|
2019
|
+
label: data.label
|
|
2020
|
+
} : {},
|
|
2021
|
+
...data.description !== void 0 ? {
|
|
2022
|
+
description: data.description
|
|
2023
|
+
} : {},
|
|
2024
|
+
...data.icon !== void 0 ? {
|
|
2025
|
+
icon: data.icon
|
|
2026
|
+
} : {},
|
|
2027
|
+
...data.sortOrder !== void 0 ? {
|
|
2028
|
+
sortOrder: data.sortOrder
|
|
2029
|
+
} : {},
|
|
2030
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2031
|
+
}).where(eq11(plans.id, planId)).returning();
|
|
2032
|
+
if (!rows[0]) throw new Error(`Plan '${planId}' not found.`);
|
|
2033
|
+
return toPlanRow2(rows[0]);
|
|
2034
|
+
}
|
|
2035
|
+
async softDelete(planId) {
|
|
2036
|
+
const now = /* @__PURE__ */ new Date();
|
|
2037
|
+
await this.db.update(plans).set({
|
|
2038
|
+
deletedAt: now,
|
|
2039
|
+
updatedAt: now
|
|
2040
|
+
}).where(eq11(plans.id, planId));
|
|
2041
|
+
}
|
|
2042
|
+
async hardDelete(planId) {
|
|
2043
|
+
await this.db.delete(plans).where(eq11(plans.id, planId));
|
|
2044
|
+
}
|
|
2045
|
+
// ─── Version lifecycle — keyed by plan key ───
|
|
2046
|
+
async listVersions(planKey) {
|
|
2047
|
+
const rows = await this.db.select().from(planVersions).where(eq11(planVersions.planId, planKey)).orderBy(asc3(planVersions.version));
|
|
2048
|
+
return rows.map((row) => this.versionRow(row));
|
|
2049
|
+
}
|
|
2050
|
+
/**
|
|
2051
|
+
* `tx` is beyond what the port asks for, and is what keeps a caller already
|
|
2052
|
+
* inside a transaction on its own connection: drawing a second one waits
|
|
2053
|
+
* for a connection that transaction is itself holding, which on a
|
|
2054
|
+
* one-connection pool never returns.
|
|
2055
|
+
*/
|
|
2056
|
+
async findVersionById(versionId, tx) {
|
|
2057
|
+
const rows = await resolveDb(this.db, tx).select().from(planVersions).where(eq11(planVersions.id, versionId)).limit(1);
|
|
2058
|
+
return rows[0] ? this.versionRow(rows[0]) : null;
|
|
2059
|
+
}
|
|
2060
|
+
async findCurrentDraft(planKey) {
|
|
2061
|
+
const rows = await this.db.select().from(planVersions).where(and11(eq11(planVersions.planId, planKey), isNull7(planVersions.publishedAt))).orderBy(desc6(planVersions.version)).limit(1);
|
|
2062
|
+
return rows[0] ? this.versionRow(rows[0]) : null;
|
|
2063
|
+
}
|
|
2064
|
+
async findLatestLivePlanVersion(planKey, tx) {
|
|
2065
|
+
const rows = await resolveDb(this.db, tx).select().from(planVersions).where(and11(
|
|
2066
|
+
eq11(planVersions.planId, planKey),
|
|
2067
|
+
sql4`${planVersions.publishedAt} IS NOT NULL`,
|
|
2068
|
+
isNull7(planVersions.supersededAt),
|
|
2069
|
+
// A version the operator terminated is not live, whatever
|
|
2070
|
+
// its supersession says: `terminate` sets `endsAt` without
|
|
2071
|
+
// a successor to supersede it.
|
|
2072
|
+
or3(isNull7(planVersions.endsAt), sql4`${planVersions.endsAt} > NOW()`)
|
|
2073
|
+
)).orderBy(desc6(planVersions.version)).limit(1);
|
|
2074
|
+
return rows[0] ? this.versionRow(rows[0]) : null;
|
|
2075
|
+
}
|
|
2076
|
+
/**
|
|
2077
|
+
* The version bookable at `asOf`: published, inside its validity window,
|
|
2078
|
+
* not terminated.
|
|
2079
|
+
*
|
|
2080
|
+
* `validUntil` is day-inclusive — a window that closes on the 28th is still
|
|
2081
|
+
* open at 23:59 on the 28th — which is why the comparison is against
|
|
2082
|
+
* `startOfUtcDay(asOf)` and not against `asOf`. The reading half of the
|
|
2083
|
+
* rule `previousUtcDay` writes in `publishWithin`; both come from
|
|
2084
|
+
* `@saasicat/core` so the two halves cannot drift apart.
|
|
2085
|
+
*/
|
|
2086
|
+
async activeVersionAt(planKey, asOf, tx) {
|
|
2087
|
+
const rows = await resolveDb(this.db, tx).select().from(planVersions).where(and11(eq11(planVersions.planId, planKey), sql4`${planVersions.publishedAt} IS NOT NULL`, or3(isNull7(planVersions.validFrom), lte4(planVersions.validFrom, asOf)), or3(isNull7(planVersions.validUntil), gte5(planVersions.validUntil, startOfUtcDay(asOf))), or3(isNull7(planVersions.endsAt), sql4`${planVersions.endsAt} > ${asOf}`))).orderBy(sql4`"validFrom" DESC NULLS LAST`, desc6(planVersions.version)).limit(1);
|
|
2088
|
+
return rows[0] ? this.versionRow(rows[0]) : null;
|
|
2089
|
+
}
|
|
2090
|
+
async createPlanVersionDraft(data) {
|
|
2091
|
+
const now = /* @__PURE__ */ new Date();
|
|
2092
|
+
const latest = await this.db.select({
|
|
2093
|
+
version: planVersions.version
|
|
2094
|
+
}).from(planVersions).where(eq11(planVersions.planId, data.planId)).orderBy(desc6(planVersions.version)).limit(1);
|
|
2095
|
+
const rows = await this.db.insert(planVersions).values({
|
|
2096
|
+
id: randomUUID9(),
|
|
2097
|
+
planId: data.planId,
|
|
2098
|
+
version: (latest[0]?.version ?? 0) + 1,
|
|
2099
|
+
baseVersionId: data.baseVersionId ?? null,
|
|
2100
|
+
features: data.features,
|
|
2101
|
+
quotas: data.quotas,
|
|
2102
|
+
monthlyNet: data.monthlyNet,
|
|
2103
|
+
yearlyNet: data.yearlyNet,
|
|
2104
|
+
marketed: data.marketed ?? true,
|
|
2105
|
+
changeNote: data.changeNote ?? "",
|
|
2106
|
+
createdByUserId: data.createdByUserId ?? null,
|
|
2107
|
+
createdAt: now,
|
|
2108
|
+
updatedAt: now,
|
|
2109
|
+
// publishedAt stays null — this is a draft. `bundles` is
|
|
2110
|
+
// app-specific and has no column here, as in adapter-prisma.
|
|
2111
|
+
...this.validityWindows ? {
|
|
2112
|
+
validFrom: toNullableDate2(data.validFrom),
|
|
2113
|
+
validUntil: toNullableDate2(data.validUntil)
|
|
2114
|
+
} : {}
|
|
2115
|
+
}).returning();
|
|
2116
|
+
return this.versionRow(rows[0]);
|
|
2117
|
+
}
|
|
2118
|
+
async updatePlanVersionDraft(versionId, data) {
|
|
2119
|
+
const rows = await this.db.update(planVersions).set({
|
|
2120
|
+
...data.features !== void 0 ? {
|
|
2121
|
+
features: data.features
|
|
2122
|
+
} : {},
|
|
2123
|
+
...data.quotas !== void 0 ? {
|
|
2124
|
+
quotas: data.quotas
|
|
2125
|
+
} : {},
|
|
2126
|
+
...data.monthlyNet !== void 0 ? {
|
|
2127
|
+
monthlyNet: data.monthlyNet
|
|
2128
|
+
} : {},
|
|
2129
|
+
...data.yearlyNet !== void 0 ? {
|
|
2130
|
+
yearlyNet: data.yearlyNet
|
|
2131
|
+
} : {},
|
|
2132
|
+
...data.marketed !== void 0 ? {
|
|
2133
|
+
marketed: data.marketed
|
|
2134
|
+
} : {},
|
|
2135
|
+
...data.changeNote !== void 0 ? {
|
|
2136
|
+
changeNote: data.changeNote
|
|
2137
|
+
} : {},
|
|
2138
|
+
...this.validityWindows && data.validFrom !== void 0 ? {
|
|
2139
|
+
validFrom: toNullableDate2(data.validFrom)
|
|
2140
|
+
} : {},
|
|
2141
|
+
...this.validityWindows && data.validUntil !== void 0 ? {
|
|
2142
|
+
validUntil: toNullableDate2(data.validUntil)
|
|
2143
|
+
} : {},
|
|
2144
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2145
|
+
}).where(eq11(planVersions.id, versionId)).returning();
|
|
2146
|
+
if (!rows[0]) {
|
|
2147
|
+
throw new Error(`PlanVersion '${versionId}' not found.`);
|
|
2148
|
+
}
|
|
2149
|
+
return this.versionRow(rows[0]);
|
|
2150
|
+
}
|
|
2151
|
+
async publishPlanVersionDraft(versionId, publishMeta, tx) {
|
|
2152
|
+
if (tx !== void 0) return this.publishWithin(resolveDb(this.db, tx), versionId, publishMeta);
|
|
2153
|
+
return this.db.transaction((transaction) => this.publishWithin(transaction, versionId, publishMeta));
|
|
2154
|
+
}
|
|
2155
|
+
async publishWithin(db, versionId, publishMeta) {
|
|
2156
|
+
const now = /* @__PURE__ */ new Date();
|
|
2157
|
+
const publishedRows = await db.update(planVersions).set({
|
|
2158
|
+
publishedAt: now,
|
|
2159
|
+
publishedByUserId: publishMeta.publishedByUserId,
|
|
2160
|
+
publishedChanges: publishMeta.publishedChanges,
|
|
2161
|
+
nonRegressive: publishMeta.nonRegressive,
|
|
2162
|
+
updatedAt: now,
|
|
2163
|
+
...this.validityWindows ? {
|
|
2164
|
+
validFrom: publishMeta.validFrom,
|
|
2165
|
+
validUntil: publishMeta.validUntil
|
|
2166
|
+
} : {}
|
|
2167
|
+
}).where(and11(eq11(planVersions.id, versionId), isNull7(planVersions.publishedAt))).returning();
|
|
2168
|
+
if (!publishedRows[0]) {
|
|
2169
|
+
throw new Error(`PlanVersion '${versionId}' not found or already published.`);
|
|
2170
|
+
}
|
|
2171
|
+
await db.update(planVersions).set(this.validityWindows ? {
|
|
2172
|
+
supersededAt: now,
|
|
2173
|
+
validUntil: previousUtcDay2(publishMeta.validFrom),
|
|
2174
|
+
updatedAt: now
|
|
2175
|
+
} : {
|
|
2176
|
+
supersededAt: now,
|
|
2177
|
+
updatedAt: now
|
|
2178
|
+
}).where(and11(eq11(planVersions.planId, publishedRows[0].planId), sql4`${planVersions.publishedAt} IS NOT NULL`, isNull7(planVersions.supersededAt), ne2(planVersions.id, versionId)));
|
|
2179
|
+
return this.versionRow(publishedRows[0]);
|
|
2180
|
+
}
|
|
2181
|
+
async deletePlanVersionDraft(versionId) {
|
|
2182
|
+
const deleted = await this.db.delete(planVersions).where(and11(eq11(planVersions.id, versionId), isNull7(planVersions.publishedAt))).returning({
|
|
2183
|
+
id: planVersions.id
|
|
2184
|
+
});
|
|
2185
|
+
if (deleted.length > 0) return;
|
|
2186
|
+
const existing = await this.db.select({
|
|
2187
|
+
publishedAt: planVersions.publishedAt
|
|
2188
|
+
}).from(planVersions).where(eq11(planVersions.id, versionId)).limit(1);
|
|
2189
|
+
if (existing[0]) {
|
|
2190
|
+
throw new Error(`PlanVersion '${versionId}' is already published and cannot be discarded (published versions are immutable \u2014 contract protection P1).`);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
async terminate(versionId, endsAt) {
|
|
2194
|
+
const rows = await this.db.update(planVersions).set({
|
|
2195
|
+
endsAt,
|
|
2196
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2197
|
+
}).where(eq11(planVersions.id, versionId)).returning();
|
|
2198
|
+
if (!rows[0]) throw new Error(`PlanVersion '${versionId}' not found.`);
|
|
2199
|
+
return this.versionRow(rows[0]);
|
|
2200
|
+
}
|
|
2201
|
+
versionRow(row) {
|
|
2202
|
+
return toPlanVersionRow2(row, row.planId, {
|
|
2203
|
+
validityWindows: this.validityWindows,
|
|
2204
|
+
// The canonical schema always has the column, so this adapter can
|
|
2205
|
+
// always answer the question — unlike a Prisma consumer whose
|
|
2206
|
+
// schema may predate it.
|
|
2207
|
+
endsAt: true
|
|
2208
|
+
});
|
|
2209
|
+
}
|
|
2210
|
+
};
|
|
2211
|
+
DrizzlePlanRepository = _ts_decorate15([
|
|
2212
|
+
Injectable15(),
|
|
2213
|
+
_ts_param14(0, Inject14(DRIZZLE_DB_TOKEN)),
|
|
2214
|
+
_ts_param14(1, Optional2()),
|
|
2215
|
+
_ts_metadata14("design:type", Function),
|
|
2216
|
+
_ts_metadata14("design:paramtypes", [
|
|
2217
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient,
|
|
2218
|
+
typeof DrizzlePlanRepositoryOptions === "undefined" ? Object : DrizzlePlanRepositoryOptions
|
|
2219
|
+
])
|
|
2220
|
+
], DrizzlePlanRepository);
|
|
2221
|
+
function toNullableDate2(value) {
|
|
2222
|
+
return value ? new Date(value) : null;
|
|
2223
|
+
}
|
|
2224
|
+
__name(toNullableDate2, "toNullableDate");
|
|
2225
|
+
|
|
2226
|
+
// src/drizzle-subscription-contract.repository.ts
|
|
2227
|
+
import { randomUUID as randomUUID10 } from "node:crypto";
|
|
2228
|
+
import { Inject as Inject15, Injectable as Injectable16 } from "@nestjs/common";
|
|
2229
|
+
import { and as and12, asc as asc4, count as count2, desc as desc7, eq as eq12, gt as gt2, inArray as inArray4, isNull as isNull8, lte as lte5, or as or4 } from "drizzle-orm";
|
|
2230
|
+
import { ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES, toRunningContractIssuer, toSubscriptionContractRecord } from "@saasicat/core";
|
|
2231
|
+
function _ts_decorate16(decorators, target, key, desc10) {
|
|
2232
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
2233
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
2234
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2235
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2236
|
+
}
|
|
2237
|
+
__name(_ts_decorate16, "_ts_decorate");
|
|
2238
|
+
function _ts_metadata15(k, v) {
|
|
2239
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2240
|
+
}
|
|
2241
|
+
__name(_ts_metadata15, "_ts_metadata");
|
|
2242
|
+
function _ts_param15(paramIndex, decorator) {
|
|
2243
|
+
return function(target, key) {
|
|
2244
|
+
decorator(target, key, paramIndex);
|
|
2245
|
+
};
|
|
2246
|
+
}
|
|
2247
|
+
__name(_ts_param15, "_ts_param");
|
|
2248
|
+
var DrizzleSubscriptionContractRepository = class {
|
|
2249
|
+
static {
|
|
2250
|
+
__name(this, "DrizzleSubscriptionContractRepository");
|
|
2251
|
+
}
|
|
2252
|
+
db;
|
|
2253
|
+
constructor(db) {
|
|
2254
|
+
this.db = db;
|
|
2255
|
+
}
|
|
2256
|
+
async list(filter) {
|
|
2257
|
+
const rows = await this.db.select().from(subscriptionContracts).where(and12(...filter.tenantId ? [
|
|
2258
|
+
eq12(subscriptionContracts.tenantId, filter.tenantId)
|
|
2259
|
+
] : [], ...filter.status ? [
|
|
2260
|
+
eq12(subscriptionContracts.status, filter.status)
|
|
2261
|
+
] : [], ...filter.asOf ? [
|
|
2262
|
+
inWindowAt(filter.asOf)
|
|
2263
|
+
] : [])).orderBy(desc7(subscriptionContracts.effectiveFrom), desc7(subscriptionContracts.createdAt));
|
|
2264
|
+
return this.withLineItems(this.db, rows);
|
|
2265
|
+
}
|
|
2266
|
+
async findById(contractId) {
|
|
2267
|
+
const rows = await this.db.select().from(subscriptionContracts).where(eq12(subscriptionContracts.id, contractId)).limit(1);
|
|
2268
|
+
return (await this.withLineItems(this.db, rows))[0] ?? null;
|
|
2269
|
+
}
|
|
2270
|
+
async findActiveByTenantId(tenantId, asOf = /* @__PURE__ */ new Date(), tx) {
|
|
2271
|
+
const db = resolveDb(this.db, tx);
|
|
2272
|
+
const rows = await db.select().from(subscriptionContracts).where(and12(eq12(subscriptionContracts.tenantId, tenantId), inArray4(subscriptionContracts.status, [
|
|
2273
|
+
...ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES
|
|
2274
|
+
]), inWindowAt(asOf))).orderBy(desc7(subscriptionContracts.effectiveFrom), desc7(subscriptionContracts.createdAt)).limit(1);
|
|
2275
|
+
return (await this.withLineItems(db, rows))[0] ?? null;
|
|
2276
|
+
}
|
|
2277
|
+
async findByOriginalOfferId(offerId, tx) {
|
|
2278
|
+
const db = resolveDb(this.db, tx);
|
|
2279
|
+
const rows = await db.select().from(subscriptionContracts).where(eq12(subscriptionContracts.originalOfferId, offerId)).orderBy(asc4(subscriptionContracts.createdAt), asc4(subscriptionContracts.id)).limit(1);
|
|
2280
|
+
return (await this.withLineItems(db, rows))[0] ?? null;
|
|
2281
|
+
}
|
|
2282
|
+
async create(data, tx) {
|
|
2283
|
+
const now = /* @__PURE__ */ new Date();
|
|
2284
|
+
const contractId = randomUUID10();
|
|
2285
|
+
return resolveDb(this.db, tx).transaction(async (transaction) => {
|
|
2286
|
+
const db = transaction;
|
|
2287
|
+
const rows = await db.insert(subscriptionContracts).values({
|
|
2288
|
+
id: contractId,
|
|
2289
|
+
tenantId: data.tenantId,
|
|
2290
|
+
status: data.status ?? "active",
|
|
2291
|
+
effectiveFrom: data.effectiveFrom,
|
|
2292
|
+
effectiveUntil: data.effectiveUntil ?? null,
|
|
2293
|
+
originalOfferId: data.originalOfferId ?? null,
|
|
2294
|
+
originalPlanVersionId: data.originalPlanVersionId ?? null,
|
|
2295
|
+
originalBundleVersionIds: data.originalBundleVersionIds ?? [],
|
|
2296
|
+
entitlementSnapshot: data.entitlementSnapshot ?? null,
|
|
2297
|
+
priceSnapshot: data.priceSnapshot,
|
|
2298
|
+
promotionSnapshots: data.promotionSnapshots ?? [],
|
|
2299
|
+
promoCodeSnapshots: data.promoCodeSnapshots ?? [],
|
|
2300
|
+
termsSnapshot: data.termsSnapshot ?? null,
|
|
2301
|
+
subscriberId: data.parties.subscriberId,
|
|
2302
|
+
subscriberSnapshot: data.parties.subscriber,
|
|
2303
|
+
issuerSnapshot: data.parties.issuer,
|
|
2304
|
+
createdAt: now,
|
|
2305
|
+
updatedAt: now
|
|
2306
|
+
}).returning();
|
|
2307
|
+
const lineRows = data.lineItems.length === 0 ? [] : await db.insert(contractLineItems).values(data.lineItems.map((item) => toLineItemInsert(item, contractId, now))).returning();
|
|
2308
|
+
return toSubscriptionContractRecord(rows[0], lineRows);
|
|
2309
|
+
});
|
|
2310
|
+
}
|
|
2311
|
+
async listRunningIssuers(limit, asOf = /* @__PURE__ */ new Date()) {
|
|
2312
|
+
const running = and12(inArray4(subscriptionContracts.status, [
|
|
2313
|
+
...ACTIVE_SUBSCRIPTION_CONTRACT_STATUSES
|
|
2314
|
+
]), or4(isNull8(subscriptionContracts.effectiveUntil), gt2(subscriptionContracts.effectiveUntil, asOf)));
|
|
2315
|
+
const [totals, rows] = await Promise.all([
|
|
2316
|
+
this.db.select({
|
|
2317
|
+
value: count2()
|
|
2318
|
+
}).from(subscriptionContracts).where(running),
|
|
2319
|
+
// Oldest first, and `id` last so two contracts that start in the
|
|
2320
|
+
// same moment are still listed in one order — a refusal that names
|
|
2321
|
+
// a different pair on every restart is one an operator cannot act
|
|
2322
|
+
// on.
|
|
2323
|
+
this.db.select({
|
|
2324
|
+
id: subscriptionContracts.id,
|
|
2325
|
+
tenantId: subscriptionContracts.tenantId,
|
|
2326
|
+
issuerSnapshot: subscriptionContracts.issuerSnapshot,
|
|
2327
|
+
effectiveFrom: subscriptionContracts.effectiveFrom
|
|
2328
|
+
}).from(subscriptionContracts).where(running).orderBy(asc4(subscriptionContracts.effectiveFrom), asc4(subscriptionContracts.createdAt), asc4(subscriptionContracts.id)).limit(limit)
|
|
2329
|
+
]);
|
|
2330
|
+
return {
|
|
2331
|
+
total: totals[0]?.value ?? 0,
|
|
2332
|
+
contracts: rows.map(toRunningContractIssuer)
|
|
2333
|
+
};
|
|
2334
|
+
}
|
|
2335
|
+
async terminate(contractId, data) {
|
|
2336
|
+
const rows = await this.db.update(subscriptionContracts).set({
|
|
2337
|
+
effectiveUntil: data.effectiveUntil,
|
|
2338
|
+
// A null status leaves the column alone: the contract keeps
|
|
2339
|
+
// whatever it had — `active`, usually — and the new
|
|
2340
|
+
// `effectiveUntil` is what takes it out of the active lookup
|
|
2341
|
+
// once that moment arrives.
|
|
2342
|
+
...data.status === null ? {} : {
|
|
2343
|
+
status: data.status
|
|
2344
|
+
},
|
|
2345
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2346
|
+
}).where(eq12(subscriptionContracts.id, contractId)).returning();
|
|
2347
|
+
if (!rows[0]) throw new Error(`SubscriptionContract '${contractId}' not found.`);
|
|
2348
|
+
return (await this.withLineItems(this.db, rows))[0];
|
|
2349
|
+
}
|
|
2350
|
+
/**
|
|
2351
|
+
* The lines for a batch of contracts in one query rather than one per
|
|
2352
|
+
* contract: `list` is the operator's contract history and would otherwise
|
|
2353
|
+
* be an N+1 over a table that only grows.
|
|
2354
|
+
*/
|
|
2355
|
+
async withLineItems(db, rows) {
|
|
2356
|
+
if (rows.length === 0) return [];
|
|
2357
|
+
const lines = await db.select().from(contractLineItems).where(inArray4(contractLineItems.contractId, rows.map((row) => row.id)));
|
|
2358
|
+
const byContract = /* @__PURE__ */ new Map();
|
|
2359
|
+
for (const line of lines) {
|
|
2360
|
+
const bucket = byContract.get(line.contractId);
|
|
2361
|
+
if (bucket) bucket.push(line);
|
|
2362
|
+
else byContract.set(line.contractId, [
|
|
2363
|
+
line
|
|
2364
|
+
]);
|
|
2365
|
+
}
|
|
2366
|
+
return rows.map((row) => toSubscriptionContractRecord(row, byContract.get(row.id) ?? []));
|
|
2367
|
+
}
|
|
2368
|
+
};
|
|
2369
|
+
DrizzleSubscriptionContractRepository = _ts_decorate16([
|
|
2370
|
+
Injectable16(),
|
|
2371
|
+
_ts_param15(0, Inject15(DRIZZLE_DB_TOKEN)),
|
|
2372
|
+
_ts_metadata15("design:type", Function),
|
|
2373
|
+
_ts_metadata15("design:paramtypes", [
|
|
2374
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
2375
|
+
])
|
|
2376
|
+
], DrizzleSubscriptionContractRepository);
|
|
2377
|
+
function inWindowAt(asOf) {
|
|
2378
|
+
return and12(lte5(subscriptionContracts.effectiveFrom, asOf), or4(isNull8(subscriptionContracts.effectiveUntil), gt2(subscriptionContracts.effectiveUntil, asOf)));
|
|
2379
|
+
}
|
|
2380
|
+
__name(inWindowAt, "inWindowAt");
|
|
2381
|
+
function toLineItemInsert(item, contractId, createdAt) {
|
|
2382
|
+
return {
|
|
2383
|
+
id: randomUUID10(),
|
|
2384
|
+
contractId,
|
|
2385
|
+
kind: item.kind,
|
|
2386
|
+
sourceKey: item.sourceKey,
|
|
2387
|
+
sourceVersionId: item.sourceVersionId ?? null,
|
|
2388
|
+
titleSnapshot: item.titleSnapshot,
|
|
2389
|
+
descriptionSnapshot: item.descriptionSnapshot ?? null,
|
|
2390
|
+
quantity: item.quantity,
|
|
2391
|
+
unit: item.unit ?? null,
|
|
2392
|
+
// Money as a decimal string all the way to the column: passing a JS
|
|
2393
|
+
// number here would round it on the way in, before anybody could
|
|
2394
|
+
// notice.
|
|
2395
|
+
priceNet: item.priceNet.toFixed(2),
|
|
2396
|
+
priceGross: item.priceGross.toFixed(2),
|
|
2397
|
+
billingCycle: item.billingCycle,
|
|
2398
|
+
currency: item.currency,
|
|
2399
|
+
taxRate: item.taxRate.toFixed(2),
|
|
2400
|
+
taxAmount: item.taxAmount.toFixed(2),
|
|
2401
|
+
minimumTermUntil: item.minimumTermUntil ?? null,
|
|
2402
|
+
featuresSnapshot: item.featuresSnapshot,
|
|
2403
|
+
quotaEffectsSnapshot: item.quotaEffectsSnapshot,
|
|
2404
|
+
metadata: item.metadata ?? null,
|
|
2405
|
+
createdAt
|
|
2406
|
+
};
|
|
2407
|
+
}
|
|
2408
|
+
__name(toLineItemInsert, "toLineItemInsert");
|
|
2409
|
+
|
|
2410
|
+
// src/drizzle-subscriber-payment-method.repository.ts
|
|
2411
|
+
import { randomUUID as randomUUID11 } from "node:crypto";
|
|
2412
|
+
import { Inject as Inject16, Injectable as Injectable17 } from "@nestjs/common";
|
|
2413
|
+
import { and as and13, asc as asc5, eq as eq13, isNull as isNull9 } from "drizzle-orm";
|
|
2414
|
+
import { ForeignPaymentMethodReferenceError, refuseForeignPaymentMethodReference, subscriberPaymentMethodColumns, toSubscriberPaymentMethodRecord } from "@saasicat/core";
|
|
2415
|
+
function _ts_decorate17(decorators, target, key, desc10) {
|
|
2416
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
2417
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
2418
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2419
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2420
|
+
}
|
|
2421
|
+
__name(_ts_decorate17, "_ts_decorate");
|
|
2422
|
+
function _ts_metadata16(k, v) {
|
|
2423
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2424
|
+
}
|
|
2425
|
+
__name(_ts_metadata16, "_ts_metadata");
|
|
2426
|
+
function _ts_param16(paramIndex, decorator) {
|
|
2427
|
+
return function(target, key) {
|
|
2428
|
+
decorator(target, key, paramIndex);
|
|
2429
|
+
};
|
|
2430
|
+
}
|
|
2431
|
+
__name(_ts_param16, "_ts_param");
|
|
2432
|
+
var DrizzleSubscriberPaymentMethodRepository = class {
|
|
2433
|
+
static {
|
|
2434
|
+
__name(this, "DrizzleSubscriberPaymentMethodRepository");
|
|
2435
|
+
}
|
|
2436
|
+
db;
|
|
2437
|
+
constructor(db) {
|
|
2438
|
+
this.db = db;
|
|
2439
|
+
}
|
|
2440
|
+
async recordConfirmed(data, tx) {
|
|
2441
|
+
return resolveDb(this.db, tx).transaction(async (transaction) => {
|
|
2442
|
+
const db = transaction;
|
|
2443
|
+
const locked = await db.select({
|
|
2444
|
+
id: subscribers.id
|
|
2445
|
+
}).from(subscribers).where(eq13(subscribers.id, data.subscriberId)).for("update").limit(1);
|
|
2446
|
+
if (locked.length === 0) {
|
|
2447
|
+
throw new Error(`Subscriber '${data.subscriberId}' does not exist; its payment method cannot be recorded.`);
|
|
2448
|
+
}
|
|
2449
|
+
const [recorded] = await db.select().from(subscriberPaymentMethods).where(this.byReference(data.gatewayAccount, data.paymentMethodRef)).limit(1);
|
|
2450
|
+
if (recorded) {
|
|
2451
|
+
refuseForeignPaymentMethodReference(recorded, data.subscriberId);
|
|
2452
|
+
return {
|
|
2453
|
+
method: toSubscriberPaymentMethodRecord(recorded),
|
|
2454
|
+
outcome: "already-recorded"
|
|
2455
|
+
};
|
|
2456
|
+
}
|
|
2457
|
+
const [active] = await db.select().from(subscriberPaymentMethods).where(this.activeOf(data.subscriberId)).limit(1);
|
|
2458
|
+
const replacedOnArrival = active && active.confirmedAt.getTime() > data.confirmedAt.getTime() ? active.confirmedAt : null;
|
|
2459
|
+
const claimed = await this.claimReference(db, {
|
|
2460
|
+
...subscriberPaymentMethodColumns(data),
|
|
2461
|
+
id: randomUUID11(),
|
|
2462
|
+
status: "REPLACED",
|
|
2463
|
+
replacedAt: replacedOnArrival ?? data.confirmedAt
|
|
2464
|
+
});
|
|
2465
|
+
if (replacedOnArrival) {
|
|
2466
|
+
return {
|
|
2467
|
+
method: toSubscriberPaymentMethodRecord(claimed),
|
|
2468
|
+
outcome: "superseded"
|
|
2469
|
+
};
|
|
2470
|
+
}
|
|
2471
|
+
if (active) {
|
|
2472
|
+
await db.update(subscriberPaymentMethods).set({
|
|
2473
|
+
status: "REPLACED",
|
|
2474
|
+
replacedAt: data.confirmedAt
|
|
2475
|
+
}).where(eq13(subscriberPaymentMethods.id, active.id));
|
|
2476
|
+
}
|
|
2477
|
+
const [inUse] = await db.update(subscriberPaymentMethods).set({
|
|
2478
|
+
status: "ACTIVE",
|
|
2479
|
+
replacedAt: null
|
|
2480
|
+
}).where(eq13(subscriberPaymentMethods.id, claimed.id)).returning();
|
|
2481
|
+
return {
|
|
2482
|
+
method: toSubscriberPaymentMethodRecord(inUse),
|
|
2483
|
+
outcome: "activated"
|
|
2484
|
+
};
|
|
2485
|
+
});
|
|
2486
|
+
}
|
|
2487
|
+
/**
|
|
2488
|
+
* Takes the reference for this subscriber, or refuses it to them.
|
|
2489
|
+
*
|
|
2490
|
+
* The first write of the three, and the only one that can be refused — so a
|
|
2491
|
+
* refusal leaves the caller's transaction as it found it, whichever way the
|
|
2492
|
+
* caller opened it. `onConflictDoNothing` on the reference's own columns is
|
|
2493
|
+
* what answers: the row the read above could not see, because the
|
|
2494
|
+
* transaction holding it had not committed, is waited for here rather than
|
|
2495
|
+
* raised as a driver error, which every driver spells differently.
|
|
2496
|
+
*
|
|
2497
|
+
* It is written `REPLACED` because the seat a subscriber's one `ACTIVE`
|
|
2498
|
+
* payment method occupies may still be taken; `recordConfirmed` gives it
|
|
2499
|
+
* the status it keeps once the seat is free. Nothing outside the
|
|
2500
|
+
* transaction sees the difference.
|
|
2501
|
+
*/
|
|
2502
|
+
async claimReference(db, values) {
|
|
2503
|
+
const [claimed] = await db.insert(subscriberPaymentMethods).values(values).onConflictDoNothing({
|
|
2504
|
+
target: [
|
|
2505
|
+
subscriberPaymentMethods.gatewayAccount,
|
|
2506
|
+
subscriberPaymentMethods.paymentMethodRef
|
|
2507
|
+
]
|
|
2508
|
+
}).returning();
|
|
2509
|
+
if (!claimed) {
|
|
2510
|
+
throw new ForeignPaymentMethodReferenceError(values.gatewayAccount, values.paymentMethodRef);
|
|
2511
|
+
}
|
|
2512
|
+
return claimed;
|
|
2513
|
+
}
|
|
2514
|
+
async findActive(subscriberId, tx) {
|
|
2515
|
+
const [row] = await resolveDb(this.db, tx).select().from(subscriberPaymentMethods).where(this.activeOf(subscriberId)).limit(1);
|
|
2516
|
+
return row ? toSubscriberPaymentMethodRecord(row) : null;
|
|
2517
|
+
}
|
|
2518
|
+
async findByReference(reference, tx) {
|
|
2519
|
+
const [row] = await resolveDb(this.db, tx).select().from(subscriberPaymentMethods).where(and13(eq13(subscriberPaymentMethods.subscriberId, reference.subscriberId), this.byReference(reference.gatewayAccount, reference.paymentMethodRef))).limit(1);
|
|
2520
|
+
return row ? toSubscriberPaymentMethodRecord(row) : null;
|
|
2521
|
+
}
|
|
2522
|
+
async accountsInUse() {
|
|
2523
|
+
const rows = await this.db.selectDistinct({
|
|
2524
|
+
gatewayAccount: subscriberPaymentMethods.gatewayAccount
|
|
2525
|
+
}).from(subscriberPaymentMethods).where(eq13(subscriberPaymentMethods.status, "ACTIVE")).orderBy(asc5(subscriberPaymentMethods.gatewayAccount));
|
|
2526
|
+
return rows.map((row) => row.gatewayAccount);
|
|
2527
|
+
}
|
|
2528
|
+
async recordSetup(data, tx) {
|
|
2529
|
+
await resolveDb(this.db, tx).insert(subscriberPaymentMethodSetups).values({
|
|
2530
|
+
id: randomUUID11(),
|
|
2531
|
+
subscriberId: data.subscriberId,
|
|
2532
|
+
gatewayAccount: data.gatewayAccount,
|
|
2533
|
+
sessionRef: data.sessionRef,
|
|
2534
|
+
customerRef: data.customerRef,
|
|
2535
|
+
startedAt: data.startedAt
|
|
2536
|
+
});
|
|
2537
|
+
}
|
|
2538
|
+
async completeSetup(match, completedAt, tx) {
|
|
2539
|
+
const completed = await resolveDb(this.db, tx).update(subscriberPaymentMethodSetups).set({
|
|
2540
|
+
completedAt
|
|
2541
|
+
}).where(and13(eq13(subscriberPaymentMethodSetups.gatewayAccount, match.gatewayAccount), eq13(subscriberPaymentMethodSetups.sessionRef, match.sessionRef), eq13(subscriberPaymentMethodSetups.subscriberId, match.subscriberId), isNull9(subscriberPaymentMethodSetups.completedAt))).returning({
|
|
2542
|
+
id: subscriberPaymentMethodSetups.id
|
|
2543
|
+
});
|
|
2544
|
+
return completed.length === 1;
|
|
2545
|
+
}
|
|
2546
|
+
activeOf(subscriberId) {
|
|
2547
|
+
return and13(eq13(subscriberPaymentMethods.subscriberId, subscriberId), eq13(subscriberPaymentMethods.status, "ACTIVE"));
|
|
2548
|
+
}
|
|
2549
|
+
byReference(gatewayAccount, paymentMethodRef) {
|
|
2550
|
+
return and13(eq13(subscriberPaymentMethods.gatewayAccount, gatewayAccount), eq13(subscriberPaymentMethods.paymentMethodRef, paymentMethodRef));
|
|
2551
|
+
}
|
|
2552
|
+
};
|
|
2553
|
+
DrizzleSubscriberPaymentMethodRepository = _ts_decorate17([
|
|
2554
|
+
Injectable17(),
|
|
2555
|
+
_ts_param16(0, Inject16(DRIZZLE_DB_TOKEN)),
|
|
2556
|
+
_ts_metadata16("design:type", Function),
|
|
2557
|
+
_ts_metadata16("design:paramtypes", [
|
|
2558
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
2559
|
+
])
|
|
2560
|
+
], DrizzleSubscriberPaymentMethodRepository);
|
|
2561
|
+
|
|
2562
|
+
// src/drizzle-subscriber.repository.ts
|
|
2563
|
+
import { randomUUID as randomUUID12 } from "node:crypto";
|
|
2564
|
+
import { Inject as Inject17, Injectable as Injectable18 } from "@nestjs/common";
|
|
2565
|
+
import { and as and14, desc as desc8, eq as eq14, isNull as isNull10 } from "drizzle-orm";
|
|
2566
|
+
import { identityCorrectionDelta, toSubscriberCorrectionRecord, toSubscriberRecord } from "@saasicat/core";
|
|
2567
|
+
function _ts_decorate18(decorators, target, key, desc10) {
|
|
2568
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
2569
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
2570
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2571
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2572
|
+
}
|
|
2573
|
+
__name(_ts_decorate18, "_ts_decorate");
|
|
2574
|
+
function _ts_metadata17(k, v) {
|
|
2575
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2576
|
+
}
|
|
2577
|
+
__name(_ts_metadata17, "_ts_metadata");
|
|
2578
|
+
function _ts_param17(paramIndex, decorator) {
|
|
2579
|
+
return function(target, key) {
|
|
2580
|
+
decorator(target, key, paramIndex);
|
|
2581
|
+
};
|
|
2582
|
+
}
|
|
2583
|
+
__name(_ts_param17, "_ts_param");
|
|
2584
|
+
var DrizzleSubscriberRepository = class {
|
|
2585
|
+
static {
|
|
2586
|
+
__name(this, "DrizzleSubscriberRepository");
|
|
2587
|
+
}
|
|
2588
|
+
db;
|
|
2589
|
+
constructor(db) {
|
|
2590
|
+
this.db = db;
|
|
2591
|
+
}
|
|
2592
|
+
async createForTenant(data, tx) {
|
|
2593
|
+
const { tenantId, ...details } = data;
|
|
2594
|
+
const now = /* @__PURE__ */ new Date();
|
|
2595
|
+
const subscriberId = randomUUID12();
|
|
2596
|
+
return resolveDb(this.db, tx).transaction(async (transaction) => {
|
|
2597
|
+
const db = transaction;
|
|
2598
|
+
const [created] = await db.insert(subscribers).values({
|
|
2599
|
+
...details,
|
|
2600
|
+
id: subscriberId,
|
|
2601
|
+
createdAt: now,
|
|
2602
|
+
updatedAt: now
|
|
2603
|
+
}).returning();
|
|
2604
|
+
const linked = await db.insert(subscriberTenants).values({
|
|
2605
|
+
id: randomUUID12(),
|
|
2606
|
+
subscriberId,
|
|
2607
|
+
tenantId,
|
|
2608
|
+
linkedAt: now
|
|
2609
|
+
}).onConflictDoNothing().returning();
|
|
2610
|
+
if (linked.length === 0) {
|
|
2611
|
+
await db.delete(subscribers).where(eq14(subscribers.id, subscriberId));
|
|
2612
|
+
return null;
|
|
2613
|
+
}
|
|
2614
|
+
return toSubscriberRecord(created, tenantId);
|
|
2615
|
+
});
|
|
2616
|
+
}
|
|
2617
|
+
async findById(subscriberId, tx) {
|
|
2618
|
+
const db = resolveDb(this.db, tx);
|
|
2619
|
+
const rows = await db.select().from(subscribers).where(eq14(subscribers.id, subscriberId)).limit(1);
|
|
2620
|
+
return rows[0] ? this.withLiveTenant(db, rows[0]) : null;
|
|
2621
|
+
}
|
|
2622
|
+
async findByTenantId(tenantId, tx) {
|
|
2623
|
+
const db = resolveDb(this.db, tx);
|
|
2624
|
+
const rows = await db.select({
|
|
2625
|
+
subscriber: subscribers
|
|
2626
|
+
}).from(subscriberTenants).innerJoin(subscribers, eq14(subscribers.id, subscriberTenants.subscriberId)).where(and14(eq14(subscriberTenants.tenantId, tenantId), isNull10(subscriberTenants.unlinkedAt))).limit(1);
|
|
2627
|
+
return rows[0] ? toSubscriberRecord(rows[0].subscriber, tenantId) : null;
|
|
2628
|
+
}
|
|
2629
|
+
async updateContact(subscriberId, change, tx) {
|
|
2630
|
+
const db = resolveDb(this.db, tx);
|
|
2631
|
+
const rows = await db.update(subscribers).set({
|
|
2632
|
+
...change,
|
|
2633
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2634
|
+
}).where(eq14(subscribers.id, subscriberId)).returning();
|
|
2635
|
+
return rows[0] ? this.withLiveTenant(db, rows[0]) : null;
|
|
2636
|
+
}
|
|
2637
|
+
async correctIdentity(subscriberId, data, tx) {
|
|
2638
|
+
return resolveDb(this.db, tx).transaction(async (transaction) => {
|
|
2639
|
+
const db = transaction;
|
|
2640
|
+
const [current] = await db.select().from(subscribers).where(eq14(subscribers.id, subscriberId)).for("update").limit(1);
|
|
2641
|
+
if (!current) return null;
|
|
2642
|
+
const delta = identityCorrectionDelta(current, data.corrected);
|
|
2643
|
+
if (Object.keys(delta.corrected).length === 0) {
|
|
2644
|
+
return {
|
|
2645
|
+
subscriber: await this.withLiveTenant(db, current),
|
|
2646
|
+
correction: null
|
|
2647
|
+
};
|
|
2648
|
+
}
|
|
2649
|
+
const [updated] = await db.update(subscribers).set({
|
|
2650
|
+
...delta.corrected,
|
|
2651
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2652
|
+
}).where(eq14(subscribers.id, subscriberId)).returning();
|
|
2653
|
+
const [correction] = await db.insert(subscriberCorrections).values({
|
|
2654
|
+
id: randomUUID12(),
|
|
2655
|
+
subscriberId,
|
|
2656
|
+
previous: delta.previous,
|
|
2657
|
+
corrected: delta.corrected,
|
|
2658
|
+
reason: data.reason,
|
|
2659
|
+
correctedBy: data.correctedBy,
|
|
2660
|
+
correctedAt: data.correctedAt
|
|
2661
|
+
}).returning();
|
|
2662
|
+
return {
|
|
2663
|
+
subscriber: await this.withLiveTenant(db, updated),
|
|
2664
|
+
correction: toSubscriberCorrectionRecord(correction)
|
|
2665
|
+
};
|
|
2666
|
+
});
|
|
2667
|
+
}
|
|
2668
|
+
async listCorrections(subscriberId) {
|
|
2669
|
+
const rows = await this.db.select().from(subscriberCorrections).where(eq14(subscriberCorrections.subscriberId, subscriberId)).orderBy(desc8(subscriberCorrections.correctedAt), desc8(subscriberCorrections.id));
|
|
2670
|
+
return rows.map(toSubscriberCorrectionRecord);
|
|
2671
|
+
}
|
|
2672
|
+
async withLiveTenant(db, row) {
|
|
2673
|
+
const links = await db.select({
|
|
2674
|
+
tenantId: subscriberTenants.tenantId
|
|
2675
|
+
}).from(subscriberTenants).where(and14(eq14(subscriberTenants.subscriberId, row.id), isNull10(subscriberTenants.unlinkedAt))).limit(1);
|
|
2676
|
+
return toSubscriberRecord(row, links[0]?.tenantId ?? null);
|
|
2677
|
+
}
|
|
2678
|
+
};
|
|
2679
|
+
DrizzleSubscriberRepository = _ts_decorate18([
|
|
2680
|
+
Injectable18(),
|
|
2681
|
+
_ts_param17(0, Inject17(DRIZZLE_DB_TOKEN)),
|
|
2682
|
+
_ts_metadata17("design:type", Function),
|
|
2683
|
+
_ts_metadata17("design:paramtypes", [
|
|
2684
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
2685
|
+
])
|
|
2686
|
+
], DrizzleSubscriberRepository);
|
|
2687
|
+
|
|
2688
|
+
// src/drizzle-subscription-usage.adapter.ts
|
|
2689
|
+
import { Inject as Inject18, Injectable as Injectable19 } from "@nestjs/common";
|
|
2690
|
+
import { eq as eq15 } from "drizzle-orm";
|
|
2691
|
+
function _ts_decorate19(decorators, target, key, desc10) {
|
|
2692
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
2693
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
2694
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2695
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2696
|
+
}
|
|
2697
|
+
__name(_ts_decorate19, "_ts_decorate");
|
|
2698
|
+
function _ts_metadata18(k, v) {
|
|
2699
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2700
|
+
}
|
|
2701
|
+
__name(_ts_metadata18, "_ts_metadata");
|
|
2702
|
+
function _ts_param18(paramIndex, decorator) {
|
|
2703
|
+
return function(target, key) {
|
|
2704
|
+
decorator(target, key, paramIndex);
|
|
2705
|
+
};
|
|
2706
|
+
}
|
|
2707
|
+
__name(_ts_param18, "_ts_param");
|
|
2708
|
+
var DrizzleSubscriptionUsageAdapter = class {
|
|
2709
|
+
static {
|
|
2710
|
+
__name(this, "DrizzleSubscriptionUsageAdapter");
|
|
2711
|
+
}
|
|
2712
|
+
db;
|
|
2713
|
+
constructor(db) {
|
|
2714
|
+
this.db = db;
|
|
2715
|
+
}
|
|
2716
|
+
async findForTenant(tenantId) {
|
|
2717
|
+
const [subscription] = await this.db.select().from(subscriptions).where(eq15(subscriptions.tenantId, tenantId)).limit(1);
|
|
2718
|
+
if (!subscription) return null;
|
|
2719
|
+
const [planVersion] = await this.db.select().from(planVersions).where(eq15(planVersions.id, subscription.planVersionId)).limit(1);
|
|
2720
|
+
if (!planVersion) {
|
|
2721
|
+
throw new Error(`Subscription ${subscription.id} references missing PlanVersion ${subscription.planVersionId}.`);
|
|
2722
|
+
}
|
|
2723
|
+
const [pendingPlanVersion] = subscription.pendingPlanVersionId ? await this.db.select().from(planVersions).where(eq15(planVersions.id, subscription.pendingPlanVersionId)).limit(1) : [];
|
|
2724
|
+
return {
|
|
2725
|
+
id: subscription.id,
|
|
2726
|
+
plan: planVersion.planId,
|
|
2727
|
+
billingCycle: subscription.billingCycle,
|
|
2728
|
+
status: subscription.status,
|
|
2729
|
+
isPilot: subscription.isPilot,
|
|
2730
|
+
pilotEndsAt: subscription.pilotEndsAt,
|
|
2731
|
+
trialEndsAt: subscription.trialEndsAt,
|
|
2732
|
+
startedAt: subscription.startedAt,
|
|
2733
|
+
currentPeriodStart: subscription.currentPeriodStart,
|
|
2734
|
+
currentPeriodEnd: subscription.currentPeriodEnd,
|
|
2735
|
+
minimumTermUntil: subscription.minimumTermUntil,
|
|
2736
|
+
canceledAt: subscription.canceledAt,
|
|
2737
|
+
canceledEffectiveAt: subscription.canceledEffectiveAt,
|
|
2738
|
+
billingAnchorDay: subscription.billingAnchorDay,
|
|
2739
|
+
pendingPlan: subscription.pendingPlan,
|
|
2740
|
+
pendingBillingCycle: subscription.pendingBillingCycle,
|
|
2741
|
+
pendingEffectiveAt: subscription.pendingEffectiveAt,
|
|
2742
|
+
planVersion: toUsagePlanVersion(planVersion),
|
|
2743
|
+
pendingPlanVersion: pendingPlanVersion ? {
|
|
2744
|
+
...toUsagePlanVersion(pendingPlanVersion),
|
|
2745
|
+
// The two fields a tenant is shown before accepting: does
|
|
2746
|
+
// the change take anything away, and what changed.
|
|
2747
|
+
nonRegressive: pendingPlanVersion.nonRegressive,
|
|
2748
|
+
publishedChanges: pendingPlanVersion.publishedChanges
|
|
2749
|
+
} : null,
|
|
2750
|
+
pendingPlanVersionEffectiveAt: subscription.pendingPlanVersionEffectiveAt,
|
|
2751
|
+
pendingPlanVersionAccepted: subscription.pendingPlanVersionAccepted,
|
|
2752
|
+
pendingPlanVersionAcceptedAt: subscription.pendingPlanVersionAcceptedAt,
|
|
2753
|
+
packageSnapshot: subscription.packageSnapshot,
|
|
2754
|
+
checkoutOfferId: subscription.checkoutOfferId
|
|
2755
|
+
};
|
|
2756
|
+
}
|
|
2757
|
+
};
|
|
2758
|
+
DrizzleSubscriptionUsageAdapter = _ts_decorate19([
|
|
2759
|
+
Injectable19(),
|
|
2760
|
+
_ts_param18(0, Inject18(DRIZZLE_DB_TOKEN)),
|
|
2761
|
+
_ts_metadata18("design:type", Function),
|
|
2762
|
+
_ts_metadata18("design:paramtypes", [
|
|
2763
|
+
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
2764
|
+
])
|
|
2765
|
+
], DrizzleSubscriptionUsageAdapter);
|
|
2766
|
+
function toUsagePlanVersion(row) {
|
|
2767
|
+
return {
|
|
2768
|
+
id: row.id,
|
|
2769
|
+
// The canonical schema stores the plan key here, which is what the
|
|
2770
|
+
// record's `planId` means too.
|
|
2771
|
+
planId: row.planId,
|
|
2772
|
+
version: row.version,
|
|
2773
|
+
publishedAt: row.publishedAt,
|
|
2774
|
+
supersededAt: row.supersededAt,
|
|
2775
|
+
changeNote: row.changeNote
|
|
2776
|
+
};
|
|
2777
|
+
}
|
|
2778
|
+
__name(toUsagePlanVersion, "toUsagePlanVersion");
|
|
2779
|
+
|
|
2780
|
+
// src/drizzle-tenant-subscription-write.adapter.ts
|
|
2781
|
+
import { Inject as Inject19, Injectable as Injectable20 } from "@nestjs/common";
|
|
2782
|
+
import { and as and15, eq as eq16, isNull as isNull11 } from "drizzle-orm";
|
|
2783
|
+
function _ts_decorate20(decorators, target, key, desc10) {
|
|
2784
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
2785
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
2786
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2787
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2788
|
+
}
|
|
2789
|
+
__name(_ts_decorate20, "_ts_decorate");
|
|
2790
|
+
function _ts_metadata19(k, v) {
|
|
2791
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2792
|
+
}
|
|
2793
|
+
__name(_ts_metadata19, "_ts_metadata");
|
|
2794
|
+
function _ts_param19(paramIndex, decorator) {
|
|
2795
|
+
return function(target, key) {
|
|
2796
|
+
decorator(target, key, paramIndex);
|
|
2797
|
+
};
|
|
2798
|
+
}
|
|
2799
|
+
__name(_ts_param19, "_ts_param");
|
|
2800
|
+
var DrizzleTenantSubscriptionWrite = class {
|
|
2801
|
+
static {
|
|
2802
|
+
__name(this, "DrizzleTenantSubscriptionWrite");
|
|
2803
|
+
}
|
|
2804
|
+
db;
|
|
2805
|
+
/** Both plan-changing writes bind the version they sell; there is no opt-out here. */
|
|
2806
|
+
bindsPlanVersion = true;
|
|
2807
|
+
plans;
|
|
2808
|
+
constructor(db) {
|
|
2809
|
+
this.db = db;
|
|
2810
|
+
this.plans = new DrizzlePlanRepository(this.db, {
|
|
2811
|
+
validityWindows: true
|
|
2812
|
+
});
|
|
2813
|
+
}
|
|
2814
|
+
async changePlanImmediate(tenantId, input) {
|
|
2815
|
+
return this.db.transaction(async (tx) => {
|
|
2816
|
+
const db = tx;
|
|
2817
|
+
const current = await this.requireSubscription(db, tenantId, {
|
|
2818
|
+
lock: true
|
|
2819
|
+
});
|
|
2820
|
+
const planVersionId = await this.activeVersionId(input.planId, input.periodStart ?? /* @__PURE__ */ new Date(), tx);
|
|
2821
|
+
const pendingMovedAway = await this.pendingVersionBelongsToAnotherPlan(tx, current.pendingPlanVersionId, input.planId);
|
|
2822
|
+
const claimed = await this.claim(db, tenantId, input.expectedCanceledAt, {
|
|
2823
|
+
plan: input.planId,
|
|
2824
|
+
billingCycle: input.cycle,
|
|
2825
|
+
planVersionId,
|
|
2826
|
+
pendingPlan: null,
|
|
2827
|
+
pendingBillingCycle: null,
|
|
2828
|
+
pendingEffectiveAt: null,
|
|
2829
|
+
...input.nextStatus ? {
|
|
2830
|
+
status: input.nextStatus
|
|
2831
|
+
} : {},
|
|
2832
|
+
...periodFields(input.periodStart, input.periodEnd),
|
|
2833
|
+
// Null and undefined both mean "leave the trial as it is" — the
|
|
2834
|
+
// platform passes a carried-over end date or nothing at all.
|
|
2835
|
+
...input.trialEndsAt ? {
|
|
2836
|
+
trialEndsAt: input.trialEndsAt
|
|
2837
|
+
} : {},
|
|
2838
|
+
...pendingMovedAway ? clearedPendingVersion() : {}
|
|
2839
|
+
});
|
|
2840
|
+
const after = await this.requireSubscription(db, tenantId);
|
|
2841
|
+
return {
|
|
2842
|
+
plan: after.plan,
|
|
2843
|
+
billingCycle: after.billingCycle,
|
|
2844
|
+
claimed: claimed > 0
|
|
2845
|
+
};
|
|
2846
|
+
});
|
|
2847
|
+
}
|
|
2848
|
+
async schedulePlanChange(tenantId, input) {
|
|
2849
|
+
const claimed = await this.claim(this.db, tenantId, input.expectedCanceledAt, {
|
|
2850
|
+
pendingPlan: input.pendingPlan,
|
|
2851
|
+
pendingBillingCycle: input.pendingBillingCycle,
|
|
2852
|
+
pendingEffectiveAt: input.pendingEffectiveAt
|
|
2853
|
+
});
|
|
2854
|
+
return {
|
|
2855
|
+
claimed: claimed > 0
|
|
2856
|
+
};
|
|
2857
|
+
}
|
|
2858
|
+
async acceptPendingPlanVersion(tenantId, userId, now) {
|
|
2859
|
+
const sub = await this.requireSubscription(this.db, tenantId);
|
|
2860
|
+
if (!sub.pendingPlanVersionId) {
|
|
2861
|
+
throw new Error(`No pending PlanVersion for tenant ${tenantId}.`);
|
|
835
2862
|
}
|
|
836
|
-
|
|
2863
|
+
const pendingPlanVersionId = sub.pendingPlanVersionId;
|
|
2864
|
+
const claimed = await this.db.update(subscriptions).set({
|
|
2865
|
+
pendingPlanVersionAccepted: true,
|
|
2866
|
+
pendingPlanVersionAcceptedAt: now,
|
|
2867
|
+
pendingPlanVersionAcceptedByUserId: userId,
|
|
2868
|
+
updatedAt: now
|
|
2869
|
+
}).where(and15(eq16(subscriptions.id, sub.id), eq16(subscriptions.pendingPlanVersionId, pendingPlanVersionId), eq16(subscriptions.pendingPlanVersionAccepted, false))).returning({
|
|
2870
|
+
id: subscriptions.id
|
|
2871
|
+
});
|
|
2872
|
+
const updated = await this.requireSubscription(this.db, tenantId);
|
|
2873
|
+
if (claimed.length === 0 && (updated.pendingPlanVersionId !== pendingPlanVersionId || !updated.pendingPlanVersionAccepted)) {
|
|
2874
|
+
throw new Error(`Pending PlanVersion changed while accepting it for tenant ${tenantId}.`);
|
|
2875
|
+
}
|
|
2876
|
+
return {
|
|
2877
|
+
accepted: true,
|
|
2878
|
+
acceptedAt: updated.pendingPlanVersionAcceptedAt,
|
|
2879
|
+
effectiveAt: updated.pendingPlanVersionEffectiveAt,
|
|
2880
|
+
alreadyAccepted: claimed.length === 0
|
|
2881
|
+
};
|
|
837
2882
|
}
|
|
838
|
-
async
|
|
839
|
-
const
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
2883
|
+
async cancelSubscription(tenantId, input) {
|
|
2884
|
+
const now = /* @__PURE__ */ new Date();
|
|
2885
|
+
const claimed = await this.db.update(subscriptions).set({
|
|
2886
|
+
canceledAt: input.canceledAt,
|
|
2887
|
+
canceledEffectiveAt: input.effectiveAt,
|
|
2888
|
+
// Absent means unchanged rather than null: an ordinary
|
|
2889
|
+
// cancellation does not touch the commitment it was measured
|
|
2890
|
+
// against, and writing the field on every call would erase a
|
|
2891
|
+
// term that is still running.
|
|
2892
|
+
...input.minimumTermUntil ? {
|
|
2893
|
+
minimumTermUntil: input.minimumTermUntil
|
|
2894
|
+
} : {},
|
|
2895
|
+
// Written only when the cancellation is already effective.
|
|
2896
|
+
// Restating the status this call read would undo whatever
|
|
2897
|
+
// changed it in between — a trial going live between the read
|
|
2898
|
+
// and the write came back as `TRIAL`, entitlements and all.
|
|
2899
|
+
// An ordinary cancellation records dates; it has no opinion
|
|
2900
|
+
// about the status.
|
|
2901
|
+
...input.terminateNow ? {
|
|
2902
|
+
status: "CANCELED"
|
|
2903
|
+
} : {},
|
|
2904
|
+
updatedAt: now
|
|
2905
|
+
}).where(and15(eq16(subscriptions.tenantId, tenantId), isNull11(subscriptions.canceledAt), isNull11(subscriptions.canceledEffectiveAt))).returning({
|
|
2906
|
+
id: subscriptions.id
|
|
2907
|
+
});
|
|
2908
|
+
const current = await this.requireSubscription(this.db, tenantId);
|
|
2909
|
+
return {
|
|
2910
|
+
canceledAt: current.canceledAt ?? null,
|
|
2911
|
+
canceledEffectiveAt: current.canceledEffectiveAt ?? null,
|
|
2912
|
+
status: current.status,
|
|
2913
|
+
alreadyCanceled: claimed.length === 0
|
|
2914
|
+
};
|
|
843
2915
|
}
|
|
844
|
-
async
|
|
845
|
-
|
|
846
|
-
|
|
2916
|
+
async applyOnboardingSelection(tenantId, input, redeemPromo) {
|
|
2917
|
+
return this.db.transaction(async (tx) => {
|
|
2918
|
+
const db = tx;
|
|
2919
|
+
const planVersionId = await this.activeVersionId(input.planId, input.periodStart ?? /* @__PURE__ */ new Date(), tx);
|
|
2920
|
+
const claimed = await this.claim(db, tenantId, input.expectedCanceledAt, {
|
|
2921
|
+
plan: input.planId,
|
|
2922
|
+
billingCycle: input.cycle,
|
|
2923
|
+
planVersionId,
|
|
2924
|
+
pendingPlan: null,
|
|
2925
|
+
pendingBillingCycle: null,
|
|
2926
|
+
pendingEffectiveAt: null,
|
|
2927
|
+
...clearedPendingVersion(),
|
|
2928
|
+
...input.nextStatus ? {
|
|
2929
|
+
status: input.nextStatus
|
|
2930
|
+
} : {},
|
|
2931
|
+
...periodFields(input.periodStart, input.periodEnd)
|
|
2932
|
+
});
|
|
2933
|
+
const updated = await this.requireSubscription(db, tenantId);
|
|
2934
|
+
if (claimed === 0) {
|
|
2935
|
+
return {
|
|
2936
|
+
plan: updated.plan,
|
|
2937
|
+
billingCycle: updated.billingCycle,
|
|
2938
|
+
subscriptionId: updated.id,
|
|
2939
|
+
promoRedemption: null,
|
|
2940
|
+
claimed: false
|
|
2941
|
+
};
|
|
2942
|
+
}
|
|
2943
|
+
let promoRedemption = null;
|
|
2944
|
+
if (redeemPromo) {
|
|
2945
|
+
promoRedemption = await redeemPromo(tx, updated.id);
|
|
2946
|
+
}
|
|
2947
|
+
return {
|
|
2948
|
+
plan: updated.plan,
|
|
2949
|
+
billingCycle: updated.billingCycle,
|
|
2950
|
+
subscriptionId: updated.id,
|
|
2951
|
+
promoRedemption,
|
|
2952
|
+
claimed: true
|
|
2953
|
+
};
|
|
2954
|
+
});
|
|
847
2955
|
}
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
2956
|
+
/**
|
|
2957
|
+
* One `UPDATE` naming the cancellation state the caller decided against,
|
|
2958
|
+
* answering with how many rows it took. Reading first and updating by
|
|
2959
|
+
* tenant alone would leave the window this closes.
|
|
2960
|
+
*/
|
|
2961
|
+
async claim(db, tenantId, expectedCanceledAt, values) {
|
|
2962
|
+
const claimed = await db.update(subscriptions).set({
|
|
2963
|
+
...values,
|
|
2964
|
+
updatedAt: /* @__PURE__ */ new Date()
|
|
2965
|
+
}).where(and15(eq16(subscriptions.tenantId, tenantId), canceledAtIs(expectedCanceledAt))).returning({
|
|
2966
|
+
id: subscriptions.id
|
|
853
2967
|
});
|
|
854
|
-
return
|
|
2968
|
+
return claimed.length;
|
|
2969
|
+
}
|
|
2970
|
+
/**
|
|
2971
|
+
* `lock: true` takes the row for the rest of the transaction, so a caller
|
|
2972
|
+
* deciding from what it read cannot have the ground move underneath it. It
|
|
2973
|
+
* is only meaningful inside a transaction — outside one the lock is
|
|
2974
|
+
* released with the statement.
|
|
2975
|
+
*/
|
|
2976
|
+
async requireSubscription(db, tenantId, options = {}) {
|
|
2977
|
+
const query = db.select().from(subscriptions).where(eq16(subscriptions.tenantId, tenantId));
|
|
2978
|
+
const rows = await (options.lock ? query.for("update") : query).limit(1);
|
|
2979
|
+
if (!rows[0]) throw new Error(`No subscription for tenant ${tenantId}.`);
|
|
2980
|
+
return rows[0];
|
|
2981
|
+
}
|
|
2982
|
+
async activeVersionId(planKey, asOf, tx) {
|
|
2983
|
+
const active = await this.plans.findActivePlanVersion?.(planKey, asOf, tx);
|
|
2984
|
+
if (!active) throw new Error(`No active PlanVersion for plan '${planKey}'.`);
|
|
2985
|
+
return active.id;
|
|
2986
|
+
}
|
|
2987
|
+
/**
|
|
2988
|
+
* On the caller's transaction, not beside it. The change already holds a
|
|
2989
|
+
* connection for its whole length; asking for a second one waits for the
|
|
2990
|
+
* connection this transaction is holding, and a one-connection pool then
|
|
2991
|
+
* never gets past here.
|
|
2992
|
+
*/
|
|
2993
|
+
async pendingVersionBelongsToAnotherPlan(tx, pendingPlanVersionId, targetPlanKey) {
|
|
2994
|
+
if (!pendingPlanVersionId) return false;
|
|
2995
|
+
const pending = await this.plans.findVersionById(pendingPlanVersionId, tx);
|
|
2996
|
+
return !pending || pending.planId !== targetPlanKey;
|
|
855
2997
|
}
|
|
856
2998
|
};
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
2999
|
+
DrizzleTenantSubscriptionWrite = _ts_decorate20([
|
|
3000
|
+
Injectable20(),
|
|
3001
|
+
_ts_param19(0, Inject19(DRIZZLE_DB_TOKEN)),
|
|
3002
|
+
_ts_metadata19("design:type", Function),
|
|
3003
|
+
_ts_metadata19("design:paramtypes", [
|
|
862
3004
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
863
3005
|
])
|
|
864
|
-
],
|
|
865
|
-
function
|
|
3006
|
+
], DrizzleTenantSubscriptionWrite);
|
|
3007
|
+
function periodFields(periodStart, periodEnd) {
|
|
3008
|
+
if (!periodStart || !periodEnd) return {};
|
|
866
3009
|
return {
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
tenantId: row.tenantId,
|
|
871
|
-
appliedValueType: row.appliedValueType,
|
|
872
|
-
appliedValue: String(row.appliedValue),
|
|
873
|
-
appliedDurationType: row.appliedDurationType,
|
|
874
|
-
appliedDurationValue: row.appliedDurationValue,
|
|
875
|
-
startsAt: row.startsAt,
|
|
876
|
-
endsAt: row.endsAt,
|
|
877
|
-
status: row.status,
|
|
878
|
-
redeemedAt: row.redeemedAt,
|
|
879
|
-
reversedAt: row.reversedAt
|
|
3010
|
+
currentPeriodStart: periodStart,
|
|
3011
|
+
currentPeriodEnd: periodEnd,
|
|
3012
|
+
billingAnchorDay: periodStart.getUTCDate()
|
|
880
3013
|
};
|
|
881
3014
|
}
|
|
882
|
-
__name(
|
|
3015
|
+
__name(periodFields, "periodFields");
|
|
3016
|
+
function clearedPendingVersion() {
|
|
3017
|
+
return {
|
|
3018
|
+
pendingPlanVersionId: null,
|
|
3019
|
+
pendingPlanVersionEffectiveAt: null,
|
|
3020
|
+
pendingPlanVersionAccepted: false,
|
|
3021
|
+
pendingPlanVersionAcceptedAt: null,
|
|
3022
|
+
pendingPlanVersionAcceptedByUserId: null,
|
|
3023
|
+
pendingPlanVersionNotifiedAt: null,
|
|
3024
|
+
pendingPlanVersionReminderSentAt: null
|
|
3025
|
+
};
|
|
3026
|
+
}
|
|
3027
|
+
__name(clearedPendingVersion, "clearedPendingVersion");
|
|
3028
|
+
function canceledAtIs(expected) {
|
|
3029
|
+
return expected === null ? isNull11(subscriptions.canceledAt) : eq16(subscriptions.canceledAt, expected);
|
|
3030
|
+
}
|
|
3031
|
+
__name(canceledAtIs, "canceledAtIs");
|
|
883
3032
|
|
|
884
|
-
// src/drizzle-
|
|
885
|
-
import { randomUUID as
|
|
886
|
-
import { Inject as
|
|
887
|
-
import { and as
|
|
888
|
-
function
|
|
889
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
890
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
3033
|
+
// src/drizzle-subscription-bundle.repository.ts
|
|
3034
|
+
import { randomUUID as randomUUID13 } from "node:crypto";
|
|
3035
|
+
import { Inject as Inject20, Injectable as Injectable21 } from "@nestjs/common";
|
|
3036
|
+
import { and as and16, desc as desc9, eq as eq17, gt as gt3, isNull as isNull12, or as or5 } from "drizzle-orm";
|
|
3037
|
+
function _ts_decorate21(decorators, target, key, desc10) {
|
|
3038
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
3039
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
891
3040
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
892
3041
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
893
3042
|
}
|
|
894
|
-
__name(
|
|
895
|
-
function
|
|
3043
|
+
__name(_ts_decorate21, "_ts_decorate");
|
|
3044
|
+
function _ts_metadata20(k, v) {
|
|
896
3045
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
897
3046
|
}
|
|
898
|
-
__name(
|
|
899
|
-
function
|
|
3047
|
+
__name(_ts_metadata20, "_ts_metadata");
|
|
3048
|
+
function _ts_param20(paramIndex, decorator) {
|
|
900
3049
|
return function(target, key) {
|
|
901
3050
|
decorator(target, key, paramIndex);
|
|
902
3051
|
};
|
|
903
3052
|
}
|
|
904
|
-
__name(
|
|
905
|
-
var
|
|
3053
|
+
__name(_ts_param20, "_ts_param");
|
|
3054
|
+
var DrizzleSubscriptionBundleRepository = class {
|
|
906
3055
|
static {
|
|
907
|
-
__name(this, "
|
|
3056
|
+
__name(this, "DrizzleSubscriptionBundleRepository");
|
|
908
3057
|
}
|
|
909
3058
|
db;
|
|
910
3059
|
constructor(db) {
|
|
911
3060
|
this.db = db;
|
|
912
3061
|
}
|
|
913
|
-
async
|
|
914
|
-
const rows = await this.db.select().from(
|
|
915
|
-
|
|
916
|
-
return row ? toRecord2(row) : null;
|
|
3062
|
+
async listBySubscription(subscriptionId) {
|
|
3063
|
+
const rows = await this.db.select().from(subscriptionBundles).where(eq17(subscriptionBundles.subscriptionId, subscriptionId)).orderBy(desc9(subscriptionBundles.startedAt));
|
|
3064
|
+
return rows.map(toRecord3);
|
|
917
3065
|
}
|
|
918
|
-
async
|
|
919
|
-
const
|
|
920
|
-
|
|
921
|
-
const row = rows[0];
|
|
922
|
-
if (!row || row.deletedAt) return null;
|
|
923
|
-
return toRecord2(row);
|
|
3066
|
+
async findById(subscriptionBundleId) {
|
|
3067
|
+
const rows = await this.db.select().from(subscriptionBundles).where(eq17(subscriptionBundles.id, subscriptionBundleId)).limit(1);
|
|
3068
|
+
return rows[0] ? toRecord3(rows[0]) : null;
|
|
924
3069
|
}
|
|
925
|
-
async
|
|
926
|
-
const
|
|
927
|
-
|
|
928
|
-
filter.status ? eq7(promoCodes.status, filter.status) : void 0,
|
|
929
|
-
filter.campaignTag ? eq7(promoCodes.campaignTag, filter.campaignTag) : void 0,
|
|
930
|
-
filter.search ? like2(promoCodes.code, `%${escapeLikePattern(normalizeCode(filter.search))}%`) : void 0
|
|
931
|
-
];
|
|
932
|
-
const rows = await this.db.select().from(promoCodes).where(and6(...conditions)).orderBy(sql`${promoCodes.createdAt} DESC`);
|
|
933
|
-
return rows.map(toRecord2);
|
|
3070
|
+
async listActiveBySubscription(subscriptionId, asOf = /* @__PURE__ */ new Date(), tx) {
|
|
3071
|
+
const rows = await resolveDb(this.db, tx).select().from(subscriptionBundles).where(and16(eq17(subscriptionBundles.subscriptionId, subscriptionId), stillActive(asOf)));
|
|
3072
|
+
return rows.map(toRecord3);
|
|
934
3073
|
}
|
|
935
|
-
async
|
|
936
|
-
const
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
minimumPlanAmountGross: data.minimumPlanAmountGross?.toFixed(2) ?? null,
|
|
950
|
-
allowZeroInvoice: data.allowZeroInvoice ?? false,
|
|
951
|
-
description: data.description ?? null,
|
|
952
|
-
campaignTag: data.campaignTag ?? null,
|
|
953
|
-
revenueDeductionAccount: data.revenueDeductionAccount ?? null,
|
|
954
|
-
createdById: data.createdById,
|
|
955
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
3074
|
+
async add(data) {
|
|
3075
|
+
const now = /* @__PURE__ */ new Date();
|
|
3076
|
+
const rows = await this.db.insert(subscriptionBundles).values({
|
|
3077
|
+
id: randomUUID13(),
|
|
3078
|
+
subscriptionId: data.subscriptionId,
|
|
3079
|
+
bundleVersionId: data.bundleVersionId,
|
|
3080
|
+
startedAt: data.startedAt ?? now,
|
|
3081
|
+
minimumTermEndsAt: data.minimumTermEndsAt ?? null,
|
|
3082
|
+
billingCycle: data.billingCycle ?? null,
|
|
3083
|
+
currentPeriodStart: data.currentPeriodStart ?? null,
|
|
3084
|
+
currentPeriodEnd: data.currentPeriodEnd ?? null,
|
|
3085
|
+
canceledAt: null,
|
|
3086
|
+
canceledEffectiveAt: null,
|
|
3087
|
+
updatedAt: now
|
|
956
3088
|
}).returning();
|
|
957
|
-
return
|
|
3089
|
+
return toRecord3(rows[0]);
|
|
958
3090
|
}
|
|
959
|
-
async
|
|
960
|
-
const rows = await this.db.update(
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
validUntil: data.validUntil,
|
|
964
|
-
maxRedemptions: data.maxRedemptions,
|
|
3091
|
+
async cancel(subscriptionBundleId, data) {
|
|
3092
|
+
const rows = await this.db.update(subscriptionBundles).set({
|
|
3093
|
+
canceledAt: data.canceledAt,
|
|
3094
|
+
canceledEffectiveAt: data.canceledEffectiveAt,
|
|
965
3095
|
updatedAt: /* @__PURE__ */ new Date()
|
|
966
|
-
}).where(
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
throw new Error(`PromoCode ${id} not found.`);
|
|
3096
|
+
}).where(and16(eq17(subscriptionBundles.id, subscriptionBundleId), isNull12(subscriptionBundles.canceledAt))).returning();
|
|
3097
|
+
if (!rows[0]) {
|
|
3098
|
+
throw new Error(`SubscriptionBundle '${subscriptionBundleId}' not found or already cancelled`);
|
|
970
3099
|
}
|
|
971
|
-
return
|
|
972
|
-
}
|
|
973
|
-
async softDelete(id) {
|
|
974
|
-
await this.db.update(promoCodes).set({
|
|
975
|
-
deletedAt: /* @__PURE__ */ new Date(),
|
|
976
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
977
|
-
}).where(eq7(promoCodes.id, id));
|
|
978
|
-
}
|
|
979
|
-
async claimSlot(id, tx) {
|
|
980
|
-
const db = resolveDb(this.db, tx);
|
|
981
|
-
const claimed = await db.update(promoCodes).set({
|
|
982
|
-
redemptionsCount: sql`${promoCodes.redemptionsCount} + 1`,
|
|
983
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
984
|
-
}).where(and6(eq7(promoCodes.id, id), eq7(promoCodes.status, "ACTIVE"), isNull4(promoCodes.deletedAt), or(isNull4(promoCodes.maxRedemptions), lt3(promoCodes.redemptionsCount, promoCodes.maxRedemptions)))).returning({
|
|
985
|
-
id: promoCodes.id
|
|
986
|
-
});
|
|
987
|
-
return claimed.length === 1;
|
|
988
|
-
}
|
|
989
|
-
async markExhaustedIfFull(id, tx) {
|
|
990
|
-
const db = resolveDb(this.db, tx);
|
|
991
|
-
await db.update(promoCodes).set({
|
|
992
|
-
status: "EXHAUSTED",
|
|
993
|
-
updatedAt: /* @__PURE__ */ new Date()
|
|
994
|
-
}).where(and6(eq7(promoCodes.id, id), eq7(promoCodes.status, "ACTIVE"), isNotNull4(promoCodes.maxRedemptions), gte3(promoCodes.redemptionsCount, promoCodes.maxRedemptions)));
|
|
3100
|
+
return toRecord3(rows[0]);
|
|
995
3101
|
}
|
|
996
|
-
async
|
|
997
|
-
const
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
status: sql`CASE WHEN ${promoCodes.status} = 'EXHAUSTED' THEN 'ACTIVE' ELSE ${promoCodes.status} END`,
|
|
3102
|
+
async reactivate(subscriptionBundleId) {
|
|
3103
|
+
const rows = await this.db.update(subscriptionBundles).set({
|
|
3104
|
+
canceledAt: null,
|
|
3105
|
+
canceledEffectiveAt: null,
|
|
1001
3106
|
updatedAt: /* @__PURE__ */ new Date()
|
|
1002
|
-
}).where(
|
|
3107
|
+
}).where(eq17(subscriptionBundles.id, subscriptionBundleId)).returning();
|
|
3108
|
+
if (!rows[0]) throw new Error(`SubscriptionBundle '${subscriptionBundleId}' not found`);
|
|
3109
|
+
return toRecord3(rows[0]);
|
|
1003
3110
|
}
|
|
1004
|
-
async
|
|
1005
|
-
const
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
"ACTIVE",
|
|
1010
|
-
"PAUSED"
|
|
1011
|
-
]), lt3(promoCodes.validUntil, now))).returning({
|
|
1012
|
-
id: promoCodes.id
|
|
1013
|
-
});
|
|
1014
|
-
return expired.length;
|
|
3111
|
+
async countActiveByBundleVersionId(bundleVersionId, asOf = /* @__PURE__ */ new Date()) {
|
|
3112
|
+
const rows = await this.db.select({
|
|
3113
|
+
id: subscriptionBundles.id
|
|
3114
|
+
}).from(subscriptionBundles).where(and16(eq17(subscriptionBundles.bundleVersionId, bundleVersionId), stillActive(asOf)));
|
|
3115
|
+
return rows.length;
|
|
1015
3116
|
}
|
|
1016
3117
|
};
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
|
|
1021
|
-
|
|
3118
|
+
DrizzleSubscriptionBundleRepository = _ts_decorate21([
|
|
3119
|
+
Injectable21(),
|
|
3120
|
+
_ts_param20(0, Inject20(DRIZZLE_DB_TOKEN)),
|
|
3121
|
+
_ts_metadata20("design:type", Function),
|
|
3122
|
+
_ts_metadata20("design:paramtypes", [
|
|
1022
3123
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
1023
3124
|
])
|
|
1024
|
-
],
|
|
1025
|
-
function
|
|
1026
|
-
return
|
|
3125
|
+
], DrizzleSubscriptionBundleRepository);
|
|
3126
|
+
function stillActive(asOf) {
|
|
3127
|
+
return or5(isNull12(subscriptionBundles.canceledAt), gt3(subscriptionBundles.canceledEffectiveAt, asOf));
|
|
1027
3128
|
}
|
|
1028
|
-
__name(
|
|
1029
|
-
function
|
|
3129
|
+
__name(stillActive, "stillActive");
|
|
3130
|
+
function toRecord3(row) {
|
|
1030
3131
|
return {
|
|
1031
3132
|
id: row.id,
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
appliesToPlans: row.appliesToPlans ?? [],
|
|
1042
|
-
appliesToBilling: row.appliesToBilling ?? null,
|
|
1043
|
-
firstTimeCustomersOnly: row.firstTimeCustomersOnly,
|
|
1044
|
-
minimumPlanAmountGross: row.minimumPlanAmountGross === null ? null : String(row.minimumPlanAmountGross),
|
|
1045
|
-
allowZeroInvoice: row.allowZeroInvoice,
|
|
1046
|
-
status: row.status,
|
|
1047
|
-
description: row.description,
|
|
1048
|
-
campaignTag: row.campaignTag,
|
|
1049
|
-
revenueDeductionAccount: row.revenueDeductionAccount,
|
|
1050
|
-
createdById: row.createdById,
|
|
3133
|
+
subscriptionId: row.subscriptionId,
|
|
3134
|
+
bundleVersionId: row.bundleVersionId,
|
|
3135
|
+
startedAt: row.startedAt,
|
|
3136
|
+
minimumTermEndsAt: row.minimumTermEndsAt,
|
|
3137
|
+
billingCycle: row.billingCycle,
|
|
3138
|
+
currentPeriodStart: row.currentPeriodStart,
|
|
3139
|
+
currentPeriodEnd: row.currentPeriodEnd,
|
|
3140
|
+
canceledAt: row.canceledAt,
|
|
3141
|
+
canceledEffectiveAt: row.canceledEffectiveAt,
|
|
1051
3142
|
createdAt: row.createdAt,
|
|
1052
|
-
updatedAt: row.updatedAt
|
|
1053
|
-
deletedAt: row.deletedAt
|
|
3143
|
+
updatedAt: row.updatedAt
|
|
1054
3144
|
};
|
|
1055
3145
|
}
|
|
1056
|
-
__name(
|
|
3146
|
+
__name(toRecord3, "toRecord");
|
|
1057
3147
|
|
|
1058
3148
|
// src/drizzle-promo-code-validation-log.repository.ts
|
|
1059
|
-
import { randomUUID as
|
|
1060
|
-
import { Inject as
|
|
1061
|
-
import { and as
|
|
1062
|
-
function
|
|
1063
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
1064
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
3149
|
+
import { randomUUID as randomUUID14 } from "node:crypto";
|
|
3150
|
+
import { Inject as Inject21, Injectable as Injectable22 } from "@nestjs/common";
|
|
3151
|
+
import { and as and17, eq as eq18 } from "drizzle-orm";
|
|
3152
|
+
function _ts_decorate22(decorators, target, key, desc10) {
|
|
3153
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
3154
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
1065
3155
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1066
3156
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1067
3157
|
}
|
|
1068
|
-
__name(
|
|
1069
|
-
function
|
|
3158
|
+
__name(_ts_decorate22, "_ts_decorate");
|
|
3159
|
+
function _ts_metadata21(k, v) {
|
|
1070
3160
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1071
3161
|
}
|
|
1072
|
-
__name(
|
|
1073
|
-
function
|
|
3162
|
+
__name(_ts_metadata21, "_ts_metadata");
|
|
3163
|
+
function _ts_param21(paramIndex, decorator) {
|
|
1074
3164
|
return function(target, key) {
|
|
1075
3165
|
decorator(target, key, paramIndex);
|
|
1076
3166
|
};
|
|
1077
3167
|
}
|
|
1078
|
-
__name(
|
|
3168
|
+
__name(_ts_param21, "_ts_param");
|
|
1079
3169
|
var DrizzlePromoCodeValidationLogRepository = class {
|
|
1080
3170
|
static {
|
|
1081
3171
|
__name(this, "DrizzlePromoCodeValidationLogRepository");
|
|
@@ -1086,7 +3176,7 @@ var DrizzlePromoCodeValidationLogRepository = class {
|
|
|
1086
3176
|
}
|
|
1087
3177
|
async log(args) {
|
|
1088
3178
|
await this.db.insert(promoCodeValidationLogs).values({
|
|
1089
|
-
id:
|
|
3179
|
+
id: randomUUID14(),
|
|
1090
3180
|
promoCodeId: args.promoCodeId,
|
|
1091
3181
|
codeAttempt: args.codeAttempt.trim().toUpperCase(),
|
|
1092
3182
|
result: args.result,
|
|
@@ -1097,39 +3187,39 @@ var DrizzlePromoCodeValidationLogRepository = class {
|
|
|
1097
3187
|
async countValid(promoCodeId) {
|
|
1098
3188
|
const rows = await this.db.select({
|
|
1099
3189
|
id: promoCodeValidationLogs.id
|
|
1100
|
-
}).from(promoCodeValidationLogs).where(
|
|
3190
|
+
}).from(promoCodeValidationLogs).where(and17(eq18(promoCodeValidationLogs.promoCodeId, promoCodeId), eq18(promoCodeValidationLogs.result, "VALID")));
|
|
1101
3191
|
return rows.length;
|
|
1102
3192
|
}
|
|
1103
3193
|
};
|
|
1104
|
-
DrizzlePromoCodeValidationLogRepository =
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
3194
|
+
DrizzlePromoCodeValidationLogRepository = _ts_decorate22([
|
|
3195
|
+
Injectable22(),
|
|
3196
|
+
_ts_param21(0, Inject21(DRIZZLE_DB_TOKEN)),
|
|
3197
|
+
_ts_metadata21("design:type", Function),
|
|
3198
|
+
_ts_metadata21("design:paramtypes", [
|
|
1109
3199
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
1110
3200
|
])
|
|
1111
3201
|
], DrizzlePromoCodeValidationLogRepository);
|
|
1112
3202
|
|
|
1113
3203
|
// src/drizzle-promo-subscription-lookup.adapter.ts
|
|
1114
|
-
import { Inject as
|
|
1115
|
-
import { eq as
|
|
1116
|
-
function
|
|
1117
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
1118
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
3204
|
+
import { Inject as Inject22, Injectable as Injectable23 } from "@nestjs/common";
|
|
3205
|
+
import { eq as eq19 } from "drizzle-orm";
|
|
3206
|
+
function _ts_decorate23(decorators, target, key, desc10) {
|
|
3207
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
3208
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
1119
3209
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1120
3210
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1121
3211
|
}
|
|
1122
|
-
__name(
|
|
1123
|
-
function
|
|
3212
|
+
__name(_ts_decorate23, "_ts_decorate");
|
|
3213
|
+
function _ts_metadata22(k, v) {
|
|
1124
3214
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1125
3215
|
}
|
|
1126
|
-
__name(
|
|
1127
|
-
function
|
|
3216
|
+
__name(_ts_metadata22, "_ts_metadata");
|
|
3217
|
+
function _ts_param22(paramIndex, decorator) {
|
|
1128
3218
|
return function(target, key) {
|
|
1129
3219
|
decorator(target, key, paramIndex);
|
|
1130
3220
|
};
|
|
1131
3221
|
}
|
|
1132
|
-
__name(
|
|
3222
|
+
__name(_ts_param22, "_ts_param");
|
|
1133
3223
|
var DrizzlePromoSubscriptionLookup = class {
|
|
1134
3224
|
static {
|
|
1135
3225
|
__name(this, "DrizzlePromoSubscriptionLookup");
|
|
@@ -1146,7 +3236,7 @@ var DrizzlePromoSubscriptionLookup = class {
|
|
|
1146
3236
|
plan: subscriptions.plan,
|
|
1147
3237
|
billingCycle: subscriptions.billingCycle,
|
|
1148
3238
|
startedAt: subscriptions.startedAt
|
|
1149
|
-
}).from(subscriptions).where(
|
|
3239
|
+
}).from(subscriptions).where(eq19(subscriptions.id, subscriptionId)).limit(1);
|
|
1150
3240
|
const row = rows[0];
|
|
1151
3241
|
if (!row) return null;
|
|
1152
3242
|
return {
|
|
@@ -1158,35 +3248,35 @@ var DrizzlePromoSubscriptionLookup = class {
|
|
|
1158
3248
|
};
|
|
1159
3249
|
}
|
|
1160
3250
|
};
|
|
1161
|
-
DrizzlePromoSubscriptionLookup =
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
3251
|
+
DrizzlePromoSubscriptionLookup = _ts_decorate23([
|
|
3252
|
+
Injectable23(),
|
|
3253
|
+
_ts_param22(0, Inject22(DRIZZLE_DB_TOKEN)),
|
|
3254
|
+
_ts_metadata22("design:type", Function),
|
|
3255
|
+
_ts_metadata22("design:paramtypes", [
|
|
1166
3256
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
1167
3257
|
])
|
|
1168
3258
|
], DrizzlePromoSubscriptionLookup);
|
|
1169
3259
|
|
|
1170
3260
|
// src/drizzle-subscription.repository.ts
|
|
1171
|
-
import { Inject as
|
|
1172
|
-
import { eq as
|
|
1173
|
-
function
|
|
1174
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
1175
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
3261
|
+
import { Inject as Inject23, Injectable as Injectable24 } from "@nestjs/common";
|
|
3262
|
+
import { and as and18, eq as eq20, gt as gt4, inArray as inArray5, isNull as isNull13, or as or6 } from "drizzle-orm";
|
|
3263
|
+
function _ts_decorate24(decorators, target, key, desc10) {
|
|
3264
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
3265
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
1176
3266
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1177
3267
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1178
3268
|
}
|
|
1179
|
-
__name(
|
|
1180
|
-
function
|
|
3269
|
+
__name(_ts_decorate24, "_ts_decorate");
|
|
3270
|
+
function _ts_metadata23(k, v) {
|
|
1181
3271
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1182
3272
|
}
|
|
1183
|
-
__name(
|
|
1184
|
-
function
|
|
3273
|
+
__name(_ts_metadata23, "_ts_metadata");
|
|
3274
|
+
function _ts_param23(paramIndex, decorator) {
|
|
1185
3275
|
return function(target, key) {
|
|
1186
3276
|
decorator(target, key, paramIndex);
|
|
1187
3277
|
};
|
|
1188
3278
|
}
|
|
1189
|
-
__name(
|
|
3279
|
+
__name(_ts_param23, "_ts_param");
|
|
1190
3280
|
var ACTIVE_STATUSES = [
|
|
1191
3281
|
"ACTIVE",
|
|
1192
3282
|
"TRIAL"
|
|
@@ -1206,19 +3296,28 @@ var DrizzleSubscriptionRepository = class {
|
|
|
1206
3296
|
const db = resolveDb(this.db, tx);
|
|
1207
3297
|
await db.select({
|
|
1208
3298
|
id: subscriptions.id
|
|
1209
|
-
}).from(subscriptions).where(
|
|
3299
|
+
}).from(subscriptions).where(eq20(subscriptions.tenantId, tenantId)).for("update");
|
|
1210
3300
|
return this.loadByTenantId(db, tenantId);
|
|
1211
3301
|
}
|
|
1212
3302
|
async countByPlanVersionId(planVersionId) {
|
|
1213
3303
|
const rows = await this.db.select({
|
|
1214
3304
|
id: subscriptions.id
|
|
1215
|
-
}).from(subscriptions).where(
|
|
3305
|
+
}).from(subscriptions).where(or6(eq20(subscriptions.planVersionId, planVersionId), eq20(subscriptions.pendingPlanVersionId, planVersionId)));
|
|
1216
3306
|
return rows.length;
|
|
1217
3307
|
}
|
|
1218
|
-
async countActiveByPlanKey(
|
|
3308
|
+
async countActiveByPlanKey() {
|
|
3309
|
+
const now = /* @__PURE__ */ new Date();
|
|
1219
3310
|
const rows = await this.db.select({
|
|
1220
3311
|
plan: subscriptions.plan
|
|
1221
|
-
}).from(subscriptions).where(
|
|
3312
|
+
}).from(subscriptions).where(and18(inArray5(subscriptions.status, ACTIVE_STATUSES), or6(
|
|
3313
|
+
isNull13(subscriptions.canceledAt),
|
|
3314
|
+
gt4(subscriptions.canceledEffectiveAt, now),
|
|
3315
|
+
// The pre-split row keeps its effective date in the
|
|
3316
|
+
// first column; reading only the second drops that
|
|
3317
|
+
// customer on the day they declare rather than the day
|
|
3318
|
+
// they leave.
|
|
3319
|
+
and18(isNull13(subscriptions.canceledEffectiveAt), gt4(subscriptions.canceledAt, now))
|
|
3320
|
+
)));
|
|
1222
3321
|
const counts = {};
|
|
1223
3322
|
for (const row of rows) {
|
|
1224
3323
|
counts[row.plan] = (counts[row.plan] ?? 0) + 1;
|
|
@@ -1226,13 +3325,13 @@ var DrizzleSubscriptionRepository = class {
|
|
|
1226
3325
|
return counts;
|
|
1227
3326
|
}
|
|
1228
3327
|
async loadByTenantId(db, tenantId) {
|
|
1229
|
-
const rows = await db.select().from(subscriptions).where(
|
|
3328
|
+
const rows = await db.select().from(subscriptions).where(eq20(subscriptions.tenantId, tenantId)).limit(1);
|
|
1230
3329
|
const row = rows[0];
|
|
1231
3330
|
if (!row) return null;
|
|
1232
3331
|
return this.toRecord(db, row);
|
|
1233
3332
|
}
|
|
1234
3333
|
async toRecord(db, row) {
|
|
1235
|
-
const versionRows = await db.select().from(planVersions).where(
|
|
3334
|
+
const versionRows = await db.select().from(planVersions).where(eq20(planVersions.id, row.planVersionId)).limit(1);
|
|
1236
3335
|
const planVersion = versionRows[0];
|
|
1237
3336
|
if (!planVersion) {
|
|
1238
3337
|
throw new Error(`Subscription ${row.id} references missing PlanVersion ${row.planVersionId}.`);
|
|
@@ -1247,6 +3346,8 @@ var DrizzleSubscriptionRepository = class {
|
|
|
1247
3346
|
pendingPlan: row.pendingPlan,
|
|
1248
3347
|
pendingEffectiveAt: row.pendingEffectiveAt,
|
|
1249
3348
|
customLimits: row.customLimits ?? null,
|
|
3349
|
+
canceledAt: row.canceledAt ?? null,
|
|
3350
|
+
canceledEffectiveAt: row.canceledEffectiveAt ?? null,
|
|
1250
3351
|
planVersionId: row.planVersionId,
|
|
1251
3352
|
planVersion: {
|
|
1252
3353
|
planId: planVersion.planId,
|
|
@@ -1256,37 +3357,37 @@ var DrizzleSubscriptionRepository = class {
|
|
|
1256
3357
|
};
|
|
1257
3358
|
}
|
|
1258
3359
|
};
|
|
1259
|
-
DrizzleSubscriptionRepository =
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
3360
|
+
DrizzleSubscriptionRepository = _ts_decorate24([
|
|
3361
|
+
Injectable24(),
|
|
3362
|
+
_ts_param23(0, Inject23(DRIZZLE_DB_TOKEN)),
|
|
3363
|
+
_ts_metadata23("design:type", Function),
|
|
3364
|
+
_ts_metadata23("design:paramtypes", [
|
|
1264
3365
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
1265
3366
|
])
|
|
1266
3367
|
], DrizzleSubscriptionRepository);
|
|
1267
3368
|
|
|
1268
3369
|
// src/drizzle-super-admin-bootstrap.adapter.ts
|
|
1269
|
-
import { randomUUID as
|
|
1270
|
-
import { Inject as
|
|
1271
|
-
import { and as
|
|
3370
|
+
import { randomUUID as randomUUID15 } from "node:crypto";
|
|
3371
|
+
import { Inject as Inject24, Injectable as Injectable25 } from "@nestjs/common";
|
|
3372
|
+
import { and as and19, count as count3, eq as eq21, isNull as isNull14 } from "drizzle-orm";
|
|
1272
3373
|
import { PlatformUserExistsError } from "@saasicat/core";
|
|
1273
|
-
function
|
|
1274
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
1275
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
3374
|
+
function _ts_decorate25(decorators, target, key, desc10) {
|
|
3375
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
3376
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
1276
3377
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1277
3378
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1278
3379
|
}
|
|
1279
|
-
__name(
|
|
1280
|
-
function
|
|
3380
|
+
__name(_ts_decorate25, "_ts_decorate");
|
|
3381
|
+
function _ts_metadata24(k, v) {
|
|
1281
3382
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1282
3383
|
}
|
|
1283
|
-
__name(
|
|
1284
|
-
function
|
|
3384
|
+
__name(_ts_metadata24, "_ts_metadata");
|
|
3385
|
+
function _ts_param24(paramIndex, decorator) {
|
|
1285
3386
|
return function(target, key) {
|
|
1286
3387
|
decorator(target, key, paramIndex);
|
|
1287
3388
|
};
|
|
1288
3389
|
}
|
|
1289
|
-
__name(
|
|
3390
|
+
__name(_ts_param24, "_ts_param");
|
|
1290
3391
|
var PASSWORD_HASHER_TOKEN = /* @__PURE__ */ Symbol.for("saasicat/adapter-drizzle/PasswordHasher");
|
|
1291
3392
|
var DrizzleSuperAdminBootstrapAdapter = class {
|
|
1292
3393
|
static {
|
|
@@ -1300,18 +3401,18 @@ var DrizzleSuperAdminBootstrapAdapter = class {
|
|
|
1300
3401
|
}
|
|
1301
3402
|
async countSuperAdmins() {
|
|
1302
3403
|
const rows = await this.db.select({
|
|
1303
|
-
value:
|
|
1304
|
-
}).from(superAdminUsers).where(
|
|
3404
|
+
value: count3()
|
|
3405
|
+
}).from(superAdminUsers).where(and19(eq21(superAdminUsers.isActive, true), isNull14(superAdminUsers.deletedAt)));
|
|
1305
3406
|
return rows[0]?.value ?? 0;
|
|
1306
3407
|
}
|
|
1307
3408
|
async createSuperAdmin(input) {
|
|
1308
3409
|
const email = input.email.trim().toLowerCase();
|
|
1309
|
-
const existing = await this.db.select().from(superAdminUsers).where(
|
|
3410
|
+
const existing = await this.db.select().from(superAdminUsers).where(eq21(superAdminUsers.email, email)).limit(1);
|
|
1310
3411
|
if (existing[0]) {
|
|
1311
3412
|
throw new PlatformUserExistsError(email, existing[0].platformRole);
|
|
1312
3413
|
}
|
|
1313
3414
|
const rows = await this.db.insert(superAdminUsers).values({
|
|
1314
|
-
id:
|
|
3415
|
+
id: randomUUID15(),
|
|
1315
3416
|
email,
|
|
1316
3417
|
passwordHash: await this.passwordHasher.hash(input.password),
|
|
1317
3418
|
firstName: input.firstName ?? null,
|
|
@@ -1331,36 +3432,36 @@ var DrizzleSuperAdminBootstrapAdapter = class {
|
|
|
1331
3432
|
};
|
|
1332
3433
|
}
|
|
1333
3434
|
};
|
|
1334
|
-
DrizzleSuperAdminBootstrapAdapter =
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
3435
|
+
DrizzleSuperAdminBootstrapAdapter = _ts_decorate25([
|
|
3436
|
+
Injectable25(),
|
|
3437
|
+
_ts_param24(0, Inject24(DRIZZLE_DB_TOKEN)),
|
|
3438
|
+
_ts_param24(1, Inject24(PASSWORD_HASHER_TOKEN)),
|
|
3439
|
+
_ts_metadata24("design:type", Function),
|
|
3440
|
+
_ts_metadata24("design:paramtypes", [
|
|
1340
3441
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient,
|
|
1341
3442
|
typeof PasswordHasher === "undefined" ? Object : PasswordHasher
|
|
1342
3443
|
])
|
|
1343
3444
|
], DrizzleSuperAdminBootstrapAdapter);
|
|
1344
3445
|
|
|
1345
3446
|
// src/drizzle-transaction-runner.adapter.ts
|
|
1346
|
-
import { Inject as
|
|
1347
|
-
function
|
|
1348
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
1349
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
3447
|
+
import { Inject as Inject25, Injectable as Injectable26 } from "@nestjs/common";
|
|
3448
|
+
function _ts_decorate26(decorators, target, key, desc10) {
|
|
3449
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
3450
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
1350
3451
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1351
3452
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1352
3453
|
}
|
|
1353
|
-
__name(
|
|
1354
|
-
function
|
|
3454
|
+
__name(_ts_decorate26, "_ts_decorate");
|
|
3455
|
+
function _ts_metadata25(k, v) {
|
|
1355
3456
|
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
1356
3457
|
}
|
|
1357
|
-
__name(
|
|
1358
|
-
function
|
|
3458
|
+
__name(_ts_metadata25, "_ts_metadata");
|
|
3459
|
+
function _ts_param25(paramIndex, decorator) {
|
|
1359
3460
|
return function(target, key) {
|
|
1360
3461
|
decorator(target, key, paramIndex);
|
|
1361
3462
|
};
|
|
1362
3463
|
}
|
|
1363
|
-
__name(
|
|
3464
|
+
__name(_ts_param25, "_ts_param");
|
|
1364
3465
|
var DrizzleTransactionRunner = class {
|
|
1365
3466
|
static {
|
|
1366
3467
|
__name(this, "DrizzleTransactionRunner");
|
|
@@ -1373,24 +3474,24 @@ var DrizzleTransactionRunner = class {
|
|
|
1373
3474
|
return this.db.transaction((tx) => fn(tx));
|
|
1374
3475
|
}
|
|
1375
3476
|
};
|
|
1376
|
-
DrizzleTransactionRunner =
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
3477
|
+
DrizzleTransactionRunner = _ts_decorate26([
|
|
3478
|
+
Injectable26(),
|
|
3479
|
+
_ts_param25(0, Inject25(DRIZZLE_DB_TOKEN)),
|
|
3480
|
+
_ts_metadata25("design:type", Function),
|
|
3481
|
+
_ts_metadata25("design:paramtypes", [
|
|
1381
3482
|
typeof DrizzleClient === "undefined" ? Object : DrizzleClient
|
|
1382
3483
|
])
|
|
1383
3484
|
], DrizzleTransactionRunner);
|
|
1384
3485
|
|
|
1385
3486
|
// src/zero-promo-revenue-aggregator.adapter.ts
|
|
1386
|
-
import { Injectable as
|
|
1387
|
-
function
|
|
1388
|
-
var c = arguments.length, r = c < 3 ? target :
|
|
1389
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key,
|
|
3487
|
+
import { Injectable as Injectable27 } from "@nestjs/common";
|
|
3488
|
+
function _ts_decorate27(decorators, target, key, desc10) {
|
|
3489
|
+
var c = arguments.length, r = c < 3 ? target : desc10 === null ? desc10 = Object.getOwnPropertyDescriptor(target, key) : desc10, d;
|
|
3490
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc10);
|
|
1390
3491
|
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
1391
3492
|
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
1392
3493
|
}
|
|
1393
|
-
__name(
|
|
3494
|
+
__name(_ts_decorate27, "_ts_decorate");
|
|
1394
3495
|
var ZeroPromoRevenueDeductionAggregator = class {
|
|
1395
3496
|
static {
|
|
1396
3497
|
__name(this, "ZeroPromoRevenueDeductionAggregator");
|
|
@@ -1399,8 +3500,8 @@ var ZeroPromoRevenueDeductionAggregator = class {
|
|
|
1399
3500
|
return "0.00";
|
|
1400
3501
|
}
|
|
1401
3502
|
};
|
|
1402
|
-
ZeroPromoRevenueDeductionAggregator =
|
|
1403
|
-
|
|
3503
|
+
ZeroPromoRevenueDeductionAggregator = _ts_decorate27([
|
|
3504
|
+
Injectable27()
|
|
1404
3505
|
], ZeroPromoRevenueDeductionAggregator);
|
|
1405
3506
|
|
|
1406
3507
|
// src/drizzle-persistence.ts
|
|
@@ -1426,18 +3527,55 @@ function drizzlePersistence(options) {
|
|
|
1426
3527
|
transactionRunner: provide((client) => new DrizzleTransactionRunner(client)),
|
|
1427
3528
|
auditQuery: provide((client) => new DrizzleAuditQueryAdapter(client)),
|
|
1428
3529
|
auditStats: provide((client) => new DrizzleAuditStatsAdapter(client)),
|
|
1429
|
-
superAdminProvisioning: buildProvisioning(db, options.passwordHasher)
|
|
3530
|
+
superAdminProvisioning: buildProvisioning(db, options.passwordHasher),
|
|
3531
|
+
appliedSettings: provide((client) => new DrizzleAppliedSettingsRepository(client))
|
|
1430
3532
|
},
|
|
1431
3533
|
entitlement: {
|
|
1432
3534
|
subscriptionRepository: provide((client) => new DrizzleSubscriptionRepository(client)),
|
|
1433
|
-
planVersionRepository: provide((client) => new DrizzlePlanVersionRepository(client))
|
|
3535
|
+
planVersionRepository: provide((client) => new DrizzlePlanVersionRepository(client)),
|
|
3536
|
+
// Entitlement reads the bookings as well as the plan: a tenant's
|
|
3537
|
+
// effective features are the union of the plan and the bundles
|
|
3538
|
+
// booked on top of it, so an adapter that omits this grants less
|
|
3539
|
+
// than the tenant paid for.
|
|
3540
|
+
subscriptionBundleRepository: provide((client) => new DrizzleSubscriptionBundleRepository(client)),
|
|
3541
|
+
// What the tenant actually agreed to, frozen at signing. Entitlement
|
|
3542
|
+
// prefers it over the live catalogue where it exists, which is what
|
|
3543
|
+
// makes a price change stop at the contracts already signed.
|
|
3544
|
+
subscriptionContractRepository: provide((client) => new DrizzleSubscriptionContractRepository(client)),
|
|
3545
|
+
// The party each of those contracts is concluded with.
|
|
3546
|
+
subscriberRepository: provide((client) => new DrizzleSubscriberRepository(client)),
|
|
3547
|
+
// The catalogue behind those bookings: entitlement resolves a
|
|
3548
|
+
// booking's features by reading the pinned version.
|
|
3549
|
+
bundleRepository: provide((client) => new DrizzleBundleRepository(client, options.bundle))
|
|
3550
|
+
},
|
|
3551
|
+
// The editable catalogue: which plans a project sells and in which
|
|
3552
|
+
// versions. Present since 2026-08-27 — before that this adapter could
|
|
3553
|
+
// read a plan version but not author one, so a Drizzle consumer had to
|
|
3554
|
+
// bring their own `CatalogModule` wiring for a slice the adapter was
|
|
3555
|
+
// otherwise complete for.
|
|
3556
|
+
catalog: {
|
|
3557
|
+
planRepository: provide((client) => new DrizzlePlanRepository(client, options.plan)),
|
|
3558
|
+
bundleRepository: provide((client) => new DrizzleBundleRepository(client, options.bundle))
|
|
3559
|
+
},
|
|
3560
|
+
// The tenant's own billing page and the writes behind its buttons.
|
|
3561
|
+
// Both members are required, and until 2026-08-27 neither existed —
|
|
3562
|
+
// which left `DrizzleTenantSubscriptionWrite` unreachable through the
|
|
3563
|
+
// documented `drizzlePersistence({ db })` path however complete it was.
|
|
3564
|
+
tenantBilling: {
|
|
3565
|
+
subscriptionUsagePort: provide((client) => new DrizzleSubscriptionUsageAdapter(client)),
|
|
3566
|
+
subscriptionWritePort: provide((client) => new DrizzleTenantSubscriptionWrite(client))
|
|
1434
3567
|
},
|
|
1435
3568
|
promo: {
|
|
1436
3569
|
promoCodeRepository: provide((client) => new DrizzlePromoCodeRepository(client)),
|
|
1437
3570
|
redemptionRepository: provide((client) => new DrizzlePromoCodeRedemptionRepository(client)),
|
|
1438
3571
|
validationLogRepository: provide((client) => new DrizzlePromoCodeValidationLogRepository(client)),
|
|
1439
3572
|
subscriptionLookup: provide((client) => new DrizzlePromoSubscriptionLookup(client)),
|
|
1440
|
-
revenueAggregator: new ZeroPromoRevenueDeductionAggregator()
|
|
3573
|
+
revenueAggregator: new ZeroPromoRevenueDeductionAggregator(),
|
|
3574
|
+
holdRepository: provide((client) => new DrizzlePromoCodeHoldRepository(client))
|
|
3575
|
+
},
|
|
3576
|
+
payments: {
|
|
3577
|
+
paymentEventLog: provide((client) => new DrizzlePaymentEventLog(client)),
|
|
3578
|
+
subscriberPaymentMethodRepository: provide((client) => new DrizzleSubscriberPaymentMethodRepository(client))
|
|
1441
3579
|
},
|
|
1442
3580
|
planCatalogReadSink: provide((client) => new DrizzlePlanCatalogReadSink(client)),
|
|
1443
3581
|
planCatalogImportSink: provide((client) => new DrizzlePlanCatalogImportSink(client))
|
|
@@ -1483,19 +3621,30 @@ __name(buildProvisioning, "buildProvisioning");
|
|
|
1483
3621
|
export {
|
|
1484
3622
|
AsyncLocalRlsBypassAdapter,
|
|
1485
3623
|
DRIZZLE_DB_TOKEN,
|
|
3624
|
+
DrizzleAppliedSettingsRepository,
|
|
1486
3625
|
DrizzleAuditAdapter,
|
|
1487
3626
|
DrizzleAuditQueryAdapter,
|
|
1488
3627
|
DrizzleAuditStatsAdapter,
|
|
3628
|
+
DrizzleBundleRepository,
|
|
1489
3629
|
DrizzleMfaAdapter,
|
|
3630
|
+
DrizzlePaymentEventLog,
|
|
1490
3631
|
DrizzlePlanCatalogImportSink,
|
|
1491
3632
|
DrizzlePlanCatalogReadSink,
|
|
3633
|
+
DrizzlePlanRepository,
|
|
1492
3634
|
DrizzlePlanVersionRepository,
|
|
3635
|
+
DrizzlePromoCodeHoldRepository,
|
|
1493
3636
|
DrizzlePromoCodeRedemptionRepository,
|
|
1494
3637
|
DrizzlePromoCodeRepository,
|
|
1495
3638
|
DrizzlePromoCodeValidationLogRepository,
|
|
1496
3639
|
DrizzlePromoSubscriptionLookup,
|
|
3640
|
+
DrizzleSubscriberPaymentMethodRepository,
|
|
3641
|
+
DrizzleSubscriberRepository,
|
|
3642
|
+
DrizzleSubscriptionBundleRepository,
|
|
3643
|
+
DrizzleSubscriptionContractRepository,
|
|
1497
3644
|
DrizzleSubscriptionRepository,
|
|
3645
|
+
DrizzleSubscriptionUsageAdapter,
|
|
1498
3646
|
DrizzleSuperAdminBootstrapAdapter,
|
|
3647
|
+
DrizzleTenantSubscriptionWrite,
|
|
1499
3648
|
DrizzleTransactionRunner,
|
|
1500
3649
|
PASSWORD_HASHER_TOKEN,
|
|
1501
3650
|
ZeroPromoRevenueDeductionAggregator,
|