@coursebuilder/adapter-drizzle 0.5.0 → 0.5.2
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-BM5UKWXI.js → chunk-27UC2HEP.js} +2 -2
- package/dist/{chunk-QTGVGIRO.js → chunk-6SLQW6OM.js} +2 -2
- package/dist/{chunk-TBM3AK3P.js → chunk-EFVZ7IN4.js} +2 -2
- package/dist/{chunk-FRA4ZCRA.js → chunk-JW2PFVLO.js} +485 -223
- package/dist/{chunk-O6JECJRY.js → chunk-QQA7STVO.js} +2 -2
- package/dist/{chunk-CQNKTFXE.js → chunk-XJXCDN7B.js} +2 -2
- package/dist/{chunk-JZQ666KH.js → chunk-YMFPIZMO.js} +2 -2
- package/dist/{chunk-Z245DYMP.js → chunk-ZYQYJNLK.js} +569 -198
- package/dist/index.d.ts +3 -1
- package/dist/index.js +12 -12
- package/dist/lib/mysql/index.cjs +1349 -721
- package/dist/lib/mysql/index.cjs.map +1 -1
- package/dist/lib/mysql/index.d.cts +3147 -126
- package/dist/lib/mysql/index.d.ts +3147 -126
- package/dist/lib/mysql/index.js +10 -8
- package/dist/lib/mysql/schemas/auth/accounts.cjs +87 -71
- package/dist/lib/mysql/schemas/auth/accounts.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/accounts.d.cts +58 -1
- package/dist/lib/mysql/schemas/auth/accounts.d.ts +58 -1
- package/dist/lib/mysql/schemas/auth/accounts.js +1 -1
- package/dist/lib/mysql/schemas/auth/device-access-token.cjs +79 -63
- package/dist/lib/mysql/schemas/auth/device-access-token.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/device-access-token.d.cts +12 -0
- package/dist/lib/mysql/schemas/auth/device-access-token.d.ts +12 -0
- package/dist/lib/mysql/schemas/auth/device-access-token.js +2 -2
- package/dist/lib/mysql/schemas/auth/device-verification.cjs +81 -65
- package/dist/lib/mysql/schemas/auth/device-verification.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/device-verification.d.cts +24 -0
- package/dist/lib/mysql/schemas/auth/device-verification.d.ts +24 -0
- package/dist/lib/mysql/schemas/auth/device-verification.js +2 -2
- package/dist/lib/mysql/schemas/auth/permissions.d.cts +28 -0
- package/dist/lib/mysql/schemas/auth/permissions.d.ts +28 -0
- package/dist/lib/mysql/schemas/auth/role-permissions.cjs +76 -60
- package/dist/lib/mysql/schemas/auth/role-permissions.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/role-permissions.d.cts +24 -0
- package/dist/lib/mysql/schemas/auth/role-permissions.d.ts +24 -0
- package/dist/lib/mysql/schemas/auth/role-permissions.js +2 -2
- package/dist/lib/mysql/schemas/auth/roles.cjs +76 -60
- package/dist/lib/mysql/schemas/auth/roles.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/roles.d.cts +28 -0
- package/dist/lib/mysql/schemas/auth/roles.d.ts +28 -0
- package/dist/lib/mysql/schemas/auth/roles.js +1 -1
- package/dist/lib/mysql/schemas/auth/sessions.cjs +77 -61
- package/dist/lib/mysql/schemas/auth/sessions.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/sessions.d.cts +12 -0
- package/dist/lib/mysql/schemas/auth/sessions.d.ts +12 -0
- package/dist/lib/mysql/schemas/auth/sessions.js +2 -2
- package/dist/lib/mysql/schemas/auth/user-permissions.cjs +78 -62
- package/dist/lib/mysql/schemas/auth/user-permissions.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/user-permissions.d.cts +24 -0
- package/dist/lib/mysql/schemas/auth/user-permissions.d.ts +24 -0
- package/dist/lib/mysql/schemas/auth/user-permissions.js +1 -1
- package/dist/lib/mysql/schemas/auth/user-prefs.cjs +82 -66
- package/dist/lib/mysql/schemas/auth/user-prefs.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/user-prefs.d.cts +28 -0
- package/dist/lib/mysql/schemas/auth/user-prefs.d.ts +28 -0
- package/dist/lib/mysql/schemas/auth/user-prefs.js +1 -1
- package/dist/lib/mysql/schemas/auth/user-roles.cjs +78 -62
- package/dist/lib/mysql/schemas/auth/user-roles.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/user-roles.d.cts +24 -0
- package/dist/lib/mysql/schemas/auth/user-roles.d.ts +24 -0
- package/dist/lib/mysql/schemas/auth/user-roles.js +1 -1
- package/dist/lib/mysql/schemas/auth/users.cjs +179 -159
- package/dist/lib/mysql/schemas/auth/users.cjs.map +1 -1
- package/dist/lib/mysql/schemas/auth/users.d.cts +32 -0
- package/dist/lib/mysql/schemas/auth/users.d.ts +32 -0
- package/dist/lib/mysql/schemas/auth/users.js +1 -1
- package/dist/lib/mysql/schemas/auth/verification-tokens.d.cts +16 -0
- package/dist/lib/mysql/schemas/auth/verification-tokens.d.ts +16 -0
- package/dist/lib/mysql/schemas/commerce/coupon.cjs +57 -51
- package/dist/lib/mysql/schemas/commerce/coupon.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/coupon.d.cts +50 -14
- package/dist/lib/mysql/schemas/commerce/coupon.d.ts +50 -14
- package/dist/lib/mysql/schemas/commerce/coupon.js +1 -1
- package/dist/lib/mysql/schemas/commerce/merchant-account.d.cts +20 -0
- package/dist/lib/mysql/schemas/commerce/merchant-account.d.ts +20 -0
- package/dist/lib/mysql/schemas/commerce/merchant-charge.d.cts +32 -0
- package/dist/lib/mysql/schemas/commerce/merchant-charge.d.ts +32 -0
- package/dist/lib/mysql/schemas/commerce/merchant-coupon.d.cts +24 -0
- package/dist/lib/mysql/schemas/commerce/merchant-coupon.d.ts +24 -0
- package/dist/lib/mysql/schemas/commerce/merchant-customer.d.cts +24 -0
- package/dist/lib/mysql/schemas/commerce/merchant-customer.d.ts +24 -0
- package/dist/lib/mysql/schemas/commerce/merchant-price.d.cts +28 -0
- package/dist/lib/mysql/schemas/commerce/merchant-price.d.ts +28 -0
- package/dist/lib/mysql/schemas/commerce/merchant-product.d.cts +24 -0
- package/dist/lib/mysql/schemas/commerce/merchant-product.d.ts +24 -0
- package/dist/lib/mysql/schemas/commerce/merchant-session.d.cts +12 -0
- package/dist/lib/mysql/schemas/commerce/merchant-session.d.ts +12 -0
- package/dist/lib/mysql/schemas/commerce/price.cjs +44 -28
- package/dist/lib/mysql/schemas/commerce/price.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/price.d.cts +28 -0
- package/dist/lib/mysql/schemas/commerce/price.d.ts +28 -0
- package/dist/lib/mysql/schemas/commerce/price.js +1 -1
- package/dist/lib/mysql/schemas/commerce/product.cjs +58 -42
- package/dist/lib/mysql/schemas/commerce/product.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/product.d.cts +32 -0
- package/dist/lib/mysql/schemas/commerce/product.d.ts +32 -0
- package/dist/lib/mysql/schemas/commerce/product.js +1 -1
- package/dist/lib/mysql/schemas/commerce/purchase-user-transfer.cjs +106 -90
- package/dist/lib/mysql/schemas/commerce/purchase-user-transfer.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/purchase-user-transfer.d.cts +40 -0
- package/dist/lib/mysql/schemas/commerce/purchase-user-transfer.d.ts +40 -0
- package/dist/lib/mysql/schemas/commerce/purchase-user-transfer.js +2 -2
- package/dist/lib/mysql/schemas/commerce/purchase.cjs +110 -98
- package/dist/lib/mysql/schemas/commerce/purchase.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/purchase.d.cts +68 -0
- package/dist/lib/mysql/schemas/commerce/purchase.d.ts +68 -0
- package/dist/lib/mysql/schemas/commerce/purchase.js +1 -1
- package/dist/lib/mysql/schemas/commerce/upgradable-products.cjs +50 -34
- package/dist/lib/mysql/schemas/commerce/upgradable-products.cjs.map +1 -1
- package/dist/lib/mysql/schemas/commerce/upgradable-products.d.cts +28 -0
- package/dist/lib/mysql/schemas/commerce/upgradable-products.d.ts +28 -0
- package/dist/lib/mysql/schemas/commerce/upgradable-products.js +2 -2
- package/dist/lib/mysql/schemas/communication/comment.cjs +82 -66
- package/dist/lib/mysql/schemas/communication/comment.cjs.map +1 -1
- package/dist/lib/mysql/schemas/communication/comment.d.cts +28 -0
- package/dist/lib/mysql/schemas/communication/comment.d.ts +28 -0
- package/dist/lib/mysql/schemas/communication/comment.js +1 -1
- package/dist/lib/mysql/schemas/communication/communication-channel.d.cts +28 -0
- package/dist/lib/mysql/schemas/communication/communication-channel.d.ts +28 -0
- package/dist/lib/mysql/schemas/communication/communication-preference-types.d.cts +28 -0
- package/dist/lib/mysql/schemas/communication/communication-preference-types.d.ts +28 -0
- package/dist/lib/mysql/schemas/communication/communication-preferences.cjs +100 -84
- package/dist/lib/mysql/schemas/communication/communication-preferences.cjs.map +1 -1
- package/dist/lib/mysql/schemas/communication/communication-preferences.d.cts +44 -0
- package/dist/lib/mysql/schemas/communication/communication-preferences.d.ts +44 -0
- package/dist/lib/mysql/schemas/communication/communication-preferences.js +1 -1
- package/dist/lib/mysql/schemas/content/content-contributions.cjs +112 -92
- package/dist/lib/mysql/schemas/content/content-contributions.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-contributions.d.cts +32 -0
- package/dist/lib/mysql/schemas/content/content-contributions.d.ts +32 -0
- package/dist/lib/mysql/schemas/content/content-contributions.js +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-product.cjs +49 -29
- package/dist/lib/mysql/schemas/content/content-resource-product.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-product.d.cts +28 -0
- package/dist/lib/mysql/schemas/content/content-resource-product.d.ts +28 -0
- package/dist/lib/mysql/schemas/content/content-resource-product.js +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-resource.cjs +49 -29
- package/dist/lib/mysql/schemas/content/content-resource-resource.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-resource.d.cts +28 -0
- package/dist/lib/mysql/schemas/content/content-resource-resource.d.ts +28 -0
- package/dist/lib/mysql/schemas/content/content-resource-resource.js +1 -1
- package/dist/lib/mysql/schemas/content/content-resource-tag.cjs +274 -0
- package/dist/lib/mysql/schemas/content/content-resource-tag.cjs.map +1 -0
- package/dist/lib/mysql/schemas/content/content-resource-tag.d.cts +97 -0
- package/dist/lib/mysql/schemas/content/content-resource-tag.d.ts +97 -0
- package/dist/lib/mysql/schemas/content/content-resource-tag.js +21 -0
- package/dist/lib/mysql/schemas/content/content-resource-tag.js.map +1 -0
- package/dist/lib/mysql/schemas/content/content-resource-version.cjs +288 -0
- package/dist/lib/mysql/schemas/content/content-resource-version.cjs.map +1 -0
- package/dist/lib/mysql/schemas/content/content-resource-version.d.cts +130 -0
- package/dist/lib/mysql/schemas/content/content-resource-version.d.ts +130 -0
- package/dist/lib/mysql/schemas/content/content-resource-version.js +21 -0
- package/dist/lib/mysql/schemas/content/content-resource-version.js.map +1 -0
- package/dist/lib/mysql/schemas/content/content-resource.cjs +141 -15
- package/dist/lib/mysql/schemas/content/content-resource.cjs.map +1 -1
- package/dist/lib/mysql/schemas/content/content-resource.d.cts +47 -0
- package/dist/lib/mysql/schemas/content/content-resource.d.ts +47 -0
- package/dist/lib/mysql/schemas/content/content-resource.js +1 -1
- package/dist/lib/mysql/schemas/content/contribution-types.d.cts +32 -0
- package/dist/lib/mysql/schemas/content/contribution-types.d.ts +32 -0
- package/dist/lib/mysql/schemas/content/lesson-progress.d.cts +40 -0
- package/dist/lib/mysql/schemas/content/lesson-progress.d.ts +40 -0
- package/dist/lib/mysql/schemas/content/resource-progress.d.cts +24 -0
- package/dist/lib/mysql/schemas/content/resource-progress.d.ts +24 -0
- package/dist/lib/mysql/schemas/content/tag-tag.cjs +241 -0
- package/dist/lib/mysql/schemas/content/tag-tag.cjs.map +1 -0
- package/dist/lib/mysql/schemas/content/tag-tag.d.cts +129 -0
- package/dist/lib/mysql/schemas/content/tag-tag.d.ts +129 -0
- package/dist/lib/mysql/schemas/content/tag-tag.js +21 -0
- package/dist/lib/mysql/schemas/content/tag-tag.js.map +1 -0
- package/dist/lib/mysql/schemas/content/tag.cjs +263 -0
- package/dist/lib/mysql/schemas/content/tag.cjs.map +1 -0
- package/dist/lib/mysql/schemas/content/tag.d.cts +114 -0
- package/dist/lib/mysql/schemas/content/tag.d.ts +114 -0
- package/dist/lib/mysql/schemas/content/tag.js +21 -0
- package/dist/lib/mysql/schemas/content/tag.js.map +1 -0
- package/dist/lib/mysql/utils.d.cts +1 -0
- package/dist/lib/mysql/utils.d.ts +1 -0
- package/dist/lib/utils.d.cts +2 -0
- package/dist/lib/utils.d.ts +2 -0
- package/package.json +8 -6
- package/src/index.ts +14 -4
- package/src/lib/mysql/index.ts +718 -182
- package/src/lib/mysql/schemas/commerce/coupon.ts +3 -9
- package/src/lib/mysql/schemas/content/content-resource-tag.ts +59 -0
- package/src/lib/mysql/schemas/content/content-resource-version.ts +69 -0
- package/src/lib/mysql/schemas/content/content-resource.ts +17 -0
- package/src/lib/mysql/schemas/content/tag-tag.ts +59 -0
- package/src/lib/mysql/schemas/content/tag.ts +48 -0
package/src/lib/mysql/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { AdapterSession, AdapterUser } from '@auth/core/adapters'
|
|
2
|
+
import slugify from '@sindresorhus/slugify'
|
|
2
3
|
import { addSeconds, isAfter } from 'date-fns'
|
|
3
4
|
import {
|
|
4
5
|
and,
|
|
@@ -19,6 +20,7 @@ import {
|
|
|
19
20
|
MySqlDatabase,
|
|
20
21
|
MySqlTableFn,
|
|
21
22
|
} from 'drizzle-orm/mysql-core'
|
|
23
|
+
import { customAlphabet } from 'nanoid'
|
|
22
24
|
import { v4 } from 'uuid'
|
|
23
25
|
import { z } from 'zod'
|
|
24
26
|
|
|
@@ -34,6 +36,7 @@ import {
|
|
|
34
36
|
merchantPriceSchema,
|
|
35
37
|
MerchantProduct,
|
|
36
38
|
merchantProductSchema,
|
|
39
|
+
NewProduct,
|
|
37
40
|
Price,
|
|
38
41
|
priceSchema,
|
|
39
42
|
Product,
|
|
@@ -51,15 +54,16 @@ import {
|
|
|
51
54
|
userSchema,
|
|
52
55
|
} from '@coursebuilder/core/schemas'
|
|
53
56
|
import {
|
|
57
|
+
ContentResourceProductSchema,
|
|
54
58
|
ContentResourceResourceSchema,
|
|
55
59
|
ContentResourceSchema,
|
|
56
60
|
type ContentResource,
|
|
57
|
-
type ContentResourceResource,
|
|
58
61
|
} from '@coursebuilder/core/schemas/content-resource-schema'
|
|
59
62
|
import { merchantAccountSchema } from '@coursebuilder/core/schemas/merchant-account-schema'
|
|
60
63
|
import { merchantCustomerSchema } from '@coursebuilder/core/schemas/merchant-customer-schema'
|
|
61
64
|
import { type ModuleProgress } from '@coursebuilder/core/schemas/resource-progress-schema'
|
|
62
65
|
import { VideoResourceSchema } from '@coursebuilder/core/schemas/video-resource'
|
|
66
|
+
import { PaymentsProviderConfig } from '@coursebuilder/core/types'
|
|
63
67
|
import { logger } from '@coursebuilder/core/utils/logger'
|
|
64
68
|
import { validateCoupon } from '@coursebuilder/core/utils/validate-coupon'
|
|
65
69
|
|
|
@@ -161,6 +165,14 @@ import {
|
|
|
161
165
|
getContentResourceResourceRelationsSchema,
|
|
162
166
|
getContentResourceResourceSchema,
|
|
163
167
|
} from './schemas/content/content-resource-resource.js'
|
|
168
|
+
import {
|
|
169
|
+
getContentResourceTagRelationsSchema,
|
|
170
|
+
getContentResourceTagSchema,
|
|
171
|
+
} from './schemas/content/content-resource-tag.js'
|
|
172
|
+
import {
|
|
173
|
+
getContentResourceVersionRelationsSchema,
|
|
174
|
+
getContentResourceVersionSchema,
|
|
175
|
+
} from './schemas/content/content-resource-version.js'
|
|
164
176
|
import {
|
|
165
177
|
getContentResourceRelationsSchema,
|
|
166
178
|
getContentResourceSchema,
|
|
@@ -171,6 +183,13 @@ import {
|
|
|
171
183
|
} from './schemas/content/contribution-types.js'
|
|
172
184
|
import { getLessonProgressSchema } from './schemas/content/lesson-progress.js'
|
|
173
185
|
import { getResourceProgressSchema } from './schemas/content/resource-progress.js'
|
|
186
|
+
import {
|
|
187
|
+
getTagTagRelationsSchema,
|
|
188
|
+
getTagTagSchema,
|
|
189
|
+
} from './schemas/content/tag-tag.js'
|
|
190
|
+
import { getTagRelationsSchema, getTagSchema } from './schemas/content/tag.js'
|
|
191
|
+
|
|
192
|
+
export const guid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 5)
|
|
174
193
|
|
|
175
194
|
export function getCourseBuilderSchema(mysqlTable: MySqlTableFn) {
|
|
176
195
|
return {
|
|
@@ -219,10 +238,16 @@ export function getCourseBuilderSchema(mysqlTable: MySqlTableFn) {
|
|
|
219
238
|
contentContributionRelations:
|
|
220
239
|
getContentContributionRelationsSchema(mysqlTable),
|
|
221
240
|
contentResource: getContentResourceSchema(mysqlTable),
|
|
241
|
+
contentResourceVersion: getContentResourceVersionSchema(mysqlTable),
|
|
242
|
+
contentResourceVersionRelations:
|
|
243
|
+
getContentResourceVersionRelationsSchema(mysqlTable),
|
|
222
244
|
contentResourceRelations: getContentResourceRelationsSchema(mysqlTable),
|
|
223
245
|
contentResourceResource: getContentResourceResourceSchema(mysqlTable),
|
|
224
246
|
contentResourceResourceRelations:
|
|
225
247
|
getContentResourceResourceRelationsSchema(mysqlTable),
|
|
248
|
+
contentResourceTag: getContentResourceTagSchema(mysqlTable),
|
|
249
|
+
contentResourceTagRelations:
|
|
250
|
+
getContentResourceTagRelationsSchema(mysqlTable),
|
|
226
251
|
contributionTypes: getContributionTypesSchema(mysqlTable),
|
|
227
252
|
contributionTypesRelations: getContributionTypesRelationsSchema(mysqlTable),
|
|
228
253
|
resourceProgress: getResourceProgressSchema(mysqlTable),
|
|
@@ -240,6 +265,10 @@ export function getCourseBuilderSchema(mysqlTable: MySqlTableFn) {
|
|
|
240
265
|
getDeviceVerificationRelationsSchema(mysqlTable),
|
|
241
266
|
deviceAccessToken: getDeviceAccessTokenSchema(mysqlTable),
|
|
242
267
|
deviceAccessTokenRelations: getDeviceAccessTokenRelationsSchema(mysqlTable),
|
|
268
|
+
tag: getTagSchema(mysqlTable),
|
|
269
|
+
tagRelations: getTagRelationsSchema(mysqlTable),
|
|
270
|
+
tagTag: getTagTagSchema(mysqlTable),
|
|
271
|
+
tagTagRelations: getTagTagRelationsSchema(mysqlTable),
|
|
243
272
|
userPrefs: getUserPrefsSchema(mysqlTable),
|
|
244
273
|
userPrefsRelations: getUserPrefsRelationsSchema(mysqlTable),
|
|
245
274
|
} as const
|
|
@@ -254,6 +283,7 @@ export type DefaultSchema = ReturnType<typeof createTables>
|
|
|
254
283
|
export function mySqlDrizzleAdapter(
|
|
255
284
|
client: InstanceType<typeof MySqlDatabase>,
|
|
256
285
|
tableFn = defaultMySqlTableFn,
|
|
286
|
+
paymentProvider?: PaymentsProviderConfig,
|
|
257
287
|
): CourseBuilderAdapter<typeof MySqlDatabase> {
|
|
258
288
|
const {
|
|
259
289
|
users,
|
|
@@ -262,6 +292,7 @@ export function mySqlDrizzleAdapter(
|
|
|
262
292
|
verificationTokens,
|
|
263
293
|
contentResource,
|
|
264
294
|
contentResourceResource,
|
|
295
|
+
contentResourceProduct,
|
|
265
296
|
purchases: purchaseTable,
|
|
266
297
|
purchaseUserTransfer,
|
|
267
298
|
coupon,
|
|
@@ -304,7 +335,7 @@ export function mySqlDrizzleAdapter(
|
|
|
304
335
|
if (coupon && couponValidation.isRedeemable) {
|
|
305
336
|
// if the Coupon is the Bulk Coupon of a Bulk Purchase,
|
|
306
337
|
// then a bulk coupon is being redeemed
|
|
307
|
-
const bulkCouponRedemption = Boolean(coupon.
|
|
338
|
+
const bulkCouponRedemption = Boolean(coupon.maxUses > 1)
|
|
308
339
|
|
|
309
340
|
const { user } = await adapter.findOrCreateUser(email)
|
|
310
341
|
|
|
@@ -325,8 +356,18 @@ export function mySqlDrizzleAdapter(
|
|
|
325
356
|
productId,
|
|
326
357
|
})
|
|
327
358
|
|
|
328
|
-
if (existingPurchases.length > 0)
|
|
329
|
-
|
|
359
|
+
if (existingPurchases.length > 0) {
|
|
360
|
+
const errorMessage = `already-purchased-${email}`
|
|
361
|
+
console.error(errorMessage)
|
|
362
|
+
return {
|
|
363
|
+
error: {
|
|
364
|
+
message: errorMessage,
|
|
365
|
+
},
|
|
366
|
+
redeemingForCurrentUser,
|
|
367
|
+
purchase: null,
|
|
368
|
+
}
|
|
369
|
+
throw new Error(errorMessage)
|
|
370
|
+
}
|
|
330
371
|
|
|
331
372
|
const purchaseId = `purchase-${v4()}`
|
|
332
373
|
|
|
@@ -410,7 +451,10 @@ export function mySqlDrizzleAdapter(
|
|
|
410
451
|
},
|
|
411
452
|
getMerchantPriceForProductId: async (productId) => {
|
|
412
453
|
const merchantPriceData = await client.query.merchantPrice.findFirst({
|
|
413
|
-
where:
|
|
454
|
+
where: and(
|
|
455
|
+
eq(merchantPrice.merchantProductId, productId),
|
|
456
|
+
eq(merchantPrice.status, 1),
|
|
457
|
+
),
|
|
414
458
|
})
|
|
415
459
|
|
|
416
460
|
const parsedMerchantPrice =
|
|
@@ -463,7 +507,7 @@ export function mySqlDrizzleAdapter(
|
|
|
463
507
|
)
|
|
464
508
|
|
|
465
509
|
if (previousPurchaseProductIds.length > 0) {
|
|
466
|
-
return
|
|
510
|
+
return client.query.upgradableProducts.findMany({
|
|
467
511
|
where: and(
|
|
468
512
|
eq(upgradableProducts.upgradableToId, productId),
|
|
469
513
|
inArray(
|
|
@@ -871,20 +915,21 @@ export function mySqlDrizzleAdapter(
|
|
|
871
915
|
},
|
|
872
916
|
async getCouponWithBulkPurchases(couponId: string): Promise<
|
|
873
917
|
| (Coupon & {
|
|
874
|
-
|
|
875
|
-
|
|
918
|
+
bulkPurchases?: Purchase[] | null
|
|
919
|
+
redeemedBulkCouponPurchases: { bulkCouponId?: string | null }[]
|
|
876
920
|
})
|
|
877
921
|
| null
|
|
878
922
|
> {
|
|
879
923
|
logger.debug('getCouponWithBulkPurchases', { couponId })
|
|
880
924
|
let couponData
|
|
925
|
+
let bulkCouponPurchases
|
|
881
926
|
try {
|
|
882
927
|
couponData =
|
|
883
928
|
(await client.query.coupon.findFirst({
|
|
884
929
|
where: eq(coupon.id, couponId),
|
|
885
930
|
with: {
|
|
886
|
-
|
|
887
|
-
|
|
931
|
+
bulkPurchases: true,
|
|
932
|
+
redeemedBulkCouponPurchases: true,
|
|
888
933
|
},
|
|
889
934
|
})) || null
|
|
890
935
|
} catch (e) {
|
|
@@ -892,6 +937,19 @@ export function mySqlDrizzleAdapter(
|
|
|
892
937
|
logger.error(e as Error)
|
|
893
938
|
}
|
|
894
939
|
|
|
940
|
+
try {
|
|
941
|
+
bulkCouponPurchases = await client.query.purchases.findMany({
|
|
942
|
+
where: eq(purchaseTable.redeemedBulkCouponId, couponId),
|
|
943
|
+
with: {
|
|
944
|
+
user: true,
|
|
945
|
+
},
|
|
946
|
+
})
|
|
947
|
+
console.log('purchases with redeemedBulkCouponId', bulkCouponPurchases)
|
|
948
|
+
} catch (e) {
|
|
949
|
+
console.log('getCouponWithBulkPurchases')
|
|
950
|
+
logger.error(e as Error)
|
|
951
|
+
}
|
|
952
|
+
|
|
895
953
|
if (!couponData) {
|
|
896
954
|
logger.debug('getCouponWithBulkPurchases', {
|
|
897
955
|
couponId,
|
|
@@ -900,20 +958,29 @@ export function mySqlDrizzleAdapter(
|
|
|
900
958
|
return null
|
|
901
959
|
}
|
|
902
960
|
|
|
961
|
+
const couponWithBulkPurchases = {
|
|
962
|
+
...couponData,
|
|
963
|
+
redeemedBulkCouponPurchases: bulkCouponPurchases || [],
|
|
964
|
+
}
|
|
965
|
+
|
|
903
966
|
const parsedCoupon = couponSchema
|
|
904
967
|
.merge(
|
|
905
968
|
z.object({
|
|
906
|
-
|
|
907
|
-
bulkPurchase: purchaseSchema.nullable().optional(),
|
|
969
|
+
redeemedBulkCouponPurchases: z.array(purchaseSchema),
|
|
908
970
|
}),
|
|
909
971
|
)
|
|
910
972
|
.nullable()
|
|
911
|
-
.safeParse(
|
|
973
|
+
.safeParse(couponWithBulkPurchases)
|
|
912
974
|
|
|
913
975
|
if (!parsedCoupon.success) {
|
|
914
|
-
console.error(
|
|
976
|
+
console.error(
|
|
977
|
+
'Error parsing coupon',
|
|
978
|
+
JSON.stringify(parsedCoupon.error),
|
|
979
|
+
couponData,
|
|
980
|
+
)
|
|
915
981
|
return null
|
|
916
982
|
}
|
|
983
|
+
|
|
917
984
|
return parsedCoupon.data
|
|
918
985
|
},
|
|
919
986
|
async getDefaultCoupon(productIds?: string[]): Promise<{
|
|
@@ -982,189 +1049,114 @@ export function mySqlDrizzleAdapter(
|
|
|
982
1049
|
async getModuleProgressForUser(
|
|
983
1050
|
userIdOrEmail: string,
|
|
984
1051
|
moduleIdOrSlug: string,
|
|
985
|
-
): Promise<ModuleProgress> {
|
|
986
|
-
|
|
987
|
-
const
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
eq(contentResource.id, moduleIdOrSlug),
|
|
992
|
-
eq(
|
|
993
|
-
sql`JSON_EXTRACT (${contentResource.fields}, "$.slug")`,
|
|
994
|
-
moduleIdOrSlug,
|
|
995
|
-
),
|
|
996
|
-
),
|
|
997
|
-
with: {
|
|
998
|
-
resources: {
|
|
999
|
-
with: {
|
|
1000
|
-
resource: {
|
|
1001
|
-
with: {
|
|
1002
|
-
resources: {
|
|
1003
|
-
with: {
|
|
1004
|
-
resource: {
|
|
1005
|
-
with: {
|
|
1006
|
-
resources: {
|
|
1007
|
-
with: {
|
|
1008
|
-
resource: true,
|
|
1009
|
-
},
|
|
1010
|
-
},
|
|
1011
|
-
},
|
|
1012
|
-
},
|
|
1013
|
-
},
|
|
1014
|
-
orderBy: asc(contentResourceResource.position),
|
|
1015
|
-
},
|
|
1016
|
-
},
|
|
1017
|
-
},
|
|
1018
|
-
},
|
|
1019
|
-
orderBy: asc(contentResourceResource.position),
|
|
1020
|
-
},
|
|
1052
|
+
): Promise<ModuleProgress | null> {
|
|
1053
|
+
// First, get the user ID
|
|
1054
|
+
const user = await client.query.users.findFirst({
|
|
1055
|
+
where: or(eq(users.id, userIdOrEmail), eq(users.email, userIdOrEmail)),
|
|
1056
|
+
columns: {
|
|
1057
|
+
id: true,
|
|
1021
1058
|
},
|
|
1022
1059
|
})
|
|
1023
1060
|
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
function flattenResources(
|
|
1027
|
-
resources: ContentResourceResource[],
|
|
1028
|
-
): ContentResource[] {
|
|
1029
|
-
const result: ContentResource[] = []
|
|
1030
|
-
|
|
1031
|
-
function recurse(resources: ContentResourceResource[]) {
|
|
1032
|
-
for (const nestedResource of resources) {
|
|
1033
|
-
const resource = nestedResource.resource
|
|
1034
|
-
result.push(resource)
|
|
1035
|
-
if (resource.resources) {
|
|
1036
|
-
recurse(resource.resources)
|
|
1037
|
-
}
|
|
1038
|
-
}
|
|
1039
|
-
}
|
|
1040
|
-
|
|
1041
|
-
recurse(resources)
|
|
1042
|
-
return result
|
|
1061
|
+
if (!user) {
|
|
1062
|
+
return null
|
|
1043
1063
|
}
|
|
1044
1064
|
|
|
1045
|
-
const
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1065
|
+
const ResultRowSchema = z.object({
|
|
1066
|
+
resource_id: z.string(),
|
|
1067
|
+
resource_type: z.enum(['lesson', 'exercise']),
|
|
1068
|
+
resource_slug: z.string().nullable(),
|
|
1069
|
+
completed_at: z
|
|
1070
|
+
.string()
|
|
1071
|
+
.nullable()
|
|
1072
|
+
.transform((val) => (val ? new Date(val) : null)),
|
|
1049
1073
|
})
|
|
1050
1074
|
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1075
|
+
// Execute the optimized query
|
|
1076
|
+
const results: any = await client.execute(sql`
|
|
1077
|
+
SELECT
|
|
1078
|
+
cr.id AS resource_id,
|
|
1079
|
+
cr.type AS resource_type,
|
|
1080
|
+
cr.fields->>'$.slug' AS resource_slug,
|
|
1081
|
+
rp.completedAt AS completed_at
|
|
1082
|
+
FROM
|
|
1083
|
+
(SELECT id, fields->>'$.slug' AS slug
|
|
1084
|
+
FROM ${contentResource}
|
|
1085
|
+
WHERE id = ${moduleIdOrSlug}
|
|
1086
|
+
OR fields->>'$.slug' = ${moduleIdOrSlug}) AS w
|
|
1087
|
+
LEFT JOIN
|
|
1088
|
+
(SELECT
|
|
1089
|
+
crr.resourceOfId AS workshop_id,
|
|
1090
|
+
crr.resourceId,
|
|
1091
|
+
crr.position
|
|
1092
|
+
FROM ${contentResourceResource} crr) AS tlr ON w.id = tlr.workshop_id
|
|
1093
|
+
LEFT JOIN ${contentResource} sections ON sections.id = tlr.resourceId AND sections.type = 'section'
|
|
1094
|
+
LEFT JOIN
|
|
1095
|
+
(SELECT
|
|
1096
|
+
crr.resourceOfId AS section_id,
|
|
1097
|
+
crr.resourceId AS resource_id,
|
|
1098
|
+
crr.position AS position_in_section,
|
|
1099
|
+
section_crr.position AS section_position
|
|
1100
|
+
FROM ${contentResourceResource} crr
|
|
1101
|
+
JOIN ${contentResourceResource} section_crr ON crr.resourceOfId = section_crr.resourceId) AS sr
|
|
1102
|
+
ON sr.section_id = sections.id
|
|
1103
|
+
JOIN ${contentResource} cr ON cr.id = COALESCE(sr.resource_id, tlr.resourceId)
|
|
1104
|
+
LEFT JOIN ${resourceProgress} rp ON rp.resourceId = cr.id
|
|
1105
|
+
AND rp.userId = ${user.id}
|
|
1106
|
+
WHERE
|
|
1107
|
+
cr.type IN ('lesson', 'exercise')
|
|
1108
|
+
|
|
1109
|
+
`)
|
|
1110
|
+
|
|
1111
|
+
// Process the results
|
|
1112
|
+
const completedLessons: ResourceProgress[] = []
|
|
1113
|
+
let nextResource: Partial<ContentResource> | null = null
|
|
1114
|
+
let completedLessonsCount = 0
|
|
1115
|
+
let totalLessonsCount = results.rows.length
|
|
1116
|
+
|
|
1117
|
+
const parsedRows = z.array(ResultRowSchema).safeParse(results.rows)
|
|
1118
|
+
|
|
1119
|
+
if (!parsedRows.success) {
|
|
1120
|
+
console.error('Error parsing rows', parsedRows.error)
|
|
1057
1121
|
return {
|
|
1058
1122
|
completedLessons: [],
|
|
1059
1123
|
nextResource: null,
|
|
1060
1124
|
percentCompleted: 0,
|
|
1061
1125
|
completedLessonsCount: 0,
|
|
1062
|
-
totalLessonsCount
|
|
1126
|
+
totalLessonsCount,
|
|
1063
1127
|
}
|
|
1064
1128
|
}
|
|
1065
1129
|
|
|
1066
|
-
const
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
role: true,
|
|
1082
|
-
},
|
|
1130
|
+
for (const row of parsedRows.data) {
|
|
1131
|
+
if (row.completed_at) {
|
|
1132
|
+
completedLessonsCount++
|
|
1133
|
+
completedLessons.push({
|
|
1134
|
+
userId: user.id as string,
|
|
1135
|
+
resourceId: row.resource_id,
|
|
1136
|
+
completedAt: new Date(row.completed_at),
|
|
1137
|
+
// Add other fields as needed
|
|
1138
|
+
})
|
|
1139
|
+
} else if (!nextResource) {
|
|
1140
|
+
nextResource = {
|
|
1141
|
+
id: row.resource_id,
|
|
1142
|
+
type: row.resource_type,
|
|
1143
|
+
fields: {
|
|
1144
|
+
slug: row.resource_slug,
|
|
1083
1145
|
},
|
|
1084
|
-
},
|
|
1085
|
-
})
|
|
1086
|
-
.then(async (res) => {
|
|
1087
|
-
if (res) {
|
|
1088
|
-
return {
|
|
1089
|
-
...res,
|
|
1090
|
-
roles: res.roles.map((r) => r.role),
|
|
1091
|
-
}
|
|
1092
1146
|
}
|
|
1093
|
-
})
|
|
1094
|
-
|
|
1095
|
-
if (!user) {
|
|
1096
|
-
console.error('User not found', userIdOrEmail)
|
|
1097
|
-
return {
|
|
1098
|
-
completedLessons: [],
|
|
1099
|
-
nextResource: null,
|
|
1100
|
-
percentCompleted: 0,
|
|
1101
|
-
completedLessonsCount: 0,
|
|
1102
|
-
totalLessonsCount,
|
|
1103
1147
|
}
|
|
1104
1148
|
}
|
|
1105
1149
|
|
|
1106
|
-
const
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
return {
|
|
1111
|
-
completedLessons: [],
|
|
1112
|
-
nextResource: null,
|
|
1113
|
-
percentCompleted: 0,
|
|
1114
|
-
completedLessonsCount: 0,
|
|
1115
|
-
totalLessonsCount,
|
|
1116
|
-
}
|
|
1117
|
-
}
|
|
1118
|
-
|
|
1119
|
-
const userProgress = await client.query.resourceProgress.findMany({
|
|
1120
|
-
where: and(
|
|
1121
|
-
eq(resourceProgress.userId, parsedUser.data.id),
|
|
1122
|
-
isNotNull(resourceProgress.completedAt),
|
|
1123
|
-
inArray(
|
|
1124
|
-
resourceProgress.resourceId,
|
|
1125
|
-
progressResources.map((r) => r.id),
|
|
1126
|
-
),
|
|
1127
|
-
),
|
|
1128
|
-
orderBy: asc(resourceProgress.completedAt),
|
|
1129
|
-
})
|
|
1130
|
-
|
|
1131
|
-
const nextResourceId = parsedResources.data
|
|
1132
|
-
.filter((r) => {
|
|
1133
|
-
return COUNTABLE_MODULE_RESOURCE_TYPES.includes(r.type)
|
|
1134
|
-
})
|
|
1135
|
-
.find((r) => !userProgress.find((p) => p.resourceId === r.id))?.id
|
|
1136
|
-
|
|
1137
|
-
const nextResource = await client.query.contentResource.findFirst({
|
|
1138
|
-
where: eq(contentResource.id, nextResourceId as string),
|
|
1139
|
-
})
|
|
1140
|
-
|
|
1141
|
-
const parsedNextResource = ContentResourceSchema.nullable()
|
|
1142
|
-
.optional()
|
|
1143
|
-
.default(null)
|
|
1144
|
-
.parse(nextResource)
|
|
1145
|
-
|
|
1146
|
-
const parsedProgress = z
|
|
1147
|
-
.array(resourceProgressSchema)
|
|
1148
|
-
.safeParse(userProgress)
|
|
1149
|
-
if (!parsedProgress.success) {
|
|
1150
|
-
console.error('Error parsing user progress', parsedProgress.error)
|
|
1151
|
-
return {
|
|
1152
|
-
completedLessons: [],
|
|
1153
|
-
nextResource: null,
|
|
1154
|
-
percentCompleted: 0,
|
|
1155
|
-
completedLessonsCount: 0,
|
|
1156
|
-
totalLessonsCount,
|
|
1157
|
-
}
|
|
1158
|
-
}
|
|
1159
|
-
const percentCompleted = Math.round(
|
|
1160
|
-
(parsedProgress.data.length / parsedResources.data.length) * 100,
|
|
1161
|
-
)
|
|
1150
|
+
const percentCompleted =
|
|
1151
|
+
totalLessonsCount > 0
|
|
1152
|
+
? Math.ceil((completedLessonsCount / totalLessonsCount) * 100)
|
|
1153
|
+
: 0
|
|
1162
1154
|
|
|
1163
1155
|
return {
|
|
1164
|
-
completedLessons
|
|
1165
|
-
nextResource
|
|
1156
|
+
completedLessons,
|
|
1157
|
+
nextResource,
|
|
1166
1158
|
percentCompleted,
|
|
1167
|
-
completedLessonsCount
|
|
1159
|
+
completedLessonsCount,
|
|
1168
1160
|
totalLessonsCount,
|
|
1169
1161
|
}
|
|
1170
1162
|
},
|
|
@@ -1179,7 +1171,7 @@ export function mySqlDrizzleAdapter(
|
|
|
1179
1171
|
})
|
|
1180
1172
|
const parsed = merchantChargeSchema.safeParse(mCharge)
|
|
1181
1173
|
if (!parsed.success) {
|
|
1182
|
-
console.
|
|
1174
|
+
console.info('Error parsing merchantCharge', mCharge)
|
|
1183
1175
|
return null
|
|
1184
1176
|
}
|
|
1185
1177
|
return parsed.data
|
|
@@ -1258,12 +1250,356 @@ export function mySqlDrizzleAdapter(
|
|
|
1258
1250
|
}),
|
|
1259
1251
|
)
|
|
1260
1252
|
},
|
|
1261
|
-
async
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1253
|
+
async archiveProduct(productId) {
|
|
1254
|
+
if (!paymentProvider) throw new Error('Payment provider not found')
|
|
1255
|
+
const product = await adapter.getProduct(productId)
|
|
1256
|
+
|
|
1257
|
+
if (!product) {
|
|
1258
|
+
throw new Error(`Product not found for id (${productId})`)
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
if (!product.price) {
|
|
1262
|
+
throw new Error(`Product has no price`)
|
|
1263
|
+
}
|
|
1264
|
+
|
|
1265
|
+
await client
|
|
1266
|
+
.update(products)
|
|
1267
|
+
.set({ status: 0, name: `${product.name} (Archived)` })
|
|
1268
|
+
.where(eq(products.id, productId))
|
|
1269
|
+
|
|
1270
|
+
await client
|
|
1271
|
+
.update(prices)
|
|
1272
|
+
.set({ status: 0, nickname: `${product.name} (Archived)` })
|
|
1273
|
+
.where(eq(prices.productId, productId))
|
|
1274
|
+
|
|
1275
|
+
await client
|
|
1276
|
+
.update(merchantProduct)
|
|
1277
|
+
.set({ status: 0 })
|
|
1278
|
+
.where(eq(merchantProduct.productId, productId))
|
|
1279
|
+
|
|
1280
|
+
await client
|
|
1281
|
+
.update(merchantPrice)
|
|
1282
|
+
.set({ status: 0 })
|
|
1283
|
+
.where(eq(merchantPrice.priceId, product.price.id))
|
|
1284
|
+
|
|
1285
|
+
const currentMerchantProduct = merchantProductSchema.nullish().parse(
|
|
1286
|
+
await client.query.merchantProduct.findFirst({
|
|
1287
|
+
where: eq(merchantProduct.productId, productId),
|
|
1265
1288
|
}),
|
|
1266
1289
|
)
|
|
1290
|
+
|
|
1291
|
+
if (!currentMerchantProduct || !currentMerchantProduct.identifier) {
|
|
1292
|
+
throw new Error(`Merchant product not found for id (${productId})`)
|
|
1293
|
+
}
|
|
1294
|
+
|
|
1295
|
+
await paymentProvider.updateProduct(currentMerchantProduct.identifier, {
|
|
1296
|
+
active: false,
|
|
1297
|
+
})
|
|
1298
|
+
|
|
1299
|
+
const currentMerchantPrice = merchantPriceSchema.nullish().parse(
|
|
1300
|
+
await client.query.merchantPrice.findFirst({
|
|
1301
|
+
where: and(
|
|
1302
|
+
eq(merchantPrice.priceId, product.price.id),
|
|
1303
|
+
eq(merchantPrice.status, 1),
|
|
1304
|
+
),
|
|
1305
|
+
}),
|
|
1306
|
+
)
|
|
1307
|
+
|
|
1308
|
+
if (!currentMerchantPrice || !currentMerchantPrice.identifier) {
|
|
1309
|
+
throw new Error(`Merchant price not found for id (${productId})`)
|
|
1310
|
+
}
|
|
1311
|
+
|
|
1312
|
+
await paymentProvider.updatePrice(currentMerchantPrice.identifier, {
|
|
1313
|
+
active: false,
|
|
1314
|
+
})
|
|
1315
|
+
|
|
1316
|
+
return adapter.getProduct(productId)
|
|
1317
|
+
},
|
|
1318
|
+
async updateProduct(input: Product) {
|
|
1319
|
+
if (!paymentProvider) throw new Error('Payment provider not found')
|
|
1320
|
+
const currentProduct = await adapter.getProduct(input.id)
|
|
1321
|
+
if (!currentProduct) {
|
|
1322
|
+
throw new Error(`Product not found`)
|
|
1323
|
+
}
|
|
1324
|
+
if (!currentProduct.price) {
|
|
1325
|
+
throw new Error(`Product has no price`)
|
|
1326
|
+
}
|
|
1327
|
+
|
|
1328
|
+
const merchantProduct = merchantProductSchema.nullish().parse(
|
|
1329
|
+
await client.query.merchantProduct.findFirst({
|
|
1330
|
+
where: (merchantProduct, { eq }) =>
|
|
1331
|
+
eq(merchantProduct.productId, input.id),
|
|
1332
|
+
}),
|
|
1333
|
+
)
|
|
1334
|
+
|
|
1335
|
+
if (!merchantProduct || !merchantProduct.identifier) {
|
|
1336
|
+
throw new Error(`Merchant product not found`)
|
|
1337
|
+
}
|
|
1338
|
+
|
|
1339
|
+
// TODO: handle upgrades
|
|
1340
|
+
|
|
1341
|
+
const stripeProduct = await paymentProvider.getProduct(
|
|
1342
|
+
merchantProduct.identifier,
|
|
1343
|
+
)
|
|
1344
|
+
|
|
1345
|
+
const priceChanged =
|
|
1346
|
+
currentProduct.price.unitAmount.toString() !==
|
|
1347
|
+
input.price?.unitAmount.toString()
|
|
1348
|
+
|
|
1349
|
+
if (priceChanged) {
|
|
1350
|
+
const currentMerchantPrice = merchantPriceSchema.nullish().parse(
|
|
1351
|
+
await client.query.merchantPrice.findFirst({
|
|
1352
|
+
where: (merchantPrice, { eq, and }) =>
|
|
1353
|
+
and(
|
|
1354
|
+
eq(merchantPrice.merchantProductId, merchantProduct.id),
|
|
1355
|
+
eq(merchantPrice.status, 1),
|
|
1356
|
+
),
|
|
1357
|
+
}),
|
|
1358
|
+
)
|
|
1359
|
+
|
|
1360
|
+
if (!currentMerchantPrice || !currentMerchantPrice.identifier) {
|
|
1361
|
+
throw new Error(`Merchant price not found`)
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
const currentStripePrice = await paymentProvider.getPrice(
|
|
1365
|
+
currentMerchantPrice.identifier,
|
|
1366
|
+
)
|
|
1367
|
+
|
|
1368
|
+
const newStripePrice = await paymentProvider.createPrice({
|
|
1369
|
+
product: stripeProduct.id,
|
|
1370
|
+
unit_amount: Math.floor(Number(input.price?.unitAmount || 0) * 100),
|
|
1371
|
+
currency: 'usd',
|
|
1372
|
+
metadata: {
|
|
1373
|
+
slug: input.fields.slug,
|
|
1374
|
+
},
|
|
1375
|
+
active: true,
|
|
1376
|
+
})
|
|
1377
|
+
|
|
1378
|
+
await paymentProvider.updateProduct(stripeProduct.id, {
|
|
1379
|
+
default_price: newStripePrice.id,
|
|
1380
|
+
})
|
|
1381
|
+
|
|
1382
|
+
const newMerchantPriceId = `mprice_${v4()}`
|
|
1383
|
+
await client.insert(merchantPrice).values({
|
|
1384
|
+
id: newMerchantPriceId,
|
|
1385
|
+
merchantProductId: merchantProduct.id,
|
|
1386
|
+
merchantAccountId: merchantProduct.merchantAccountId,
|
|
1387
|
+
priceId: currentProduct.price.id,
|
|
1388
|
+
status: 1,
|
|
1389
|
+
identifier: newStripePrice.id,
|
|
1390
|
+
})
|
|
1391
|
+
|
|
1392
|
+
if (currentMerchantPrice) {
|
|
1393
|
+
await client
|
|
1394
|
+
.update(merchantPrice)
|
|
1395
|
+
.set({
|
|
1396
|
+
status: 0,
|
|
1397
|
+
})
|
|
1398
|
+
.where(eq(merchantPrice.id, currentMerchantPrice.id))
|
|
1399
|
+
}
|
|
1400
|
+
|
|
1401
|
+
await client
|
|
1402
|
+
.update(prices)
|
|
1403
|
+
.set({
|
|
1404
|
+
unitAmount: Math.floor(
|
|
1405
|
+
Number(input.price?.unitAmount || 0),
|
|
1406
|
+
).toString(),
|
|
1407
|
+
nickname: input.name,
|
|
1408
|
+
})
|
|
1409
|
+
.where(eq(prices.id, currentProduct.price.id))
|
|
1410
|
+
|
|
1411
|
+
if (currentStripePrice) {
|
|
1412
|
+
await paymentProvider.updatePrice(currentStripePrice.id, {
|
|
1413
|
+
active: false,
|
|
1414
|
+
})
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
await paymentProvider.updateProduct(stripeProduct.id, {
|
|
1419
|
+
name: input.name,
|
|
1420
|
+
active: true,
|
|
1421
|
+
images: input.fields.image?.url ? [input.fields.image.url] : undefined,
|
|
1422
|
+
description: input.fields.description || '',
|
|
1423
|
+
metadata: {
|
|
1424
|
+
slug: input.fields.slug,
|
|
1425
|
+
},
|
|
1426
|
+
})
|
|
1427
|
+
|
|
1428
|
+
const { image, ...fieldsNoImage } = input.fields
|
|
1429
|
+
|
|
1430
|
+
await client
|
|
1431
|
+
.update(products)
|
|
1432
|
+
.set({
|
|
1433
|
+
name: input.name,
|
|
1434
|
+
quantityAvailable: input.quantityAvailable,
|
|
1435
|
+
status: 1,
|
|
1436
|
+
fields: {
|
|
1437
|
+
...fieldsNoImage,
|
|
1438
|
+
...(image?.url && { image }),
|
|
1439
|
+
},
|
|
1440
|
+
})
|
|
1441
|
+
.where(eq(products.id, currentProduct.id))
|
|
1442
|
+
|
|
1443
|
+
return adapter.getProduct(currentProduct.id)
|
|
1444
|
+
},
|
|
1445
|
+
async createProduct(input: NewProduct) {
|
|
1446
|
+
if (!paymentProvider) throw new Error('Payment provider not found')
|
|
1447
|
+
const merchantAccount = merchantAccountSchema.nullish().parse(
|
|
1448
|
+
await client.query.merchantAccount.findFirst({
|
|
1449
|
+
where: (merchantAccount, { eq }) =>
|
|
1450
|
+
eq(merchantAccount.label, 'stripe'),
|
|
1451
|
+
}),
|
|
1452
|
+
)
|
|
1453
|
+
|
|
1454
|
+
if (!merchantAccount) {
|
|
1455
|
+
throw new Error('Merchant account not found')
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
const hash = guid()
|
|
1459
|
+
const newProductId = slugify(`product-${hash}`)
|
|
1460
|
+
|
|
1461
|
+
const newProduct = {
|
|
1462
|
+
id: newProductId,
|
|
1463
|
+
name: input.name,
|
|
1464
|
+
status: 1,
|
|
1465
|
+
type: 'self-paced',
|
|
1466
|
+
quantityAvailable: input.quantityAvailable,
|
|
1467
|
+
fields: {
|
|
1468
|
+
state: 'draft',
|
|
1469
|
+
visibility: 'unlisted',
|
|
1470
|
+
slug: slugify(`${input.name}-${hash}`),
|
|
1471
|
+
},
|
|
1472
|
+
}
|
|
1473
|
+
|
|
1474
|
+
await client.insert(products).values(newProduct)
|
|
1475
|
+
|
|
1476
|
+
const priceHash = guid()
|
|
1477
|
+
const newPriceId = `price-${priceHash}`
|
|
1478
|
+
|
|
1479
|
+
await client.insert(prices).values({
|
|
1480
|
+
id: newPriceId,
|
|
1481
|
+
productId: newProductId,
|
|
1482
|
+
unitAmount: input.price.toString(),
|
|
1483
|
+
status: 1,
|
|
1484
|
+
})
|
|
1485
|
+
|
|
1486
|
+
const product = await adapter.getProduct(newProductId)
|
|
1487
|
+
|
|
1488
|
+
const stripeProduct = await paymentProvider.createProduct({
|
|
1489
|
+
name: input.name,
|
|
1490
|
+
metadata: {
|
|
1491
|
+
slug: product?.fields?.slug || null,
|
|
1492
|
+
},
|
|
1493
|
+
})
|
|
1494
|
+
|
|
1495
|
+
const stripePrice = await paymentProvider.createPrice({
|
|
1496
|
+
product: stripeProduct.id,
|
|
1497
|
+
unit_amount: Math.floor(Number(input.price) * 100),
|
|
1498
|
+
currency: 'usd',
|
|
1499
|
+
nickname: input.name,
|
|
1500
|
+
metadata: {
|
|
1501
|
+
slug: product?.fields?.slug || null,
|
|
1502
|
+
},
|
|
1503
|
+
})
|
|
1504
|
+
|
|
1505
|
+
const newMerchantProductId = `mproduct_${v4()}`
|
|
1506
|
+
|
|
1507
|
+
await client.insert(merchantProduct).values({
|
|
1508
|
+
id: newMerchantProductId,
|
|
1509
|
+
merchantAccountId: merchantAccount.id,
|
|
1510
|
+
productId: newProductId,
|
|
1511
|
+
identifier: stripeProduct.id,
|
|
1512
|
+
status: 1,
|
|
1513
|
+
})
|
|
1514
|
+
|
|
1515
|
+
const newMerchantPriceId = `mprice_${v4()}`
|
|
1516
|
+
await client.insert(merchantPrice).values({
|
|
1517
|
+
id: newMerchantPriceId,
|
|
1518
|
+
merchantAccountId: merchantAccount.id,
|
|
1519
|
+
merchantProductId: newMerchantProductId,
|
|
1520
|
+
priceId: newPriceId,
|
|
1521
|
+
identifier: stripePrice.id,
|
|
1522
|
+
status: 1,
|
|
1523
|
+
})
|
|
1524
|
+
|
|
1525
|
+
// TODO: handle upgrades
|
|
1526
|
+
|
|
1527
|
+
return product
|
|
1528
|
+
},
|
|
1529
|
+
async getProduct(
|
|
1530
|
+
productSlugOrId?: string,
|
|
1531
|
+
withResources: boolean = true,
|
|
1532
|
+
): Promise<Product | null> {
|
|
1533
|
+
if (!productSlugOrId) {
|
|
1534
|
+
return null
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
try {
|
|
1538
|
+
const productData = await client.query.products.findFirst({
|
|
1539
|
+
where: and(
|
|
1540
|
+
or(
|
|
1541
|
+
eq(
|
|
1542
|
+
sql`JSON_EXTRACT (${products.fields}, "$.slug")`,
|
|
1543
|
+
`${productSlugOrId}`,
|
|
1544
|
+
),
|
|
1545
|
+
eq(products.id, productSlugOrId),
|
|
1546
|
+
),
|
|
1547
|
+
),
|
|
1548
|
+
with: {
|
|
1549
|
+
price: true,
|
|
1550
|
+
...(withResources && {
|
|
1551
|
+
resources: {
|
|
1552
|
+
with: {
|
|
1553
|
+
resource: {
|
|
1554
|
+
with: {
|
|
1555
|
+
resources: true,
|
|
1556
|
+
},
|
|
1557
|
+
},
|
|
1558
|
+
},
|
|
1559
|
+
},
|
|
1560
|
+
}),
|
|
1561
|
+
},
|
|
1562
|
+
})
|
|
1563
|
+
const parsedProduct = productSchema.safeParse(productData)
|
|
1564
|
+
if (!parsedProduct.success) {
|
|
1565
|
+
console.error(
|
|
1566
|
+
'Error parsing product',
|
|
1567
|
+
JSON.stringify(parsedProduct.error),
|
|
1568
|
+
JSON.stringify(productData),
|
|
1569
|
+
)
|
|
1570
|
+
return null
|
|
1571
|
+
}
|
|
1572
|
+
return parsedProduct.data
|
|
1573
|
+
} catch (e) {
|
|
1574
|
+
console.log('getProduct error', e)
|
|
1575
|
+
return null
|
|
1576
|
+
}
|
|
1577
|
+
},
|
|
1578
|
+
async getProductResources(
|
|
1579
|
+
productId: string,
|
|
1580
|
+
): Promise<ContentResource[] | null> {
|
|
1581
|
+
const contentResourceProductsForProduct = z
|
|
1582
|
+
.array(ContentResourceProductSchema)
|
|
1583
|
+
.nullable()
|
|
1584
|
+
.parse(
|
|
1585
|
+
await client.query.contentResourceProduct.findMany({
|
|
1586
|
+
where: eq(contentResourceProduct.productId, productId),
|
|
1587
|
+
}),
|
|
1588
|
+
)
|
|
1589
|
+
|
|
1590
|
+
if (!contentResourceProductsForProduct) {
|
|
1591
|
+
return null
|
|
1592
|
+
} else {
|
|
1593
|
+
const contentResources = z.array(ContentResourceSchema).parse(
|
|
1594
|
+
await client.query.contentResource.findMany({
|
|
1595
|
+
where: inArray(
|
|
1596
|
+
contentResource.id,
|
|
1597
|
+
contentResourceProductsForProduct.map((crp) => crp.resourceId),
|
|
1598
|
+
),
|
|
1599
|
+
}),
|
|
1600
|
+
)
|
|
1601
|
+
return contentResources
|
|
1602
|
+
}
|
|
1267
1603
|
},
|
|
1268
1604
|
async getPurchaseCountForProduct(productId: string): Promise<number> {
|
|
1269
1605
|
return await client.query.purchases
|
|
@@ -1306,7 +1642,11 @@ export function mySqlDrizzleAdapter(
|
|
|
1306
1642
|
)
|
|
1307
1643
|
|
|
1308
1644
|
if (!chargeForPurchase) {
|
|
1309
|
-
logger.error(
|
|
1645
|
+
logger.error(
|
|
1646
|
+
new Error(
|
|
1647
|
+
`No charge found for purchase: Stripe Charge ID: ${stripeChargeId}`,
|
|
1648
|
+
),
|
|
1649
|
+
)
|
|
1310
1650
|
return null
|
|
1311
1651
|
}
|
|
1312
1652
|
|
|
@@ -1516,6 +1856,34 @@ export function mySqlDrizzleAdapter(
|
|
|
1516
1856
|
purchase: parsedPurchase.data,
|
|
1517
1857
|
}
|
|
1518
1858
|
},
|
|
1859
|
+
async getUserWithPurchasersByEmail(email: string): Promise<any | null> {
|
|
1860
|
+
const user = await client.query.users.findFirst({
|
|
1861
|
+
where: eq(users.email, email.trim().toLowerCase()),
|
|
1862
|
+
with: {
|
|
1863
|
+
// merchantCustomer: true,
|
|
1864
|
+
roles: {
|
|
1865
|
+
with: {
|
|
1866
|
+
role: true,
|
|
1867
|
+
},
|
|
1868
|
+
},
|
|
1869
|
+
purchases: {
|
|
1870
|
+
with: {
|
|
1871
|
+
bulkCoupon: true,
|
|
1872
|
+
merchantCharge: true,
|
|
1873
|
+
product: {
|
|
1874
|
+
columns: {
|
|
1875
|
+
id: true,
|
|
1876
|
+
name: true,
|
|
1877
|
+
key: true,
|
|
1878
|
+
},
|
|
1879
|
+
},
|
|
1880
|
+
},
|
|
1881
|
+
},
|
|
1882
|
+
},
|
|
1883
|
+
})
|
|
1884
|
+
|
|
1885
|
+
return user
|
|
1886
|
+
},
|
|
1519
1887
|
async getUserById(userId: string): Promise<User | null> {
|
|
1520
1888
|
const user = await client.query.users
|
|
1521
1889
|
.findFirst({
|
|
@@ -1632,17 +2000,156 @@ export function mySqlDrizzleAdapter(
|
|
|
1632
2000
|
|
|
1633
2001
|
return parsedLessonProgress.data
|
|
1634
2002
|
},
|
|
2003
|
+
transferPurchaseToUser: async (options: {
|
|
2004
|
+
purchaseId: string
|
|
2005
|
+
targetUserId: string
|
|
2006
|
+
sourceUserId: string
|
|
2007
|
+
}) => {
|
|
2008
|
+
const { purchaseId, targetUserId, sourceUserId } = options
|
|
2009
|
+
const transferId = `put_${v4()}`
|
|
2010
|
+
let purchase = await adapter.getPurchase(purchaseId)
|
|
2011
|
+
if (!purchase) throw new Error('No purchase found')
|
|
2012
|
+
|
|
2013
|
+
if (purchase.userId !== sourceUserId)
|
|
2014
|
+
throw new Error('Invalid source user')
|
|
2015
|
+
|
|
2016
|
+
await client.transaction(async (trx) => {
|
|
2017
|
+
if (!purchase) throw new Error('No purchase found')
|
|
2018
|
+
await trx
|
|
2019
|
+
.update(purchaseTable)
|
|
2020
|
+
.set({ userId: targetUserId })
|
|
2021
|
+
.where(eq(purchaseTable.id, purchase.id))
|
|
2022
|
+
|
|
2023
|
+
await trx.insert(purchaseUserTransfer).values({
|
|
2024
|
+
id: transferId,
|
|
2025
|
+
purchaseId: purchase.id,
|
|
2026
|
+
sourceUserId: sourceUserId,
|
|
2027
|
+
targetUserId: targetUserId,
|
|
2028
|
+
transferState: 'COMPLETED',
|
|
2029
|
+
completedAt: new Date(),
|
|
2030
|
+
})
|
|
2031
|
+
|
|
2032
|
+
purchase = await adapter.getPurchase(purchaseId)
|
|
2033
|
+
|
|
2034
|
+
if (!purchase) throw new Error('No purchase found')
|
|
2035
|
+
|
|
2036
|
+
if (paymentProvider && purchase.merchantChargeId) {
|
|
2037
|
+
await trx
|
|
2038
|
+
.update(merchantCharge)
|
|
2039
|
+
.set({
|
|
2040
|
+
userId: targetUserId,
|
|
2041
|
+
})
|
|
2042
|
+
.where(eq(merchantCharge.id, purchase.merchantChargeId))
|
|
2043
|
+
|
|
2044
|
+
const updatedMerchantCharge = await adapter.getMerchantCharge(
|
|
2045
|
+
purchase.merchantChargeId,
|
|
2046
|
+
)
|
|
2047
|
+
|
|
2048
|
+
if (!updatedMerchantCharge)
|
|
2049
|
+
throw new Error('No merchant charge found')
|
|
2050
|
+
|
|
2051
|
+
await trx
|
|
2052
|
+
.update(merchantCustomer)
|
|
2053
|
+
.set({ userId: targetUserId })
|
|
2054
|
+
.where(
|
|
2055
|
+
eq(merchantCustomer.id, updatedMerchantCharge.merchantCustomerId),
|
|
2056
|
+
)
|
|
2057
|
+
|
|
2058
|
+
const updatedMerchantCustomer = merchantCustomerSchema.parse(
|
|
2059
|
+
await client.query.merchantCustomer.findFirst({
|
|
2060
|
+
where: eq(
|
|
2061
|
+
merchantCustomer.id,
|
|
2062
|
+
updatedMerchantCharge.merchantCustomerId,
|
|
2063
|
+
),
|
|
2064
|
+
}),
|
|
2065
|
+
)
|
|
2066
|
+
|
|
2067
|
+
const targetUser = userSchema.parse(
|
|
2068
|
+
await client.query.users.findFirst({
|
|
2069
|
+
where: eq(users.id, targetUserId),
|
|
2070
|
+
}),
|
|
2071
|
+
)
|
|
2072
|
+
|
|
2073
|
+
const sourceUser = userSchema.parse(
|
|
2074
|
+
await client.query.users.findFirst({
|
|
2075
|
+
where: eq(users.id, sourceUserId),
|
|
2076
|
+
}),
|
|
2077
|
+
)
|
|
2078
|
+
|
|
2079
|
+
await paymentProvider.updateCustomer(
|
|
2080
|
+
updatedMerchantCustomer.identifier,
|
|
2081
|
+
{
|
|
2082
|
+
email: targetUser.email,
|
|
2083
|
+
name: targetUser.name || sourceUser.name || '',
|
|
2084
|
+
metadata: {
|
|
2085
|
+
transferId,
|
|
2086
|
+
transferredFrom: sourceUser.email,
|
|
2087
|
+
transferredOn: new Date().toISOString(),
|
|
2088
|
+
},
|
|
2089
|
+
},
|
|
2090
|
+
)
|
|
2091
|
+
}
|
|
2092
|
+
})
|
|
2093
|
+
return await adapter.getPurchaseUserTransferById({
|
|
2094
|
+
id: transferId,
|
|
2095
|
+
})
|
|
2096
|
+
},
|
|
1635
2097
|
transferPurchasesToNewUser(options: {
|
|
1636
2098
|
merchantCustomerId: string
|
|
1637
2099
|
userId: string
|
|
1638
2100
|
}): Promise<unknown> {
|
|
1639
2101
|
throw new Error('Method not implemented.')
|
|
1640
2102
|
},
|
|
1641
|
-
updatePurchaseStatusForCharge(
|
|
2103
|
+
async updatePurchaseStatusForCharge(
|
|
1642
2104
|
chargeId: string,
|
|
1643
2105
|
status: 'Valid' | 'Refunded' | 'Disputed' | 'Banned' | 'Restricted',
|
|
1644
2106
|
): Promise<Purchase | undefined> {
|
|
1645
|
-
|
|
2107
|
+
try {
|
|
2108
|
+
console.log('updatePurchaseStatusForCharge', { chargeId, status })
|
|
2109
|
+
const merchantChargeForPurchase =
|
|
2110
|
+
await client.query.merchantCharge.findFirst({
|
|
2111
|
+
where: or(
|
|
2112
|
+
eq(merchantCharge.identifier, chargeId),
|
|
2113
|
+
eq(merchantCharge.id, chargeId),
|
|
2114
|
+
),
|
|
2115
|
+
})
|
|
2116
|
+
|
|
2117
|
+
console.log('merchantChargeForPurchase', { merchantChargeForPurchase })
|
|
2118
|
+
|
|
2119
|
+
const parsedMerchantChargeForPurchase = merchantChargeSchema.parse(
|
|
2120
|
+
merchantChargeForPurchase,
|
|
2121
|
+
)
|
|
2122
|
+
|
|
2123
|
+
if (!parsedMerchantChargeForPurchase)
|
|
2124
|
+
throw new Error(`no-charge-found-for-purchase ${chargeId}`)
|
|
2125
|
+
|
|
2126
|
+
const purchase = await client.query.purchases.findFirst({
|
|
2127
|
+
where: eq(
|
|
2128
|
+
purchaseTable.merchantChargeId,
|
|
2129
|
+
parsedMerchantChargeForPurchase.id,
|
|
2130
|
+
),
|
|
2131
|
+
})
|
|
2132
|
+
|
|
2133
|
+
const parsedPurchase = purchaseSchema.nullable().parse(purchase)
|
|
2134
|
+
|
|
2135
|
+
if (parsedPurchase) {
|
|
2136
|
+
await client
|
|
2137
|
+
.update(purchaseTable)
|
|
2138
|
+
.set({ status: status })
|
|
2139
|
+
.where(eq(purchaseTable.id, parsedPurchase.id))
|
|
2140
|
+
|
|
2141
|
+
const newPurchase = await client.query.purchases.findFirst({
|
|
2142
|
+
where: eq(purchaseTable.id, parsedPurchase.id),
|
|
2143
|
+
})
|
|
2144
|
+
|
|
2145
|
+
return purchaseSchema.optional().parse(newPurchase)
|
|
2146
|
+
} else {
|
|
2147
|
+
throw new Error(`no-purchase-found-for-charge ${chargeId}`)
|
|
2148
|
+
}
|
|
2149
|
+
} catch (e) {
|
|
2150
|
+
console.log('error updating purchase status', e)
|
|
2151
|
+
throw e
|
|
2152
|
+
}
|
|
1646
2153
|
},
|
|
1647
2154
|
async updatePurchaseUserTransferTransferState(options: {
|
|
1648
2155
|
id: string
|
|
@@ -1706,6 +2213,35 @@ export function mySqlDrizzleAdapter(
|
|
|
1706
2213
|
|
|
1707
2214
|
return parsedResourceJoin.data
|
|
1708
2215
|
},
|
|
2216
|
+
async removeResourceFromResource(options) {
|
|
2217
|
+
const { childResourceId, parentResourceId } = options
|
|
2218
|
+
|
|
2219
|
+
const resourceJoin = await client.query.contentResourceResource.findFirst(
|
|
2220
|
+
{
|
|
2221
|
+
where: and(
|
|
2222
|
+
eq(contentResourceResource.resourceOfId, parentResourceId),
|
|
2223
|
+
eq(contentResourceResource.resourceId, childResourceId),
|
|
2224
|
+
),
|
|
2225
|
+
},
|
|
2226
|
+
)
|
|
2227
|
+
|
|
2228
|
+
const parsedResourceJoin =
|
|
2229
|
+
ContentResourceResourceSchema.safeParse(resourceJoin)
|
|
2230
|
+
if (!parsedResourceJoin.success) {
|
|
2231
|
+
return null
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
await client
|
|
2235
|
+
.delete(contentResourceResource)
|
|
2236
|
+
.where(
|
|
2237
|
+
and(
|
|
2238
|
+
eq(contentResourceResource.resourceOfId, parentResourceId),
|
|
2239
|
+
eq(contentResourceResource.resourceId, childResourceId),
|
|
2240
|
+
),
|
|
2241
|
+
)
|
|
2242
|
+
|
|
2243
|
+
return parsedResourceJoin.data.resource
|
|
2244
|
+
},
|
|
1709
2245
|
async updateContentResourceFields(options) {
|
|
1710
2246
|
if (!options.id) {
|
|
1711
2247
|
throw new Error('No content resource id.')
|