Skip to content

Marjia029/coding-exercise-platform

Repository files navigation

Coding Exercise Platform

Full-stack coding exercise system with instructor authoring, async learner submissions, per-test evaluation, and JWT auth.

Features

  • Instructor and learner roles with JWT authentication
  • Exercise creation and editing with multi-language templates
  • Visible and hidden test cases
  • Asynchronous code execution via Celery + Redis — Run and Submit return immediately; results are polled by the frontend
  • Submission history and per-test result breakdown
  • Saved learner drafts in editor
  • Solved problem indicator and "My Solution" panel

Language Support

Language Template authoring Code execution
JavaScript Yes Yes (node:20-alpine)
Python Yes Yes (python:3.12-slim)
C++ Yes Yes (gcc:14)
Java Yes Yes (eclipse-temurin:21-jdk-alpine)

Tech Stack

Layer Technology
Backend Django 5.2 + Django REST Framework
Auth SimpleJWT (JWT access + refresh tokens)
Task queue Celery 5
Message broker / result backend Redis 7
Code execution Docker-out-of-Docker sandboxed containers
Frontend React 19 + Vite
Database SQLite (default)

How Async Execution Works

Why async?

Each code submission spawns one Docker container per test case. Container startup, compilation (C++/Java), and execution can take several seconds. Running this synchronously would block the Django request thread and time out under any load.

Architecture

Browser → POST /run or /submit
            │
            ▼
       Django view
       (validates, creates DB record for submit)
            │
            ▼ .delay()
       Celery task ──► Redis (broker)
            │
            ▼ (worker picks up task)
       CodeRunner
       (spawns Docker containers per test case)
            │
            ▼
       Results stored in Redis (run) or DB (submit)
            │
            ▼ polling
       Browser ◄── GET /tasks/{id}/ or GET /submissions/{id}/

Run flow (not persisted)

  1. POST /api/exercises/{id}/run/ → dispatches evaluate_run_task → returns {"task_id": "..."} (HTTP 202)
  2. Frontend polls GET /api/tasks/{task_id}/ every 500 ms
  3. When state == "SUCCESS", the full result payload is returned

Submit flow (persisted)

  1. POST /api/exercises/{id}/submit/ → creates CodingSubmission(status="queued") → dispatches evaluate_submission_task → returns the queued submission (HTTP 202)
  2. Frontend polls GET /api/submissions/{id}/ every 500 ms
  3. When status != "queued", the completed submission (passed/failed/error) is returned

Local Setup

Prerequisites

  • Python 3.12+
  • Node.js 20+
  • Docker Desktop (running)
  • Redis 7 — install via one of:
    • Windows (WSL2): sudo apt install redis-server && redis-server
    • macOS: brew install redis && brew services start redis
    • Docker: docker run -d -p 6379:6379 redis:7-alpine

1 — Clone and configure environment

# Copy the example env file
cp .env.example .env

The defaults in .env.example work for local development without changes.

2 — Backend

# Create and activate virtual environment
python -m venv venv
venv\Scripts\activate          # Windows
# source venv/bin/activate     # macOS/Linux

# Install dependencies (includes celery[redis])
pip install -r requirements.txt

# Run database migrations
python backend/manage.py migrate

# (Optional) Seed demo users
python backend/manage.py seed_demo
# Creates: instructor / pass123  and  learner / pass123

# Start Django dev server
python backend/manage.py runserver
# → http://127.0.0.1:8000

3 — Celery worker (separate terminal)

The Celery worker processes code execution tasks. It must be running for Run and Submit to complete.

venv\Scripts\activate
cd backend
celery -A config worker --loglevel=info

You should see output like:

[tasks]
  . coding.tasks.evaluate_run_task
  . coding.tasks.evaluate_submission_task

[2024-...] celery@hostname ready.

4 — Frontend

cd frontend
npm install
npm run dev
# → http://127.0.0.1:5173

