Skip to content

rahulgavhar/codezen

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

25 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

⚡ CodeZen

A full-stack, production-grade competitive programming & technical interview platform

Node.js React Supabase Redis Socket.io GraphQL Azure


CodeZen is a high-performance, full-stack platform for competitive programming and technical hiring. It combines a VSCode-like code editor, a scalable real-time contest engine, a peer-to-peer WebRTC interview suite, and an AI-powered job recommendation system — all engineered with production-grade reliability in mind.


📋 Table of Contents


✨ Feature Highlights

🏆 Competitive Programming Contests

A complete contest system designed to handle real-time events at scale.

  • Contest Lifecycle: Staff create contests with custom problems, time limits, memory limits, and point values. Contests automatically transition between Upcoming → Live → Ended states.
  • Problem Sets: Each contest problem is a versioned snapshot with its own description, I/O format, constraints, and scoring. Problems map to a global problem bank.
  • Participant Registration: Users must register before a contest starts. Registrations are closed once the contest begins.
  • Contest Submission Flow: A participant submits code from within the contest editor. The submission is dispatched to Judge0, judged asynchronously via webhook, and the result is automatically linked back to the contest leaderboard.
  • Base Submission Sync: Practice submissions made on the same problems during the contest window are auto-synced into contest results — so if a user solves a problem in the IDE, the verdict carries over.

⚡ Real-Time Leaderboard (Redis ZSET)

The leaderboard is powered entirely by Redis Sorted Sets — no polling, no cron jobs, no database scans.

  • Scores are encoded as a composite integer: solved_count × 1,000,000 - penalty_minutes.
  • This single score value encodes both the number of problems solved and the time penalty, ensuring correct ordering in a single ZSET.
  • Penalty is computed as time_since_contest_start + (wrong_attempts × 10 minutes) per problem.
  • Rankings are fetched using ZREVRANKO(log N) regardless of participant count.
  • The my_standing feature uses an additional ZREVRANK call to pinpoint the current user's exact rank without fetching the entire leaderboard.
  • For live contests, rankings come from Redis. For ended/upcoming contests, rankings are computed from the database and sorted in-memory.

⏪ Keystroke-Level Code Replay

Watch how any contestant solved a problem — event by event, keystroke by keystroke.

Capture Pipeline:

Monaco Editor → Socket.io (replay:events) → In-Memory Buffer → Background Worker → Supabase Storage
  1. Initialization: When a user opens a contest problem, replay:init creates a timeline_id and a contest_replays DB row.
  2. Event Capture: Every Monaco editor change is captured as { seq, ts, op, pos, char }. Events are batched every 500ms or 50 events and sent via Socket.io.
  3. In-Memory Aggregation: The server holds events in a per-timeline Map<seq, event> buffer, deduplicated by sequence number.
  4. Background Flusher: A setInterval worker fires every 2 seconds (configurable). It flushes buffers that have ≥500 events or have been idle for the flush interval to Supabase Storage as named JSON chunks: contest/{contestId}/timeline/{timelineId}/chunk_{startSeq}_{endSeq}.json.
  5. Finalization: On accepted submission or contest end, the buffer is force-flushed and the timeline is marked is_finalized = true — making it immutable.
  6. Playback: The frontend fetches all chunks, merges events sorted by seq, and reconstructs the code state from scratch by applying operations in order, with play/pause/seek controls.

Guarantees: Strict sequence ordering, idempotent duplicate detection, graceful shutdown flush.

🎙️ Live Technical Interviews (WebRTC + Collaborative Editor)

