Skip to content

feat(aih): invoke post cohort purchase workflow when redeeming a full price coupon #471

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

vojtaholik
Copy link
Member

@vojtaholik vojtaholik commented May 14, 2025

gif

Summary by CodeRabbit

  • New Features
    • Introduced a new event for full-price coupon redemptions, now including organization information for better tracking.
    • Enhanced purchase workflows to respond to both new purchases and full-price coupon redemptions for cohort products.
  • Bug Fixes
    • Improved event data payloads to ensure organizational context is included when redeeming full-price coupons.
  • Chores
    • Updated internal event handling and logging for better traceability during coupon redemption and purchase creation.

@vojtaholik vojtaholik requested a review from joelhooks May 14, 2025 08:53
Copy link

vercel bot commented May 14, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
ai-hero ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 14, 2025 2:37pm
astro-party ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 14, 2025 2:37pm
course-builder-egghead ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 14, 2025 2:37pm
course-builder-poc ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 14, 2025 2:37pm
cursor-pro ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 14, 2025 2:37pm
epic-react-builder ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 14, 2025 2:37pm
epic-web-builder ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 14, 2025 2:37pm
epicai-pro 🛑 Canceled (Inspect) May 14, 2025 2:37pm
go-local-first ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 14, 2025 2:37pm

Copy link
Contributor

coderabbitai bot commented May 14, 2025

Walkthrough

This update introduces a new event, FullPriceCouponRedeemed, across several packages and integrates it into the purchase and workflow logic. The event now includes an orgId property, and related workflows and adapters are updated to emit, process, and listen for this event, particularly for cohort-type products.

Changes

File(s) Change Summary
packages/core/src/inngest/commerce/event-full-price-coupon-redeemed.ts Added event constant, type, and Zod schema for FullPriceCouponRedeemed, including purchaseId and productType.
packages/core/src/inngest/index.ts Registered FULL_PRICE_COUPON_REDEEMED_EVENT in the core events mapping.
apps/ai-hero/src/inngest/functions/post-cohort-purchase-workflow.ts Updated workflow to listen for both NEW_PURCHASE_CREATED_EVENT and FULL_PRICE_COUPON_REDEEMED_EVENT for cohort products; removed unused import; added new event import.
packages/core/src/adapters.ts Extended CourseBuilderAdapter.createPurchase method signature to optionally accept organizationId.
packages/adapter-drizzle/src/lib/mysql/index.ts Modified redeemFullPriceCoupon to fetch and include organizationId when creating a purchase.
packages/core/src/lib/actions/redeem.ts After coupon redemption, emits the new event to Inngest with purchase and product details; logs outcomes and errors; existing email notification unchanged.
.changeset/sixty-eggs-doubt.md Changeset documenting the addition of the new event and its organizational context.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Adapter
    participant Core
    participant Inngest
    participant Workflow

    User->>Adapter: Redeem full-price coupon
    Adapter->>Core: createPurchase({ ..., organizationId })
    Core->>Inngest: Emit FullPriceCouponRedeemed (purchaseId, productType, orgId)
    Inngest->>Workflow: Trigger postCohortPurchaseWorkflow (on event)
    Workflow->>Core: Fetch product details and process cohort logic
Loading

Suggested reviewers

  • joelhooks

Poem

In the warren where code bunnies dwell,
A coupon hops in, with a story to tell.
Now with orgs in the mix,
Events leap and click—
Cohorts and carrots, all handled so well!
🥕✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

Scope: all 3 workspace projects
 WARN  Ignoring not compatible lockfile at /tmp/eslint/pnpm-lock.yaml
Progress: resolved 1, reused 0, downloaded 0, added 0
 WARN  deprecated [email protected]: This version is no longer supported. Please see https://eslint.org/version-support for other options.
/tmp/eslint/packages/config/eslint-config:
 ERR_PNPM_WORKSPACE_PKG_NOT_FOUND  In packages/config/eslint-config: "@coursebuilder/eslint-plugin@workspace:*" is in the dependencies but no package named "@coursebuilder/eslint-plugin" is present in the workspace

This error happened while installing a direct dependency of /tmp/eslint/packages/config/eslint-config

Packages found in the workspace:

Tip

⚡️ Faster reviews with caching
  • CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.

Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e17759b and c31b8c4.