Summary of running processes

Process Command Port
Redis redis-server (or Docker) 6379
Django API python backend/manage.py runserver 8000
Celery worker celery -A config worker --loglevel=info (from backend/)
React dev server npm run dev (from frontend/) 5173

Docker Setup (all services in one command)

Docker Compose starts Redis, Django, the Celery worker, and pre-pulls all language runtime images automatically.

docker-compose up --build

Services started:

Service Description
redis Redis 7 broker + result backend
web Django API on port 8000
celery Celery worker (concurrency 2), shares Docker socket
pull-images One-shot init that pre-pulls all language Docker images

Both web and celery mount /var/run/docker.sock so they can spawn code-runner containers on the host (Docker-out-of-Docker).

The Redis URL is injected automatically by docker-compose via environment overrides — no changes to .env needed.


Running Tests

venv\Scripts\activate
python backend/manage.py test coding

Tests run with Celery in eager mode (tasks execute synchronously in-process, no broker needed) and CodeRunner.run_code mocked, so no Redis or Docker is required to run the test suite.


API Overview

Auth

POST /api/auth/register/   body: {username, password, role}
POST /api/auth/login/      body: {username, password}
POST /api/auth/refresh/    body: {refresh}

Instructor

GET  POST   /api/instructor/exercises/
GET  PATCH  DELETE  /api/instructor/exercises/{id}/
GET  POST   /api/instructor/exercises/{id}/testcases/
GET  PATCH  DELETE  /api/instructor/exercises/{id}/testcases/{tc_id}/

Learner

GET   /api/exercises/
GET   /api/exercises/{id}/
POST  /api/exercises/{id}/run/          → {task_id}  (HTTP 202)
POST  /api/exercises/{id}/submit/       → queued submission  (HTTP 202)
GET   /api/exercises/{id}/submissions/
GET   /api/submissions/{id}/            poll until status != "queued"
GET   /api/tasks/{task_id}/             poll until state == "SUCCESS"

Project Structure

backend/
  config/
    celery.py          ← Celery app
    settings.py        ← Django + Celery/Redis config
    urls.py
  coding/
    models.py
    serializers.py
    views.py           ← run/submit dispatch tasks (HTTP 202)
    tasks.py           ← evaluate_submission_task, evaluate_run_task
    permissions.py
    urls.py
    tests.py
    services/
      code_runner.py   ← Docker sandbox runner
    management/
      commands/
        seed_demo.py
frontend/
  src/
    api.js             ← fetch wrapper + pollTask() + pollSubmission()
    pages/
      LearnerExercisePage.jsx   ← async polling for run/submit
      ...
docker/
  runners/             ← per-language stub Dockerfiles
docker-compose.yml
Dockerfile
requirements.txt
.env.example

Environment Variables

Copy .env.example.env before running locally.

Variable Default Description
DJANGO_SECRET_KEY (change this) Django/JWT secret
DJANGO_DEBUG True Debug mode
DJANGO_ALLOWED_HOSTS localhost,127.0.0.1 Allowed hosts
DJANGO_TIME_ZONE UTC Timezone
DB_ENGINE django.db.backends.sqlite3 Database backend
DB_NAME backend/db.sqlite3 Database path
JWT_ACCESS_MINUTES 30 Access token lifetime
JWT_REFRESH_DAYS 7 Refresh token lifetime
CELERY_BROKER_URL redis://localhost:6379/0 Redis broker
CELERY_RESULT_BACKEND redis://localhost:6379/0 Redis result store

Security Notes

  • .env is git-ignored — never commit real secrets
  • .env.example is committed as a safe template
  • Code runner containers have no network, read-only FS, 128 MB RAM cap, 0.5 CPU cap, and all Linux capabilities dropped
  • The Docker-out-of-Docker setup is demo-only — for production use a purpose-built sandbox service (e.g. Firecracker, gVisor, or a managed code execution API)

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors