feat(leaderboard): fix GROUP BY bug, anti-gaming protection, nightly snapshots & streak tracking - #1
Open
devmasalati wants to merge 1 commit into
Open
Conversation
…eaks - Fix duplicate-user GROUP BY bug in getLeaderboard (pe.progress_percentage removed from GROUP BY; replaced with MAX aggregate) - Add peer_review_votes gate: helpfulReviews only counts reviews with >= 1 like - Add service-level rate limit: max 5 peer reviews / reviewer / path / 24 h - Pre-compute leaderboard nightly into leaderboard_snapshots (< 50 ms reads) - Serve leaderboard from snapshots + real-time Redis streak increments - Add daily streak tracking cron writing user_activity_streaks + Redis keys - Add collaborative-learning routes with peerReviewLimiter middleware - Wire leaderboard-precompute (02:30 UTC) and streak-tracking (00:05 UTC) crons - Migration: leaderboard_snapshots, user_activity_streaks, peer_review_votes
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
Completes the
CollaborativeLearningServiceleaderboard with correctness fixes, anti-gaming protection, a pre-computed snapshot layer for fast reads, real-time Redis streak increments, and full infrastructure wiring.Problem Statement
GROUP BY u.id, …, pe.progress_percentage— one row per (user, progress) pairhelpfulReviewsscorestreakDayswas always 0 — never populatedChanges
1. Fix GROUP BY bug —
collaborative-learning.service.tsRemoved
pe.progress_percentagefrom theGROUP BYclause across all three leaderboard query branches (milestone,path,global). UsesMAX(pe.progress_percentage)as an aggregate instead.2. Anti-gaming peer review protection
Service layer (
createPeerReview):429if>= 5Leaderboard query (
computeLeaderboardLive):helpfulReviewsnow uses anEXISTS (SELECT 1 FROM peer_review_votes prv WHERE prv.review_id = pr.id)sub-select so only reviews with at least one liked vote are countedNew
votePeerReviewmethod: upserts a row intopeer_review_votes, enforcing one vote per (review, voter) pair.Route middleware (
collaborative-learning.routes.ts):peerReviewLimiterapplied toPOST /peer-reviews—max: 5,windowMs: 24h, keyed per user3. Nightly leaderboard pre-computation —
leaderboardPrecompute.job.tscomputeLeaderboardLivefor every(type × period)combinationleaderboard_snapshotsviaON CONFLICT … DO UPDATE4. Snapshot-first leaderboard reads —
getLeaderboardRequest flow:
leaderboard_snapshotsby(type, target_id, period)— single indexed lookup, <50 msGET streak:current:<userId>)5. Daily streak tracking —
streakTracking.job.tscurrent_streakif activity was consecutive; resets to 0 if gap detectedlongest_streakuser_activity_streaksin batches of 500streak:current:<userId>→ Redis (30-day TTL) for real-time leaderboard reads6. DB Migration —
20260818000000_create_leaderboard_and_streaks_tables.tsThree new tables:
leaderboard_snapshotsuser_activity_streakspeer_review_votesUnique constraints:
leaderboard_snapshots:UNIQUE (type, COALESCE(target_id::text, ''), period)user_activity_streaks:UNIQUE (user_id)peer_review_votes:UNIQUE (review_id, voter_id)7. Infrastructure wiring
scheduler.ts— Two new BullMQ repeatable jobs registered at startup:leaderboard-precompute-recurring→30 2 * * *streak-tracking-recurring→5 0 * * *maintenance.worker.ts— Two newjob.namedispatch handlers:leaderboard-precompute-scheduled→runLeaderboardPrecompute()streak-tracking-scheduled→runStreakTracking()routes/index.ts— Mounts/api/v1/collaborative-learningrouter.New API Endpoints
GET/api/v1/collaborative-learning/leaderboardtype,id,periodPOST/api/v1/collaborative-learning/peer-reviewsPOST/api/v1/collaborative-learning/peer-reviews/:id/votePOST/api/v1/collaborative-learning/forumsPOST/api/v1/collaborative-learning/forums/:id/messagesGET/api/v1/collaborative-learning/forums/:id/messagesPOST/api/v1/collaborative-learning/study-groupsPOST/api/v1/collaborative-learning/study-groups/:id/joinAcceptance Criteria Verification
DISTINCTon all aggregate joinsleaderboard_snapshots+ 60s L1 cachestreakTracking.job.tsat 00:05 UTC; Redis keys for real-time readsFiles Changed
src/database/migrations/20260818000000_create_leaderboard_and_streaks_tables.tssrc/services/collaborative-learning.service.tsvotePeerReviewsrc/jobs/leaderboardPrecompute.job.tssrc/jobs/streakTracking.job.tssrc/routes/collaborative-learning.routes.tssrc/routes/index.ts/collaborative-learningroutersrc/workers/scheduler.tssrc/workers/maintenance.worker.ts