@coursebuilder/adapter-drizzle 2.1.0 → 3.0.0-canary.0.0c9beb2ae
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-KIQLUFJX.js → chunk-2GW3BJE2.js} +2 -2
- package/dist/{chunk-M3WM5Q7W.js → chunk-33ZN7FN7.js} +2 -2
- package/dist/chunk-4VZJQAPV.js +74 -0
- package/dist/chunk-5FLS4ZBX.js +109 -0
- package/dist/chunk-5YR5M22S.js +147 -0
- package/dist/chunk-6XQ6C3IX.js +18 -0
- package/dist/chunk-AIDM2CH6.js +146 -0
- package/dist/chunk-ALTODDUX.js +173 -0
- package/dist/chunk-AZY2VRAE.js +63 -0
- package/dist/chunk-BS7T27VL.js +239 -0
- package/dist/chunk-CN3FCRLR.js +55 -0
- package/dist/chunk-EAXC4CQD.js +245 -0
- package/dist/chunk-EDDG2YFS.js +249 -0
- package/dist/chunk-EDKWCMA7.js +210 -0
- package/dist/{chunk-CFSRMUNC.js → chunk-EDM6DKA5.js} +2 -2
- package/dist/{chunk-EZEQQJNH.js → chunk-EIQBTF5J.js} +2 -2
- package/dist/chunk-FYT7RTVW.js +248 -0
- package/dist/{chunk-DUFH5L2F.js → chunk-INZTF6QV.js} +2 -2
- package/dist/{chunk-3AF6MZQ7.js → chunk-JVHX4JHA.js} +2 -2
- package/dist/chunk-LWEKWDLC.js +300 -0
- package/dist/chunk-OJPEBGGE.js +139 -0
- package/dist/chunk-QKOSV34V.js +16268 -0
- package/dist/chunk-QO3QE3LE.js +330 -0
- package/dist/chunk-QQKNNFFB.js +399 -0
- package/dist/chunk-RBHCV3UW.js +20 -0
- package/dist/chunk-TNH2C27F.js +252 -0
- package/dist/chunk-TNWNW666.js +133 -0
- package/dist/chunk-VBEXYQ6L.js +156 -0
- package/dist/chunk-W3ILNMLD.js +265 -0
- package/dist/chunk-WJ2ZUXL3.js +71 -0
- package/dist/{chunk-4SWTV7Z3.js → chunk-WUMDFFN3.js} +2 -2
- package/dist/{chunk-FJGNLVE4.js → chunk-X7DRYXB7.js} +2 -2
- package/dist/{chunk-PNPTKVJ4.js → chunk-XUBZKYH6.js} +24 -15
- package/dist/{chunk-MYG6PCE3.js → chunk-ZY4AFKUQ.js} +2 -2
- package/dist/index.d.ts +5 -11
- package/dist/index.js +42 -19
- package/dist/lib/mysql/cms-resource-actions.cjs +15932 -4129
- package/dist/lib/mysql/cms-resource-actions.cjs.map +1 -1
- package/dist/lib/mysql/cms-resource-actions.d.cts +10 -25
- package/dist/lib/mysql/cms-resource-actions.d.ts +10 -25
- package/dist/lib/mysql/cms-resource-actions.js +2 -1
- package/dist/lib/mysql/context.cjs +3380 -0
- package/dist/lib/mysql/context.cjs.map +1 -0
- package/dist/lib/mysql/context.d.cts +15 -0
- package/dist/lib/mysql/context.d.ts +15 -0
- package/dist/lib/mysql/context.js +34 -0
- package/dist/lib/mysql/context.js.map +1 -0
- package/dist/lib/mysql/index.cjs +21648 -15013
- package/dist/lib/mysql/index.cjs.map +1 -1
- package/dist/lib/mysql/index.d.cts +24 -14300
- package/dist/lib/mysql/index.d.ts +24 -14300
- package/dist/lib/mysql/index.js +47 -23
- package/dist/lib/mysql/ports/auth-storage.cjs +84 -0
- package/dist/lib/mysql/ports/auth-storage.cjs.map +1 -0
- package/dist/lib/mysql/ports/auth-storage.d.cts +22 -0
- package/dist/lib/mysql/ports/auth-storage.d.ts +22 -0
- package/dist/lib/mysql/ports/auth-storage.js +8 -0
- package/dist/lib/mysql/ports/auth-storage.js.map +1 -0
- package/dist/lib/mysql/ports/catalog.cjs +16688 -0
- package/dist/lib/mysql/ports/catalog.cjs.map +1 -0
- package/dist/lib/mysql/ports/catalog.d.cts +24 -0
- package/dist/lib/mysql/ports/catalog.d.ts +24 -0
- package/dist/lib/mysql/ports/catalog.js +9 -0
- package/dist/lib/mysql/ports/catalog.js.map +1 -0
- package/dist/lib/mysql/ports/content-resource.cjs +321 -0
- package/dist/lib/mysql/ports/content-resource.cjs.map +1 -0
- package/dist/lib/mysql/ports/content-resource.d.cts +23 -0
- package/dist/lib/mysql/ports/content-resource.d.ts +23 -0
- package/dist/lib/mysql/ports/content-resource.js +8 -0
- package/dist/lib/mysql/ports/content-resource.js.map +1 -0
- package/dist/lib/mysql/ports/coupon.cjs +16524 -0
- package/dist/lib/mysql/ports/coupon.cjs.map +1 -0
- package/dist/lib/mysql/ports/coupon.d.cts +22 -0
- package/dist/lib/mysql/ports/coupon.d.ts +22 -0
- package/dist/lib/mysql/ports/coupon.js +9 -0
- package/dist/lib/mysql/ports/coupon.js.map +1 -0
- package/dist/lib/mysql/ports/entitlement.cjs +16350 -0
- package/dist/lib/mysql/ports/entitlement.cjs.map +1 -0
- package/dist/lib/mysql/ports/entitlement.d.cts +22 -0
- package/dist/lib/mysql/ports/entitlement.d.ts +22 -0
- package/dist/lib/mysql/ports/entitlement.js +9 -0
- package/dist/lib/mysql/ports/entitlement.js.map +1 -0
- package/dist/lib/mysql/ports/identity.cjs +154 -0
- package/dist/lib/mysql/ports/identity.cjs.map +1 -0
- package/dist/lib/mysql/ports/identity.d.cts +22 -0
- package/dist/lib/mysql/ports/identity.d.ts +22 -0
- package/dist/lib/mysql/ports/identity.js +8 -0
- package/dist/lib/mysql/ports/identity.js.map +1 -0
- package/dist/lib/mysql/ports/merchant.cjs +194 -0
- package/dist/lib/mysql/ports/merchant.cjs.map +1 -0
- package/dist/lib/mysql/ports/merchant.d.cts +22 -0
- package/dist/lib/mysql/ports/merchant.d.ts +22 -0
- package/dist/lib/mysql/ports/merchant.js +8 -0
- package/dist/lib/mysql/ports/merchant.js.map +1 -0
- package/dist/lib/mysql/ports/organization.cjs +16426 -0
- package/dist/lib/mysql/ports/organization.cjs.map +1 -0
- package/dist/lib/mysql/ports/organization.d.cts +22 -0
- package/dist/lib/mysql/ports/organization.d.ts +22 -0
- package/dist/lib/mysql/ports/organization.js +9 -0
- package/dist/lib/mysql/ports/organization.js.map +1 -0
- package/dist/lib/mysql/ports/progress.cjs +16544 -0
- package/dist/lib/mysql/ports/progress.cjs.map +1 -0
- package/dist/lib/mysql/ports/progress.d.cts +22 -0
- package/dist/lib/mysql/ports/progress.d.ts +22 -0
- package/dist/lib/mysql/ports/progress.js +9 -0
- package/dist/lib/mysql/ports/progress.js.map +1 -0
- package/dist/lib/mysql/ports/purchase.cjs +16609 -0
- package/dist/lib/mysql/ports/purchase.cjs.map +1 -0
- package/dist/lib/mysql/ports/purchase.d.cts +23 -0
- package/dist/lib/mysql/ports/purchase.d.ts +23 -0
- package/dist/lib/mysql/ports/purchase.js +9 -0
- package/dist/lib/mysql/ports/purchase.js.map +1 -0
- package/dist/lib/mysql/ports/subscription.cjs +76 -0
- package/dist/lib/mysql/ports/subscription.cjs.map +1 -0
- package/dist/lib/mysql/ports/subscription.d.cts +22 -0
- package/dist/lib/mysql/ports/subscription.d.ts +22 -0
- package/dist/lib/mysql/ports/subscription.js +8 -0
- package/dist/lib/mysql/ports/subscription.js.map +1 -0
- package/dist/lib/mysql/schema.cjs +3373 -0
- package/dist/lib/mysql/schema.cjs.map +1 -0
- package/dist/lib/mysql/schema.d.cts +14385 -0
- package/dist/lib/mysql/schema.d.ts +14385 -0
- package/dist/lib/mysql/schema.js +35 -0
- package/dist/lib/mysql/schema.js.map +1 -0
- package/dist/lib/mysql/schemas/auth/accounts.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/accounts.js +6 -6
- package/dist/lib/mysql/schemas/auth/device-access-token.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/device-access-token.js +7 -7
- package/dist/lib/mysql/schemas/auth/device-verification.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/device-verification.js +7 -7
- package/dist/lib/mysql/schemas/auth/personal-access-token.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/personal-access-token.js +7 -7
- package/dist/lib/mysql/schemas/auth/profiles.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/profiles.js +6 -6
- package/dist/lib/mysql/schemas/auth/role-permissions.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/role-permissions.js +7 -7
- package/dist/lib/mysql/schemas/auth/roles.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/roles.js +6 -6
- package/dist/lib/mysql/schemas/auth/sessions.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/sessions.js +7 -7
- package/dist/lib/mysql/schemas/auth/user-permissions.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/user-permissions.js +6 -6
- package/dist/lib/mysql/schemas/auth/user-prefs.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/user-prefs.js +6 -6
- package/dist/lib/mysql/schemas/auth/user-roles.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/user-roles.js +6 -6
- package/dist/lib/mysql/schemas/auth/users.cjs +5 -1
- package/dist/lib/mysql/schemas/auth/users.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/users.js +6 -6
- package/dist/lib/mysql/schemas/commerce/coupon.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/coupon.js +6 -6
- package/dist/lib/mysql/schemas/commerce/merchant-charge.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/merchant-charge.js +6 -6
- package/dist/lib/mysql/schemas/commerce/merchant-subscription.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/merchant-subscription.js +6 -6
- package/dist/lib/mysql/schemas/commerce/price.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/price.js +6 -6
- package/dist/lib/mysql/schemas/commerce/product.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/product.js +6 -6
- package/dist/lib/mysql/schemas/commerce/purchase-user-transfer.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/purchase-user-transfer.js +7 -7
- package/dist/lib/mysql/schemas/commerce/purchase.cjs +11 -2
- package/dist/lib/mysql/schemas/commerce/purchase.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/purchase.js +6 -6
- package/dist/lib/mysql/schemas/commerce/subscription.cjs +6 -1
- package/dist/lib/mysql/schemas/commerce/subscription.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/subscription.js +6 -6
- package/dist/lib/mysql/schemas/commerce/upgradable-products.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/upgradable-products.js +7 -7
- package/dist/lib/mysql/schemas/communication/comment.cjs +5 -1
- package/dist/lib/mysql/schemas/communication/comment.cjs.map +1 -1
- package/dist/lib/mysql/schemas/communication/comment.js +6 -6
- package/dist/lib/mysql/schemas/communication/communication-preferences.cjs +5 -1
- package/dist/lib/mysql/schemas/communication/communication-preferences.cjs.map +1 -1
- package/dist/lib/mysql/schemas/communication/communication-preferences.js +6 -6
- package/dist/lib/mysql/schemas/communication/question-response.cjs.map +1 -1
- package/dist/lib/mysql/schemas/communication/question-response.js +7 -7
- package/dist/lib/mysql/schemas/content/content-contributions.cjs +5 -1
- package/dist/lib/mysql/schemas/content/content-contributions.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-contributions.js +6 -6
- package/dist/lib/mysql/schemas/content/content-resource-product.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-product.js +6 -6
- package/dist/lib/mysql/schemas/content/content-resource-resource.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-resource.js +6 -6
- package/dist/lib/mysql/schemas/content/content-resource-tag.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-tag.js +6 -6
- package/dist/lib/mysql/schemas/content/content-resource-version.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-version.js +6 -6
- package/dist/lib/mysql/schemas/content/content-resource.cjs +5 -1
- package/dist/lib/mysql/schemas/content/content-resource.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource.js +6 -6
- package/dist/lib/mysql/schemas/content/tag-tag.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/tag-tag.js +6 -6
- package/dist/lib/mysql/schemas/content/tag.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/tag.js +6 -6
- package/dist/lib/mysql/schemas/entitlements/entitlement.cjs +11 -2
- package/dist/lib/mysql/schemas/entitlements/entitlement.cjs.map +1 -1
- package/dist/lib/mysql/schemas/entitlements/entitlement.js +7 -7
- package/dist/lib/mysql/schemas/org/organization-membership-roles.cjs +11 -2
- package/dist/lib/mysql/schemas/org/organization-membership-roles.cjs.map +1 -1
- package/dist/lib/mysql/schemas/org/organization-membership-roles.js +6 -6
- package/dist/lib/mysql/schemas/org/organization-memberships.cjs +11 -2
- package/dist/lib/mysql/schemas/org/organization-memberships.cjs.map +1 -1
- package/dist/lib/mysql/schemas/org/organization-memberships.d.cts +16 -0
- package/dist/lib/mysql/schemas/org/organization-memberships.d.ts +16 -0
- package/dist/lib/mysql/schemas/org/organization-memberships.js +6 -6
- package/dist/lib/mysql/schemas/org/organizations.cjs +11 -2
- package/dist/lib/mysql/schemas/org/organizations.cjs.map +1 -1
- package/dist/lib/mysql/schemas/org/organizations.d.cts +16 -0
- package/dist/lib/mysql/schemas/org/organizations.d.ts +16 -0
- package/dist/lib/mysql/schemas/org/organizations.js +6 -6
- package/dist/lib/mysql/workflows/content-resource-workflow-utils.cjs +40 -0
- package/dist/lib/mysql/workflows/content-resource-workflow-utils.cjs.map +1 -0
- package/dist/lib/mysql/workflows/content-resource-workflow-utils.d.cts +4 -0
- package/dist/lib/mysql/workflows/content-resource-workflow-utils.d.ts +4 -0
- package/dist/lib/mysql/workflows/content-resource-workflow-utils.js +10 -0
- package/dist/lib/mysql/workflows/content-resource-workflow-utils.js.map +1 -0
- package/dist/lib/mysql/workflows/create-cohort.cjs +184 -0
- package/dist/lib/mysql/workflows/create-cohort.cjs.map +1 -0
- package/dist/lib/mysql/workflows/create-cohort.d.cts +24 -0
- package/dist/lib/mysql/workflows/create-cohort.d.ts +24 -0
- package/dist/lib/mysql/workflows/create-cohort.js +9 -0
- package/dist/lib/mysql/workflows/create-cohort.js.map +1 -0
- package/dist/lib/mysql/workflows/create-event-series.cjs +283 -0
- package/dist/lib/mysql/workflows/create-event-series.cjs.map +1 -0
- package/dist/lib/mysql/workflows/create-event-series.d.cts +24 -0
- package/dist/lib/mysql/workflows/create-event-series.d.ts +24 -0
- package/dist/lib/mysql/workflows/create-event-series.js +9 -0
- package/dist/lib/mysql/workflows/create-event-series.js.map +1 -0
- package/dist/lib/mysql/workflows/create-event.cjs +194 -0
- package/dist/lib/mysql/workflows/create-event.cjs.map +1 -0
- package/dist/lib/mysql/workflows/create-event.d.cts +24 -0
- package/dist/lib/mysql/workflows/create-event.d.ts +24 -0
- package/dist/lib/mysql/workflows/create-event.js +9 -0
- package/dist/lib/mysql/workflows/create-event.js.map +1 -0
- package/dist/lib/mysql/workflows/create-merchant-charge-and-purchase.cjs +231 -0
- package/dist/lib/mysql/workflows/create-merchant-charge-and-purchase.cjs.map +1 -0
- package/dist/lib/mysql/workflows/create-merchant-charge-and-purchase.d.cts +22 -0
- package/dist/lib/mysql/workflows/create-merchant-charge-and-purchase.d.ts +22 -0
- package/dist/lib/mysql/workflows/create-merchant-charge-and-purchase.js +8 -0
- package/dist/lib/mysql/workflows/create-merchant-charge-and-purchase.js.map +1 -0
- package/dist/lib/mysql/workflows/create-workshop.cjs +286 -0
- package/dist/lib/mysql/workflows/create-workshop.cjs.map +1 -0
- package/dist/lib/mysql/workflows/create-workshop.d.cts +24 -0
- package/dist/lib/mysql/workflows/create-workshop.d.ts +24 -0
- package/dist/lib/mysql/workflows/create-workshop.js +9 -0
- package/dist/lib/mysql/workflows/create-workshop.js.map +1 -0
- package/dist/lib/mysql/workflows/redeem-full-price-coupon.cjs +130 -0
- package/dist/lib/mysql/workflows/redeem-full-price-coupon.cjs.map +1 -0
- package/dist/lib/mysql/workflows/redeem-full-price-coupon.d.cts +24 -0
- package/dist/lib/mysql/workflows/redeem-full-price-coupon.d.ts +24 -0
- package/dist/lib/mysql/workflows/redeem-full-price-coupon.js +8 -0
- package/dist/lib/mysql/workflows/redeem-full-price-coupon.js.map +1 -0
- package/dist/lib/mysql/workflows/transfer-purchase-to-user.cjs +95 -0
- package/dist/lib/mysql/workflows/transfer-purchase-to-user.cjs.map +1 -0
- package/dist/lib/mysql/workflows/transfer-purchase-to-user.d.cts +30 -0
- package/dist/lib/mysql/workflows/transfer-purchase-to-user.d.ts +30 -0
- package/dist/lib/mysql/workflows/transfer-purchase-to-user.js +8 -0
- package/dist/lib/mysql/workflows/transfer-purchase-to-user.js.map +1 -0
- package/dist/lib/utils.d.cts +1 -10
- package/dist/lib/utils.d.ts +1 -10
- package/dist/types.d.ts +14 -0
- package/dist/types.js +1 -0
- package/package.json +9 -5
- package/src/index.ts +3 -2
- package/src/lib/mysql/cms-resource-actions.ts +37 -0
- package/src/lib/mysql/context.ts +20 -0
- package/src/lib/mysql/index.ts +118 -4872
- package/src/lib/mysql/ports/auth-storage.ts +139 -0
- package/src/lib/mysql/ports/catalog.ts +610 -0
- package/src/lib/mysql/ports/content-resource.ts +442 -0
- package/src/lib/mysql/ports/coupon.ts +387 -0
- package/src/lib/mysql/ports/entitlement.ts +96 -0
- package/src/lib/mysql/ports/identity.ts +160 -0
- package/src/lib/mysql/ports/merchant.ts +274 -0
- package/src/lib/mysql/ports/organization.ts +216 -0
- package/src/lib/mysql/ports/progress.ts +366 -0
- package/src/lib/mysql/ports/purchase.ts +517 -0
- package/src/lib/mysql/ports/subscription.ts +93 -0
- package/src/lib/mysql/schema.ts +374 -0
- package/src/lib/mysql/schemas/org/organization-memberships.ts +7 -0
- package/src/lib/mysql/schemas/org/organizations.ts +8 -0
- package/src/lib/mysql/workflows/content-resource-workflow-utils.ts +22 -0
- package/src/lib/mysql/workflows/create-cohort.ts +239 -0
- package/src/lib/mysql/workflows/create-event-series.ts +384 -0
- package/src/lib/mysql/workflows/create-event.ts +232 -0
- package/src/lib/mysql/workflows/create-merchant-charge-and-purchase.ts +365 -0
- package/src/lib/mysql/workflows/create-workshop.ts +372 -0
- package/src/lib/mysql/workflows/redeem-full-price-coupon.ts +179 -0
- package/src/lib/mysql/workflows/transfer-purchase-to-user.ts +142 -0
- package/src/types.ts +33 -0
- package/dist/authjs.d.ts +0 -27
- package/dist/authjs.js +0 -1
- package/dist/chunk-25RKHMRM.js +0 -8729
- package/dist/chunk-2AW5KYAK.js +0 -101
- package/dist/chunk-3EF5ZXLG.js +0 -101
- package/dist/chunk-7FBSP3VD.js +0 -78
- package/dist/chunk-AP5HBIV4.js +0 -112
- package/dist/chunk-F56AI52P.js +0 -49
- package/dist/chunk-HAQZSRJL.js +0 -4696
- package/dist/chunk-I2U5PTWN.js +0 -79
- package/dist/chunk-IBQGXFEX.js +0 -8729
- package/dist/chunk-PJORQUQP.js +0 -58
- package/dist/chunk-W6RPAAKG.js +0 -2144
- package/dist/chunk-WKJTU24D.js +0 -88
- package/dist/chunk-WWBSNLRC.js +0 -63
- package/src/authjs.ts +0 -34
package/src/lib/mysql/index.ts
CHANGED
|
@@ -1,4894 +1,140 @@
|
|
|
1
|
-
import slugify from '@sindresorhus/slugify'
|
|
2
|
-
import { addSeconds, isAfter } from 'date-fns'
|
|
3
|
-
import {
|
|
4
|
-
and,
|
|
5
|
-
asc,
|
|
6
|
-
count,
|
|
7
|
-
desc,
|
|
8
|
-
eq,
|
|
9
|
-
gte,
|
|
10
|
-
inArray,
|
|
11
|
-
isNotNull,
|
|
12
|
-
isNull,
|
|
13
|
-
not,
|
|
14
|
-
or,
|
|
15
|
-
sql,
|
|
16
|
-
} from 'drizzle-orm'
|
|
17
1
|
import {
|
|
18
2
|
mysqlTable as defaultMySqlTableFn,
|
|
19
3
|
MySqlDatabase,
|
|
20
|
-
MySqlTableFn,
|
|
4
|
+
type MySqlTableFn,
|
|
21
5
|
} from 'drizzle-orm/mysql-core'
|
|
22
|
-
import { customAlphabet } from 'nanoid'
|
|
23
|
-
import { v4 } from 'uuid'
|
|
24
|
-
import { z } from 'zod'
|
|
25
|
-
|
|
26
|
-
import type {
|
|
27
|
-
CourseBuilderAuthSession,
|
|
28
|
-
CourseBuilderUser,
|
|
29
|
-
} from '@coursebuilder/core/auth'
|
|
30
|
-
import {
|
|
31
|
-
parseAndExpandPurchaseBenefitTransport,
|
|
32
|
-
PURCHASE_BENEFIT_METADATA_KEY,
|
|
33
|
-
} from '@coursebuilder/core/pricing/purchase-benefit-transport'
|
|
34
|
-
import {
|
|
35
|
-
Coupon,
|
|
36
|
-
couponSchema,
|
|
37
|
-
Entitlement,
|
|
38
|
-
entitlementSchema,
|
|
39
|
-
MerchantCharge,
|
|
40
|
-
merchantChargeSchema,
|
|
41
|
-
MerchantCoupon,
|
|
42
|
-
merchantCouponSchema,
|
|
43
|
-
MerchantCustomer,
|
|
44
|
-
MerchantEvents,
|
|
45
|
-
merchantEventsSchema,
|
|
46
|
-
merchantPriceSchema,
|
|
47
|
-
MerchantProduct,
|
|
48
|
-
merchantProductSchema,
|
|
49
|
-
NewProduct,
|
|
50
|
-
Price,
|
|
51
|
-
priceSchema,
|
|
52
|
-
Product,
|
|
53
|
-
productSchema,
|
|
54
|
-
Purchase,
|
|
55
|
-
purchaseSchema,
|
|
56
|
-
PurchaseUserTransfer,
|
|
57
|
-
purchaseUserTransferSchema,
|
|
58
|
-
PurchaseUserTransferState,
|
|
59
|
-
ResourceProgress,
|
|
60
|
-
resourceProgressSchema,
|
|
61
|
-
UpgradableProduct,
|
|
62
|
-
upgradableProductSchema,
|
|
63
|
-
User,
|
|
64
|
-
userSchema,
|
|
65
|
-
} from '@coursebuilder/core/schemas'
|
|
66
|
-
import {
|
|
67
|
-
ContentResourceProductSchema,
|
|
68
|
-
ContentResourceResourceSchema,
|
|
69
|
-
ContentResourceSchema,
|
|
70
|
-
type ContentResource,
|
|
71
|
-
} from '@coursebuilder/core/schemas/content-resource-schema'
|
|
72
|
-
import { merchantAccountSchema } from '@coursebuilder/core/schemas/merchant-account-schema'
|
|
73
|
-
import { merchantCustomerSchema } from '@coursebuilder/core/schemas/merchant-customer-schema'
|
|
74
|
-
import {
|
|
75
|
-
MerchantSession,
|
|
76
|
-
MerchantSessionSchema,
|
|
77
|
-
} from '@coursebuilder/core/schemas/merchant-session'
|
|
78
|
-
import { MerchantSubscriptionSchema } from '@coursebuilder/core/schemas/merchant-subscription'
|
|
79
|
-
import { OrganizationMemberSchema } from '@coursebuilder/core/schemas/organization-member'
|
|
80
|
-
import { OrganizationSchema } from '@coursebuilder/core/schemas/organization-schema'
|
|
81
|
-
import { type ModuleProgress } from '@coursebuilder/core/schemas/resource-progress-schema'
|
|
82
|
-
import { SubscriptionSchema } from '@coursebuilder/core/schemas/subscription'
|
|
83
|
-
import { VideoResourceSchema } from '@coursebuilder/core/schemas/video-resource'
|
|
84
|
-
import { PaymentsProviderConfig } from '@coursebuilder/core/types'
|
|
85
|
-
import { logger as baseLogger } from '@coursebuilder/core/utils/logger'
|
|
86
|
-
import { validateCoupon } from '@coursebuilder/core/utils/validate-coupon'
|
|
87
6
|
|
|
88
|
-
import type {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
} from './
|
|
93
|
-
import {
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
} from './
|
|
97
|
-
import {
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
} from './
|
|
101
|
-
import {
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
} from './
|
|
105
|
-
import {
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
} from './
|
|
109
|
-
import {
|
|
110
|
-
getProfilesRelationsSchema,
|
|
111
|
-
getProfilesSchema,
|
|
112
|
-
} from './schemas/auth/profiles.js'
|
|
113
|
-
import {
|
|
114
|
-
getRolePermissionsRelationsSchema,
|
|
115
|
-
getRolePermissionsSchema,
|
|
116
|
-
} from './schemas/auth/role-permissions.js'
|
|
117
|
-
import {
|
|
118
|
-
getRolesRelationsSchema,
|
|
119
|
-
getRolesSchema,
|
|
120
|
-
} from './schemas/auth/roles.js'
|
|
121
|
-
import {
|
|
122
|
-
getSessionRelationsSchema,
|
|
123
|
-
getSessionsSchema,
|
|
124
|
-
} from './schemas/auth/sessions.js'
|
|
125
|
-
import {
|
|
126
|
-
getUserPermissionsRelationsSchema,
|
|
127
|
-
getUserPermissionsSchema,
|
|
128
|
-
} from './schemas/auth/user-permissions.js'
|
|
129
|
-
import {
|
|
130
|
-
getUserPrefsRelationsSchema,
|
|
131
|
-
getUserPrefsSchema,
|
|
132
|
-
} from './schemas/auth/user-prefs.js'
|
|
133
|
-
import {
|
|
134
|
-
getUserRolesRelationsSchema,
|
|
135
|
-
getUserRolesSchema,
|
|
136
|
-
} from './schemas/auth/user-roles.js'
|
|
137
|
-
import {
|
|
138
|
-
getUsersRelationsSchema,
|
|
139
|
-
getUsersSchema,
|
|
140
|
-
type UsersSchemaOverrides,
|
|
141
|
-
} from './schemas/auth/users.js'
|
|
142
|
-
import { getVerificationTokensSchema } from './schemas/auth/verification-tokens.js'
|
|
143
|
-
import {
|
|
144
|
-
getCouponRelationsSchema,
|
|
145
|
-
getCouponSchema,
|
|
146
|
-
} from './schemas/commerce/coupon.js'
|
|
147
|
-
import { getMerchantAccountSchema } from './schemas/commerce/merchant-account.js'
|
|
148
|
-
import {
|
|
149
|
-
getMerchantChargeRelationsSchema,
|
|
150
|
-
getMerchantChargeSchema,
|
|
151
|
-
} from './schemas/commerce/merchant-charge.js'
|
|
152
|
-
import { getMerchantCouponSchema } from './schemas/commerce/merchant-coupon.js'
|
|
153
|
-
import { getMerchantCustomerSchema } from './schemas/commerce/merchant-customer.js'
|
|
154
|
-
import {
|
|
155
|
-
getMerchantEventsRelationsSchema,
|
|
156
|
-
getMerchantEventsSchema,
|
|
157
|
-
} from './schemas/commerce/merchant-events.js'
|
|
158
|
-
import { getMerchantPriceSchema } from './schemas/commerce/merchant-price.js'
|
|
159
|
-
import { getMerchantProductSchema } from './schemas/commerce/merchant-product.js'
|
|
160
|
-
import { getMerchantSessionSchema } from './schemas/commerce/merchant-session.js'
|
|
161
|
-
import {
|
|
162
|
-
getMerchantSubscriptionRelationsSchema,
|
|
163
|
-
getMerchantSubscriptionSchema,
|
|
164
|
-
} from './schemas/commerce/merchant-subscription.js'
|
|
165
|
-
import { getPriceSchema } from './schemas/commerce/price.js'
|
|
166
|
-
import {
|
|
167
|
-
getProductRelationsSchema,
|
|
168
|
-
getProductSchema,
|
|
169
|
-
} from './schemas/commerce/product.js'
|
|
170
|
-
import {
|
|
171
|
-
getPurchaseUserTransferRelationsSchema,
|
|
172
|
-
getPurchaseUserTransferSchema,
|
|
173
|
-
} from './schemas/commerce/purchase-user-transfer.js'
|
|
174
|
-
import {
|
|
175
|
-
getPurchaseRelationsSchema,
|
|
176
|
-
getPurchaseSchema,
|
|
177
|
-
} from './schemas/commerce/purchase.js'
|
|
178
|
-
import {
|
|
179
|
-
getSubscriptionRelationsSchema,
|
|
180
|
-
getSubscriptionSchema,
|
|
181
|
-
} from './schemas/commerce/subscription.js'
|
|
182
|
-
import {
|
|
183
|
-
getUpgradableProductsRelationsSchema,
|
|
184
|
-
getUpgradableProductsSchema,
|
|
185
|
-
} from './schemas/commerce/upgradable-products.js'
|
|
186
|
-
import {
|
|
187
|
-
getCommentRelationsSchema,
|
|
188
|
-
getCommentsSchema,
|
|
189
|
-
} from './schemas/communication/comment.js'
|
|
190
|
-
import { getCommunicationChannelSchema } from './schemas/communication/communication-channel.js'
|
|
191
|
-
import { getCommunicationPreferenceTypesSchema } from './schemas/communication/communication-preference-types.js'
|
|
192
|
-
import {
|
|
193
|
-
getCommunicationPreferencesRelationsSchema,
|
|
194
|
-
getCommunicationPreferencesSchema,
|
|
195
|
-
} from './schemas/communication/communication-preferences.js'
|
|
196
|
-
import {
|
|
197
|
-
getQuestionResponseRelationsSchema,
|
|
198
|
-
getQuestionResponseSchema,
|
|
199
|
-
} from './schemas/communication/question-response.js'
|
|
200
|
-
import {
|
|
201
|
-
getContentContributionRelationsSchema,
|
|
202
|
-
getContentContributionsSchema,
|
|
203
|
-
} from './schemas/content/content-contributions.js'
|
|
204
|
-
import {
|
|
205
|
-
getContentResourceProductRelationsSchema,
|
|
206
|
-
getContentResourceProductSchema,
|
|
207
|
-
} from './schemas/content/content-resource-product.js'
|
|
208
|
-
import {
|
|
209
|
-
getContentResourceResourceRelationsSchema,
|
|
210
|
-
getContentResourceResourceSchema,
|
|
211
|
-
} from './schemas/content/content-resource-resource.js'
|
|
212
|
-
import {
|
|
213
|
-
getContentResourceTagRelationsSchema,
|
|
214
|
-
getContentResourceTagSchema,
|
|
215
|
-
} from './schemas/content/content-resource-tag.js'
|
|
216
|
-
import {
|
|
217
|
-
getContentResourceVersionRelationsSchema,
|
|
218
|
-
getContentResourceVersionSchema,
|
|
219
|
-
} from './schemas/content/content-resource-version.js'
|
|
220
|
-
import {
|
|
221
|
-
getContentResourceRelationsSchema,
|
|
222
|
-
getContentResourceSchema,
|
|
223
|
-
} from './schemas/content/content-resource.js'
|
|
224
|
-
import {
|
|
225
|
-
getContributionTypesRelationsSchema,
|
|
226
|
-
getContributionTypesSchema,
|
|
227
|
-
} from './schemas/content/contribution-types.js'
|
|
228
|
-
import { getLessonProgressSchema } from './schemas/content/lesson-progress.js'
|
|
229
|
-
import { getResourceProgressSchema } from './schemas/content/resource-progress.js'
|
|
230
|
-
import {
|
|
231
|
-
getTagTagRelationsSchema,
|
|
232
|
-
getTagTagSchema,
|
|
233
|
-
} from './schemas/content/tag-tag.js'
|
|
234
|
-
import { getTagRelationsSchema, getTagSchema } from './schemas/content/tag.js'
|
|
235
|
-
import { getEntitlementTypesSchema } from './schemas/entitlements/entitlement-type.js'
|
|
236
|
-
import {
|
|
237
|
-
getEntitlementRelationsSchema,
|
|
238
|
-
getEntitlementsSchema,
|
|
239
|
-
} from './schemas/entitlements/entitlement.js'
|
|
240
|
-
import {
|
|
241
|
-
getOrganizationMembershipRolesRelationsSchema,
|
|
242
|
-
getOrganizationMembershipRolesSchema,
|
|
243
|
-
} from './schemas/org/organization-membership-roles.js'
|
|
244
|
-
import {
|
|
245
|
-
getOrganizationMembershipsRelationsSchema,
|
|
246
|
-
getOrganizationMembershipsSchema,
|
|
247
|
-
} from './schemas/org/organization-memberships.js'
|
|
248
|
-
import {
|
|
249
|
-
getOrganizationsRelationsSchema,
|
|
250
|
-
getOrganizationsSchema,
|
|
251
|
-
} from './schemas/org/organizations.js'
|
|
7
|
+
import type { PaymentsProviderConfig } from '@coursebuilder/core/types'
|
|
8
|
+
|
|
9
|
+
import type { DrizzleAdapterComposition } from '../../types.js'
|
|
10
|
+
import { createMySqlContext } from './context.js'
|
|
11
|
+
import { createAuthStoragePort } from './ports/auth-storage.js'
|
|
12
|
+
import { createCatalogPort } from './ports/catalog.js'
|
|
13
|
+
import { createContentResourcePort } from './ports/content-resource.js'
|
|
14
|
+
import { createCouponPort } from './ports/coupon.js'
|
|
15
|
+
import { createEntitlementPort } from './ports/entitlement.js'
|
|
16
|
+
import { createIdentityPort } from './ports/identity.js'
|
|
17
|
+
import { createMerchantPort } from './ports/merchant.js'
|
|
18
|
+
import { createOrganizationPort } from './ports/organization.js'
|
|
19
|
+
import { createProgressPort } from './ports/progress.js'
|
|
20
|
+
import { createPurchasePort } from './ports/purchase.js'
|
|
21
|
+
import { createSubscriptionPort } from './ports/subscription.js'
|
|
22
|
+
import { createCohortWorkflow } from './workflows/create-cohort.js'
|
|
23
|
+
import { createEventSeriesWorkflow } from './workflows/create-event-series.js'
|
|
24
|
+
import { createEventWorkflow } from './workflows/create-event.js'
|
|
25
|
+
import { createMerchantChargeAndPurchaseWorkflow } from './workflows/create-merchant-charge-and-purchase.js'
|
|
26
|
+
import { createWorkshopWorkflow } from './workflows/create-workshop.js'
|
|
27
|
+
import { createRedeemFullPriceCouponWorkflow } from './workflows/redeem-full-price-coupon.js'
|
|
28
|
+
import { createTransferPurchaseToUserWorkflow } from './workflows/transfer-purchase-to-user.js'
|
|
252
29
|
|
|
253
30
|
export * from './cms-resource-actions.js'
|
|
31
|
+
export * from './schema.js'
|
|
254
32
|
|
|
255
|
-
export
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
// Create a new Date object for 23:59:59 UTC on the date part of the input date
|
|
261
|
-
// The input date should be a JS Date representing 00:00:00 LA time for the chosen day.
|
|
262
|
-
// Its UTC date parts (getUTCFullYear, etc.) will give us the correct calendar day.
|
|
263
|
-
return new Date(
|
|
264
|
-
Date.UTC(
|
|
265
|
-
date.getUTCFullYear(),
|
|
266
|
-
date.getUTCMonth(), // 0-indexed
|
|
267
|
-
date.getUTCDate(),
|
|
268
|
-
23, // hours
|
|
269
|
-
59, // minutes
|
|
270
|
-
59, // seconds
|
|
271
|
-
0, // milliseconds
|
|
272
|
-
),
|
|
273
|
-
)
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
function toError(error: unknown) {
|
|
277
|
-
return error instanceof Error ? error : new Error(String(error))
|
|
278
|
-
}
|
|
279
|
-
|
|
280
|
-
const logger = baseLogger as unknown as {
|
|
281
|
-
debug: (message: string, attrs?: Record<string, unknown>) => void
|
|
282
|
-
error: (error: Error, attrs?: Record<string, unknown>) => void
|
|
283
|
-
info: (message: string, attrs?: Record<string, unknown>) => void
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
type PurchaseBenefitExpansion = ReturnType<
|
|
287
|
-
typeof parseAndExpandPurchaseBenefitTransport
|
|
288
|
-
> | null
|
|
289
|
-
|
|
290
|
-
function mergePurchaseBenefitFields(
|
|
291
|
-
fields: Record<string, any>,
|
|
292
|
-
expansion: PurchaseBenefitExpansion,
|
|
293
|
-
) {
|
|
294
|
-
if (!expansion) return fields
|
|
295
|
-
|
|
296
|
-
return {
|
|
297
|
-
...fields,
|
|
298
|
-
...(expansion.success
|
|
299
|
-
? { purchaseBenefits: expansion.benefits }
|
|
300
|
-
: { purchaseBenefitsReview: expansion.review }),
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
export type CourseBuilderSchemaOverrides<
|
|
305
|
-
TUsers extends ReturnType<typeof getUsersSchema> = ReturnType<
|
|
306
|
-
typeof getUsersSchema
|
|
307
|
-
>,
|
|
308
|
-
> = UsersSchemaOverrides<TUsers>
|
|
309
|
-
|
|
310
|
-
export function getCourseBuilderSchema<
|
|
311
|
-
TUsers extends ReturnType<typeof getUsersSchema> = ReturnType<
|
|
312
|
-
typeof getUsersSchema
|
|
313
|
-
>,
|
|
314
|
-
>(
|
|
315
|
-
mysqlTable: MySqlTableFn,
|
|
316
|
-
overrides: CourseBuilderSchemaOverrides<TUsers> = {},
|
|
317
|
-
) {
|
|
318
|
-
const resolvedGetUsersSchema = (overrides.getUsersSchema ??
|
|
319
|
-
getUsersSchema) as (mysqlTable: MySqlTableFn) => TUsers
|
|
320
|
-
const userRelationsOverrides: UsersSchemaOverrides<TUsers> = {
|
|
321
|
-
getUsersSchema: resolvedGetUsersSchema,
|
|
322
|
-
}
|
|
323
|
-
return {
|
|
324
|
-
accounts: getAccountsSchema(mysqlTable),
|
|
325
|
-
accountsRelations: getAccountsRelationsSchema(
|
|
326
|
-
mysqlTable,
|
|
327
|
-
userRelationsOverrides,
|
|
328
|
-
),
|
|
329
|
-
permissions: getPermissionsSchema(mysqlTable),
|
|
330
|
-
permissionsRelations: getPermissionsRelationsSchema(mysqlTable),
|
|
331
|
-
rolePermissions: getRolePermissionsSchema(mysqlTable),
|
|
332
|
-
rolePermissionsRelations: getRolePermissionsRelationsSchema(mysqlTable),
|
|
333
|
-
roles: getRolesSchema(mysqlTable),
|
|
334
|
-
rolesRelations: getRolesRelationsSchema(mysqlTable),
|
|
335
|
-
sessions: getSessionsSchema(mysqlTable),
|
|
336
|
-
sessionsRelations: getSessionRelationsSchema(
|
|
337
|
-
mysqlTable,
|
|
338
|
-
userRelationsOverrides,
|
|
339
|
-
),
|
|
340
|
-
userPermissions: getUserPermissionsSchema(mysqlTable),
|
|
341
|
-
userPermissionsRelations: getUserPermissionsRelationsSchema(
|
|
342
|
-
mysqlTable,
|
|
343
|
-
userRelationsOverrides,
|
|
344
|
-
),
|
|
345
|
-
userRoles: getUserRolesSchema(mysqlTable),
|
|
346
|
-
userRolesRelations: getUserRolesRelationsSchema(
|
|
347
|
-
mysqlTable,
|
|
348
|
-
userRelationsOverrides,
|
|
349
|
-
),
|
|
350
|
-
users: resolvedGetUsersSchema(mysqlTable),
|
|
351
|
-
usersRelations: getUsersRelationsSchema(mysqlTable, userRelationsOverrides),
|
|
352
|
-
verificationTokens: getVerificationTokensSchema(mysqlTable),
|
|
353
|
-
coupon: getCouponSchema(mysqlTable),
|
|
354
|
-
couponRelations: getCouponRelationsSchema(mysqlTable),
|
|
355
|
-
lessonProgress: getLessonProgressSchema(mysqlTable),
|
|
356
|
-
merchantAccount: getMerchantAccountSchema(mysqlTable),
|
|
357
|
-
merchantCharge: getMerchantChargeSchema(mysqlTable),
|
|
358
|
-
merchantChargeRelations: getMerchantChargeRelationsSchema(mysqlTable),
|
|
359
|
-
merchantEvents: getMerchantEventsSchema(mysqlTable),
|
|
360
|
-
merchantEventsRelations: getMerchantEventsRelationsSchema(mysqlTable),
|
|
361
|
-
merchantCoupon: getMerchantCouponSchema(mysqlTable),
|
|
362
|
-
merchantCustomer: getMerchantCustomerSchema(mysqlTable),
|
|
363
|
-
merchantPrice: getMerchantPriceSchema(mysqlTable),
|
|
364
|
-
merchantProduct: getMerchantProductSchema(mysqlTable),
|
|
365
|
-
merchantSession: getMerchantSessionSchema(mysqlTable),
|
|
366
|
-
prices: getPriceSchema(mysqlTable),
|
|
367
|
-
products: getProductSchema(mysqlTable),
|
|
368
|
-
purchases: getPurchaseSchema(mysqlTable),
|
|
369
|
-
purchaseRelations: getPurchaseRelationsSchema(
|
|
370
|
-
mysqlTable,
|
|
371
|
-
userRelationsOverrides,
|
|
372
|
-
),
|
|
373
|
-
purchaseUserTransfer: getPurchaseUserTransferSchema(mysqlTable),
|
|
374
|
-
purchaseUserTransferRelations: getPurchaseUserTransferRelationsSchema(
|
|
375
|
-
mysqlTable,
|
|
376
|
-
userRelationsOverrides,
|
|
377
|
-
),
|
|
378
|
-
communicationChannel: getCommunicationChannelSchema(mysqlTable),
|
|
379
|
-
communicationPreferenceTypes:
|
|
380
|
-
getCommunicationPreferenceTypesSchema(mysqlTable),
|
|
381
|
-
communicationPreferences: getCommunicationPreferencesSchema(mysqlTable),
|
|
382
|
-
communicationPreferencesRelations:
|
|
383
|
-
getCommunicationPreferencesRelationsSchema(
|
|
384
|
-
mysqlTable,
|
|
385
|
-
userRelationsOverrides,
|
|
386
|
-
),
|
|
387
|
-
contentContributions: getContentContributionsSchema(mysqlTable),
|
|
388
|
-
contentContributionRelations: getContentContributionRelationsSchema(
|
|
389
|
-
mysqlTable,
|
|
390
|
-
userRelationsOverrides,
|
|
391
|
-
),
|
|
392
|
-
contentResource: getContentResourceSchema(mysqlTable),
|
|
393
|
-
contentResourceVersion: getContentResourceVersionSchema(mysqlTable),
|
|
394
|
-
contentResourceVersionRelations: getContentResourceVersionRelationsSchema(
|
|
395
|
-
mysqlTable,
|
|
396
|
-
userRelationsOverrides,
|
|
397
|
-
),
|
|
398
|
-
contentResourceRelations: getContentResourceRelationsSchema(
|
|
399
|
-
mysqlTable,
|
|
400
|
-
userRelationsOverrides,
|
|
401
|
-
),
|
|
402
|
-
contentResourceResource: getContentResourceResourceSchema(mysqlTable),
|
|
403
|
-
contentResourceResourceRelations:
|
|
404
|
-
getContentResourceResourceRelationsSchema(mysqlTable),
|
|
405
|
-
contentResourceTag: getContentResourceTagSchema(mysqlTable),
|
|
406
|
-
contentResourceTagRelations:
|
|
407
|
-
getContentResourceTagRelationsSchema(mysqlTable),
|
|
408
|
-
contributionTypes: getContributionTypesSchema(mysqlTable),
|
|
409
|
-
contributionTypesRelations: getContributionTypesRelationsSchema(mysqlTable),
|
|
410
|
-
resourceProgress: getResourceProgressSchema(mysqlTable),
|
|
411
|
-
questionResponse: getQuestionResponseSchema(mysqlTable),
|
|
412
|
-
questionResponseRelations: getQuestionResponseRelationsSchema(
|
|
413
|
-
mysqlTable,
|
|
414
|
-
userRelationsOverrides,
|
|
415
|
-
),
|
|
416
|
-
upgradableProducts: getUpgradableProductsSchema(mysqlTable),
|
|
417
|
-
upgradableProductsRelations:
|
|
418
|
-
getUpgradableProductsRelationsSchema(mysqlTable),
|
|
419
|
-
contentResourceProduct: getContentResourceProductSchema(mysqlTable),
|
|
420
|
-
contentResourceProductRelations:
|
|
421
|
-
getContentResourceProductRelationsSchema(mysqlTable),
|
|
422
|
-
productRelations: getProductRelationsSchema(mysqlTable),
|
|
423
|
-
comments: getCommentsSchema(mysqlTable),
|
|
424
|
-
commentsRelations: getCommentRelationsSchema(
|
|
425
|
-
mysqlTable,
|
|
426
|
-
userRelationsOverrides,
|
|
427
|
-
),
|
|
428
|
-
deviceVerifications: getDeviceVerificationSchema(mysqlTable),
|
|
429
|
-
deviceVerificationRelations: getDeviceVerificationRelationsSchema(
|
|
430
|
-
mysqlTable,
|
|
431
|
-
userRelationsOverrides,
|
|
432
|
-
),
|
|
433
|
-
deviceAccessToken: getDeviceAccessTokenSchema(mysqlTable),
|
|
434
|
-
deviceAccessTokenRelations: getDeviceAccessTokenRelationsSchema(
|
|
435
|
-
mysqlTable,
|
|
436
|
-
userRelationsOverrides,
|
|
437
|
-
),
|
|
438
|
-
personalAccessToken: getPersonalAccessTokenSchema(mysqlTable),
|
|
439
|
-
personalAccessTokenRelations: getPersonalAccessTokenRelationsSchema(
|
|
440
|
-
mysqlTable,
|
|
441
|
-
userRelationsOverrides,
|
|
442
|
-
),
|
|
443
|
-
tag: getTagSchema(mysqlTable),
|
|
444
|
-
tagRelations: getTagRelationsSchema(mysqlTable),
|
|
445
|
-
tagTag: getTagTagSchema(mysqlTable),
|
|
446
|
-
tagTagRelations: getTagTagRelationsSchema(mysqlTable),
|
|
447
|
-
userPrefs: getUserPrefsSchema(mysqlTable),
|
|
448
|
-
userPrefsRelations: getUserPrefsRelationsSchema(
|
|
449
|
-
mysqlTable,
|
|
450
|
-
userRelationsOverrides,
|
|
451
|
-
),
|
|
452
|
-
organization: getOrganizationsSchema(mysqlTable),
|
|
453
|
-
organizationRelations: getOrganizationsRelationsSchema(mysqlTable),
|
|
454
|
-
organizationMemberships: getOrganizationMembershipsSchema(mysqlTable),
|
|
455
|
-
organizationMembershipRelations: getOrganizationMembershipsRelationsSchema(
|
|
456
|
-
mysqlTable,
|
|
457
|
-
userRelationsOverrides,
|
|
458
|
-
),
|
|
459
|
-
organizationMembershipRoles:
|
|
460
|
-
getOrganizationMembershipRolesSchema(mysqlTable),
|
|
461
|
-
organizationMembershipRolesRelations:
|
|
462
|
-
getOrganizationMembershipRolesRelationsSchema(mysqlTable),
|
|
463
|
-
merchantSubscription: getMerchantSubscriptionSchema(mysqlTable),
|
|
464
|
-
merchantSubscriptionRelations:
|
|
465
|
-
getMerchantSubscriptionRelationsSchema(mysqlTable),
|
|
466
|
-
subscription: getSubscriptionSchema(mysqlTable),
|
|
467
|
-
subscriptionRelations: getSubscriptionRelationsSchema(mysqlTable),
|
|
468
|
-
profiles: getProfilesSchema(mysqlTable),
|
|
469
|
-
profilesRelations: getProfilesRelationsSchema(
|
|
470
|
-
mysqlTable,
|
|
471
|
-
userRelationsOverrides,
|
|
472
|
-
),
|
|
473
|
-
entitlementTypes: getEntitlementTypesSchema(mysqlTable),
|
|
474
|
-
entitlements: getEntitlementsSchema(mysqlTable),
|
|
475
|
-
entitlementsRelations: getEntitlementRelationsSchema(
|
|
476
|
-
mysqlTable,
|
|
477
|
-
userRelationsOverrides,
|
|
478
|
-
),
|
|
479
|
-
} as const
|
|
480
|
-
}
|
|
481
|
-
|
|
482
|
-
export function createTables(mySqlTable: MySqlTableFn) {
|
|
483
|
-
return getCourseBuilderSchema(mySqlTable)
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
export type DefaultSchema = ReturnType<typeof createTables>
|
|
33
|
+
export {
|
|
34
|
+
guid,
|
|
35
|
+
normalizeExpirationDate,
|
|
36
|
+
} from './workflows/content-resource-workflow-utils.js'
|
|
487
37
|
|
|
488
38
|
/**
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
*
|
|
492
|
-
* @
|
|
39
|
+
* Composes the legacy combined MySQL adapter from capability ports and private
|
|
40
|
+
* cross-port workflows.
|
|
41
|
+
*
|
|
42
|
+
* @param client - The Drizzle MySQL client retained on the compatibility adapter.
|
|
43
|
+
* @param tableFn - Optional table factory used to construct one shared schema.
|
|
44
|
+
* @param paymentProvider - Optional payment-provider configuration.
|
|
45
|
+
* @returns The compatibility adapter implementing all capability and Auth.js methods.
|
|
493
46
|
*
|
|
494
|
-
*
|
|
495
|
-
*
|
|
496
|
-
*
|
|
497
|
-
*
|
|
498
|
-
* delete the app copies. Removal criteria: no call sites inside createUser
|
|
499
|
-
* and zero personal-organization-service.ts files under apps/.
|
|
500
|
-
* Do not add new callers of the inline-hook path.
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const adapter = mySqlDrizzleAdapter(client)
|
|
50
|
+
* ```
|
|
501
51
|
*/
|
|
502
|
-
async function provisionPersonalOrganization(
|
|
503
|
-
adapter: AuthJsCourseBuilderAdapter<typeof MySqlDatabase>,
|
|
504
|
-
params: { userId: string; email: string },
|
|
505
|
-
): Promise<void> {
|
|
506
|
-
const { userId, email } = params
|
|
507
|
-
|
|
508
|
-
// creating a personal organization for the user
|
|
509
|
-
// every user gets an organization of their very own
|
|
510
|
-
const personalOrganization = await adapter.createOrganization({
|
|
511
|
-
name: `Personal (${email})`,
|
|
512
|
-
})
|
|
513
|
-
|
|
514
|
-
if (!personalOrganization) {
|
|
515
|
-
throw new Error('Failed to create personal organization')
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
const membership = await adapter.addMemberToOrganization({
|
|
519
|
-
organizationId: personalOrganization.id,
|
|
520
|
-
userId: userId,
|
|
521
|
-
invitedById: userId,
|
|
522
|
-
})
|
|
523
|
-
|
|
524
|
-
if (!membership) {
|
|
525
|
-
throw new Error('Failed to add user to personal organization')
|
|
526
|
-
}
|
|
527
|
-
|
|
528
|
-
await adapter.addRoleForMember({
|
|
529
|
-
organizationId: personalOrganization.id,
|
|
530
|
-
memberId: membership.id,
|
|
531
|
-
role: 'owner',
|
|
532
|
-
})
|
|
533
|
-
}
|
|
534
|
-
|
|
535
52
|
export function mySqlDrizzleAdapter(
|
|
536
53
|
client: InstanceType<typeof MySqlDatabase>,
|
|
537
|
-
tableFn = defaultMySqlTableFn,
|
|
54
|
+
tableFn: MySqlTableFn = defaultMySqlTableFn,
|
|
538
55
|
paymentProvider?: PaymentsProviderConfig,
|
|
539
|
-
):
|
|
540
|
-
const
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
merchantProduct,
|
|
560
|
-
prices,
|
|
561
|
-
products,
|
|
562
|
-
upgradableProducts,
|
|
563
|
-
resourceProgress,
|
|
564
|
-
comments,
|
|
565
|
-
organization: organizationTable,
|
|
566
|
-
organizationMemberships: organizationMembershipTable,
|
|
567
|
-
organizationMembershipRoles: organizationMembershipRoleTable,
|
|
568
|
-
roles: roleTable,
|
|
569
|
-
merchantSubscription: merchantSubscriptionTable,
|
|
570
|
-
subscription: subscriptionTable,
|
|
571
|
-
entitlements: entitlementTable,
|
|
572
|
-
entitlementTypes,
|
|
573
|
-
} = createTables(tableFn)
|
|
574
|
-
|
|
575
|
-
const normalizeLearnerEmail = (email: string) =>
|
|
576
|
-
email.trim().toLowerCase().replace(/\s+/g, '+')
|
|
577
|
-
|
|
578
|
-
const adapter: AuthJsCourseBuilderAdapter<typeof MySqlDatabase> = {
|
|
579
|
-
client,
|
|
580
|
-
async provisionPersonalOrganization(params) {
|
|
581
|
-
await provisionPersonalOrganization(adapter, params)
|
|
582
|
-
},
|
|
583
|
-
async redeemFullPriceCoupon(options) {
|
|
584
|
-
const {
|
|
585
|
-
email: baseEmail,
|
|
586
|
-
couponId,
|
|
587
|
-
productIds,
|
|
588
|
-
currentUserId,
|
|
589
|
-
redeemingProductId,
|
|
590
|
-
} = options
|
|
591
|
-
const email = normalizeLearnerEmail(String(baseEmail))
|
|
592
|
-
|
|
593
|
-
const coupon = await adapter.getCouponWithBulkPurchases(couponId)
|
|
594
|
-
|
|
595
|
-
const productId =
|
|
596
|
-
(coupon && (coupon.restrictedToProductId as string)) ||
|
|
597
|
-
redeemingProductId
|
|
598
|
-
|
|
599
|
-
if (!productId) throw new Error(`unable-to-find-any-product-id`)
|
|
600
|
-
|
|
601
|
-
const couponValidation = validateCoupon(coupon, productIds)
|
|
602
|
-
|
|
603
|
-
if (coupon && couponValidation.isRedeemable) {
|
|
604
|
-
// if the Coupon is the Bulk Coupon of a Bulk Purchase,
|
|
605
|
-
// then a bulk coupon is being redeemed
|
|
606
|
-
const bulkCouponRedemption = Boolean(coupon.maxUses > 1)
|
|
607
|
-
|
|
608
|
-
const { user } = await adapter.findOrCreateUser(email)
|
|
609
|
-
|
|
610
|
-
if (!user) throw new Error(`unable-to-create-user-${email}`)
|
|
611
|
-
|
|
612
|
-
const currentUser = currentUserId
|
|
613
|
-
? await adapter.getUserById(currentUserId)
|
|
614
|
-
: null
|
|
615
|
-
|
|
616
|
-
const redeemingForCurrentUser = currentUser?.id === user.id
|
|
617
|
-
|
|
618
|
-
if (bulkCouponRedemption) {
|
|
619
|
-
const existingValidRedemption =
|
|
620
|
-
await client.query.purchases.findFirst({
|
|
621
|
-
where: and(
|
|
622
|
-
eq(purchaseTable.redeemedBulkCouponId, coupon.id),
|
|
623
|
-
eq(purchaseTable.userId, user.id),
|
|
624
|
-
eq(purchaseTable.status, 'Valid'),
|
|
625
|
-
),
|
|
626
|
-
})
|
|
627
|
-
|
|
628
|
-
if (existingValidRedemption) {
|
|
629
|
-
logger.info('adapter.coupon.redeem.duplicate_team_seat', {
|
|
630
|
-
couponId: coupon.id,
|
|
631
|
-
currentUserId,
|
|
632
|
-
email,
|
|
633
|
-
productId,
|
|
634
|
-
purchaseId: existingValidRedemption.id,
|
|
635
|
-
userId: user.id,
|
|
636
|
-
})
|
|
637
|
-
return {
|
|
638
|
-
error: {
|
|
639
|
-
message: 'team-seat-redemption-already-claimed',
|
|
640
|
-
},
|
|
641
|
-
redeemingForCurrentUser,
|
|
642
|
-
purchase: null,
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
}
|
|
646
|
-
|
|
647
|
-
// To prevent double-purchasing, check if this user already has a
|
|
648
|
-
// Purchase record for this product that is valid and wasn't a bulk
|
|
649
|
-
// coupon purchase.
|
|
650
|
-
const existingPurchases =
|
|
651
|
-
await adapter.getExistingNonBulkValidPurchasesOfProduct({
|
|
652
|
-
userId: user.id,
|
|
653
|
-
productId,
|
|
654
|
-
})
|
|
655
|
-
|
|
656
|
-
if (existingPurchases.length > 0) {
|
|
657
|
-
const errorMessage = `already-purchased-${email}`
|
|
658
|
-
logger.info('adapter.coupon.redeem.already_purchased', {
|
|
659
|
-
couponId: coupon.id,
|
|
660
|
-
currentUserId,
|
|
661
|
-
email,
|
|
662
|
-
productId,
|
|
663
|
-
userId: user.id,
|
|
664
|
-
})
|
|
665
|
-
return {
|
|
666
|
-
error: {
|
|
667
|
-
message: errorMessage,
|
|
668
|
-
},
|
|
669
|
-
redeemingForCurrentUser,
|
|
670
|
-
purchase: null,
|
|
671
|
-
}
|
|
672
|
-
throw new Error(errorMessage)
|
|
673
|
-
}
|
|
674
|
-
|
|
675
|
-
const purchaseId = `purchase-${v4()}`
|
|
676
|
-
const userMemberships = await adapter.getMembershipsForUser(user.id)
|
|
677
|
-
const organizationId =
|
|
678
|
-
coupon.organizationId ||
|
|
679
|
-
userMemberships.find((m) => m.organization.name?.includes(user.email))
|
|
680
|
-
?.organizationId // safer way to make sure we are using personal organization
|
|
681
|
-
|
|
682
|
-
await adapter.createPurchase({
|
|
683
|
-
id: purchaseId,
|
|
684
|
-
userId: user.id,
|
|
685
|
-
couponId: bulkCouponRedemption ? null : coupon.id,
|
|
686
|
-
redeemedBulkCouponId: bulkCouponRedemption ? coupon.id : null,
|
|
687
|
-
productId,
|
|
688
|
-
totalAmount: '0',
|
|
689
|
-
organizationId,
|
|
690
|
-
metadata: {
|
|
691
|
-
couponUsedId: bulkCouponRedemption ? null : coupon.id,
|
|
692
|
-
},
|
|
693
|
-
})
|
|
694
|
-
|
|
695
|
-
const newPurchase = await adapter.getPurchase(purchaseId)
|
|
696
|
-
|
|
697
|
-
await adapter.incrementCouponUsedCount(coupon.id)
|
|
698
|
-
|
|
699
|
-
await adapter.createPurchaseTransfer({
|
|
700
|
-
sourceUserId: user.id,
|
|
701
|
-
purchaseId: purchaseId,
|
|
702
|
-
expiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7),
|
|
703
|
-
})
|
|
704
|
-
|
|
705
|
-
return { purchase: newPurchase, redeemingForCurrentUser }
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
return null
|
|
709
|
-
},
|
|
710
|
-
createPurchaseTransfer: async (options) => {
|
|
711
|
-
const id = `put_${v4()}`
|
|
712
|
-
await client.insert(purchaseUserTransfer).values({
|
|
713
|
-
id,
|
|
714
|
-
purchaseId: options.purchaseId,
|
|
715
|
-
sourceUserId: options.sourceUserId,
|
|
716
|
-
expiresAt: options.expiresAt,
|
|
717
|
-
})
|
|
718
|
-
},
|
|
719
|
-
incrementCouponUsedCount: async (couponId) => {
|
|
720
|
-
await client
|
|
721
|
-
.update(coupon)
|
|
722
|
-
.set({ usedCount: sql`${coupon.usedCount} + 1` })
|
|
723
|
-
.where(eq(coupon.id, couponId))
|
|
724
|
-
},
|
|
725
|
-
getExistingNonBulkValidPurchasesOfProduct: async ({
|
|
726
|
-
userId,
|
|
727
|
-
productId,
|
|
728
|
-
}) => {
|
|
729
|
-
const existingPurchases = await client.query.purchases.findMany({
|
|
730
|
-
where: and(
|
|
731
|
-
eq(purchaseTable.userId, userId),
|
|
732
|
-
productId ? eq(purchaseTable.productId, productId) : undefined,
|
|
733
|
-
eq(purchaseTable.status, 'Valid'),
|
|
734
|
-
isNull(purchaseTable.bulkCouponId),
|
|
735
|
-
),
|
|
736
|
-
})
|
|
737
|
-
|
|
738
|
-
return z.array(purchaseSchema).parse(existingPurchases)
|
|
739
|
-
},
|
|
740
|
-
createMerchantCustomer: async (options) => {
|
|
741
|
-
await client.insert(merchantCustomer).values({
|
|
742
|
-
id: `mc_${v4()}`,
|
|
743
|
-
identifier: options.identifier,
|
|
744
|
-
merchantAccountId: options.merchantAccountId,
|
|
745
|
-
userId: options.userId,
|
|
746
|
-
status: 1,
|
|
747
|
-
})
|
|
748
|
-
return merchantCustomerSchema.parse(
|
|
749
|
-
await client.query.merchantCustomer.findFirst({
|
|
750
|
-
where: eq(merchantCustomer.identifier, options.identifier),
|
|
751
|
-
}),
|
|
752
|
-
)
|
|
753
|
-
},
|
|
754
|
-
createMerchantCoupon: async (options) => {
|
|
755
|
-
await client.insert(merchantCoupon).values({
|
|
756
|
-
id: `mcoupon_${v4()}`,
|
|
757
|
-
identifier: options.identifier,
|
|
758
|
-
merchantAccountId: options.merchantAccountId,
|
|
759
|
-
type: options.type,
|
|
760
|
-
amountDiscount: Math.floor(options.amountDiscount),
|
|
761
|
-
status: 1,
|
|
762
|
-
})
|
|
763
|
-
return merchantCouponSchema.parse(
|
|
764
|
-
await client.query.merchantCoupon.findFirst({
|
|
765
|
-
where: eq(merchantCoupon.identifier, options.identifier),
|
|
766
|
-
}),
|
|
767
|
-
)
|
|
768
|
-
},
|
|
769
|
-
getMerchantAccount: async (options) => {
|
|
770
|
-
return merchantAccountSchema.parse(
|
|
771
|
-
await client.query.merchantAccount.findFirst({
|
|
772
|
-
where: eq(merchantAccount.label, options.provider),
|
|
773
|
-
}),
|
|
774
|
-
)
|
|
775
|
-
},
|
|
776
|
-
getMerchantPriceForProductId: async (productId) => {
|
|
777
|
-
const merchantPriceData = await client.query.merchantPrice.findFirst({
|
|
778
|
-
where: and(
|
|
779
|
-
eq(merchantPrice.merchantProductId, productId),
|
|
780
|
-
eq(merchantPrice.status, 1),
|
|
781
|
-
),
|
|
782
|
-
})
|
|
783
|
-
|
|
784
|
-
const parsedMerchantPrice =
|
|
785
|
-
merchantPriceSchema.safeParse(merchantPriceData)
|
|
786
|
-
|
|
787
|
-
if (!parsedMerchantPrice.success) {
|
|
788
|
-
logger.error(parsedMerchantPrice.error, {
|
|
789
|
-
event: 'adapter.merchantPrice.parse_failed',
|
|
790
|
-
productId,
|
|
791
|
-
})
|
|
792
|
-
return null
|
|
793
|
-
}
|
|
794
|
-
|
|
795
|
-
return parsedMerchantPrice.data
|
|
796
|
-
},
|
|
797
|
-
getMerchantProductForProductId: async (productId) => {
|
|
798
|
-
const merchantProductData = await client.query.merchantProduct.findFirst({
|
|
799
|
-
where: eq(merchantProduct.productId, productId),
|
|
800
|
-
})
|
|
801
|
-
|
|
802
|
-
if (!merchantProductData) return null
|
|
803
|
-
return merchantProductSchema.parse(merchantProductData)
|
|
804
|
-
},
|
|
805
|
-
getMerchantCustomerForUserId: async (userId) => {
|
|
806
|
-
const merchantCustomerData =
|
|
807
|
-
await client.query.merchantCustomer.findFirst({
|
|
808
|
-
where: eq(merchantCustomer.userId, userId),
|
|
809
|
-
})
|
|
810
|
-
|
|
811
|
-
if (!merchantCustomerData) return null
|
|
812
|
-
return merchantCustomerSchema.parse(merchantCustomerData)
|
|
813
|
-
},
|
|
814
|
-
getUpgradableProducts: async (options) => {
|
|
815
|
-
const { upgradableFromId, upgradableToId } = options
|
|
816
|
-
return z.array(upgradableProductSchema).parse(
|
|
817
|
-
await client.query.upgradableProducts.findMany({
|
|
818
|
-
where: and(
|
|
819
|
-
eq(upgradableProducts.upgradableFromId, upgradableFromId),
|
|
820
|
-
eq(upgradableProducts.upgradableToId, upgradableToId),
|
|
821
|
-
),
|
|
822
|
-
}),
|
|
823
|
-
)
|
|
824
|
-
},
|
|
825
|
-
async availableUpgradesForProduct(
|
|
826
|
-
purchases: any,
|
|
827
|
-
productId: string,
|
|
828
|
-
): Promise<any[]> {
|
|
829
|
-
const previousPurchaseProductIds = purchases.map(
|
|
830
|
-
({ productId }: Purchase) => productId,
|
|
831
|
-
)
|
|
832
|
-
|
|
833
|
-
if (previousPurchaseProductIds.length > 0) {
|
|
834
|
-
return client.query.upgradableProducts.findMany({
|
|
835
|
-
where: and(
|
|
836
|
-
eq(upgradableProducts.upgradableToId, productId),
|
|
837
|
-
inArray(
|
|
838
|
-
upgradableProducts.upgradableFromId,
|
|
839
|
-
previousPurchaseProductIds,
|
|
840
|
-
),
|
|
841
|
-
),
|
|
842
|
-
})
|
|
843
|
-
}
|
|
844
|
-
return []
|
|
845
|
-
},
|
|
846
|
-
clearLessonProgressForUser(options: {
|
|
847
|
-
userId: string
|
|
848
|
-
lessons: { id: string; slug: string }[]
|
|
849
|
-
}): Promise<void> {
|
|
850
|
-
throw new Error('clearLessonProgressForUser Method not implemented.')
|
|
851
|
-
},
|
|
852
|
-
async completeLessonProgressForUser(options: {
|
|
853
|
-
userId: string
|
|
854
|
-
lessonId?: string
|
|
855
|
-
}): Promise<ResourceProgress | null> {
|
|
856
|
-
if (!options.lessonId) {
|
|
857
|
-
throw new Error('No lessonId provided')
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
let lessonProgress = await client.query.resourceProgress.findFirst({
|
|
861
|
-
where: and(
|
|
862
|
-
eq(resourceProgress.userId, options.userId),
|
|
863
|
-
eq(resourceProgress.resourceId, options.lessonId),
|
|
864
|
-
),
|
|
865
|
-
})
|
|
866
|
-
|
|
867
|
-
const now = new Date()
|
|
868
|
-
|
|
869
|
-
if (lessonProgress) {
|
|
870
|
-
if (!lessonProgress.completedAt) {
|
|
871
|
-
await client
|
|
872
|
-
.update(resourceProgress)
|
|
873
|
-
.set({
|
|
874
|
-
completedAt: now,
|
|
875
|
-
updatedAt: now,
|
|
876
|
-
})
|
|
877
|
-
.where(
|
|
878
|
-
and(
|
|
879
|
-
eq(resourceProgress.userId, options.userId),
|
|
880
|
-
eq(resourceProgress.resourceId, options.lessonId),
|
|
881
|
-
),
|
|
882
|
-
)
|
|
883
|
-
}
|
|
884
|
-
} else {
|
|
885
|
-
await client.insert(resourceProgress).values({
|
|
886
|
-
userId: options.userId,
|
|
887
|
-
resourceId: options.lessonId,
|
|
888
|
-
completedAt: now,
|
|
889
|
-
updatedAt: now,
|
|
890
|
-
})
|
|
891
|
-
}
|
|
892
|
-
lessonProgress = await client.query.resourceProgress.findFirst({
|
|
893
|
-
where: and(
|
|
894
|
-
eq(resourceProgress.userId, options.userId),
|
|
895
|
-
eq(resourceProgress.resourceId, options.lessonId),
|
|
896
|
-
),
|
|
897
|
-
})
|
|
898
|
-
const parsedLessonProgress =
|
|
899
|
-
resourceProgressSchema.safeParse(lessonProgress)
|
|
900
|
-
|
|
901
|
-
if (!parsedLessonProgress.success) {
|
|
902
|
-
logger.error(parsedLessonProgress.error, {
|
|
903
|
-
event: 'adapter.lessonProgress.parse_failed',
|
|
904
|
-
lessonId: options.lessonId,
|
|
905
|
-
userId: options.userId,
|
|
906
|
-
})
|
|
907
|
-
return null
|
|
908
|
-
}
|
|
909
|
-
|
|
910
|
-
return parsedLessonProgress.data
|
|
911
|
-
},
|
|
912
|
-
async couponForIdOrCode(options: {
|
|
913
|
-
code?: string
|
|
914
|
-
couponId?: string
|
|
915
|
-
}): Promise<(Coupon & { merchantCoupon: MerchantCoupon }) | null> {
|
|
916
|
-
if (!options.couponId && !options.code) return null
|
|
917
|
-
const couponForIdOrCode = await client.query.coupon.findFirst({
|
|
918
|
-
where: or(
|
|
919
|
-
and(
|
|
920
|
-
or(
|
|
921
|
-
options.code ? eq(coupon.code, options.code) : undefined,
|
|
922
|
-
options.couponId ? eq(coupon.id, options.couponId) : undefined,
|
|
923
|
-
),
|
|
924
|
-
gte(coupon.expires, new Date()),
|
|
925
|
-
),
|
|
926
|
-
and(
|
|
927
|
-
or(
|
|
928
|
-
options.code ? eq(coupon.code, options.code) : undefined,
|
|
929
|
-
options.couponId ? eq(coupon.id, options.couponId) : undefined,
|
|
930
|
-
),
|
|
931
|
-
isNull(coupon.expires),
|
|
932
|
-
),
|
|
933
|
-
),
|
|
934
|
-
with: {
|
|
935
|
-
merchantCoupon: true,
|
|
936
|
-
},
|
|
937
|
-
})
|
|
938
|
-
|
|
939
|
-
if (!couponForIdOrCode) return null
|
|
940
|
-
|
|
941
|
-
const parsedCoupon = couponSchema
|
|
942
|
-
.extend({
|
|
943
|
-
merchantCoupon: merchantCouponSchema,
|
|
944
|
-
})
|
|
945
|
-
.safeParse(couponForIdOrCode)
|
|
946
|
-
|
|
947
|
-
if (!parsedCoupon.success) {
|
|
948
|
-
logger.error(parsedCoupon.error, {
|
|
949
|
-
code: options.code,
|
|
950
|
-
couponId: options.couponId,
|
|
951
|
-
event: 'adapter.coupon.parse_failed',
|
|
952
|
-
})
|
|
953
|
-
return null
|
|
954
|
-
}
|
|
955
|
-
|
|
956
|
-
return parsedCoupon.data
|
|
957
|
-
},
|
|
958
|
-
async createMerchantSession(options): Promise<MerchantSession> {
|
|
959
|
-
const id = `ms_${v4()}`
|
|
960
|
-
await client.insert(merchantSession).values({
|
|
961
|
-
id,
|
|
962
|
-
identifier: options.identifier,
|
|
963
|
-
merchantAccountId: options.merchantAccountId,
|
|
964
|
-
...(options.organizationId
|
|
965
|
-
? { organizationId: options.organizationId }
|
|
966
|
-
: {}),
|
|
967
|
-
})
|
|
968
|
-
|
|
969
|
-
return MerchantSessionSchema.parse(
|
|
970
|
-
await client.query.merchantSession.findFirst({
|
|
971
|
-
where: eq(merchantSession.id, id),
|
|
972
|
-
}),
|
|
973
|
-
)
|
|
974
|
-
},
|
|
975
|
-
async createMerchantChargeAndPurchase(options): Promise<Purchase> {
|
|
976
|
-
const purchaseId = await client.transaction(async (trx) => {
|
|
977
|
-
try {
|
|
978
|
-
const {
|
|
979
|
-
userId,
|
|
980
|
-
stripeChargeId,
|
|
981
|
-
stripeCouponId,
|
|
982
|
-
merchantAccountId,
|
|
983
|
-
merchantProductId,
|
|
984
|
-
merchantCustomerId,
|
|
985
|
-
productId,
|
|
986
|
-
stripeChargeAmount,
|
|
987
|
-
quantity = 1,
|
|
988
|
-
checkoutSessionId,
|
|
989
|
-
appliedPPPStripeCouponId,
|
|
990
|
-
upgradedFromPurchaseId,
|
|
991
|
-
country,
|
|
992
|
-
usedCouponId,
|
|
993
|
-
organizationId,
|
|
994
|
-
attributionSnapshot,
|
|
995
|
-
} = options
|
|
996
|
-
|
|
997
|
-
const existingMerchantCharge = merchantChargeSchema.nullable().parse(
|
|
998
|
-
(await client.query.merchantCharge.findFirst({
|
|
999
|
-
where: eq(merchantCharge.identifier, stripeChargeId),
|
|
1000
|
-
})) || null,
|
|
1001
|
-
)
|
|
1002
|
-
|
|
1003
|
-
const existingPurchaseForCharge = existingMerchantCharge
|
|
1004
|
-
? await client.query.purchases.findFirst({
|
|
1005
|
-
where: eq(
|
|
1006
|
-
purchaseTable.merchantChargeId,
|
|
1007
|
-
existingMerchantCharge.id,
|
|
1008
|
-
),
|
|
1009
|
-
with: {
|
|
1010
|
-
user: true,
|
|
1011
|
-
product: true,
|
|
1012
|
-
bulkCoupon: true,
|
|
1013
|
-
},
|
|
1014
|
-
})
|
|
1015
|
-
: null
|
|
1016
|
-
|
|
1017
|
-
if (existingPurchaseForCharge) {
|
|
1018
|
-
return existingPurchaseForCharge.id
|
|
1019
|
-
}
|
|
1020
|
-
|
|
1021
|
-
const merchantChargeId = `mc_${v4()}`
|
|
1022
|
-
const purchaseId = `purch_${v4()}`
|
|
1023
|
-
|
|
1024
|
-
const newMerchantCharge = await client.insert(merchantCharge).values({
|
|
1025
|
-
id: merchantChargeId,
|
|
1026
|
-
userId,
|
|
1027
|
-
identifier: stripeChargeId,
|
|
1028
|
-
merchantAccountId,
|
|
1029
|
-
merchantProductId,
|
|
1030
|
-
merchantCustomerId,
|
|
1031
|
-
})
|
|
1032
|
-
|
|
1033
|
-
const existingPurchase = purchaseSchema.nullable().parse(
|
|
1034
|
-
(await client.query.purchases.findFirst({
|
|
1035
|
-
where: and(
|
|
1036
|
-
eq(purchaseTable.productId, productId),
|
|
1037
|
-
eq(purchaseTable.userId, userId),
|
|
1038
|
-
inArray(purchaseTable.status, ['Valid', 'Restricted']),
|
|
1039
|
-
),
|
|
1040
|
-
})) || null,
|
|
1041
|
-
)
|
|
1042
|
-
|
|
1043
|
-
const existingBulkCoupon = couponSchema.nullable().parse(
|
|
1044
|
-
await client
|
|
1045
|
-
.select()
|
|
1046
|
-
.from(coupon)
|
|
1047
|
-
.leftJoin(
|
|
1048
|
-
purchaseTable,
|
|
1049
|
-
and(
|
|
1050
|
-
eq(coupon.id, purchaseTable.bulkCouponId),
|
|
1051
|
-
eq(purchaseTable.userId, userId),
|
|
1052
|
-
),
|
|
1053
|
-
)
|
|
1054
|
-
.where(
|
|
1055
|
-
and(
|
|
1056
|
-
eq(coupon.restrictedToProductId, productId),
|
|
1057
|
-
eq(purchaseTable.userId, userId),
|
|
1058
|
-
eq(purchaseTable.status, 'Valid'),
|
|
1059
|
-
),
|
|
1060
|
-
)
|
|
1061
|
-
.then((res) => {
|
|
1062
|
-
return res[0]?.Coupon ?? null
|
|
1063
|
-
}),
|
|
1064
|
-
)
|
|
1065
|
-
|
|
1066
|
-
const isBulkPurchase =
|
|
1067
|
-
quantity > 1 || Boolean(existingBulkCoupon) || Boolean(options.bulk)
|
|
1068
|
-
|
|
1069
|
-
let bulkCouponId: string | null = null
|
|
1070
|
-
let couponToUpdate = null
|
|
1071
|
-
const purchaseBenefitsTransport =
|
|
1072
|
-
options.purchaseBenefitsTransport?.trim() || undefined
|
|
1073
|
-
const purchaseBenefitExpansion = purchaseBenefitsTransport
|
|
1074
|
-
? parseAndExpandPurchaseBenefitTransport({
|
|
1075
|
-
transport: purchaseBenefitsTransport,
|
|
1076
|
-
appliesTo: isBulkPurchase ? 'redeemed_team_seat' : 'buyer',
|
|
1077
|
-
})
|
|
1078
|
-
: null
|
|
1079
|
-
|
|
1080
|
-
if (purchaseBenefitExpansion && !purchaseBenefitExpansion.success) {
|
|
1081
|
-
logger.info('purchase_benefits.transport.needs_review', {
|
|
1082
|
-
checkoutSessionId,
|
|
1083
|
-
event: 'purchase_benefits.transport.needs_review',
|
|
1084
|
-
metadataKey: PURCHASE_BENEFIT_METADATA_KEY,
|
|
1085
|
-
productId,
|
|
1086
|
-
stripeChargeId,
|
|
1087
|
-
userId,
|
|
1088
|
-
errors: purchaseBenefitExpansion.review.errors,
|
|
1089
|
-
})
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
if (isBulkPurchase) {
|
|
1093
|
-
bulkCouponId =
|
|
1094
|
-
existingBulkCoupon !== null ? existingBulkCoupon.id : v4()
|
|
1095
|
-
|
|
1096
|
-
if (existingBulkCoupon !== null) {
|
|
1097
|
-
const existingCouponFields = existingBulkCoupon.fields ?? {}
|
|
1098
|
-
couponToUpdate = await client
|
|
1099
|
-
.update(coupon)
|
|
1100
|
-
.set({
|
|
1101
|
-
maxUses: (existingBulkCoupon?.maxUses || 0) + quantity,
|
|
1102
|
-
fields: mergePurchaseBenefitFields(
|
|
1103
|
-
existingCouponFields,
|
|
1104
|
-
purchaseBenefitExpansion,
|
|
1105
|
-
),
|
|
1106
|
-
...(organizationId ? { organizationId } : {}),
|
|
1107
|
-
})
|
|
1108
|
-
.where(eq(coupon.id, bulkCouponId as string))
|
|
1109
|
-
} else {
|
|
1110
|
-
// Try to find merchant coupon by stripeCouponId first
|
|
1111
|
-
let merchantCouponToUse = stripeCouponId
|
|
1112
|
-
? merchantCouponSchema.nullable().parse(
|
|
1113
|
-
(await client.query.merchantCoupon.findFirst({
|
|
1114
|
-
where: eq(merchantCoupon.identifier, stripeCouponId),
|
|
1115
|
-
})) || null,
|
|
1116
|
-
)
|
|
1117
|
-
: null
|
|
1118
|
-
|
|
1119
|
-
// Fallback: If not found, try to find via usedCouponId from checkout session metadata
|
|
1120
|
-
if (!merchantCouponToUse && usedCouponId) {
|
|
1121
|
-
// First get the coupon record to find its merchantCouponId
|
|
1122
|
-
const usedCoupon = couponSchema.nullable().parse(
|
|
1123
|
-
(await client.query.coupon.findFirst({
|
|
1124
|
-
where: eq(coupon.id, usedCouponId),
|
|
1125
|
-
})) || null,
|
|
1126
|
-
)
|
|
1127
|
-
|
|
1128
|
-
// Then get the merchant coupon
|
|
1129
|
-
if (usedCoupon?.merchantCouponId) {
|
|
1130
|
-
merchantCouponToUse = merchantCouponSchema.nullable().parse(
|
|
1131
|
-
(await client.query.merchantCoupon.findFirst({
|
|
1132
|
-
where: eq(merchantCoupon.id, usedCoupon.merchantCouponId),
|
|
1133
|
-
})) || null,
|
|
1134
|
-
)
|
|
1135
|
-
}
|
|
1136
|
-
}
|
|
1137
|
-
|
|
1138
|
-
const bulkCouponValues = {
|
|
1139
|
-
id: bulkCouponId as string,
|
|
1140
|
-
percentageDiscount: '1.0',
|
|
1141
|
-
restrictedToProductId: productId,
|
|
1142
|
-
maxUses: quantity,
|
|
1143
|
-
status: 1,
|
|
1144
|
-
fields: mergePurchaseBenefitFields(
|
|
1145
|
-
{},
|
|
1146
|
-
purchaseBenefitExpansion,
|
|
1147
|
-
),
|
|
1148
|
-
...(organizationId ? { organizationId } : {}),
|
|
1149
|
-
...(merchantCouponToUse
|
|
1150
|
-
? {
|
|
1151
|
-
merchantCouponId: merchantCouponToUse.id,
|
|
1152
|
-
}
|
|
1153
|
-
: {}),
|
|
1154
|
-
}
|
|
1155
|
-
|
|
1156
|
-
couponToUpdate = await client
|
|
1157
|
-
.insert(coupon)
|
|
1158
|
-
.values(bulkCouponValues)
|
|
1159
|
-
}
|
|
1160
|
-
}
|
|
1161
|
-
|
|
1162
|
-
// create a new merchant session
|
|
1163
|
-
const merchantSessionId = `ms_${v4()}`
|
|
1164
|
-
|
|
1165
|
-
await client.insert(merchantSession).values({
|
|
1166
|
-
id: merchantSessionId,
|
|
1167
|
-
identifier: checkoutSessionId,
|
|
1168
|
-
merchantAccountId,
|
|
1169
|
-
})
|
|
1170
|
-
|
|
1171
|
-
const merchantCouponUsed = stripeCouponId
|
|
1172
|
-
? await client.query.merchantCoupon.findFirst({
|
|
1173
|
-
where: eq(merchantCoupon.identifier, stripeCouponId),
|
|
1174
|
-
})
|
|
1175
|
-
: null
|
|
1176
|
-
|
|
1177
|
-
const pppMerchantCoupon = appliedPPPStripeCouponId
|
|
1178
|
-
? await client.query.merchantCoupon.findFirst({
|
|
1179
|
-
where: and(
|
|
1180
|
-
eq(merchantCoupon.identifier, appliedPPPStripeCouponId),
|
|
1181
|
-
eq(merchantCoupon.type, 'ppp'),
|
|
1182
|
-
),
|
|
1183
|
-
})
|
|
1184
|
-
: null
|
|
1185
|
-
|
|
1186
|
-
const newPurchaseStatus =
|
|
1187
|
-
merchantCouponUsed?.type === 'ppp' || pppMerchantCoupon
|
|
1188
|
-
? 'Restricted'
|
|
1189
|
-
: 'Valid'
|
|
1190
|
-
|
|
1191
|
-
await client.insert(purchaseTable).values({
|
|
1192
|
-
id: purchaseId,
|
|
1193
|
-
status: newPurchaseStatus,
|
|
1194
|
-
userId,
|
|
1195
|
-
productId,
|
|
1196
|
-
merchantChargeId,
|
|
1197
|
-
totalAmount: (stripeChargeAmount / 100).toFixed(),
|
|
1198
|
-
bulkCouponId,
|
|
1199
|
-
merchantSessionId,
|
|
1200
|
-
country,
|
|
1201
|
-
fields: {
|
|
1202
|
-
...(!isBulkPurchase
|
|
1203
|
-
? mergePurchaseBenefitFields({}, purchaseBenefitExpansion)
|
|
1204
|
-
: {}),
|
|
1205
|
-
...(attributionSnapshot
|
|
1206
|
-
? { attribution: attributionSnapshot }
|
|
1207
|
-
: {}),
|
|
1208
|
-
},
|
|
1209
|
-
upgradedFromId: upgradedFromPurchaseId || null,
|
|
1210
|
-
couponId: usedCouponId || null,
|
|
1211
|
-
...(organizationId ? { organizationId } : {}),
|
|
1212
|
-
})
|
|
1213
|
-
|
|
1214
|
-
const oneWeekInMilliseconds = 1000 * 60 * 60 * 24 * 7
|
|
1215
|
-
|
|
1216
|
-
await client.insert(purchaseUserTransfer).values({
|
|
1217
|
-
id: `put_${v4()}`,
|
|
1218
|
-
purchaseId: purchaseId as string,
|
|
1219
|
-
expiresAt: existingPurchase
|
|
1220
|
-
? new Date()
|
|
1221
|
-
: new Date(Date.now() + oneWeekInMilliseconds),
|
|
1222
|
-
sourceUserId: userId,
|
|
1223
|
-
...(organizationId ? { organizationId } : {}),
|
|
1224
|
-
})
|
|
1225
|
-
|
|
1226
|
-
// const result = await Promise.all([
|
|
1227
|
-
// newMerchantCharge,
|
|
1228
|
-
// newPurchase,
|
|
1229
|
-
// newPurchaseUserTransfer,
|
|
1230
|
-
// newMerchantSession,
|
|
1231
|
-
// ...(couponToUpdate ? [couponToUpdate] : []),
|
|
1232
|
-
// ])
|
|
1233
|
-
//
|
|
1234
|
-
// console.log('result', { result })
|
|
1235
|
-
|
|
1236
|
-
return purchaseId
|
|
1237
|
-
} catch (error) {
|
|
1238
|
-
logger.error(toError(error), {
|
|
1239
|
-
checkoutSessionId: options.checkoutSessionId,
|
|
1240
|
-
event: 'adapter.purchase.create.failed',
|
|
1241
|
-
organizationId: options.organizationId,
|
|
1242
|
-
productId: options.productId,
|
|
1243
|
-
stripeChargeId: options.stripeChargeId,
|
|
1244
|
-
userId: options.userId,
|
|
1245
|
-
})
|
|
1246
|
-
trx.rollback()
|
|
1247
|
-
throw error
|
|
1248
|
-
}
|
|
1249
|
-
})
|
|
1250
|
-
|
|
1251
|
-
const parsedPurchase = purchaseSchema.safeParse(
|
|
1252
|
-
await client.query.purchases.findFirst({
|
|
1253
|
-
where: eq(purchaseTable.id, purchaseId as string),
|
|
1254
|
-
}),
|
|
1255
|
-
)
|
|
1256
|
-
|
|
1257
|
-
if (!parsedPurchase.success) {
|
|
1258
|
-
logger.error(parsedPurchase.error, {
|
|
1259
|
-
event: 'adapter.purchase.parse_failed',
|
|
1260
|
-
productId: options.productId,
|
|
1261
|
-
purchaseId: purchaseId as string,
|
|
1262
|
-
userId: options.userId,
|
|
1263
|
-
})
|
|
1264
|
-
throw new Error('Error parsing purchase')
|
|
1265
|
-
}
|
|
1266
|
-
|
|
1267
|
-
return parsedPurchase.data
|
|
1268
|
-
},
|
|
1269
|
-
async findOrCreateMerchantCustomer(options: {
|
|
1270
|
-
user: User
|
|
1271
|
-
identifier: string
|
|
1272
|
-
merchantAccountId: string
|
|
1273
|
-
}): Promise<MerchantCustomer | null> {
|
|
1274
|
-
const merchantCustomer = merchantCustomerSchema
|
|
1275
|
-
.nullable()
|
|
1276
|
-
.optional()
|
|
1277
|
-
.parse(
|
|
1278
|
-
await client.query.merchantCustomer.findFirst({
|
|
1279
|
-
where: (merchantCustomer, { eq }) =>
|
|
1280
|
-
eq(merchantCustomer.identifier, options.identifier),
|
|
1281
|
-
}),
|
|
1282
|
-
)
|
|
1283
|
-
|
|
1284
|
-
if (merchantCustomer) {
|
|
1285
|
-
return merchantCustomer
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
return await adapter.createMerchantCustomer({
|
|
1289
|
-
identifier: options.identifier,
|
|
1290
|
-
merchantAccountId: options.merchantAccountId,
|
|
1291
|
-
userId: options.user.id,
|
|
1292
|
-
})
|
|
1293
|
-
},
|
|
1294
|
-
async createMerchantEvent(options: {
|
|
1295
|
-
merchantAccountId: string
|
|
1296
|
-
identifier: string
|
|
1297
|
-
payload: Record<string, any>
|
|
1298
|
-
}): Promise<MerchantEvents> {
|
|
1299
|
-
const eventId = `me_${v4()}`
|
|
1300
|
-
|
|
1301
|
-
await client.insert(merchantEvents).values({
|
|
1302
|
-
id: eventId,
|
|
1303
|
-
merchantAccountId: options.merchantAccountId,
|
|
1304
|
-
identifier: options.identifier,
|
|
1305
|
-
payload: options.payload,
|
|
1306
|
-
})
|
|
1307
|
-
|
|
1308
|
-
const createdEvent = await client.query.merchantEvents.findFirst({
|
|
1309
|
-
where: eq(merchantEvents.id, eventId),
|
|
1310
|
-
})
|
|
1311
|
-
|
|
1312
|
-
return merchantEventsSchema.parse(createdEvent)
|
|
1313
|
-
},
|
|
1314
|
-
async getMerchantEventByIdentifier(
|
|
1315
|
-
identifier: string,
|
|
1316
|
-
): Promise<MerchantEvents | null> {
|
|
1317
|
-
const event = await client.query.merchantEvents.findFirst({
|
|
1318
|
-
where: eq(merchantEvents.identifier, identifier),
|
|
1319
|
-
})
|
|
1320
|
-
|
|
1321
|
-
return event ? merchantEventsSchema.parse(event) : null
|
|
1322
|
-
},
|
|
1323
|
-
async getMerchantEventsByAccount(
|
|
1324
|
-
merchantAccountId: string,
|
|
1325
|
-
): Promise<MerchantEvents[]> {
|
|
1326
|
-
const events = await client.query.merchantEvents.findMany({
|
|
1327
|
-
where: eq(merchantEvents.merchantAccountId, merchantAccountId),
|
|
1328
|
-
orderBy: [desc(merchantEvents.createdAt)],
|
|
1329
|
-
})
|
|
1330
|
-
|
|
1331
|
-
return events.map((event) => merchantEventsSchema.parse(event))
|
|
1332
|
-
},
|
|
1333
|
-
async findOrCreateUser(
|
|
1334
|
-
email: string,
|
|
1335
|
-
name?: string | null,
|
|
1336
|
-
): Promise<{
|
|
1337
|
-
user: User
|
|
1338
|
-
isNewUser: boolean
|
|
1339
|
-
}> {
|
|
1340
|
-
const user = await adapter.getUserByEmail?.(email)
|
|
1341
|
-
|
|
1342
|
-
if (!user) {
|
|
1343
|
-
const newUser = await adapter.createUser?.({
|
|
1344
|
-
id: `u_${v4()}`,
|
|
1345
|
-
email,
|
|
1346
|
-
name,
|
|
1347
|
-
emailVerified: null,
|
|
1348
|
-
})
|
|
1349
|
-
if (!newUser) {
|
|
1350
|
-
throw new Error('Could not create user')
|
|
1351
|
-
}
|
|
1352
|
-
logger.info('adapter.user.created', {
|
|
1353
|
-
email,
|
|
1354
|
-
userId: newUser.id,
|
|
1355
|
-
})
|
|
1356
|
-
return {
|
|
1357
|
-
user: newUser as User,
|
|
1358
|
-
isNewUser: true,
|
|
1359
|
-
}
|
|
1360
|
-
}
|
|
1361
|
-
|
|
1362
|
-
return {
|
|
1363
|
-
user: user as User,
|
|
1364
|
-
isNewUser: false,
|
|
1365
|
-
}
|
|
1366
|
-
},
|
|
1367
|
-
async getCoupon(couponIdOrCode: string): Promise<Coupon | null> {
|
|
1368
|
-
const loadedCoupon =
|
|
1369
|
-
(await client.query.coupon.findFirst({
|
|
1370
|
-
where: or(
|
|
1371
|
-
eq(coupon.id, couponIdOrCode),
|
|
1372
|
-
eq(coupon.code, couponIdOrCode),
|
|
1373
|
-
),
|
|
1374
|
-
})) || null
|
|
1375
|
-
|
|
1376
|
-
logger.debug('getCoupon', {
|
|
1377
|
-
couponIdOrCode,
|
|
1378
|
-
found: !!loadedCoupon,
|
|
1379
|
-
})
|
|
1380
|
-
|
|
1381
|
-
return couponSchema.nullable().parse(loadedCoupon)
|
|
1382
|
-
},
|
|
1383
|
-
async getPurchasesForBulkCouponId(
|
|
1384
|
-
bulkCouponId: string,
|
|
1385
|
-
): Promise<(Purchase & { user: User })[]> {
|
|
1386
|
-
return z.array(purchaseSchema.extend({ user: userSchema })).parse(
|
|
1387
|
-
await client.query.purchases.findMany({
|
|
1388
|
-
where: eq(purchaseTable.bulkCouponId, bulkCouponId),
|
|
1389
|
-
with: {
|
|
1390
|
-
user: true,
|
|
1391
|
-
},
|
|
1392
|
-
}),
|
|
1393
|
-
)
|
|
1394
|
-
},
|
|
1395
|
-
async getCouponWithBulkPurchases(couponId: string): Promise<
|
|
1396
|
-
| (Coupon & {
|
|
1397
|
-
bulkPurchases?: Purchase[] | null
|
|
1398
|
-
redeemedBulkCouponPurchases: { bulkCouponId?: string | null }[]
|
|
1399
|
-
})
|
|
1400
|
-
| null
|
|
1401
|
-
> {
|
|
1402
|
-
logger.debug('getCouponWithBulkPurchases', { couponId })
|
|
1403
|
-
let couponData
|
|
1404
|
-
let bulkCouponPurchases: unknown[] = []
|
|
1405
|
-
try {
|
|
1406
|
-
couponData =
|
|
1407
|
-
(await client.query.coupon.findFirst({
|
|
1408
|
-
where: eq(coupon.id, couponId),
|
|
1409
|
-
with: {
|
|
1410
|
-
bulkPurchases: true,
|
|
1411
|
-
redeemedBulkCouponPurchases: true,
|
|
1412
|
-
},
|
|
1413
|
-
})) || null
|
|
1414
|
-
} catch (e) {
|
|
1415
|
-
logger.error(e as Error, {
|
|
1416
|
-
method: 'getCouponWithBulkPurchases',
|
|
1417
|
-
couponId,
|
|
1418
|
-
step: 'load-coupon',
|
|
1419
|
-
})
|
|
1420
|
-
}
|
|
1421
|
-
|
|
1422
|
-
try {
|
|
1423
|
-
bulkCouponPurchases = await client.query.purchases.findMany({
|
|
1424
|
-
where: eq(purchaseTable.redeemedBulkCouponId, couponId),
|
|
1425
|
-
with: {
|
|
1426
|
-
user: true,
|
|
1427
|
-
},
|
|
1428
|
-
})
|
|
1429
|
-
logger.debug('getCouponWithBulkPurchases.redeemed', {
|
|
1430
|
-
couponId,
|
|
1431
|
-
count: bulkCouponPurchases?.length ?? 0,
|
|
1432
|
-
})
|
|
1433
|
-
} catch (e) {
|
|
1434
|
-
logger.error(e as Error, {
|
|
1435
|
-
method: 'getCouponWithBulkPurchases',
|
|
1436
|
-
couponId,
|
|
1437
|
-
step: 'load-redeemed-purchases',
|
|
1438
|
-
})
|
|
1439
|
-
}
|
|
1440
|
-
|
|
1441
|
-
if (!couponData) {
|
|
1442
|
-
logger.debug('getCouponWithBulkPurchases', {
|
|
1443
|
-
couponId,
|
|
1444
|
-
error: 'no coupon found',
|
|
1445
|
-
})
|
|
1446
|
-
return null
|
|
1447
|
-
}
|
|
1448
|
-
|
|
1449
|
-
const couponWithBulkPurchases = {
|
|
1450
|
-
...couponData,
|
|
1451
|
-
redeemedBulkCouponPurchases: bulkCouponPurchases || [],
|
|
1452
|
-
}
|
|
1453
|
-
|
|
1454
|
-
const parsedCoupon = couponSchema
|
|
1455
|
-
.merge(
|
|
1456
|
-
z.object({
|
|
1457
|
-
redeemedBulkCouponPurchases: z.array(purchaseSchema),
|
|
1458
|
-
}),
|
|
1459
|
-
)
|
|
1460
|
-
.nullable()
|
|
1461
|
-
.safeParse(couponWithBulkPurchases)
|
|
1462
|
-
|
|
1463
|
-
if (!parsedCoupon.success) {
|
|
1464
|
-
logger.error(parsedCoupon.error, {
|
|
1465
|
-
couponId: couponData?.id ?? couponId,
|
|
1466
|
-
event: 'adapter.coupon.parse_failed',
|
|
1467
|
-
redeemedBulkCouponPurchasesCount: bulkCouponPurchases?.length ?? 0,
|
|
1468
|
-
})
|
|
1469
|
-
return null
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
return parsedCoupon.data
|
|
1473
|
-
},
|
|
1474
|
-
async getDefaultCoupon(productIds?: string[]): Promise<{
|
|
1475
|
-
defaultMerchantCoupon: MerchantCoupon
|
|
1476
|
-
defaultCoupon: Coupon
|
|
1477
|
-
} | null> {
|
|
1478
|
-
const activeSaleCoupon = await client.query.coupon.findFirst({
|
|
1479
|
-
where: and(
|
|
1480
|
-
eq(coupon.status, 1),
|
|
1481
|
-
eq(coupon.default, true),
|
|
1482
|
-
gte(coupon.expires, new Date()),
|
|
1483
|
-
or(
|
|
1484
|
-
productIds
|
|
1485
|
-
? inArray(coupon.restrictedToProductId, productIds)
|
|
1486
|
-
: undefined,
|
|
1487
|
-
isNull(coupon.restrictedToProductId),
|
|
1488
|
-
),
|
|
1489
|
-
),
|
|
1490
|
-
orderBy: desc(coupon.percentageDiscount),
|
|
1491
|
-
with: {
|
|
1492
|
-
merchantCoupon: true,
|
|
1493
|
-
product: true,
|
|
1494
|
-
},
|
|
1495
|
-
})
|
|
1496
|
-
if (activeSaleCoupon) {
|
|
1497
|
-
const { restrictedToProductId } = activeSaleCoupon
|
|
1498
|
-
const validForProdcutId = restrictedToProductId
|
|
1499
|
-
? productIds?.includes(restrictedToProductId as string)
|
|
1500
|
-
: true
|
|
1501
|
-
|
|
1502
|
-
const { merchantCoupon: defaultMerchantCoupon, ...defaultCoupon } =
|
|
1503
|
-
activeSaleCoupon
|
|
1504
|
-
|
|
1505
|
-
if (validForProdcutId) {
|
|
1506
|
-
return {
|
|
1507
|
-
defaultMerchantCoupon: merchantCouponSchema.parse(
|
|
1508
|
-
defaultMerchantCoupon,
|
|
1509
|
-
),
|
|
1510
|
-
defaultCoupon: couponSchema.parse(defaultCoupon),
|
|
1511
|
-
}
|
|
1512
|
-
}
|
|
1513
|
-
}
|
|
1514
|
-
return null
|
|
1515
|
-
},
|
|
1516
|
-
getLessonProgressCountsByDate(): Promise<
|
|
1517
|
-
{
|
|
1518
|
-
count: number
|
|
1519
|
-
completedAt: string
|
|
1520
|
-
}[]
|
|
1521
|
-
> {
|
|
1522
|
-
throw new Error('getLessonProgressCountsByDate Method not implemented.')
|
|
1523
|
-
},
|
|
1524
|
-
async getLessonProgressForUser(
|
|
1525
|
-
userId: string,
|
|
1526
|
-
): Promise<ResourceProgress[]> {
|
|
1527
|
-
const userProgress = await client.query.resourceProgress.findMany({
|
|
1528
|
-
where: eq(resourceProgress.userId, userId),
|
|
1529
|
-
})
|
|
1530
|
-
const parsed = z.array(resourceProgressSchema).safeParse(userProgress)
|
|
1531
|
-
if (!parsed.success) {
|
|
1532
|
-
logger.error(parsed.error, {
|
|
1533
|
-
event: 'adapter.userProgress.parse_failed',
|
|
1534
|
-
userId,
|
|
1535
|
-
})
|
|
1536
|
-
return []
|
|
1537
|
-
}
|
|
1538
|
-
return parsed.data
|
|
1539
|
-
},
|
|
1540
|
-
async getModuleProgressForUser(
|
|
1541
|
-
userIdOrEmail: string,
|
|
1542
|
-
moduleIdOrSlug: string,
|
|
1543
|
-
): Promise<ModuleProgress | null> {
|
|
1544
|
-
// First, get the user ID
|
|
1545
|
-
const user = await client.query.users.findFirst({
|
|
1546
|
-
where: or(eq(users.id, userIdOrEmail), eq(users.email, userIdOrEmail)),
|
|
1547
|
-
columns: {
|
|
1548
|
-
id: true,
|
|
1549
|
-
},
|
|
1550
|
-
})
|
|
1551
|
-
|
|
1552
|
-
if (!user) {
|
|
1553
|
-
return null
|
|
1554
|
-
}
|
|
1555
|
-
|
|
1556
|
-
const ResultRowSchema = z.object({
|
|
1557
|
-
resource_id: z.string(),
|
|
1558
|
-
resource_type: z.enum(['lesson', 'exercise', 'post']),
|
|
1559
|
-
resource_slug: z.string().nullable(),
|
|
1560
|
-
completed_at: z
|
|
1561
|
-
.string()
|
|
1562
|
-
.nullable()
|
|
1563
|
-
.transform((val) => (val ? new Date(val) : null)),
|
|
1564
|
-
})
|
|
1565
|
-
|
|
1566
|
-
// Execute the optimized query - create a flattened, properly ordered list
|
|
1567
|
-
const results: any = await client.execute(sql`
|
|
1568
|
-
WITH RECURSIVE workshop AS (
|
|
1569
|
-
SELECT id, fields->>'$.slug' AS slug
|
|
1570
|
-
FROM ${contentResource}
|
|
1571
|
-
WHERE id = ${moduleIdOrSlug} OR fields->>'$.slug' = ${moduleIdOrSlug}
|
|
1572
|
-
),
|
|
1573
|
-
-- Get all workshop resources with their positions
|
|
1574
|
-
workshop_structure AS (
|
|
1575
|
-
SELECT
|
|
1576
|
-
w.id AS workshop_id,
|
|
1577
|
-
crr.resourceId,
|
|
1578
|
-
crr.position AS position,
|
|
1579
|
-
cr.type AS resource_type,
|
|
1580
|
-
cr.fields->>'$.slug' AS resource_slug,
|
|
1581
|
-
cr.id AS resource_content_id
|
|
1582
|
-
FROM workshop w
|
|
1583
|
-
JOIN ${contentResourceResource} crr ON w.id = crr.resourceOfId
|
|
1584
|
-
JOIN ${contentResource} cr ON crr.resourceId = cr.id
|
|
1585
|
-
ORDER BY crr.position
|
|
1586
|
-
),
|
|
1587
|
-
-- Recursively expand sections and maintain global ordering
|
|
1588
|
-
expanded_resources AS (
|
|
1589
|
-
-- Base case: direct lessons/posts from workshop
|
|
1590
|
-
SELECT
|
|
1591
|
-
workshop_id,
|
|
1592
|
-
position * 1000 AS global_order, -- Multiply by 1000 to leave room for section items
|
|
1593
|
-
resource_content_id AS resource_id,
|
|
1594
|
-
resource_type,
|
|
1595
|
-
resource_slug
|
|
1596
|
-
FROM workshop_structure
|
|
1597
|
-
WHERE resource_type IN ('lesson', 'post')
|
|
1598
|
-
UNION ALL
|
|
1599
|
-
-- Recursive case: lessons within sections
|
|
1600
|
-
SELECT
|
|
1601
|
-
ws.workshop_id,
|
|
1602
|
-
ws.position * 1000 + crr.position AS global_order, -- Section position * 1000 + lesson position
|
|
1603
|
-
cr.id AS resource_id,
|
|
1604
|
-
cr.type AS resource_type,
|
|
1605
|
-
cr.fields->>'$.slug' AS resource_slug
|
|
1606
|
-
FROM workshop_structure ws
|
|
1607
|
-
JOIN ${contentResourceResource} crr ON ws.resource_content_id = crr.resourceOfId
|
|
1608
|
-
JOIN ${contentResource} cr ON crr.resourceId = cr.id
|
|
1609
|
-
WHERE ws.resource_type = 'section'
|
|
1610
|
-
AND cr.type IN ('lesson', 'post')
|
|
1611
|
-
)
|
|
1612
|
-
SELECT
|
|
1613
|
-
er.resource_id,
|
|
1614
|
-
er.resource_type,
|
|
1615
|
-
er.resource_slug,
|
|
1616
|
-
rp.completedAt AS completed_at,
|
|
1617
|
-
er.global_order
|
|
1618
|
-
FROM expanded_resources er
|
|
1619
|
-
LEFT JOIN ${resourceProgress} rp ON rp.resourceId = er.resource_id
|
|
1620
|
-
AND rp.userId = ${user.id}
|
|
1621
|
-
ORDER BY er.global_order ASC
|
|
1622
|
-
`)
|
|
1623
|
-
// Process the results
|
|
1624
|
-
const completedLessons: ResourceProgress[] = []
|
|
1625
|
-
let nextResource: Partial<ContentResource> | null = null
|
|
1626
|
-
let completedLessonsCount = 0
|
|
1627
|
-
let totalLessonsCount = results.rows.length
|
|
1628
|
-
|
|
1629
|
-
const parsedRows = z.array(ResultRowSchema).safeParse(results.rows)
|
|
1630
|
-
|
|
1631
|
-
if (!parsedRows.success) {
|
|
1632
|
-
logger.error(parsedRows.error, {
|
|
1633
|
-
event: 'adapter.moduleProgress.rows.parse_failed',
|
|
1634
|
-
moduleIdOrSlug,
|
|
1635
|
-
userId: user.id,
|
|
1636
|
-
})
|
|
1637
|
-
return {
|
|
1638
|
-
completedLessons: [],
|
|
1639
|
-
nextResource: null,
|
|
1640
|
-
percentCompleted: 0,
|
|
1641
|
-
completedLessonsCount: 0,
|
|
1642
|
-
totalLessonsCount,
|
|
1643
|
-
}
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
for (const row of parsedRows.data) {
|
|
1647
|
-
if (row.completed_at) {
|
|
1648
|
-
completedLessonsCount++
|
|
1649
|
-
completedLessons.push({
|
|
1650
|
-
userId: user.id as string,
|
|
1651
|
-
resourceId: row.resource_id,
|
|
1652
|
-
completedAt: new Date(row.completed_at),
|
|
1653
|
-
// Add other fields as needed
|
|
1654
|
-
})
|
|
1655
|
-
} else if (!nextResource) {
|
|
1656
|
-
nextResource = {
|
|
1657
|
-
id: row.resource_id,
|
|
1658
|
-
type: row.resource_type,
|
|
1659
|
-
fields: {
|
|
1660
|
-
slug: row.resource_slug,
|
|
1661
|
-
},
|
|
1662
|
-
}
|
|
1663
|
-
}
|
|
1664
|
-
}
|
|
1665
|
-
|
|
1666
|
-
const percentCompleted =
|
|
1667
|
-
totalLessonsCount > 0
|
|
1668
|
-
? Math.ceil((completedLessonsCount / totalLessonsCount) * 100)
|
|
1669
|
-
: 0
|
|
1670
|
-
|
|
1671
|
-
return {
|
|
1672
|
-
completedLessons,
|
|
1673
|
-
nextResource,
|
|
1674
|
-
percentCompleted,
|
|
1675
|
-
completedLessonsCount,
|
|
1676
|
-
totalLessonsCount,
|
|
1677
|
-
}
|
|
1678
|
-
},
|
|
1679
|
-
getLessonProgresses(): Promise<ResourceProgress[]> {
|
|
1680
|
-
throw new Error('getLessonProgresses Method not implemented.')
|
|
1681
|
-
},
|
|
1682
|
-
async getMerchantCharge(
|
|
1683
|
-
merchantChargeId: string,
|
|
1684
|
-
): Promise<MerchantCharge | null> {
|
|
1685
|
-
const mCharge = await client.query.merchantCharge.findFirst({
|
|
1686
|
-
where: eq(merchantCharge.id, merchantChargeId),
|
|
1687
|
-
})
|
|
1688
|
-
const parsed = merchantChargeSchema.safeParse(mCharge)
|
|
1689
|
-
if (!parsed.success) {
|
|
1690
|
-
logger.error(parsed.error, {
|
|
1691
|
-
event: 'adapter.merchantCharge.parse_failed',
|
|
1692
|
-
merchantChargeId,
|
|
1693
|
-
})
|
|
1694
|
-
return null
|
|
1695
|
-
}
|
|
1696
|
-
return parsed.data
|
|
1697
|
-
},
|
|
1698
|
-
async getMerchantCouponsForTypeAndPercent(params: {
|
|
1699
|
-
type: string
|
|
1700
|
-
percentageDiscount: number
|
|
1701
|
-
}): Promise<MerchantCoupon[]> {
|
|
1702
|
-
return z.array(merchantCouponSchema).parse(
|
|
1703
|
-
await client.query.merchantCoupon.findMany({
|
|
1704
|
-
where: and(
|
|
1705
|
-
eq(merchantCoupon.type, params.type),
|
|
1706
|
-
eq(
|
|
1707
|
-
merchantCoupon.percentageDiscount,
|
|
1708
|
-
params.percentageDiscount.toString(),
|
|
1709
|
-
),
|
|
1710
|
-
),
|
|
1711
|
-
}),
|
|
1712
|
-
)
|
|
1713
|
-
},
|
|
1714
|
-
async getMerchantCouponForTypeAndPercent(params: {
|
|
1715
|
-
type: string
|
|
1716
|
-
percentageDiscount: number
|
|
1717
|
-
}): Promise<MerchantCoupon | null> {
|
|
1718
|
-
const foundMerchantCoupon = await client.query.merchantCoupon.findFirst({
|
|
1719
|
-
where: and(
|
|
1720
|
-
eq(merchantCoupon.type, params.type),
|
|
1721
|
-
eq(
|
|
1722
|
-
merchantCoupon.percentageDiscount,
|
|
1723
|
-
params.percentageDiscount.toString(),
|
|
1724
|
-
),
|
|
1725
|
-
),
|
|
1726
|
-
})
|
|
1727
|
-
|
|
1728
|
-
const parsed = merchantCouponSchema
|
|
1729
|
-
.nullable()
|
|
1730
|
-
.safeParse(foundMerchantCoupon)
|
|
1731
|
-
if (parsed.success) {
|
|
1732
|
-
return parsed.data
|
|
1733
|
-
}
|
|
1734
|
-
|
|
1735
|
-
return null
|
|
1736
|
-
},
|
|
1737
|
-
async getMerchantCouponForTypeAndAmount(params: {
|
|
1738
|
-
type: string
|
|
1739
|
-
amountDiscount: number
|
|
1740
|
-
}): Promise<MerchantCoupon | null> {
|
|
1741
|
-
const foundMerchantCoupon = await client.query.merchantCoupon.findFirst({
|
|
1742
|
-
where: and(
|
|
1743
|
-
eq(merchantCoupon.type, params.type),
|
|
1744
|
-
eq(merchantCoupon.amountDiscount, params.amountDiscount),
|
|
1745
|
-
),
|
|
1746
|
-
})
|
|
1747
|
-
|
|
1748
|
-
const parsed = merchantCouponSchema
|
|
1749
|
-
.nullable()
|
|
1750
|
-
.safeParse(foundMerchantCoupon)
|
|
1751
|
-
if (parsed.success) {
|
|
1752
|
-
return parsed.data
|
|
1753
|
-
}
|
|
1754
|
-
|
|
1755
|
-
return null
|
|
1756
|
-
},
|
|
1757
|
-
async getMerchantCoupon(
|
|
1758
|
-
merchantCouponId: string,
|
|
1759
|
-
): Promise<MerchantCoupon | null> {
|
|
1760
|
-
const foundMerchantCoupon = await client.query.merchantCoupon.findFirst({
|
|
1761
|
-
where: eq(merchantCoupon.id, merchantCouponId),
|
|
1762
|
-
})
|
|
1763
|
-
|
|
1764
|
-
const parsed = merchantCouponSchema
|
|
1765
|
-
.nullable()
|
|
1766
|
-
.safeParse(foundMerchantCoupon)
|
|
1767
|
-
if (parsed.success) {
|
|
1768
|
-
return parsed.data
|
|
1769
|
-
}
|
|
1770
|
-
|
|
1771
|
-
return null
|
|
56
|
+
): DrizzleAdapterComposition<InstanceType<typeof MySqlDatabase>> {
|
|
57
|
+
const context = createMySqlContext(client, tableFn)
|
|
58
|
+
const organizationPort = createOrganizationPort(context)
|
|
59
|
+
const catalogPort = createCatalogPort(context, paymentProvider)
|
|
60
|
+
const contentResourcePort = createContentResourcePort(context)
|
|
61
|
+
const couponPort = createCouponPort(context)
|
|
62
|
+
const entitlementPort = createEntitlementPort(context)
|
|
63
|
+
const identityPort = createIdentityPort(context)
|
|
64
|
+
const merchantPort = createMerchantPort(context)
|
|
65
|
+
const subscriptionPort = createSubscriptionPort(context)
|
|
66
|
+
const authStoragePort = createAuthStoragePort(context)
|
|
67
|
+
const progressPort = createProgressPort(context)
|
|
68
|
+
const purchasePort = createPurchasePort(context)
|
|
69
|
+
const merchantChargeAndPurchaseWorkflow =
|
|
70
|
+
createMerchantChargeAndPurchaseWorkflow(context)
|
|
71
|
+
const transferPurchaseToUserWorkflow = createTransferPurchaseToUserWorkflow(
|
|
72
|
+
context,
|
|
73
|
+
paymentProvider,
|
|
74
|
+
{
|
|
75
|
+
getPurchaseUserTransferById: purchasePort.getPurchaseUserTransferById,
|
|
1772
76
|
},
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
await client.query.prices.findFirst({
|
|
1788
|
-
where: eq(prices.productId, productId),
|
|
1789
|
-
}),
|
|
1790
|
-
)
|
|
1791
|
-
},
|
|
1792
|
-
async archiveProduct(productId) {
|
|
1793
|
-
if (!paymentProvider) throw new Error('Payment provider not found')
|
|
1794
|
-
const product = await adapter.getProduct(productId)
|
|
1795
|
-
|
|
1796
|
-
if (!product) {
|
|
1797
|
-
throw new Error(`Product not found for id (${productId})`)
|
|
1798
|
-
}
|
|
1799
|
-
|
|
1800
|
-
if (!product.price) {
|
|
1801
|
-
throw new Error(`Product has no price`)
|
|
1802
|
-
}
|
|
1803
|
-
|
|
1804
|
-
await client
|
|
1805
|
-
.update(products)
|
|
1806
|
-
.set({ status: 0, name: `${product.name} (Archived)` })
|
|
1807
|
-
.where(eq(products.id, productId))
|
|
1808
|
-
|
|
1809
|
-
await client
|
|
1810
|
-
.update(prices)
|
|
1811
|
-
.set({ status: 0, nickname: `${product.name} (Archived)` })
|
|
1812
|
-
.where(eq(prices.productId, productId))
|
|
1813
|
-
|
|
1814
|
-
await client
|
|
1815
|
-
.update(merchantProduct)
|
|
1816
|
-
.set({ status: 0 })
|
|
1817
|
-
.where(eq(merchantProduct.productId, productId))
|
|
1818
|
-
|
|
1819
|
-
await client
|
|
1820
|
-
.update(merchantPrice)
|
|
1821
|
-
.set({ status: 0 })
|
|
1822
|
-
.where(eq(merchantPrice.priceId, product.price.id))
|
|
1823
|
-
|
|
1824
|
-
const currentMerchantProduct = merchantProductSchema.nullish().parse(
|
|
1825
|
-
await client.query.merchantProduct.findFirst({
|
|
1826
|
-
where: eq(merchantProduct.productId, productId),
|
|
1827
|
-
}),
|
|
1828
|
-
)
|
|
1829
|
-
|
|
1830
|
-
if (!currentMerchantProduct || !currentMerchantProduct.identifier) {
|
|
1831
|
-
throw new Error(`Merchant product not found for id (${productId})`)
|
|
1832
|
-
}
|
|
1833
|
-
|
|
1834
|
-
await paymentProvider.updateProduct(currentMerchantProduct.identifier, {
|
|
1835
|
-
active: false,
|
|
1836
|
-
})
|
|
1837
|
-
|
|
1838
|
-
const currentMerchantPrice = merchantPriceSchema.nullish().parse(
|
|
1839
|
-
await client.query.merchantPrice.findFirst({
|
|
1840
|
-
where: and(
|
|
1841
|
-
eq(merchantPrice.priceId, product.price.id),
|
|
1842
|
-
eq(merchantPrice.status, 1),
|
|
1843
|
-
),
|
|
1844
|
-
}),
|
|
1845
|
-
)
|
|
1846
|
-
|
|
1847
|
-
if (!currentMerchantPrice || !currentMerchantPrice.identifier) {
|
|
1848
|
-
throw new Error(`Merchant price not found for id (${productId})`)
|
|
1849
|
-
}
|
|
1850
|
-
|
|
1851
|
-
await paymentProvider.updatePrice(currentMerchantPrice.identifier, {
|
|
1852
|
-
active: false,
|
|
1853
|
-
})
|
|
1854
|
-
|
|
1855
|
-
return adapter.getProduct(productId)
|
|
77
|
+
)
|
|
78
|
+
const redeemFullPriceCouponWorkflow = createRedeemFullPriceCouponWorkflow(
|
|
79
|
+
context,
|
|
80
|
+
{
|
|
81
|
+
getCouponWithBulkPurchases: couponPort.getCouponWithBulkPurchases,
|
|
82
|
+
incrementCouponUsedCount: couponPort.incrementCouponUsedCount,
|
|
83
|
+
findOrCreateUser: identityPort.findOrCreateUser,
|
|
84
|
+
getUserById: identityPort.getUserById,
|
|
85
|
+
getMembershipsForUser: organizationPort.getMembershipsForUser,
|
|
86
|
+
createPurchase: purchasePort.createPurchase,
|
|
87
|
+
createPurchaseTransfer: purchasePort.createPurchaseTransfer,
|
|
88
|
+
getExistingNonBulkValidPurchasesOfProduct:
|
|
89
|
+
purchasePort.getExistingNonBulkValidPurchasesOfProduct,
|
|
90
|
+
getPurchase: purchasePort.getPurchase,
|
|
1856
91
|
},
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
eq(merchantProduct.productId, input.id),
|
|
1871
|
-
}),
|
|
1872
|
-
)
|
|
1873
|
-
|
|
1874
|
-
if (!merchantProduct || !merchantProduct.identifier) {
|
|
1875
|
-
throw new Error(`Merchant product not found`)
|
|
1876
|
-
}
|
|
1877
|
-
|
|
1878
|
-
// TODO: handle upgrades
|
|
1879
|
-
|
|
1880
|
-
const stripeProduct = await paymentProvider.getProduct(
|
|
1881
|
-
merchantProduct.identifier,
|
|
1882
|
-
)
|
|
1883
|
-
|
|
1884
|
-
const priceChanged =
|
|
1885
|
-
currentProduct.price.unitAmount.toString() !==
|
|
1886
|
-
input.price?.unitAmount.toString()
|
|
1887
|
-
|
|
1888
|
-
// Get current merchant price and Stripe price to check if it needs updating
|
|
1889
|
-
const currentMerchantPrice = merchantPriceSchema.nullish().parse(
|
|
1890
|
-
await client.query.merchantPrice.findFirst({
|
|
1891
|
-
where: (merchantPrice, { eq, and }) =>
|
|
1892
|
-
and(
|
|
1893
|
-
eq(merchantPrice.merchantProductId, merchantProduct.id),
|
|
1894
|
-
eq(merchantPrice.status, 1),
|
|
1895
|
-
),
|
|
1896
|
-
}),
|
|
1897
|
-
)
|
|
1898
|
-
|
|
1899
|
-
if (!currentMerchantPrice || !currentMerchantPrice.identifier) {
|
|
1900
|
-
throw new Error(`Merchant price not found`)
|
|
1901
|
-
}
|
|
1902
|
-
|
|
1903
|
-
const currentStripePrice = await paymentProvider.getPrice(
|
|
1904
|
-
currentMerchantPrice.identifier,
|
|
1905
|
-
)
|
|
1906
|
-
|
|
1907
|
-
// Check if type changed to/from membership (requires Stripe price recreation)
|
|
1908
|
-
const membershipTypeChanged =
|
|
1909
|
-
currentProduct.type !== input.type &&
|
|
1910
|
-
(input.type === 'membership' || currentProduct.type === 'membership')
|
|
1911
|
-
|
|
1912
|
-
// Check if billing interval changed for membership products
|
|
1913
|
-
const billingIntervalChanged =
|
|
1914
|
-
input.type === 'membership' &&
|
|
1915
|
-
currentProduct.type === 'membership' &&
|
|
1916
|
-
currentProduct.fields?.billingInterval !== input.fields?.billingInterval
|
|
1917
|
-
|
|
1918
|
-
// Check if Stripe price recurring status doesn't match expected state for input type:
|
|
1919
|
-
// - membership products require recurring=true
|
|
1920
|
-
// - non-membership products require recurring=false (null/undefined)
|
|
1921
|
-
const isMembership = input.type === 'membership'
|
|
1922
|
-
const stripePriceMismatch =
|
|
1923
|
-
isMembership !== !!currentStripePrice.recurring
|
|
1924
|
-
|
|
1925
|
-
// Need to recreate price if: price changed, type changed to/from membership,
|
|
1926
|
-
// billing interval changed, or Stripe price doesn't match expected recurring status
|
|
1927
|
-
const needsPriceRecreation =
|
|
1928
|
-
priceChanged ||
|
|
1929
|
-
membershipTypeChanged ||
|
|
1930
|
-
billingIntervalChanged ||
|
|
1931
|
-
stripePriceMismatch
|
|
1932
|
-
|
|
1933
|
-
if (needsPriceRecreation) {
|
|
1934
|
-
// Get billing interval from input or default to 'year'
|
|
1935
|
-
const billingInterval = input.fields?.billingInterval || 'year'
|
|
1936
|
-
|
|
1937
|
-
const priceParams = {
|
|
1938
|
-
product: stripeProduct.id,
|
|
1939
|
-
unit_amount: Math.floor(Number(input.price?.unitAmount || 0) * 100),
|
|
1940
|
-
currency: 'usd',
|
|
1941
|
-
metadata: {
|
|
1942
|
-
slug: input.fields.slug,
|
|
1943
|
-
},
|
|
1944
|
-
active: true,
|
|
1945
|
-
// Membership products get recurring billing
|
|
1946
|
-
...(input.type === 'membership' && {
|
|
1947
|
-
recurring: { interval: billingInterval },
|
|
1948
|
-
}),
|
|
1949
|
-
}
|
|
1950
|
-
|
|
1951
|
-
logger.debug('adapter.product.price_recreation.started', {
|
|
1952
|
-
billingInterval,
|
|
1953
|
-
currency: priceParams.currency,
|
|
1954
|
-
currentMerchantPriceId: currentMerchantPrice.id,
|
|
1955
|
-
isMembership: input.type === 'membership',
|
|
1956
|
-
merchantProductId: merchantProduct.id,
|
|
1957
|
-
productId: input.id,
|
|
1958
|
-
productSlug: input.fields.slug,
|
|
1959
|
-
stripeProductId: stripeProduct.id,
|
|
1960
|
-
unitAmount: priceParams.unit_amount,
|
|
1961
|
-
})
|
|
1962
|
-
|
|
1963
|
-
const newStripePrice = await paymentProvider.createPrice(priceParams)
|
|
1964
|
-
|
|
1965
|
-
await paymentProvider.updateProduct(stripeProduct.id, {
|
|
1966
|
-
default_price: newStripePrice.id,
|
|
1967
|
-
})
|
|
1968
|
-
|
|
1969
|
-
const newMerchantPriceId = `mprice_${v4()}`
|
|
1970
|
-
await client.insert(merchantPrice).values({
|
|
1971
|
-
id: newMerchantPriceId,
|
|
1972
|
-
merchantProductId: merchantProduct.id,
|
|
1973
|
-
merchantAccountId: merchantProduct.merchantAccountId,
|
|
1974
|
-
priceId: currentProduct.price.id,
|
|
1975
|
-
status: 1,
|
|
1976
|
-
identifier: newStripePrice.id,
|
|
1977
|
-
})
|
|
1978
|
-
|
|
1979
|
-
if (currentMerchantPrice) {
|
|
1980
|
-
await client
|
|
1981
|
-
.update(merchantPrice)
|
|
1982
|
-
.set({
|
|
1983
|
-
status: 0,
|
|
1984
|
-
})
|
|
1985
|
-
.where(eq(merchantPrice.id, currentMerchantPrice.id))
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
|
-
await client
|
|
1989
|
-
.update(prices)
|
|
1990
|
-
.set({
|
|
1991
|
-
unitAmount: Math.floor(
|
|
1992
|
-
Number(input.price?.unitAmount || 0),
|
|
1993
|
-
).toString(),
|
|
1994
|
-
nickname: input.name,
|
|
1995
|
-
})
|
|
1996
|
-
.where(eq(prices.id, currentProduct.price.id))
|
|
1997
|
-
|
|
1998
|
-
if (currentStripePrice) {
|
|
1999
|
-
await paymentProvider.updatePrice(currentStripePrice.id, {
|
|
2000
|
-
active: false,
|
|
2001
|
-
})
|
|
2002
|
-
}
|
|
2003
|
-
}
|
|
2004
|
-
|
|
2005
|
-
await paymentProvider.updateProduct(stripeProduct.id, {
|
|
2006
|
-
name: input.name,
|
|
2007
|
-
active: true,
|
|
2008
|
-
images: input.fields.image?.url ? [input.fields.image.url] : undefined,
|
|
2009
|
-
description: input.fields.description || '',
|
|
2010
|
-
metadata: {
|
|
2011
|
-
slug: input.fields.slug,
|
|
2012
|
-
},
|
|
2013
|
-
})
|
|
2014
|
-
|
|
2015
|
-
const { image, ...fieldsNoImage } = input.fields
|
|
2016
|
-
|
|
2017
|
-
await client
|
|
2018
|
-
.update(products)
|
|
2019
|
-
.set({
|
|
2020
|
-
name: input.name,
|
|
2021
|
-
quantityAvailable: input.quantityAvailable,
|
|
2022
|
-
status: 1,
|
|
2023
|
-
fields: {
|
|
2024
|
-
...fieldsNoImage,
|
|
2025
|
-
...(image?.url && { image }),
|
|
2026
|
-
},
|
|
2027
|
-
type: input.type,
|
|
2028
|
-
})
|
|
2029
|
-
.where(eq(products.id, currentProduct.id))
|
|
2030
|
-
|
|
2031
|
-
return adapter.getProduct(currentProduct.id)
|
|
92
|
+
)
|
|
93
|
+
const createEventWorkflowOperations = createEventWorkflow(context, {
|
|
94
|
+
getEvent: contentResourcePort.getEvent,
|
|
95
|
+
addResourceToProduct: catalogPort.addResourceToProduct,
|
|
96
|
+
createProduct: catalogPort.createProduct,
|
|
97
|
+
createCoupon: couponPort.createCoupon,
|
|
98
|
+
})
|
|
99
|
+
const createEventSeriesWorkflowOperations = createEventSeriesWorkflow(
|
|
100
|
+
context,
|
|
101
|
+
{
|
|
102
|
+
addResourceToProduct: catalogPort.addResourceToProduct,
|
|
103
|
+
createProduct: catalogPort.createProduct,
|
|
104
|
+
createCoupon: couponPort.createCoupon,
|
|
2032
105
|
},
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
|
|
2042
|
-
if (!merchantAccount) {
|
|
2043
|
-
throw new Error('Merchant account not found')
|
|
2044
|
-
}
|
|
106
|
+
)
|
|
107
|
+
const createCohortWorkflowOperations = createCohortWorkflow(context, {
|
|
108
|
+
createProduct: catalogPort.createProduct,
|
|
109
|
+
createCoupon: couponPort.createCoupon,
|
|
110
|
+
})
|
|
111
|
+
const createWorkshopWorkflowOperations = createWorkshopWorkflow(context, {
|
|
112
|
+
createProduct: catalogPort.createProduct,
|
|
113
|
+
createCoupon: couponPort.createCoupon,
|
|
114
|
+
})
|
|
2045
115
|
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
2053
|
-
|
|
2054
|
-
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2068
|
-
}),
|
|
2069
|
-
},
|
|
2070
|
-
}
|
|
2071
|
-
|
|
2072
|
-
await client.insert(products).values(newProduct)
|
|
2073
|
-
|
|
2074
|
-
const priceHash = guid()
|
|
2075
|
-
const newPriceId = `price-${priceHash}`
|
|
2076
|
-
|
|
2077
|
-
await client.insert(prices).values({
|
|
2078
|
-
id: newPriceId,
|
|
2079
|
-
productId: newProductId,
|
|
2080
|
-
unitAmount: input.price.toString(),
|
|
2081
|
-
status: 1,
|
|
2082
|
-
})
|
|
2083
|
-
|
|
2084
|
-
const product = await adapter.getProduct(newProductId)
|
|
2085
|
-
|
|
2086
|
-
const stripeProduct = await paymentProvider.createProduct({
|
|
2087
|
-
name: input.name,
|
|
2088
|
-
metadata: {
|
|
2089
|
-
slug: product?.fields?.slug || null,
|
|
2090
|
-
},
|
|
2091
|
-
})
|
|
2092
|
-
|
|
2093
|
-
const stripePrice = await paymentProvider.createPrice({
|
|
2094
|
-
product: stripeProduct.id,
|
|
2095
|
-
unit_amount: Math.floor(Number(input.price) * 100),
|
|
2096
|
-
currency: 'usd',
|
|
2097
|
-
nickname: input.name,
|
|
2098
|
-
metadata: {
|
|
2099
|
-
slug: product?.fields?.slug || null,
|
|
2100
|
-
},
|
|
2101
|
-
// Membership products get recurring billing
|
|
2102
|
-
...(input.type === 'membership' && {
|
|
2103
|
-
recurring: { interval: input.billingInterval || 'year' },
|
|
2104
|
-
}),
|
|
2105
|
-
})
|
|
2106
|
-
|
|
2107
|
-
const newMerchantProductId = `mproduct_${v4()}`
|
|
2108
|
-
|
|
2109
|
-
await client.insert(merchantProduct).values({
|
|
2110
|
-
id: newMerchantProductId,
|
|
2111
|
-
merchantAccountId: merchantAccount.id,
|
|
2112
|
-
productId: newProductId,
|
|
2113
|
-
identifier: stripeProduct.id,
|
|
2114
|
-
status: 1,
|
|
2115
|
-
})
|
|
2116
|
-
|
|
2117
|
-
const newMerchantPriceId = `mprice_${v4()}`
|
|
2118
|
-
await client.insert(merchantPrice).values({
|
|
2119
|
-
id: newMerchantPriceId,
|
|
2120
|
-
merchantAccountId: merchantAccount.id,
|
|
2121
|
-
merchantProductId: newMerchantProductId,
|
|
2122
|
-
priceId: newPriceId,
|
|
2123
|
-
identifier: stripePrice.id,
|
|
2124
|
-
status: 1,
|
|
2125
|
-
})
|
|
2126
|
-
|
|
2127
|
-
// TODO: handle upgrades
|
|
2128
|
-
|
|
2129
|
-
return product
|
|
2130
|
-
},
|
|
2131
|
-
async getProduct(
|
|
2132
|
-
productSlugOrId?: string,
|
|
2133
|
-
withResources: boolean = true,
|
|
2134
|
-
): Promise<Product | null> {
|
|
2135
|
-
if (!productSlugOrId) {
|
|
2136
|
-
return null
|
|
2137
|
-
}
|
|
2138
|
-
|
|
2139
|
-
try {
|
|
2140
|
-
const productData = await client.query.products.findFirst({
|
|
2141
|
-
where: and(
|
|
2142
|
-
or(
|
|
2143
|
-
eq(
|
|
2144
|
-
sql`JSON_EXTRACT (${products.fields}, "$.slug")`,
|
|
2145
|
-
`${productSlugOrId}`,
|
|
2146
|
-
),
|
|
2147
|
-
eq(products.id, productSlugOrId),
|
|
2148
|
-
),
|
|
2149
|
-
),
|
|
2150
|
-
with: {
|
|
2151
|
-
price: true,
|
|
2152
|
-
...(withResources && {
|
|
2153
|
-
resources: {
|
|
2154
|
-
with: {
|
|
2155
|
-
resource: {
|
|
2156
|
-
with: {
|
|
2157
|
-
resources: true,
|
|
2158
|
-
},
|
|
2159
|
-
},
|
|
2160
|
-
},
|
|
2161
|
-
},
|
|
2162
|
-
}),
|
|
2163
|
-
},
|
|
2164
|
-
})
|
|
2165
|
-
const parsedProduct = productSchema.safeParse(productData)
|
|
2166
|
-
if (!parsedProduct.success) {
|
|
2167
|
-
logger.error(parsedProduct.error, {
|
|
2168
|
-
event: 'adapter.product.parse_failed',
|
|
2169
|
-
productId: productData?.id,
|
|
2170
|
-
productSlugOrId,
|
|
2171
|
-
})
|
|
2172
|
-
return null
|
|
2173
|
-
}
|
|
2174
|
-
return parsedProduct.data
|
|
2175
|
-
} catch (e) {
|
|
2176
|
-
logger.error(toError(e), {
|
|
2177
|
-
event: 'adapter.product.lookup.failed',
|
|
2178
|
-
productSlugOrId,
|
|
2179
|
-
withResources,
|
|
2180
|
-
})
|
|
2181
|
-
return null
|
|
2182
|
-
}
|
|
2183
|
-
},
|
|
2184
|
-
async getProductResources(
|
|
2185
|
-
productId: string,
|
|
2186
|
-
): Promise<ContentResource[] | null> {
|
|
2187
|
-
const contentResourceProductsForProduct = z
|
|
2188
|
-
.array(ContentResourceProductSchema)
|
|
2189
|
-
.nullable()
|
|
2190
|
-
.parse(
|
|
2191
|
-
await client.query.contentResourceProduct.findMany({
|
|
2192
|
-
where: eq(contentResourceProduct.productId, productId),
|
|
2193
|
-
}),
|
|
2194
|
-
)
|
|
2195
|
-
|
|
2196
|
-
if (!contentResourceProductsForProduct) {
|
|
2197
|
-
return null
|
|
2198
|
-
} else {
|
|
2199
|
-
const contentResources = z.array(ContentResourceSchema).parse(
|
|
2200
|
-
await client.query.contentResource.findMany({
|
|
2201
|
-
where: inArray(
|
|
2202
|
-
contentResource.id,
|
|
2203
|
-
contentResourceProductsForProduct.map((crp) => crp.resourceId),
|
|
2204
|
-
),
|
|
2205
|
-
}),
|
|
2206
|
-
)
|
|
2207
|
-
return contentResources
|
|
2208
|
-
}
|
|
2209
|
-
},
|
|
2210
|
-
async getPurchaseCountForProduct(productId: string): Promise<number> {
|
|
2211
|
-
return await client.query.purchases
|
|
2212
|
-
.findMany({
|
|
2213
|
-
where: and(
|
|
2214
|
-
eq(purchaseTable.productId, productId),
|
|
2215
|
-
inArray(purchaseTable.status, ['Valid', 'Restricted']),
|
|
2216
|
-
),
|
|
2217
|
-
})
|
|
2218
|
-
.then((res) => res.length)
|
|
2219
|
-
},
|
|
2220
|
-
async getPurchase(purchaseId: string): Promise<Purchase | null> {
|
|
2221
|
-
const purchase = await client.query.purchases.findFirst({
|
|
2222
|
-
where: eq(purchaseTable.id, purchaseId),
|
|
2223
|
-
})
|
|
2224
|
-
|
|
2225
|
-
return purchase ? purchaseSchema.parse(purchase) : null
|
|
2226
|
-
},
|
|
2227
|
-
async createPurchase(options): Promise<Purchase> {
|
|
2228
|
-
const newPurchaseId = options.id || `purchase-${v4()}`
|
|
2229
|
-
await client.insert(purchaseTable).values({
|
|
2230
|
-
...options,
|
|
2231
|
-
id: newPurchaseId,
|
|
2232
|
-
})
|
|
2233
|
-
|
|
2234
|
-
const newPurchase = await adapter.getPurchase(newPurchaseId)
|
|
2235
|
-
|
|
2236
|
-
return purchaseSchema.parse(newPurchase)
|
|
2237
|
-
},
|
|
2238
|
-
|
|
2239
|
-
async getPurchaseForStripeCharge(
|
|
2240
|
-
stripeChargeId: string,
|
|
2241
|
-
): Promise<Purchase | null> {
|
|
2242
|
-
logger.debug('getPurchaseForStripeCharge', { stripeChargeId })
|
|
2243
|
-
|
|
2244
|
-
const chargeForPurchase = merchantChargeSchema.nullable().parse(
|
|
2245
|
-
(await client.query.merchantCharge.findFirst({
|
|
2246
|
-
where: eq(merchantCharge.identifier, stripeChargeId),
|
|
2247
|
-
})) || null,
|
|
2248
|
-
)
|
|
2249
|
-
|
|
2250
|
-
if (!chargeForPurchase) {
|
|
2251
|
-
// Expected during initial checkout processing — the merchant
|
|
2252
|
-
// charge hasn't been created yet. Also expected when the
|
|
2253
|
-
// payment-status-checker passes a PaymentIntent ID (pi_xxx)
|
|
2254
|
-
// instead of a Charge ID (ch_xxx).
|
|
2255
|
-
logger.debug('getPurchaseForStripeCharge: no matching charge', {
|
|
2256
|
-
stripeChargeId,
|
|
2257
|
-
idPrefix: stripeChargeId?.substring(0, 3),
|
|
2258
|
-
})
|
|
2259
|
-
return null
|
|
2260
|
-
}
|
|
2261
|
-
|
|
2262
|
-
const purchase = purchaseSchema.safeParse(
|
|
2263
|
-
await client.query.purchases.findFirst({
|
|
2264
|
-
where: eq(purchaseTable.merchantChargeId, chargeForPurchase.id),
|
|
2265
|
-
with: {
|
|
2266
|
-
user: true,
|
|
2267
|
-
product: true,
|
|
2268
|
-
bulkCoupon: true,
|
|
2269
|
-
},
|
|
2270
|
-
}),
|
|
2271
|
-
)
|
|
2272
|
-
|
|
2273
|
-
if (!purchase.success) {
|
|
2274
|
-
return null
|
|
2275
|
-
}
|
|
2276
|
-
|
|
2277
|
-
return purchase.data
|
|
2278
|
-
},
|
|
2279
|
-
async getPurchaseByCheckoutSessionId(
|
|
2280
|
-
checkoutSessionId: string,
|
|
2281
|
-
): Promise<Purchase | null> {
|
|
2282
|
-
logger.debug('getPurchaseByCheckoutSessionId', { checkoutSessionId })
|
|
2283
|
-
|
|
2284
|
-
// Find the MerchantSession by its identifier (the Stripe checkout session ID)
|
|
2285
|
-
const session = await client.query.merchantSession.findFirst({
|
|
2286
|
-
where: eq(merchantSession.identifier, checkoutSessionId),
|
|
2287
|
-
})
|
|
2288
|
-
|
|
2289
|
-
if (!session) {
|
|
2290
|
-
logger.debug('getPurchaseByCheckoutSessionId: no matching session', {
|
|
2291
|
-
checkoutSessionId,
|
|
2292
|
-
})
|
|
2293
|
-
return null
|
|
2294
|
-
}
|
|
2295
|
-
|
|
2296
|
-
const purchase = purchaseSchema.safeParse(
|
|
2297
|
-
await client.query.purchases.findFirst({
|
|
2298
|
-
where: eq(purchaseTable.merchantSessionId, String(session.id)),
|
|
2299
|
-
with: {
|
|
2300
|
-
user: true,
|
|
2301
|
-
product: true,
|
|
2302
|
-
bulkCoupon: true,
|
|
2303
|
-
},
|
|
2304
|
-
}),
|
|
2305
|
-
)
|
|
2306
|
-
|
|
2307
|
-
if (!purchase.success) {
|
|
2308
|
-
return null
|
|
2309
|
-
}
|
|
2310
|
-
|
|
2311
|
-
return purchase.data
|
|
2312
|
-
},
|
|
2313
|
-
async getPurchaseUserTransferById(options: { id: string }): Promise<
|
|
2314
|
-
| (PurchaseUserTransfer & {
|
|
2315
|
-
sourceUser: User
|
|
2316
|
-
targetUser: User | null
|
|
2317
|
-
purchase: Purchase
|
|
2318
|
-
})
|
|
2319
|
-
| null
|
|
2320
|
-
> {
|
|
2321
|
-
const purchaseTransferData =
|
|
2322
|
-
await client.query.purchaseUserTransfer.findFirst({
|
|
2323
|
-
where: eq(purchaseUserTransfer.id, options.id),
|
|
2324
|
-
with: {
|
|
2325
|
-
sourceUser: true,
|
|
2326
|
-
targetUser: true,
|
|
2327
|
-
purchase: true,
|
|
2328
|
-
},
|
|
2329
|
-
})
|
|
2330
|
-
|
|
2331
|
-
return purchaseUserTransferSchema
|
|
2332
|
-
.merge(
|
|
2333
|
-
z.object({
|
|
2334
|
-
sourceUser: userSchema,
|
|
2335
|
-
targetUser: userSchema.nullable(),
|
|
2336
|
-
purchase: purchaseSchema,
|
|
2337
|
-
organizationId: z.string().nullable(),
|
|
2338
|
-
}),
|
|
2339
|
-
)
|
|
2340
|
-
.nullable()
|
|
2341
|
-
.parse(purchaseTransferData)
|
|
2342
|
-
},
|
|
2343
|
-
async getPurchaseWithUser(purchaseId: string): Promise<
|
|
2344
|
-
| (Purchase & {
|
|
2345
|
-
user: User
|
|
2346
|
-
})
|
|
2347
|
-
| null
|
|
2348
|
-
> {
|
|
2349
|
-
const purchaseData =
|
|
2350
|
-
(await client.query.purchases.findFirst({
|
|
2351
|
-
where: eq(purchaseTable.id, purchaseId),
|
|
2352
|
-
with: {
|
|
2353
|
-
user: true,
|
|
2354
|
-
},
|
|
2355
|
-
})) || null
|
|
2356
|
-
|
|
2357
|
-
const parsedPurchase = purchaseSchema
|
|
2358
|
-
.merge(z.object({ user: userSchema }))
|
|
2359
|
-
.nullable()
|
|
2360
|
-
.safeParse(purchaseData)
|
|
2361
|
-
|
|
2362
|
-
if (!parsedPurchase.success) {
|
|
2363
|
-
logger.error(parsedPurchase.error, {
|
|
2364
|
-
event: 'adapter.purchase.parse_failed',
|
|
2365
|
-
purchaseId,
|
|
2366
|
-
})
|
|
2367
|
-
return null
|
|
2368
|
-
}
|
|
2369
|
-
return parsedPurchase.data
|
|
2370
|
-
},
|
|
2371
|
-
async getPurchasesForUser(userId?: string): Promise<Purchase[]> {
|
|
2372
|
-
if (!userId) {
|
|
2373
|
-
return []
|
|
2374
|
-
}
|
|
2375
|
-
|
|
2376
|
-
const visiblePurchaseStates = ['Valid', 'Refunded', 'Restricted']
|
|
2377
|
-
|
|
2378
|
-
const userPurchases = await client.query.purchases.findMany({
|
|
2379
|
-
where: and(
|
|
2380
|
-
eq(purchaseTable.userId, userId),
|
|
2381
|
-
inArray(purchaseTable.status, visiblePurchaseStates),
|
|
2382
|
-
),
|
|
2383
|
-
with: {
|
|
2384
|
-
user: true,
|
|
2385
|
-
product: true,
|
|
2386
|
-
bulkCoupon: true,
|
|
2387
|
-
},
|
|
2388
|
-
orderBy: asc(purchaseTable.createdAt),
|
|
2389
|
-
})
|
|
2390
|
-
|
|
2391
|
-
const parsedPurchases = z.array(purchaseSchema).safeParse(userPurchases)
|
|
2392
|
-
|
|
2393
|
-
if (!parsedPurchases.success) {
|
|
2394
|
-
logger.error(parsedPurchases.error, {
|
|
2395
|
-
event: 'adapter.purchase.list.parse_failed',
|
|
2396
|
-
purchaseCount: userPurchases.length,
|
|
2397
|
-
userId,
|
|
2398
|
-
})
|
|
2399
|
-
return []
|
|
2400
|
-
}
|
|
2401
|
-
|
|
2402
|
-
return parsedPurchases.data
|
|
2403
|
-
},
|
|
2404
|
-
async getEntitlementsForUser(params: {
|
|
2405
|
-
userId: string
|
|
2406
|
-
sourceType?: string
|
|
2407
|
-
entitlementType?: string
|
|
2408
|
-
}): Promise<Entitlement[]> {
|
|
2409
|
-
const { userId, sourceType, entitlementType } = params
|
|
2410
|
-
|
|
2411
|
-
if (!userId) {
|
|
2412
|
-
return []
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
const conditions = [eq(entitlementTable.userId, userId)]
|
|
2416
|
-
|
|
2417
|
-
if (sourceType) {
|
|
2418
|
-
conditions.push(eq(entitlementTable.sourceType, sourceType))
|
|
2419
|
-
}
|
|
2420
|
-
|
|
2421
|
-
if (entitlementType) {
|
|
2422
|
-
conditions.push(eq(entitlementTable.entitlementType, entitlementType))
|
|
2423
|
-
}
|
|
2424
|
-
|
|
2425
|
-
// Only return active entitlements (not deleted, not expired)
|
|
2426
|
-
conditions.push(isNull(entitlementTable.deletedAt))
|
|
2427
|
-
const expiresCondition = or(
|
|
2428
|
-
isNull(entitlementTable.expiresAt),
|
|
2429
|
-
gte(entitlementTable.expiresAt, sql`CURRENT_TIMESTAMP`),
|
|
2430
|
-
)
|
|
2431
|
-
if (expiresCondition) {
|
|
2432
|
-
conditions.push(expiresCondition)
|
|
2433
|
-
}
|
|
2434
|
-
|
|
2435
|
-
const userEntitlements = await client.query.entitlements.findMany({
|
|
2436
|
-
where: conditions.length > 0 ? and(...conditions) : undefined,
|
|
2437
|
-
orderBy: asc(entitlementTable.createdAt),
|
|
2438
|
-
})
|
|
2439
|
-
|
|
2440
|
-
const parsedEntitlements = z
|
|
2441
|
-
.array(entitlementSchema)
|
|
2442
|
-
.safeParse(userEntitlements)
|
|
2443
|
-
|
|
2444
|
-
if (!parsedEntitlements.success) {
|
|
2445
|
-
logger.error(parsedEntitlements.error, {
|
|
2446
|
-
entitlementCount: userEntitlements.length,
|
|
2447
|
-
entitlementType,
|
|
2448
|
-
event: 'adapter.entitlement.list.parse_failed',
|
|
2449
|
-
sourceType,
|
|
2450
|
-
userId,
|
|
2451
|
-
})
|
|
2452
|
-
return []
|
|
2453
|
-
}
|
|
2454
|
-
|
|
2455
|
-
return parsedEntitlements.data
|
|
2456
|
-
},
|
|
2457
|
-
async getEntitlementTypeByName(
|
|
2458
|
-
name: string,
|
|
2459
|
-
): Promise<{ id: string; name: string } | null> {
|
|
2460
|
-
const entitlementType = await client.query.entitlementTypes.findFirst({
|
|
2461
|
-
where: eq(entitlementTypes.name, name),
|
|
2462
|
-
})
|
|
2463
|
-
|
|
2464
|
-
if (!entitlementType) {
|
|
2465
|
-
return null
|
|
2466
|
-
}
|
|
2467
|
-
|
|
2468
|
-
return {
|
|
2469
|
-
id: String(entitlementType.id),
|
|
2470
|
-
name: String(entitlementType.name),
|
|
2471
|
-
}
|
|
2472
|
-
},
|
|
2473
|
-
async getPurchaseDetails(
|
|
2474
|
-
purchaseId: string,
|
|
2475
|
-
userId: string,
|
|
2476
|
-
): Promise<{
|
|
2477
|
-
purchase?: Purchase
|
|
2478
|
-
existingPurchase?: Purchase | null
|
|
2479
|
-
availableUpgrades: UpgradableProduct[]
|
|
2480
|
-
}> {
|
|
2481
|
-
const visiblePurchaseStates = ['Valid', 'Refunded', 'Restricted']
|
|
2482
|
-
|
|
2483
|
-
const userPurchases = await client.query.purchases.findMany({
|
|
2484
|
-
where: and(
|
|
2485
|
-
eq(purchaseTable.userId, userId),
|
|
2486
|
-
inArray(purchaseTable.status, visiblePurchaseStates),
|
|
2487
|
-
),
|
|
2488
|
-
with: {
|
|
2489
|
-
user: true,
|
|
2490
|
-
product: true,
|
|
2491
|
-
bulkCoupon: true,
|
|
2492
|
-
},
|
|
2493
|
-
orderBy: asc(purchaseTable.createdAt),
|
|
2494
|
-
})
|
|
2495
|
-
|
|
2496
|
-
const parsedPurchases = z.array(purchaseSchema).safeParse(userPurchases)
|
|
2497
|
-
|
|
2498
|
-
const allPurchases = parsedPurchases.success ? parsedPurchases.data : []
|
|
2499
|
-
|
|
2500
|
-
const thePurchase = await client.query.purchases.findFirst({
|
|
2501
|
-
where: and(
|
|
2502
|
-
eq(purchaseTable.id, purchaseId),
|
|
2503
|
-
eq(purchaseTable.userId, userId),
|
|
2504
|
-
),
|
|
2505
|
-
with: {
|
|
2506
|
-
user: true,
|
|
2507
|
-
product: true,
|
|
2508
|
-
bulkCoupon: true,
|
|
2509
|
-
},
|
|
2510
|
-
})
|
|
2511
|
-
|
|
2512
|
-
const parsedPurchase = purchaseSchema.safeParse(thePurchase)
|
|
2513
|
-
|
|
2514
|
-
if (!parsedPurchase.success) {
|
|
2515
|
-
logger.error(parsedPurchase.error, {
|
|
2516
|
-
event: 'adapter.purchase.details.parse_failed',
|
|
2517
|
-
purchaseId,
|
|
2518
|
-
userId,
|
|
2519
|
-
})
|
|
2520
|
-
return {
|
|
2521
|
-
availableUpgrades: [],
|
|
2522
|
-
}
|
|
2523
|
-
}
|
|
2524
|
-
|
|
2525
|
-
const purchaseCanUpgrade = ['Valid', 'Restricted'].includes(
|
|
2526
|
-
parsedPurchase.data.state || '',
|
|
2527
|
-
)
|
|
2528
|
-
|
|
2529
|
-
let availableUpgrades: any[] = []
|
|
2530
|
-
|
|
2531
|
-
if (purchaseCanUpgrade) {
|
|
2532
|
-
availableUpgrades = await client.query.upgradableProducts.findMany({
|
|
2533
|
-
where: and(
|
|
2534
|
-
eq(
|
|
2535
|
-
upgradableProducts.upgradableFromId,
|
|
2536
|
-
parsedPurchase.data.product?.id as string,
|
|
2537
|
-
),
|
|
2538
|
-
not(
|
|
2539
|
-
inArray(
|
|
2540
|
-
upgradableProducts.upgradableToId,
|
|
2541
|
-
allPurchases.map((p) => p.product?.id as string),
|
|
2542
|
-
),
|
|
2543
|
-
),
|
|
2544
|
-
),
|
|
2545
|
-
with: {
|
|
2546
|
-
upgradableTo: true,
|
|
2547
|
-
upgradableFrom: true,
|
|
2548
|
-
},
|
|
2549
|
-
})
|
|
2550
|
-
}
|
|
2551
|
-
|
|
2552
|
-
const existingPurchase = purchaseSchema
|
|
2553
|
-
.optional()
|
|
2554
|
-
.nullable()
|
|
2555
|
-
.parse(
|
|
2556
|
-
(await client.query.purchases.findFirst({
|
|
2557
|
-
where: and(
|
|
2558
|
-
eq(purchaseTable.userId, userId),
|
|
2559
|
-
eq(purchaseTable.productId, parsedPurchase.data.productId),
|
|
2560
|
-
inArray(purchaseTable.status, ['Valid', 'Restricted']),
|
|
2561
|
-
isNull(purchaseTable.bulkCouponId),
|
|
2562
|
-
not(eq(purchaseTable.id, parsedPurchase.data.id)),
|
|
2563
|
-
),
|
|
2564
|
-
with: {
|
|
2565
|
-
user: true,
|
|
2566
|
-
product: true,
|
|
2567
|
-
bulkCoupon: true,
|
|
2568
|
-
},
|
|
2569
|
-
})) || null,
|
|
2570
|
-
)
|
|
2571
|
-
|
|
2572
|
-
return {
|
|
2573
|
-
availableUpgrades: z
|
|
2574
|
-
.array(upgradableProductSchema)
|
|
2575
|
-
.parse(availableUpgrades),
|
|
2576
|
-
existingPurchase,
|
|
2577
|
-
purchase: parsedPurchase.data,
|
|
2578
|
-
}
|
|
2579
|
-
},
|
|
2580
|
-
async getUserWithPurchasersByEmail(email: string): Promise<any | null> {
|
|
2581
|
-
const user = await client.query.users.findFirst({
|
|
2582
|
-
where: eq(users.email, email.trim().toLowerCase()),
|
|
2583
|
-
with: {
|
|
2584
|
-
// merchantCustomer: true,
|
|
2585
|
-
roles: {
|
|
2586
|
-
with: {
|
|
2587
|
-
role: true,
|
|
2588
|
-
},
|
|
2589
|
-
},
|
|
2590
|
-
purchases: {
|
|
2591
|
-
with: {
|
|
2592
|
-
bulkCoupon: true,
|
|
2593
|
-
merchantCharge: true,
|
|
2594
|
-
product: {
|
|
2595
|
-
columns: {
|
|
2596
|
-
id: true,
|
|
2597
|
-
name: true,
|
|
2598
|
-
key: true,
|
|
2599
|
-
},
|
|
2600
|
-
},
|
|
2601
|
-
},
|
|
2602
|
-
},
|
|
2603
|
-
},
|
|
2604
|
-
})
|
|
2605
|
-
|
|
2606
|
-
return user
|
|
2607
|
-
},
|
|
2608
|
-
async getUserById(userId: string): Promise<User | null> {
|
|
2609
|
-
const user = await client.query.users
|
|
2610
|
-
.findFirst({
|
|
2611
|
-
where: eq(users.id, userId),
|
|
2612
|
-
with: {
|
|
2613
|
-
roles: {
|
|
2614
|
-
with: {
|
|
2615
|
-
role: true,
|
|
2616
|
-
},
|
|
2617
|
-
},
|
|
2618
|
-
},
|
|
2619
|
-
})
|
|
2620
|
-
.then(async (res) => {
|
|
2621
|
-
if (res) {
|
|
2622
|
-
return {
|
|
2623
|
-
...res,
|
|
2624
|
-
roles: res.roles.map((r) => r.role),
|
|
2625
|
-
}
|
|
2626
|
-
}
|
|
2627
|
-
})
|
|
2628
|
-
|
|
2629
|
-
return userSchema.nullable().parse(user ?? null)
|
|
2630
|
-
},
|
|
2631
|
-
async pricesOfPurchasesTowardOneBundle({
|
|
2632
|
-
userId,
|
|
2633
|
-
bundleId,
|
|
2634
|
-
}: {
|
|
2635
|
-
userId: string | undefined
|
|
2636
|
-
bundleId: string
|
|
2637
|
-
}): Promise<Price[]> {
|
|
2638
|
-
if (userId === undefined) return []
|
|
2639
|
-
|
|
2640
|
-
const canUpgradeProducts = await client.query.upgradableProducts.findMany(
|
|
2641
|
-
{
|
|
2642
|
-
where: eq(upgradableProducts.upgradableToId, bundleId),
|
|
2643
|
-
},
|
|
2644
|
-
)
|
|
2645
|
-
|
|
2646
|
-
const upgradableFrom = z.array(z.string()).parse(
|
|
2647
|
-
canUpgradeProducts.map((product) => {
|
|
2648
|
-
return product.upgradableFromId
|
|
2649
|
-
}),
|
|
2650
|
-
)
|
|
2651
|
-
|
|
2652
|
-
const purchases = await client.query.purchases.findMany({
|
|
2653
|
-
where: and(
|
|
2654
|
-
eq(purchaseTable.userId, userId),
|
|
2655
|
-
inArray(purchaseTable.productId, upgradableFrom),
|
|
2656
|
-
),
|
|
2657
|
-
})
|
|
2658
|
-
|
|
2659
|
-
const productsPurchased = z.array(z.string()).parse(
|
|
2660
|
-
purchases.map((purchase) => {
|
|
2661
|
-
return purchase.productId
|
|
2662
|
-
}),
|
|
2663
|
-
)
|
|
2664
|
-
|
|
2665
|
-
if (productsPurchased.length === 0) return []
|
|
2666
|
-
|
|
2667
|
-
const foundPrices = await client.query.prices.findMany({
|
|
2668
|
-
where: inArray(prices.productId, productsPurchased),
|
|
2669
|
-
})
|
|
2670
|
-
|
|
2671
|
-
return z.array(priceSchema).parse(foundPrices)
|
|
2672
|
-
},
|
|
2673
|
-
async toggleLessonProgressForUser(options: {
|
|
2674
|
-
userId: string
|
|
2675
|
-
lessonId?: string
|
|
2676
|
-
}): Promise<ResourceProgress | null> {
|
|
2677
|
-
if (!options.lessonId) {
|
|
2678
|
-
throw new Error('No lessonId provided')
|
|
2679
|
-
}
|
|
2680
|
-
|
|
2681
|
-
let lessonProgress = await client.query.resourceProgress.findFirst({
|
|
2682
|
-
where: and(
|
|
2683
|
-
eq(resourceProgress.userId, options.userId),
|
|
2684
|
-
eq(resourceProgress.resourceId, options.lessonId),
|
|
2685
|
-
),
|
|
2686
|
-
})
|
|
2687
|
-
|
|
2688
|
-
const now = new Date()
|
|
2689
|
-
|
|
2690
|
-
if (lessonProgress) {
|
|
2691
|
-
await client
|
|
2692
|
-
.update(resourceProgress)
|
|
2693
|
-
.set({
|
|
2694
|
-
completedAt: lessonProgress.completedAt ? null : now,
|
|
2695
|
-
updatedAt: now,
|
|
2696
|
-
})
|
|
2697
|
-
.where(
|
|
2698
|
-
and(
|
|
2699
|
-
eq(resourceProgress.userId, options.userId),
|
|
2700
|
-
eq(resourceProgress.resourceId, options.lessonId),
|
|
2701
|
-
),
|
|
2702
|
-
)
|
|
2703
|
-
} else {
|
|
2704
|
-
await client.insert(resourceProgress).values({
|
|
2705
|
-
userId: options.userId,
|
|
2706
|
-
resourceId: options.lessonId,
|
|
2707
|
-
completedAt: now,
|
|
2708
|
-
updatedAt: now,
|
|
2709
|
-
})
|
|
2710
|
-
}
|
|
2711
|
-
|
|
2712
|
-
lessonProgress = await client.query.resourceProgress.findFirst({
|
|
2713
|
-
where: and(
|
|
2714
|
-
eq(resourceProgress.userId, options.userId),
|
|
2715
|
-
eq(resourceProgress.resourceId, options.lessonId),
|
|
2716
|
-
),
|
|
2717
|
-
})
|
|
2718
|
-
|
|
2719
|
-
const parsedLessonProgress =
|
|
2720
|
-
resourceProgressSchema.safeParse(lessonProgress)
|
|
2721
|
-
|
|
2722
|
-
if (!parsedLessonProgress.success) {
|
|
2723
|
-
logger.error(parsedLessonProgress.error, {
|
|
2724
|
-
event: 'adapter.lessonProgress.parse_failed',
|
|
2725
|
-
lessonId: options.lessonId,
|
|
2726
|
-
userId: options.userId,
|
|
2727
|
-
})
|
|
2728
|
-
return null
|
|
2729
|
-
}
|
|
2730
|
-
|
|
2731
|
-
return parsedLessonProgress.data
|
|
2732
|
-
},
|
|
2733
|
-
transferPurchaseToUser: async (options: {
|
|
2734
|
-
purchaseId: string
|
|
2735
|
-
targetUserId: string
|
|
2736
|
-
sourceUserId: string
|
|
2737
|
-
}) => {
|
|
2738
|
-
const { purchaseId, targetUserId, sourceUserId } = options
|
|
2739
|
-
const transferId = `put_${v4()}`
|
|
2740
|
-
let purchase = await adapter.getPurchase(purchaseId)
|
|
2741
|
-
if (!purchase) throw new Error('No purchase found')
|
|
2742
|
-
|
|
2743
|
-
if (purchase.userId !== sourceUserId)
|
|
2744
|
-
throw new Error('Invalid source user')
|
|
2745
|
-
|
|
2746
|
-
await client.transaction(async (trx) => {
|
|
2747
|
-
if (!purchase) throw new Error('No purchase found')
|
|
2748
|
-
await trx
|
|
2749
|
-
.update(purchaseTable)
|
|
2750
|
-
.set({ userId: targetUserId })
|
|
2751
|
-
.where(eq(purchaseTable.id, purchase.id))
|
|
2752
|
-
|
|
2753
|
-
await trx.insert(purchaseUserTransfer).values({
|
|
2754
|
-
id: transferId,
|
|
2755
|
-
purchaseId: purchase.id,
|
|
2756
|
-
sourceUserId: sourceUserId,
|
|
2757
|
-
targetUserId: targetUserId,
|
|
2758
|
-
transferState: 'COMPLETED',
|
|
2759
|
-
completedAt: new Date(),
|
|
2760
|
-
})
|
|
2761
|
-
|
|
2762
|
-
purchase = await adapter.getPurchase(purchaseId)
|
|
2763
|
-
|
|
2764
|
-
if (!purchase) throw new Error('No purchase found')
|
|
2765
|
-
|
|
2766
|
-
if (paymentProvider && purchase.merchantChargeId) {
|
|
2767
|
-
await trx
|
|
2768
|
-
.update(merchantCharge)
|
|
2769
|
-
.set({
|
|
2770
|
-
userId: targetUserId,
|
|
2771
|
-
})
|
|
2772
|
-
.where(eq(merchantCharge.id, purchase.merchantChargeId))
|
|
2773
|
-
|
|
2774
|
-
const updatedMerchantCharge = await adapter.getMerchantCharge(
|
|
2775
|
-
purchase.merchantChargeId,
|
|
2776
|
-
)
|
|
2777
|
-
|
|
2778
|
-
if (!updatedMerchantCharge)
|
|
2779
|
-
throw new Error('No merchant charge found')
|
|
2780
|
-
|
|
2781
|
-
await trx
|
|
2782
|
-
.update(merchantCustomer)
|
|
2783
|
-
.set({ userId: targetUserId })
|
|
2784
|
-
.where(
|
|
2785
|
-
eq(merchantCustomer.id, updatedMerchantCharge.merchantCustomerId),
|
|
2786
|
-
)
|
|
2787
|
-
|
|
2788
|
-
const updatedMerchantCustomer = merchantCustomerSchema.parse(
|
|
2789
|
-
await client.query.merchantCustomer.findFirst({
|
|
2790
|
-
where: eq(
|
|
2791
|
-
merchantCustomer.id,
|
|
2792
|
-
updatedMerchantCharge.merchantCustomerId,
|
|
2793
|
-
),
|
|
2794
|
-
}),
|
|
2795
|
-
)
|
|
2796
|
-
|
|
2797
|
-
const targetUser = userSchema.parse(
|
|
2798
|
-
await client.query.users.findFirst({
|
|
2799
|
-
where: eq(users.id, targetUserId),
|
|
2800
|
-
}),
|
|
2801
|
-
)
|
|
2802
|
-
|
|
2803
|
-
const sourceUser = userSchema.parse(
|
|
2804
|
-
await client.query.users.findFirst({
|
|
2805
|
-
where: eq(users.id, sourceUserId),
|
|
2806
|
-
}),
|
|
2807
|
-
)
|
|
2808
|
-
|
|
2809
|
-
await paymentProvider.updateCustomer(
|
|
2810
|
-
updatedMerchantCustomer.identifier,
|
|
2811
|
-
{
|
|
2812
|
-
email: targetUser.email,
|
|
2813
|
-
name: targetUser.name || sourceUser.name || '',
|
|
2814
|
-
metadata: {
|
|
2815
|
-
transferId,
|
|
2816
|
-
transferredFrom: sourceUser.email,
|
|
2817
|
-
transferredOn: new Date().toISOString(),
|
|
2818
|
-
},
|
|
2819
|
-
},
|
|
2820
|
-
)
|
|
2821
|
-
}
|
|
2822
|
-
})
|
|
2823
|
-
return await adapter.getPurchaseUserTransferById({
|
|
2824
|
-
id: transferId,
|
|
2825
|
-
})
|
|
2826
|
-
},
|
|
2827
|
-
transferPurchasesToNewUser(options: {
|
|
2828
|
-
merchantCustomerId: string
|
|
2829
|
-
userId: string
|
|
2830
|
-
}): Promise<unknown> {
|
|
2831
|
-
throw new Error('Method not implemented.')
|
|
2832
|
-
},
|
|
2833
|
-
async updatePurchaseStatusForCharge(
|
|
2834
|
-
chargeId: string,
|
|
2835
|
-
status: 'Valid' | 'Refunded' | 'Disputed' | 'Banned' | 'Restricted',
|
|
2836
|
-
): Promise<Purchase | undefined> {
|
|
2837
|
-
try {
|
|
2838
|
-
logger.debug('adapter.purchase.status_update.started', {
|
|
2839
|
-
chargeId,
|
|
2840
|
-
status,
|
|
2841
|
-
})
|
|
2842
|
-
const merchantChargeForPurchase =
|
|
2843
|
-
await client.query.merchantCharge.findFirst({
|
|
2844
|
-
where: or(
|
|
2845
|
-
eq(merchantCharge.identifier, chargeId),
|
|
2846
|
-
eq(merchantCharge.id, chargeId),
|
|
2847
|
-
),
|
|
2848
|
-
})
|
|
2849
|
-
|
|
2850
|
-
logger.debug('adapter.purchase.status_update.charge_resolved', {
|
|
2851
|
-
chargeId,
|
|
2852
|
-
merchantChargeId: merchantChargeForPurchase?.id ?? null,
|
|
2853
|
-
merchantChargeIdentifier:
|
|
2854
|
-
merchantChargeForPurchase?.identifier ?? null,
|
|
2855
|
-
})
|
|
2856
|
-
|
|
2857
|
-
const parsedMerchantChargeForPurchase = merchantChargeSchema.parse(
|
|
2858
|
-
merchantChargeForPurchase,
|
|
2859
|
-
)
|
|
2860
|
-
|
|
2861
|
-
if (!parsedMerchantChargeForPurchase)
|
|
2862
|
-
throw new Error(`no-charge-found-for-purchase ${chargeId}`)
|
|
2863
|
-
|
|
2864
|
-
const purchase = await client.query.purchases.findFirst({
|
|
2865
|
-
where: eq(
|
|
2866
|
-
purchaseTable.merchantChargeId,
|
|
2867
|
-
parsedMerchantChargeForPurchase.id,
|
|
2868
|
-
),
|
|
2869
|
-
})
|
|
2870
|
-
|
|
2871
|
-
const parsedPurchase = purchaseSchema.nullable().parse(purchase)
|
|
2872
|
-
|
|
2873
|
-
if (parsedPurchase) {
|
|
2874
|
-
await client
|
|
2875
|
-
.update(purchaseTable)
|
|
2876
|
-
.set({ status: status })
|
|
2877
|
-
.where(eq(purchaseTable.id, parsedPurchase.id))
|
|
2878
|
-
|
|
2879
|
-
const newPurchase = await client.query.purchases.findFirst({
|
|
2880
|
-
where: eq(purchaseTable.id, parsedPurchase.id),
|
|
2881
|
-
})
|
|
2882
|
-
|
|
2883
|
-
return purchaseSchema.optional().parse(newPurchase)
|
|
2884
|
-
} else {
|
|
2885
|
-
throw new Error(`no-purchase-found-for-charge ${chargeId}`)
|
|
2886
|
-
}
|
|
2887
|
-
} catch (e) {
|
|
2888
|
-
logger.error(toError(e), {
|
|
2889
|
-
chargeId,
|
|
2890
|
-
event: 'adapter.purchase.status_update.failed',
|
|
2891
|
-
status,
|
|
2892
|
-
})
|
|
2893
|
-
throw e
|
|
2894
|
-
}
|
|
2895
|
-
},
|
|
2896
|
-
async updatePurchaseUserTransferTransferState(options: {
|
|
2897
|
-
id: string
|
|
2898
|
-
transferState: PurchaseUserTransferState
|
|
2899
|
-
}): Promise<PurchaseUserTransfer | null> {
|
|
2900
|
-
await client
|
|
2901
|
-
.update(purchaseUserTransfer)
|
|
2902
|
-
.set({
|
|
2903
|
-
transferState: options.transferState,
|
|
2904
|
-
})
|
|
2905
|
-
.where(eq(purchaseUserTransfer.id, options.id))
|
|
2906
|
-
|
|
2907
|
-
const purchaseUserTransferData =
|
|
2908
|
-
(await client.query.purchaseUserTransfer.findFirst({
|
|
2909
|
-
where: eq(purchaseUserTransfer.id, options.id),
|
|
2910
|
-
})) || null
|
|
2911
|
-
|
|
2912
|
-
return purchaseUserTransferSchema
|
|
2913
|
-
.nullable()
|
|
2914
|
-
.parse(purchaseUserTransferData)
|
|
2915
|
-
},
|
|
2916
|
-
addResourceToResource: async function (options) {
|
|
2917
|
-
const { parentResourceId, childResourceId } = options
|
|
2918
|
-
|
|
2919
|
-
const parentResourceData = await client.query.contentResource.findFirst({
|
|
2920
|
-
where: or(
|
|
2921
|
-
eq(
|
|
2922
|
-
sql`JSON_EXTRACT (${contentResource.fields}, "$.slug")`,
|
|
2923
|
-
parentResourceId,
|
|
2924
|
-
),
|
|
2925
|
-
eq(contentResource.id, parentResourceId),
|
|
2926
|
-
),
|
|
2927
|
-
with: {
|
|
2928
|
-
resources: true,
|
|
2929
|
-
},
|
|
2930
|
-
})
|
|
2931
|
-
|
|
2932
|
-
const parsedParentResource =
|
|
2933
|
-
ContentResourceSchema.safeParse(parentResourceData)
|
|
2934
|
-
if (!parsedParentResource.success) {
|
|
2935
|
-
return null
|
|
2936
|
-
}
|
|
2937
|
-
|
|
2938
|
-
const parentResource = parsedParentResource.data
|
|
2939
|
-
|
|
2940
|
-
await client.insert(contentResourceResource).values({
|
|
2941
|
-
resourceOfId: parentResource.id,
|
|
2942
|
-
resourceId: childResourceId,
|
|
2943
|
-
position: parentResource.resources?.length || 0,
|
|
2944
|
-
})
|
|
2945
|
-
|
|
2946
|
-
const resourceJoin = await client.query.contentResourceResource.findFirst(
|
|
2947
|
-
{
|
|
2948
|
-
where: and(
|
|
2949
|
-
eq(contentResourceResource.resourceOfId, parentResource.id),
|
|
2950
|
-
eq(contentResourceResource.resourceId, childResourceId),
|
|
2951
|
-
),
|
|
2952
|
-
with: {
|
|
2953
|
-
resource: true,
|
|
2954
|
-
},
|
|
2955
|
-
},
|
|
2956
|
-
)
|
|
2957
|
-
|
|
2958
|
-
const parsedResourceJoin =
|
|
2959
|
-
ContentResourceResourceSchema.safeParse(resourceJoin)
|
|
2960
|
-
if (!parsedResourceJoin.success) {
|
|
2961
|
-
return null
|
|
2962
|
-
}
|
|
2963
|
-
|
|
2964
|
-
return parsedResourceJoin.data
|
|
2965
|
-
},
|
|
2966
|
-
async removeResourceFromResource(options) {
|
|
2967
|
-
const { childResourceId, parentResourceId } = options
|
|
2968
|
-
|
|
2969
|
-
const parentResourceData = await client.query.contentResource.findFirst({
|
|
2970
|
-
where: or(
|
|
2971
|
-
eq(
|
|
2972
|
-
sql`JSON_EXTRACT (${contentResource.fields}, "$.slug")`,
|
|
2973
|
-
parentResourceId,
|
|
2974
|
-
),
|
|
2975
|
-
eq(contentResource.id, parentResourceId),
|
|
2976
|
-
),
|
|
2977
|
-
})
|
|
2978
|
-
|
|
2979
|
-
const parsedParentResource =
|
|
2980
|
-
ContentResourceSchema.safeParse(parentResourceData)
|
|
2981
|
-
if (!parsedParentResource.success) {
|
|
2982
|
-
return null
|
|
2983
|
-
}
|
|
2984
|
-
|
|
2985
|
-
const parentResource = parsedParentResource.data
|
|
2986
|
-
|
|
2987
|
-
const resourceJoin = await client.query.contentResourceResource.findFirst(
|
|
2988
|
-
{
|
|
2989
|
-
where: and(
|
|
2990
|
-
eq(contentResourceResource.resourceOfId, parentResource.id),
|
|
2991
|
-
eq(contentResourceResource.resourceId, childResourceId),
|
|
2992
|
-
),
|
|
2993
|
-
with: {
|
|
2994
|
-
resource: true,
|
|
2995
|
-
},
|
|
2996
|
-
},
|
|
2997
|
-
)
|
|
2998
|
-
|
|
2999
|
-
const parsedResourceJoin =
|
|
3000
|
-
ContentResourceResourceSchema.safeParse(resourceJoin)
|
|
3001
|
-
if (!parsedResourceJoin.success) {
|
|
3002
|
-
return null
|
|
3003
|
-
}
|
|
3004
|
-
|
|
3005
|
-
await client
|
|
3006
|
-
.delete(contentResourceResource)
|
|
3007
|
-
.where(
|
|
3008
|
-
and(
|
|
3009
|
-
eq(contentResourceResource.resourceOfId, parentResource.id),
|
|
3010
|
-
eq(contentResourceResource.resourceId, childResourceId),
|
|
3011
|
-
),
|
|
3012
|
-
)
|
|
3013
|
-
|
|
3014
|
-
return parsedResourceJoin.data.resource
|
|
3015
|
-
},
|
|
3016
|
-
async updateContentResourceFields(options) {
|
|
3017
|
-
if (!options.id) {
|
|
3018
|
-
throw new Error('No content resource id.')
|
|
3019
|
-
}
|
|
3020
|
-
|
|
3021
|
-
const currentResource = await client
|
|
3022
|
-
.select()
|
|
3023
|
-
.from(contentResource)
|
|
3024
|
-
.where(eq(contentResource.id, options.id))
|
|
3025
|
-
.then((res) => res[0])
|
|
3026
|
-
|
|
3027
|
-
await client
|
|
3028
|
-
.update(contentResource)
|
|
3029
|
-
.set({ fields: { ...currentResource.fields, ...options.fields } })
|
|
3030
|
-
.where(eq(contentResource.id, options.id))
|
|
3031
|
-
|
|
3032
|
-
const resource = await client.query.contentResource.findFirst({
|
|
3033
|
-
where: eq(contentResource.id, options.id),
|
|
3034
|
-
with: {
|
|
3035
|
-
resources: {
|
|
3036
|
-
with: {
|
|
3037
|
-
resource: {
|
|
3038
|
-
with: {
|
|
3039
|
-
resources: {
|
|
3040
|
-
with: {
|
|
3041
|
-
resource: true,
|
|
3042
|
-
},
|
|
3043
|
-
orderBy: asc(contentResourceResource.position),
|
|
3044
|
-
},
|
|
3045
|
-
},
|
|
3046
|
-
},
|
|
3047
|
-
},
|
|
3048
|
-
orderBy: asc(contentResourceResource.position),
|
|
3049
|
-
},
|
|
3050
|
-
},
|
|
3051
|
-
})
|
|
3052
|
-
|
|
3053
|
-
const parsedResource = ContentResourceSchema.safeParse(resource)
|
|
3054
|
-
|
|
3055
|
-
if (!parsedResource.success) {
|
|
3056
|
-
logger.error(parsedResource.error, {
|
|
3057
|
-
event: 'adapter.contentResource.parse_failed',
|
|
3058
|
-
resourceId: options.id,
|
|
3059
|
-
})
|
|
3060
|
-
return null
|
|
3061
|
-
}
|
|
3062
|
-
|
|
3063
|
-
return parsedResource.data
|
|
3064
|
-
},
|
|
3065
|
-
async getVideoResource(id) {
|
|
3066
|
-
if (!id) {
|
|
3067
|
-
return null
|
|
3068
|
-
}
|
|
3069
|
-
|
|
3070
|
-
const query = sql`
|
|
3071
|
-
SELECT
|
|
3072
|
-
id as id,
|
|
3073
|
-
CAST(updatedAt AS DATETIME) as updatedAt,
|
|
3074
|
-
CAST(createdAt AS DATETIME) as createdAt,
|
|
3075
|
-
JSON_EXTRACT (${contentResource.fields}, "$.state") AS state,
|
|
3076
|
-
JSON_EXTRACT (${contentResource.fields}, "$.duration") AS duration,
|
|
3077
|
-
JSON_EXTRACT (${contentResource.fields}, "$.muxPlaybackId") AS muxPlaybackId,
|
|
3078
|
-
JSON_EXTRACT (${contentResource.fields}, "$.muxAssetId") AS muxAssetId,
|
|
3079
|
-
JSON_EXTRACT (${contentResource.fields}, "$.transcript") AS transcript,
|
|
3080
|
-
JSON_EXTRACT (${contentResource.fields}, "$.chapters") AS chapters
|
|
3081
|
-
FROM
|
|
3082
|
-
${contentResource}
|
|
3083
|
-
WHERE
|
|
3084
|
-
type = 'videoResource'
|
|
3085
|
-
AND (id = ${id});
|
|
3086
|
-
|
|
3087
|
-
`
|
|
3088
|
-
return client
|
|
3089
|
-
.execute(query)
|
|
3090
|
-
.then((result: any) => {
|
|
3091
|
-
const parsedResource = VideoResourceSchema.safeParse(result.rows[0])
|
|
3092
|
-
return parsedResource.success ? parsedResource.data : null
|
|
3093
|
-
})
|
|
3094
|
-
.catch((error) => {
|
|
3095
|
-
logger.error(toError(error), {
|
|
3096
|
-
event: 'adapter.videoResource.lookup.failed',
|
|
3097
|
-
resourceId: id,
|
|
3098
|
-
})
|
|
3099
|
-
throw error
|
|
3100
|
-
})
|
|
3101
|
-
},
|
|
3102
|
-
async getParentResourceOfVideoResource(
|
|
3103
|
-
videoResourceId: string,
|
|
3104
|
-
): Promise<ContentResource | null> {
|
|
3105
|
-
if (!videoResourceId) {
|
|
3106
|
-
return null
|
|
3107
|
-
}
|
|
3108
|
-
|
|
3109
|
-
// Use a SQL query to find the parent post or lesson of this video resource
|
|
3110
|
-
const results = await client
|
|
3111
|
-
.select()
|
|
3112
|
-
.from(contentResource)
|
|
3113
|
-
.innerJoin(
|
|
3114
|
-
contentResourceResource,
|
|
3115
|
-
eq(contentResourceResource.resourceOfId, contentResource.id),
|
|
3116
|
-
)
|
|
3117
|
-
.where(
|
|
3118
|
-
and(
|
|
3119
|
-
eq(contentResourceResource.resourceId, videoResourceId),
|
|
3120
|
-
or(
|
|
3121
|
-
eq(contentResource.type, 'post'),
|
|
3122
|
-
eq(contentResource.type, 'lesson'),
|
|
3123
|
-
),
|
|
3124
|
-
),
|
|
3125
|
-
)
|
|
3126
|
-
.limit(1)
|
|
3127
|
-
|
|
3128
|
-
if (!results || results.length === 0) {
|
|
3129
|
-
return null
|
|
3130
|
-
}
|
|
3131
|
-
|
|
3132
|
-
const resourceData = results[0]?.ContentResource
|
|
3133
|
-
|
|
3134
|
-
if (!resourceData) {
|
|
3135
|
-
return null
|
|
3136
|
-
}
|
|
3137
|
-
|
|
3138
|
-
// Fetch the full resource with relationships
|
|
3139
|
-
const fullResource = await client.query.contentResource.findFirst({
|
|
3140
|
-
where: eq(contentResource.id, resourceData.id),
|
|
3141
|
-
with: {
|
|
3142
|
-
resources: true,
|
|
3143
|
-
},
|
|
3144
|
-
})
|
|
3145
|
-
|
|
3146
|
-
if (!fullResource) {
|
|
3147
|
-
return null
|
|
3148
|
-
}
|
|
3149
|
-
|
|
3150
|
-
return ContentResourceSchema.parse(fullResource)
|
|
3151
|
-
},
|
|
3152
|
-
async createContentResource(data) {
|
|
3153
|
-
const id = data.id || crypto.randomUUID()
|
|
3154
|
-
|
|
3155
|
-
await client.insert(contentResource).values({ ...data, id })
|
|
3156
|
-
|
|
3157
|
-
const resource = await client.query.contentResource.findFirst({
|
|
3158
|
-
where: eq(contentResource.id, id),
|
|
3159
|
-
with: {
|
|
3160
|
-
resources: {
|
|
3161
|
-
with: {
|
|
3162
|
-
resource: {
|
|
3163
|
-
with: {
|
|
3164
|
-
resources: {
|
|
3165
|
-
with: {
|
|
3166
|
-
resource: true,
|
|
3167
|
-
},
|
|
3168
|
-
orderBy: asc(contentResourceResource.position),
|
|
3169
|
-
},
|
|
3170
|
-
},
|
|
3171
|
-
},
|
|
3172
|
-
},
|
|
3173
|
-
orderBy: asc(contentResourceResource.position),
|
|
3174
|
-
},
|
|
3175
|
-
},
|
|
3176
|
-
})
|
|
3177
|
-
|
|
3178
|
-
const parsedResource = ContentResourceSchema.safeParse(resource)
|
|
3179
|
-
|
|
3180
|
-
if (!parsedResource.success) {
|
|
3181
|
-
logger.error(parsedResource.error, {
|
|
3182
|
-
event: 'adapter.contentResource.parse_failed',
|
|
3183
|
-
resourceId: id,
|
|
3184
|
-
resourceType: data.type,
|
|
3185
|
-
})
|
|
3186
|
-
throw new Error('Error parsing resource')
|
|
3187
|
-
}
|
|
3188
|
-
|
|
3189
|
-
return parsedResource.data
|
|
3190
|
-
},
|
|
3191
|
-
async getContentResource(data) {
|
|
3192
|
-
const resource = await client.query.contentResource.findFirst({
|
|
3193
|
-
where: or(
|
|
3194
|
-
eq(contentResource.id, data),
|
|
3195
|
-
eq(
|
|
3196
|
-
sql`JSON_EXTRACT (${contentResource.fields}, "$.slug")`,
|
|
3197
|
-
`${data}`,
|
|
3198
|
-
),
|
|
3199
|
-
),
|
|
3200
|
-
with: {
|
|
3201
|
-
resources: {
|
|
3202
|
-
with: {
|
|
3203
|
-
resource: {
|
|
3204
|
-
with: {
|
|
3205
|
-
resources: {
|
|
3206
|
-
with: {
|
|
3207
|
-
resource: {
|
|
3208
|
-
with: {
|
|
3209
|
-
resources: {
|
|
3210
|
-
with: {
|
|
3211
|
-
resource: true,
|
|
3212
|
-
},
|
|
3213
|
-
},
|
|
3214
|
-
},
|
|
3215
|
-
},
|
|
3216
|
-
},
|
|
3217
|
-
orderBy: asc(contentResourceResource.position),
|
|
3218
|
-
},
|
|
3219
|
-
},
|
|
3220
|
-
},
|
|
3221
|
-
},
|
|
3222
|
-
orderBy: asc(contentResourceResource.position),
|
|
3223
|
-
},
|
|
3224
|
-
},
|
|
3225
|
-
})
|
|
3226
|
-
|
|
3227
|
-
const parsedResource = ContentResourceSchema.safeParse(resource)
|
|
3228
|
-
|
|
3229
|
-
if (!parsedResource.success) {
|
|
3230
|
-
logger.error(parsedResource.error, {
|
|
3231
|
-
event: 'adapter.contentResource.parse_failed',
|
|
3232
|
-
resourceId: resource?.id,
|
|
3233
|
-
resourceIdOrSlug: data,
|
|
3234
|
-
})
|
|
3235
|
-
return null
|
|
3236
|
-
}
|
|
3237
|
-
|
|
3238
|
-
return parsedResource.data
|
|
3239
|
-
},
|
|
3240
|
-
async getEvent(
|
|
3241
|
-
eventIdOrSlug: string,
|
|
3242
|
-
options?: {
|
|
3243
|
-
withResources?: boolean
|
|
3244
|
-
withTags?: boolean
|
|
3245
|
-
withProducts?: boolean
|
|
3246
|
-
withPricing?: boolean
|
|
3247
|
-
},
|
|
3248
|
-
): Promise<ContentResource | null> {
|
|
3249
|
-
const {
|
|
3250
|
-
withResources = false,
|
|
3251
|
-
withTags = false,
|
|
3252
|
-
withProducts = false,
|
|
3253
|
-
withPricing = false,
|
|
3254
|
-
} = options || {}
|
|
3255
|
-
|
|
3256
|
-
const eventQuery = await client.query.contentResource.findFirst({
|
|
3257
|
-
where: and(
|
|
3258
|
-
or(
|
|
3259
|
-
eq(contentResource.type, 'event'),
|
|
3260
|
-
eq(contentResource.type, 'event-series'),
|
|
3261
|
-
),
|
|
3262
|
-
or(
|
|
3263
|
-
eq(contentResource.id, eventIdOrSlug),
|
|
3264
|
-
eq(
|
|
3265
|
-
sql`JSON_EXTRACT (${contentResource.fields}, "$.slug")`,
|
|
3266
|
-
eventIdOrSlug,
|
|
3267
|
-
),
|
|
3268
|
-
),
|
|
3269
|
-
),
|
|
3270
|
-
with: {
|
|
3271
|
-
...(withResources && {
|
|
3272
|
-
resources: {
|
|
3273
|
-
with: { resource: true },
|
|
3274
|
-
orderBy: asc(contentResourceResource.position),
|
|
3275
|
-
},
|
|
3276
|
-
}),
|
|
3277
|
-
...(withTags && {
|
|
3278
|
-
tags: {
|
|
3279
|
-
with: { tag: true },
|
|
3280
|
-
orderBy: asc(contentResourceTag.position),
|
|
3281
|
-
},
|
|
3282
|
-
}),
|
|
3283
|
-
...(withProducts && {
|
|
3284
|
-
resourceProducts: {
|
|
3285
|
-
with: {
|
|
3286
|
-
product: {
|
|
3287
|
-
...(withPricing && {
|
|
3288
|
-
with: { price: true },
|
|
3289
|
-
}),
|
|
3290
|
-
},
|
|
3291
|
-
},
|
|
3292
|
-
},
|
|
3293
|
-
}),
|
|
3294
|
-
},
|
|
3295
|
-
})
|
|
3296
|
-
|
|
3297
|
-
const parsedEvent = ContentResourceSchema.safeParse(eventQuery)
|
|
3298
|
-
if (!parsedEvent.success) {
|
|
3299
|
-
logger.debug('Error parsing event', {
|
|
3300
|
-
eventQuery,
|
|
3301
|
-
error: parsedEvent.error,
|
|
3302
|
-
})
|
|
3303
|
-
return null
|
|
3304
|
-
}
|
|
3305
|
-
|
|
3306
|
-
return parsedEvent.data
|
|
3307
|
-
},
|
|
3308
|
-
async addResourceToProduct(options: {
|
|
3309
|
-
resource: ContentResource
|
|
3310
|
-
productId: string
|
|
3311
|
-
userId: string
|
|
3312
|
-
}) {
|
|
3313
|
-
const { resource, productId, userId } = options
|
|
3314
|
-
const product = await adapter.getProduct(productId)
|
|
3315
|
-
|
|
3316
|
-
if (!product) {
|
|
3317
|
-
throw new Error(`Product not found for id (${productId})`)
|
|
3318
|
-
}
|
|
3319
|
-
|
|
3320
|
-
await client.insert(contentResourceProduct).values({
|
|
3321
|
-
resourceId: resource.id,
|
|
3322
|
-
productId,
|
|
3323
|
-
position: product.resources?.length || 0,
|
|
3324
|
-
metadata: {
|
|
3325
|
-
addedBy: userId,
|
|
3326
|
-
},
|
|
3327
|
-
})
|
|
3328
|
-
|
|
3329
|
-
const resourceProduct =
|
|
3330
|
-
await client.query.contentResourceProduct.findFirst({
|
|
3331
|
-
where: eq(contentResourceProduct.resourceId, resource.id),
|
|
3332
|
-
with: {
|
|
3333
|
-
resource: true,
|
|
3334
|
-
product: true,
|
|
3335
|
-
},
|
|
3336
|
-
})
|
|
3337
|
-
|
|
3338
|
-
return (
|
|
3339
|
-
ContentResourceProductSchema.safeParse(resourceProduct).data || null
|
|
3340
|
-
)
|
|
3341
|
-
},
|
|
3342
|
-
async createEvent(
|
|
3343
|
-
input: {
|
|
3344
|
-
type: 'event'
|
|
3345
|
-
fields: {
|
|
3346
|
-
title: string
|
|
3347
|
-
startsAt?: Date | null | undefined
|
|
3348
|
-
endsAt?: Date | null | undefined
|
|
3349
|
-
description?: string | null | undefined
|
|
3350
|
-
price?: number | null | undefined
|
|
3351
|
-
quantity?: number | null | undefined
|
|
3352
|
-
state?: string | null | undefined
|
|
3353
|
-
visibility?: string | null | undefined
|
|
3354
|
-
slug?: string | null | undefined
|
|
3355
|
-
tagIds?:
|
|
3356
|
-
| { id: string; fields: Record<string, any> }[]
|
|
3357
|
-
| null
|
|
3358
|
-
| undefined
|
|
3359
|
-
}
|
|
3360
|
-
coupon?: {
|
|
3361
|
-
enabled: boolean
|
|
3362
|
-
percentageDiscount: string
|
|
3363
|
-
expires: Date
|
|
3364
|
-
}
|
|
3365
|
-
},
|
|
3366
|
-
userId: string,
|
|
3367
|
-
) {
|
|
3368
|
-
const hash = guid()
|
|
3369
|
-
const newEventId = slugify(`${input.type}~${hash}`)
|
|
3370
|
-
|
|
3371
|
-
const newEvent = {
|
|
3372
|
-
id: newEventId,
|
|
3373
|
-
type: 'event',
|
|
3374
|
-
fields: {
|
|
3375
|
-
title: input.fields.title,
|
|
3376
|
-
state: 'draft',
|
|
3377
|
-
visibility: 'public',
|
|
3378
|
-
slug: slugify(`${input.fields.title}~${hash}`),
|
|
3379
|
-
description: input.fields.description,
|
|
3380
|
-
startsAt: input.fields.startsAt,
|
|
3381
|
-
endsAt: input.fields.endsAt,
|
|
3382
|
-
},
|
|
3383
|
-
createdById: userId,
|
|
3384
|
-
}
|
|
3385
|
-
|
|
3386
|
-
await client.insert(contentResource).values(newEvent)
|
|
3387
|
-
const event = await adapter.getEvent(newEventId, {
|
|
3388
|
-
withResources: true,
|
|
3389
|
-
withTags: true,
|
|
3390
|
-
withProducts: true,
|
|
3391
|
-
withPricing: true,
|
|
3392
|
-
})
|
|
3393
|
-
|
|
3394
|
-
const parsedEvent = ContentResourceSchema.safeParse(event)
|
|
3395
|
-
|
|
3396
|
-
if (!parsedEvent.success) {
|
|
3397
|
-
logger.error(new Error('Error parsing event resource'))
|
|
3398
|
-
throw new Error('Error parsing event resource')
|
|
3399
|
-
}
|
|
3400
|
-
|
|
3401
|
-
// if price is provided, create a product and associate it with the event
|
|
3402
|
-
if (input.fields.price && input.fields.price > 0) {
|
|
3403
|
-
try {
|
|
3404
|
-
const product = await adapter.createProduct({
|
|
3405
|
-
name: input.fields.title,
|
|
3406
|
-
price: input.fields.price,
|
|
3407
|
-
quantityAvailable: input.fields.quantity ?? -1,
|
|
3408
|
-
type: 'live',
|
|
3409
|
-
state: 'published',
|
|
3410
|
-
visibility: 'public',
|
|
3411
|
-
})
|
|
3412
|
-
if (product) {
|
|
3413
|
-
const resourceProduct = await adapter.addResourceToProduct({
|
|
3414
|
-
resource: parsedEvent.data,
|
|
3415
|
-
productId: product.id,
|
|
3416
|
-
userId,
|
|
3417
|
-
})
|
|
3418
|
-
if (!resourceProduct) {
|
|
3419
|
-
logger.error(new Error('Failed to add resource to product'))
|
|
3420
|
-
logger.debug('event.addResourceToProduct.failed', {
|
|
3421
|
-
eventId: newEventId,
|
|
3422
|
-
productId: product.id,
|
|
3423
|
-
userId,
|
|
3424
|
-
})
|
|
3425
|
-
} else {
|
|
3426
|
-
logger.debug('event.addResourceToProduct.success', {
|
|
3427
|
-
eventId: newEventId,
|
|
3428
|
-
productId: product.id,
|
|
3429
|
-
userId,
|
|
3430
|
-
})
|
|
3431
|
-
}
|
|
3432
|
-
// Create coupon if enabled
|
|
3433
|
-
if (
|
|
3434
|
-
input.coupon?.enabled &&
|
|
3435
|
-
input.coupon.percentageDiscount &&
|
|
3436
|
-
input.coupon.expires
|
|
3437
|
-
) {
|
|
3438
|
-
try {
|
|
3439
|
-
const finalExpires = normalizeExpirationDate(
|
|
3440
|
-
input.coupon.expires,
|
|
3441
|
-
)
|
|
3442
|
-
const couponInput = {
|
|
3443
|
-
quantity: '1',
|
|
3444
|
-
maxUses: -1,
|
|
3445
|
-
expires: finalExpires || null,
|
|
3446
|
-
restrictedToProductId: product.id,
|
|
3447
|
-
percentageDiscount: input.coupon.percentageDiscount,
|
|
3448
|
-
status: 1,
|
|
3449
|
-
default: true,
|
|
3450
|
-
fields: {
|
|
3451
|
-
bypassSoldOut: false,
|
|
3452
|
-
},
|
|
3453
|
-
}
|
|
3454
|
-
await adapter.createCoupon(couponInput)
|
|
3455
|
-
logger.debug('event.create.coupon.success', {
|
|
3456
|
-
eventId: newEventId,
|
|
3457
|
-
productId: product.id,
|
|
3458
|
-
userId,
|
|
3459
|
-
percentageDiscount: input.coupon.percentageDiscount,
|
|
3460
|
-
})
|
|
3461
|
-
} catch (couponError) {
|
|
3462
|
-
logger.error(
|
|
3463
|
-
new Error(
|
|
3464
|
-
`Failed to create coupon for event: ${couponError}`,
|
|
3465
|
-
),
|
|
3466
|
-
)
|
|
3467
|
-
logger.debug('event.create.coupon.failed', {
|
|
3468
|
-
eventId: newEventId,
|
|
3469
|
-
productId: product.id,
|
|
3470
|
-
userId,
|
|
3471
|
-
})
|
|
3472
|
-
// Don't throw here - event creation should succeed even if coupon fails
|
|
3473
|
-
}
|
|
3474
|
-
}
|
|
3475
|
-
} else {
|
|
3476
|
-
logger.error(new Error('Failed to create product'))
|
|
3477
|
-
logger.debug('event.create.product.failed', {
|
|
3478
|
-
eventId: newEventId,
|
|
3479
|
-
userId,
|
|
3480
|
-
price: input.fields.price,
|
|
3481
|
-
})
|
|
3482
|
-
}
|
|
3483
|
-
} catch (error) {
|
|
3484
|
-
logger.error(
|
|
3485
|
-
new Error(`Error creating and associating product: ${error}`),
|
|
3486
|
-
)
|
|
3487
|
-
logger.debug('event.create.product.failed', {
|
|
3488
|
-
eventId: newEventId,
|
|
3489
|
-
userId,
|
|
3490
|
-
price: input.fields.price,
|
|
3491
|
-
})
|
|
3492
|
-
}
|
|
3493
|
-
}
|
|
3494
|
-
|
|
3495
|
-
// if we provide tagIds, we need to associate them with the event
|
|
3496
|
-
if (input.fields.tagIds) {
|
|
3497
|
-
try {
|
|
3498
|
-
await client.insert(contentResourceTag).values(
|
|
3499
|
-
input.fields.tagIds.map((tag) => ({
|
|
3500
|
-
contentResourceId: newEventId,
|
|
3501
|
-
tagId: tag.id,
|
|
3502
|
-
createdAt: new Date(),
|
|
3503
|
-
updatedAt: new Date(),
|
|
3504
|
-
position: 0,
|
|
3505
|
-
})),
|
|
3506
|
-
)
|
|
3507
|
-
logger.debug('event.create.tags.success', {
|
|
3508
|
-
eventId: newEventId,
|
|
3509
|
-
userId,
|
|
3510
|
-
tagIds: input.fields.tagIds,
|
|
3511
|
-
})
|
|
3512
|
-
} catch (error) {
|
|
3513
|
-
logger.error(new Error(`Error associating tags with event: ${error}`))
|
|
3514
|
-
logger.debug('event.create.tags.failed', {
|
|
3515
|
-
eventId: newEventId,
|
|
3516
|
-
userId,
|
|
3517
|
-
tagIds: input.fields.tagIds,
|
|
3518
|
-
})
|
|
3519
|
-
}
|
|
3520
|
-
}
|
|
3521
|
-
|
|
3522
|
-
return parsedEvent.data
|
|
3523
|
-
},
|
|
3524
|
-
async createEventSeries(
|
|
3525
|
-
input: {
|
|
3526
|
-
eventSeries: {
|
|
3527
|
-
type: 'event-series'
|
|
3528
|
-
fields: {
|
|
3529
|
-
title: string
|
|
3530
|
-
description?: string | undefined
|
|
3531
|
-
tagIds?:
|
|
3532
|
-
| { id: string; fields: { label: string; name: string } }[]
|
|
3533
|
-
| null
|
|
3534
|
-
| undefined
|
|
3535
|
-
}
|
|
3536
|
-
sharedFields: {
|
|
3537
|
-
price: number | null | undefined
|
|
3538
|
-
quantity: number | null | undefined
|
|
3539
|
-
}
|
|
3540
|
-
}
|
|
3541
|
-
childEvents: Array<{
|
|
3542
|
-
type: 'event'
|
|
3543
|
-
fields: {
|
|
3544
|
-
title: string
|
|
3545
|
-
startsAt: Date | null | undefined
|
|
3546
|
-
endsAt: Date | null | undefined
|
|
3547
|
-
description?: string | undefined
|
|
3548
|
-
tagIds?:
|
|
3549
|
-
| { id: string; fields: { label: string; name: string } }[]
|
|
3550
|
-
| null
|
|
3551
|
-
| undefined
|
|
3552
|
-
}
|
|
3553
|
-
}>
|
|
3554
|
-
coupon?:
|
|
3555
|
-
| {
|
|
3556
|
-
enabled: boolean
|
|
3557
|
-
expires?: Date | undefined
|
|
3558
|
-
percentageDiscount?: string | undefined
|
|
3559
|
-
}
|
|
3560
|
-
| undefined
|
|
3561
|
-
},
|
|
3562
|
-
userId: string,
|
|
3563
|
-
) {
|
|
3564
|
-
const { eventSeries: eventSeriesInput, childEvents: childEventsInput } =
|
|
3565
|
-
input
|
|
3566
|
-
|
|
3567
|
-
if (childEventsInput.length === 0) {
|
|
3568
|
-
throw new Error('At least one event is required')
|
|
3569
|
-
}
|
|
3570
|
-
|
|
3571
|
-
// Execute all database operations within a transaction
|
|
3572
|
-
const result = await client.transaction(async (tx) => {
|
|
3573
|
-
// Step 1: Create the event series resource
|
|
3574
|
-
const eventSeriesHash = guid()
|
|
3575
|
-
const eventSeriesResourceId = slugify(`event-series~${eventSeriesHash}`)
|
|
3576
|
-
|
|
3577
|
-
const newEventSeries = {
|
|
3578
|
-
id: eventSeriesResourceId,
|
|
3579
|
-
type: 'event-series',
|
|
3580
|
-
fields: {
|
|
3581
|
-
...eventSeriesInput.fields,
|
|
3582
|
-
title: eventSeriesInput.fields.title,
|
|
3583
|
-
state: 'draft',
|
|
3584
|
-
visibility: 'public',
|
|
3585
|
-
slug: slugify(
|
|
3586
|
-
`${eventSeriesInput.fields.title}~${eventSeriesHash}`,
|
|
3587
|
-
),
|
|
3588
|
-
},
|
|
3589
|
-
createdById: userId,
|
|
3590
|
-
}
|
|
3591
|
-
|
|
3592
|
-
await tx.insert(contentResource).values(newEventSeries)
|
|
3593
|
-
|
|
3594
|
-
// Fetch the created event series with relations
|
|
3595
|
-
const eventSeriesResource = await tx.query.contentResource.findFirst({
|
|
3596
|
-
where: eq(contentResource.id, eventSeriesResourceId),
|
|
3597
|
-
with: {
|
|
3598
|
-
resources: {
|
|
3599
|
-
with: {
|
|
3600
|
-
resource: {
|
|
3601
|
-
with: {
|
|
3602
|
-
resources: {
|
|
3603
|
-
with: {
|
|
3604
|
-
resource: true,
|
|
3605
|
-
},
|
|
3606
|
-
orderBy: asc(contentResourceResource.position),
|
|
3607
|
-
},
|
|
3608
|
-
},
|
|
3609
|
-
},
|
|
3610
|
-
},
|
|
3611
|
-
orderBy: asc(contentResourceResource.position),
|
|
3612
|
-
},
|
|
3613
|
-
tags: {
|
|
3614
|
-
with: {
|
|
3615
|
-
tag: true,
|
|
3616
|
-
},
|
|
3617
|
-
orderBy: asc(contentResourceTag.position),
|
|
3618
|
-
},
|
|
3619
|
-
resourceProducts: {
|
|
3620
|
-
with: {
|
|
3621
|
-
product: {
|
|
3622
|
-
with: {
|
|
3623
|
-
price: true,
|
|
3624
|
-
},
|
|
3625
|
-
},
|
|
3626
|
-
},
|
|
3627
|
-
},
|
|
3628
|
-
},
|
|
3629
|
-
})
|
|
3630
|
-
|
|
3631
|
-
const parsedEventSeries =
|
|
3632
|
-
ContentResourceSchema.safeParse(eventSeriesResource)
|
|
3633
|
-
if (!parsedEventSeries.success) {
|
|
3634
|
-
throw new Error('Error parsing event series resource')
|
|
3635
|
-
}
|
|
3636
|
-
|
|
3637
|
-
// Step 2: Associate tags with event series
|
|
3638
|
-
if (eventSeriesInput.fields.tagIds) {
|
|
3639
|
-
await tx.insert(contentResourceTag).values(
|
|
3640
|
-
eventSeriesInput.fields.tagIds.map((tag) => ({
|
|
3641
|
-
contentResourceId: eventSeriesResourceId,
|
|
3642
|
-
tagId: tag.id,
|
|
3643
|
-
createdAt: new Date(),
|
|
3644
|
-
updatedAt: new Date(),
|
|
3645
|
-
position: 0,
|
|
3646
|
-
})),
|
|
3647
|
-
)
|
|
3648
|
-
}
|
|
3649
|
-
|
|
3650
|
-
// Step 3: Create child events and associate them
|
|
3651
|
-
const createdChildEvents = []
|
|
3652
|
-
|
|
3653
|
-
for (let i = 0; i < childEventsInput.length; i++) {
|
|
3654
|
-
const childEventInput = childEventsInput[i]
|
|
3655
|
-
|
|
3656
|
-
if (!childEventInput) {
|
|
3657
|
-
throw new Error(`Child event input is required`)
|
|
3658
|
-
}
|
|
3659
|
-
|
|
3660
|
-
// Create child event
|
|
3661
|
-
const childEventHash = guid()
|
|
3662
|
-
const childEventResourceId = slugify(`event~${childEventHash}`)
|
|
3663
|
-
|
|
3664
|
-
const newChildEvent = {
|
|
3665
|
-
id: childEventResourceId,
|
|
3666
|
-
type: 'event',
|
|
3667
|
-
fields: {
|
|
3668
|
-
title: childEventInput.fields.title,
|
|
3669
|
-
startsAt: childEventInput.fields.startsAt,
|
|
3670
|
-
endsAt: childEventInput.fields.endsAt,
|
|
3671
|
-
description: childEventInput.fields.description,
|
|
3672
|
-
state: 'draft',
|
|
3673
|
-
visibility: 'public',
|
|
3674
|
-
slug: slugify(
|
|
3675
|
-
`${childEventInput.fields.title}~${childEventHash}`,
|
|
3676
|
-
),
|
|
3677
|
-
// No price/quantity - they're on the event series
|
|
3678
|
-
price: null,
|
|
3679
|
-
quantity: null,
|
|
3680
|
-
},
|
|
3681
|
-
createdById: userId,
|
|
3682
|
-
}
|
|
3683
|
-
|
|
3684
|
-
await tx.insert(contentResource).values(newChildEvent)
|
|
3685
|
-
|
|
3686
|
-
// Fetch the created child event with relations
|
|
3687
|
-
const childEventResource = await tx.query.contentResource.findFirst({
|
|
3688
|
-
where: eq(contentResource.id, childEventResourceId),
|
|
3689
|
-
with: {
|
|
3690
|
-
resources: {
|
|
3691
|
-
with: {
|
|
3692
|
-
resource: {
|
|
3693
|
-
with: {
|
|
3694
|
-
resources: {
|
|
3695
|
-
with: {
|
|
3696
|
-
resource: true,
|
|
3697
|
-
},
|
|
3698
|
-
orderBy: asc(contentResourceResource.position),
|
|
3699
|
-
},
|
|
3700
|
-
},
|
|
3701
|
-
},
|
|
3702
|
-
},
|
|
3703
|
-
orderBy: asc(contentResourceResource.position),
|
|
3704
|
-
},
|
|
3705
|
-
tags: {
|
|
3706
|
-
with: {
|
|
3707
|
-
tag: true,
|
|
3708
|
-
},
|
|
3709
|
-
orderBy: asc(contentResourceTag.position),
|
|
3710
|
-
},
|
|
3711
|
-
resourceProducts: {
|
|
3712
|
-
with: {
|
|
3713
|
-
product: {
|
|
3714
|
-
with: {
|
|
3715
|
-
price: true,
|
|
3716
|
-
},
|
|
3717
|
-
},
|
|
3718
|
-
},
|
|
3719
|
-
},
|
|
3720
|
-
},
|
|
3721
|
-
})
|
|
3722
|
-
|
|
3723
|
-
const parsedChildEvent =
|
|
3724
|
-
ContentResourceSchema.safeParse(childEventResource)
|
|
3725
|
-
if (!parsedChildEvent.success) {
|
|
3726
|
-
throw new Error(`Error parsing child event resource ${i + 1}`)
|
|
3727
|
-
}
|
|
3728
|
-
|
|
3729
|
-
// Associate tags with child event
|
|
3730
|
-
if (childEventInput.fields.tagIds) {
|
|
3731
|
-
await tx.insert(contentResourceTag).values(
|
|
3732
|
-
childEventInput.fields.tagIds.map((tag) => ({
|
|
3733
|
-
contentResourceId: childEventResourceId,
|
|
3734
|
-
tagId: tag.id,
|
|
3735
|
-
createdAt: new Date(),
|
|
3736
|
-
updatedAt: new Date(),
|
|
3737
|
-
position: 0,
|
|
3738
|
-
})),
|
|
3739
|
-
)
|
|
3740
|
-
}
|
|
3741
|
-
|
|
3742
|
-
// Associate child event with event series
|
|
3743
|
-
await tx.insert(contentResourceResource).values({
|
|
3744
|
-
resourceOfId: eventSeriesResourceId,
|
|
3745
|
-
resourceId: childEventResourceId,
|
|
3746
|
-
position: i,
|
|
3747
|
-
})
|
|
3748
|
-
|
|
3749
|
-
createdChildEvents.push(parsedChildEvent.data)
|
|
3750
|
-
}
|
|
3751
|
-
|
|
3752
|
-
return {
|
|
3753
|
-
eventSeries: parsedEventSeries.data,
|
|
3754
|
-
childEvents: createdChildEvents,
|
|
3755
|
-
}
|
|
3756
|
-
})
|
|
3757
|
-
|
|
3758
|
-
const { eventSeries, childEvents } = result
|
|
3759
|
-
|
|
3760
|
-
// Step 4: Handle product creation outside transaction
|
|
3761
|
-
if (
|
|
3762
|
-
eventSeriesInput.sharedFields.price &&
|
|
3763
|
-
eventSeriesInput.sharedFields.price > 0
|
|
3764
|
-
) {
|
|
3765
|
-
try {
|
|
3766
|
-
const product = await adapter.createProduct({
|
|
3767
|
-
name: eventSeriesInput.fields.title,
|
|
3768
|
-
price: eventSeriesInput.sharedFields.price,
|
|
3769
|
-
quantityAvailable: eventSeriesInput.sharedFields.quantity ?? -1,
|
|
3770
|
-
type: 'live',
|
|
3771
|
-
state: 'published',
|
|
3772
|
-
visibility: 'public',
|
|
3773
|
-
})
|
|
3774
|
-
if (product) {
|
|
3775
|
-
await adapter.addResourceToProduct({
|
|
3776
|
-
resource: eventSeries,
|
|
3777
|
-
productId: product.id,
|
|
3778
|
-
userId,
|
|
3779
|
-
})
|
|
3780
|
-
|
|
3781
|
-
// Create coupon if enabled
|
|
3782
|
-
if (
|
|
3783
|
-
input.coupon?.enabled &&
|
|
3784
|
-
input.coupon.percentageDiscount &&
|
|
3785
|
-
input.coupon.expires
|
|
3786
|
-
) {
|
|
3787
|
-
try {
|
|
3788
|
-
let finalExpires = input.coupon.expires
|
|
3789
|
-
if (finalExpires instanceof Date) {
|
|
3790
|
-
finalExpires = new Date(
|
|
3791
|
-
Date.UTC(
|
|
3792
|
-
finalExpires.getUTCFullYear(),
|
|
3793
|
-
finalExpires.getUTCMonth(),
|
|
3794
|
-
finalExpires.getUTCDate(),
|
|
3795
|
-
23,
|
|
3796
|
-
59,
|
|
3797
|
-
59,
|
|
3798
|
-
0,
|
|
3799
|
-
),
|
|
3800
|
-
)
|
|
3801
|
-
}
|
|
3802
|
-
const couponInput = {
|
|
3803
|
-
quantity: '1',
|
|
3804
|
-
maxUses: 1,
|
|
3805
|
-
expires: finalExpires,
|
|
3806
|
-
restrictedToProductId: product.id,
|
|
3807
|
-
percentageDiscount: input.coupon.percentageDiscount,
|
|
3808
|
-
status: 1,
|
|
3809
|
-
default: true,
|
|
3810
|
-
fields: {},
|
|
3811
|
-
}
|
|
3812
|
-
await adapter.createCoupon(couponInput)
|
|
3813
|
-
logger.debug('event.series.coupon.success', {
|
|
3814
|
-
eventSeriesId: eventSeries.id,
|
|
3815
|
-
productId: product.id,
|
|
3816
|
-
userId,
|
|
3817
|
-
percentageDiscount: input.coupon.percentageDiscount,
|
|
3818
|
-
})
|
|
3819
|
-
} catch (couponError) {
|
|
3820
|
-
logger.error(
|
|
3821
|
-
new Error(
|
|
3822
|
-
`Error creating coupon for event series: ${couponError}`,
|
|
3823
|
-
),
|
|
3824
|
-
)
|
|
3825
|
-
logger.debug('event.series.coupon.failed', {
|
|
3826
|
-
eventSeriesId: eventSeries.id,
|
|
3827
|
-
productId: product.id,
|
|
3828
|
-
userId,
|
|
3829
|
-
})
|
|
3830
|
-
// Don't throw here - event series creation should succeed even if coupon fails
|
|
3831
|
-
}
|
|
3832
|
-
}
|
|
3833
|
-
} else {
|
|
3834
|
-
logger.error(new Error('Failed to create product'))
|
|
3835
|
-
logger.debug('event.series.product.failed', {
|
|
3836
|
-
eventSeriesId: eventSeries.id,
|
|
3837
|
-
userId,
|
|
3838
|
-
price: eventSeriesInput.sharedFields.price,
|
|
3839
|
-
})
|
|
3840
|
-
}
|
|
3841
|
-
} catch (error) {
|
|
3842
|
-
logger.error(
|
|
3843
|
-
new Error(`Error creating and associating product: ${error}`),
|
|
3844
|
-
)
|
|
3845
|
-
logger.debug('event.series.product.creation.failed', {
|
|
3846
|
-
eventSeriesId: eventSeries.id,
|
|
3847
|
-
userId,
|
|
3848
|
-
})
|
|
3849
|
-
// Note: We don't throw here since the core data is already committed
|
|
3850
|
-
}
|
|
3851
|
-
}
|
|
3852
|
-
|
|
3853
|
-
return { eventSeries, childEvents }
|
|
3854
|
-
},
|
|
3855
|
-
async createCohort(
|
|
3856
|
-
input: {
|
|
3857
|
-
cohort: {
|
|
3858
|
-
title: string
|
|
3859
|
-
description?: string
|
|
3860
|
-
tagIds?:
|
|
3861
|
-
| { id: string; fields: { label: string; name: string } }[]
|
|
3862
|
-
| null
|
|
3863
|
-
}
|
|
3864
|
-
dates: {
|
|
3865
|
-
start: Date
|
|
3866
|
-
end: Date
|
|
3867
|
-
}
|
|
3868
|
-
createProduct?: boolean
|
|
3869
|
-
pricing: {
|
|
3870
|
-
price?: number | null
|
|
3871
|
-
}
|
|
3872
|
-
coupon?: {
|
|
3873
|
-
enabled: boolean
|
|
3874
|
-
percentageDiscount?: string
|
|
3875
|
-
expires?: Date
|
|
3876
|
-
}
|
|
3877
|
-
workshops: { id: string }[]
|
|
3878
|
-
},
|
|
3879
|
-
userId: string,
|
|
3880
|
-
) {
|
|
3881
|
-
const hash = guid()
|
|
3882
|
-
const cohortId = `cohort~${hash}`
|
|
3883
|
-
|
|
3884
|
-
// Transaction only handles database operations
|
|
3885
|
-
const cohort = await client.transaction(async (tx) => {
|
|
3886
|
-
// Create cohort content resource
|
|
3887
|
-
await tx.insert(contentResource).values({
|
|
3888
|
-
id: cohortId,
|
|
3889
|
-
type: 'cohort',
|
|
3890
|
-
createdById: userId,
|
|
3891
|
-
fields: {
|
|
3892
|
-
title: input.cohort.title,
|
|
3893
|
-
description: input.cohort.description,
|
|
3894
|
-
state: 'draft',
|
|
3895
|
-
visibility: 'unlisted',
|
|
3896
|
-
slug: slugify(`${input.cohort.title}~${hash}`),
|
|
3897
|
-
startsAt: input.dates.start,
|
|
3898
|
-
endsAt: input.dates.end,
|
|
3899
|
-
},
|
|
3900
|
-
})
|
|
3901
|
-
|
|
3902
|
-
// Fetch created cohort
|
|
3903
|
-
const cohort = await tx.query.contentResource.findFirst({
|
|
3904
|
-
where: eq(contentResource.id, cohortId),
|
|
3905
|
-
with: {
|
|
3906
|
-
resources: {
|
|
3907
|
-
with: {
|
|
3908
|
-
resource: true,
|
|
3909
|
-
},
|
|
3910
|
-
orderBy: asc(contentResourceResource.position),
|
|
3911
|
-
},
|
|
3912
|
-
},
|
|
3913
|
-
})
|
|
3914
|
-
|
|
3915
|
-
if (!cohort) {
|
|
3916
|
-
throw new Error('Failed to create cohort')
|
|
3917
|
-
}
|
|
3918
|
-
|
|
3919
|
-
const parsedCohort = ContentResourceSchema.safeParse(cohort)
|
|
3920
|
-
if (!parsedCohort.success) {
|
|
3921
|
-
throw new Error('Invalid cohort data')
|
|
3922
|
-
}
|
|
3923
|
-
|
|
3924
|
-
// Associate tags with cohort
|
|
3925
|
-
if (input.cohort.tagIds) {
|
|
3926
|
-
await tx.insert(contentResourceTag).values(
|
|
3927
|
-
input.cohort.tagIds.map((tag) => ({
|
|
3928
|
-
contentResourceId: cohortId,
|
|
3929
|
-
tagId: tag.id,
|
|
3930
|
-
createdAt: new Date(),
|
|
3931
|
-
updatedAt: new Date(),
|
|
3932
|
-
position: 0,
|
|
3933
|
-
})),
|
|
3934
|
-
)
|
|
3935
|
-
}
|
|
3936
|
-
|
|
3937
|
-
// Link workshops to cohort
|
|
3938
|
-
let position = 0
|
|
3939
|
-
for (const workshop of input.workshops) {
|
|
3940
|
-
await tx.insert(contentResourceResource).values({
|
|
3941
|
-
resourceOfId: cohortId,
|
|
3942
|
-
resourceId: workshop.id,
|
|
3943
|
-
position,
|
|
3944
|
-
})
|
|
3945
|
-
position++
|
|
3946
|
-
}
|
|
3947
|
-
|
|
3948
|
-
return parsedCohort.data
|
|
3949
|
-
})
|
|
3950
|
-
|
|
3951
|
-
// Handle product and coupon creation outside transaction
|
|
3952
|
-
let product: any = null
|
|
3953
|
-
if (
|
|
3954
|
-
input.createProduct &&
|
|
3955
|
-
input.pricing.price &&
|
|
3956
|
-
input.pricing.price > 0
|
|
3957
|
-
) {
|
|
3958
|
-
try {
|
|
3959
|
-
product = await adapter.createProduct({
|
|
3960
|
-
name: input.cohort.title,
|
|
3961
|
-
price: input.pricing.price,
|
|
3962
|
-
quantityAvailable: -1,
|
|
3963
|
-
type: 'cohort',
|
|
3964
|
-
state: 'published',
|
|
3965
|
-
visibility: 'public',
|
|
3966
|
-
openEnrollment: new Date().toISOString(),
|
|
3967
|
-
closeEnrollment: input.dates.start.toISOString(),
|
|
3968
|
-
})
|
|
3969
|
-
|
|
3970
|
-
if (product) {
|
|
3971
|
-
// Link product to cohort resource
|
|
3972
|
-
await client.insert(contentResourceProduct).values({
|
|
3973
|
-
resourceId: cohortId,
|
|
3974
|
-
productId: product.id,
|
|
3975
|
-
position: 0,
|
|
3976
|
-
metadata: {
|
|
3977
|
-
addedBy: userId,
|
|
3978
|
-
},
|
|
3979
|
-
})
|
|
3980
|
-
|
|
3981
|
-
// Create coupon if enabled
|
|
3982
|
-
if (
|
|
3983
|
-
input.coupon?.enabled &&
|
|
3984
|
-
input.coupon.percentageDiscount &&
|
|
3985
|
-
input.coupon.expires
|
|
3986
|
-
) {
|
|
3987
|
-
try {
|
|
3988
|
-
const finalExpires = normalizeExpirationDate(
|
|
3989
|
-
input.coupon.expires,
|
|
3990
|
-
)
|
|
3991
|
-
await adapter.createCoupon({
|
|
3992
|
-
percentageDiscount: input.coupon.percentageDiscount,
|
|
3993
|
-
expires: finalExpires || null,
|
|
3994
|
-
restrictedToProductId: product.id,
|
|
3995
|
-
default: true,
|
|
3996
|
-
maxUses: -1,
|
|
3997
|
-
quantity: '-1',
|
|
3998
|
-
status: 1,
|
|
3999
|
-
fields: {},
|
|
4000
|
-
})
|
|
4001
|
-
logger.debug('cohort.create.coupon.success', {
|
|
4002
|
-
cohortId,
|
|
4003
|
-
productId: product.id,
|
|
4004
|
-
userId,
|
|
4005
|
-
percentageDiscount: input.coupon.percentageDiscount,
|
|
4006
|
-
})
|
|
4007
|
-
} catch (couponError) {
|
|
4008
|
-
logger.error(
|
|
4009
|
-
new Error(
|
|
4010
|
-
`Failed to create coupon for cohort: ${couponError}`,
|
|
4011
|
-
),
|
|
4012
|
-
)
|
|
4013
|
-
logger.debug('cohort.create.coupon.failed', {
|
|
4014
|
-
cohortId,
|
|
4015
|
-
productId: product.id,
|
|
4016
|
-
userId,
|
|
4017
|
-
})
|
|
4018
|
-
// Don't throw - cohort creation should succeed even if coupon fails
|
|
4019
|
-
}
|
|
4020
|
-
}
|
|
4021
|
-
}
|
|
4022
|
-
} catch (productError) {
|
|
4023
|
-
logger.error(
|
|
4024
|
-
new Error(`Failed to create product for cohort: ${productError}`),
|
|
4025
|
-
)
|
|
4026
|
-
logger.debug('cohort.create.product.failed', {
|
|
4027
|
-
cohortId,
|
|
4028
|
-
userId,
|
|
4029
|
-
price: input.pricing.price,
|
|
4030
|
-
})
|
|
4031
|
-
// Don't throw - cohort creation should succeed even if product fails
|
|
4032
|
-
}
|
|
4033
|
-
}
|
|
4034
|
-
|
|
4035
|
-
return {
|
|
4036
|
-
cohort,
|
|
4037
|
-
product,
|
|
4038
|
-
}
|
|
4039
|
-
},
|
|
4040
|
-
async createWorkshop(
|
|
4041
|
-
input: {
|
|
4042
|
-
workshop: {
|
|
4043
|
-
title: string
|
|
4044
|
-
description?: string
|
|
4045
|
-
tagIds?:
|
|
4046
|
-
| { id: string; fields: { label: string; name: string } }[]
|
|
4047
|
-
| null
|
|
4048
|
-
}
|
|
4049
|
-
createProduct?: boolean
|
|
4050
|
-
pricing: {
|
|
4051
|
-
price?: number | null
|
|
4052
|
-
quantity?: number | null
|
|
4053
|
-
}
|
|
4054
|
-
coupon?: {
|
|
4055
|
-
enabled: boolean
|
|
4056
|
-
percentageDiscount?: string
|
|
4057
|
-
expires?: Date
|
|
4058
|
-
}
|
|
4059
|
-
structure: Array<
|
|
4060
|
-
| {
|
|
4061
|
-
type: 'section'
|
|
4062
|
-
title: string
|
|
4063
|
-
lessons: { title: string; videoResourceId?: string }[]
|
|
4064
|
-
}
|
|
4065
|
-
| { type: 'lesson'; title: string; videoResourceId?: string }
|
|
4066
|
-
>
|
|
4067
|
-
},
|
|
4068
|
-
userId: string,
|
|
4069
|
-
) {
|
|
4070
|
-
const hash = guid()
|
|
4071
|
-
const workshopId = `workshop~${hash}`
|
|
4072
|
-
|
|
4073
|
-
// Transaction only handles database operations
|
|
4074
|
-
const result = await client.transaction(async (tx) => {
|
|
4075
|
-
// Create workshop content resource
|
|
4076
|
-
await tx.insert(contentResource).values({
|
|
4077
|
-
id: workshopId,
|
|
4078
|
-
type: 'workshop',
|
|
4079
|
-
createdById: userId,
|
|
4080
|
-
fields: {
|
|
4081
|
-
title: input.workshop.title,
|
|
4082
|
-
description: input.workshop.description,
|
|
4083
|
-
state: 'draft',
|
|
4084
|
-
visibility: 'unlisted',
|
|
4085
|
-
slug: slugify(`${input.workshop.title}~${hash}`),
|
|
4086
|
-
},
|
|
4087
|
-
})
|
|
4088
|
-
|
|
4089
|
-
// Fetch created workshop
|
|
4090
|
-
const workshop = await tx.query.contentResource.findFirst({
|
|
4091
|
-
where: eq(contentResource.id, workshopId),
|
|
4092
|
-
with: {
|
|
4093
|
-
resources: {
|
|
4094
|
-
with: {
|
|
4095
|
-
resource: {
|
|
4096
|
-
with: {
|
|
4097
|
-
resources: {
|
|
4098
|
-
with: {
|
|
4099
|
-
resource: true,
|
|
4100
|
-
},
|
|
4101
|
-
orderBy: asc(contentResourceResource.position),
|
|
4102
|
-
},
|
|
4103
|
-
},
|
|
4104
|
-
},
|
|
4105
|
-
},
|
|
4106
|
-
orderBy: asc(contentResourceResource.position),
|
|
4107
|
-
},
|
|
4108
|
-
},
|
|
4109
|
-
})
|
|
4110
|
-
|
|
4111
|
-
if (!workshop) {
|
|
4112
|
-
throw new Error('Failed to create workshop')
|
|
4113
|
-
}
|
|
4114
|
-
|
|
4115
|
-
const parsedWorkshop = ContentResourceSchema.safeParse(workshop)
|
|
4116
|
-
if (!parsedWorkshop.success) {
|
|
4117
|
-
throw new Error('Invalid workshop data')
|
|
4118
|
-
}
|
|
4119
|
-
|
|
4120
|
-
// Associate tags with workshop
|
|
4121
|
-
if (input.workshop.tagIds) {
|
|
4122
|
-
await tx.insert(contentResourceTag).values(
|
|
4123
|
-
input.workshop.tagIds.map((tag) => ({
|
|
4124
|
-
contentResourceId: workshopId,
|
|
4125
|
-
tagId: tag.id,
|
|
4126
|
-
createdAt: new Date(),
|
|
4127
|
-
updatedAt: new Date(),
|
|
4128
|
-
position: 0,
|
|
4129
|
-
})),
|
|
4130
|
-
)
|
|
4131
|
-
}
|
|
4132
|
-
|
|
4133
|
-
// Create sections and lessons
|
|
4134
|
-
const createdSections: ContentResource[] = []
|
|
4135
|
-
const createdLessons: ContentResource[] = []
|
|
4136
|
-
let position = 0
|
|
4137
|
-
|
|
4138
|
-
for (const item of input.structure) {
|
|
4139
|
-
if (item.type === 'section') {
|
|
4140
|
-
// Create section
|
|
4141
|
-
const sectionHash = guid()
|
|
4142
|
-
const sectionId = `section~${sectionHash}`
|
|
4143
|
-
|
|
4144
|
-
await tx.insert(contentResource).values({
|
|
4145
|
-
id: sectionId,
|
|
4146
|
-
type: 'section',
|
|
4147
|
-
createdById: userId,
|
|
4148
|
-
fields: {
|
|
4149
|
-
title: item.title,
|
|
4150
|
-
state: 'draft',
|
|
4151
|
-
visibility: 'unlisted',
|
|
4152
|
-
slug: slugify(`${item.title}~${sectionHash}`),
|
|
4153
|
-
},
|
|
4154
|
-
})
|
|
4155
|
-
|
|
4156
|
-
// Link section to workshop
|
|
4157
|
-
await tx.insert(contentResourceResource).values({
|
|
4158
|
-
resourceOfId: workshopId,
|
|
4159
|
-
resourceId: sectionId,
|
|
4160
|
-
position,
|
|
4161
|
-
})
|
|
4162
|
-
|
|
4163
|
-
const section = await tx.query.contentResource.findFirst({
|
|
4164
|
-
where: eq(contentResource.id, sectionId),
|
|
4165
|
-
})
|
|
4166
|
-
|
|
4167
|
-
if (section) {
|
|
4168
|
-
createdSections.push(section as ContentResource)
|
|
4169
|
-
}
|
|
4170
|
-
|
|
4171
|
-
// Create lessons in section
|
|
4172
|
-
let lessonPosition = 0
|
|
4173
|
-
for (const lessonData of item.lessons) {
|
|
4174
|
-
const lessonHash = guid()
|
|
4175
|
-
const lessonId = `lesson_${lessonHash}`
|
|
4176
|
-
|
|
4177
|
-
await tx.insert(contentResource).values({
|
|
4178
|
-
id: lessonId,
|
|
4179
|
-
type: 'lesson',
|
|
4180
|
-
createdById: userId,
|
|
4181
|
-
fields: {
|
|
4182
|
-
title: lessonData.title,
|
|
4183
|
-
state: 'draft',
|
|
4184
|
-
visibility: 'unlisted',
|
|
4185
|
-
slug: slugify(`${lessonData.title}~${lessonHash}`),
|
|
4186
|
-
lessonType: 'lesson',
|
|
4187
|
-
},
|
|
4188
|
-
})
|
|
4189
|
-
|
|
4190
|
-
// Link video if provided
|
|
4191
|
-
if (lessonData.videoResourceId) {
|
|
4192
|
-
await tx.insert(contentResourceResource).values({
|
|
4193
|
-
resourceOfId: lessonId,
|
|
4194
|
-
resourceId: lessonData.videoResourceId,
|
|
4195
|
-
position: 0,
|
|
4196
|
-
})
|
|
4197
|
-
}
|
|
4198
|
-
|
|
4199
|
-
// Link lesson to section
|
|
4200
|
-
await tx.insert(contentResourceResource).values({
|
|
4201
|
-
resourceOfId: sectionId,
|
|
4202
|
-
resourceId: lessonId,
|
|
4203
|
-
position: lessonPosition,
|
|
4204
|
-
})
|
|
4205
|
-
|
|
4206
|
-
const lesson = await tx.query.contentResource.findFirst({
|
|
4207
|
-
where: eq(contentResource.id, lessonId),
|
|
4208
|
-
})
|
|
4209
|
-
|
|
4210
|
-
if (lesson) {
|
|
4211
|
-
createdLessons.push(lesson as ContentResource)
|
|
4212
|
-
}
|
|
4213
|
-
|
|
4214
|
-
lessonPosition++
|
|
4215
|
-
}
|
|
4216
|
-
|
|
4217
|
-
position++
|
|
4218
|
-
} else {
|
|
4219
|
-
// Create top-level lesson
|
|
4220
|
-
const lessonHash = guid()
|
|
4221
|
-
const lessonId = `lesson_${lessonHash}`
|
|
4222
|
-
|
|
4223
|
-
await tx.insert(contentResource).values({
|
|
4224
|
-
id: lessonId,
|
|
4225
|
-
type: 'lesson',
|
|
4226
|
-
createdById: userId,
|
|
4227
|
-
fields: {
|
|
4228
|
-
title: item.title,
|
|
4229
|
-
state: 'draft',
|
|
4230
|
-
visibility: 'unlisted',
|
|
4231
|
-
slug: slugify(`${item.title}~${lessonHash}`),
|
|
4232
|
-
lessonType: 'lesson',
|
|
4233
|
-
},
|
|
4234
|
-
})
|
|
4235
|
-
|
|
4236
|
-
// Link video if provided
|
|
4237
|
-
if (item.videoResourceId) {
|
|
4238
|
-
await tx.insert(contentResourceResource).values({
|
|
4239
|
-
resourceOfId: lessonId,
|
|
4240
|
-
resourceId: item.videoResourceId,
|
|
4241
|
-
position: 0,
|
|
4242
|
-
})
|
|
4243
|
-
}
|
|
4244
|
-
|
|
4245
|
-
// Link lesson to workshop
|
|
4246
|
-
await tx.insert(contentResourceResource).values({
|
|
4247
|
-
resourceOfId: workshopId,
|
|
4248
|
-
resourceId: lessonId,
|
|
4249
|
-
position,
|
|
4250
|
-
})
|
|
4251
|
-
|
|
4252
|
-
const lesson = await tx.query.contentResource.findFirst({
|
|
4253
|
-
where: eq(contentResource.id, lessonId),
|
|
4254
|
-
})
|
|
4255
|
-
|
|
4256
|
-
if (lesson) {
|
|
4257
|
-
createdLessons.push(lesson as ContentResource)
|
|
4258
|
-
}
|
|
4259
|
-
|
|
4260
|
-
position++
|
|
4261
|
-
}
|
|
4262
|
-
}
|
|
4263
|
-
|
|
4264
|
-
return {
|
|
4265
|
-
workshop: parsedWorkshop.data,
|
|
4266
|
-
sections: createdSections,
|
|
4267
|
-
lessons: createdLessons,
|
|
4268
|
-
}
|
|
4269
|
-
})
|
|
4270
|
-
|
|
4271
|
-
// Handle product and coupon creation outside transaction
|
|
4272
|
-
let product: any = null
|
|
4273
|
-
if (
|
|
4274
|
-
input.createProduct &&
|
|
4275
|
-
input.pricing.price &&
|
|
4276
|
-
input.pricing.price > 0
|
|
4277
|
-
) {
|
|
4278
|
-
try {
|
|
4279
|
-
product = await adapter.createProduct({
|
|
4280
|
-
name: input.workshop.title,
|
|
4281
|
-
price: input.pricing.price,
|
|
4282
|
-
quantityAvailable: input.pricing.quantity ?? -1,
|
|
4283
|
-
type: 'self-paced',
|
|
4284
|
-
state: 'published',
|
|
4285
|
-
visibility: 'public',
|
|
4286
|
-
})
|
|
4287
|
-
|
|
4288
|
-
if (product) {
|
|
4289
|
-
// Link product to workshop resource
|
|
4290
|
-
await client.insert(contentResourceProduct).values({
|
|
4291
|
-
resourceId: workshopId,
|
|
4292
|
-
productId: product.id,
|
|
4293
|
-
position: 0,
|
|
4294
|
-
metadata: {
|
|
4295
|
-
addedBy: userId,
|
|
4296
|
-
},
|
|
4297
|
-
})
|
|
4298
|
-
|
|
4299
|
-
// Create coupon if enabled
|
|
4300
|
-
if (
|
|
4301
|
-
input.coupon?.enabled &&
|
|
4302
|
-
input.coupon.percentageDiscount &&
|
|
4303
|
-
input.coupon.expires
|
|
4304
|
-
) {
|
|
4305
|
-
try {
|
|
4306
|
-
const finalExpires = normalizeExpirationDate(
|
|
4307
|
-
input.coupon.expires,
|
|
4308
|
-
)
|
|
4309
|
-
await adapter.createCoupon({
|
|
4310
|
-
percentageDiscount: input.coupon.percentageDiscount,
|
|
4311
|
-
expires: finalExpires || null,
|
|
4312
|
-
restrictedToProductId: product.id,
|
|
4313
|
-
default: true,
|
|
4314
|
-
maxUses: -1,
|
|
4315
|
-
quantity: '-1',
|
|
4316
|
-
status: 1,
|
|
4317
|
-
fields: {},
|
|
4318
|
-
})
|
|
4319
|
-
logger.debug('workshop.create.coupon.success', {
|
|
4320
|
-
workshopId,
|
|
4321
|
-
productId: product.id,
|
|
4322
|
-
userId,
|
|
4323
|
-
percentageDiscount: input.coupon.percentageDiscount,
|
|
4324
|
-
})
|
|
4325
|
-
} catch (couponError) {
|
|
4326
|
-
logger.error(
|
|
4327
|
-
new Error(
|
|
4328
|
-
`Failed to create coupon for workshop: ${couponError}`,
|
|
4329
|
-
),
|
|
4330
|
-
)
|
|
4331
|
-
logger.debug('workshop.create.coupon.failed', {
|
|
4332
|
-
workshopId,
|
|
4333
|
-
productId: product.id,
|
|
4334
|
-
userId,
|
|
4335
|
-
})
|
|
4336
|
-
// Don't throw - workshop creation should succeed even if coupon fails
|
|
4337
|
-
}
|
|
4338
|
-
}
|
|
4339
|
-
}
|
|
4340
|
-
} catch (productError) {
|
|
4341
|
-
logger.error(
|
|
4342
|
-
new Error(`Failed to create product for workshop: ${productError}`),
|
|
4343
|
-
)
|
|
4344
|
-
logger.debug('workshop.create.product.failed', {
|
|
4345
|
-
workshopId,
|
|
4346
|
-
userId,
|
|
4347
|
-
price: input.pricing.price,
|
|
4348
|
-
})
|
|
4349
|
-
// Don't throw - workshop creation should succeed even if product fails
|
|
4350
|
-
}
|
|
4351
|
-
}
|
|
4352
|
-
|
|
4353
|
-
return {
|
|
4354
|
-
...result,
|
|
4355
|
-
product,
|
|
4356
|
-
}
|
|
4357
|
-
},
|
|
4358
|
-
async createCoupon(input: {
|
|
4359
|
-
quantity: string
|
|
4360
|
-
maxUses: number
|
|
4361
|
-
expires: Date | null
|
|
4362
|
-
restrictedToProductId: string | null
|
|
4363
|
-
percentageDiscount: string
|
|
4364
|
-
status: number
|
|
4365
|
-
default: boolean
|
|
4366
|
-
fields: Record<string, any>
|
|
4367
|
-
}) {
|
|
4368
|
-
function findClosestDiscount(percentOff: number) {
|
|
4369
|
-
// we want a fraction so if it is whole number, we make it fractional
|
|
4370
|
-
percentOff = percentOff <= 1 ? percentOff : percentOff / 100
|
|
4371
|
-
return [1, 0.95, 0.9, 0.75, 0.6, 0.5, 0.4, 0.25, 0.1].reduce((a, b) => {
|
|
4372
|
-
let aDiff = Math.abs(a - percentOff)
|
|
4373
|
-
let bDiff = Math.abs(b - percentOff)
|
|
4374
|
-
|
|
4375
|
-
if (aDiff === bDiff) {
|
|
4376
|
-
// Choose largest vs smallest (> vs <)
|
|
4377
|
-
return a > b ? a : b
|
|
4378
|
-
} else {
|
|
4379
|
-
return bDiff < aDiff ? b : a
|
|
4380
|
-
}
|
|
4381
|
-
})
|
|
4382
|
-
}
|
|
4383
|
-
|
|
4384
|
-
const percentageDiscount = findClosestDiscount(
|
|
4385
|
-
Number(input.percentageDiscount) * 100,
|
|
4386
|
-
)
|
|
4387
|
-
|
|
4388
|
-
const merchantCouponResult =
|
|
4389
|
-
percentageDiscount < 1
|
|
4390
|
-
? await client.query.merchantCoupon.findFirst({
|
|
4391
|
-
where: and(
|
|
4392
|
-
eq(
|
|
4393
|
-
merchantCoupon.percentageDiscount,
|
|
4394
|
-
percentageDiscount.toString(),
|
|
4395
|
-
),
|
|
4396
|
-
eq(merchantCoupon.type, 'special'),
|
|
4397
|
-
),
|
|
4398
|
-
})
|
|
4399
|
-
: null
|
|
4400
|
-
|
|
4401
|
-
if (!merchantCouponResult) {
|
|
4402
|
-
return []
|
|
4403
|
-
}
|
|
4404
|
-
|
|
4405
|
-
const codesArray: string[] = []
|
|
4406
|
-
const requestedQuantity = Number(input.quantity)
|
|
4407
|
-
const couponQuantity = requestedQuantity < 0 ? 1 : requestedQuantity
|
|
4408
|
-
|
|
4409
|
-
await client.transaction(async (trx) => {
|
|
4410
|
-
// insert coupon for CouponInput quantity
|
|
4411
|
-
for (let i = 0; i < couponQuantity; i++) {
|
|
4412
|
-
const id = `coupon_${guid()}`
|
|
4413
|
-
await trx.insert(coupon).values({
|
|
4414
|
-
...input,
|
|
4415
|
-
merchantCouponId: merchantCouponResult.id as string,
|
|
4416
|
-
id,
|
|
4417
|
-
})
|
|
4418
|
-
codesArray.push(id)
|
|
4419
|
-
}
|
|
4420
|
-
})
|
|
4421
|
-
|
|
4422
|
-
return codesArray
|
|
4423
|
-
},
|
|
4424
|
-
async createUser(data) {
|
|
4425
|
-
const id = crypto.randomUUID()
|
|
4426
|
-
try {
|
|
4427
|
-
await client.insert(users).values({ ...data, id })
|
|
4428
|
-
|
|
4429
|
-
await provisionPersonalOrganization(adapter, {
|
|
4430
|
-
userId: id,
|
|
4431
|
-
email: data.email,
|
|
4432
|
-
})
|
|
4433
|
-
|
|
4434
|
-
const user = await adapter.getUser?.(id)
|
|
4435
|
-
|
|
4436
|
-
if (!user) {
|
|
4437
|
-
throw new Error('Failed to get user')
|
|
4438
|
-
}
|
|
4439
|
-
|
|
4440
|
-
return user
|
|
4441
|
-
} catch (error) {
|
|
4442
|
-
logger.error(toError(error), {
|
|
4443
|
-
email: data.email,
|
|
4444
|
-
event: 'adapter.user.create.failed',
|
|
4445
|
-
userId: id,
|
|
4446
|
-
})
|
|
4447
|
-
throw error
|
|
4448
|
-
}
|
|
4449
|
-
},
|
|
4450
|
-
async getUser(data) {
|
|
4451
|
-
return (
|
|
4452
|
-
(await client
|
|
4453
|
-
.select()
|
|
4454
|
-
.from(users)
|
|
4455
|
-
.where(eq(users.id, data))
|
|
4456
|
-
.then((res) => res[0])) ?? null
|
|
4457
|
-
)
|
|
4458
|
-
},
|
|
4459
|
-
async getUserByEmail(data) {
|
|
4460
|
-
return (
|
|
4461
|
-
(await client
|
|
4462
|
-
.select()
|
|
4463
|
-
.from(users)
|
|
4464
|
-
.where(eq(users.email, data))
|
|
4465
|
-
.then((res) => res[0])) ?? null
|
|
4466
|
-
)
|
|
4467
|
-
},
|
|
4468
|
-
async createSession(data) {
|
|
4469
|
-
await client.insert(sessions).values(data)
|
|
4470
|
-
|
|
4471
|
-
return await client
|
|
4472
|
-
.select()
|
|
4473
|
-
.from(sessions)
|
|
4474
|
-
.where(eq(sessions.sessionToken, data.sessionToken))
|
|
4475
|
-
.then((res) => res[0] as CourseBuilderAuthSession)
|
|
4476
|
-
},
|
|
4477
|
-
async getSessionAndUser(data) {
|
|
4478
|
-
return (
|
|
4479
|
-
(await client
|
|
4480
|
-
.select({
|
|
4481
|
-
session: sessions,
|
|
4482
|
-
user: users,
|
|
4483
|
-
})
|
|
4484
|
-
.from(sessions)
|
|
4485
|
-
.where(eq(sessions.sessionToken, data))
|
|
4486
|
-
.innerJoin(users, eq(users.id, sessions.userId))
|
|
4487
|
-
.then((res) => res[0])) ?? null
|
|
4488
|
-
)
|
|
4489
|
-
},
|
|
4490
|
-
async updateUser(data) {
|
|
4491
|
-
if (!data.id) {
|
|
4492
|
-
throw new Error('No user id.')
|
|
4493
|
-
}
|
|
4494
|
-
|
|
4495
|
-
await client.update(users).set(data).where(eq(users.id, data.id))
|
|
4496
|
-
|
|
4497
|
-
return await client
|
|
4498
|
-
.select()
|
|
4499
|
-
.from(users)
|
|
4500
|
-
.where(eq(users.id, data.id))
|
|
4501
|
-
.then((res) => res[0] as CourseBuilderUser)
|
|
4502
|
-
},
|
|
4503
|
-
async updateSession(data) {
|
|
4504
|
-
await client
|
|
4505
|
-
.update(sessions)
|
|
4506
|
-
.set(data)
|
|
4507
|
-
.where(eq(sessions.sessionToken, data.sessionToken))
|
|
4508
|
-
|
|
4509
|
-
return await client
|
|
4510
|
-
.select()
|
|
4511
|
-
.from(sessions)
|
|
4512
|
-
.where(eq(sessions.sessionToken, data.sessionToken))
|
|
4513
|
-
.then((res) => res[0])
|
|
4514
|
-
},
|
|
4515
|
-
async linkAccount(rawAccount) {
|
|
4516
|
-
await client.insert(accounts).values(rawAccount)
|
|
4517
|
-
},
|
|
4518
|
-
async getUserByAccount(account) {
|
|
4519
|
-
const dbAccount =
|
|
4520
|
-
(await client
|
|
4521
|
-
.select()
|
|
4522
|
-
.from(accounts)
|
|
4523
|
-
.where(
|
|
4524
|
-
and(
|
|
4525
|
-
eq(accounts.providerAccountId, account.providerAccountId),
|
|
4526
|
-
eq(accounts.provider, account.provider),
|
|
4527
|
-
),
|
|
4528
|
-
)
|
|
4529
|
-
.leftJoin(users, eq(accounts.userId, users.id))
|
|
4530
|
-
.then((res) => res[0])) ?? null
|
|
4531
|
-
|
|
4532
|
-
if (!dbAccount) {
|
|
4533
|
-
return null
|
|
4534
|
-
}
|
|
4535
|
-
|
|
4536
|
-
return dbAccount.User
|
|
4537
|
-
},
|
|
4538
|
-
async deleteSession(sessionToken) {
|
|
4539
|
-
const session =
|
|
4540
|
-
(await client
|
|
4541
|
-
.select()
|
|
4542
|
-
.from(sessions)
|
|
4543
|
-
.where(eq(sessions.sessionToken, sessionToken))
|
|
4544
|
-
.then((res) => res[0])) ?? null
|
|
4545
|
-
|
|
4546
|
-
await client
|
|
4547
|
-
.delete(sessions)
|
|
4548
|
-
.where(eq(sessions.sessionToken, sessionToken))
|
|
4549
|
-
|
|
4550
|
-
return session
|
|
4551
|
-
},
|
|
4552
|
-
async createVerificationToken(token) {
|
|
4553
|
-
await client.insert(verificationTokens).values(token)
|
|
4554
|
-
|
|
4555
|
-
return await client
|
|
4556
|
-
.select()
|
|
4557
|
-
.from(verificationTokens)
|
|
4558
|
-
.where(eq(verificationTokens.identifier, token.identifier))
|
|
4559
|
-
.then((res) => res[0])
|
|
4560
|
-
},
|
|
4561
|
-
async useVerificationToken(token) {
|
|
4562
|
-
try {
|
|
4563
|
-
const deletedToken =
|
|
4564
|
-
(await client
|
|
4565
|
-
.select()
|
|
4566
|
-
.from(verificationTokens)
|
|
4567
|
-
.where(
|
|
4568
|
-
and(
|
|
4569
|
-
eq(verificationTokens.identifier, token.identifier),
|
|
4570
|
-
eq(verificationTokens.token, token.token),
|
|
4571
|
-
),
|
|
4572
|
-
)
|
|
4573
|
-
.then((res) => res[0])) ?? null
|
|
4574
|
-
|
|
4575
|
-
if (deletedToken?.createdAt) {
|
|
4576
|
-
const TIMEOUT_IN_SECONDS = 90
|
|
4577
|
-
const expireMultipleClicks = addSeconds(
|
|
4578
|
-
deletedToken.createdAt,
|
|
4579
|
-
TIMEOUT_IN_SECONDS,
|
|
4580
|
-
)
|
|
4581
|
-
const now = new Date()
|
|
4582
|
-
|
|
4583
|
-
if (isAfter(expireMultipleClicks, now)) {
|
|
4584
|
-
// @ts-ignore
|
|
4585
|
-
const { id: _, ...verificationToken } = token
|
|
4586
|
-
return deletedToken
|
|
4587
|
-
} else {
|
|
4588
|
-
await client
|
|
4589
|
-
.delete(verificationTokens)
|
|
4590
|
-
.where(
|
|
4591
|
-
and(
|
|
4592
|
-
eq(verificationTokens.identifier, token.identifier),
|
|
4593
|
-
eq(verificationTokens.token, token.token),
|
|
4594
|
-
),
|
|
4595
|
-
)
|
|
4596
|
-
return deletedToken
|
|
4597
|
-
}
|
|
4598
|
-
}
|
|
4599
|
-
|
|
4600
|
-
return deletedToken
|
|
4601
|
-
} catch (err) {
|
|
4602
|
-
throw new Error('No verification token found.')
|
|
4603
|
-
}
|
|
4604
|
-
},
|
|
4605
|
-
async deleteUser(id) {
|
|
4606
|
-
const user = await client
|
|
4607
|
-
.select()
|
|
4608
|
-
.from(users)
|
|
4609
|
-
.where(eq(users.id, id))
|
|
4610
|
-
.then((res) => res[0] ?? null)
|
|
4611
|
-
|
|
4612
|
-
await client.delete(users).where(eq(users.id, id))
|
|
4613
|
-
await client.delete(sessions).where(eq(sessions.userId, id))
|
|
4614
|
-
await client.delete(accounts).where(eq(accounts.userId, id))
|
|
4615
|
-
|
|
4616
|
-
return user
|
|
4617
|
-
},
|
|
4618
|
-
async unlinkAccount(account) {
|
|
4619
|
-
await client
|
|
4620
|
-
.delete(accounts)
|
|
4621
|
-
.where(
|
|
4622
|
-
and(
|
|
4623
|
-
eq(accounts.providerAccountId, account.providerAccountId),
|
|
4624
|
-
eq(accounts.provider, account.provider),
|
|
4625
|
-
),
|
|
4626
|
-
)
|
|
4627
|
-
|
|
4628
|
-
return undefined
|
|
4629
|
-
},
|
|
4630
|
-
createOrganization: async (options: { name: string }) => {
|
|
4631
|
-
const organizationId = crypto.randomUUID()
|
|
4632
|
-
await client.insert(organizationTable).values({
|
|
4633
|
-
...options,
|
|
4634
|
-
id: organizationId,
|
|
4635
|
-
})
|
|
4636
|
-
|
|
4637
|
-
return adapter.getOrganization(organizationId)
|
|
4638
|
-
},
|
|
4639
|
-
getOrganization: async (organizationId: string) => {
|
|
4640
|
-
return OrganizationSchema.parse(
|
|
4641
|
-
await client.query.organization.findFirst({
|
|
4642
|
-
where: eq(organizationTable.id, organizationId),
|
|
4643
|
-
}),
|
|
4644
|
-
)
|
|
4645
|
-
},
|
|
4646
|
-
addMemberToOrganization: async (options: {
|
|
4647
|
-
organizationId: string
|
|
4648
|
-
userId: string
|
|
4649
|
-
invitedById: string
|
|
4650
|
-
}) => {
|
|
4651
|
-
const currentMembership =
|
|
4652
|
-
await client.query.organizationMemberships.findFirst({
|
|
4653
|
-
where: and(
|
|
4654
|
-
eq(
|
|
4655
|
-
organizationMembershipTable.organizationId,
|
|
4656
|
-
options.organizationId,
|
|
4657
|
-
),
|
|
4658
|
-
eq(organizationMembershipTable.userId, options.userId),
|
|
4659
|
-
),
|
|
4660
|
-
with: {
|
|
4661
|
-
organization: true,
|
|
4662
|
-
user: true,
|
|
4663
|
-
},
|
|
4664
|
-
})
|
|
4665
|
-
|
|
4666
|
-
if (currentMembership) {
|
|
4667
|
-
return OrganizationMemberSchema.parse(currentMembership)
|
|
4668
|
-
} else {
|
|
4669
|
-
const id = crypto.randomUUID()
|
|
4670
|
-
await client.insert(organizationMembershipTable).values({
|
|
4671
|
-
...options,
|
|
4672
|
-
id,
|
|
4673
|
-
})
|
|
4674
|
-
|
|
4675
|
-
return OrganizationMemberSchema.parse(
|
|
4676
|
-
await client.query.organizationMemberships.findFirst({
|
|
4677
|
-
where: eq(organizationMembershipTable.id, id),
|
|
4678
|
-
with: {
|
|
4679
|
-
organization: true,
|
|
4680
|
-
user: true,
|
|
4681
|
-
},
|
|
4682
|
-
}),
|
|
4683
|
-
)
|
|
4684
|
-
}
|
|
4685
|
-
},
|
|
4686
|
-
removeMemberFromOrganization: async (options: {
|
|
4687
|
-
organizationId: string
|
|
4688
|
-
userId: string
|
|
4689
|
-
}) => {
|
|
4690
|
-
await client
|
|
4691
|
-
.delete(organizationMembershipTable)
|
|
4692
|
-
.where(
|
|
4693
|
-
and(
|
|
4694
|
-
eq(
|
|
4695
|
-
organizationMembershipTable.organizationId,
|
|
4696
|
-
options.organizationId,
|
|
4697
|
-
),
|
|
4698
|
-
eq(organizationMembershipTable.userId, options.userId),
|
|
4699
|
-
),
|
|
4700
|
-
)
|
|
4701
|
-
},
|
|
4702
|
-
addRoleForMember: async (options: {
|
|
4703
|
-
organizationId: string
|
|
4704
|
-
memberId: string
|
|
4705
|
-
role: string
|
|
4706
|
-
}) => {
|
|
4707
|
-
const existingRole = z
|
|
4708
|
-
.object({
|
|
4709
|
-
id: z.string(),
|
|
4710
|
-
})
|
|
4711
|
-
.nullish()
|
|
4712
|
-
.parse(
|
|
4713
|
-
await client.query.roles.findFirst({
|
|
4714
|
-
where: and(
|
|
4715
|
-
eq(roleTable.organizationId, options.organizationId),
|
|
4716
|
-
eq(roleTable.name, options.role),
|
|
4717
|
-
),
|
|
4718
|
-
}),
|
|
4719
|
-
)
|
|
4720
|
-
|
|
4721
|
-
const roleId = existingRole?.id || crypto.randomUUID()
|
|
4722
|
-
|
|
4723
|
-
if (!existingRole) {
|
|
4724
|
-
await client.insert(roleTable).values({
|
|
4725
|
-
name: options.role,
|
|
4726
|
-
organizationId: options.organizationId,
|
|
4727
|
-
id: roleId,
|
|
4728
|
-
})
|
|
4729
|
-
}
|
|
4730
|
-
|
|
4731
|
-
const currentOrgMembershipRole =
|
|
4732
|
-
await client.query.organizationMembershipRoles.findFirst({
|
|
4733
|
-
where: and(
|
|
4734
|
-
eq(
|
|
4735
|
-
organizationMembershipRoleTable.organizationMembershipId,
|
|
4736
|
-
options.memberId,
|
|
4737
|
-
),
|
|
4738
|
-
eq(organizationMembershipRoleTable.roleId, roleId),
|
|
4739
|
-
),
|
|
4740
|
-
})
|
|
4741
|
-
|
|
4742
|
-
if (!currentOrgMembershipRole) {
|
|
4743
|
-
await client.insert(organizationMembershipRoleTable).values({
|
|
4744
|
-
organizationId: options.organizationId,
|
|
4745
|
-
organizationMembershipId: options.memberId,
|
|
4746
|
-
roleId,
|
|
4747
|
-
})
|
|
4748
|
-
}
|
|
4749
|
-
},
|
|
4750
|
-
removeRoleForMember: async (options: {
|
|
4751
|
-
organizationId: string
|
|
4752
|
-
memberId: string
|
|
4753
|
-
role: string
|
|
4754
|
-
}) => {
|
|
4755
|
-
const existingRole = z
|
|
4756
|
-
.object({
|
|
4757
|
-
id: z.string(),
|
|
4758
|
-
})
|
|
4759
|
-
.nullable()
|
|
4760
|
-
.parse(
|
|
4761
|
-
await client.query.roles.findFirst({
|
|
4762
|
-
where: and(
|
|
4763
|
-
eq(roleTable.organizationId, options.organizationId),
|
|
4764
|
-
eq(roleTable.name, options.role),
|
|
4765
|
-
),
|
|
4766
|
-
}),
|
|
4767
|
-
)
|
|
4768
|
-
|
|
4769
|
-
const roleId = existingRole?.id
|
|
4770
|
-
|
|
4771
|
-
if (roleId) {
|
|
4772
|
-
await client
|
|
4773
|
-
.delete(organizationMembershipRoleTable)
|
|
4774
|
-
.where(eq(organizationMembershipRoleTable.roleId, roleId))
|
|
4775
|
-
}
|
|
4776
|
-
},
|
|
4777
|
-
getMembershipsForUser: async (userId: string) => {
|
|
4778
|
-
return OrganizationMemberSchema.array().parse(
|
|
4779
|
-
(await client.query.organizationMemberships.findMany({
|
|
4780
|
-
where: eq(organizationMembershipTable.userId, userId),
|
|
4781
|
-
with: {
|
|
4782
|
-
organization: true,
|
|
4783
|
-
user: true,
|
|
4784
|
-
},
|
|
4785
|
-
})) || [],
|
|
4786
|
-
)
|
|
4787
|
-
},
|
|
4788
|
-
getOrganizationMembers: async (organizationId: string) => {
|
|
4789
|
-
return OrganizationMemberSchema.array().parse(
|
|
4790
|
-
(await client.query.organizationMemberships.findMany({
|
|
4791
|
-
where: eq(organizationMembershipTable.organizationId, organizationId),
|
|
4792
|
-
})) || [],
|
|
4793
|
-
)
|
|
4794
|
-
},
|
|
4795
|
-
createSubscription: async (options: {
|
|
4796
|
-
organizationId: string
|
|
4797
|
-
merchantSubscriptionId: string
|
|
4798
|
-
productId: string
|
|
4799
|
-
}) => {
|
|
4800
|
-
const id = `sub_${crypto.randomUUID()}`
|
|
4801
|
-
await client.insert(subscriptionTable).values({
|
|
4802
|
-
...options,
|
|
4803
|
-
id,
|
|
4804
|
-
})
|
|
4805
|
-
|
|
4806
|
-
return SubscriptionSchema.parse(
|
|
4807
|
-
await client.query.subscription.findFirst({
|
|
4808
|
-
where: eq(subscriptionTable.id, id),
|
|
4809
|
-
with: {
|
|
4810
|
-
product: true,
|
|
4811
|
-
},
|
|
4812
|
-
}),
|
|
4813
|
-
)
|
|
4814
|
-
},
|
|
4815
|
-
getMerchantSubscription: async (merchantSubscriptionId: string) => {
|
|
4816
|
-
return MerchantSubscriptionSchema.parse(
|
|
4817
|
-
await client.query.merchantSubscription.findFirst({
|
|
4818
|
-
where: eq(merchantSubscriptionTable.id, merchantSubscriptionId),
|
|
4819
|
-
}),
|
|
4820
|
-
)
|
|
4821
|
-
},
|
|
4822
|
-
createMerchantSubscription: async (options: {
|
|
4823
|
-
merchantAccountId: string
|
|
4824
|
-
merchantCustomerId: string
|
|
4825
|
-
merchantProductId: string
|
|
4826
|
-
identifier: string
|
|
4827
|
-
}) => {
|
|
4828
|
-
const id = crypto.randomUUID()
|
|
4829
|
-
await client.insert(merchantSubscriptionTable).values({
|
|
4830
|
-
...options,
|
|
4831
|
-
id,
|
|
4832
|
-
})
|
|
4833
|
-
|
|
4834
|
-
return MerchantSubscriptionSchema.parse(
|
|
4835
|
-
await client.query.merchantSubscription.findFirst({
|
|
4836
|
-
where: eq(merchantSubscriptionTable.id, id),
|
|
4837
|
-
}),
|
|
4838
|
-
)
|
|
4839
|
-
},
|
|
4840
|
-
updateMerchantSubscription: async (options: {
|
|
4841
|
-
merchantSubscriptionId: string
|
|
4842
|
-
status: string
|
|
4843
|
-
}) => {
|
|
4844
|
-
throw new Error('Not implemented')
|
|
4845
|
-
},
|
|
4846
|
-
deleteMerchantSubscription: async (merchantSubscriptionId: string) => {
|
|
4847
|
-
throw new Error('Not implemented')
|
|
4848
|
-
},
|
|
4849
|
-
getSubscriptionForStripeId: async (stripeSubscriptionId: string) => {
|
|
4850
|
-
const merchantSubscriptionParsed = MerchantSubscriptionSchema.safeParse(
|
|
4851
|
-
await client.query.merchantSubscription.findFirst({
|
|
4852
|
-
where: eq(merchantSubscriptionTable.identifier, stripeSubscriptionId),
|
|
4853
|
-
}),
|
|
4854
|
-
)
|
|
4855
|
-
|
|
4856
|
-
if (!merchantSubscriptionParsed.success) {
|
|
4857
|
-
throw new Error(
|
|
4858
|
-
`No merchant subscription found for stripe id ${stripeSubscriptionId} ${merchantSubscriptionParsed.error}`,
|
|
4859
|
-
)
|
|
4860
|
-
}
|
|
4861
|
-
|
|
4862
|
-
const subscriptionParsed = SubscriptionSchema.safeParse(
|
|
4863
|
-
await client.query.subscription.findFirst({
|
|
4864
|
-
where: eq(
|
|
4865
|
-
subscriptionTable.merchantSubscriptionId,
|
|
4866
|
-
merchantSubscriptionParsed.data.id,
|
|
4867
|
-
),
|
|
4868
|
-
with: {
|
|
4869
|
-
product: true,
|
|
4870
|
-
},
|
|
4871
|
-
}),
|
|
4872
|
-
)
|
|
4873
|
-
|
|
4874
|
-
if (!subscriptionParsed.success) {
|
|
4875
|
-
throw new Error(
|
|
4876
|
-
`No subscription found for merchant subscription ${merchantSubscriptionParsed.data.id} ${subscriptionParsed.error}`,
|
|
4877
|
-
)
|
|
4878
|
-
}
|
|
4879
|
-
|
|
4880
|
-
return subscriptionParsed.data
|
|
4881
|
-
},
|
|
4882
|
-
updateSubscriptionStatus: async (
|
|
4883
|
-
subscriptionId: string,
|
|
4884
|
-
status: string,
|
|
4885
|
-
) => {
|
|
4886
|
-
await client
|
|
4887
|
-
.update(subscriptionTable)
|
|
4888
|
-
.set({ status })
|
|
4889
|
-
.where(eq(subscriptionTable.id, subscriptionId))
|
|
4890
|
-
},
|
|
4891
|
-
}
|
|
116
|
+
const adapter: DrizzleAdapterComposition<InstanceType<typeof MySqlDatabase>> =
|
|
117
|
+
{
|
|
118
|
+
client,
|
|
119
|
+
...redeemFullPriceCouponWorkflow,
|
|
120
|
+
...purchasePort,
|
|
121
|
+
...merchantChargeAndPurchaseWorkflow,
|
|
122
|
+
...transferPurchaseToUserWorkflow,
|
|
123
|
+
...contentResourcePort,
|
|
124
|
+
...createEventWorkflowOperations,
|
|
125
|
+
...createEventSeriesWorkflowOperations,
|
|
126
|
+
...createCohortWorkflowOperations,
|
|
127
|
+
...createWorkshopWorkflowOperations,
|
|
128
|
+
...identityPort,
|
|
129
|
+
...authStoragePort,
|
|
130
|
+
...organizationPort,
|
|
131
|
+
...catalogPort,
|
|
132
|
+
...couponPort,
|
|
133
|
+
...entitlementPort,
|
|
134
|
+
...merchantPort,
|
|
135
|
+
...progressPort,
|
|
136
|
+
...subscriptionPort,
|
|
137
|
+
}
|
|
4892
138
|
|
|
4893
139
|
return adapter
|
|
4894
140
|
}
|