A full interview suite for staff to evaluate candidates in real-time.

  • Scheduling: Staff schedule interviews with a candidate, a time window, and an optional problem. A unique room_id is auto-generated.
  • WebRTC Signaling: Peer-to-peer video/audio is established over Socket.io as the signaling channel. The server forwards offer, answer, and ice-candidate events between the two sockets.
  • Collaborative Code Editor: The candidate's keystrokes are broadcast via code-change events. The interviewer receives them in real-time as a read-only observer. A code-sync-request / code-sync-response handshake enables the interviewer to request a full code snapshot at any point.
  • Screen Sharing: Participants can share their screen. screen-share-started / screen-share-stopped events notify the other party.
  • Audio/Video Toggles: Mute/unmute and camera on/off state is signaled to the other participant.
  • Connection Tracking: candidate_connected and interviewer_connected DB flags are kept in sync, so the UI always reflects who is in the room.
  • Interview Status Machine: Interviews transition Scheduled → Ongoing → Completed. Completion can be triggered by the interviewer or automatically when end_time has passed.
  • Post-Interview Evaluation: Staff can submit feedback, candidate_rating (1-5), and technical_score after the interview concludes.
  • Code Submissions: In-session code runs are tracked per interview.

💻 Full-Featured Code Editor & Standalone IDE

  • Monaco Editor: VSCode-grade editing experience with syntax highlighting, multi-language support, minimap, and configurable themes.
  • Sample Test Runner: Before submitting, users can run their code against sample inputs synchronously. The server submits to Judge0, polls for up to 60 seconds, and returns the result.
  • Full Submission: All test cases are submitted to Judge0 in parallel with a 60-second overall timeout. Results arrive asynchronously via Judge0 webhooks.
  • Standalone IDE (/ide): A freeform code runner that accepts custom stdin. Useful for practicing outside problem constraints.
  • Problem Detail View: Tabbed interface with problem statement, constraints, examples, submissions history, and an integrated editor side-by-side.

🧩 Asynchronous Code Execution (Judge0 + Webhook)

User submits → DB record created (pending) → Test cases fetched from Supabase Storage
  → All test cases submitted to Judge0 in parallel → Webhook fires per test case
    → Per-test verdict aggregated → Final submission verdict emitted
  • Test case input/output files are stored in Supabase Storage and read on-demand during submission.
  • Each test case gets its own Judge0 token, tracked in a test_results JSONB column.
  • Webhooks are received at PUT /api/webhooks/judge0. The server uses a per-submission async lock (submissionWebhookLocks) to prevent race conditions when multiple test cases finish simultaneously.
  • Intermediate verdicts are computed and persisted as results arrive, so the UI shows live progress.
  • A Stuck Test Poller is scheduled after 15 seconds if a submission remains pending — it polls Judge0 directly for any test cases that missed their webhook.
  • C++ compilation errors encoded in base64 by Judge0 are automatically decoded before being stored.

🤖 AI-Powered Job Recommendations

Users can upload their resume (PDF/DOC) to unlock personalized job recommendations.

  • Resumes are uploaded to Supabase Storage under resumes/{clerkUserId}/{timestamp}-{filename}.
  • Three external API calls are made in parallel: /upload-resume, /analyze-resume, /get-recommendations.
  • Skills extracted from all three responses are de-duplicated, normalized, and persisted on the user profile.
  • On subsequent visits, the latest stored resume is fetched from Supabase and re-sent to the recommendation API without re-uploading.
  • Only users with app_role = 'user' can access this feature (staff are excluded).

☁️ Azure VM Orchestration (Cost-Optimized Execution)

Judge0 runs on an Azure VM that is managed dynamically to avoid idle cloud costs.

  • Auto-Start on Request: Every authenticated API request passes through trackActivityAndStartVM middleware. If the VM is deallocated or stopped, it is started via the Azure ARM Compute SDK non-blocking in the background.
  • Execution Guard: ensureVMReadyForExecution() blocks the code submission until the VM reaches running state (up to 180 seconds, polling every 5 seconds).
  • Auto-Shutdown Cron: A node-cron job runs every 5 hours (0 */5 * * *). It reads the user's last_active_at timestamp from the DB. If no activity for ≥5 hours, the VM is stopped and deallocated.
  • Activity Tracking: Every authenticated request fires updateUserActivity() to refresh the last_active_at timestamp (non-blocking, no request delay).

