Skip to content

fix: reward history pagination/accuracy + course enrollment cache correctness - #185

Merged
DeFiVC merged 2 commits into
ChainLearnOfficial:mainfrom
Davidemulo:fix/issues-151-152-153-154-cache-rewards
Jul 29, 2026
Merged

fix: reward history pagination/accuracy + course enrollment cache correctness#185
DeFiVC merged 2 commits into
ChainLearnOfficial:mainfrom
Davidemulo:fix/issues-151-152-153-154-cache-rewards

Conversation

@Davidemulo

@Davidemulo Davidemulo commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Reward #154getHistory returned every claimed reward with no pagination, unbounded for long-tenured users. Added page/limit (zod-coerced, matching the existing listCoursesSchema convention already used by /api/courses), a total count, and wired it through the route/controller with the same pagination response shape courses already return.
  • Reward #153 — history hardcoded amount: REWARD_AMOUNT for every entry, so historical records would silently change if REWARD_AMOUNT is ever adjusted. Added quiz_submissions.reward_amount (migration 0006), persisted at the exact moment a claim is marked rewardClaimed: true (both processRewardClaim and RewardService.claimReward), and read back in getHistory. Pre-migration rows read null and fall back to REWARD_AMOUNT — an accurate backfill (that constant was the only value ever granted before this column existed), not a guess. Also switched history cache invalidation from a single key to cacheInvalidatePattern, since the cache key is now page/limit-scoped.
  • Enrollment #151listCourses and getCourseDetail each independently re-queried the enrollments table for the current user on every authenticated request, regardless of whether the course list/detail cache itself was a hit. Added CourseService.getEnrolledCourseIds — caches a user's full enrolled-course-ID set (small, changes rarely) for 30s, used by both call sites instead of a live query each time.
  • Course #152enroll()'s cache invalidation runs after the DB transaction commits — unavoidable, since Redis isn't part of the Postgres transaction and can't be made atomic with the commit. cacheDel/cacheInvalidatePattern already fail soft and every cache touched has a bounded TTL, so a transient failure was already recoverable, just undiagnosable as such. Centralized the four post-enroll invalidations (including the new enrollment-set cache) into one Promise.allSettled batch — reduces the commit-to-invalidation window from four sequential round-trips to one concurrent batch — and logs a single structured warning scoped to the specific enrollment if any sub-invalidation fails, rather than relying only on cacheDel's generic per-key warning.

Closes #151, Closes #152, Closes #153, Closes #154

Important caveat

Could not run the test suite — disk space on my machine was critically low (~1.7GB free) for this work, making npm install/running unsafe. Written by close reading of the existing course/reward service patterns and the courses module's established pagination convention. Please run tests/e2e/courses.test.ts and tests/e2e/rewards.test.ts before merging.

Test plan

  • tests/e2e/rewards.test.ts passes, including new pagination coverage
  • tests/e2e/courses.test.ts passes
  • Manually claim a reward, confirm reward_amount is persisted and /api/rewards/history returns it (not a hardcoded constant) with correct page/limit/total
  • Manually enroll in a course and confirm isEnrolled flips to true on the very next /api/courses and /api/courses/:id call (no stale cache read)

…loses ChainLearnOfficial#151, ChainLearnOfficial#152, ChainLearnOfficial#153, ChainLearnOfficial#154)

ChainLearnOfficial#154 — getHistory returned every claimed reward with no pagination,
unbounded for long-tenured users. Added page/limit (zod-coerced,
matching the existing listCoursesSchema convention), a total count,
and wired page/limit through the route/controller with the same
pagination response shape courses already use.

ChainLearnOfficial#153 — history hardcoded `amount: REWARD_AMOUNT` for every entry, so
historical records would silently change if REWARD_AMOUNT is ever
adjusted. Added quiz_submissions.reward_amount (migration 0006),
persisted at the exact moment a claim is marked rewardClaimed: true (in
both processRewardClaim and RewardService.claimReward), and read back
in getHistory. Pre-migration rows read null and fall back to
REWARD_AMOUNT, which is an accurate backfill (that constant was the
only value ever granted before this column existed), not a guess.

Also switched the history cache invalidation from a single cacheDel
key to cacheInvalidatePattern, since the cache key is now
page/limit-scoped (multiple cached pages per user) rather than one flat
key.

ChainLearnOfficial#151 — listCourses and getCourseDetail each independently re-queried
the enrollments table for the current user's enrollment status on
every authenticated request, regardless of whether the course list/
detail cache itself was a hit. Added CourseService.getEnrolledCourseIds
— caches a user's full set of enrolled course IDs (small, changes
rarely) for 30s, used by both call sites instead of a live query each
time.

ChainLearnOfficial#152 — enroll()'s cache invalidation runs after the DB transaction
commits (unavoidable: Redis isn't part of the Postgres transaction, so
this can't be made atomic with the commit). cacheDel/
cacheInvalidatePattern already fail soft and every cache touched has a
bounded TTL, so a transient failure was already recoverable, just
undiagnosable as such. Centralized the four post-enroll invalidations
(including the new per-user enrollment-set cache) into one
Promise.allSettled batch — reduces the real commit-to-invalidation
window from four sequential round-trips to one concurrent batch — and
logs a single structured warning scoped to this enrollment if any
sub-invalidation reports a failure, rather than only cacheDel's
generic per-key warning.

Could not run the test suite (disk space on my machine was critically
low, ~1.7GB free, for this work — installing/running was unsafe).
Written by close reading of the existing course/reward service
patterns and the courses module's established pagination convention;
please run the existing e2e suites (tests/e2e/courses.test.ts,
tests/e2e/rewards.test.ts) before merging.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@drips-wave

drips-wave Bot commented Jul 28, 2026

Copy link
Copy Markdown

@Davidemulo 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! 🚀

Learn more about application limits

@Davidemulo
Davidemulo force-pushed the fix/issues-151-152-153-154-cache-rewards branch 2 times, most recently from 857ff70 to a37283a Compare July 29, 2026 10:50
@DeFiVC
DeFiVC merged commit 72fcab1 into ChainLearnOfficial:main Jul 29, 2026
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.

Reward Reward Course Enrollment

2 participants