From e16f2ae640eb9e8bc319cc9b4a8d30c5c8289c63 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Tue, 28 Jul 2026 13:55:14 +0000 Subject: [PATCH] perf(progress): scope user lesson progress database queries to relevant lessons Avoid fetching the student's entire lesson progress history in the courses catalog, student dashboard, and certificate pages. Restrict queries using `lessonId: { in: allLessonIds }` where `allLessonIds` matches only the lessons rendered on the specific page. This prevents redundant database records, reduces memory footprint, and improves response latency. Co-authored-by: projectamazonph <286085559+projectamazonph@users.noreply.github.com> --- .jules/bolt.md | 4 ++++ .../courses/[courseSlug]/certificate/page.tsx | 21 +++++++++++++------ src/app/(dashboard)/courses/page.tsx | 16 +++++++++----- src/app/(dashboard)/dashboard/page.tsx | 16 +++++++++----- 4 files changed, 41 insertions(+), 16 deletions(-) diff --git a/.jules/bolt.md b/.jules/bolt.md index ba67a71..f04062e 100644 --- a/.jules/bolt.md +++ b/.jules/bolt.md @@ -3,3 +3,7 @@ ## 2026-07-16 - [O(N*M) Nested Loop Lookups in Grading Engines] **Learning:** In interactive scenarios (such as Bid Elevator and STR Triage), grading engines frequently iterate over user decisions and match them against scenario properties (like keywords or search terms). Performing `array.find()` inside loop bodies or filter predicates results in costly O(N*M) lookups. **Action:** Convert arrays to `Map` lookups before entering loops/nested scans. Mapping keys once in O(M) time enables O(1) lookups during execution, transforming the time complexity of the grading logic to O(N + M). + +## 2026-07-17 - [Querying Unscoped User Lesson Progress History] +**Learning:** Querying a user's entire `lessonProgress` history without scoping to relevant courses or specific lesson IDs fetches redundant records from the database, resulting in unnecessary payloads, high memory consumption, and increased query latency as the student progresses. +**Action:** Extract the specific `lessonIds` of interest (e.g. from current courses/modules being rendered) and filter the progress lookup with `lessonId: { in: lessonIds }`. Avoid fetching the entire table by always scoping user-specific queries. diff --git a/src/app/(dashboard)/courses/[courseSlug]/certificate/page.tsx b/src/app/(dashboard)/courses/[courseSlug]/certificate/page.tsx index 3640169..7a0c9bc 100644 --- a/src/app/(dashboard)/courses/[courseSlug]/certificate/page.tsx +++ b/src/app/(dashboard)/courses/[courseSlug]/certificate/page.tsx @@ -194,13 +194,22 @@ async function PendingLessons({ take: 50, }); + const allLessonIds = allLessons.map((l) => l.id); + const completedSet = new Set( - ( - await db.lessonProgress.findMany({ - where: { userId, status: 'COMPLETED', deletedAt: null }, - select: { lessonId: true }, - }) - ).map((p) => p.lessonId), + allLessonIds.length > 0 + ? ( + await db.lessonProgress.findMany({ + where: { + userId, + lessonId: { in: allLessonIds }, + status: 'COMPLETED', + deletedAt: null, + }, + select: { lessonId: true }, + }) + ).map((p) => p.lessonId) + : [] ); const pending = allLessons.filter((l) => !completedSet.has(l.id)).slice(0, 8); diff --git a/src/app/(dashboard)/courses/page.tsx b/src/app/(dashboard)/courses/page.tsx index 1d0c6c3..08404f0 100644 --- a/src/app/(dashboard)/courses/page.tsx +++ b/src/app/(dashboard)/courses/page.tsx @@ -37,11 +37,17 @@ export default async function CoursesIndexPage() { }, }); - // Get user's lesson progress - const lessonProgress = await db.lessonProgress.findMany({ - where: { userId: user.id, deletedAt: null }, - select: { lessonId: true, status: true }, - }); + // Get user's lesson progress scoped to the courses being displayed to prevent large database payloads + const allLessonIds = courses.flatMap((c) => + c.modules.flatMap((m) => m.lessons.map((l) => l.id)) + ); + + const lessonProgress = allLessonIds.length > 0 + ? await db.lessonProgress.findMany({ + where: { userId: user.id, lessonId: { in: allLessonIds }, deletedAt: null }, + select: { lessonId: true, status: true }, + }) + : []; const progressMap = new Map(lessonProgress.map((p) => [p.lessonId, p.status])); return ( diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx index 546298d..e841f1e 100644 --- a/src/app/(dashboard)/dashboard/page.tsx +++ b/src/app/(dashboard)/dashboard/page.tsx @@ -30,11 +30,17 @@ export default async function DashboardPage() { }, }); - // Get user's lesson progress - const lessonProgress = await db.lessonProgress.findMany({ - where: { userId: user.id, deletedAt: null }, - select: { lessonId: true, status: true }, - }); + // Get user's lesson progress scoped to the courses being displayed to prevent large database payloads + const allLessonIds = courses.flatMap((c) => + c.modules.flatMap((m) => m.lessons.map((l) => l.id)) + ); + + const lessonProgress = allLessonIds.length > 0 + ? await db.lessonProgress.findMany({ + where: { userId: user.id, lessonId: { in: allLessonIds }, deletedAt: null }, + select: { lessonId: true, status: true }, + }) + : []; const progressMap = new Map(lessonProgress.map((p) => [p.lessonId, p.status])); // Compute aggregate stats