👤 User Profiles & Progress Tracking

  • Onboarding Flow: New users are routed to /onboarding to set a username, role (user or staff), and company name (for staff). app_role can only be set during onboarding when it is NULL.
  • Profile Stats: Problems solved, contests participated, current rating, max rating.
  • Activity Heatmap: GitHub-style contribution heatmap showing daily activity.
  • Rating History: Line chart of rating changes over time.
  • Public Profiles: Viewable by anyone at /profile/:username. Staff have separate public profiles at /staff/:staffId.
  • Skills: Extracted from resumes and displayed on the profile.

📊 Rich Dashboards

User Dashboard:

  • Submission statistics (accepted, wrong answer, runtime error, etc.)
  • Language breakdown pie chart
  • Recent accepted submissions
  • Contest participation history

Staff Dashboard:

  • Overview of all managed contests
  • Candidate pipeline with interview statuses
  • Quick-access to schedule interviews and create contests

🏗️ Architecture

System Overview

┌─────────────────────────────────────────────────────────────────────┐
│                          CLIENT (React + Vite)                       │
│  React Router v7 │ Redux Toolkit │ Monaco Editor │ Socket.io Client  │
└──────────────────────────────┬──────────────────────────────────────┘
                               │  REST + GraphQL + WebSocket
┌──────────────────────────────▼──────────────────────────────────────┐
│                      SERVER (Express 5 + Node.js)                    │
│                                                                       │
│  ┌─────────────┐  ┌──────────────┐  ┌─────────────────────────────┐ │
│  │  REST API   │  │   GraphQL    │  │     Socket.io Namespaces     │ │
│  │  (Express)  │  │  (Apollo 5)  │  │  WebRTC Signaling           │ │
│  └──────┬──────┘  └──────┬───────┘  │  Contest Replay Signaling   │ │
│         │                │          └─────────────┬───────────────┘ │
│  ┌──────▼────────────────▼──────────────────────▼───────────────┐  │
│  │                     Service Layer                              │  │
│  │  contest.service │ submissions.service │ interview.service     │  │
│  │  contestReplay.service │ users.service │ vm.service           │  │
│  └──────┬────────────────────────────────────────────────────────┘  │
│         │                                                             │
│  ┌──────▼────────────────────────────────────────────────────────┐  │
│  │                   Repository Layer (SQL via postgres)          │  │
│  └──────┬────────────────────────────────────────────────────────┘  │
└─────────┼───────────────────────────────────────────────────────────┘
          │
   ┌──────▼──────────────────────────────────────────────┐
   │                 External Services                     │
   │  ┌──────────┐  ┌───────┐  ┌───────┐  ┌───────────┐ │
   │  │ Supabase │  │ Redis │  │Judge0 │  │  Azure VM │ │
   │  │ Postgres │  │ ZSET  │  │  API  │  │  ARM SDK  │ │
   │  │ Storage  │  │       │  │       │  │           │ │
   │  └──────────┘  └───────┘  └───────┘  └───────────┘ │
   └─────────────────────────────────────────────────────┘

Code Replay Pipeline (Detailed)

┌──────────────┐      replay:events      ┌──────────────────────┐
│ Monaco Editor│ ──────────────────────► │  Socket.io Handler   │
│ (500ms batch)│                         │  appendContestReplay  │
└──────────────┘                         │  Events()            │
                                         └──────────┬───────────┘
                                                    │  writes to
                                         ┌──────────▼───────────┐
                                         │  In-Memory Buffer    │
                                         │  Map<seq, event>     │
                                         └──────────┬───────────┘
                                                    │  every 2s or 500 events
                                         ┌──────────▼───────────┐
                                         │  Background Worker   │
                                         │  flushReplayBuffers  │
                                         │  Tick()              │
                                         └──────────┬───────────┘
                                                    │  uploads chunk JSON
                                         ┌──────────▼───────────┐
                                         │  Supabase Storage    │
                                         │  chunk_000001_000500 │
                                         │        .json         │
                                         └──────────────────────┘

