Competitive LeetCode Progress Tracking for Teams
Features • Tech Stack • Setup • Deployment
LeetGrind is a competitive progress tracking platform for LeetCode teams. It automatically fetches daily statistics, calculates ranking points, and generates leaderboard snapshots for groups.
- Automated Daily Updates: Vercel cron jobs fetch LeetCode stats at midnight UTC
- Team Leaderboards: Track progress across groups with 5+ members
- Immutable History: Append-only architecture preserves complete statistical history
- Smart Scoring: Weighted algorithm considering problem difficulty, total solved, and global rank
- Google OAuth: Secure authentication via NextAuth v5
| Component | Implementation |
|---|---|
| Auth | NextAuth v5 (Google OAuth) |
| Cron Auth | x-vercel-cron header (prod) / Bearer ${CRON_SECRET} (test) |
| Rate Limiting | Upstash Redis sliding window (optional, disabled by default) |
| Batch Processing | 5 concurrent requests, 1s inter-batch delay, 5s timeout |
| Scoring | (total×10) + (easy×1) + (med×3) + (hard×5) + max(0, 5M-rank)/1000 |
rankingPoints = (totalSolved * 10)
+ (easySolved * 1)
+ (mediumSolved * 3)
+ (hardSolved * 5)
+ max(0, 5000000 - globalRank) / 1000Leaderboard sorted by:
- Ranking points (descending)
- Global rank (ascending, tiebreaker)
- Username (alphabetical, stability)
| Layer | Technology |
|---|---|
| Runtime | Node.js via Bun |
| Framework | Next.js 16 (App Router), React 19 |
| Database | PostgreSQL 16 |
| ORM | Prisma 7 (generated to src/generated/prisma) |
| Auth | NextAuth v5 (Google OAuth) |
| Styling | Tailwind CSS 4, shadcn/ui |
| Charts | Recharts 2 |
| Validation | Zod 4 |
| Rate Limiting | Upstash Redis + @upstash/ratelimit |
| Cron | Vercel Cron Jobs |
| Testing | Jest 30, React Testing Library |
| Deployment | Vercel |
- Bun 1.x or Node.js 20+
- PostgreSQL 16
- Docker (optional)
- Google OAuth credentials
- Clone and install:
git clone <repository-url>
cd leetgrind
bun install- Start PostgreSQL:
docker-compose up -d
# Wait for "healthy" status
docker-compose ps- Configure environment (copy
.env.exampleto.env):
DATABASE_URL="postgresql://postgres:password@localhost:5432/leetgrind"
NEXTAUTH_SECRET="<openssl rand -base64 32>"
GOOGLE_CLIENT_ID="<from Google Cloud Console>"
GOOGLE_CLIENT_SECRET="<from Google Cloud Console>"
CRON_SECRET="<openssl rand -base64 32>"- Setup database:
bunx prisma generate
bunx prisma migrate deploy- Run development server:
bun run dev
# → http://localhost:3000- Go to Google Cloud Console
- Create OAuth 2.0 credentials
- Add redirect URI:
http://localhost:3000/api/auth/callback/google - Copy Client ID and Client Secret to
.env
- Push repository to GitHub/GitLab/Bitbucket
- Import project to Vercel
- Configure environment variables in Vercel dashboard (same as
.env) - Ensure
CRON_SECRETis set (required even though Vercel usesx-vercel-cronheader) - Deploy (Vercel automatically detects
vercel.jsoncron configuration)
Cron jobs authenticate automatically via x-vercel-cron: 1 header in production.
# All tests (unit + API integration)
bun test
# Unit tests only
bun run test:unit
# Database integration tests (requires DATABASE_URL)
bun run test:db
# Watch mode
bun run test:watch
# Coverage
bun run test:coveragecurl -X POST http://localhost:3000/api/cron/update-stats \
-H "Authorization: Bearer ${CRON_SECRET}"| Decision | Rationale | Tradeoff |
|---|---|---|
| Immutable Snapshots | Never update DailyStat records; preserves historical accuracy, simplifies concurrency |
~1KB/profile/day storage (365MB/year for 1K profiles) |
| Batch Processing | 5 concurrent + 1s delay avoids LeetCode IP bans | Processes 100 profiles in ~25 seconds |
| Server Actions | Type-safe, auto-deduplication, less boilerplate | Less flexible for external API consumers |
| JSON Snapshots | Single-row queries instead of complex joins | Requires Zod validation at app layer |
| Optional Rate Limiting | Free tier friendly (Upstash 10K req/day) | Enable for >500 users or public deployments |
- No Official LeetCode API: Relies on undocumented GraphQL endpoint
- Username Changes: Break historical tracking (no UUID identity)
- UTC Normalization: Snapshots at midnight UTC (timezone-dependent)
- Daily Updates Only: No realtime stats (24h refresh cycle)
| Metric | Bottleneck | Mitigation |
|---|---|---|
| DB Connections | ~50 concurrent | Add ?connection_limit=20 to DATABASE_URL |
| Cron Timeout | 10 min (Vercel) | Shard by profile ID range |
| Storage | Multi-TB limit | Archive old snapshots (>90d) to S3 |
- Multi-provider auth (GitHub, Discord)
- Webhook notifications (Slack/Discord)
- Streak tracking
- Difficulty-specific leaderboards
- Public embeddable leaderboard widgets
⭐ Star on GitHub • 📖 Documentation • 🐛 Report Bug
MIT License • Built with Bun & Next.js