Skip to content

Code-master-pragyan/judge-engine

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

11 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation


         

A self-hosted competitive programming platform built for institutions.
Host timed contests · Judge submissions in real-time · Rank participants live

Overview · Features · Architecture · Get Started · Database · API · Roadmap



✦ Overview

VRDCT is a full-stack competitive programming platform for colleges, bootcamps, and coding clubs. Unlike public platforms — you own everything. Your data, your contests, your judge pipeline.

Register → Join Contest → Write Code → Get Judged → See Leaderboard

Two question modes, real-time verdict, partial scoring, and a live leaderboard with podium highlights for top 3.


✦ Features

🎓 For Students
Feature Details
📋 Registration Modal Name, class, roll number — blurred backdrop popup
⏱️ Live Countdown Contest timer, red pulse animation in final 2 minutes
🖊️ Monaco Editor VS Code-grade editor — Python, Java, C++
🐛 Bug Fix Mode Admin-provided buggy code pre-loaded per language
💾 Code Persistence Code saved per question + per language in localStorage
▶️ Custom Test Runner Run against custom stdin before final submit
🏁 Submit All One-click final submission — locks score, shows results
📊 Personal Results Partial credit, progress bars, accuracy breakdown
🏆 Leaderboard Full rankings with gold / silver / bronze podium
🛠️ For Admins
Feature Details
Inline Contest Builder Create contest + questions + test cases in a single form
🐛 Bug Fix Questions Per-language buggy starter code per question
🧪 Test Case Builder Add/remove cases, mark samples, side-by-side I/O
🖼️ Banner Upload Cloudinary image upload directly from the browser
🕐 TZ-Aware Scheduling Times stored with offset — no UTC shift bugs
📝 Draft Mode Build contests privately, publish when ready
🎯 Contest Dashboard Live / upcoming / ended status at a glance

✦ Architecture

System Overview

graph TD
    User(["👤 User / Admin"])

    subgraph Frontend ["Frontend — React 18 + Vite"]
        UI["Pages & Components"]
        SC["Supabase Client"]
        AX["Axios API Client"]
    end

    subgraph Backend ["Backend — Node.js + Express"]
        AUTH["Auth Controller\n/register  /login"]
        JUDGE["Judge Controller\n/run  /submit  /finalize"]
        MW["JWT Middleware"]
    end

    subgraph Services ["External Services"]
        DB[("Supabase\nPostgreSQL")]
        SAND["Code Sandbox\ncompile → run → cleanup"]
        CDN["Cloudinary\nImage Storage"]
    end

    User --> UI
    UI --> SC
    UI --> AX
    SC --> DB
    AX --> MW --> AUTH
    AX --> MW --> JUDGE
    AUTH --> DB
    JUDGE --> DB
    JUDGE --> SAND
    UI --> CDN

    style User fill:#1a1a2e,stroke:#6c63ff,color:#fff
    style Frontend fill:#0f1b35,stroke:#6c63ff,color:#ccc
    style Backend fill:#0f1b35,stroke:#6c63ff,color:#ccc
    style Services fill:#0f1b35,stroke:#14a085,color:#ccc
    style DB fill:#0d4f4f,stroke:#14a085,color:#fff
    style SAND fill:#2d1b69,stroke:#6c63ff,color:#fff
    style CDN fill:#1a2744,stroke:#3448C5,color:#fff
Loading

Submission Flow

sequenceDiagram
    actor S as Student
    participant UI as React UI
    participant API as Express API
    participant DB as PostgreSQL
    participant J as Code Sandbox

    S->>UI: Click Submit
    UI->>API: POST /submit/:questionId
    API->>DB: Check registration
    API->>DB: Check contest is live
    API->>DB: Fetch test cases
    API->>J: Compile code (once)
    loop Each test case
        J->>J: Run with input
        J-->>API: output + timeMs
    end
    API->>DB: Save submission + verdict
    API-->>UI: verdict · passedCount · totalCount
    UI-->>S: Show result
Loading

Contest Lifecycle

stateDiagram-v2
    direction LR
    [*] --> Draft : Admin creates
    Draft --> Upcoming : Published
    Upcoming --> Live : start_time reached
    Live --> Finalized : Student submits all
    Live --> Ended : end_time reached
    Finalized --> Ended : end_time reached
    Ended --> [*]
Loading

✦ Tech Stack