Submission Execution Flow

POST /api/submissions
  │
  ├── ensureVMReadyForExecution() ─► polls Azure VM state → starts if needed
  │
  ├── createSubmission() in DB (verdict: 'pending')
  │
  ├── fetch test cases from Supabase Storage
  │
  ├── for each test case:
  │     └── POST to Judge0 with callback_url → receives token
  │
  ├── store all tokens in test_results JSONB
  │
  └── return submission to client (pending)

  [Later, async]
  PUT /api/webhooks/judge0
    │
    ├── withSubmissionWebhookLock(submissionId, ...)
    │     ├── find test case by token in test_results JSONB
    │     ├── update test case verdict
    │     ├── compute intermediate overall verdict
    │     └── if all tests done → compute final verdict → mark solved
    │
    └── if still pending after 15s → Stuck Test Poller polls Judge0 directly

🛠️ Tech Stack

Frontend

Technology Purpose
React 19 UI framework with concurrent features
Vite 7 Build tool with HMR
React Router v7 Client-side routing with role-based guards
Redux Toolkit + Redux Persist Global state management with localStorage persistence
Tailwind CSS v4 + DaisyUI Utility-first styling with component library
@monaco-editor/react VSCode-grade embedded code editor
Socket.io Client WebSockets for real-time events (replay, interviews, leaderboard)
@clerk/clerk-react Authentication UI and session management
Chart.js + react-chartjs-2 Submission stats charts
react-calendar-heatmap GitHub-style activity heatmap
axios HTTP client for REST API calls
react-hot-toast Toast notifications
Lucide React + React Icons Icon libraries

Backend

Technology Purpose
Node.js (ESM) JavaScript runtime
Express 5 HTTP server with async error handling
Apollo Server 5 GraphQL server
@as-integrations/express5 Apollo–Express integration
Socket.io 4 WebSocket server for real-time features
@clerk/express Clerk authentication middleware (clerkMiddleware())
Supabase JS SDK Database queries and Storage operations
postgres Low-level PostgreSQL client for raw SQL queries
ioredis Redis client for ZSET leaderboard and caching
@azure/arm-compute + @azure/identity Azure VM power management
groq-sdk Groq LLM API integration
node-cron Cron job scheduler (VM auto-shutdown)
multer Multipart form-data for resume uploads
axios HTTP client for Judge0 and external APIs
dotenv Environment variable management
cors, cookie-parser HTTP middleware

Infrastructure & External Services

Service Role
Supabase (PostgreSQL) Primary relational database
Supabase Storage Blob storage for test cases, replay chunks, and resumes
Redis Leaderboard ZSET, in-flight data
Judge0 Sandboxed code execution engine (self-hosted on Azure VM)
Azure VM (ARM) Compute host for Judge0; dynamically started/stopped
Clerk Authentication, user management, webhooks for user sync
Groq API LLM inference for AI features
Job Recommendation API External microservice for resume parsing and job matching

📁 Project Structure

codezen/
├── client/                          # React frontend (Vite)
│   └── src/
│       ├── App.jsx                  # Root router with RBAC guards
│       ├── main.jsx                 # React + Redux + Clerk bootstrap
│       ├── pages/
│       │   ├── HomePage.jsx         # Landing page
│       │   ├── Dashboard.jsx        # User stats dashboard
│       │   ├── ProblemsPage.jsx     # Problem listing
│       │   ├── ProblemDetail.jsx    # Problem view + editor
│       │   ├── CodeEditor.jsx       # Full-page code editor
│       │   ├── Ide.jsx              # Standalone IDE (freeform runner)
│       │   ├── Interview.jsx        # Live interview room (WebRTC)
│       │   ├── AllInterviews.jsx    # User's interview list
│       │   ├── JobRecommendations.jsx # Resume upload + job matching
│       │   ├── MySubmissions.jsx    # Submission history
│       │   ├── Submission.jsx       # Single submission detail
│       │   ├── Onboarding.jsx       # New user role selection
│       │   ├── MyProfile.jsx        # Authenticated user profile
│       │   ├── PublicProfile.jsx    # Public user profile
│       │   ├── contest/
│       │   │   ├── AllContests.jsx          # Contest listing
│       │   │   ├── Contest.jsx              # Contest info / registration
│       │   │   ├── OngoingContest.jsx       # Live contest dashboard
│       │   │   ├── ContestProblemDetail.jsx # Contest problem + editor + replay
│       │   │   └── CodeReplay.jsx           # Replay viewer
│       │   └── staff/
│       │       ├── StaffDashboard.jsx       # Staff overview
│       │       ├── StaffContests.jsx        # Contest list
│       │       ├── StaffCreateContest.jsx   # Create contest form
│       │       ├── StaffContestDetail.jsx   # Manage contest / leaderboard
│       │       ├── StaffInterviews.jsx      # Interview pipeline
│       │       ├── StaffScheduleInterview.jsx # Schedule interview form
│       │       ├── StaffInterviewDetail.jsx # View interview / evaluate
│       │       ├── StaffProfile.jsx         # Staff edit profile
│       │       └── StaffPublicProfile.jsx   # Staff public profile
│       ├── components/
│       ├── redux/                   # Redux slices & store
│       ├── lib/                     # Axios instances, helpers
│       └── utils/                   # Utility functions
│
├── server/                          # Express backend
│   └── src/
│       ├── server.js                # Entry point: Express + Apollo + Socket.io
│       ├── config/
│       │   ├── env.config.js        # Typed env variable access
│       │   ├── supabase.client.js   # Supabase JS client singleton
│       │   └── redis.client.js      # ioredis client with graceful close
│       ├── routes/
│       │   ├── problems.route.js    # GET /api/problems (public)
│       │   ├── contests.route.js    # /api/contests (CRUD + leaderboard)
│       │   ├── submissions.route.js # /api/submissions (create, run, list)
│       │   ├── users.route.js       # /api/users (profile, resume, jobs)
│       │   ├── interviews.route.js  # /api/interviews (CRUD)
│       │   ├── interview_problems.route.js
│       │   ├── webhooks.route.js    # PUT /api/webhooks/judge0
│       │   └── judge0.route.js      # GET /api/judge0 (health check)
│       ├── controllers/
│       ├── services/
│       │   ├── contest.service.js       # Contest CRUD, leaderboard logic
│       │   ├── contestReplay.service.js # In-memory replay buffers + flush worker
│       │   ├── submissions.service.js   # Submission pipeline, webhook aggregation
│       │   ├── interview.service.js     # Interview CRUD + status machine
│       │   ├── judge0.service.js        # Judge0 API wrapper
│       │   ├── users.service.js         # Profile, resume, job recommendations
│       │   └── vm.service.js            # Azure VM start/stop + activity tracking
│       ├── repositories/
│       │   ├── contests.repo.js
│       │   ├── submissions.repo.js
│       │   ├── interviews.repo.js
│       │   ├── users.repo.js
│       │   ├── problems.repo.js
│       │   ├── contestReplay.repo.js
│       │   └── vms.repo.js
│       ├── graphql/
│       │   └── schema.js            # Apollo typeDefs + resolvers (meProfile)
│       ├── lib/
│       │   ├── webrtc.signaling.js      # Socket.io WebRTC relay
│       │   └── contestReplay.signaling.js # Socket.io replay event ingestion
│       ├── middleware/
│       │   └── auth.middleware.js   # trackActivityAndStartVM
│       └── jobs/
│           └── vmSync.job.js        # node-cron every 5h: auto-shutdown
│
└── context/                         # Engineering design docs
    ├── CodeReplay.md                # Replay system spec
    └── LiveLeaderboard.md           # Leaderboard design spec

🔌 API Reference

REST Endpoints

