Quiz batch generation, custom feedback, course waitlist (Closes #308, #309, #310, #311) - #453
Merged
DeFiVC merged 4 commits intoAug 31, 2026
Conversation
|
@onyinyechinwokwu-success Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits. You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀 |
archiveCourse (ChainLearnOfficial#358) logs a "course.archived" audit event that wasn't in the AuditEvent type, breaking `npm run typecheck` for anyone building on main.
ChainLearnOfficial#308) Generates quizzes for multiple modules of a course in one request, reusing generateQuiz's per-module enrollment check, rate limit, existing-quiz short-circuit, and AI-fallback path. Modules are generated sequentially to avoid bursting the AI service, and each module's success/failure is reported independently so one failing module (e.g. its own per-module rate limit) doesn't block the rest of the batch. A dedicated route-level rate limit caps how often the batch endpoint itself can be called.
…ial#310) Adds a per-course FIFO waitlist: - POST/DELETE /api/v1/courses/:id/waitlist to join/leave - DELETE /api/v1/courses/:id/enroll to drop an enrollment — the companion action to enroll() that didn't previously exist, needed to give "a spot opens up" concrete meaning in this codebase - dropEnrollment notifies the waitlist head via the notifications table; the notified user stays queued until they actually enroll, at which point enroll() cleans up their waitlist entry automatically enroll()'s waitlist cleanup is nested under the same course-scoped `waitlist:{courseId}` lock used by joinWaitlist/leaveWaitlist/ notifyNextWaitlisted, so position numbers can't develop a gap from a join/leave racing an enrollment on the same course.
onyinyechinwokwu-success
force-pushed
the
feat/308-309-310-311-quiz-course-modules
branch
from
August 31, 2026 18:26
3b77c81 to
334089f
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Resolves all four issues assigned to me in this wave: #308, #309, #310, #311.
Update: while rebasing onto
main, PR #440 (issues #320-323) merged in and turned out to (a) duplicate my #309/#310 work with an equivalent implementation, and (b) contain a corrupted merge that brokemain's build (deleted several unrelated tables/methods —notifications,course_shares,batchEnroll,getPrerequisites,archiveCourse,publishCourse,duplicateCourse, most ofCourseController). This PR has been reworked accordingly — see the breakdown below.POST /api/v1/quizzes/generate-batchendpoint #308 —POST /api/v1/quizzes/generate-batch: generates quizzes for multiple modules of a course in one request. ReusesgenerateQuiz's per-module enrollment check, rate limit, existing-quiz short-circuit, and AI-fallback path. Modules are generated sequentially (not in parallel) to avoid bursting the AI service, and each module's success/failure is reported independently so one module failing doesn't block the rest of the batch. Capped at 10 modules per request, with a dedicated route-level rate limit (5/min) on top of the existing per-module/hour limit.correctFeedback/incorrectFeedbacksupport, AI-generated, used insubmitQuizwith the same generic fallback). No new commit needed here; verified against the acceptance criteria and closing as satisfied.WaitlistService) but never wired up the one thing . Add course enrollment waitlist #310's acceptance criteria actually require: "next person is notified when a spot opens." There was no way for a spot to open at all (no unenroll/drop action existed), andWaitlistService.getNextOnWaitlistwas dead code, never called. This PR adds the missing piece:DELETE /api/v1/courses/:id/enroll— drops the caller's enrollment (the companion action toenroll()this codebase didn't have).dropEnrollmentthen callsWaitlistService.getNextOnWaitlistand records acourse.waitlist.notifiedaudit-log entry for the identified user. There's currently no notifications table to write a user-facing notification to (dropped by Implement issues #320 #321 #322 #323 - webhooks, rewards leaderboard, quiz feedback, course waitlist #440's bad merge) — this records the signal via the audit log instead of silently doing nothing, so it isn't lost and can be wired to a real notification channel once that table exists again.enroll()'s existingWaitlistService.removeFromWaitlistcall takes them off — no changes needed there, it was already correct.GET /api/v1/courses/:id/leaderboardendpoint #311 — The leaderboard endpoint was already fully implemented under . AddGET /api/v1/courses/:id/leaderboardendpoint #324 (top 20, average score, quizzes-taken count, 5-min cache — matches . AddGET /api/v1/courses/:id/leaderboardendpoint #311's acceptance criteria exactly), but Implement issues #320 #321 #322 #323 - webhooks, rewards leaderboard, quiz feedback, course waitlist #440's merge collaterally deletedCourseController.leaderboardand theCourseLeaderboardEntrytype import /LEADERBOARD_SIZE/LEADERBOARD_TTL_SECONDSconstants fromcourse.service.ts, even though the route and the rest ofgetLeaderboard's body were left in place. Restored just that (it's my assigned issue, not scope creep) — confirmed viatests/unit/courses/leaderboard.test.ts, which fails on plainmainand passes on this branch.Also fixed
src/audit/index.ts: added the missing"course.archived"member to theAuditEventunion —archiveCourse(. AddPOST /api/v1/admin/courses/:id/archiveendpoint #358) logs this event but the type didn't include it, breakingnpm run typecheckfor anyone building onmainbefore this PR (this predates Implement issues #320 #321 #322 #323 - webhooks, rewards leaderboard, quiz feedback, course waitlist #440 and is unrelated to it).Scope note on the broken build
Per direction, this PR does not attempt to fix the rest of #440's regression (the deleted
notifications/course_sharestables,batchEnroll,getPrerequisites,archiveCourse,publishCourse,duplicateCourse, or the several other strippedCourseControllermethods thatcourse.routes.ts/admin-course.controller.tsstill call). That's a separate, much larger cleanup outside these 4 issues' scope — flagging it here for maintainer awareness. I verified with a side-by-sidenpm run typecheck/npm testrun against plainmainthat every remaining failure already exists there unchanged; this PR fixes the 6 leaderboard-specific errors (part of #311) and introduces zero new ones.Test plan
npm run typecheck— verified via a direct diff against plainmain's error output: this branch has exactly 6 fewer errors (the leaderboard fix) and zero new ones.npm run lint— 0 errors introduced (2 pre-existing errors intests/e2e/course-waitlist.test.ts, a file this PR never touches, exist identically onmain).npm test— verified via a direct diff against plainmain's failure list: this branch's failing-test list ismain's exact list minus the leaderboard test (now passing), zero new failures.src/test/quiz-generate-batch.test.ts(batch generation, per-module independence, existing-quiz reuse),src/test/course-waitlist.test.ts(dropEnrollment, the audit-log notification, and confirmingenroll()'s existing waitlist cleanup still works) — both pass against a live Postgres+Redis where reachable;tests/unit/quizzes/ai-client.test.ts's andtests/unit/courses/enrollment-limit.test.ts's pre-existing mocked-DB tests are unaffected (verified their call sequences still match).