@coursebuilder/adapter-drizzle 2.1.1 → 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-7FBSP3VD.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-F56AI52P.js → chunk-INZTF6QV.js} +2 -2
- package/dist/{chunk-AP5HBIV4.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-PJORQUQP.js → chunk-WUMDFFN3.js} +2 -2
- package/dist/{chunk-2AW5KYAK.js → chunk-X7DRYXB7.js} +2 -2
- package/dist/{chunk-PNPTKVJ4.js → chunk-XUBZKYH6.js} +24 -15
- package/dist/{chunk-WWBSNLRC.js → chunk-ZY4AFKUQ.js} +2 -2
- package/dist/index.d.ts +5 -11
- package/dist/index.js +41 -18
- package/dist/lib/mysql/cms-resource-actions.cjs +15912 -4127
- package/dist/lib/mysql/cms-resource-actions.cjs.map +1 -1
- package/dist/lib/mysql/cms-resource-actions.d.cts +8 -38
- package/dist/lib/mysql/cms-resource-actions.d.ts +8 -38
- 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 +21875 -15261
- 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 +46 -22
- 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 +4 -4
- package/dist/lib/mysql/schemas/auth/device-access-token.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/device-access-token.js +5 -5
- package/dist/lib/mysql/schemas/auth/device-verification.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/device-verification.js +5 -5
- package/dist/lib/mysql/schemas/auth/personal-access-token.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/personal-access-token.js +5 -5
- package/dist/lib/mysql/schemas/auth/profiles.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/profiles.js +4 -4
- package/dist/lib/mysql/schemas/auth/role-permissions.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/role-permissions.js +5 -5
- package/dist/lib/mysql/schemas/auth/roles.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/roles.js +4 -4
- package/dist/lib/mysql/schemas/auth/sessions.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/sessions.js +5 -5
- package/dist/lib/mysql/schemas/auth/user-permissions.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/user-permissions.js +4 -4
- package/dist/lib/mysql/schemas/auth/user-prefs.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/user-prefs.js +4 -4
- package/dist/lib/mysql/schemas/auth/user-roles.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/user-roles.js +4 -4
- 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 +4 -4
- package/dist/lib/mysql/schemas/commerce/coupon.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/coupon.js +4 -4
- package/dist/lib/mysql/schemas/commerce/merchant-charge.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/merchant-charge.js +4 -4
- package/dist/lib/mysql/schemas/commerce/merchant-subscription.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/merchant-subscription.js +4 -4
- package/dist/lib/mysql/schemas/commerce/price.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/price.js +4 -4
- package/dist/lib/mysql/schemas/commerce/product.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/product.js +4 -4
- package/dist/lib/mysql/schemas/commerce/purchase-user-transfer.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/purchase-user-transfer.js +5 -5
- 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 +4 -4
- 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 +4 -4
- package/dist/lib/mysql/schemas/commerce/upgradable-products.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/upgradable-products.js +5 -5
- 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 +4 -4
- 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 +4 -4
- package/dist/lib/mysql/schemas/communication/question-response.cjs.map +1 -1
- package/dist/lib/mysql/schemas/communication/question-response.js +5 -5
- 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 +4 -4
- package/dist/lib/mysql/schemas/content/content-resource-product.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-product.js +4 -4
- package/dist/lib/mysql/schemas/content/content-resource-resource.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-resource.js +4 -4
- package/dist/lib/mysql/schemas/content/content-resource-tag.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-tag.js +4 -4
- package/dist/lib/mysql/schemas/content/content-resource-version.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-version.js +4 -4
- 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 +4 -4
- package/dist/lib/mysql/schemas/content/tag-tag.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/tag-tag.js +4 -4
- package/dist/lib/mysql/schemas/content/tag.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/tag.js +4 -4
- 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 +5 -5
- 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 +4 -4
- 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 +4 -4
- 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 +4 -4
- 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/context.ts +20 -0
- package/src/lib/mysql/index.ts +118 -4873
- 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-62VSIVME.js +0 -4714
- package/dist/chunk-RVXF3JZ7.js +0 -8732
- package/dist/chunk-WEJL5OFI.js +0 -8732
- package/src/authjs.ts +0 -34
|
@@ -0,0 +1,610 @@
|
|
|
1
|
+
import slugify from '@sindresorhus/slugify'
|
|
2
|
+
import { and, eq, inArray, or, sql } from 'drizzle-orm'
|
|
3
|
+
import { customAlphabet } from 'nanoid'
|
|
4
|
+
import { v4 } from 'uuid'
|
|
5
|
+
import { z } from 'zod'
|
|
6
|
+
|
|
7
|
+
import type { CatalogPort } from '@coursebuilder/core/ports'
|
|
8
|
+
import {
|
|
9
|
+
merchantPriceSchema,
|
|
10
|
+
merchantProductSchema,
|
|
11
|
+
priceSchema,
|
|
12
|
+
productSchema,
|
|
13
|
+
upgradableProductSchema,
|
|
14
|
+
type NewProduct,
|
|
15
|
+
type Price,
|
|
16
|
+
type Product,
|
|
17
|
+
type Purchase,
|
|
18
|
+
} from '@coursebuilder/core/schemas'
|
|
19
|
+
import {
|
|
20
|
+
ContentResourceProductSchema,
|
|
21
|
+
ContentResourceSchema,
|
|
22
|
+
type ContentResource,
|
|
23
|
+
} from '@coursebuilder/core/schemas/content-resource-schema'
|
|
24
|
+
import { merchantAccountSchema } from '@coursebuilder/core/schemas/merchant-account-schema'
|
|
25
|
+
import type { PaymentsProviderConfig } from '@coursebuilder/core/types'
|
|
26
|
+
import { logger as baseLogger } from '@coursebuilder/utils/logger'
|
|
27
|
+
|
|
28
|
+
import type { MySqlContext } from '../context.js'
|
|
29
|
+
|
|
30
|
+
const guid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 5)
|
|
31
|
+
|
|
32
|
+
function toError(error: unknown) {
|
|
33
|
+
return error instanceof Error ? error : new Error(String(error))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const logger = baseLogger as unknown as {
|
|
37
|
+
debug: (message: string, attrs?: Record<string, unknown>) => void
|
|
38
|
+
error: (error: Error, attrs?: Record<string, unknown>) => void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Creates the MySQL implementation of catalog and pricing operations.
|
|
43
|
+
*
|
|
44
|
+
* @param context - The shared MySQL client and table set.
|
|
45
|
+
* @param paymentProvider - Optional provider used by product write operations.
|
|
46
|
+
* @returns All eleven CatalogPort operations.
|
|
47
|
+
*
|
|
48
|
+
* @example
|
|
49
|
+
* ```ts
|
|
50
|
+
* const catalogPort = createCatalogPort(context, paymentProvider)
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export function createCatalogPort(
|
|
54
|
+
{ client, tables }: MySqlContext,
|
|
55
|
+
paymentProvider?: PaymentsProviderConfig,
|
|
56
|
+
): CatalogPort {
|
|
57
|
+
const {
|
|
58
|
+
contentResource,
|
|
59
|
+
contentResourceProduct,
|
|
60
|
+
merchantAccount,
|
|
61
|
+
merchantPrice,
|
|
62
|
+
merchantProduct,
|
|
63
|
+
prices,
|
|
64
|
+
products,
|
|
65
|
+
purchases: purchaseTable,
|
|
66
|
+
upgradableProducts,
|
|
67
|
+
} = tables
|
|
68
|
+
|
|
69
|
+
const getProduct: CatalogPort['getProduct'] = async (
|
|
70
|
+
productSlugOrId?: string,
|
|
71
|
+
withResources: boolean = true,
|
|
72
|
+
): Promise<Product | null> => {
|
|
73
|
+
if (!productSlugOrId) {
|
|
74
|
+
return null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
const productData = await client.query.products.findFirst({
|
|
79
|
+
where: and(
|
|
80
|
+
or(
|
|
81
|
+
eq(
|
|
82
|
+
sql`JSON_EXTRACT (${products.fields}, "$.slug")`,
|
|
83
|
+
`${productSlugOrId}`,
|
|
84
|
+
),
|
|
85
|
+
eq(products.id, productSlugOrId),
|
|
86
|
+
),
|
|
87
|
+
),
|
|
88
|
+
with: {
|
|
89
|
+
price: true,
|
|
90
|
+
...(withResources && {
|
|
91
|
+
resources: {
|
|
92
|
+
with: {
|
|
93
|
+
resource: {
|
|
94
|
+
with: {
|
|
95
|
+
resources: true,
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
}),
|
|
101
|
+
},
|
|
102
|
+
})
|
|
103
|
+
const parsedProduct = productSchema.safeParse(productData)
|
|
104
|
+
if (!parsedProduct.success) {
|
|
105
|
+
logger.error(parsedProduct.error, {
|
|
106
|
+
event: 'adapter.product.parse_failed',
|
|
107
|
+
productId: productData?.id,
|
|
108
|
+
productSlugOrId,
|
|
109
|
+
})
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
112
|
+
return parsedProduct.data
|
|
113
|
+
} catch (e) {
|
|
114
|
+
logger.error(toError(e), {
|
|
115
|
+
event: 'adapter.product.lookup.failed',
|
|
116
|
+
productSlugOrId,
|
|
117
|
+
withResources,
|
|
118
|
+
})
|
|
119
|
+
return null
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
getProduct,
|
|
125
|
+
getUpgradableProducts: async (options) => {
|
|
126
|
+
const { upgradableFromId, upgradableToId } = options
|
|
127
|
+
return z.array(upgradableProductSchema).parse(
|
|
128
|
+
await client.query.upgradableProducts.findMany({
|
|
129
|
+
where: and(
|
|
130
|
+
eq(upgradableProducts.upgradableFromId, upgradableFromId),
|
|
131
|
+
eq(upgradableProducts.upgradableToId, upgradableToId),
|
|
132
|
+
),
|
|
133
|
+
}),
|
|
134
|
+
)
|
|
135
|
+
},
|
|
136
|
+
async availableUpgradesForProduct(
|
|
137
|
+
purchases: any,
|
|
138
|
+
productId: string,
|
|
139
|
+
): Promise<any[]> {
|
|
140
|
+
const previousPurchaseProductIds = purchases.map(
|
|
141
|
+
({ productId }: Purchase) => productId,
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
if (previousPurchaseProductIds.length > 0) {
|
|
145
|
+
return client.query.upgradableProducts.findMany({
|
|
146
|
+
where: and(
|
|
147
|
+
eq(upgradableProducts.upgradableToId, productId),
|
|
148
|
+
inArray(
|
|
149
|
+
upgradableProducts.upgradableFromId,
|
|
150
|
+
previousPurchaseProductIds,
|
|
151
|
+
),
|
|
152
|
+
),
|
|
153
|
+
})
|
|
154
|
+
}
|
|
155
|
+
return []
|
|
156
|
+
},
|
|
157
|
+
async getPrice(productId: string): Promise<Price | null> {
|
|
158
|
+
return priceSchema.nullable().parse(
|
|
159
|
+
await client.query.prices.findFirst({
|
|
160
|
+
where: eq(prices.productId, productId),
|
|
161
|
+
}),
|
|
162
|
+
)
|
|
163
|
+
},
|
|
164
|
+
async getPriceForProduct(productId: string): Promise<Price | null> {
|
|
165
|
+
return priceSchema.nullable().parse(
|
|
166
|
+
await client.query.prices.findFirst({
|
|
167
|
+
where: eq(prices.productId, productId),
|
|
168
|
+
}),
|
|
169
|
+
)
|
|
170
|
+
},
|
|
171
|
+
async archiveProduct(productId) {
|
|
172
|
+
if (!paymentProvider) throw new Error('Payment provider not found')
|
|
173
|
+
const product = await getProduct(productId)
|
|
174
|
+
|
|
175
|
+
if (!product) {
|
|
176
|
+
throw new Error(`Product not found for id (${productId})`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!product.price) {
|
|
180
|
+
throw new Error(`Product has no price`)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
await client
|
|
184
|
+
.update(products)
|
|
185
|
+
.set({ status: 0, name: `${product.name} (Archived)` })
|
|
186
|
+
.where(eq(products.id, productId))
|
|
187
|
+
|
|
188
|
+
await client
|
|
189
|
+
.update(prices)
|
|
190
|
+
.set({ status: 0, nickname: `${product.name} (Archived)` })
|
|
191
|
+
.where(eq(prices.productId, productId))
|
|
192
|
+
|
|
193
|
+
await client
|
|
194
|
+
.update(merchantProduct)
|
|
195
|
+
.set({ status: 0 })
|
|
196
|
+
.where(eq(merchantProduct.productId, productId))
|
|
197
|
+
|
|
198
|
+
await client
|
|
199
|
+
.update(merchantPrice)
|
|
200
|
+
.set({ status: 0 })
|
|
201
|
+
.where(eq(merchantPrice.priceId, product.price.id))
|
|
202
|
+
|
|
203
|
+
const currentMerchantProduct = merchantProductSchema.nullish().parse(
|
|
204
|
+
await client.query.merchantProduct.findFirst({
|
|
205
|
+
where: eq(merchantProduct.productId, productId),
|
|
206
|
+
}),
|
|
207
|
+
)
|
|
208
|
+
|
|
209
|
+
if (!currentMerchantProduct || !currentMerchantProduct.identifier) {
|
|
210
|
+
throw new Error(`Merchant product not found for id (${productId})`)
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
await paymentProvider.updateProduct(currentMerchantProduct.identifier, {
|
|
214
|
+
active: false,
|
|
215
|
+
})
|
|
216
|
+
|
|
217
|
+
const currentMerchantPrice = merchantPriceSchema.nullish().parse(
|
|
218
|
+
await client.query.merchantPrice.findFirst({
|
|
219
|
+
where: eq(merchantPrice.priceId, product.price.id),
|
|
220
|
+
}),
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
if (!currentMerchantPrice || !currentMerchantPrice.identifier) {
|
|
224
|
+
throw new Error(`Merchant price not found for id (${productId})`)
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
await paymentProvider.updatePrice(currentMerchantPrice.identifier, {
|
|
228
|
+
active: false,
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
return getProduct(productId)
|
|
232
|
+
},
|
|
233
|
+
async updateProduct(input: Product) {
|
|
234
|
+
if (!paymentProvider) throw new Error('Payment provider not found')
|
|
235
|
+
const currentProduct = await getProduct(input.id)
|
|
236
|
+
if (!currentProduct) {
|
|
237
|
+
throw new Error(`Product not found`)
|
|
238
|
+
}
|
|
239
|
+
if (!currentProduct.price) {
|
|
240
|
+
throw new Error(`Product has no price`)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
const merchantProduct = merchantProductSchema.nullish().parse(
|
|
244
|
+
await client.query.merchantProduct.findFirst({
|
|
245
|
+
where: (merchantProduct, { eq }) =>
|
|
246
|
+
eq(merchantProduct.productId, input.id),
|
|
247
|
+
}),
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
if (!merchantProduct || !merchantProduct.identifier) {
|
|
251
|
+
throw new Error(`Merchant product not found`)
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// TODO: handle upgrades
|
|
255
|
+
|
|
256
|
+
const stripeProduct = await paymentProvider.getProduct(
|
|
257
|
+
merchantProduct.identifier,
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
const priceChanged =
|
|
261
|
+
currentProduct.price.unitAmount.toString() !==
|
|
262
|
+
input.price?.unitAmount.toString()
|
|
263
|
+
|
|
264
|
+
// Get current merchant price and Stripe price to check if it needs updating
|
|
265
|
+
const currentMerchantPrice = merchantPriceSchema.nullish().parse(
|
|
266
|
+
await client.query.merchantPrice.findFirst({
|
|
267
|
+
where: (merchantPrice, { eq, and }) =>
|
|
268
|
+
and(
|
|
269
|
+
eq(merchantPrice.merchantProductId, merchantProduct.id),
|
|
270
|
+
eq(merchantPrice.status, 1),
|
|
271
|
+
),
|
|
272
|
+
}),
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if (!currentMerchantPrice || !currentMerchantPrice.identifier) {
|
|
276
|
+
throw new Error(`Merchant price not found`)
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const currentStripePrice = await paymentProvider.getPrice(
|
|
280
|
+
currentMerchantPrice.identifier,
|
|
281
|
+
)
|
|
282
|
+
|
|
283
|
+
// Check if type changed to/from membership (requires Stripe price recreation)
|
|
284
|
+
const membershipTypeChanged =
|
|
285
|
+
currentProduct.type !== input.type &&
|
|
286
|
+
(input.type === 'membership' || currentProduct.type === 'membership')
|
|
287
|
+
|
|
288
|
+
// Check if billing interval changed for membership products
|
|
289
|
+
const billingIntervalChanged =
|
|
290
|
+
input.type === 'membership' &&
|
|
291
|
+
currentProduct.type === 'membership' &&
|
|
292
|
+
currentProduct.fields?.billingInterval !== input.fields?.billingInterval
|
|
293
|
+
|
|
294
|
+
// Check if Stripe price recurring status doesn't match expected state for input type:
|
|
295
|
+
// - membership products require recurring=true
|
|
296
|
+
// - non-membership products require recurring=false (null/undefined)
|
|
297
|
+
const isMembership = input.type === 'membership'
|
|
298
|
+
const stripePriceMismatch =
|
|
299
|
+
isMembership !== !!currentStripePrice.recurring
|
|
300
|
+
|
|
301
|
+
// Need to recreate price if: price changed, type changed to/from membership,
|
|
302
|
+
// billing interval changed, or Stripe price doesn't match expected recurring status
|
|
303
|
+
const needsPriceRecreation =
|
|
304
|
+
priceChanged ||
|
|
305
|
+
membershipTypeChanged ||
|
|
306
|
+
billingIntervalChanged ||
|
|
307
|
+
stripePriceMismatch
|
|
308
|
+
|
|
309
|
+
if (needsPriceRecreation) {
|
|
310
|
+
// Get billing interval from input or default to 'year'
|
|
311
|
+
const billingInterval = input.fields?.billingInterval || 'year'
|
|
312
|
+
|
|
313
|
+
const priceParams = {
|
|
314
|
+
product: stripeProduct.id,
|
|
315
|
+
unit_amount: Math.floor(Number(input.price?.unitAmount || 0) * 100),
|
|
316
|
+
currency: 'usd',
|
|
317
|
+
metadata: {
|
|
318
|
+
slug: input.fields.slug,
|
|
319
|
+
},
|
|
320
|
+
active: true,
|
|
321
|
+
// Membership products get recurring billing
|
|
322
|
+
...(input.type === 'membership' && {
|
|
323
|
+
recurring: { interval: billingInterval },
|
|
324
|
+
}),
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
logger.debug('adapter.product.price_recreation.started', {
|
|
328
|
+
billingInterval,
|
|
329
|
+
currency: priceParams.currency,
|
|
330
|
+
currentMerchantPriceId: currentMerchantPrice.id,
|
|
331
|
+
isMembership: input.type === 'membership',
|
|
332
|
+
merchantProductId: merchantProduct.id,
|
|
333
|
+
productId: input.id,
|
|
334
|
+
productSlug: input.fields.slug,
|
|
335
|
+
stripeProductId: stripeProduct.id,
|
|
336
|
+
unitAmount: priceParams.unit_amount,
|
|
337
|
+
})
|
|
338
|
+
|
|
339
|
+
const newStripePrice = await paymentProvider.createPrice(priceParams)
|
|
340
|
+
|
|
341
|
+
await paymentProvider.updateProduct(stripeProduct.id, {
|
|
342
|
+
default_price: newStripePrice.id,
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
const newMerchantPriceId = `mprice_${v4()}`
|
|
346
|
+
await client.insert(merchantPrice).values({
|
|
347
|
+
id: newMerchantPriceId,
|
|
348
|
+
merchantProductId: merchantProduct.id,
|
|
349
|
+
merchantAccountId: merchantProduct.merchantAccountId,
|
|
350
|
+
priceId: currentProduct.price.id,
|
|
351
|
+
status: 1,
|
|
352
|
+
identifier: newStripePrice.id,
|
|
353
|
+
})
|
|
354
|
+
|
|
355
|
+
if (currentMerchantPrice) {
|
|
356
|
+
await client
|
|
357
|
+
.update(merchantPrice)
|
|
358
|
+
.set({
|
|
359
|
+
status: 0,
|
|
360
|
+
})
|
|
361
|
+
.where(eq(merchantPrice.id, currentMerchantPrice.id))
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
await client
|
|
365
|
+
.update(prices)
|
|
366
|
+
.set({
|
|
367
|
+
unitAmount: Math.floor(
|
|
368
|
+
Number(input.price?.unitAmount || 0),
|
|
369
|
+
).toString(),
|
|
370
|
+
nickname: input.name,
|
|
371
|
+
})
|
|
372
|
+
.where(eq(prices.id, currentProduct.price.id))
|
|
373
|
+
|
|
374
|
+
if (currentStripePrice) {
|
|
375
|
+
await paymentProvider.updatePrice(currentStripePrice.id, {
|
|
376
|
+
active: false,
|
|
377
|
+
})
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
await paymentProvider.updateProduct(stripeProduct.id, {
|
|
382
|
+
name: input.name,
|
|
383
|
+
active: true,
|
|
384
|
+
images: input.fields.image?.url ? [input.fields.image.url] : undefined,
|
|
385
|
+
description: input.fields.description || '',
|
|
386
|
+
metadata: {
|
|
387
|
+
slug: input.fields.slug,
|
|
388
|
+
},
|
|
389
|
+
})
|
|
390
|
+
|
|
391
|
+
const { image, ...fieldsNoImage } = input.fields
|
|
392
|
+
|
|
393
|
+
await client
|
|
394
|
+
.update(products)
|
|
395
|
+
.set({
|
|
396
|
+
name: input.name,
|
|
397
|
+
quantityAvailable: input.quantityAvailable,
|
|
398
|
+
status: 1,
|
|
399
|
+
fields: {
|
|
400
|
+
...fieldsNoImage,
|
|
401
|
+
...(image?.url && { image }),
|
|
402
|
+
},
|
|
403
|
+
type: input.type,
|
|
404
|
+
})
|
|
405
|
+
.where(eq(products.id, currentProduct.id))
|
|
406
|
+
|
|
407
|
+
return getProduct(currentProduct.id)
|
|
408
|
+
},
|
|
409
|
+
async createProduct(input: NewProduct) {
|
|
410
|
+
if (!paymentProvider) throw new Error('Payment provider not found')
|
|
411
|
+
const merchantAccount = merchantAccountSchema.nullish().parse(
|
|
412
|
+
await client.query.merchantAccount.findFirst({
|
|
413
|
+
where: (merchantAccount, { eq }) =>
|
|
414
|
+
eq(merchantAccount.label, 'stripe'),
|
|
415
|
+
}),
|
|
416
|
+
)
|
|
417
|
+
|
|
418
|
+
if (!merchantAccount) {
|
|
419
|
+
throw new Error('Merchant account not found')
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
const hash = guid()
|
|
423
|
+
const newProductId = slugify(`product-${hash}`)
|
|
424
|
+
|
|
425
|
+
const newProduct = {
|
|
426
|
+
id: newProductId,
|
|
427
|
+
name: input.name,
|
|
428
|
+
status: 1,
|
|
429
|
+
type: input.type || 'self-paced',
|
|
430
|
+
quantityAvailable: input.quantityAvailable,
|
|
431
|
+
fields: {
|
|
432
|
+
state: input.state || 'draft',
|
|
433
|
+
visibility: input.visibility || 'unlisted',
|
|
434
|
+
slug: slugify(`${input.name}-${hash}`),
|
|
435
|
+
...(input.openEnrollment && { openEnrollment: input.openEnrollment }),
|
|
436
|
+
...(input.closeEnrollment && {
|
|
437
|
+
closeEnrollment: input.closeEnrollment,
|
|
438
|
+
}),
|
|
439
|
+
...(typeof input.availableAfterDays === 'number' && {
|
|
440
|
+
availableAfterDays: input.availableAfterDays,
|
|
441
|
+
}),
|
|
442
|
+
...(typeof input.accessDurationDays === 'number' && {
|
|
443
|
+
accessDurationDays: input.accessDurationDays,
|
|
444
|
+
}),
|
|
445
|
+
},
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
await client.insert(products).values(newProduct)
|
|
449
|
+
|
|
450
|
+
const priceHash = guid()
|
|
451
|
+
const newPriceId = `price-${priceHash}`
|
|
452
|
+
|
|
453
|
+
await client.insert(prices).values({
|
|
454
|
+
id: newPriceId,
|
|
455
|
+
productId: newProductId,
|
|
456
|
+
unitAmount: input.price.toString(),
|
|
457
|
+
status: 1,
|
|
458
|
+
})
|
|
459
|
+
|
|
460
|
+
const product = await getProduct(newProductId)
|
|
461
|
+
|
|
462
|
+
const stripeProduct = await paymentProvider.createProduct({
|
|
463
|
+
name: input.name,
|
|
464
|
+
metadata: {
|
|
465
|
+
slug: product?.fields?.slug || null,
|
|
466
|
+
},
|
|
467
|
+
})
|
|
468
|
+
|
|
469
|
+
const stripePrice = await paymentProvider.createPrice({
|
|
470
|
+
product: stripeProduct.id,
|
|
471
|
+
unit_amount: Math.floor(Number(input.price) * 100),
|
|
472
|
+
currency: 'usd',
|
|
473
|
+
nickname: input.name,
|
|
474
|
+
metadata: {
|
|
475
|
+
slug: product?.fields?.slug || null,
|
|
476
|
+
},
|
|
477
|
+
// Membership products get recurring billing
|
|
478
|
+
...(input.type === 'membership' && {
|
|
479
|
+
recurring: { interval: input.billingInterval || 'year' },
|
|
480
|
+
}),
|
|
481
|
+
})
|
|
482
|
+
|
|
483
|
+
const newMerchantProductId = `mproduct_${v4()}`
|
|
484
|
+
|
|
485
|
+
await client.insert(merchantProduct).values({
|
|
486
|
+
id: newMerchantProductId,
|
|
487
|
+
merchantAccountId: merchantAccount.id,
|
|
488
|
+
productId: newProductId,
|
|
489
|
+
identifier: stripeProduct.id,
|
|
490
|
+
status: 1,
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
const newMerchantPriceId = `mprice_${v4()}`
|
|
494
|
+
await client.insert(merchantPrice).values({
|
|
495
|
+
id: newMerchantPriceId,
|
|
496
|
+
merchantAccountId: merchantAccount.id,
|
|
497
|
+
merchantProductId: newMerchantProductId,
|
|
498
|
+
priceId: newPriceId,
|
|
499
|
+
identifier: stripePrice.id,
|
|
500
|
+
status: 1,
|
|
501
|
+
})
|
|
502
|
+
|
|
503
|
+
// TODO: handle upgrades
|
|
504
|
+
|
|
505
|
+
return product
|
|
506
|
+
},
|
|
507
|
+
async getProductResources(
|
|
508
|
+
productId: string,
|
|
509
|
+
): Promise<ContentResource[] | null> {
|
|
510
|
+
const contentResourceProductsForProduct = z
|
|
511
|
+
.array(ContentResourceProductSchema)
|
|
512
|
+
.nullable()
|
|
513
|
+
.parse(
|
|
514
|
+
await client.query.contentResourceProduct.findMany({
|
|
515
|
+
where: eq(contentResourceProduct.productId, productId),
|
|
516
|
+
}),
|
|
517
|
+
)
|
|
518
|
+
|
|
519
|
+
if (!contentResourceProductsForProduct) {
|
|
520
|
+
return null
|
|
521
|
+
} else {
|
|
522
|
+
const contentResources = z.array(ContentResourceSchema).parse(
|
|
523
|
+
await client.query.contentResource.findMany({
|
|
524
|
+
where: inArray(
|
|
525
|
+
contentResource.id,
|
|
526
|
+
contentResourceProductsForProduct.map((crp) => crp.resourceId),
|
|
527
|
+
),
|
|
528
|
+
}),
|
|
529
|
+
)
|
|
530
|
+
return contentResources
|
|
531
|
+
}
|
|
532
|
+
},
|
|
533
|
+
async pricesOfPurchasesTowardOneBundle({
|
|
534
|
+
userId,
|
|
535
|
+
bundleId,
|
|
536
|
+
}: {
|
|
537
|
+
userId: string | undefined
|
|
538
|
+
bundleId: string
|
|
539
|
+
}): Promise<Price[]> {
|
|
540
|
+
if (userId === undefined) return []
|
|
541
|
+
|
|
542
|
+
const canUpgradeProducts = await client.query.upgradableProducts.findMany(
|
|
543
|
+
{
|
|
544
|
+
where: eq(upgradableProducts.upgradableToId, bundleId),
|
|
545
|
+
},
|
|
546
|
+
)
|
|
547
|
+
|
|
548
|
+
const upgradableFrom = z.array(z.string()).parse(
|
|
549
|
+
canUpgradeProducts.map((product) => {
|
|
550
|
+
return product.upgradableFromId
|
|
551
|
+
}),
|
|
552
|
+
)
|
|
553
|
+
|
|
554
|
+
const purchases = await client.query.purchases.findMany({
|
|
555
|
+
where: and(
|
|
556
|
+
eq(purchaseTable.userId, userId),
|
|
557
|
+
inArray(purchaseTable.productId, upgradableFrom),
|
|
558
|
+
),
|
|
559
|
+
})
|
|
560
|
+
|
|
561
|
+
const productsPurchased = z.array(z.string()).parse(
|
|
562
|
+
purchases.map((purchase) => {
|
|
563
|
+
return purchase.productId
|
|
564
|
+
}),
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
if (productsPurchased.length === 0) return []
|
|
568
|
+
|
|
569
|
+
const foundPrices = await client.query.prices.findMany({
|
|
570
|
+
where: inArray(prices.productId, productsPurchased),
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
return z.array(priceSchema).parse(foundPrices)
|
|
574
|
+
},
|
|
575
|
+
async addResourceToProduct(options: {
|
|
576
|
+
resource: ContentResource
|
|
577
|
+
productId: string
|
|
578
|
+
userId: string
|
|
579
|
+
}) {
|
|
580
|
+
const { resource, productId, userId } = options
|
|
581
|
+
const product = await getProduct(productId)
|
|
582
|
+
|
|
583
|
+
if (!product) {
|
|
584
|
+
throw new Error(`Product not found for id (${productId})`)
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
await client.insert(contentResourceProduct).values({
|
|
588
|
+
resourceId: resource.id,
|
|
589
|
+
productId,
|
|
590
|
+
position: product.resources?.length || 0,
|
|
591
|
+
metadata: {
|
|
592
|
+
addedBy: userId,
|
|
593
|
+
},
|
|
594
|
+
})
|
|
595
|
+
|
|
596
|
+
const resourceProduct =
|
|
597
|
+
await client.query.contentResourceProduct.findFirst({
|
|
598
|
+
where: eq(contentResourceProduct.resourceId, resource.id),
|
|
599
|
+
with: {
|
|
600
|
+
resource: true,
|
|
601
|
+
product: true,
|
|
602
|
+
},
|
|
603
|
+
})
|
|
604
|
+
|
|
605
|
+
return (
|
|
606
|
+
ContentResourceProductSchema.safeParse(resourceProduct).data || null
|
|
607
|
+
)
|
|
608
|
+
},
|
|
609
|
+
}
|
|
610
|
+
}
|