Method Endpoint Auth Description
GET /api/health No Health check
GET /api/problems No List all problems
GET /api/problems/:id No Get problem by ID
POST /api/submissions Yes Create a new submission
POST /api/submissions/run Yes Run against sample input
GET /api/submissions Yes Get user's submissions
GET /api/submissions/:id Yes Get single submission
GET /api/contests Yes List all contests
POST /api/contests Staff Create a contest
GET /api/contests/:id Yes Get contest details
GET /api/contests/:id/problems Yes Get contest problems
POST /api/contests/:id/register Yes Register for a contest
GET /api/contests/:id/leaderboard Yes Get paginated leaderboard
POST /api/contests/:id/submissions Yes Submit to contest problem
POST /api/contests/:id/replay/init Yes Initialize replay timeline
POST /api/contests/:id/replay/:timelineId/finalize Yes Finalize replay
GET /api/users/profile Yes Get own profile
PUT /api/users/profile Yes Update profile
GET /api/users/:username No Get public profile
POST /api/users/resume Yes Upload resume + extract skills
GET /api/users/jobs/recommendations Yes Get job recommendations
GET /api/interviews Yes Get user's interviews
POST /api/interviews Staff Create/schedule an interview
GET /api/interviews/:id Yes Get interview details
PUT /api/interviews/:id Staff Update interview (feedback, score)
PUT /api/webhooks/judge0 No Judge0 async execution callback
GET /api/judge0 No Judge0 health status

GraphQL Endpoint

Endpoint Query Description
POST /graphql meProfile Get authenticated user's full profile

Socket.io Events

WebRTC Interview Signaling:

Event (Emit) Event (Listen) Description
join-interview participant-joined Join interview room
offer offer Forward WebRTC SDP offer
answer answer Forward WebRTC SDP answer
ice-candidate ice-candidate Forward ICE candidate
toggle-audio participant-audio-toggled Notify audio state change
toggle-camera participant-camera-toggled Notify video state change
code-change code-changed Broadcast code delta to interviewer
code-sync-request code-sync-request Request full code snapshot
code-sync-response code-synced Deliver full code snapshot
screen-share-started participant-screen-sharing Notify screen share state
end-call participant-left Leave interview room

Contest Replay Signaling:

Event (Emit) Description
replay:init Initialize a new replay timeline
replay:events Append a batch of keystroke events
replay:flush Force-flush the in-memory buffer to storage
replay:finalize Finalize and lock the timeline

⚙️ Configuration

Server — server/.env

# Core
PORT=3000
NODE_ENV=development
FRONTEND_URL=http://localhost:5173

# PostgreSQL (Supabase)
DATABASE_URL=postgresql://<user>:<password>@<host>:5432/<database>

# Supabase
SUPABASE_URL=https://<project-ref>.supabase.co
SUPABASE_SERVICE_ROLE_KEY=<supabase-service-role-key>

# Clerk Auth
CLERK_PUBLISHABLE_KEY=<clerk-publishable-key>
CLERK_SECRET_KEY=<clerk-secret-key>

# Judge0 Code Execution
JUDGE_SERVER_URL=http://<judge-host>:8080
JUDGE_AUTH_TOKEN=<judge-x-auth-token>

# Webhook URL for Judge0 callbacks (must be publicly reachable)
PUBLIC_BACKEND_URL=https://yourdomain.com
JUDGE_CALLBACK_URL=  # optional override

# Redis
REDIS_URL=redis://localhost:6379

# Azure VM (for Judge0 host)
SUBSCRIPTION_ID=<azure-subscription-id>
TENANT_ID=<azure-tenant-id>
CLIENT_ID=<azure-client-id>
CLIENT_SECRET=<azure-client-secret>
RESOURCE_GROUP=<azure-resource-group>
VM_NAME=<azure-vm-name>

# AI
GROQ_API_KEY=<groq-api-key>

# Optional
MAX_REQUEST_BODY_LIMIT=10mb
REPLAY_FLUSH_INTERVAL_MS=2000
REPLAY_FLUSH_EVENT_THRESHOLD=500
REPLAY_STORAGE_BUCKET=contest_submission_events
RESUMES_STORAGE_BUCKET=resumes
JOB_RECOMMENDATION_API_URL=https://your-job-api.example.com

Client — client/.env

VITE_CLERK_PUBLISHABLE_KEY=<clerk-publishable-key>
VITE_API_BASE_URL=http://localhost:3000
VITE_SUPABASE_URL=https://<project-ref>.supabase.co
VITE_SUPABASE_ANON_KEY=<supabase-anon-key>

🚀 Getting Started

Prerequisites

  • Node.js v18 or higher
  • Redis (local via Docker or a managed cloud instance)
  • Supabase project (PostgreSQL + Storage)
  • Clerk application (get publishable + secret keys)
  • Judge0 instance (self-hosted or cloud) — must be reachable by the backend

1. Clone the Repository

git clone https://github.com/yourusername/codezen.git
cd codezen

2. Set Up the Server

cd server
cp .env.example .env
# Fill in all values in .env
npm install
npm run dev

The server starts on http://localhost:3000 and automatically:

  • Connects to Redis and Supabase
  • Starts the Apollo GraphQL server at /graphql
  • Initializes WebRTC and Replay Socket.io namespaces
  • Starts the replay flush worker
  • Registers the VM sync cron job (production only)

3. Set Up the Client

cd client
# Create .env with your Clerk and Supabase keys
npm install
npm run dev

Vite serves the frontend at http://localhost:5173.

4. Required Supabase Setup

Create the following Storage buckets in your Supabase project:

  • contest_submission_events — for replay timeline JSON chunks
  • resumes — for user-uploaded resume files
  • A bucket for test case files (input/output for each problem)

Ensure your Supabase Row Level Security (RLS) policies restrict replay and submission access to the owning user.


🔐 Authentication & RBAC

Authentication is handled by Clerk. Every authenticated request includes a JWT validated by @clerk/express's clerkMiddleware().

Role app_role value Access
Regular User 'user' Problems, contests, IDE, interviews (as candidate), job recommendations
Staff 'staff' All of the above + contest management, interview scheduling, candidate evaluation
Unauthenticated null Homepage, problems listing, public profiles

Key Rules:

  • app_role is set once during onboarding (when the DB value is NULL) and is immutable afterwards.
  • Staff cannot participate in contests as contestants.
  • Staff dashboard, contest creation, and interview management routes are guarded at both the frontend router level (App.jsx) and backend service layer (assertStaff()).

🧠 Core Design Decisions

Why Redis ZSET for Leaderboards?

A relational ORDER BY query over thousands of submissions is O(N log N) and degrades under high concurrency. Redis ZSET's ZADD and ZREVRANK are both O(log N) and atomic — no locks, no transactions, no cache invalidation issues. The composite score (solved × 1M) - penalty fits in a single float, making complex multi-key sorting unnecessary.

Why In-Memory Buffers for Replay (Not a Message Queue)?

The replay system uses in-process Map buffers rather than Redis Streams to avoid the operational overhead of consumer groups and the latency of round-trips on every keystroke. The periodic flush worker is a lightweight setInterval, and graceful shutdown flushes all buffers synchronously before process.exit(). This is the right trade-off for a monolithic deployment.

Why Per-Submission Async Locks for Webhooks?

Judge0 fires one webhook per test case. Multiple test cases for the same submission finish nearly simultaneously, creating a race between concurrent DB reads and writes. A JavaScript Promise-chain lock (zero overhead, no external dependency) serializes these updates, preventing torn writes to the test_results JSONB column.

Why Webhook + Stuck Poller Instead of Only Polling?

Webhooks deliver results immediately with no polling overhead. But webhooks can be lost under network failures. The Stuck Test Poller is a safety net: 15 seconds after a partial update, if the submission is still pending, the server polls Judge0 directly for any tokens that went quiet. This gives both speed and reliability.


📜 License

This project is licensed under the ISC License.


Built from the ground up with ❤️ — every line intentional, every feature production-ready.

About

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

No releases published

Packages

 
 
 

Contributors