Conversation
Rewrite guide pages to read from Course Builder database instead of Sanity CMS. Guides are stored as ContentResource with type='list' and fields.type='guide', with sections and resource links via the ContentResourceResource join table. - Add guides-from-course-builder.ts with CB query functions - Add load-guide-wrapper.ts for client-safe server-side imports - Update guide index and slug pages to use new data source - Add guide/published Inngest event and TypeSense upsert function - Register upsertGuideToTypesense in Inngest config Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Guides now fully read from Course Builder. Move guide Zod schemas to src/schemas/guide.ts, delete Sanity fetching code (src/lib/guides.ts), remove guide from Sanity studio schema/structure/desk, and add ListTypeSchema for list-type resource discrimination. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
💤 Files with no reviewable changes (1)
📝 WalkthroughWalkthroughMigrates guide content from Sanity to a Course Builder MySQL backend, adds Inngest event and Typesense upsert workflow for published guides, implements server-side DB loaders with client-safe wrappers, updates Next.js guide pages to use the new loaders, and removes Sanity guide schemas/Studio structures. Changes
Sequence Diagram(s)sequenceDiagram
actor User
participant App as Next.js App
participant CourseDB as Course Builder DB
participant Inngest as Inngest
participant Typesense as Typesense
User->>App: Publish or update guide
App->>Inngest: Emit GUIDE_PUBLISHED_EVENT
Inngest->>Inngest: Validate event (Zod)
Inngest->>Typesense: upsert-guide step with payload
Typesense->>Typesense: Transform payload -> document
Typesense-->>Inngest: Upsert result
App->>CourseDB: loadGuide(slug) / loadGuides()
CourseDB-->>App: Return guide(s) rows
App->>App: Map DB rows -> Guide types
App-->>User: Render guide pages
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
The resource document schema still referenced the deleted guide type, causing a Sanity studio error. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (3)
src/inngest/functions/typesense/upsert-guide-to-typesense.ts (1)
40-49: Avoid schema drift by reusingGuidePublishedEventSchemadirectly.
guideSchemaduplicates the event schema fields. Reuse the imported schema so producer/consumer contracts stay locked.Proposed simplification
-export const guideSchema = z.object({ - guideId: z.string(), - title: z.string(), - description: z.string().optional(), - slug: z.string(), - path: z.string(), - image: z.string().optional(), - createdAt: z.string().optional(), - updatedAt: z.string(), -}) +export const guideSchema = GuidePublishedEventSchema🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/inngest/functions/typesense/upsert-guide-to-typesense.ts` around lines 40 - 49, The current guideSchema duplicates fields from GuidePublishedEventSchema; replace the z.object literal by reusing the imported GuidePublishedEventSchema to prevent schema drift. Locate guideSchema and change its definition to reference GuidePublishedEventSchema directly (or use GuidePublishedEventSchema.pick(...) / .omit(...) if only a subset is needed) so the consumer/producer contract is shared via the single source of truth.src/pages/guides/index.tsx (1)
19-20: Minor: Redundant filter condition.The
&& guideis unnecessary sinceguideis always truthy when iterating over an array.Suggested simplification
resources: guides.filter( - (guide) => guide.state === 'published' && guide, + (guide) => guide.state === 'published', ),🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/pages/guides/index.tsx` around lines 19 - 20, The filter predicate in the expression using guides.filter currently has a redundant "&& guide" check; update the predicate (the arrow function for guides.filter) to only check guide.state === 'published' (remove the "&& guide") so the filter only tests the published state; locate the arrow function passed to guides.filter in the component/page and simplify it accordingly.src/lib/guides-from-course-builder.ts (1)
140-207: N+1 query pattern for section resources.Each section triggers a separate database query for its child resources. For guides with many sections, this results in N+1 queries. While acceptable for SSG (build-time only), consider a single JOIN query if performance becomes a concern with larger guides.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/lib/guides-from-course-builder.ts` around lines 140 - 207, The current code issues an N+1 query inside the sectionRows.map async loop by calling activeConn.execute for each sectionId; instead, run one query up-front that selects all resource rows WHERE crr.resourceOfId IN (all section ids) (using the same SELECT fields and JOIN from the existing activeConn.execute), parse/JSON-decode each row's metadata and compute resource fields exactly as done in the per-section mapper, then group the returned rows by resourceOfId and, in the existing sectionRows.map, lookup the grouped resources for that sectionId to build GuideResource objects (use the same identifiers resourceRow, resourceFields, metadata, mapResourceType, etc.) so you replace the per-section execute call with a single batch fetch and grouping step.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@src/inngest/functions/typesense/upsert-guide-to-typesense.ts`:
- Around line 82-90: The upsert path started in the async
step.run('upsert-guide', ...) around syncWithSearchProvider (using guideSchema,
syncToTypeSense, transformGuideData) lacks structured start/success/error logs
and respect for the repo-wide log toggle; add an explicit info log before
starting the upsert (e.g., "starting upsert-guide" with event id/context), a
success info log after syncWithSearchProvider resolves, and an error log that
catches and logs handled errors and unexpected exceptions (including the error
object) within the step.run block; ensure these logs use the shared logger used
elsewhere (e.g., processLogger or repo logger) and honor the LOG_LEVEL/--no-log
toggle so that when logging is disabled no log file is created and no output is
emitted.
- Around line 79-82: The repository registers upsertGuideToTypesense to handle
GUIDE_PUBLISHED_EVENT but never emits that event; add an event producer by
calling inngest.send({ name: GUIDE_PUBLISHED_EVENT, data: { ...guide payload...
} }) at the point where guides are published (the code path that performs the
publish/save action), ensuring the payload includes the guide id, title,
content/summary and any metadata the upsertGuideToTypesense handler expects so
indexing will be triggered.
In `@src/inngest/inngest.server.ts`:
- Line 63: The import for GUIDE_PUBLISHED_EVENT and GuidePublished should use
the repository's src alias; update the import statement that currently imports
GUIDE_PUBLISHED_EVENT and GuidePublished from './events/guide-published' to use
'@/inngest/events/guide-published' so it follows the "src/**/*.{ts,tsx}" `@/` path
alias convention.
In `@src/lib/guides-from-course-builder.ts`:
- Around line 5-7: The module-level const access = { uri:
process.env.COURSE_BUILDER_DATABASE_URL } reads the env var at import time and
can be undefined; move the environment access into the runtime scope (e.g.,
inside the functions that use ConnectionOptions or inside any function that
constructs the connection) or use the shared pool from db.ts instead of the
module-scoped access; specifically replace module-scope use of
access/ConnectionOptions with code that reads
process.env.COURSE_BUILDER_DATABASE_URL at call time (or calls the db.ts pool
factory) and pass that constructed ConnectionOptions into the functions that
establish connections.
- Around line 9-21: The file defines a duplicate singleton getConnectionPool()
and connectionPool which should be removed and replaced with the shared
getPool() from db.ts; delete the local connectionPool variable and
getConnectionPool() function, import or use the existing getPool() function
instead, and replace all calls to getConnectionPool() in this module with
getPool() to avoid duplicate pools and ensure a single shared connection pool is
used.
---
Nitpick comments:
In `@src/inngest/functions/typesense/upsert-guide-to-typesense.ts`:
- Around line 40-49: The current guideSchema duplicates fields from
GuidePublishedEventSchema; replace the z.object literal by reusing the imported
GuidePublishedEventSchema to prevent schema drift. Locate guideSchema and change
its definition to reference GuidePublishedEventSchema directly (or use
GuidePublishedEventSchema.pick(...) / .omit(...) if only a subset is needed) so
the consumer/producer contract is shared via the single source of truth.
In `@src/lib/guides-from-course-builder.ts`:
- Around line 140-207: The current code issues an N+1 query inside the
sectionRows.map async loop by calling activeConn.execute for each sectionId;
instead, run one query up-front that selects all resource rows WHERE
crr.resourceOfId IN (all section ids) (using the same SELECT fields and JOIN
from the existing activeConn.execute), parse/JSON-decode each row's metadata and
compute resource fields exactly as done in the per-section mapper, then group
the returned rows by resourceOfId and, in the existing sectionRows.map, lookup
the grouped resources for that sectionId to build GuideResource objects (use the
same identifiers resourceRow, resourceFields, metadata, mapResourceType, etc.)
so you replace the per-section execute call with a single batch fetch and
grouping step.
In `@src/pages/guides/index.tsx`:
- Around line 19-20: The filter predicate in the expression using guides.filter
currently has a redundant "&& guide" check; update the predicate (the arrow
function for guides.filter) to only check guide.state === 'published' (remove
the "&& guide") so the filter only tests the published state; locate the arrow
function passed to guides.filter in the component/page and simplify it
accordingly.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 9ce3cea9-d8eb-485e-be76-64118f86ce7c
📒 Files selected for processing (15)
src/components/guides/guide-template.tsxsrc/inngest/events/guide-published.tssrc/inngest/functions/typesense/upsert-guide-to-typesense.tssrc/inngest/inngest.config.tssrc/inngest/inngest.server.tssrc/lib/guides-from-course-builder.tssrc/lib/load-guide-wrapper.tssrc/pages/guides/[slug].tsxsrc/pages/guides/index.tsxsrc/schemas/guide.tssrc/schemas/list.tsstudio/deskStructure.tsstudio/schemas/documents/guide.tsxstudio/schemas/schema.jsstudio/src/structure/guides.js
💤 Files with no reviewable changes (3)
- src/schemas/guide.ts
- studio/schemas/documents/guide.tsx
- studio/src/structure/guides.js
| export const upsertGuideToTypesense = inngest.createFunction( | ||
| {id: 'upsert-guide-to-typesense', name: 'Upsert Guide to Typesense'}, | ||
| {event: GUIDE_PUBLISHED_EVENT}, | ||
| async ({event, step}) => { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify producer coverage for GUIDE_PUBLISHED_EVENT
# 1) Find all references to the event constant/string
rg -nP --type=ts -C2 '\bGUIDE_PUBLISHED_EVENT\b|guide/published'
# 2) Find Inngest send callsites
rg -nP --type=ts -C3 '\binngest\.send\s*\('
# Expected:
# - At least one send call that emits GUIDE_PUBLISHED_EVENT (or name: "guide/published")Repository: skillrecordings/egghead-next
Length of output: 8163
Add producer for GUIDE_PUBLISHED_EVENT to enable guide indexing.
The upsertGuideToTypesense function is registered to listen for guide/published events, but no code path in the repository calls inngest.send() to emit this event. Without an event producer, guide search indexing will never execute. Add an inngest.send({name: GUIDE_PUBLISHED_EVENT, data: {...}}) call at the point where guides are published.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/inngest/functions/typesense/upsert-guide-to-typesense.ts` around lines 79
- 82, The repository registers upsertGuideToTypesense to handle
GUIDE_PUBLISHED_EVENT but never emits that event; add an event producer by
calling inngest.send({ name: GUIDE_PUBLISHED_EVENT, data: { ...guide payload...
} }) at the point where guides are published (the code path that performs the
publish/save action), ensuring the payload includes the guide id, title,
content/summary and any metadata the upsertGuideToTypesense handler expects so
indexing will be triggered.
| async ({event, step}) => { | ||
| await step.run('upsert-guide', async () => { | ||
| return syncWithSearchProvider( | ||
| guideSchema, | ||
| event.data, | ||
| syncToTypeSense, | ||
| transformGuideData, | ||
| ) | ||
| }) |
There was a problem hiding this comment.
Add structured logs around the upsert operation.
This new background indexing path lacks explicit start/success/error logs, which makes troubleshooting hard and misses the repo’s logging requirements.
As per coding guidelines, "Record start/end of major operations, successful paths, handled errors, and unexpected failures" and "Provide a toggle to disable file logging: LOG_LEVEL=off or --no-log; when disabled, do not create the log file and suppress log output".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/inngest/functions/typesense/upsert-guide-to-typesense.ts` around lines 82
- 90, The upsert path started in the async step.run('upsert-guide', ...) around
syncWithSearchProvider (using guideSchema, syncToTypeSense, transformGuideData)
lacks structured start/success/error logs and respect for the repo-wide log
toggle; add an explicit info log before starting the upsert (e.g., "starting
upsert-guide" with event id/context), a success info log after
syncWithSearchProvider resolves, and an error log that catches and logs handled
errors and unexpected exceptions (including the error object) within the
step.run block; ensure these logs use the shared logger used elsewhere (e.g.,
processLogger or repo logger) and honor the LOG_LEVEL/--no-log toggle so that
when logging is disabled no log file is created and no output is emitted.
| WORKSHOP_QUOTE_REQUEST_EVENT, | ||
| WorkshopQuoteRequest, | ||
| } from './events/workshop-quote-request' | ||
| import {GUIDE_PUBLISHED_EVENT, GuidePublished} from './events/guide-published' |
There was a problem hiding this comment.
Use the @/ alias for this new src import.
Please switch this new import to @/inngest/events/guide-published to match repo import conventions.
As per coding guidelines, "src/**/*.{ts,tsx}: Use the @/ path alias for imports from the src directory".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/inngest/inngest.server.ts` at line 63, The import for
GUIDE_PUBLISHED_EVENT and GuidePublished should use the repository's src alias;
update the import statement that currently imports GUIDE_PUBLISHED_EVENT and
GuidePublished from './events/guide-published' to use
'@/inngest/events/guide-published' so it follows the "src/**/*.{ts,tsx}" `@/` path
alias convention.
| const access: ConnectionOptions = { | ||
| uri: process.env.COURSE_BUILDER_DATABASE_URL, | ||
| } |
There was a problem hiding this comment.
Module-scope environment access may fail if env var is loaded later.
The access object reads process.env.COURSE_BUILDER_DATABASE_URL at module load time. If this wrapper is imported before environment variables are fully loaded (e.g., during module resolution), access.uri will be undefined even when the functions later check for the env var.
Consider moving the env var access inside the function scope, or rely on the shared pool from db.ts which handles this.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/guides-from-course-builder.ts` around lines 5 - 7, The module-level
const access = { uri: process.env.COURSE_BUILDER_DATABASE_URL } reads the env
var at import time and can be undefined; move the environment access into the
runtime scope (e.g., inside the functions that use ConnectionOptions or inside
any function that constructs the connection) or use the shared pool from db.ts
instead of the module-scoped access; specifically replace module-scope use of
access/ConnectionOptions with code that reads
process.env.COURSE_BUILDER_DATABASE_URL at call time (or calls the db.ts pool
factory) and pass that constructed ConnectionOptions into the functions that
establish connections.
| let connectionPool: Pool | null = null | ||
|
|
||
| function getConnectionPool(): Pool { | ||
| if (!connectionPool) { | ||
| connectionPool = mysql.createPool({ | ||
| ...access, | ||
| waitForConnections: true, | ||
| connectionLimit: 10, | ||
| queueLimit: 0, | ||
| }) | ||
| } | ||
| return connectionPool | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Verify there's a shared pool implementation in db.ts that can be reused
ast-grep --pattern 'export function getPool() {
$$$
}'Repository: skillrecordings/egghead-next
Length of output: 54
🏁 Script executed:
# Search for db.ts file and its content
find . -name "db.ts" -o -name "db.js" | head -20Repository: skillrecordings/egghead-next
Length of output: 87
🏁 Script executed:
# Search for getConnectionPool or getPool functions in the codebase
rg "getConnectionPool|getPool" --type ts --type js | head -30Repository: skillrecordings/egghead-next
Length of output: 1397
🏁 Script executed:
# Check the actual guides-from-course-builder.ts file
cat src/lib/guides-from-course-builder.tsRepository: skillrecordings/egghead-next
Length of output: 8525
🏁 Script executed:
# Read the db.ts file to verify getPool() implementation
cat src/lib/db.tsRepository: skillrecordings/egghead-next
Length of output: 544
Remove duplicate connection pool; use the shared getPool() from db.ts.
This file creates its own getConnectionPool() singleton, but src/lib/db.ts already provides getPool() configured for the same database. Both implement identical connection pool configs (connectionLimit: 10), creating unnecessary duplication and risking connection exhaustion during builds or concurrent operations.
Per coding guidelines: "Always use the existing getConnectionPool() when accessing the Course Builder database."
Suggested fix: reuse existing pool
-import * as mysql from 'mysql2/promise'
-import {ConnectionOptions, RowDataPacket, Pool} from 'mysql2/promise'
+import {RowDataPacket} from 'mysql2/promise'
+import {getPool} from '@/lib/db'
import type {Guide, GuideSection, GuideResource} from '@/schemas/guide'
-const access: ConnectionOptions = {
- uri: process.env.COURSE_BUILDER_DATABASE_URL,
-}
-
-let connectionPool: Pool | null = null
-
-function getConnectionPool(): Pool {
- if (!connectionPool) {
- connectionPool = mysql.createPool({
- ...access,
- waitForConnections: true,
- connectionLimit: 10,
- queueLimit: 0,
- })
- }
- return connectionPool
-}Then replace all getConnectionPool() calls with getPool().
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/lib/guides-from-course-builder.ts` around lines 9 - 21, The file defines
a duplicate singleton getConnectionPool() and connectionPool which should be
removed and replaced with the shared getPool() from db.ts; delete the local
connectionPool variable and getConnectionPool() function, import or use the
existing getPool() function instead, and replace all calls to
getConnectionPool() in this module with getPool() to avoid duplicate pools and
ensure a single shared connection pool is used.
Summary
guides-from-course-builder.tswith full hierarchy queries (guide → sections → resources) reading from Course Builder'sContentResource/ContentResourceResourcetables/guidesand/guides/[slug]pages to use Course Builder via a server-side wrapper (load-guide-wrapper.ts), with zero changes toGuideTemplatetype: 'guide'facetsrc/schemas/guide.ts; addListTypeSchema(tutorial | nextUp | guide) insrc/schemas/list.tsTest plan
/guidesindex page renders published guides from Course Builder/guides/coding-your-way-to-the-jobrenders with correct sections and resourcesnpx tsc --noEmit— passes cleanly🤖 Generated with Claude Code
Summary by CodeRabbit
New Features
Refactor
Chores