📒 Files selected for processing (1)
  • packages/adapter-drizzle/src/lib/mysql/index.ts (2 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/adapter-drizzle/src/lib/mysql/index.ts
✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Comment on lines 435 to 436
const userMemberships = await adapter.getMembershipsForUser(user.id)
const organizationId = userMemberships[0].organizationId
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@joelhooks I'm not 100% sure if this is the right way to get user's org id. it needs to be passed down to createPurchase

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for a redemption we need to look at the coupon, which should have an org id and join the user to that org

  • team purchase
  • create bulk coupon w/ org id (this happens below in createMerchantChargeAndPurchase)
  • redeem coupon
  • use coupon org id for redemption purchase
  • join user to org as learner

you can fallback to the personal org of the user as a last resort, but we probably want to ensure all coupons get created with an org id?

I made the personal org id lookup a little "safer" versus index[0] access

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🔭 Outside diff range comments (2)
packages/core/src/inngest/index.ts (1)

89-99: 💡 Verification agent

🧩 Analysis chain

Missing event handler in core functions array.

While the new event type is added to the CourseBuilderCoreEvents type, there appears to be no corresponding handler added to the courseBuilderCoreFunctions array. If the event requires a specific handler, it should be added here.

Run the following script to check if the event has a handler defined elsewhere:


🏁 Script executed:

#!/bin/bash
# Check for handlers for the FullPriceCouponRedeemed event
echo "Searching for event definitions:"
rg -A 3 "FULL_PRICE_COUPON_REDEEMED_EVENT" --type ts

echo "Searching for event handlers:"
rg -A 5 "fullPriceCouponRedeemed|FullPriceCouponRedeemed.*=>|'commerce/full-price-coupon-redeemed'" --type ts

Length of output: 3828


Add handler for FullPriceCouponRedeemed event

I searched the core package and confirmed that while FULL_PRICE_COUPON_REDEEMED_EVENT and its type are defined, there’s no corresponding handler function exported or included in the courseBuilderCoreFunctions array. If this event needs to be handled at the core level, please:

• In packages/core/src/inngest/commerce/event-full-price-coupon-redeemed.ts, implement and export a handler function (e.g. fullPriceCouponRedeemed)
• In packages/core/src/inngest/index.ts, import that handler:

import { fullPriceCouponRedeemed } from './commerce/event-full-price-coupon-redeemed'

• Add it to the courseBuilderCoreFunctions array alongside the other handlers:

export const courseBuilderCoreFunctions = [
  ...coreVideoProcessingFunctions,
  sendPostPurchaseEmail,
  /* … */
  updatePurchaseStatus,
+ fullPriceCouponRedeemed,
]

This ensures the new event type has its handler registered in the core functions list.

packages/adapter-drizzle/src/lib/mysql/index.ts (1)

374-465: 🛠️ Refactor suggestion

Consider adding error handling for missing organization memberships.

The implementation assumes that every user has at least one organization membership, but this might not always be the case. If a user somehow doesn't have any memberships, the code could throw an error when trying to access userMemberships[0].

Consider adding a fallback mechanism or more robust error handling:

async redeemFullPriceCoupon(options) {
  const {
    email: baseEmail,
    couponId,
    productIds,
    currentUserId,
    redeemingProductId,
  } = options
  const email = String(baseEmail).replace(' ', '+')

  // Rest of the existing code...

  const purchaseId = `purchase-${v4()}`
  const userMemberships = await adapter.getMembershipsForUser(user.id)
- const organizationId = userMemberships[0].organizationId
+ let organizationId: string | undefined
+ 
+ if (userMemberships.length > 0) {
+   organizationId = userMemberships[0].organizationId
+ } else {
+   console.warn(`User ${user.id} has no organization memberships. Creating purchase without organization.`)
+ }

  await adapter.createPurchase({
    id: purchaseId,
    userId: user.id,
    couponId: bulkCouponRedemption ? null : coupon.id,
    redeemedBulkCouponId: bulkCouponRedemption ? coupon.id : null,
    productId,
    totalAmount: '0',
    organizationId,
    metadata: {
      couponUsedId: bulkCouponRedemption ? null : coupon.id,
    },
  })

  // Rest of the existing code...
}
🧰 Tools
🪛 Biome (1.9.4)

[error] 431-431: This code is unreachable

... because this statement will return from the function beforehand

(lint/correctness/noUnreachable)

🧹 Nitpick comments (3)
.changeset/sixty-eggs-doubt.md (1)

1-7: Changeset description could be more descriptive.

While the changeset correctly identifies the affected packages, the description "create FullPriceCouponRedeemed event,pass orgId" is quite minimal. Consider providing more context about the purpose of this event and how it integrates with the existing purchase workflow.

-create FullPriceCouponRedeemed event,pass orgId
+create FullPriceCouponRedeemed event that includes organization ID to support post-cohort purchase workflow when redeeming full price coupons
packages/core/src/lib/actions/redeem.ts (1)

44-44: Consider removing this debug log statement before shipping to production.

Debug logs like this should typically be removed or replaced with proper structured logging for production code.

-console.log('createdPurchase', createdPurchase)
+// Consider using structured logging if needed for production
apps/ai-hero/src/inngest/functions/post-cohort-purchase-workflow.ts (1)

43-43: Consider removing this debug log or enhancing it for production.

Similar to the previous file, consider removing debug logs or enhancing them with structured logging for production.

-console.log(`product`, product)
+// Replace with structured logging if needed for production
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ab549a7 and be79214.

📒 Files selected for processing (7)
  • .changeset/sixty-eggs-doubt.md (1 hunks)
  • apps/ai-hero/src/inngest/functions/post-cohort-purchase-workflow.ts (2 hunks)
  • packages/adapter-drizzle/src/lib/mysql/index.ts (2 hunks)
  • packages/core/src/adapters.ts (1 hunks)
  • packages/core/src/inngest/commerce/event-full-price-coupon-redeemed.ts (1 hunks)
  • packages/core/src/inngest/index.ts (2 hunks)
  • packages/core/src/lib/actions/redeem.ts (3 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
packages/core/src/inngest/index.ts (1)
packages/core/src/inngest/commerce/event-full-price-coupon-redeemed.ts (2)
  • FULL_PRICE_COUPON_REDEEMED_EVENT (3-4)
  • FullPriceCouponRedeemed (6-9)
packages/core/src/lib/actions/redeem.ts (1)
packages/core/src/inngest/commerce/event-full-price-coupon-redeemed.ts (1)
  • FULL_PRICE_COUPON_REDEEMED_EVENT (3-4)
apps/ai-hero/src/inngest/functions/post-cohort-purchase-workflow.ts (2)
packages/core/src/inngest/commerce/event-new-purchase-created.ts (1)
  • NEW_PURCHASE_CREATED_EVENT (3-3)
packages/core/src/inngest/commerce/event-full-price-coupon-redeemed.ts (1)
  • FULL_PRICE_COUPON_REDEEMED_EVENT (3-4)
⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: typecheck
  • GitHub Check: format
  • GitHub Check: e2e-node (course-builder-web)
  • GitHub Check: lint
  • GitHub Check: build
🔇 Additional comments (9)
packages/core/src/adapters.ts (1)

44-56: New organizationId parameter correctly extends the interface.

The addition of the optional organizationId parameter to the createPurchase options interface allows associating purchases with specific organizations. This change properly supports the introduction of the FullPriceCouponRedeemed event that includes organizational context.

packages/core/src/inngest/index.ts (2)

6-9: Clean import of new event types.

The imports for the new event type and interface are properly structured and follow the established pattern for event definitions in the system.


85-86: Event type properly added to the core events map.

The FullPriceCouponRedeemed event is correctly added to the CourseBuilderCoreEvents type, allowing it to be properly typed throughout the system when event handling is performed.

packages/adapter-drizzle/src/lib/mysql/index.ts (1)

438-446: Organization ID correctly passed to createPurchase.

The organization ID is properly passed to the createPurchase method, utilizing the new parameter added to the adapter interface. This ensures purchases created during coupon redemption are associated with the correct organization.

packages/core/src/lib/actions/redeem.ts (2)

2-2: Good import statement for the new event.

The import is correctly added to support the new feature for invoking the post cohort purchase workflow on full price coupon redemption.


56-71: Good implementation of event dispatching for full price coupon redemption.

The code correctly:

  1. Fetches the product associated with the purchase
  2. Checks if the product exists before dispatching the event
  3. Uses the product type or falls back to a default type
  4. Includes error handling with appropriate logging and error message

The implementation aligns well with the feature goal of invoking the post cohort purchase workflow when redeeming a full price coupon.

apps/ai-hero/src/inngest/functions/post-cohort-purchase-workflow.ts (2)

12-12: Good import for the new event constant.

Correctly imports the new event constant needed for the workflow function.


20-29: Well-structured event trigger configuration.

The workflow function now properly listens for both purchase creation and coupon redemption events, with appropriate filtering for cohort-type products. The array structure allows for clean extension if more event types need to be added in the future.

packages/core/src/inngest/commerce/event-full-price-coupon-redeemed.ts (1)

1-19: Well-structured event definition with proper type safety.

The new event is:

  1. Clearly named and consistently follows existing conventions
  2. Properly typed with TypeScript for compile-time validation
  3. Schema-validated with Zod for runtime validation
  4. Includes appropriate enum values for product types

The event definition is concise, type-safe, and follows best practices for event-driven architectures.

@joelhooks joelhooks merged commit 1edb931 into main May 14, 2025
16 checks passed
@joelhooks joelhooks deleted the vh/feat/api/invoke-post-cohort-purchase-workflow-when-redeeming-full-price-coupon branch May 14, 2025 21:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants