Skip to content

feat(aih): cohort access ability for workshops #470

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

Merged
merged 9 commits into from
May 12, 2025

Conversation

vojtaholik
Copy link
Member

@vojtaholik vojtaholik commented May 11, 2025

image

gif

Summary by CodeRabbit

  • New Features

    • Added support for more granular content access permissions based on user entitlements, enabling read access to specific content and lessons.
    • Introduced a new permission indicator for content creation, allowing interfaces to display create options based on user rights.
  • Improvements

    • Centralized and streamlined entitlement handling for content access.
    • Enhanced ability checks by caching results for improved performance.
    • Updated internal queries to dynamically fetch entitlement type information.
    • Middleware now processes API routes, expanding request handling coverage.
  • Bug Fixes

    • Removed unnecessary suspense loading states for a smoother user experience.
  • Chores

    • Cleaned up unused code, imports, and debug statements for better maintainability.
    • Minor style and formatting adjustments.
    • Added logging for request headers to assist debugging.

@vojtaholik vojtaholik requested a review from joelhooks May 11, 2025 06:39
Copy link

vercel bot commented May 11, 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 12, 2025 8:39am
course-builder-egghead ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 12, 2025 8:39am
course-builder-poc ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 12, 2025 8:39am
cursor-pro ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 12, 2025 8:39am
epic-react-builder ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 12, 2025 8:39am
epic-web-builder ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 12, 2025 8:39am
epicai-pro ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 12, 2025 8:39am
go-local-first ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 12, 2025 8:39am
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
astro-party ⬜️ Ignored (Inspect) Visit Preview May 12, 2025 8:39am

Copy link
Contributor

coderabbitai bot commented May 11, 2025

Walkthrough

The changes refactor entitlement-based permission logic for cohort content access, centralizing checks within defineRulesForPurchases and updating how entitlement types are handled. Several components and utilities are updated to reflect these changes, including ability rule queries, prop types, and permission checks. Minor cleanup, logging adjustments, and middleware matcher updates are also made.

Changes

File(s) Change Summary
apps/ai-hero/src/ability/index.ts Refactored entitlement-based permission logic, moving checks for cohort content access to defineRulesForPurchases and updating the ViewerAbilityInput type with an optional entitlementTypes property. Removed entitlement logic from getAbilityRules.
apps/ai-hero/src/app/(commerce)/products/page.tsx Changed product fetching from getActiveSelfPacedProducts to getProducts.
apps/ai-hero/src/app/(content)/workshops/[module]/[lesson]/(view)/layout.tsx Removed React.Suspense and fallback UI; added but did not use an import for getAbilityForResource.
apps/ai-hero/src/app/(content)/workshops/_components/workshop-resource-list.tsx Changed currentLessonSlug prop type to string; replaced internal usage of params.lesson with prop; improved ability checks and query control; added moduleId prop to LessonResource.
apps/ai-hero/src/components/navigation/index.tsx Removed ability rules query and related variables.
apps/ai-hero/src/inngest/functions/post-cohort-purchase-workflow.ts Changed entitlement ID generation to use a UUID suffix instead of a static entitlement type ID prefix.
apps/ai-hero/src/lib/cohorts-query.ts Fetched entitlement type ID from the database; updated cohort entitlement identification logic to check sourceType instead of metadata.
apps/ai-hero/src/lib/workshops-query.ts Removed all console.debug statements related to cohort and resource rows; added id to selected columns in getMinimalWorkshop query.
apps/ai-hero/src/server/auth.ts Removed an extraneous blank line after header retrieval.
apps/ai-hero/src/trpc/api/routers/ability.ts Cleaned up imports; expanded logic to retrieve user session and organization ID for ability rule queries.
apps/ai-hero/src/utils/get-current-ability-rules.ts Removed unused imports and helper function; added entitlementTypes to returned rules; updated getAbilityForResource to include canCreate permission.
packages/utils-auth/src/current-ability-rules.ts Added canCreate boolean property to AbilityForResource type.
.changeset/wide-years-cut.md Added canCreate property to the abilityForResource type in the @coursebuilder/utils-auth package.
apps/ai-hero/src/middleware.ts Modified middleware matcher pattern to include /api routes for processing.
apps/ai-hero/src/trpc/api/trpc.ts Added logging of incoming request headers in createTRPCContext.
apps/ai-hero/src/app/(content)/workshops/_components/video-overlay-pricing-widget.tsx Added padding-top style to pricing details and changed buy button text to "Enroll Now".
apps/ai-hero/src/utils/get-resource-section.ts Added early continue in loop to skip non-section resources in getResourceSection.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Frontend
    participant Backend
    participant Database

    User->>Frontend: Requests workshop lesson/resource
    Frontend->>Backend: Fetch ability rules (with session, orgId)
    Backend->>Database: Query entitlementTypes, entitlements
    Database-->>Backend: Return entitlementTypes and entitlements
    Backend->>Frontend: Return ability rules (incl. entitlementTypes)
    Frontend->>Frontend: Check canView/canCreate using rules
    Frontend-->>User: Render content or show access restrictions