Layer Technology
Frontend React 18, Vite, TailwindCSS, React Router v6
Editor Monaco Editor (@monaco-editor/react)
Backend Node.js, Express.js
Database Supabase (PostgreSQL)
Auth Custom JWT stored in localStorage
Media Cloudinary — unsigned upload preset
Code Execution Custom sandbox — compile once, run per test case
Code Persistence localStorage keyed by contest · questionId · lang

✦ Project Structure

vrdct/
├── frontend/
│   └── src/
│       ├── pages/
│       │   ├── Contests.jsx              ← listing + registration modal
│       │   ├── ContestDetail.jsx         ← countdown + side nav + editor
│       │   ├── ProblemSolvingView.jsx    ← Monaco + judge + localStorage
│       │   ├── Leaderboard.jsx           ← podium + full ranked table
│       │   └── AdminDashboard.jsx        ← contest + question builder
│       ├── context/
│       │   └── AuthContext.jsx           ← JWT auth state
│       └── utils/
│           ├── time.js                   ← timezone-safe parsing
│           └── uploadToCloudinary.js     ← browser-side upload
│
└── backend/
    ├── controllers/
    │   ├── authController.js
    │   └── judgeController.js            ← run · create · submit · finalize
    ├── middleware/
    │   └── authMiddleware.js             ← verifyToken · verifyAdmin
    ├── utils/
    │   ├── judge.js                      ← sandbox pipeline
    │   └── verdict.js                    ← checkAllTestCases
    └── routes/
        ├── auth.js
        └── judge.js

✦ Getting Started

Prerequisites

node >= 18     npm >= 9

Accounts: Supabase · Cloudinary


1 · Clone

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

2 · Backend

cd backend && npm install

backend/.env

PORT=5000
JWT_SECRET=your_jwt_secret

DB_HOST=db.xxxxxxxxxxxx.supabase.co
DB_PORT=5432
DB_NAME=postgres
DB_USER=postgres
DB_PASSWORD=your_db_password

3 · Frontend

cd frontend && npm install

frontend/.env

VITE_API_URL=http://localhost:5000
VITE_SUPABASE_URL=https://xxxxxxxxxxxx.supabase.co
VITE_SUPABASE_ANON_KEY=your_anon_key
VITE_CLOUDINARY_CLOUD_NAME=your_cloud_name
VITE_CLOUDINARY_UPLOAD_PRESET=contest_images

4 · Database

Run the schema below in your Supabase SQL editor.

5 · First admin

UPDATE users SET role = 'admin' WHERE email = 'your@email.com';

6 · Run

# Terminal 1 — backend
cd backend && npm run dev

# Terminal 2 — frontend
cd frontend && npm run dev

Open http://localhost:5173


✦ Database

Click to expand full SQL schema
CREATE TABLE users (
  id            UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  username      VARCHAR(50)  UNIQUE NOT NULL,
  email         VARCHAR(255) UNIQUE NOT NULL,
  password_hash VARCHAR(255) NOT NULL,
  role          VARCHAR(20)  DEFAULT 'student' CHECK (role IN ('student','admin')),
  created_at    TIMESTAMP DEFAULT NOW()
);

CREATE TABLE contests (
  id           UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  title        VARCHAR(255) NOT NULL,
  description  TEXT,
  image_url    TEXT,
  start_time   TIMESTAMPTZ NOT NULL,
  end_time     TIMESTAMPTZ NOT NULL,
  created_by   UUID REFERENCES users(id),
  is_published BOOLEAN DEFAULT false,
  created_at   TIMESTAMP DEFAULT NOW()
);

CREATE TABLE contest_questions (
  id            UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  contest_id    UUID REFERENCES contests(id) ON DELETE CASCADE,
  title         VARCHAR(255) NOT NULL,
  description   TEXT NOT NULL,
  question_type VARCHAR(20) DEFAULT 'normal'
                CHECK (question_type IN ('normal','bug_fix')),
  starter_code  JSONB,        -- { "python": "...", "java": "...", "cpp": "..." }
  time_limit_ms INTEGER DEFAULT 2000,
  order_index   INTEGER DEFAULT 0,
  created_at    TIMESTAMPTZ DEFAULT NOW()
);

CREATE TABLE contest_test_cases (
  id              UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  question_id     UUID REFERENCES contest_questions(id) ON DELETE CASCADE,
  input           TEXT DEFAULT '',
  expected_output TEXT NOT NULL,
  is_sample       BOOLEAN DEFAULT false,
  order_index     INTEGER DEFAULT 0
);

