Skip to content

feat: daily coding challenge system (#52)#115

Merged
BillChirico merged 4 commits intomainfrom
feat/daily-challenge
Feb 27, 2026
Merged

feat: daily coding challenge system (#52)#115
BillChirico merged 4 commits intomainfrom
feat/daily-challenge

Conversation

@BillChirico
Copy link
Collaborator

Daily Coding Challenge System

Closes #52

What's in this PR

Migration (migrations/011_challenges.cjs)

  • challenge_solves table with composite primary key (guild_id, challenge_index, user_id)
  • Index on guild_id for efficient leaderboard queries

Challenge Seed (src/data/challenges.json)

  • 32 problems: 10 easy, 12 medium, 8 hard
  • Classics: Two Sum, FizzBuzz, Palindrome, Binary Search, LRU Cache, Trapping Rain Water, LRU Cache, etc.
  • Each entry has: title, description, difficulty, hints, sampleInput, sampleOutput, languages

Challenge Scheduler (src/modules/challengeScheduler.js)

  • Timezone-aware day-of-year calculation via Intl.DateTimeFormat
  • Cycles challenges by dayOfYear % challenges.length
  • Posts embed to configured channel, auto-creates discussion thread
  • In-memory double-post prevention per guild
  • Plugged into existing 60s poll loop in scheduler.js

Challenge Embed

  • Color-coded by difficulty: 🟢 Easy (#57F287), 🟡 Medium (#FEE75C), 🔴 Hard (#ED4245)
  • Title: 🧩 Daily Challenge #<day> — <title>
  • Fields: Difficulty, Languages, Sample Input/Output (code blocks)
  • Footer: solve count + CTA
  • Buttons: 💡 Hint (ephemeral, shows all hints), ✅ Mark Solved

/challenge command (src/commands/challenge.js)

  • /challenge today — Re-posts today's challenge
  • /challenge streak — User's consecutive solve streak + total
  • /challenge leaderboard — Top 10 this week + all-time

Button Handlers (registered in src/modules/events.js)

  • challenge_solve_<index> — Upserts solve, replies with total count (ephemeral)
  • challenge_hint_<index> — Shows all hints ephemerally

Config

  • Added challenges: { enabled, channelId, postTime, timezone } to config.json
  • Added 'challenges' to SAFE_CONFIG_KEYS
  • Added "challenge": "everyone" to permissions.commands
  • Added Challenges panel to web dashboard

Tests

Test Files  98 passed (98)
     Tests  2075 passed | 1 skipped (2076)

Lint

Checked 203 files in 105ms. No fixes applied.

- Add migration 011 for challenge_solves table (guild_id, challenge_index, user_id, solved_at)
- Add 32 challenge seed problems (10 easy, 12 medium, 8 hard) covering classic CS problems
- Add challengeScheduler.js module with:
  - Timezone-aware time/date helpers
  - Daily challenge selection via day-of-year % challenges.length
  - Auto-posting embed with difficulty colors and discussion thread
  - Double-post prevention via in-memory date tracking
  - handleSolveButton / handleHintButton for button interactions
- Add /challenge command with today, streak, and leaderboard subcommands
- Register challenge_solve_ and challenge_hint_ button handlers in events.js
- Wire checkDailyChallenge into existing 60s scheduler poll loop
- Add challenges config section (enabled, channelId, postTime, timezone)
- Add 'challenges' to SAFE_CONFIG_KEYS allowlist
- Add 'challenge': 'everyone' to permissions.commands
- Add challenges config panel to web dashboard
- Add tests: 98 test files, 2075 tests passing, 0 lint errors

Closes #52
Copilot AI review requested due to automatic review settings February 27, 2026 20:34
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 27, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 42eafb7 and b330657.

📒 Files selected for processing (11)
  • config.json
  • migrations/011_challenges.cjs
  • src/api/utils/configAllowlist.js
  • src/commands/challenge.js
  • src/data/challenges.json
  • src/modules/challengeScheduler.js
  • src/modules/events.js
  • src/modules/scheduler.js
  • tests/commands/challenge.test.js
  • tests/modules/challengeScheduler.test.js
  • web/src/components/dashboard/config-editor.tsx

📝 Walkthrough

Summary by CodeRabbit

Release Notes

  • New Features

    • Added daily coding challenges system with /challenge today, /challenge streak, and /challenge leaderboard commands
    • Server admins can now configure challenges in settings: enable/disable, set channel, post time, and timezone
    • Automatic daily challenge posting to designated channels with solve tracking and leaderboards
  • Tests

    • Added comprehensive test coverage for challenge commands and scheduling

Walkthrough

Introduces a comprehensive daily coding challenge feature for Discord, including configuration sections, database schema, challenge data, scheduler with posting logic, slash command with subcommands for viewing challenges and leaderboards, event handlers for user interactions, and dashboard UI for configuration.

Changes

Cohort / File(s) Summary
Configuration
config.json, src/api/utils/configAllowlist.js
Adds top-level "challenges" config object with enabled, channelId, postTime, and timezone fields; adds "challenge" permission to allowedCommands; extends SAFE_CONFIG_KEYS to include "challenges".
Database
migrations/011_challenges.cjs
Creates challenge_solves table with guild_id, challenge_date, challenge_index, user_id, and solved_at columns; establishes composite primary key and guild_id index.
Challenge Data
src/data/challenges.json
Adds seed file containing coding challenge objects with title, description, difficulty, hints, sample input/output, and supported languages.
Command Implementation
src/commands/challenge.js
Implements /challenge slash command with three subcommands: today (displays current challenge with solve count), streak (computes consecutive-day solve streak), and leaderboard (shows weekly and all-time top solvers).
Scheduler & Events
src/modules/challengeScheduler.js, src/modules/scheduler.js, src/modules/events.js
Adds comprehensive challenge scheduler with timezone-aware daily posting to configured channels with discussion threads; integrates into polling scheduler; registers button interaction handlers for solve and hint actions with database tracking.
Tests
tests/commands/challenge.test.js, tests/modules/challengeScheduler.test.js
Comprehensive test coverage for command execution paths, streak and leaderboard computation, daily posting workflow, interaction handling, duplicate-post prevention, and timezone-aware date calculations.
Dashboard
web/src/components/dashboard/config-editor.tsx
Adds "Daily Coding Challenges" UI card to config editor with controls for enabling feature, setting channel ID, post time, and timezone; recognizes new challenges config section.

Possibly related PRs

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/daily-challenge

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@claude
Copy link

claude bot commented Feb 27, 2026

Claude encountered an error —— View job


I'll analyze this and get back to you.

Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary — 3 critical, 2 warnings, 1 nitpick

🔴 Critical

  1. row.total string/number type mismatch (src/commands/challenge.js:203) — PostgreSQL COUNT returns a string; strict !== 1 always evaluates true, so "1 solve" incorrectly displays as "1 solves"
  2. handleSolveButton missing bounds validation (src/modules/challengeScheduler.js:294) — Unlike handleHintButton, this function doesn't verify challengeIndex exists in the CHALLENGES array before inserting into the database, allowing invalid indices to pollute data
  3. Challenge system stagnates after 32 days (migrations/011_challenges.cjs:14) — The composite PK (guild_id, challenge_index, user_id) + ON CONFLICT DO NOTHING means once all 32 challenges cycle, re-solves are silently discarded. Streaks and leaderboards freeze permanently

🟡 Warning
4. Streak calculation ignores index wrap-around (src/commands/challenge.js:124-130) — Consecutive days spanning the 31→0 boundary produce a difference of 31, not 1, breaking the streak
5. AGENTS.md not updated — New files (challenge.js, challengeScheduler.js, challenges.json) and the challenge_solves table are missing from the Key Files and Database Tables sections

🔵 Nitpick
6. lastPostedDate Map never pruned (src/modules/challengeScheduler.js:34) — Grows unboundedly with guilds

Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary — 3 critical, 2 warnings, 1 nitpick

🔴 Critical

  1. row.total string/number type mismatch (src/commands/challenge.js:203) — PostgreSQL COUNT returns a string; strict !== 1 always evaluates true, so "1 solve" incorrectly displays as "1 solves"
  2. handleSolveButton missing bounds validation (src/modules/challengeScheduler.js:294) — Unlike handleHintButton, this function doesn't verify challengeIndex exists in the CHALLENGES array before inserting into the database, allowing invalid indices to pollute data
  3. Challenge system stagnates after 32 days (migrations/011_challenges.cjs:14) — The composite PK (guild_id, challenge_index, user_id) + ON CONFLICT DO NOTHING means once all 32 challenges cycle, re-solves are silently discarded. Streaks and leaderboards freeze permanently

🟡 Warning
4. Streak calculation ignores index wrap-around (src/commands/challenge.js:124-130) — Consecutive days spanning the 31→0 boundary produce a difference of 31, not 1, breaking the streak
5. AGENTS.md not updated — New files (challenge.js, challengeScheduler.js, challenges.json) and the challenge_solves table are missing from the Key Files and Database Tables sections

🔵 Nitpick
6. lastPostedDate Map never pruned (src/modules/challengeScheduler.js:34) — Grows unboundedly with guilds

@greptile-apps
Copy link

greptile-apps bot commented Feb 27, 2026

Greptile Summary

implemented daily coding challenge system with 32 problems, timezone-aware scheduler, solve tracking, and leaderboards

Major Changes:

  • database migration adds challenge_solves table with composite PK enabling date-based streak calculation and challenge re-solving when cycles repeat
  • scheduler integrated into existing 60s poll loop, posts challenges at configured time with difficulty-colored embeds and auto-creates discussion threads
  • /challenge command provides today/streak/leaderboard views with proper solve count aggregation
  • button handlers for hints (ephemeral) and solve marking (updates embed footer, tracks in DB)
  • web dashboard panel for channel/time/timezone configuration

Already Identified Issues:
The previous review thread correctly identified a timezone mismatch in the streak calculation (challenge.js:131-152) where UTC dates are used to compare against guild-timezone-stored challenge dates, causing incorrect streak detection for non-UTC guilds.

Confidence Score: 4/5

  • safe to merge with one known timezone bug affecting streak calculation for non-UTC guilds
  • comprehensive implementation with good test coverage (2075 tests passing), proper SQL injection prevention, bounds validation, and error handling; main issue is the timezone mismatch in streak calculation already identified in previous review threads
  • src/commands/challenge.js needs timezone fix in streak calculation (lines 131-152) to use guild timezone instead of UTC when comparing dates

Important Files Changed

Filename Overview
migrations/011_challenges.cjs created challenge_solves table with composite PK (guild_id, challenge_date, user_id) and index, enables date-based streak calculation
src/modules/challengeScheduler.js implemented timezone-aware scheduler with day-of-year cycling, auto-posting, thread creation, and button handlers for hints/solves
src/commands/challenge.js added /challenge command with today/streak/leaderboard subcommands; streak calculation has timezone mismatch with stored data
src/modules/events.js registered challenge button handler for solve/hint interactions with proper error handling

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[60s Scheduler Loop] -->|Check| B[checkDailyChallenge]
    B -->|For each guild| C{Challenges enabled?}
    C -->|Yes| D{Time to post?}
    C -->|No| E[Skip]
    D -->|Yes| F[postDailyChallenge]
    D -->|Not yet| E
    F --> G[Select challenge by dayOfYear mod length]
    G --> H[Send embed with buttons to channel]
    H --> I[Create discussion thread]
    
    J[User clicks Mark Solved] --> K[handleSolveButton]
    K --> L[Validate challenge index bounds]
    L --> M[Get guild timezone]
    M --> N[Upsert to challenge_solves table]
    N --> O[Update embed with new solve count]
    O --> P[Reply with user total solves]
    
    Q[User clicks Hint] --> R[handleHintButton]
    R --> S[Show hints ephemerally]
    
    T[Command: challenge streak] --> U[Query challenge_solves by date]
    U --> V[Calculate consecutive days]
    V --> W[Display streak and total]
    
    X[Command: challenge leaderboard] --> Y[Query top 10 this week]
    X --> Z[Query top 10 all-time]
    Y --> AA[Display leaderboards]
    Z --> AA
    
    AB[Web Dashboard] --> AC[Configure channel, time, timezone]
    AC --> AD[Save to config]
Loading

Last reviewed commit: b330657

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

11 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Implements a daily coding challenge feature set for the Discord bot, including scheduled posting, solve tracking, and dashboard/config support, backed by a new database table.

Changes:

  • Adds challenge_solves persistence plus a scheduler module to post daily challenge embeds with hint/solve buttons.
  • Introduces /challenge command (today/streak/leaderboard) and registers button interaction handlers.
  • Extends config surfaces (bot config.json, API allowlist, and web dashboard config editor) and adds Vitest coverage.

Reviewed changes

Copilot reviewed 11 out of 11 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
web/src/components/dashboard/config-editor.tsx Adds a “Daily Coding Challenges” section to the web dashboard editor and recognizes challenges as a config section.
tests/modules/challengeScheduler.test.js Adds unit tests for challenge scheduling helpers, embed/buttons, posting, and button handlers.
tests/commands/challenge.test.js Adds tests covering /challenge subcommands and DB error handling.
src/modules/scheduler.js Hooks daily challenge checks into the existing 60s scheduler poll loop.
src/modules/events.js Registers an interactionCreate handler for hint/solve buttons.
src/modules/challengeScheduler.js New module implementing daily selection logic, posting flow, and hint/solve button behavior.
src/data/challenges.json Adds the seeded challenge catalog used by the scheduler/command.
src/commands/challenge.js New /challenge command implementation (today/streak/leaderboard).
src/api/utils/configAllowlist.js Allowlists the new challenges config section for the API.
migrations/011_challenges.cjs Adds DB table for solve tracking and an index.
config.json Adds challenges config defaults and enables the challenge command permission.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

- fix: row.total from COUNT(*) is a pg bigint string; convert with Number()
  before pluralisation comparison in leaderboard formatter
- fix: add CHALLENGES[challengeIndex] bounds check to handleSolveButton
  (mirrors the guard already present in handleHintButton)
- fix: add solved_date DATE column to challenge_solves PK so challenges
  can be solved again after the 32-day modular cycle repeats; update
  ON CONFLICT clause to match new 4-column key

Fixes PRRT_kwDORICdSM5xQx3i, PRRT_kwDORICdSM5xQx5S, PRRT_kwDORICdSM5xQx8o
coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 27, 2026
Copy link

@claude claude bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Summary — 2 critical, 5 warnings, 3 nitpicks

🔴 Critical

  1. Column name mismatch breaks solve tracking at runtime (src/modules/challengeScheduler.js:312-317) — The INSERT references solved_date which doesn't exist (column is challenge_date), and the ON CONFLICT targets a non-existent constraint. Every "Mark Solved" button press will throw a PostgreSQL error.

  2. Streak calculation is fundamentally broken (src/commands/challenge.js:115-136) — Orders by challenge_index DESC and checks consecutive indices. Challenge indices don't correlate with temporal order (they're dayOfYear % 33), and wrap-around from 32→0 produces a difference of 32, not 1. Should use challenge_date for consecutive-day streak logic.

🟡 Warning

  1. Solve count queries not scoped to date (src/commands/challenge.js:73-76, src/modules/challengeScheduler.js:327-330) — Both count by challenge_index only without filtering by challenge_date. Once challenges cycle, counts will include historical solves from prior occurrences.

  2. Strict time equality risks missing daily post (src/modules/challengeScheduler.js:260) — currentTime !== postTime means if the 60s poll doesn't fire during the exact configured minute, the post is missed for the entire day. Use >= with the existing double-post guard.

  3. DST-sensitive date arithmetic (src/modules/challengeScheduler.js:57-63) — Date objects constructed from strings without timezone offset are parsed in server-local time. Around DST boundaries this can cause off-by-one in day-of-year. Use Date.UTC() instead.

  4. AGENTS.md not updated — New files, challenge_solves table, and challenge cycling behavior need to be documented per project conventions.

  5. Embed footer mentions "React ✅" (src/modules/challengeScheduler.js:148) — No reaction handler exists; only button handlers are wired. Misleading to users.

🔵 Nitpick

  1. Redundant index (migrations/011_challenges.cjs:29-32) — idx_challenge_solves_guild on (guild_id) is already covered by the PK prefix. A (guild_id, solved_at) composite would be more useful for leaderboard queries.

  2. Challenge count mismatch (src/data/challenges.json) — JSON has 33 entries but PR description says 32; test at challengeScheduler.test.js:209 also hardcodes % 32.

  3. lastPostedDate not persisted (src/modules/challengeScheduler.js:34) — Bot restart loses tracking, could double-post on the same day.


AI prompt to fix all issues
You are working on the `feat/daily-challenge` branch of VolvoxLLC/volvox-bot.

Fix the following issues in order:

1. **src/modules/challengeScheduler.js:312-317** — The INSERT statement references column `solved_date` which does not exist. The correct column name is `challenge_date`. Also the ON CONFLICT clause targets `(guild_id, challenge_index, user_id, solved_date)` which doesn't match the PK `(guild_id, challenge_date, user_id)`. Fix the INSERT to:
   INSERT INTO challenge_solves (guild_id, challenge_date, challenge_index, user_id)
   VALUES ($1, CURRENT_DATE, $2, $3)
   ON CONFLICT (guild_id, challenge_date, user_id) DO NOTHING

2. **src/commands/challenge.js:115-136** — Streak calculation orders by `challenge_index DESC` and checks consecutive indices. Replace with a query that selects `DISTINCT challenge_date ... ORDER BY challenge_date DESC` and computes streak from consecutive calendar dates (difference of 1 day between adjacent rows).

3. **src/commands/challenge.js:73-76** — Add `AND challenge_date = CURRENT_DATE` to the solve count WHERE clause.

4. **src/modules/challengeScheduler.js:327-330** — Add `AND challenge_date = CURRENT_DATE` to the solve count WHERE clause.

5. **src/modules/challengeScheduler.js:260** — Change `if (currentTime !== postTime) return;` to `if (currentTime < postTime) return;` so the post is not missed if the poll loop drifts past the exact minute.

6. **src/modules/challengeScheduler.js:57-63** — Replace the Date arithmetic with UTC-based math:
   const startOfYearUtcMs = Date.UTC(year, 0, 1);
   const localDateUtcMs = Date.UTC(year, month - 1, day);
   const diffMs = localDateUtcMs - startOfYearUtcMs;

7. **src/modules/challengeScheduler.js:148** — Change footer text from "React ✅ or click Mark Solved" to "Click ✅ Mark Solved when you've got it!"

8. **migrations/011_challenges.cjs:29-32** — Change the index to `ON challenge_solves(guild_id, solved_at)` for better leaderboard query support.

9. **src/data/challenges.json** — The file has 33 entries. Update the PR description to say 33 or remove one entry. Also fix test assertion at tests/modules/challengeScheduler.test.js:209 from `% 32` to `% CHALLENGES.length` or `% 33`.

10. **AGENTS.md** — Add to Key Files table: `src/commands/challenge.js` (Daily challenge command), `src/modules/challengeScheduler.js` (Challenge scheduler, embed builder, button handlers), `src/data/challenges.json` (Challenge seed data). Add to Database Tables: `challenge_solves` — Daily challenge solve tracking per guild/user/date. Add to Common Pitfalls: Challenge cycling — challenges repeat every `CHALLENGES.length` days via day-of-year modulo.

11. Update tests in tests/commands/challenge.test.js and tests/modules/challengeScheduler.test.js to match the corrected query shapes (challenge_date column, date-based streak, CURRENT_DATE filters).

Copilot AI review requested due to automatic review settings February 27, 2026 21:20
coderabbitai[bot]
coderabbitai bot previously approved these changes Feb 27, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 11 out of 11 changed files in this pull request and generated 4 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

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.

Daily coding challenge — auto-post puzzles with discussion threads

2 participants