Loading

Possibly related PRs

  • feat: cohort entitlements #469: Modifies entitlement handling in ability rules and permission checks, related to cohort entitlement logic but focused on post-purchase entitlement addition.

Suggested reviewers

  • joelhooks
  • nicollguarnizo

Poem

🐇 Hopping through code with a twitch of my nose,
Permissions refined where the cohort content grows.
UUIDs dance in a neat little line,
Access checks now both clear and fine.
No more suspense, just smooth, steady flow—
Rabbit cheers for the changes, watch us all grow! 🌿✨

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
/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.

✨ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

@@ -103,7 +105,7 @@ export const postCohortPurchaseWorkflow = inngest.createFunction(
if (cohortContentAccessEntitlementType && cohortResource?.resources) {
await step.run(`add user to cohort via entitlement`, async () => {
for (const resource of cohortResource.resources || []) {
const entitlementId = `${cohortContentAccessEntitlementType.id}-${resource.resource.id}`
const entitlementId = `${resource.resource.id}-${guid()}`
Copy link
Member Author

Choose a reason for hiding this comment

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

making sure these are all unique

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 (1)
apps/ai-hero/src/inngest/functions/post-cohort-purchase-workflow.ts (1)

106-112: 💡 Verification agent

🧩 Analysis chain

Potential type-mismatch & redundant compound key when generating entitlementId

entitlements.id is typically defined as a UUID (or at most a 36-char varchar).
Concatenating uuidv4() with an additional -<resource.id>:

const entitlementId = `${randomId}-${resource.resource.id}`

may:

  1. Exceed the column’s length / UUID format constraints and blow up the insert.
  2. Add no real value—randomId is already globally unique.
  3. Make debugging harder (id is no longer a valid UUID).

Unless you have an explicit requirement for the compound id, consider keeping the id a pure UUID and store resource.resource.id only in sourceId/metadata.

- const randomId = uuidv4()
- const entitlementId = `${randomId}-${resource.resource.id}`
+ const entitlementId = uuidv4()

Bonus: before inserting, upsert or check for an existing entitlement to avoid duplicates if the workflow is retried.


🏁 Script executed:

#!/bin/bash
# Locate the Drizzle table definition for `entitlements` and show its schema
rg -l "export const entitlements" -g "*.ts" | xargs -I{} sed -n '1,200p' {}

Length of output: 79


🏁 Script executed:

#!/bin/bash
# Search for all references to `entitlements` across TypeScript files to locate its table definition
rg -n "entitlements" -g '*.ts'

Length of output: 22536


🏁 Script executed:

#!/bin/bash
# Show the first 200 lines of the AI Hero DB schema to locate the `entitlements` table definition
sed -n '1,200p' apps/ai-hero/src/db/schema.ts

Length of output: 1949


🏁 Script executed:

#!/bin/bash
# Display the schema definition for the entitlements table to verify column types
sed -n '1,200p' packages/adapter-drizzle/src/lib/mysql/schemas/entitlements/entitlement.ts

Length of output: 2415


Simplify entitlementId and store resource.id separately

The Entitlement.id column is defined as varchar(191), so concatenating a 36-char UUID plus -<resource.id> won’t exceed the length or violate any UUID format (there’s no strict UUID constraint). Embedding resource.resource.id in the primary key provides little added value—uuidv4() alone is already globally unique—and makes it harder to query or reference the resource later.

Instead, use a pure UUID for id and persist resource.resource.id in sourceId or metadata. Also, to guard against duplicate entitlements when the workflow is retried, consider an upsert or a pre-insert existence check.

File: apps/ai-hero/src/inngest/functions/post-cohort-purchase-workflow.ts
Lines 106–112

- const randomId = uuidv4()
- const entitlementId = `${randomId}-${resource.resource.id}`
+ const entitlementId = uuidv4()
  // e.g. sourceId: resource.resource.id
🧹 Nitpick comments (2)
apps/ai-hero/src/lib/cohorts-query.ts (1)

93-105: Dynamic type lookup is good – but ensure column types align and keep logic DRY

Fetching the entitlement-type id avoids hard-coding – nice!
A couple of follow-ups:

  1. entitlements.entitlementType must be the same type as entitlementTypes.id (likely UUID). If it still stores a string like 'cohort_content_access', the new filter will silently fail. Please double-check the column definition and existing data.

  2. You now have two nearly identical queries (here and in post-cohort-purchase-workflow.ts). Extract a small helper:

export async function getEntitlementTypeId(name: string) {
  return (
    await db.query.entitlementTypes.findFirst({
      where: eq(entitlementTypes.name, name),
    })
  )?.id
}
  1. Consistency: syncDiscordRoles still filters by the string 'cohort_discord_role'. If that column was switched to UUIDs too, that query will break.
apps/ai-hero/src/trpc/api/routers/ability.ts (1)

75-88: Remove unused session variable to prevent linter / build warnings

const { session } = await getServerAuthSession() is never referenced afterwards.

- const { session } = await getServerAuthSession()
+ await getServerAuthSession() // still enforces auth cookie decoding (if needed)

(or keep the destructuring and actually leverage session?.user.id in the ability rules call).

📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 03071d9 and 05a130c.

📒 Files selected for processing (12)
  • apps/ai-hero/src/ability/index.ts (2 hunks)
  • apps/ai-hero/src/app/(commerce)/products/page.tsx (2 hunks)
  • apps/ai-hero/src/app/(content)/workshops/[module]/[lesson]/(view)/layout.tsx (2 hunks)
  • apps/ai-hero/src/app/(content)/workshops/_components/workshop-resource-list.tsx (6 hunks)
  • apps/ai-hero/src/components/navigation/index.tsx (0 hunks)
  • apps/ai-hero/src/inngest/functions/post-cohort-purchase-workflow.ts (2 hunks)
  • apps/ai-hero/src/lib/cohorts-query.ts (3 hunks)
  • apps/ai-hero/src/lib/workshops-query.ts (1 hunks)
  • apps/ai-hero/src/server/auth.ts (0 hunks)
  • apps/ai-hero/src/trpc/api/routers/ability.ts (2 hunks)
  • apps/ai-hero/src/utils/get-current-ability-rules.ts (3 hunks)
  • packages/utils-auth/src/current-ability-rules.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • apps/ai-hero/src/server/auth.ts
  • apps/ai-hero/src/components/navigation/index.tsx
🧰 Additional context used
🧬 Code Graph Analysis (2)
apps/ai-hero/src/app/(content)/workshops/[module]/[lesson]/(view)/layout.tsx (1)
apps/ai-hero/src/app/(content)/workshops/_components/workshop-resource-list.tsx (1)
  • WorkshopResourceList (42-246)
apps/ai-hero/src/app/(content)/workshops/_components/workshop-resource-list.tsx (1)
apps/ai-hero/src/ability/index.ts (1)
  • createAppAbility (102-102)
⏰ Context from checks skipped due to timeout of 90000ms (4)
  • GitHub Check: typecheck
  • GitHub Check: lint
  • GitHub Check: build
  • GitHub Check: format
🔇 Additional comments (19)
packages/utils-auth/src/current-ability-rules.ts (1)

81-82: Extension of permission system looks good

The addition of the canCreate property to AbilityForResource is well-documented and follows the established pattern of the existing properties. This extension enables the cohort access ability feature described in the PR objectives.

apps/ai-hero/src/app/(commerce)/products/page.tsx (1)

3-3: API function name change looks good

Changed from using getActiveSelfPacedProducts to the more generic getProducts function, which aligns with the refactoring of entitlement and ability handling across the app. The function signature appears to be compatible as no other changes were needed in the component.

Also applies to: 16-16

apps/ai-hero/src/lib/workshops-query.ts (1)

265-265: Debug log removal is appropriate

Removal of console.debug statement is a good cleanup. This appears to be part of broader cleanup of debug logs related to cohort rows and cohort resource rows in this file, which is appropriate for production code.

apps/ai-hero/src/app/(content)/workshops/[module]/[lesson]/(view)/layout.tsx (1)

29-46: Suspense wrapper removal looks good

Removing the React.Suspense wrapper and its fallback UI streamlines the component rendering. This change aligns with updates to the WorkshopResourceList component which now accepts a currentLessonSlug prop instead of using useParams, making the loading state handling more direct.

apps/ai-hero/src/utils/get-current-ability-rules.ts (4)

51-52: Good addition: Now fetching entitlement types from database

This change supports the centralization of entitlement-based permission logic, providing necessary context to the ability rules.


86-86: Nice integration with ability rules

Passing the entitlement types to defineRulesForPurchases ensures the permission system has access to all necessary metadata for cohort access checks.


110-110: Good permission check addition

Adding the canCreate permission check is essential for supporting workshop content management in the UI, allowing authorized users to edit content.


116-117: Properly exposing the new permission

The canCreate permission is correctly exposed in the returned object, making it available to components that need to check for creation rights.

apps/ai-hero/src/app/(content)/workshops/_components/workshop-resource-list.tsx (6)

55-63: Improved ability rules query implementation

Good improvements:

  1. Now properly passing props.currentLessonSlug as lessonId
  2. Added enabled flag to prevent unnecessary queries
  3. Query only runs when workshop navigation is available

This makes the query more efficient and correctly tied to the current lesson.


164-165: Consistent use of props

Using props.currentLessonSlug instead of params.lesson improves consistency and makes the component more predictable, since it now relies on props passed from parent rather than route parameters.


283-284: Smart permission caching

Caching the permission check results improves performance by avoiding repeated calls to ability.can() in the JSX template.


334-339: Clean conditional rendering with cached permission

Using the cached canView makes the code more readable and efficient than calling ability.can() directly in the JSX.


344-355: Good implementation of conditional edit button

The edit button is now properly shown only to users with content creation permissions, using the cached canCreate value.


33-33:

❓ Verification inconclusive

Prop type refinement

Changed currentLessonSlug from string | null to just string, ensuring more type safety. Make sure all callers provide a string value.


🏁 Script executed:

#!/bin/bash
# Check if there are places where null might be passed to currentLessonSlug
rg -A 3 -B 3 "currentLessonSlug={(.*)null" --glob "*.tsx" --glob "*.jsx"

Length of output: 203


🏁 Script executed:

#!/bin/bash
# Search for any instances where currentLessonSlug is explicitly set to null
rg -n -C3 "currentLessonSlug\s*=\s*{\s*null\s*}" --glob "*.tsx" --glob "*.jsx"

Length of output: 224


🏁 Script executed:

#!/bin/bash
# Re-run search for any JSX prop passing null to currentLessonSlug, using POSIX spaces
rg -n -C3 "currentLessonSlug[[:space:]]*=[[:space:]]*{[[:space:]]*null[[:space:]]*}" --glob "*.tsx" --glob "*.jsx"

Length of output: 314


🏁 Script executed:

#!/bin/bash
# Literal search for passing null as currentLessonSlug in JSX
rg -F -n "currentLessonSlug={null}" --glob "*.tsx" --glob "*.jsx"

Length of output: 67


#!/bin/bash
# Find all usages of the <WorkshopResourceList /> component to confirm prop coverage
rg -n "<WorkshopResourceList" --glob "*.tsx" --glob "*.jsx" -C2

Prop type refinement check
I couldn’t find any JSX usage that passes null into currentLessonSlug or omits the prop entirely. It looks safe to tighten the type from string | null to string.

Please manually verify any dynamic data sources or higher-level components feeding this prop never supply null.

apps/ai-hero/src/ability/index.ts (5)

199-202: Good type extension for entitlement types

Adding the optional entitlementTypes property to ViewerAbilityInput provides necessary type safety for the new entitlement-based checks.


209-216: Clean destructuring of input parameters

Good refactoring to destructure more variables from the input, making the code more readable.


313-316: Dynamic entitlement type identification

Finding the cohort entitlement type by name rather than hardcoding an ID is more maintainable and flexible.


318-326: Workshop-level cohort access control

This block properly implements workshop-level access control based on entitlements, checking if the workshop ID is in the user's entitlement metadata.


328-339: Lesson-level cohort access check

The lesson-level entitlement check is implemented correctly, but there's a TODO comment that should be addressed.

The comment on line 329 indicates that validation is needed. Consider implementing proper validation logic or removing the TODO if it's already validated elsewhere.

@@ -2,6 +2,7 @@ import React from 'react'
import { WorkshopResourceList } from '@/app/(content)/workshops/_components/workshop-resource-list'
import LayoutClient from '@/components/layout-client'
import { ActiveHeadingProvider } from '@/hooks/use-active-heading'
import { getAbilityForResource } from '@/utils/get-current-ability-rules'
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Unused import detected

The getAbilityForResource function is imported but not used in this file. Consider removing this import if it's not needed.

-import { getAbilityForResource } from '@/utils/get-current-ability-rules'
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
import { getAbilityForResource } from '@/utils/get-current-ability-rules'
// (import removed as it was unused)

Comment on lines +75 to +87
const { session } = await getServerAuthSession()
const cookieStore = await cookies()
const organizationId = cookieStore.get('organizationId')?.value

const abilityRules = await getCurrentAbilityRules({
lessonId: input?.lessonId,
moduleId: input?.moduleId,
...(organizationId && {
organizationId,
}),
})

return abilityRules
Copy link
Member Author

Choose a reason for hiding this comment

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

took me a while to figure out why active entitlements weren't present in trpc context.

turns out await headers() used for getting organizationId doesn't work in trpc context so using cookies instead and passing it down to abilities.

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

🧹 Nitpick comments (1)
apps/ai-hero/src/ability/index.ts (1)

331-333: Consider enhancing lessonModule lookup comment

While the code correctly finds a lesson within a module's resources, the comment could be more descriptive to explain why this check is necessary before granting lesson-level access.

- // lesson check
- // TODO: validate
+ // Find if current lesson belongs to the module before checking lesson-level access
+ // TODO: validate that contentIds exists and is properly structured
const lessonModule = module?.resources?.find(
  (resource) => resource.resourceId === lesson?.id,
)
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 2bbf031 and 8d1e131.

📒 Files selected for processing (1)
  • apps/ai-hero/src/ability/index.ts (4 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (2)
  • GitHub Check: format
  • GitHub Check: build
🔇 Additional comments (5)
apps/ai-hero/src/ability/index.ts (5)

182-185: Appropriate removal of global content access rule

The commented out global content access rule makes sense in the context of this PR. As we're moving to an entitlement-based approach for cohort content access, we no longer want to grant broad access to all content with specific creation dates and statuses.

Note that the original comment from Joel about potentially changing from "Content" to "CohortContent" could still be considered if you want to make the permission model more specific in the future.


199-202: LGTM: Proper type extension for entitlement types

The ViewerAbilityInput type is correctly extended with the entitlementTypes optional property, which is essential for the cohort access feature. This allows passing entitlement type data from the database to the ability rules.


266-269: Good practice commenting out legacy code

Clearly marking this as legacy non-entitlement based access helps developers understand why this code is being replaced. The PR correctly moves away from purchase-based content access to a more flexible entitlement-based approach for cohort workshops.


314-317: LGTM: Proper entitlement type lookup

The code correctly identifies the cohort entitlement type by looking for the one named 'cohort_content_access'. This approach allows for flexibility if additional entitlement types are added later.


319-327: Correct implementation of workshop-level content access

The implementation correctly checks if the user has entitlements of type 'cohort_content_access' and grants read access to Content with IDs in the entitlement's metadata. Using $in operator is the right approach for checking against an array of contentIds.

Comment on lines +330 to +340
// TODO: validate
const lessonModule = module?.resources?.find(
(resource) => resource.resourceId === lesson?.id,
)
if (user?.entitlements && lessonModule) {
user.entitlements.forEach((entitlement) => {
if (entitlement.type === cohortEntitlementType?.id) {
can('read', 'Content', { id: { $in: entitlement.metadata.contentIds } })
}
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Address TODO comment and consider potential redundancy

The lesson-level check correctly uses $in operator as suggested in previous reviews. However, there are two issues to address:

  1. The "TODO: validate" comment should be addressed with specific validation requirements
  2. Consider potential redundancy between module-level and lesson-level checks - a user might get duplicate permissions if they have access to both a module and its lessons

Consider adding validation for entitlement.metadata.contentIds to ensure it's always an array:

if (entitlement.type === cohortEntitlementType?.id) {
+   // Ensure contentIds is an array before using $in operator
+   const contentIds = Array.isArray(entitlement.metadata.contentIds) 
+     ? entitlement.metadata.contentIds 
+     : [entitlement.metadata.contentIds];
-   can('read', 'Content', { id: { $in: entitlement.metadata.contentIds } })
+   can('read', 'Content', { id: { $in: contentIds } })
}

Also consider adding a comment explaining the purpose of the separate module and lesson level checks.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// TODO: validate
const lessonModule = module?.resources?.find(
(resource) => resource.resourceId === lesson?.id,
)
if (user?.entitlements && lessonModule) {
user.entitlements.forEach((entitlement) => {
if (entitlement.type === cohortEntitlementType?.id) {
can('read', 'Content', { id: { $in: entitlement.metadata.contentIds } })
}
})
}
// TODO: validate
const lessonModule = module?.resources?.find(
(resource) => resource.resourceId === lesson?.id,
)
if (user?.entitlements && lessonModule) {
user.entitlements.forEach((entitlement) => {
if (entitlement.type === cohortEntitlementType?.id) {
// Ensure contentIds is an array before using $in operator
const contentIds = Array.isArray(entitlement.metadata.contentIds)
? entitlement.metadata.contentIds
: [entitlement.metadata.contentIds];
can('read', 'Content', { id: { $in: contentIds } })
}
})
}

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: 0

🧹 Nitpick comments (1)
apps/ai-hero/src/utils/get-resource-section.ts (1)

13-15: Improved filtering logic for section resources.

The addition of an early return when a resource's type is not 'section' makes the function more precise and efficient, ensuring that only relevant resources are processed.

Consider using filter() before forEach() for better readability:

-	moduleResource.resources.forEach((section) => {
-		if (section.resource.type !== 'section') return
+	moduleResource.resources
+		.filter((section) => section.resource.type === 'section')
+		.forEach((section) => {
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between af3677f and 9cdf186.

📒 Files selected for processing (3)
  • apps/ai-hero/src/app/(content)/workshops/_components/video-overlay-pricing-widget.tsx (1 hunks)
  • apps/ai-hero/src/utils/get-current-ability-rules.ts (4 hunks)
  • apps/ai-hero/src/utils/get-resource-section.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/ai-hero/src/utils/get-current-ability-rules.ts
🔇 Additional comments (2)
apps/ai-hero/src/app/(content)/workshops/_components/video-overlay-pricing-widget.tsx (2)

55-55: UI refinement with zero top padding.

The addition of pt-0 to the className ensures consistent spacing in the pricing details component by explicitly setting the top padding to zero, maintaining alignment with the design system.


61-61: Improved user clarity with descriptive button text.

The addition of "Enroll Now" text to the BuyButton provides clearer context to users about the action they're taking. This is more appropriate for workshop enrollment than a generic or empty button.

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