UNPKG

@coursebuilder/adapter-drizzle

Version:

Drizzle adapter for Course Builder.

1 lines 875 kB
{"version":3,"sources":["../../../src/lib/mysql/index.ts","../../../../../node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.mjs","../../../../core/dist/chunk-VLQXSCFN.js","../../../../../node_modules/.pnpm/zod@3.24.2/node_modules/zod/lib/index.mjs","../../../../core/src/schemas/upgradable-product-schema.ts","../../../../core/src/schemas/video-resource.ts","../../../../core/src/schemas/price-schema.ts","../../../../core/src/schemas/content-resource-schema.ts","../../../../core/src/schemas/product-schema.ts","../../../../core/src/schemas/user-schema.ts","../../../../core/src/schemas/coupon-schema.ts","../../../../core/src/schemas/purchase-schema.ts","../../../../core/src/schemas/purchase-user-transfer-schema.ts","../../../../core/src/schemas/resource-progress-schema.ts","../../../../core/src/schemas/merchant-customer-schema.ts","../../../../core/src/schemas/merchant-price-schema.ts","../../../../core/src/schemas/merchant-product-schema.ts","../../../../core/src/schemas/merchant-charge-schema.ts","../../../../core/src/schemas/merchant-coupon-schema.ts","../../../../core/src/schemas/merchant-account-schema.ts","../../../../core/src/schemas/merchant-session.ts","../../../../core/src/schemas/merchant-subscription.ts","../../../../core/src/schemas/organization-schema.ts","../../../../core/src/schemas/organization-member.ts","../../../../core/src/schemas/subscription.ts","../../../../core/src/errors.ts","../../../../core/src/lib/utils/logger.ts","../../../../core/src/lib/utils/validate-coupon.ts","../../../src/lib/mysql/schemas/auth/accounts.ts","../../../src/lib/mysql/schemas/auth/users.ts","../../../src/lib/mysql/schemas/commerce/purchase.ts","../../../src/lib/mysql/schemas/org/organization-memberships.ts","../../../src/lib/mysql/schemas/org/organization-membership-roles.ts","../../../src/lib/mysql/schemas/auth/roles.ts","../../../src/lib/mysql/schemas/auth/user-roles.ts","../../../src/lib/mysql/schemas/org/organizations.ts","../../../src/lib/mysql/schemas/commerce/subscription.ts","../../../src/lib/mysql/schemas/commerce/merchant-subscription.ts","../../../src/lib/mysql/schemas/commerce/merchant-charge.ts","../../../src/lib/mysql/schemas/commerce/merchant-account.ts","../../../src/lib/mysql/schemas/commerce/merchant-customer.ts","../../../src/lib/mysql/schemas/commerce/merchant-product.ts","../../../src/lib/mysql/schemas/commerce/product.ts","../../../src/lib/mysql/schemas/content/content-resource-product.ts","../../../src/lib/mysql/schemas/content/content-resource.ts","../../../src/lib/mysql/schemas/content/content-contributions.ts","../../../src/lib/mysql/schemas/content/contribution-types.ts","../../../src/lib/mysql/schemas/content/content-resource-resource.ts","../../../src/lib/mysql/schemas/content/content-resource-tag.ts","../../../src/lib/mysql/schemas/content/tag.ts","../../../src/lib/mysql/schemas/content/tag-tag.ts","../../../src/lib/mysql/schemas/content/content-resource-version.ts","../../../src/lib/mysql/schemas/commerce/price.ts","../../../src/lib/mysql/schemas/commerce/merchant-price.ts","../../../src/lib/mysql/schemas/commerce/coupon.ts","../../../src/lib/mysql/schemas/commerce/merchant-coupon.ts","../../../src/lib/mysql/schemas/commerce/merchant-session.ts","../../../src/lib/mysql/schemas/communication/comment.ts","../../../src/lib/mysql/schemas/communication/communication-preferences.ts","../../../src/lib/mysql/schemas/communication/communication-channel.ts","../../../src/lib/mysql/schemas/communication/communication-preference-types.ts","../../../src/lib/mysql/schemas/auth/profiles.ts","../../../src/lib/mysql/schemas/auth/user-permissions.ts","../../../src/lib/mysql/schemas/auth/permissions.ts","../../../src/lib/mysql/schemas/auth/user-prefs.ts","../../../src/lib/mysql/schemas/auth/device-access-token.ts","../../../src/lib/mysql/schemas/auth/device-verification.ts","../../../src/lib/mysql/schemas/auth/role-permissions.ts","../../../src/lib/mysql/schemas/auth/sessions.ts","../../../src/lib/mysql/schemas/auth/verification-tokens.ts","../../../src/lib/mysql/schemas/commerce/purchase-user-transfer.ts","../../../src/lib/mysql/schemas/commerce/upgradable-products.ts","../../../src/lib/mysql/schemas/content/lesson-progress.ts","../../../src/lib/mysql/schemas/content/resource-progress.ts","../../../src/lib/mysql/schemas/entitlements/entitlement-type.ts","../../../src/lib/mysql/schemas/entitlements/entitlement.ts"],"sourcesContent":["import type { AdapterSession, AdapterUser } from '@auth/core/adapters'\nimport slugify from '@sindresorhus/slugify'\nimport { addSeconds, isAfter } from 'date-fns'\nimport {\n\tand,\n\tasc,\n\tcount,\n\tdesc,\n\teq,\n\tgte,\n\tinArray,\n\tisNotNull,\n\tisNull,\n\tnot,\n\tor,\n\tsql,\n} from 'drizzle-orm'\nimport {\n\tmysqlTable as defaultMySqlTableFn,\n\tMySqlDatabase,\n\tMySqlTableFn,\n} from 'drizzle-orm/mysql-core'\nimport { customAlphabet } from 'nanoid'\nimport { v4 } from 'uuid'\nimport { z } from 'zod'\n\nimport { type CourseBuilderAdapter } from '@coursebuilder/core/adapters'\nimport {\n\tCoupon,\n\tcouponSchema,\n\tMerchantCharge,\n\tmerchantChargeSchema,\n\tMerchantCoupon,\n\tmerchantCouponSchema,\n\tMerchantCustomer,\n\tmerchantPriceSchema,\n\tMerchantProduct,\n\tmerchantProductSchema,\n\tNewProduct,\n\tPrice,\n\tpriceSchema,\n\tProduct,\n\tproductSchema,\n\tPurchase,\n\tpurchaseSchema,\n\tPurchaseUserTransfer,\n\tpurchaseUserTransferSchema,\n\tPurchaseUserTransferState,\n\tResourceProgress,\n\tresourceProgressSchema,\n\tUpgradableProduct,\n\tupgradableProductSchema,\n\tUser,\n\tuserSchema,\n} from '@coursebuilder/core/schemas'\nimport {\n\tContentResourceProductSchema,\n\tContentResourceResourceSchema,\n\tContentResourceSchema,\n\ttype ContentResource,\n} from '@coursebuilder/core/schemas/content-resource-schema'\nimport { merchantAccountSchema } from '@coursebuilder/core/schemas/merchant-account-schema'\nimport { merchantCustomerSchema } from '@coursebuilder/core/schemas/merchant-customer-schema'\nimport {\n\tMerchantSession,\n\tMerchantSessionSchema,\n} from '@coursebuilder/core/schemas/merchant-session'\nimport { MerchantSubscriptionSchema } from '@coursebuilder/core/schemas/merchant-subscription'\nimport { OrganizationMemberSchema } from '@coursebuilder/core/schemas/organization-member'\nimport { OrganizationSchema } from '@coursebuilder/core/schemas/organization-schema'\nimport { type ModuleProgress } from '@coursebuilder/core/schemas/resource-progress-schema'\nimport { SubscriptionSchema } from '@coursebuilder/core/schemas/subscription'\nimport { VideoResourceSchema } from '@coursebuilder/core/schemas/video-resource'\nimport { PaymentsProviderConfig } from '@coursebuilder/core/types'\nimport { logger } from '@coursebuilder/core/utils/logger'\nimport { validateCoupon } from '@coursebuilder/core/utils/validate-coupon'\n\nimport {\n\tgetAccountsRelationsSchema,\n\tgetAccountsSchema,\n} from './schemas/auth/accounts.js'\nimport {\n\tgetDeviceAccessTokenRelationsSchema,\n\tgetDeviceAccessTokenSchema,\n} from './schemas/auth/device-access-token.js'\nimport {\n\tgetDeviceVerificationRelationsSchema,\n\tgetDeviceVerificationSchema,\n} from './schemas/auth/device-verification.js'\nimport {\n\tgetPermissionsRelationsSchema,\n\tgetPermissionsSchema,\n} from './schemas/auth/permissions.js'\nimport {\n\tgetProfilesRelationsSchema,\n\tgetProfilesSchema,\n} from './schemas/auth/profiles.js'\nimport {\n\tgetRolePermissionsRelationsSchema,\n\tgetRolePermissionsSchema,\n} from './schemas/auth/role-permissions.js'\nimport {\n\tgetRolesRelationsSchema,\n\tgetRolesSchema,\n} from './schemas/auth/roles.js'\nimport {\n\tgetSessionRelationsSchema,\n\tgetSessionsSchema,\n} from './schemas/auth/sessions.js'\nimport {\n\tgetUserPermissionsRelationsSchema,\n\tgetUserPermissionsSchema,\n} from './schemas/auth/user-permissions.js'\nimport {\n\tgetUserPrefsRelationsSchema,\n\tgetUserPrefsSchema,\n} from './schemas/auth/user-prefs.js'\nimport {\n\tgetUserRolesRelationsSchema,\n\tgetUserRolesSchema,\n} from './schemas/auth/user-roles.js'\nimport {\n\tgetUsersRelationsSchema,\n\tgetUsersSchema,\n} from './schemas/auth/users.js'\nimport { getVerificationTokensSchema } from './schemas/auth/verification-tokens.js'\nimport {\n\tgetCouponRelationsSchema,\n\tgetCouponSchema,\n} from './schemas/commerce/coupon.js'\nimport { getMerchantAccountSchema } from './schemas/commerce/merchant-account.js'\nimport {\n\tgetMerchantChargeRelationsSchema,\n\tgetMerchantChargeSchema,\n} from './schemas/commerce/merchant-charge.js'\nimport { getMerchantCouponSchema } from './schemas/commerce/merchant-coupon.js'\nimport { getMerchantCustomerSchema } from './schemas/commerce/merchant-customer.js'\nimport { getMerchantPriceSchema } from './schemas/commerce/merchant-price.js'\nimport { getMerchantProductSchema } from './schemas/commerce/merchant-product.js'\nimport { getMerchantSessionSchema } from './schemas/commerce/merchant-session.js'\nimport {\n\tgetMerchantSubscriptionRelationsSchema,\n\tgetMerchantSubscriptionSchema,\n} from './schemas/commerce/merchant-subscription.js'\nimport { getPriceSchema } from './schemas/commerce/price.js'\nimport {\n\tgetProductRelationsSchema,\n\tgetProductSchema,\n} from './schemas/commerce/product.js'\nimport {\n\tgetPurchaseUserTransferRelationsSchema,\n\tgetPurchaseUserTransferSchema,\n} from './schemas/commerce/purchase-user-transfer.js'\nimport {\n\tgetPurchaseRelationsSchema,\n\tgetPurchaseSchema,\n} from './schemas/commerce/purchase.js'\nimport {\n\tgetSubscriptionRelationsSchema,\n\tgetSubscriptionSchema,\n} from './schemas/commerce/subscription.js'\nimport {\n\tgetUpgradableProductsRelationsSchema,\n\tgetUpgradableProductsSchema,\n} from './schemas/commerce/upgradable-products.js'\nimport {\n\tgetCommentRelationsSchema,\n\tgetCommentsSchema,\n} from './schemas/communication/comment.js'\nimport { getCommunicationChannelSchema } from './schemas/communication/communication-channel.js'\nimport { getCommunicationPreferenceTypesSchema } from './schemas/communication/communication-preference-types.js'\nimport {\n\tgetCommunicationPreferencesRelationsSchema,\n\tgetCommunicationPreferencesSchema,\n} from './schemas/communication/communication-preferences.js'\nimport {\n\tgetContentContributionRelationsSchema,\n\tgetContentContributionsSchema,\n} from './schemas/content/content-contributions.js'\nimport {\n\tgetContentResourceProductRelationsSchema,\n\tgetContentResourceProductSchema,\n} from './schemas/content/content-resource-product.js'\nimport {\n\tgetContentResourceResourceRelationsSchema,\n\tgetContentResourceResourceSchema,\n} from './schemas/content/content-resource-resource.js'\nimport {\n\tgetContentResourceTagRelationsSchema,\n\tgetContentResourceTagSchema,\n} from './schemas/content/content-resource-tag.js'\nimport {\n\tgetContentResourceVersionRelationsSchema,\n\tgetContentResourceVersionSchema,\n} from './schemas/content/content-resource-version.js'\nimport {\n\tgetContentResourceRelationsSchema,\n\tgetContentResourceSchema,\n} from './schemas/content/content-resource.js'\nimport {\n\tgetContributionTypesRelationsSchema,\n\tgetContributionTypesSchema,\n} from './schemas/content/contribution-types.js'\nimport { getLessonProgressSchema } from './schemas/content/lesson-progress.js'\nimport { getResourceProgressSchema } from './schemas/content/resource-progress.js'\nimport {\n\tgetTagTagRelationsSchema,\n\tgetTagTagSchema,\n} from './schemas/content/tag-tag.js'\nimport { getTagRelationsSchema, getTagSchema } from './schemas/content/tag.js'\nimport { getEntitlementTypesSchema } from './schemas/entitlements/entitlement-type.js'\nimport {\n\tgetEntitlementRelationsSchema,\n\tgetEntitlementsSchema,\n} from './schemas/entitlements/entitlement.js'\nimport {\n\tgetOrganizationMembershipRolesRelationsSchema,\n\tgetOrganizationMembershipRolesSchema,\n} from './schemas/org/organization-membership-roles.js'\nimport {\n\tgetOrganizationMembershipsRelationsSchema,\n\tgetOrganizationMembershipsSchema,\n} from './schemas/org/organization-memberships.js'\nimport {\n\tgetOrganizationsRelationsSchema,\n\tgetOrganizationsSchema,\n} from './schemas/org/organizations.js'\n\nexport const guid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz', 5)\n\nexport function getCourseBuilderSchema(mysqlTable: MySqlTableFn) {\n\treturn {\n\t\taccounts: getAccountsSchema(mysqlTable),\n\t\taccountsRelations: getAccountsRelationsSchema(mysqlTable),\n\t\tpermissions: getPermissionsSchema(mysqlTable),\n\t\tpermissionsRelations: getPermissionsRelationsSchema(mysqlTable),\n\t\trolePermissions: getRolePermissionsSchema(mysqlTable),\n\t\trolePermissionsRelations: getRolePermissionsRelationsSchema(mysqlTable),\n\t\troles: getRolesSchema(mysqlTable),\n\t\trolesRelations: getRolesRelationsSchema(mysqlTable),\n\t\tsessions: getSessionsSchema(mysqlTable),\n\t\tsessionsRelations: getSessionRelationsSchema(mysqlTable),\n\t\tuserPermissions: getUserPermissionsSchema(mysqlTable),\n\t\tuserPermissionsRelations: getUserPermissionsRelationsSchema(mysqlTable),\n\t\tuserRoles: getUserRolesSchema(mysqlTable),\n\t\tuserRolesRelations: getUserRolesRelationsSchema(mysqlTable),\n\t\tusers: getUsersSchema(mysqlTable),\n\t\tusersRelations: getUsersRelationsSchema(mysqlTable),\n\t\tverificationTokens: getVerificationTokensSchema(mysqlTable),\n\t\tcoupon: getCouponSchema(mysqlTable),\n\t\tcouponRelations: getCouponRelationsSchema(mysqlTable),\n\t\tlessonProgress: getLessonProgressSchema(mysqlTable),\n\t\tmerchantAccount: getMerchantAccountSchema(mysqlTable),\n\t\tmerchantCharge: getMerchantChargeSchema(mysqlTable),\n\t\tmerchantChargeRelations: getMerchantChargeRelationsSchema(mysqlTable),\n\t\tmerchantCoupon: getMerchantCouponSchema(mysqlTable),\n\t\tmerchantCustomer: getMerchantCustomerSchema(mysqlTable),\n\t\tmerchantPrice: getMerchantPriceSchema(mysqlTable),\n\t\tmerchantProduct: getMerchantProductSchema(mysqlTable),\n\t\tmerchantSession: getMerchantSessionSchema(mysqlTable),\n\t\tprices: getPriceSchema(mysqlTable),\n\t\tproducts: getProductSchema(mysqlTable),\n\t\tpurchases: getPurchaseSchema(mysqlTable),\n\t\tpurchaseRelations: getPurchaseRelationsSchema(mysqlTable),\n\t\tpurchaseUserTransfer: getPurchaseUserTransferSchema(mysqlTable),\n\t\tpurchaseUserTransferRelations:\n\t\t\tgetPurchaseUserTransferRelationsSchema(mysqlTable),\n\t\tcommunicationChannel: getCommunicationChannelSchema(mysqlTable),\n\t\tcommunicationPreferenceTypes:\n\t\t\tgetCommunicationPreferenceTypesSchema(mysqlTable),\n\t\tcommunicationPreferences: getCommunicationPreferencesSchema(mysqlTable),\n\t\tcommunicationPreferencesRelations:\n\t\t\tgetCommunicationPreferencesRelationsSchema(mysqlTable),\n\t\tcontentContributions: getContentContributionsSchema(mysqlTable),\n\t\tcontentContributionRelations:\n\t\t\tgetContentContributionRelationsSchema(mysqlTable),\n\t\tcontentResource: getContentResourceSchema(mysqlTable),\n\t\tcontentResourceVersion: getContentResourceVersionSchema(mysqlTable),\n\t\tcontentResourceVersionRelations:\n\t\t\tgetContentResourceVersionRelationsSchema(mysqlTable),\n\t\tcontentResourceRelations: getContentResourceRelationsSchema(mysqlTable),\n\t\tcontentResourceResource: getContentResourceResourceSchema(mysqlTable),\n\t\tcontentResourceResourceRelations:\n\t\t\tgetContentResourceResourceRelationsSchema(mysqlTable),\n\t\tcontentResourceTag: getContentResourceTagSchema(mysqlTable),\n\t\tcontentResourceTagRelations:\n\t\t\tgetContentResourceTagRelationsSchema(mysqlTable),\n\t\tcontributionTypes: getContributionTypesSchema(mysqlTable),\n\t\tcontributionTypesRelations: getContributionTypesRelationsSchema(mysqlTable),\n\t\tresourceProgress: getResourceProgressSchema(mysqlTable),\n\t\tupgradableProducts: getUpgradableProductsSchema(mysqlTable),\n\t\tupgradableProductsRelations:\n\t\t\tgetUpgradableProductsRelationsSchema(mysqlTable),\n\t\tcontentResourceProduct: getContentResourceProductSchema(mysqlTable),\n\t\tcontentResourceProductRelations:\n\t\t\tgetContentResourceProductRelationsSchema(mysqlTable),\n\t\tproductRelations: getProductRelationsSchema(mysqlTable),\n\t\tcomments: getCommentsSchema(mysqlTable),\n\t\tcommentsRelations: getCommentRelationsSchema(mysqlTable),\n\t\tdeviceVerifications: getDeviceVerificationSchema(mysqlTable),\n\t\tdeviceVerificationRelations:\n\t\t\tgetDeviceVerificationRelationsSchema(mysqlTable),\n\t\tdeviceAccessToken: getDeviceAccessTokenSchema(mysqlTable),\n\t\tdeviceAccessTokenRelations: getDeviceAccessTokenRelationsSchema(mysqlTable),\n\t\ttag: getTagSchema(mysqlTable),\n\t\ttagRelations: getTagRelationsSchema(mysqlTable),\n\t\ttagTag: getTagTagSchema(mysqlTable),\n\t\ttagTagRelations: getTagTagRelationsSchema(mysqlTable),\n\t\tuserPrefs: getUserPrefsSchema(mysqlTable),\n\t\tuserPrefsRelations: getUserPrefsRelationsSchema(mysqlTable),\n\t\torganization: getOrganizationsSchema(mysqlTable),\n\t\torganizationRelations: getOrganizationsRelationsSchema(mysqlTable),\n\t\torganizationMemberships: getOrganizationMembershipsSchema(mysqlTable),\n\t\torganizationMembershipRelations:\n\t\t\tgetOrganizationMembershipsRelationsSchema(mysqlTable),\n\t\torganizationMembershipRoles:\n\t\t\tgetOrganizationMembershipRolesSchema(mysqlTable),\n\t\torganizationMembershipRolesRelations:\n\t\t\tgetOrganizationMembershipRolesRelationsSchema(mysqlTable),\n\t\tmerchantSubscription: getMerchantSubscriptionSchema(mysqlTable),\n\t\tmerchantSubscriptionRelations:\n\t\t\tgetMerchantSubscriptionRelationsSchema(mysqlTable),\n\t\tsubscription: getSubscriptionSchema(mysqlTable),\n\t\tsubscriptionRelations: getSubscriptionRelationsSchema(mysqlTable),\n\t\tprofiles: getProfilesSchema(mysqlTable),\n\t\tprofilesRelations: getProfilesRelationsSchema(mysqlTable),\n\t\tentitlementTypes: getEntitlementTypesSchema(mysqlTable),\n\t\tentitlements: getEntitlementsSchema(mysqlTable),\n\t\tentitlementsRelations: getEntitlementRelationsSchema(mysqlTable),\n\t} as const\n}\n\nexport function createTables(mySqlTable: MySqlTableFn) {\n\treturn getCourseBuilderSchema(mySqlTable)\n}\n\nexport type DefaultSchema = ReturnType<typeof createTables>\n\nexport function mySqlDrizzleAdapter(\n\tclient: InstanceType<typeof MySqlDatabase>,\n\ttableFn = defaultMySqlTableFn,\n\tpaymentProvider?: PaymentsProviderConfig,\n): CourseBuilderAdapter<typeof MySqlDatabase> {\n\tconst {\n\t\tusers,\n\t\taccounts,\n\t\tsessions,\n\t\tverificationTokens,\n\t\tcontentResource,\n\t\tcontentResourceResource,\n\t\tcontentResourceProduct,\n\t\tpurchases: purchaseTable,\n\t\tpurchaseUserTransfer,\n\t\tcoupon,\n\t\tmerchantCoupon,\n\t\tmerchantCharge,\n\t\tmerchantAccount,\n\t\tmerchantPrice,\n\t\tmerchantCustomer,\n\t\tmerchantSession,\n\t\tmerchantProduct,\n\t\tprices,\n\t\tproducts,\n\t\tupgradableProducts,\n\t\tresourceProgress,\n\t\tcomments,\n\t\torganization: organizationTable,\n\t\torganizationMemberships: organizationMembershipTable,\n\t\torganizationMembershipRoles: organizationMembershipRoleTable,\n\t\troles: roleTable,\n\t\tmerchantSubscription: merchantSubscriptionTable,\n\t\tsubscription: subscriptionTable,\n\t} = createTables(tableFn)\n\n\tconst adapter: CourseBuilderAdapter = {\n\t\tclient,\n\t\tasync redeemFullPriceCoupon(options) {\n\t\t\tconst {\n\t\t\t\temail: baseEmail,\n\t\t\t\tcouponId,\n\t\t\t\tproductIds,\n\t\t\t\tcurrentUserId,\n\t\t\t\tredeemingProductId,\n\t\t\t} = options\n\t\t\tconst email = String(baseEmail).replace(' ', '+')\n\n\t\t\tconst coupon = await adapter.getCouponWithBulkPurchases(couponId)\n\n\t\t\tconst productId =\n\t\t\t\t(coupon && (coupon.restrictedToProductId as string)) ||\n\t\t\t\tredeemingProductId\n\n\t\t\tif (!productId) throw new Error(`unable-to-find-any-product-id`)\n\n\t\t\tconst couponValidation = validateCoupon(coupon, productIds)\n\n\t\t\tif (coupon && couponValidation.isRedeemable) {\n\t\t\t\t// if the Coupon is the Bulk Coupon of a Bulk Purchase,\n\t\t\t\t// then a bulk coupon is being redeemed\n\t\t\t\tconst bulkCouponRedemption = Boolean(coupon.maxUses > 1)\n\n\t\t\t\tconst { user } = await adapter.findOrCreateUser(email)\n\n\t\t\t\tif (!user) throw new Error(`unable-to-create-user-${email}`)\n\n\t\t\t\tconst currentUser = currentUserId\n\t\t\t\t\t? await adapter.getUserById(currentUserId)\n\t\t\t\t\t: null\n\n\t\t\t\tconst redeemingForCurrentUser = currentUser?.id === user.id\n\n\t\t\t\t// To prevent double-purchasing, check if this user already has a\n\t\t\t\t// Purchase record for this product that is valid and wasn't a bulk\n\t\t\t\t// coupon purchase.\n\t\t\t\tconst existingPurchases =\n\t\t\t\t\tawait adapter.getExistingNonBulkValidPurchasesOfProduct({\n\t\t\t\t\t\tuserId: user.id,\n\t\t\t\t\t\tproductId,\n\t\t\t\t\t})\n\n\t\t\t\tif (existingPurchases.length > 0) {\n\t\t\t\t\tconst errorMessage = `already-purchased-${email}`\n\t\t\t\t\tconsole.error(errorMessage)\n\t\t\t\t\treturn {\n\t\t\t\t\t\terror: {\n\t\t\t\t\t\t\tmessage: errorMessage,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tredeemingForCurrentUser,\n\t\t\t\t\t\tpurchase: null,\n\t\t\t\t\t}\n\t\t\t\t\tthrow new Error(errorMessage)\n\t\t\t\t}\n\n\t\t\t\tconst purchaseId = `purchase-${v4()}`\n\t\t\t\tconst userMemberships = await adapter.getMembershipsForUser(user.id)\n\t\t\t\tconst organizationId =\n\t\t\t\t\tcoupon.organizationId ||\n\t\t\t\t\tuserMemberships.find((m) => m.organization.name?.includes(user.email))\n\t\t\t\t\t\t?.organizationId // safer way to make sure we are using personal organization\n\n\t\t\t\tawait adapter.createPurchase({\n\t\t\t\t\tid: purchaseId,\n\t\t\t\t\tuserId: user.id,\n\t\t\t\t\tcouponId: bulkCouponRedemption ? null : coupon.id,\n\t\t\t\t\tredeemedBulkCouponId: bulkCouponRedemption ? coupon.id : null,\n\t\t\t\t\tproductId,\n\t\t\t\t\ttotalAmount: '0',\n\t\t\t\t\torganizationId,\n\t\t\t\t\tmetadata: {\n\t\t\t\t\t\tcouponUsedId: bulkCouponRedemption ? null : coupon.id,\n\t\t\t\t\t},\n\t\t\t\t})\n\n\t\t\t\tconst newPurchase = await adapter.getPurchase(purchaseId)\n\n\t\t\t\tawait adapter.incrementCouponUsedCount(coupon.id)\n\n\t\t\t\tawait adapter.createPurchaseTransfer({\n\t\t\t\t\tsourceUserId: user.id,\n\t\t\t\t\tpurchaseId: purchaseId,\n\t\t\t\t\texpiresAt: new Date(Date.now() + 1000 * 60 * 60 * 24 * 7),\n\t\t\t\t})\n\n\t\t\t\treturn { purchase: newPurchase, redeemingForCurrentUser }\n\t\t\t}\n\n\t\t\treturn null\n\t\t},\n\t\tcreatePurchaseTransfer: async (options) => {\n\t\t\tconst id = `put_${v4()}`\n\t\t\tawait client.insert(purchaseUserTransfer).values({\n\t\t\t\tid,\n\t\t\t\tpurchaseId: options.purchaseId,\n\t\t\t\tsourceUserId: options.sourceUserId,\n\t\t\t\texpiresAt: options.expiresAt,\n\t\t\t})\n\t\t},\n\t\tincrementCouponUsedCount: async (couponId) => {\n\t\t\tawait client\n\t\t\t\t.update(coupon)\n\t\t\t\t.set({ usedCount: sql`${coupon.usedCount} + 1` })\n\t\t\t\t.where(eq(coupon.id, couponId))\n\t\t},\n\t\tgetExistingNonBulkValidPurchasesOfProduct: async ({\n\t\t\tuserId,\n\t\t\tproductId,\n\t\t}) => {\n\t\t\tconst existingPurchases = await client.query.purchases.findMany({\n\t\t\t\twhere: and(\n\t\t\t\t\teq(purchaseTable.userId, userId),\n\t\t\t\t\tproductId ? eq(purchaseTable.productId, productId) : undefined,\n\t\t\t\t\teq(purchaseTable.status, 'Valid'),\n\t\t\t\t\tisNull(purchaseTable.bulkCouponId),\n\t\t\t\t),\n\t\t\t})\n\n\t\t\treturn z.array(purchaseSchema).parse(existingPurchases)\n\t\t},\n\t\tcreateMerchantCustomer: async (options) => {\n\t\t\tawait client.insert(merchantCustomer).values({\n\t\t\t\tid: `mc_${v4()}`,\n\t\t\t\tidentifier: options.identifier,\n\t\t\t\tmerchantAccountId: options.merchantAccountId,\n\t\t\t\tuserId: options.userId,\n\t\t\t\tstatus: 1,\n\t\t\t})\n\t\t\treturn merchantCustomerSchema.parse(\n\t\t\t\tawait client.query.merchantCustomer.findFirst({\n\t\t\t\t\twhere: eq(merchantCustomer.identifier, options.identifier),\n\t\t\t\t}),\n\t\t\t)\n\t\t},\n\t\tgetMerchantAccount: async (options) => {\n\t\t\treturn merchantAccountSchema.parse(\n\t\t\t\tawait client.query.merchantAccount.findFirst({\n\t\t\t\t\twhere: eq(merchantAccount.label, options.provider),\n\t\t\t\t}),\n\t\t\t)\n\t\t},\n\t\tgetMerchantPriceForProductId: async (productId) => {\n\t\t\tconst merchantPriceData = await client.query.merchantPrice.findFirst({\n\t\t\t\twhere: and(\n\t\t\t\t\teq(merchantPrice.merchantProductId, productId),\n\t\t\t\t\teq(merchantPrice.status, 1),\n\t\t\t\t),\n\t\t\t})\n\n\t\t\tconst parsedMerchantPrice =\n\t\t\t\tmerchantPriceSchema.safeParse(merchantPriceData)\n\n\t\t\tif (!parsedMerchantPrice.success) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t'Error parsing merchant price',\n\t\t\t\t\tJSON.stringify(parsedMerchantPrice.error),\n\t\t\t\t)\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\treturn parsedMerchantPrice.data\n\t\t},\n\t\tgetMerchantProductForProductId: async (productId) => {\n\t\t\tconst merchantProductData = await client.query.merchantProduct.findFirst({\n\t\t\t\twhere: eq(merchantProduct.productId, productId),\n\t\t\t})\n\n\t\t\tif (!merchantProductData) return null\n\t\t\treturn merchantProductSchema.parse(merchantProductData)\n\t\t},\n\t\tgetMerchantCustomerForUserId: async (userId) => {\n\t\t\tconst merchantCustomerData =\n\t\t\t\tawait client.query.merchantCustomer.findFirst({\n\t\t\t\t\twhere: eq(merchantCustomer.userId, userId),\n\t\t\t\t})\n\n\t\t\tif (!merchantCustomerData) return null\n\t\t\treturn merchantCustomerSchema.parse(merchantCustomerData)\n\t\t},\n\t\tgetUpgradableProducts: async (options) => {\n\t\t\tconst { upgradableFromId, upgradableToId } = options\n\t\t\treturn z.array(upgradableProductSchema).parse(\n\t\t\t\tawait client.query.upgradableProducts.findMany({\n\t\t\t\t\twhere: and(\n\t\t\t\t\t\teq(upgradableProducts.upgradableFromId, upgradableFromId),\n\t\t\t\t\t\teq(upgradableProducts.upgradableToId, upgradableToId),\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t)\n\t\t},\n\t\tasync availableUpgradesForProduct(\n\t\t\tpurchases: any,\n\t\t\tproductId: string,\n\t\t): Promise<any[]> {\n\t\t\tconst previousPurchaseProductIds = purchases.map(\n\t\t\t\t({ productId }: Purchase) => productId,\n\t\t\t)\n\n\t\t\tif (previousPurchaseProductIds.length > 0) {\n\t\t\t\treturn client.query.upgradableProducts.findMany({\n\t\t\t\t\twhere: and(\n\t\t\t\t\t\teq(upgradableProducts.upgradableToId, productId),\n\t\t\t\t\t\tinArray(\n\t\t\t\t\t\t\tupgradableProducts.upgradableFromId,\n\t\t\t\t\t\t\tpreviousPurchaseProductIds,\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t})\n\t\t\t}\n\t\t\treturn []\n\t\t},\n\t\tclearLessonProgressForUser(options: {\n\t\t\tuserId: string\n\t\t\tlessons: { id: string; slug: string }[]\n\t\t}): Promise<void> {\n\t\t\tthrow new Error('clearLessonProgressForUser Method not implemented.')\n\t\t},\n\t\tasync completeLessonProgressForUser(options: {\n\t\t\tuserId: string\n\t\t\tlessonId?: string\n\t\t}): Promise<ResourceProgress | null> {\n\t\t\tif (!options.lessonId) {\n\t\t\t\tthrow new Error('No lessonId provided')\n\t\t\t}\n\n\t\t\tlet lessonProgress = await client.query.resourceProgress.findFirst({\n\t\t\t\twhere: and(\n\t\t\t\t\teq(resourceProgress.userId, options.userId),\n\t\t\t\t\teq(resourceProgress.resourceId, options.lessonId),\n\t\t\t\t),\n\t\t\t})\n\n\t\t\tconst now = new Date()\n\n\t\t\tif (lessonProgress) {\n\t\t\t\tif (!lessonProgress.completedAt) {\n\t\t\t\t\tawait client\n\t\t\t\t\t\t.update(resourceProgress)\n\t\t\t\t\t\t.set({\n\t\t\t\t\t\t\tcompletedAt: now,\n\t\t\t\t\t\t\tupdatedAt: now,\n\t\t\t\t\t\t})\n\t\t\t\t\t\t.where(eq(resourceProgress.resourceId, options.lessonId))\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tawait client.insert(resourceProgress).values({\n\t\t\t\t\tuserId: options.userId,\n\t\t\t\t\tresourceId: options.lessonId,\n\t\t\t\t\tcompletedAt: now,\n\t\t\t\t\tupdatedAt: now,\n\t\t\t\t})\n\t\t\t}\n\t\t\tlessonProgress = await client.query.resourceProgress.findFirst({\n\t\t\t\twhere: and(\n\t\t\t\t\teq(resourceProgress.userId, options.userId),\n\t\t\t\t\teq(resourceProgress.resourceId, options.lessonId),\n\t\t\t\t),\n\t\t\t})\n\t\t\tconst parsedLessonProgress =\n\t\t\t\tresourceProgressSchema.safeParse(lessonProgress)\n\n\t\t\tif (!parsedLessonProgress.success) {\n\t\t\t\tconsole.error('Error parsing lesson progress', lessonProgress)\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\treturn parsedLessonProgress.data\n\t\t},\n\t\tasync couponForIdOrCode(options: {\n\t\t\tcode?: string\n\t\t\tcouponId?: string\n\t\t}): Promise<(Coupon & { merchantCoupon: MerchantCoupon }) | null> {\n\t\t\tif (!options.couponId && !options.code) return null\n\t\t\tconst couponForIdOrCode = await client.query.coupon.findFirst({\n\t\t\t\twhere: or(\n\t\t\t\t\tand(\n\t\t\t\t\t\tor(\n\t\t\t\t\t\t\toptions.code ? eq(coupon.code, options.code) : undefined,\n\t\t\t\t\t\t\toptions.couponId ? eq(coupon.id, options.couponId) : undefined,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tgte(coupon.expires, new Date()),\n\t\t\t\t\t),\n\t\t\t\t\tand(\n\t\t\t\t\t\tor(\n\t\t\t\t\t\t\toptions.code ? eq(coupon.code, options.code) : undefined,\n\t\t\t\t\t\t\toptions.couponId ? eq(coupon.id, options.couponId) : undefined,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tisNull(coupon.expires),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\twith: {\n\t\t\t\t\tmerchantCoupon: true,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tif (!couponForIdOrCode) return null\n\n\t\t\tconst parsedCoupon = couponSchema\n\t\t\t\t.extend({\n\t\t\t\t\tmerchantCoupon: merchantCouponSchema,\n\t\t\t\t})\n\t\t\t\t.safeParse(couponForIdOrCode)\n\n\t\t\tif (!parsedCoupon.success) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t'Error parsing coupon',\n\t\t\t\t\tJSON.stringify(parsedCoupon.error),\n\t\t\t\t)\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\treturn parsedCoupon.data\n\t\t},\n\t\tasync createMerchantSession(options): Promise<MerchantSession> {\n\t\t\tconst id = `ms_${v4()}`\n\t\t\tawait client.insert(merchantSession).values({\n\t\t\t\tid,\n\t\t\t\tidentifier: options.identifier,\n\t\t\t\tmerchantAccountId: options.merchantAccountId,\n\t\t\t\t...(options.organizationId\n\t\t\t\t\t? { organizationId: options.organizationId }\n\t\t\t\t\t: {}),\n\t\t\t})\n\n\t\t\treturn MerchantSessionSchema.parse(\n\t\t\t\tawait client.query.merchantSession.findFirst({\n\t\t\t\t\twhere: eq(merchantSession.id, id),\n\t\t\t\t}),\n\t\t\t)\n\t\t},\n\t\tasync createMerchantChargeAndPurchase(options): Promise<Purchase> {\n\t\t\tconst purchaseId = await client.transaction(async (trx) => {\n\t\t\t\ttry {\n\t\t\t\t\tconst {\n\t\t\t\t\t\tuserId,\n\t\t\t\t\t\tstripeChargeId,\n\t\t\t\t\t\tstripeCouponId,\n\t\t\t\t\t\tmerchantAccountId,\n\t\t\t\t\t\tmerchantProductId,\n\t\t\t\t\t\tmerchantCustomerId,\n\t\t\t\t\t\tproductId,\n\t\t\t\t\t\tstripeChargeAmount,\n\t\t\t\t\t\tquantity = 1,\n\t\t\t\t\t\tcheckoutSessionId,\n\t\t\t\t\t\tappliedPPPStripeCouponId,\n\t\t\t\t\t\tupgradedFromPurchaseId,\n\t\t\t\t\t\tcountry,\n\t\t\t\t\t\tusedCouponId,\n\t\t\t\t\t\torganizationId,\n\t\t\t\t\t} = options\n\n\t\t\t\t\tconst existingMerchantCharge = merchantChargeSchema.nullable().parse(\n\t\t\t\t\t\t(await client.query.merchantCharge.findFirst({\n\t\t\t\t\t\t\twhere: eq(merchantCharge.identifier, stripeChargeId),\n\t\t\t\t\t\t})) || null,\n\t\t\t\t\t)\n\n\t\t\t\t\tconst existingPurchaseForCharge = existingMerchantCharge\n\t\t\t\t\t\t? await client.query.purchases.findFirst({\n\t\t\t\t\t\t\t\twhere: eq(\n\t\t\t\t\t\t\t\t\tpurchaseTable.merchantChargeId,\n\t\t\t\t\t\t\t\t\texistingMerchantCharge.id,\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t\twith: {\n\t\t\t\t\t\t\t\t\tuser: true,\n\t\t\t\t\t\t\t\t\tproduct: true,\n\t\t\t\t\t\t\t\t\tbulkCoupon: true,\n\t\t\t\t\t\t\t\t},\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: null\n\n\t\t\t\t\tif (existingPurchaseForCharge) {\n\t\t\t\t\t\treturn existingPurchaseForCharge.id\n\t\t\t\t\t}\n\n\t\t\t\t\tconst merchantChargeId = `mc_${v4()}`\n\t\t\t\t\tconst purchaseId = `purch_${v4()}`\n\n\t\t\t\t\tconst newMerchantCharge = await client.insert(merchantCharge).values({\n\t\t\t\t\t\tid: merchantChargeId,\n\t\t\t\t\t\tuserId,\n\t\t\t\t\t\tidentifier: stripeChargeId,\n\t\t\t\t\t\tmerchantAccountId,\n\t\t\t\t\t\tmerchantProductId,\n\t\t\t\t\t\tmerchantCustomerId,\n\t\t\t\t\t})\n\n\t\t\t\t\tconst existingPurchase = purchaseSchema.nullable().parse(\n\t\t\t\t\t\t(await client.query.purchases.findFirst({\n\t\t\t\t\t\t\twhere: and(\n\t\t\t\t\t\t\t\teq(purchaseTable.productId, productId),\n\t\t\t\t\t\t\t\teq(purchaseTable.userId, userId),\n\t\t\t\t\t\t\t\tinArray(purchaseTable.status, ['Valid', 'Restricted']),\n\t\t\t\t\t\t\t),\n\t\t\t\t\t\t})) || null,\n\t\t\t\t\t)\n\n\t\t\t\t\tconst existingBulkCoupon = couponSchema.nullable().parse(\n\t\t\t\t\t\tawait client\n\t\t\t\t\t\t\t.select()\n\t\t\t\t\t\t\t.from(coupon)\n\t\t\t\t\t\t\t.leftJoin(\n\t\t\t\t\t\t\t\tpurchaseTable,\n\t\t\t\t\t\t\t\tand(\n\t\t\t\t\t\t\t\t\teq(coupon.id, purchaseTable.bulkCouponId),\n\t\t\t\t\t\t\t\t\teq(purchaseTable.userId, userId),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.where(\n\t\t\t\t\t\t\t\tand(\n\t\t\t\t\t\t\t\t\teq(coupon.restrictedToProductId, productId),\n\t\t\t\t\t\t\t\t\teq(purchaseTable.userId, userId),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t.then((res) => {\n\t\t\t\t\t\t\t\treturn res[0]?.Coupon ?? null\n\t\t\t\t\t\t\t}),\n\t\t\t\t\t)\n\n\t\t\t\t\tconst isBulkPurchase =\n\t\t\t\t\t\tquantity > 1 ||\n\t\t\t\t\t\tBoolean(existingBulkCoupon) ||\n\t\t\t\t\t\toptions.bulk ||\n\t\t\t\t\t\tBoolean(existingPurchase?.status === 'Valid')\n\n\t\t\t\t\tlet bulkCouponId: string | null = null\n\t\t\t\t\tlet couponToUpdate = null\n\n\t\t\t\t\tif (isBulkPurchase) {\n\t\t\t\t\t\tbulkCouponId =\n\t\t\t\t\t\t\texistingBulkCoupon !== null ? existingBulkCoupon.id : v4()\n\n\t\t\t\t\t\tif (existingBulkCoupon !== null) {\n\t\t\t\t\t\t\tcouponToUpdate = await client\n\t\t\t\t\t\t\t\t.update(coupon)\n\t\t\t\t\t\t\t\t.set({\n\t\t\t\t\t\t\t\t\tmaxUses: (existingBulkCoupon?.maxUses || 0) + quantity,\n\t\t\t\t\t\t\t\t\t...(organizationId ? { organizationId } : {}),\n\t\t\t\t\t\t\t\t})\n\t\t\t\t\t\t\t\t.where(eq(coupon.id, bulkCouponId))\n\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\tconst merchantCouponToUse = stripeCouponId\n\t\t\t\t\t\t\t\t? merchantCouponSchema.nullable().parse(\n\t\t\t\t\t\t\t\t\t\tawait client.query.merchantCoupon.findFirst({\n\t\t\t\t\t\t\t\t\t\t\twhere: eq(merchantCoupon.identifier, stripeCouponId),\n\t\t\t\t\t\t\t\t\t\t}),\n\t\t\t\t\t\t\t\t\t)\n\t\t\t\t\t\t\t\t: null\n\n\t\t\t\t\t\t\tcouponToUpdate = await client.insert(coupon).values({\n\t\t\t\t\t\t\t\tid: bulkCouponId as string,\n\t\t\t\t\t\t\t\tpercentageDiscount: '1.0',\n\t\t\t\t\t\t\t\trestrictedToProductId: productId,\n\t\t\t\t\t\t\t\tmaxUses: quantity,\n\t\t\t\t\t\t\t\tstatus: 1,\n\t\t\t\t\t\t\t\t...(organizationId ? { organizationId } : {}),\n\t\t\t\t\t\t\t\t...(merchantCouponToUse\n\t\t\t\t\t\t\t\t\t? {\n\t\t\t\t\t\t\t\t\t\t\tmerchantCouponId: merchantCouponToUse.id,\n\t\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t\t: {}),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\t// create a new merchant session\n\t\t\t\t\tconst merchantSessionId = `ms_${v4()}`\n\n\t\t\t\t\tawait client.insert(merchantSession).values({\n\t\t\t\t\t\tid: merchantSessionId,\n\t\t\t\t\t\tidentifier: checkoutSessionId,\n\t\t\t\t\t\tmerchantAccountId,\n\t\t\t\t\t})\n\n\t\t\t\t\tconst merchantCouponUsed = stripeCouponId\n\t\t\t\t\t\t? await client.query.merchantCoupon.findFirst({\n\t\t\t\t\t\t\t\twhere: eq(merchantCoupon.identifier, stripeCouponId),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: null\n\n\t\t\t\t\tconst pppMerchantCoupon = appliedPPPStripeCouponId\n\t\t\t\t\t\t? await client.query.merchantCoupon.findFirst({\n\t\t\t\t\t\t\t\twhere: and(\n\t\t\t\t\t\t\t\t\teq(merchantCoupon.identifier, appliedPPPStripeCouponId),\n\t\t\t\t\t\t\t\t\teq(merchantCoupon.type, 'ppp'),\n\t\t\t\t\t\t\t\t),\n\t\t\t\t\t\t\t})\n\t\t\t\t\t\t: null\n\n\t\t\t\t\tconst newPurchaseStatus =\n\t\t\t\t\t\tmerchantCouponUsed?.type === 'ppp' || pppMerchantCoupon\n\t\t\t\t\t\t\t? 'Restricted'\n\t\t\t\t\t\t\t: 'Valid'\n\n\t\t\t\t\tawait client.insert(purchaseTable).values({\n\t\t\t\t\t\tid: purchaseId,\n\t\t\t\t\t\tstatus: newPurchaseStatus,\n\t\t\t\t\t\tuserId,\n\t\t\t\t\t\tproductId,\n\t\t\t\t\t\tmerchantChargeId,\n\t\t\t\t\t\ttotalAmount: (stripeChargeAmount / 100).toFixed(),\n\t\t\t\t\t\tbulkCouponId,\n\t\t\t\t\t\tmerchantSessionId,\n\t\t\t\t\t\tcountry,\n\t\t\t\t\t\tupgradedFromId: upgradedFromPurchaseId || null,\n\t\t\t\t\t\tcouponId: usedCouponId || null,\n\t\t\t\t\t\t...(organizationId ? { organizationId } : {}),\n\t\t\t\t\t})\n\n\t\t\t\t\tconst oneWeekInMilliseconds = 1000 * 60 * 60 * 24 * 7\n\n\t\t\t\t\tawait client.insert(purchaseUserTransfer).values({\n\t\t\t\t\t\tid: `put_${v4()}`,\n\t\t\t\t\t\tpurchaseId: purchaseId as string,\n\t\t\t\t\t\texpiresAt: existingPurchase\n\t\t\t\t\t\t\t? new Date()\n\t\t\t\t\t\t\t: new Date(Date.now() + oneWeekInMilliseconds),\n\t\t\t\t\t\tsourceUserId: userId,\n\t\t\t\t\t\t...(organizationId ? { organizationId } : {}),\n\t\t\t\t\t})\n\n\t\t\t\t\t// const result = await Promise.all([\n\t\t\t\t\t// \tnewMerchantCharge,\n\t\t\t\t\t// \tnewPurchase,\n\t\t\t\t\t// \tnewPurchaseUserTransfer,\n\t\t\t\t\t// \tnewMerchantSession,\n\t\t\t\t\t// \t...(couponToUpdate ? [couponToUpdate] : []),\n\t\t\t\t\t// ])\n\t\t\t\t\t//\n\t\t\t\t\t// console.log('result', { result })\n\n\t\t\t\t\treturn purchaseId\n\t\t\t\t} catch (error) {\n\t\t\t\t\tconsole.error(error)\n\t\t\t\t\ttrx.rollback()\n\t\t\t\t\tthrow error\n\t\t\t\t}\n\t\t\t})\n\n\t\t\tconst parsedPurchase = purchaseSchema.safeParse(\n\t\t\t\tawait client.query.purchases.findFirst({\n\t\t\t\t\twhere: eq(purchaseTable.id, purchaseId as string),\n\t\t\t\t}),\n\t\t\t)\n\n\t\t\tif (!parsedPurchase.success) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t'Error parsing purchase',\n\t\t\t\t\tparsedPurchase,\n\t\t\t\t\tJSON.stringify(parsedPurchase, null, 2),\n\t\t\t\t)\n\t\t\t\tthrow new Error('Error parsing purchase')\n\t\t\t}\n\n\t\t\treturn parsedPurchase.data\n\t\t},\n\t\tasync findOrCreateMerchantCustomer(options: {\n\t\t\tuser: User\n\t\t\tidentifier: string\n\t\t\tmerchantAccountId: string\n\t\t}): Promise<MerchantCustomer | null> {\n\t\t\tconst merchantCustomer = merchantCustomerSchema\n\t\t\t\t.nullable()\n\t\t\t\t.optional()\n\t\t\t\t.parse(\n\t\t\t\t\tawait client.query.merchantCustomer.findFirst({\n\t\t\t\t\t\twhere: (merchantCustomer, { eq }) =>\n\t\t\t\t\t\t\teq(merchantCustomer.identifier, options.identifier),\n\t\t\t\t\t}),\n\t\t\t\t)\n\n\t\t\tif (merchantCustomer) {\n\t\t\t\treturn merchantCustomer\n\t\t\t}\n\n\t\t\treturn await adapter.createMerchantCustomer({\n\t\t\t\tidentifier: options.identifier,\n\t\t\t\tmerchantAccountId: options.merchantAccountId,\n\t\t\t\tuserId: options.user.id,\n\t\t\t})\n\t\t},\n\t\tasync findOrCreateUser(\n\t\t\temail: string,\n\t\t\tname?: string | null,\n\t\t): Promise<{\n\t\t\tuser: User\n\t\t\tisNewUser: boolean\n\t\t}> {\n\t\t\tconst user = await adapter.getUserByEmail?.(email)\n\n\t\t\tif (!user) {\n\t\t\t\tconst newUser = await adapter.createUser?.({\n\t\t\t\t\tid: `u_${v4()}`,\n\t\t\t\t\temail,\n\t\t\t\t\tname,\n\t\t\t\t\temailVerified: null,\n\t\t\t\t})\n\t\t\t\tconsole.log('newUser', { newUser })\n\t\t\t\tif (!newUser) {\n\t\t\t\t\tthrow new Error('Could not create user')\n\t\t\t\t}\n\t\t\t\treturn {\n\t\t\t\t\tuser: newUser as User,\n\t\t\t\t\tisNewUser: true,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\treturn {\n\t\t\t\tuser: user as User,\n\t\t\t\tisNewUser: false,\n\t\t\t}\n\t\t},\n\t\tasync getCoupon(couponIdOrCode: string): Promise<Coupon | null> {\n\t\t\tconst loadedCoupon =\n\t\t\t\t(await client.query.coupon.findFirst({\n\t\t\t\t\twhere: or(\n\t\t\t\t\t\teq(coupon.id, couponIdOrCode),\n\t\t\t\t\t\teq(coupon.code, couponIdOrCode),\n\t\t\t\t\t),\n\t\t\t\t})) || null\n\n\t\t\tlogger.debug('loadedCoupon', { loadedCoupon })\n\n\t\t\treturn couponSchema.nullable().parse(loadedCoupon)\n\t\t},\n\t\tasync getPurchasesForBulkCouponId(\n\t\t\tbulkCouponId: string,\n\t\t): Promise<(Purchase & { user: User })[]> {\n\t\t\treturn z.array(purchaseSchema.extend({ user: userSchema })).parse(\n\t\t\t\tawait client.query.purchases.findMany({\n\t\t\t\t\twhere: eq(purchaseTable.bulkCouponId, bulkCouponId),\n\t\t\t\t\twith: {\n\t\t\t\t\t\tuser: true,\n\t\t\t\t\t},\n\t\t\t\t}),\n\t\t\t)\n\t\t},\n\t\tasync getCouponWithBulkPurchases(couponId: string): Promise<\n\t\t\t| (Coupon & {\n\t\t\t\t\tbulkPurchases?: Purchase[] | null\n\t\t\t\t\tredeemedBulkCouponPurchases: { bulkCouponId?: string | null }[]\n\t\t\t })\n\t\t\t| null\n\t\t> {\n\t\t\tlogger.debug('getCouponWithBulkPurchases', { couponId })\n\t\t\tlet couponData\n\t\t\tlet bulkCouponPurchases\n\t\t\ttry {\n\t\t\t\tcouponData =\n\t\t\t\t\t(await client.query.coupon.findFirst({\n\t\t\t\t\t\twhere: eq(coupon.id, couponId),\n\t\t\t\t\t\twith: {\n\t\t\t\t\t\t\tbulkPurchases: true,\n\t\t\t\t\t\t\tredeemedBulkCouponPurchases: true,\n\t\t\t\t\t\t},\n\t\t\t\t\t})) || null\n\t\t\t} catch (e) {\n\t\t\t\tconsole.log('getCouponWithBulkPurchases')\n\t\t\t\tlogger.error(e as Error)\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\tbulkCouponPurchases = await client.query.purchases.findMany({\n\t\t\t\t\twhere: eq(purchaseTable.redeemedBulkCouponId, couponId),\n\t\t\t\t\twith: {\n\t\t\t\t\t\tuser: true,\n\t\t\t\t\t},\n\t\t\t\t})\n\t\t\t\tconsole.log('purchases with redeemedBulkCouponId', bulkCouponPurchases)\n\t\t\t} catch (e) {\n\t\t\t\tconsole.log('getCouponWithBulkPurchases')\n\t\t\t\tlogger.error(e as Error)\n\t\t\t}\n\n\t\t\tif (!couponData) {\n\t\t\t\tlogger.debug('getCouponWithBulkPurchases', {\n\t\t\t\t\tcouponId,\n\t\t\t\t\terror: 'no coupon found',\n\t\t\t\t})\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst couponWithBulkPurchases = {\n\t\t\t\t...couponData,\n\t\t\t\tredeemedBulkCouponPurchases: bulkCouponPurchases || [],\n\t\t\t}\n\n\t\t\tconst parsedCoupon = couponSchema\n\t\t\t\t.merge(\n\t\t\t\t\tz.object({\n\t\t\t\t\t\tredeemedBulkCouponPurchases: z.array(purchaseSchema),\n\t\t\t\t\t}),\n\t\t\t\t)\n\t\t\t\t.nullable()\n\t\t\t\t.safeParse(couponWithBulkPurchases)\n\n\t\t\tif (!parsedCoupon.success) {\n\t\t\t\tconsole.error(\n\t\t\t\t\t'Error parsing coupon',\n\t\t\t\t\tJSON.stringify(parsedCoupon.error),\n\t\t\t\t\tcouponData,\n\t\t\t\t)\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\treturn parsedCoupon.data\n\t\t},\n\t\tasync getDefaultCoupon(productIds?: string[]): Promise<{\n\t\t\tdefaultMerchantCoupon: MerchantCoupon\n\t\t\tdefaultCoupon: Coupon\n\t\t} | null> {\n\t\t\tconst activeSaleCoupon = await client.query.coupon.findFirst({\n\t\t\t\twhere: and(\n\t\t\t\t\teq(coupon.status, 1),\n\t\t\t\t\teq(coupon.default, true),\n\t\t\t\t\tgte(coupon.expires, new Date()),\n\t\t\t\t\tor(\n\t\t\t\t\t\tproductIds\n\t\t\t\t\t\t\t? inArray(coupon.restrictedToProductId, productIds)\n\t\t\t\t\t\t\t: undefined,\n\t\t\t\t\t\tisNull(coupon.restrictedToProductId),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t\torderBy: desc(coupon.percentageDiscount),\n\t\t\t\twith: {\n\t\t\t\t\tmerchantCoupon: true,\n\t\t\t\t\tproduct: true,\n\t\t\t\t},\n\t\t\t})\n\t\t\tif (activeSaleCoupon) {\n\t\t\t\tconst { restrictedToProductId } = activeSaleCoupon\n\t\t\t\tconst validForProdcutId = restrictedToProductId\n\t\t\t\t\t? productIds?.includes(restrictedToProductId as string)\n\t\t\t\t\t: true\n\n\t\t\t\tconst { merchantCoupon: defaultMerchantCoupon, ...defaultCoupon } =\n\t\t\t\t\tactiveSaleCoupon\n\n\t\t\t\tif (validForProdcutId) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdefaultMerchantCoupon: merchantCouponSchema.parse(\n\t\t\t\t\t\t\tdefaultMerchantCoupon,\n\t\t\t\t\t\t),\n\t\t\t\t\t\tdefaultCoupon: couponSchema.parse(defaultCoupon),\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t\treturn null\n\t\t},\n\t\tgetLessonProgressCountsByDate(): Promise<\n\t\t\t{\n\t\t\t\tcount: number\n\t\t\t\tcompletedAt: string\n\t\t\t}[]\n\t\t> {\n\t\t\tthrow new Error('getLessonProgressCountsByDate Method not implemented.')\n\t\t},\n\t\tasync getLessonProgressForUser(\n\t\t\tuserId: string,\n\t\t): Promise<ResourceProgress[]> {\n\t\t\tconst userProgress = await client.query.resourceProgress.findMany({\n\t\t\t\twhere: eq(resourceProgress.userId, userId),\n\t\t\t})\n\t\t\tconst parsed = z.array(resourceProgressSchema).safeParse(userProgress)\n\t\t\tif (!parsed.success) {\n\t\t\t\tconsole.error('Error parsing user progress', userProgress)\n\t\t\t\treturn []\n\t\t\t}\n\t\t\treturn parsed.data\n\t\t},\n\t\tasync getModuleProgressForUser(\n\t\t\tuserIdOrEmail: string,\n\t\t\tmoduleIdOrSlug: string,\n\t\t): Promise<ModuleProgress | null> {\n\t\t\t// First, get the user ID\n\t\t\tconst user = await client.query.users.findFirst({\n\t\t\t\twhere: or(eq(users.id, userIdOrEmail), eq(users.email, userIdOrEmail)),\n\t\t\t\tcolumns: {\n\t\t\t\t\tid: true,\n\t\t\t\t},\n\t\t\t})\n\n\t\t\tif (!user) {\n\t\t\t\treturn null\n\t\t\t}\n\n\t\t\tconst ResultRowSchema = z.object({\n\t\t\t\tresource_id: z.string(),\n\t\t\t\tresource_type: z.enum(['lesson', 'exercise', 'post']),\n\t\t\t\tresource_slug: z.string().nullable(),\n\t\t\t\tcompleted_at: z\n\t\t\t\t\t.string()\n\t\t\t\t\t.nullable()\n\t\t\t\t\t.transform((val) => (val ? new Date(val) : null)),\n\t\t\t})\n\n\t\t\t// Execute the optimized query\n\t\t\tconst results: any = await client.execute(sql`\n SELECT\n\t\t\t\t\t\tcr.id AS resource_id,\n\t\t\t\t\t\tcr.type AS resource_type,\n\t\t\t\t\t\tcr.fields->>'$.slug' AS resource_slug,\n\t\t\t\t\t\trp.completedAt AS completed_at\n\t\t\t\tFROM\n\t\t\t\t\t\t(SELECT id, fields->>'$.slug' AS slug\n\t\t\t\t\t\t FROM ${contentResource}\n\t\t\t\t\t\t WHERE id = ${moduleIdOrSlug}\n\t\t\t\t\t\t\t\tOR fields->>'$.slug' = ${moduleIdOrSlug}) AS w\n\t\t\t\tLEFT JOIN\n\t\t\t\t\t\t(SELECT\n\t\t\t\t\t\t\t\tcrr.resourceOfId AS workshop_id,\n\t\t\t\t\t\t\t\tcrr.resourceId,\n\t\t\t\t\t\t\t\tcrr.position\n\t\t\t\t\t\t FROM ${contentResourceResource} crr) AS tlr ON w.id = tlr.workshop_id\n\t\t\t\tLEFT JOIN ${contentResource} sections ON sections.id = tlr.resourceId AND sections.type = 'section'\n\t\t\t\tLEFT JOIN\n\t\t\t\t\t\t(SELECT\n\t\t\t\t\t\t\t\tcrr.resourceOfId AS section_id,\n\t\t\t\t\t\t\t\tcrr.resourceId AS resource_id,\n\t\t\t\t\t\t\t\tcrr.position AS position_in_section,\n\t\t\t\t\t\t\t\tsection_crr.position AS section_position\n\t\t\t\t\t\t FROM ${contentResourceResource} crr\n\t\t\t\t\t\t JOIN ${contentResourceResource} section_crr ON crr.resourceOfId = section_crr.resourceId) AS sr\n\t\t\t\tON sr.section_id = sections.id\n\t\t\t\tJOIN ${contentResource} cr ON cr.id = COALESCE(sr.resource_id, tlr.resourceId)\n\t\t\t\tLEFT JOIN ${resourceProgress} rp ON rp.resourceId = cr.id\n\t\t\t\t\t\tAND rp.userId = ${user.id}\n\t\t\t\tWHERE\n\t\t\t\t\t\tcr.type IN ('lesson', 'exercise', 'post')\n\n `)\n\t\t\t// Process the results\n\t\t\tconst completedLessons: ResourceProgress[] = []\n\t\t\tlet nextResource: Partial<ContentResource> | null = null\n\t\t\tlet completedLessonsCount = 0\n\t\t\tlet totalLessonsCount = results.rows.length\n\n\t\t\tconst parsedRows = z.array(ResultRowSchema).safeParse(results.rows)\n\n\t\t\tif (!parsedRows.success) {\n\t\t\t\tconsole.error('Error parsing rows', parsedRows.error)\n\t\t\t\treturn {\n\t\t\t\t\tcompletedLessons: [],\n\t\t\t\t\tnextResource: null,\n\t\t\t\t\tpercentCompleted: 0,\n\t\t\t\t\tcompletedLessonsCount: 0,\n\t\t\t\t\ttotalLessonsCount,\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tfor (const row of parsedRows.data) {\n\t\t\t\tif (row.completed_at) {\n\t\t\t\t\tcompletedLessonsCount++\n\t\t\t\t\tcompletedLessons.push({\n\t\t\t\t\t\tuserId: user.id as string,\n\t\t\t\t\t\tresourceId: row.resource_id,\n\t\t\t\t\t\tcompletedAt: new Date(row.completed_at),\n\t\t\t\t\t\t// Add other fields as needed\n\t\t\t\t\t})\n\t\t\t\t} else if (!nextResource) {\n\t\t\t\t\tnextResource = {\n\t\t\t\t\t\tid: row.resource_id,\n\t\t\t\t\t\ttype: row.resource_type,\n\t\t\t\t\t\tfields: {\n\t\t\t\t\t\t\tslug: row.resource_slug,\n\t\t\t\t\t\t},\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst percentCompleted =\n\t\t\t\ttotalLessonsCount > 0\n\t\t\t\t\t? Math.ceil((completedLessonsCount / totalLessonsCount) * 100)\n\t\t\t\t\t: 0\n\n\t\t\treturn {\n\t\t\t\tcompletedLessons,\n\t\t\t\tnextResource,\n\t\t\t\tpercentCompleted,\n\t\t\t\tcompletedLessonsCount,\n\t\t\t\ttotalLessonsCount,\n\t\t\t}\n\t\t},\n\t\tgetLessonProgresses(): Promise<ResourceProgress[]> {\n\t\t\tthrow new Error('getLessonProgresses Method not implemented.')\n\t\t},\n\t\tasync getMerchantCharge(\n\t\t\tmerchantChargeId: string,\n\t\t): Promise<MerchantCharge | null> {\n\t\t\tconst mCharge = await client.query.merchantCharge.findFirst({\n\t\t\t\twhere: eq(merchantCharge.id, merchantChargeId),\n\t\t\t})\n\t\t\tconst parsed = merchantChargeSchema.safeParse(mCharge)\n\t\t\tif (!parsed.success) {\n\t\t\t\tconsole.info('Error parsing merchantCharge', mCharge)\n\t\t\t\treturn null\n\t\t\t}\n\t\t\treturn parsed.data\n\t\t},\n\t\tasync getMerchantCouponsForTypeAndPercent(params: {\n\t\t\ttype: string\n\t\t\tpercentageDiscount: number\n\t\t}): Promise<MerchantCoupon[]> {\n\t\t\treturn z.array(merchantCouponSchema).parse(\n\t\t\t\tawait client.query.merchantCoupon.findMany({\n\t\t\t\t\twhere: and(\n\t\t\t\t\t\teq(merchantCoupon.type, params.type),\n\t\t\t\t\t\teq(\n\t\t\t\t\t\t\tmerchantCoupon.percentageDiscount,\n\t\t\t\t\t\t\tparams.percentageDiscount.toString(),\n\t\t\t\t\t\t),\n\t\t\t\t\t),\n\t\t\t\t}),\n\t\t\t)\n\t\t},\n\t\tasync getMerchantCouponForTypeAndPercent(params: {\n\t\t\ttype: string\n\t\t\tpercentageDiscount: number\n\t\t}): Promise<MerchantCoupon | null> {\n\t\t\tconst foundMerchantCoupon = await client.query.merchantCoupon.findFirst({\n\t\t\t\twhere: and(\n\t\t\t\t\teq(merchantCoupon.type, params.type),\n\t\t\t\t\teq(\n\t\t\t\t\t\tmerchantCoupon.percentageDiscount,\n\t\t\t\t\t\tparams.percentageDiscount.toString(),\n\t\t\t\t\t),\n\t\t\t\t),\n\t\t\t})\n\n\t\t\tconst parsed = merchantCouponSchema\n\t\t\t\t.nullable()\n\t\t\t\t.safeParse(foundMerchantCoupon)\n\t\t\tif (parsed.success) {\n\t\t\t\treturn parsed.data\n\t\t\t}\n\n\t\t\treturn null\n\t\t},\n\t\tasync getMerchantCoupon(\n\t\t\tmerchantCouponId: string,\n\t\t): Promise<MerchantCoupon | null> {\n\t\t\tconst foundMerchantCoupon = await client.query.merchantCoupon.findFirst({\n\t\t\t\twhere: eq(merchantCoupon.id, merchantCouponId),\n\t\t\t})\n\n\t\t\tconst parsed = merchantCouponSchema\n\t\t\t\t.nullable()\n\t\t\t\t.safeParse(foundMerchantCoupon)\n\t\t\tif (parsed.success) {\n\t\t\t\treturn parsed.data\n\t\t\t}\n\n\t\t\treturn null\n\t\t},\n\t\tasync getMerchantProd