Overview
LeaderboardService.getTopPlayers() and getTopBadgeHolders() in src/gamification/leaderboards/leaderboards.service.ts run an ORDER BY ... DESC aggregate over the full user-progress/badge tables on every request, and getTopPlayers(limit) passes the caller-supplied limit straight into take() with no upper bound. Leaderboards are read-heavy and change slowly, so recomputing the sort per request is wasteful, and an unbounded limit (e.g. ?limit=1000000) lets a caller force a full-table sort and large response — a cheap DoS.
Specifications
Tasks:
- Cache the top-N leaderboard in Redis with a short TTL and serve reads from cache; invalidate or let it expire on point/badge changes.
- Clamp
limit to a documented maximum (e.g. 100) and reject or coerce larger values.
- Ensure the ordering column is index-backed (coordinate with the gamification index work).
- Add tests for the max-limit clamp and for cache hit/miss behaviour.
Impacted Files:
src/gamification/leaderboards/leaderboards.service.ts
Acceptance Criteria
- Repeated leaderboard reads inside the TTL do not re-run the sort query.
- A caller cannot request more than the maximum number of rows.
- Tests cover the clamp and caching.
Overview
LeaderboardService.getTopPlayers()andgetTopBadgeHolders()insrc/gamification/leaderboards/leaderboards.service.tsrun anORDER BY ... DESCaggregate over the full user-progress/badge tables on every request, andgetTopPlayers(limit)passes the caller-suppliedlimitstraight intotake()with no upper bound. Leaderboards are read-heavy and change slowly, so recomputing the sort per request is wasteful, and an unboundedlimit(e.g.?limit=1000000) lets a caller force a full-table sort and large response — a cheap DoS.Specifications
Tasks:
limitto a documented maximum (e.g. 100) and reject or coerce larger values.Impacted Files:
src/gamification/leaderboards/leaderboards.service.tsAcceptance Criteria