- revokePoints now accepts optional recordingId; when absent it deletes
one matching row (for actions like COMMENT_CREATE that have no recording)
- deleteComment queues revokePoints + checkAchievements so leaderboard
and social achievements stay in sync
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
PostgreSQL cannot resolve the type of a parameterized $1 = 0.005 in
-$1 * EXTRACT(EPOCH ...) and fails with an operator type error. Using
sql.raw() embeds the constant directly in the query string so userId
is the only parameter.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add migration 0004: partial unique index on user_points (user_id, action, recording_id)
for RECORDING_CREATE and RECORDING_FEATURED to prevent earn-on-republish farming
- Add revokePoints() to gamification lib; awardPoints() now uses onConflictDoNothing
- Add gamificationQueue (BullMQ) with 3-attempt exponential backoff
- Add gamification worker handling awardPoints, revokePoints, checkAchievements jobs
- Move all inline gamification calls in recordings + comments resolvers to queue
- Revoke RECORDING_CREATE points when a recording is unpublished (published → draft)
- Register gamification worker at server startup alongside mail worker
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>