CREATE TABLE contest_registrations (
  id            UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  contest_id    UUID REFERENCES contests(id) ON DELETE CASCADE,
  user_id       UUID REFERENCES users(id)    ON DELETE CASCADE,
  full_name     VARCHAR(255) NOT NULL,
  class         VARCHAR(100) NOT NULL,
  roll_no       VARCHAR(50)  NOT NULL,
  registered_at TIMESTAMP DEFAULT NOW(),
  final_score   INTEGER DEFAULT 0,
  submitted_at  TIMESTAMP,
  UNIQUE (contest_id, user_id)
);

CREATE TABLE submissions (
  id           UUID DEFAULT gen_random_uuid() PRIMARY KEY,
  question_id  UUID REFERENCES contest_questions(id) ON DELETE CASCADE,
  contest_id   UUID REFERENCES contests(id)          ON DELETE CASCADE,
  user_id      UUID REFERENCES users(id)             ON DELETE CASCADE,
  language     VARCHAR(20) NOT NULL
               CHECK (language IN ('python','java','cpp')),
  code         TEXT NOT NULL,
  verdict      VARCHAR(30) NOT NULL,
  passed_count INTEGER DEFAULT 0,
  total_count  INTEGER DEFAULT 0,
  time_ms      INTEGER DEFAULT 0,
  created_at   TIMESTAMPTZ DEFAULT NOW()
);

✦ API

Auth

Method Endpoint Description
POST /api/auth/register Register new user
POST /api/auth/login Login — returns JWT

Judge

Method Endpoint Auth Description
POST /api/judge/run Token Run code against custom input
POST /api/judge/contest Admin Create contest + questions in one transaction
POST /api/judge/submit/:questionId Token Submit + judge code
POST /api/judge/contest/:contestId/finalize Token Lock final score
GET /api/judge/submission/:id Token Fetch a submission
Submit — request & response

Request

POST /api/judge/submit/:questionId

{
  "code": "print(int(input()) * 2)",
  "language": "python",
  "contestId": "uuid-here"
}

Response

{
  "submissionId": "uuid",
  "finalVerdict": "AC",
  "passedCount": 5,
  "totalCount": 5,
  "testCaseDetails": [
    { "testCase": 1, "verdict": "AC", "timeMs": 42 },
    { "testCase": 2, "verdict": "AC", "timeMs": 38 }
  ]
}

Verdict Reference

Code Meaning
AC Accepted — all test cases passed
WA Wrong Answer
TLE Time Limit Exceeded
CE Compile Error
RE Runtime Error

✦ Scoring

Each question   →  100 pts max
Partial credit  →  floor( passed / total × 100 )
Final score     →  best submission per question, summed
Tiebreaker      →  earlier submission time wins
Student Q1 Q2 Q3 Total Rank
Arjun 100 80 100 280 🥇
Priya 100 100 60 260 🥈
Rahul 80 60 100 240 🥉

✦ Design Decisions

Why contest-scoped questions instead of a global problem bank?
Each contest owns its questions. No pollution between contests, no accidental reuse, simpler permission model.
Why localStorage for code persistence?
Monaco remounts when switching questions. localStorage gives instant restoration per contestId + questionId + language — zero API calls, zero latency.
Why JSONB for starter_code?
Bug Fix mode needs different buggy code per language. A single JSONB column { "python": "", "java": "", "cpp": "" } handles this cleanly without extra tables.
Why TIMESTAMPTZ instead of TIMESTAMP?
TIMESTAMPTZ compares correctly against NOW() (UTC) regardless of how the client generated the string. This makes the live-check query — start_time <= NOW() AND end_time >= NOW() — reliable across all timezones.

✦ Roadmap

  • Realtime leaderboard via Supabase subscriptions
  • Plagiarism detection — cross-submission similarity scoring
  • ICPC mode — penalty time scoring
  • Admin analytics — pass rate and time distribution per question
  • Per-student submission history
  • Export leaderboard as CSV / PDF
  • Email notifications for contest start
  • Mobile-responsive editor

✦ Contributing

git checkout -b feature/your-feature
git commit -m "feat: describe your change"
git push origin feature/your-feature
# Open a Pull Request

Commit convention: feat · fix · refactor · docs · style


✦ License

MIT — use it, ship it, build on it.



VRDCT — built with obsession over details.

Drop a ⭐ if this helped you.

About

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages