Latest commit

History

History
322 lines (243 loc) · 12.4 KB

File metadata and controls

322 lines (243 loc) · 12.4 KB

CodeBox

CodeBox is a full-stack online coding judge. Users can browse programming problems, write solutions in Python, Java, or C++, run code against sample or custom inputs, submit solutions against hidden test cases, and review their submission history. Administrators can create problems, define function signatures, and manage test cases from the web UI.

The project is a microservice monorepo built with Spring Boot, React, PostgreSQL, Kafka, and Docker-based execution sandboxes.

Features

  • Email/password signup and login with role-based JWT authentication
  • Public problem catalog with generated starter code
  • Admin-only problem and test-case management
  • Monaco-based browser code editor
  • Python 3.12, Java 21, and C++17 judging
  • Synchronous runs against sample or custom inputs
  • Asynchronous submissions against all test cases
  • Per-user submission history and verdict polling
  • Docker Compose development environment
  • Kubernetes manifests and a Jenkins-to-ArgoCD GitOps deployment flow

Tech Stack

LayerTechnologies
FrontendReact 19, Vite, React Router, Zustand, Axios, Monaco Editor, Tailwind CSS
BackendJava 21, Spring Boot 3.5, Spring Web, Spring Data JPA
Data and messagingPostgreSQL 16, Kafka 3.7
AuthenticationJWT (HS256), BCrypt password hashing
Judge sandboxDocker containers with network, memory, CPU, and time limits
DeliveryDocker Compose, Kubernetes/Kustomize, Jenkins, ArgoCD

System Design

High-Level Architecture

flowchart LR
User[Browser] --> Frontend[React frontend<br/>Nginx or Vite]
Frontend -->|/api/auth/*| Auth[Auth Service :8081]
Frontend -->|/api/problems/*<br/>/api/admin/*| Problem[Problem Service :8085]
Frontend -->|/api/submissions/*| Submission[Submission Service :8082]
Auth --> DB[(PostgreSQL)]
Problem --> DB
Submission --> DB
Submission -->|HTTP: Run| Judge[Judge Service :8084]
Submission -->|submission.created| Kafka[(Kafka)]
Kafka --> Judge
Judge -->|Internal HTTP:<br/>judge bundle| Problem
Judge -->|submission.judged| Kafka
Kafka --> Submission
Judge --> Docker[Isolated runtime containers<br/>Python / Java / C++]
Loading

The frontend is the public entry point. Its Nginx configuration, or the Vite development proxy, routes /api requests to the appropriate backend service. Backend services communicate over HTTP for immediate requests and Kafka for asynchronous submission processing.

Although the three stateful services currently connect to one PostgreSQL database, each service owns its own tables:

ServiceResponsibilityState
auth-serviceSignup, login, admin seeding, JWT issuance, and /auth/meusers
problem-serviceProblems, function signatures, starter code, sample cases, hidden cases, and admin APIsproblems, test_cases
submission-serviceUser-facing run/submit APIs, submission lifecycle, history, and verdict persistencesubmissions
judge-serviceHarness generation, sandboxed code execution, output parsing, and gradingStateless
frontend-serviceReact SPA and path-based API reverse proxyStateless

Authentication and Authorization

  1. auth-service verifies credentials and issues an HS256 access token containing the user ID, email, and role.
  2. The frontend stores the token in local storage and sends it as Authorization: Bearer <token>.
  3. auth-service, problem-service, and submission-service independently validate the token using the same secret, issuer, and audience.
  4. Public problem reads expose sample cases only. Admin problem APIs require the ADMIN role.
  5. The judge fetches hidden cases from problem-service through /internal/problems/{id}/judge-bundle, guarded by a separate internal service token.

Run Flow

The Run action is synchronous, is not persisted, and evaluates either user-provided custom inputs or the problem's public sample cases.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant P as Problem Service
participant J as Judge Service
participant D as Docker Sandbox
UI->>S: POST /submissions/run
S->>P: GET /problems/{id}
P-->>S: Signature, starter metadata, sample cases
S->>J: POST /run
J->>D: Compile/run generated harness + user code
D-->>J: stdout, stderr, exit status
J-->>S: Verdict and case outputs
S-->>UI: Immediate run result
Loading

Submit Flow

The Submit action is asynchronous. It persists a queued submission, publishes an event, and returns 202 Accepted. The frontend polls the submission API until a final verdict is available.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant K as Kafka
participant J as Judge Service
participant P as Problem Service
participant D as Docker Sandbox
UI->>S: POST /submissions
S->>S: Persist status = QUEUED
S->>K: codebox.submission.created
S-->>UI: 202 Accepted + submission ID
K->>J: Consume submission
J->>P: GET internal judge bundle
P-->>J: Signature + all test cases
J->>D: Compile/run in isolated container
D-->>J: Execution result
J->>K: codebox.submission.judged
K->>S: Consume verdict
S->>S: Persist final status
UI->>S: GET /submissions/{id}
S-->>UI: Final verdict
Loading

Judge and Sandbox

The judge generates a language-specific harness that invokes the submitted function for every test case and emits a JSON result array. Outputs are compared using JSON equivalence.

Each execution runs in a temporary Docker container with:

  • No network access
  • Configurable CPU and memory limits
  • A configurable execution timeout
  • Separate stdout and stderr capture
  • Cleanup after process exit with docker run --rm

Runtime images:

LanguageImageCommand
Pythonpython:3.12-alpinepython main.py
Javaeclipse-temurin:21-alpinejavac Main.java && java -cp . Main
C++gcc:13g++ -O2 -std=c++17 ...

Docker Compose gives the judge access to the host Docker socket. Kubernetes uses a privileged Docker-in-Docker sidecar with a persistent image cache.

Data Model

  • A User has an email, BCrypt password hash, and USER or ADMIN role.
  • A Problem stores its description, difficulty, tags, function signature, supported languages, and generated starter code.
  • A TestCase stores positional input arguments and expected output as JSONB, plus sample visibility and ordering.
  • A Submission stores the user, problem, language, source code, status, runtime, score, failure details, and timestamps.

Submission states are QUEUED, ACCEPTED, WRONG_ANSWER, RUNTIME_ERROR, COMPILE_ERROR, TIME_LIMIT_EXCEEDED, and INTERNAL_ERROR.

Design Tradeoffs

  • Kafka decouples user-facing submission creation from slower code execution and allows the judge to scale independently.
  • The current persist-then-publish flow does not use a transactional outbox. A process failure between those operations can leave a submission queued without an event.
  • Kafka delivery may be repeated, so verdict application is naturally overwrite-safe, but the judge can still execute a duplicate event more than once.
  • Sharing one PostgreSQL instance simplifies local operation, but separate databases or schemas would provide stronger service isolation.
  • Docker provides practical execution isolation for this project, but access to the host Docker socket and privileged Docker-in-Docker are high-trust capabilities that require careful production hardening.

Repository Layout

.
├── frontend/ # React SPA, Nginx proxy, frontend Dockerfile
├── services/
│ ├── auth-service/ # Identity and JWTs
│ ├── problem-service/ # Problem catalog and test cases
│ ├── submission-service/ # Runs, submissions, and history
│ └── judge-service/ # Sandboxed execution and grading
├── scripts/ # Local runner, stop script, and E2E test
├── k8s/ # Kustomize manifests and deployment guide
├── argocd/ # ArgoCD Application
├── docker-compose.yml # Complete local stack
├── Jenkinsfile # Changed-service image build and tag bump
└── pom.xml # Maven multi-module parent

Getting Started

Prerequisites

  • Docker with Docker Compose
  • JDK 21 and Maven for host-based backend development
  • Node.js 20+ and npm for host-based frontend development
  • Python 3 for the end-to-end test

Option 1: Run Everything with Docker Compose

From the repository root:

docker compose up --build

Open http://localhost:8080.

The default local admin account is:

Email: admin@codebox.dev
Password: admin12345

Stop the stack with:

docker compose down

The first judge request can be slower while Docker downloads the language runtime image.

Option 2: Run Applications on the Host

This mode runs PostgreSQL and Kafka in Docker, the Spring services as local Java processes, and the frontend with Vite:

scripts/run-local.sh

Open http://localhost:5173. Logs are written to .local-run/logs/.

scripts/stop-local.sh
scripts/stop-local.sh --infra # also stop PostgreSQL and Kafka

Use SKIP_BUILD=1 scripts/run-local.sh to reuse existing service JARs.

Local Ports

ComponentPort
Frontend, Docker Compose8080
Frontend, Vite5173
Auth service8081
Submission service8082
Judge service8084
Problem service8085
PostgreSQL5432
Kafka host listener29092

API Overview

All frontend requests use the /api prefix, which the frontend proxy removes before forwarding.

Method and pathPurposeAccess
POST /auth/signupCreate a userPublic
POST /auth/loginIssue an access tokenPublic
GET /auth/meReturn the authenticated userAuthenticated
GET /problemsList problemsPublic
GET /problems/{id}Get problem details and sample casesPublic
/admin/problems/**Manage problems and test casesAdmin
POST /submissions/runRun against sample or custom inputsAuthenticated
POST /submissionsQueue a judged submissionAuthenticated
GET /submissions/{id}Get an owned submissionAuthenticated
GET /submissionsList the current user's submissionsAuthenticated

Configuration

Important environment variables:

VariableUsed byPurpose
DB_URL, DB_USERNAME, DB_PASSWORDStateful backend servicesPostgreSQL connection
JWT_SECRETAuth, problem, submissionShared HS256 secret; must be at least 32 bytes
JWT_ISSUER, JWT_AUDIENCEAuth, problem, submissionJWT validation boundary
ADMIN_SEED_EMAIL, ADMIN_SEED_PASSWORDAuthBootstrap administrator
INTERNAL_TOKENProblemProtect hidden-case endpoint
PROBLEM_SERVICE_INTERNAL_TOKENJudgeAuthenticate to hidden-case endpoint
KAFKA_BOOTSTRAP_SERVERSSubmission, judgeKafka connection
JUDGE_EXECUTION_TIMEOUTJudgePer-execution timeout, for example PT30S
JUDGE_CONTAINER_MEMORY, JUDGE_CONTAINER_CPUSJudgeSandbox resource limits

The defaults in docker-compose.yml are for local development only. Replace all secrets before deploying.

Build and Test

Build all backend services:

mvn clean package

Build the frontend:

cd frontend
npm install
npm run build

With the full stack running, execute the end-to-end test:

python3 scripts/e2e_test.py

The E2E test creates a problem and user, verifies authorization boundaries, runs custom and sample inputs, submits correct and incorrect Python/Java/C++ solutions, polls verdicts, checks history, and removes the test problem.

Deployment

The production-oriented manifests under k8s/ deploy the five application services to a k3s cluster while PostgreSQL and Kafka run externally on the VM.

The delivery flow is:

Git push
-> Jenkins detects changed services
-> builds and pushes only affected images
-> updates image tags in k8s/kustomization.yaml
-> commits the tag change
-> ArgoCD syncs and self-heals the cluster

Secrets are intentionally excluded from Kustomize and ArgoCD. See k8s/README.md for the complete cluster setup and operational notes.

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

Latest commit

History

History
322 lines (243 loc) · 12.4 KB

File metadata and controls

322 lines (243 loc) · 12.4 KB

CodeBox

CodeBox is a full-stack online coding judge. Users can browse programming problems, write solutions in Python, Java, or C++, run code against sample or custom inputs, submit solutions against hidden test cases, and review their submission history. Administrators can create problems, define function signatures, and manage test cases from the web UI.

The project is a microservice monorepo built with Spring Boot, React, PostgreSQL, Kafka, and Docker-based execution sandboxes.

Features

  • Email/password signup and login with role-based JWT authentication
  • Public problem catalog with generated starter code
  • Admin-only problem and test-case management
  • Monaco-based browser code editor
  • Python 3.12, Java 21, and C++17 judging
  • Synchronous runs against sample or custom inputs
  • Asynchronous submissions against all test cases
  • Per-user submission history and verdict polling
  • Docker Compose development environment
  • Kubernetes manifests and a Jenkins-to-ArgoCD GitOps deployment flow

Tech Stack

LayerTechnologies
FrontendReact 19, Vite, React Router, Zustand, Axios, Monaco Editor, Tailwind CSS
BackendJava 21, Spring Boot 3.5, Spring Web, Spring Data JPA
Data and messagingPostgreSQL 16, Kafka 3.7
AuthenticationJWT (HS256), BCrypt password hashing
Judge sandboxDocker containers with network, memory, CPU, and time limits
DeliveryDocker Compose, Kubernetes/Kustomize, Jenkins, ArgoCD

System Design

High-Level Architecture

flowchart LR
User[Browser] --> Frontend[React frontend<br/>Nginx or Vite]
Frontend -->|/api/auth/*| Auth[Auth Service :8081]
Frontend -->|/api/problems/*<br/>/api/admin/*| Problem[Problem Service :8085]
Frontend -->|/api/submissions/*| Submission[Submission Service :8082]
Auth --> DB[(PostgreSQL)]
Problem --> DB
Submission --> DB
Submission -->|HTTP: Run| Judge[Judge Service :8084]
Submission -->|submission.created| Kafka[(Kafka)]
Kafka --> Judge
Judge -->|Internal HTTP:<br/>judge bundle| Problem
Judge -->|submission.judged| Kafka
Kafka --> Submission
Judge --> Docker[Isolated runtime containers<br/>Python / Java / C++]
Loading

The frontend is the public entry point. Its Nginx configuration, or the Vite development proxy, routes /api requests to the appropriate backend service. Backend services communicate over HTTP for immediate requests and Kafka for asynchronous submission processing.

Although the three stateful services currently connect to one PostgreSQL database, each service owns its own tables:

ServiceResponsibilityState
auth-serviceSignup, login, admin seeding, JWT issuance, and /auth/meusers
problem-serviceProblems, function signatures, starter code, sample cases, hidden cases, and admin APIsproblems, test_cases
submission-serviceUser-facing run/submit APIs, submission lifecycle, history, and verdict persistencesubmissions
judge-serviceHarness generation, sandboxed code execution, output parsing, and gradingStateless
frontend-serviceReact SPA and path-based API reverse proxyStateless

Authentication and Authorization

  1. auth-service verifies credentials and issues an HS256 access token containing the user ID, email, and role.
  2. The frontend stores the token in local storage and sends it as Authorization: Bearer <token>.
  3. auth-service, problem-service, and submission-service independently validate the token using the same secret, issuer, and audience.
  4. Public problem reads expose sample cases only. Admin problem APIs require the ADMIN role.
  5. The judge fetches hidden cases from problem-service through /internal/problems/{id}/judge-bundle, guarded by a separate internal service token.

Run Flow

The Run action is synchronous, is not persisted, and evaluates either user-provided custom inputs or the problem's public sample cases.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant P as Problem Service
participant J as Judge Service
participant D as Docker Sandbox
UI->>S: POST /submissions/run
S->>P: GET /problems/{id}
P-->>S: Signature, starter metadata, sample cases
S->>J: POST /run
J->>D: Compile/run generated harness + user code
D-->>J: stdout, stderr, exit status
J-->>S: Verdict and case outputs
S-->>UI: Immediate run result
Loading

Submit Flow

The Submit action is asynchronous. It persists a queued submission, publishes an event, and returns 202 Accepted. The frontend polls the submission API until a final verdict is available.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant K as Kafka
participant J as Judge Service
participant P as Problem Service
participant D as Docker Sandbox
UI->>S: POST /submissions
S->>S: Persist status = QUEUED
S->>K: codebox.submission.created
S-->>UI: 202 Accepted + submission ID
K->>J: Consume submission
J->>P: GET internal judge bundle
P-->>J: Signature + all test cases
J->>D: Compile/run in isolated container
D-->>J: Execution result
J->>K: codebox.submission.judged
K->>S: Consume verdict
S->>S: Persist final status
UI->>S: GET /submissions/{id}
S-->>UI: Final verdict
Loading

Judge and Sandbox

The judge generates a language-specific harness that invokes the submitted function for every test case and emits a JSON result array. Outputs are compared using JSON equivalence.

Each execution runs in a temporary Docker container with:

  • No network access
  • Configurable CPU and memory limits
  • A configurable execution timeout
  • Separate stdout and stderr capture
  • Cleanup after process exit with docker run --rm

Runtime images:

LanguageImageCommand
Pythonpython:3.12-alpinepython main.py
Javaeclipse-temurin:21-alpinejavac Main.java && java -cp . Main
C++gcc:13g++ -O2 -std=c++17 ...

Docker Compose gives the judge access to the host Docker socket. Kubernetes uses a privileged Docker-in-Docker sidecar with a persistent image cache.

Data Model

  • A User has an email, BCrypt password hash, and USER or ADMIN role.
  • A Problem stores its description, difficulty, tags, function signature, supported languages, and generated starter code.
  • A TestCase stores positional input arguments and expected output as JSONB, plus sample visibility and ordering.
  • A Submission stores the user, problem, language, source code, status, runtime, score, failure details, and timestamps.

Submission states are QUEUED, ACCEPTED, WRONG_ANSWER, RUNTIME_ERROR, COMPILE_ERROR, TIME_LIMIT_EXCEEDED, and INTERNAL_ERROR.

Design Tradeoffs

  • Kafka decouples user-facing submission creation from slower code execution and allows the judge to scale independently.
  • The current persist-then-publish flow does not use a transactional outbox. A process failure between those operations can leave a submission queued without an event.
  • Kafka delivery may be repeated, so verdict application is naturally overwrite-safe, but the judge can still execute a duplicate event more than once.
  • Sharing one PostgreSQL instance simplifies local operation, but separate databases or schemas would provide stronger service isolation.
  • Docker provides practical execution isolation for this project, but access to the host Docker socket and privileged Docker-in-Docker are high-trust capabilities that require careful production hardening.

Repository Layout

.
├── frontend/ # React SPA, Nginx proxy, frontend Dockerfile
├── services/
│ ├── auth-service/ # Identity and JWTs
│ ├── problem-service/ # Problem catalog and test cases
│ ├── submission-service/ # Runs, submissions, and history
│ └── judge-service/ # Sandboxed execution and grading
├── scripts/ # Local runner, stop script, and E2E test
├── k8s/ # Kustomize manifests and deployment guide
├── argocd/ # ArgoCD Application
├── docker-compose.yml # Complete local stack
├── Jenkinsfile # Changed-service image build and tag bump
└── pom.xml # Maven multi-module parent

Getting Started

Prerequisites

  • Docker with Docker Compose
  • JDK 21 and Maven for host-based backend development
  • Node.js 20+ and npm for host-based frontend development
  • Python 3 for the end-to-end test

Option 1: Run Everything with Docker Compose

From the repository root:

docker compose up --build

Open http://localhost:8080.

The default local admin account is:

Email: admin@codebox.dev
Password: admin12345

Stop the stack with:

docker compose down

The first judge request can be slower while Docker downloads the language runtime image.

Option 2: Run Applications on the Host

This mode runs PostgreSQL and Kafka in Docker, the Spring services as local Java processes, and the frontend with Vite:

scripts/run-local.sh

Open http://localhost:5173. Logs are written to .local-run/logs/.

scripts/stop-local.sh
scripts/stop-local.sh --infra # also stop PostgreSQL and Kafka

Use SKIP_BUILD=1 scripts/run-local.sh to reuse existing service JARs.

Local Ports

ComponentPort
Frontend, Docker Compose8080
Frontend, Vite5173
Auth service8081
Submission service8082
Judge service8084
Problem service8085
PostgreSQL5432
Kafka host listener29092

API Overview

All frontend requests use the /api prefix, which the frontend proxy removes before forwarding.

Method and pathPurposeAccess
POST /auth/signupCreate a userPublic
POST /auth/loginIssue an access tokenPublic
GET /auth/meReturn the authenticated userAuthenticated
GET /problemsList problemsPublic
GET /problems/{id}Get problem details and sample casesPublic
/admin/problems/**Manage problems and test casesAdmin
POST /submissions/runRun against sample or custom inputsAuthenticated
POST /submissionsQueue a judged submissionAuthenticated
GET /submissions/{id}Get an owned submissionAuthenticated
GET /submissionsList the current user's submissionsAuthenticated

Configuration

Important environment variables:

VariableUsed byPurpose
DB_URL, DB_USERNAME, DB_PASSWORDStateful backend servicesPostgreSQL connection
JWT_SECRETAuth, problem, submissionShared HS256 secret; must be at least 32 bytes
JWT_ISSUER, JWT_AUDIENCEAuth, problem, submissionJWT validation boundary
ADMIN_SEED_EMAIL, ADMIN_SEED_PASSWORDAuthBootstrap administrator
INTERNAL_TOKENProblemProtect hidden-case endpoint
PROBLEM_SERVICE_INTERNAL_TOKENJudgeAuthenticate to hidden-case endpoint
KAFKA_BOOTSTRAP_SERVERSSubmission, judgeKafka connection
JUDGE_EXECUTION_TIMEOUTJudgePer-execution timeout, for example PT30S
JUDGE_CONTAINER_MEMORY, JUDGE_CONTAINER_CPUSJudgeSandbox resource limits

The defaults in docker-compose.yml are for local development only. Replace all secrets before deploying.

Build and Test

Build all backend services:

mvn clean package

Build the frontend:

cd frontend
npm install
npm run build

With the full stack running, execute the end-to-end test:

python3 scripts/e2e_test.py

The E2E test creates a problem and user, verifies authorization boundaries, runs custom and sample inputs, submits correct and incorrect Python/Java/C++ solutions, polls verdicts, checks history, and removes the test problem.

Deployment

The production-oriented manifests under k8s/ deploy the five application services to a k3s cluster while PostgreSQL and Kafka run externally on the VM.

The delivery flow is:

Git push
-> Jenkins detects changed services
-> builds and pushes only affected images
-> updates image tags in k8s/kustomization.yaml
-> commits the tag change
-> ArgoCD syncs and self-heals the cluster

Secrets are intentionally excluded from Kustomize and ArgoCD. See k8s/README.md for the complete cluster setup and operational notes.

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
322 lines (243 loc) · 12.4 KB

File metadata and controls

322 lines (243 loc) · 12.4 KB

CodeBox

CodeBox is a full-stack online coding judge. Users can browse programming problems, write solutions in Python, Java, or C++, run code against sample or custom inputs, submit solutions against hidden test cases, and review their submission history. Administrators can create problems, define function signatures, and manage test cases from the web UI.

The project is a microservice monorepo built with Spring Boot, React, PostgreSQL, Kafka, and Docker-based execution sandboxes.

Features

  • Email/password signup and login with role-based JWT authentication
  • Public problem catalog with generated starter code
  • Admin-only problem and test-case management
  • Monaco-based browser code editor
  • Python 3.12, Java 21, and C++17 judging
  • Synchronous runs against sample or custom inputs
  • Asynchronous submissions against all test cases
  • Per-user submission history and verdict polling
  • Docker Compose development environment
  • Kubernetes manifests and a Jenkins-to-ArgoCD GitOps deployment flow

Tech Stack

LayerTechnologies
FrontendReact 19, Vite, React Router, Zustand, Axios, Monaco Editor, Tailwind CSS
BackendJava 21, Spring Boot 3.5, Spring Web, Spring Data JPA
Data and messagingPostgreSQL 16, Kafka 3.7
AuthenticationJWT (HS256), BCrypt password hashing
Judge sandboxDocker containers with network, memory, CPU, and time limits
DeliveryDocker Compose, Kubernetes/Kustomize, Jenkins, ArgoCD

System Design

High-Level Architecture

flowchart LR
User[Browser] --> Frontend[React frontend<br/>Nginx or Vite]
Frontend -->|/api/auth/*| Auth[Auth Service :8081]
Frontend -->|/api/problems/*<br/>/api/admin/*| Problem[Problem Service :8085]
Frontend -->|/api/submissions/*| Submission[Submission Service :8082]
Auth --> DB[(PostgreSQL)]
Problem --> DB
Submission --> DB
Submission -->|HTTP: Run| Judge[Judge Service :8084]
Submission -->|submission.created| Kafka[(Kafka)]
Kafka --> Judge
Judge -->|Internal HTTP:<br/>judge bundle| Problem
Judge -->|submission.judged| Kafka
Kafka --> Submission
Judge --> Docker[Isolated runtime containers<br/>Python / Java / C++]
Loading

The frontend is the public entry point. Its Nginx configuration, or the Vite development proxy, routes /api requests to the appropriate backend service. Backend services communicate over HTTP for immediate requests and Kafka for asynchronous submission processing.

Although the three stateful services currently connect to one PostgreSQL database, each service owns its own tables:

ServiceResponsibilityState
auth-serviceSignup, login, admin seeding, JWT issuance, and /auth/meusers
problem-serviceProblems, function signatures, starter code, sample cases, hidden cases, and admin APIsproblems, test_cases
submission-serviceUser-facing run/submit APIs, submission lifecycle, history, and verdict persistencesubmissions
judge-serviceHarness generation, sandboxed code execution, output parsing, and gradingStateless
frontend-serviceReact SPA and path-based API reverse proxyStateless

Authentication and Authorization

  1. auth-service verifies credentials and issues an HS256 access token containing the user ID, email, and role.
  2. The frontend stores the token in local storage and sends it as Authorization: Bearer <token>.
  3. auth-service, problem-service, and submission-service independently validate the token using the same secret, issuer, and audience.
  4. Public problem reads expose sample cases only. Admin problem APIs require the ADMIN role.
  5. The judge fetches hidden cases from problem-service through /internal/problems/{id}/judge-bundle, guarded by a separate internal service token.

Run Flow

The Run action is synchronous, is not persisted, and evaluates either user-provided custom inputs or the problem's public sample cases.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant P as Problem Service
participant J as Judge Service
participant D as Docker Sandbox
UI->>S: POST /submissions/run
S->>P: GET /problems/{id}
P-->>S: Signature, starter metadata, sample cases
S->>J: POST /run
J->>D: Compile/run generated harness + user code
D-->>J: stdout, stderr, exit status
J-->>S: Verdict and case outputs
S-->>UI: Immediate run result
Loading

Submit Flow

The Submit action is asynchronous. It persists a queued submission, publishes an event, and returns 202 Accepted. The frontend polls the submission API until a final verdict is available.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant K as Kafka
participant J as Judge Service
participant P as Problem Service
participant D as Docker Sandbox
UI->>S: POST /submissions
S->>S: Persist status = QUEUED
S->>K: codebox.submission.created
S-->>UI: 202 Accepted + submission ID
K->>J: Consume submission
J->>P: GET internal judge bundle
P-->>J: Signature + all test cases
J->>D: Compile/run in isolated container
D-->>J: Execution result
J->>K: codebox.submission.judged
K->>S: Consume verdict
S->>S: Persist final status
UI->>S: GET /submissions/{id}
S-->>UI: Final verdict
Loading

Judge and Sandbox

The judge generates a language-specific harness that invokes the submitted function for every test case and emits a JSON result array. Outputs are compared using JSON equivalence.

Each execution runs in a temporary Docker container with:

  • No network access
  • Configurable CPU and memory limits
  • A configurable execution timeout
  • Separate stdout and stderr capture
  • Cleanup after process exit with docker run --rm

Runtime images:

LanguageImageCommand
Pythonpython:3.12-alpinepython main.py
Javaeclipse-temurin:21-alpinejavac Main.java && java -cp . Main
C++gcc:13g++ -O2 -std=c++17 ...

Docker Compose gives the judge access to the host Docker socket. Kubernetes uses a privileged Docker-in-Docker sidecar with a persistent image cache.

Data Model

  • A User has an email, BCrypt password hash, and USER or ADMIN role.
  • A Problem stores its description, difficulty, tags, function signature, supported languages, and generated starter code.
  • A TestCase stores positional input arguments and expected output as JSONB, plus sample visibility and ordering.
  • A Submission stores the user, problem, language, source code, status, runtime, score, failure details, and timestamps.

Submission states are QUEUED, ACCEPTED, WRONG_ANSWER, RUNTIME_ERROR, COMPILE_ERROR, TIME_LIMIT_EXCEEDED, and INTERNAL_ERROR.

Design Tradeoffs

  • Kafka decouples user-facing submission creation from slower code execution and allows the judge to scale independently.
  • The current persist-then-publish flow does not use a transactional outbox. A process failure between those operations can leave a submission queued without an event.
  • Kafka delivery may be repeated, so verdict application is naturally overwrite-safe, but the judge can still execute a duplicate event more than once.
  • Sharing one PostgreSQL instance simplifies local operation, but separate databases or schemas would provide stronger service isolation.
  • Docker provides practical execution isolation for this project, but access to the host Docker socket and privileged Docker-in-Docker are high-trust capabilities that require careful production hardening.

Repository Layout

.
├── frontend/ # React SPA, Nginx proxy, frontend Dockerfile
├── services/
│ ├── auth-service/ # Identity and JWTs
│ ├── problem-service/ # Problem catalog and test cases
│ ├── submission-service/ # Runs, submissions, and history
│ └── judge-service/ # Sandboxed execution and grading
├── scripts/ # Local runner, stop script, and E2E test
├── k8s/ # Kustomize manifests and deployment guide
├── argocd/ # ArgoCD Application
├── docker-compose.yml # Complete local stack
├── Jenkinsfile # Changed-service image build and tag bump
└── pom.xml # Maven multi-module parent

Getting Started

Prerequisites

  • Docker with Docker Compose
  • JDK 21 and Maven for host-based backend development
  • Node.js 20+ and npm for host-based frontend development
  • Python 3 for the end-to-end test

Option 1: Run Everything with Docker Compose

From the repository root:

docker compose up --build

Open http://localhost:8080.

The default local admin account is:

Email: admin@codebox.dev
Password: admin12345

Stop the stack with:

docker compose down

The first judge request can be slower while Docker downloads the language runtime image.

Option 2: Run Applications on the Host

This mode runs PostgreSQL and Kafka in Docker, the Spring services as local Java processes, and the frontend with Vite:

scripts/run-local.sh

Open http://localhost:5173. Logs are written to .local-run/logs/.

scripts/stop-local.sh
scripts/stop-local.sh --infra # also stop PostgreSQL and Kafka

Use SKIP_BUILD=1 scripts/run-local.sh to reuse existing service JARs.

Local Ports

ComponentPort
Frontend, Docker Compose8080
Frontend, Vite5173
Auth service8081
Submission service8082
Judge service8084
Problem service8085
PostgreSQL5432
Kafka host listener29092

API Overview

All frontend requests use the /api prefix, which the frontend proxy removes before forwarding.

Method and pathPurposeAccess
POST /auth/signupCreate a userPublic
POST /auth/loginIssue an access tokenPublic
GET /auth/meReturn the authenticated userAuthenticated
GET /problemsList problemsPublic
GET /problems/{id}Get problem details and sample casesPublic
/admin/problems/**Manage problems and test casesAdmin
POST /submissions/runRun against sample or custom inputsAuthenticated
POST /submissionsQueue a judged submissionAuthenticated
GET /submissions/{id}Get an owned submissionAuthenticated
GET /submissionsList the current user's submissionsAuthenticated

Configuration

Important environment variables:

VariableUsed byPurpose
DB_URL, DB_USERNAME, DB_PASSWORDStateful backend servicesPostgreSQL connection
JWT_SECRETAuth, problem, submissionShared HS256 secret; must be at least 32 bytes
JWT_ISSUER, JWT_AUDIENCEAuth, problem, submissionJWT validation boundary
ADMIN_SEED_EMAIL, ADMIN_SEED_PASSWORDAuthBootstrap administrator
INTERNAL_TOKENProblemProtect hidden-case endpoint
PROBLEM_SERVICE_INTERNAL_TOKENJudgeAuthenticate to hidden-case endpoint
KAFKA_BOOTSTRAP_SERVERSSubmission, judgeKafka connection
JUDGE_EXECUTION_TIMEOUTJudgePer-execution timeout, for example PT30S
JUDGE_CONTAINER_MEMORY, JUDGE_CONTAINER_CPUSJudgeSandbox resource limits

The defaults in docker-compose.yml are for local development only. Replace all secrets before deploying.

Build and Test

Build all backend services:

mvn clean package

Build the frontend:

cd frontend
npm install
npm run build

With the full stack running, execute the end-to-end test:

python3 scripts/e2e_test.py

The E2E test creates a problem and user, verifies authorization boundaries, runs custom and sample inputs, submits correct and incorrect Python/Java/C++ solutions, polls verdicts, checks history, and removes the test problem.

Deployment

The production-oriented manifests under k8s/ deploy the five application services to a k3s cluster while PostgreSQL and Kafka run externally on the VM.

The delivery flow is:

Git push
-> Jenkins detects changed services
-> builds and pushes only affected images
-> updates image tags in k8s/kustomization.yaml
-> commits the tag change
-> ArgoCD syncs and self-heals the cluster

Secrets are intentionally excluded from Kustomize and ArgoCD. See k8s/README.md for the complete cluster setup and operational notes.

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
322 lines (243 loc) · 12.4 KB

File metadata and controls

322 lines (243 loc) · 12.4 KB

CodeBox

CodeBox is a full-stack online coding judge. Users can browse programming problems, write solutions in Python, Java, or C++, run code against sample or custom inputs, submit solutions against hidden test cases, and review their submission history. Administrators can create problems, define function signatures, and manage test cases from the web UI.

The project is a microservice monorepo built with Spring Boot, React, PostgreSQL, Kafka, and Docker-based execution sandboxes.

Features

  • Email/password signup and login with role-based JWT authentication
  • Public problem catalog with generated starter code
  • Admin-only problem and test-case management
  • Monaco-based browser code editor
  • Python 3.12, Java 21, and C++17 judging
  • Synchronous runs against sample or custom inputs
  • Asynchronous submissions against all test cases
  • Per-user submission history and verdict polling
  • Docker Compose development environment
  • Kubernetes manifests and a Jenkins-to-ArgoCD GitOps deployment flow

Tech Stack

LayerTechnologies
FrontendReact 19, Vite, React Router, Zustand, Axios, Monaco Editor, Tailwind CSS
BackendJava 21, Spring Boot 3.5, Spring Web, Spring Data JPA
Data and messagingPostgreSQL 16, Kafka 3.7
AuthenticationJWT (HS256), BCrypt password hashing
Judge sandboxDocker containers with network, memory, CPU, and time limits
DeliveryDocker Compose, Kubernetes/Kustomize, Jenkins, ArgoCD

System Design

High-Level Architecture

flowchart LR
User[Browser] --> Frontend[React frontend<br/>Nginx or Vite]
Frontend -->|/api/auth/*| Auth[Auth Service :8081]
Frontend -->|/api/problems/*<br/>/api/admin/*| Problem[Problem Service :8085]
Frontend -->|/api/submissions/*| Submission[Submission Service :8082]
Auth --> DB[(PostgreSQL)]
Problem --> DB
Submission --> DB
Submission -->|HTTP: Run| Judge[Judge Service :8084]
Submission -->|submission.created| Kafka[(Kafka)]
Kafka --> Judge
Judge -->|Internal HTTP:<br/>judge bundle| Problem
Judge -->|submission.judged| Kafka
Kafka --> Submission
Judge --> Docker[Isolated runtime containers<br/>Python / Java / C++]
Loading

The frontend is the public entry point. Its Nginx configuration, or the Vite development proxy, routes /api requests to the appropriate backend service. Backend services communicate over HTTP for immediate requests and Kafka for asynchronous submission processing.

Although the three stateful services currently connect to one PostgreSQL database, each service owns its own tables:

ServiceResponsibilityState
auth-serviceSignup, login, admin seeding, JWT issuance, and /auth/meusers
problem-serviceProblems, function signatures, starter code, sample cases, hidden cases, and admin APIsproblems, test_cases
submission-serviceUser-facing run/submit APIs, submission lifecycle, history, and verdict persistencesubmissions
judge-serviceHarness generation, sandboxed code execution, output parsing, and gradingStateless
frontend-serviceReact SPA and path-based API reverse proxyStateless

Authentication and Authorization

  1. auth-service verifies credentials and issues an HS256 access token containing the user ID, email, and role.
  2. The frontend stores the token in local storage and sends it as Authorization: Bearer <token>.
  3. auth-service, problem-service, and submission-service independently validate the token using the same secret, issuer, and audience.
  4. Public problem reads expose sample cases only. Admin problem APIs require the ADMIN role.
  5. The judge fetches hidden cases from problem-service through /internal/problems/{id}/judge-bundle, guarded by a separate internal service token.

Run Flow

The Run action is synchronous, is not persisted, and evaluates either user-provided custom inputs or the problem's public sample cases.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant P as Problem Service
participant J as Judge Service
participant D as Docker Sandbox
UI->>S: POST /submissions/run
S->>P: GET /problems/{id}
P-->>S: Signature, starter metadata, sample cases
S->>J: POST /run
J->>D: Compile/run generated harness + user code
D-->>J: stdout, stderr, exit status
J-->>S: Verdict and case outputs
S-->>UI: Immediate run result
Loading

Submit Flow

The Submit action is asynchronous. It persists a queued submission, publishes an event, and returns 202 Accepted. The frontend polls the submission API until a final verdict is available.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant K as Kafka
participant J as Judge Service
participant P as Problem Service
participant D as Docker Sandbox
UI->>S: POST /submissions
S->>S: Persist status = QUEUED
S->>K: codebox.submission.created
S-->>UI: 202 Accepted + submission ID
K->>J: Consume submission
J->>P: GET internal judge bundle
P-->>J: Signature + all test cases
J->>D: Compile/run in isolated container
D-->>J: Execution result
J->>K: codebox.submission.judged
K->>S: Consume verdict
S->>S: Persist final status
UI->>S: GET /submissions/{id}
S-->>UI: Final verdict
Loading

Judge and Sandbox

The judge generates a language-specific harness that invokes the submitted function for every test case and emits a JSON result array. Outputs are compared using JSON equivalence.

Each execution runs in a temporary Docker container with:

  • No network access
  • Configurable CPU and memory limits
  • A configurable execution timeout
  • Separate stdout and stderr capture
  • Cleanup after process exit with docker run --rm

Runtime images:

LanguageImageCommand
Pythonpython:3.12-alpinepython main.py
Javaeclipse-temurin:21-alpinejavac Main.java && java -cp . Main
C++gcc:13g++ -O2 -std=c++17 ...

Docker Compose gives the judge access to the host Docker socket. Kubernetes uses a privileged Docker-in-Docker sidecar with a persistent image cache.

Data Model

  • A User has an email, BCrypt password hash, and USER or ADMIN role.
  • A Problem stores its description, difficulty, tags, function signature, supported languages, and generated starter code.
  • A TestCase stores positional input arguments and expected output as JSONB, plus sample visibility and ordering.
  • A Submission stores the user, problem, language, source code, status, runtime, score, failure details, and timestamps.

Submission states are QUEUED, ACCEPTED, WRONG_ANSWER, RUNTIME_ERROR, COMPILE_ERROR, TIME_LIMIT_EXCEEDED, and INTERNAL_ERROR.

Design Tradeoffs

  • Kafka decouples user-facing submission creation from slower code execution and allows the judge to scale independently.
  • The current persist-then-publish flow does not use a transactional outbox. A process failure between those operations can leave a submission queued without an event.
  • Kafka delivery may be repeated, so verdict application is naturally overwrite-safe, but the judge can still execute a duplicate event more than once.
  • Sharing one PostgreSQL instance simplifies local operation, but separate databases or schemas would provide stronger service isolation.
  • Docker provides practical execution isolation for this project, but access to the host Docker socket and privileged Docker-in-Docker are high-trust capabilities that require careful production hardening.

Repository Layout

.
├── frontend/ # React SPA, Nginx proxy, frontend Dockerfile
├── services/
│ ├── auth-service/ # Identity and JWTs
│ ├── problem-service/ # Problem catalog and test cases
│ ├── submission-service/ # Runs, submissions, and history
│ └── judge-service/ # Sandboxed execution and grading
├── scripts/ # Local runner, stop script, and E2E test
├── k8s/ # Kustomize manifests and deployment guide
├── argocd/ # ArgoCD Application
├── docker-compose.yml # Complete local stack
├── Jenkinsfile # Changed-service image build and tag bump
└── pom.xml # Maven multi-module parent

Getting Started

Prerequisites

  • Docker with Docker Compose
  • JDK 21 and Maven for host-based backend development
  • Node.js 20+ and npm for host-based frontend development
  • Python 3 for the end-to-end test

Option 1: Run Everything with Docker Compose

From the repository root:

docker compose up --build

Open http://localhost:8080.

The default local admin account is:

Email: admin@codebox.dev
Password: admin12345

Stop the stack with:

docker compose down

The first judge request can be slower while Docker downloads the language runtime image.

Option 2: Run Applications on the Host

This mode runs PostgreSQL and Kafka in Docker, the Spring services as local Java processes, and the frontend with Vite:

scripts/run-local.sh

Open http://localhost:5173. Logs are written to .local-run/logs/.

scripts/stop-local.sh
scripts/stop-local.sh --infra # also stop PostgreSQL and Kafka

Use SKIP_BUILD=1 scripts/run-local.sh to reuse existing service JARs.

Local Ports

ComponentPort
Frontend, Docker Compose8080
Frontend, Vite5173
Auth service8081
Submission service8082
Judge service8084
Problem service8085
PostgreSQL5432
Kafka host listener29092

API Overview

All frontend requests use the /api prefix, which the frontend proxy removes before forwarding.

Method and pathPurposeAccess
POST /auth/signupCreate a userPublic
POST /auth/loginIssue an access tokenPublic
GET /auth/meReturn the authenticated userAuthenticated
GET /problemsList problemsPublic
GET /problems/{id}Get problem details and sample casesPublic
/admin/problems/**Manage problems and test casesAdmin
POST /submissions/runRun against sample or custom inputsAuthenticated
POST /submissionsQueue a judged submissionAuthenticated
GET /submissions/{id}Get an owned submissionAuthenticated
GET /submissionsList the current user's submissionsAuthenticated

Configuration

Important environment variables:

VariableUsed byPurpose
DB_URL, DB_USERNAME, DB_PASSWORDStateful backend servicesPostgreSQL connection
JWT_SECRETAuth, problem, submissionShared HS256 secret; must be at least 32 bytes
JWT_ISSUER, JWT_AUDIENCEAuth, problem, submissionJWT validation boundary
ADMIN_SEED_EMAIL, ADMIN_SEED_PASSWORDAuthBootstrap administrator
INTERNAL_TOKENProblemProtect hidden-case endpoint
PROBLEM_SERVICE_INTERNAL_TOKENJudgeAuthenticate to hidden-case endpoint
KAFKA_BOOTSTRAP_SERVERSSubmission, judgeKafka connection
JUDGE_EXECUTION_TIMEOUTJudgePer-execution timeout, for example PT30S
JUDGE_CONTAINER_MEMORY, JUDGE_CONTAINER_CPUSJudgeSandbox resource limits

The defaults in docker-compose.yml are for local development only. Replace all secrets before deploying.

Build and Test

Build all backend services:

mvn clean package

Build the frontend:

cd frontend
npm install
npm run build

With the full stack running, execute the end-to-end test:

python3 scripts/e2e_test.py

The E2E test creates a problem and user, verifies authorization boundaries, runs custom and sample inputs, submits correct and incorrect Python/Java/C++ solutions, polls verdicts, checks history, and removes the test problem.

Deployment

The production-oriented manifests under k8s/ deploy the five application services to a k3s cluster while PostgreSQL and Kafka run externally on the VM.

The delivery flow is:

Git push
-> Jenkins detects changed services
-> builds and pushes only affected images
-> updates image tags in k8s/kustomization.yaml
-> commits the tag change
-> ArgoCD syncs and self-heals the cluster

Secrets are intentionally excluded from Kustomize and ArgoCD. See k8s/README.md for the complete cluster setup and operational notes.

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

Latest commit

History

History
322 lines (243 loc) · 12.4 KB

File metadata and controls

322 lines (243 loc) · 12.4 KB

CodeBox

CodeBox is a full-stack online coding judge. Users can browse programming problems, write solutions in Python, Java, or C++, run code against sample or custom inputs, submit solutions against hidden test cases, and review their submission history. Administrators can create problems, define function signatures, and manage test cases from the web UI.

The project is a microservice monorepo built with Spring Boot, React, PostgreSQL, Kafka, and Docker-based execution sandboxes.

Features

  • Email/password signup and login with role-based JWT authentication
  • Public problem catalog with generated starter code
  • Admin-only problem and test-case management
  • Monaco-based browser code editor
  • Python 3.12, Java 21, and C++17 judging
  • Synchronous runs against sample or custom inputs
  • Asynchronous submissions against all test cases
  • Per-user submission history and verdict polling
  • Docker Compose development environment
  • Kubernetes manifests and a Jenkins-to-ArgoCD GitOps deployment flow

Tech Stack

LayerTechnologies
FrontendReact 19, Vite, React Router, Zustand, Axios, Monaco Editor, Tailwind CSS
BackendJava 21, Spring Boot 3.5, Spring Web, Spring Data JPA
Data and messagingPostgreSQL 16, Kafka 3.7
AuthenticationJWT (HS256), BCrypt password hashing
Judge sandboxDocker containers with network, memory, CPU, and time limits
DeliveryDocker Compose, Kubernetes/Kustomize, Jenkins, ArgoCD

System Design

High-Level Architecture

flowchart LR
User[Browser] --> Frontend[React frontend<br/>Nginx or Vite]
Frontend -->|/api/auth/*| Auth[Auth Service :8081]
Frontend -->|/api/problems/*<br/>/api/admin/*| Problem[Problem Service :8085]
Frontend -->|/api/submissions/*| Submission[Submission Service :8082]
Auth --> DB[(PostgreSQL)]
Problem --> DB
Submission --> DB
Submission -->|HTTP: Run| Judge[Judge Service :8084]
Submission -->|submission.created| Kafka[(Kafka)]
Kafka --> Judge
Judge -->|Internal HTTP:<br/>judge bundle| Problem
Judge -->|submission.judged| Kafka
Kafka --> Submission
Judge --> Docker[Isolated runtime containers<br/>Python / Java / C++]
Loading

The frontend is the public entry point. Its Nginx configuration, or the Vite development proxy, routes /api requests to the appropriate backend service. Backend services communicate over HTTP for immediate requests and Kafka for asynchronous submission processing.

Although the three stateful services currently connect to one PostgreSQL database, each service owns its own tables:

ServiceResponsibilityState
auth-serviceSignup, login, admin seeding, JWT issuance, and /auth/meusers
problem-serviceProblems, function signatures, starter code, sample cases, hidden cases, and admin APIsproblems, test_cases
submission-serviceUser-facing run/submit APIs, submission lifecycle, history, and verdict persistencesubmissions
judge-serviceHarness generation, sandboxed code execution, output parsing, and gradingStateless
frontend-serviceReact SPA and path-based API reverse proxyStateless

Authentication and Authorization

  1. auth-service verifies credentials and issues an HS256 access token containing the user ID, email, and role.
  2. The frontend stores the token in local storage and sends it as Authorization: Bearer <token>.
  3. auth-service, problem-service, and submission-service independently validate the token using the same secret, issuer, and audience.
  4. Public problem reads expose sample cases only. Admin problem APIs require the ADMIN role.
  5. The judge fetches hidden cases from problem-service through /internal/problems/{id}/judge-bundle, guarded by a separate internal service token.

Run Flow

The Run action is synchronous, is not persisted, and evaluates either user-provided custom inputs or the problem's public sample cases.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant P as Problem Service
participant J as Judge Service
participant D as Docker Sandbox
UI->>S: POST /submissions/run
S->>P: GET /problems/{id}
P-->>S: Signature, starter metadata, sample cases
S->>J: POST /run
J->>D: Compile/run generated harness + user code
D-->>J: stdout, stderr, exit status
J-->>S: Verdict and case outputs
S-->>UI: Immediate run result
Loading

Submit Flow

The Submit action is asynchronous. It persists a queued submission, publishes an event, and returns 202 Accepted. The frontend polls the submission API until a final verdict is available.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant K as Kafka
participant J as Judge Service
participant P as Problem Service
participant D as Docker Sandbox
UI->>S: POST /submissions
S->>S: Persist status = QUEUED
S->>K: codebox.submission.created
S-->>UI: 202 Accepted + submission ID
K->>J: Consume submission
J->>P: GET internal judge bundle
P-->>J: Signature + all test cases
J->>D: Compile/run in isolated container
D-->>J: Execution result
J->>K: codebox.submission.judged
K->>S: Consume verdict
S->>S: Persist final status
UI->>S: GET /submissions/{id}
S-->>UI: Final verdict
Loading

Judge and Sandbox

The judge generates a language-specific harness that invokes the submitted function for every test case and emits a JSON result array. Outputs are compared using JSON equivalence.

Each execution runs in a temporary Docker container with:

  • No network access
  • Configurable CPU and memory limits
  • A configurable execution timeout
  • Separate stdout and stderr capture
  • Cleanup after process exit with docker run --rm

Runtime images:

LanguageImageCommand
Pythonpython:3.12-alpinepython main.py
Javaeclipse-temurin:21-alpinejavac Main.java && java -cp . Main
C++gcc:13g++ -O2 -std=c++17 ...

Docker Compose gives the judge access to the host Docker socket. Kubernetes uses a privileged Docker-in-Docker sidecar with a persistent image cache.

Data Model

  • A User has an email, BCrypt password hash, and USER or ADMIN role.
  • A Problem stores its description, difficulty, tags, function signature, supported languages, and generated starter code.
  • A TestCase stores positional input arguments and expected output as JSONB, plus sample visibility and ordering.
  • A Submission stores the user, problem, language, source code, status, runtime, score, failure details, and timestamps.

Submission states are QUEUED, ACCEPTED, WRONG_ANSWER, RUNTIME_ERROR, COMPILE_ERROR, TIME_LIMIT_EXCEEDED, and INTERNAL_ERROR.

Design Tradeoffs

  • Kafka decouples user-facing submission creation from slower code execution and allows the judge to scale independently.
  • The current persist-then-publish flow does not use a transactional outbox. A process failure between those operations can leave a submission queued without an event.
  • Kafka delivery may be repeated, so verdict application is naturally overwrite-safe, but the judge can still execute a duplicate event more than once.
  • Sharing one PostgreSQL instance simplifies local operation, but separate databases or schemas would provide stronger service isolation.
  • Docker provides practical execution isolation for this project, but access to the host Docker socket and privileged Docker-in-Docker are high-trust capabilities that require careful production hardening.

Repository Layout

.
├── frontend/ # React SPA, Nginx proxy, frontend Dockerfile
├── services/
│ ├── auth-service/ # Identity and JWTs
│ ├── problem-service/ # Problem catalog and test cases
│ ├── submission-service/ # Runs, submissions, and history
│ └── judge-service/ # Sandboxed execution and grading
├── scripts/ # Local runner, stop script, and E2E test
├── k8s/ # Kustomize manifests and deployment guide
├── argocd/ # ArgoCD Application
├── docker-compose.yml # Complete local stack
├── Jenkinsfile # Changed-service image build and tag bump
└── pom.xml # Maven multi-module parent

Getting Started

Prerequisites

  • Docker with Docker Compose
  • JDK 21 and Maven for host-based backend development
  • Node.js 20+ and npm for host-based frontend development
  • Python 3 for the end-to-end test

Option 1: Run Everything with Docker Compose

From the repository root:

docker compose up --build

Open http://localhost:8080.

The default local admin account is:

Email: admin@codebox.dev
Password: admin12345

Stop the stack with:

docker compose down

The first judge request can be slower while Docker downloads the language runtime image.

Option 2: Run Applications on the Host

This mode runs PostgreSQL and Kafka in Docker, the Spring services as local Java processes, and the frontend with Vite:

scripts/run-local.sh

Open http://localhost:5173. Logs are written to .local-run/logs/.

scripts/stop-local.sh
scripts/stop-local.sh --infra # also stop PostgreSQL and Kafka

Use SKIP_BUILD=1 scripts/run-local.sh to reuse existing service JARs.

Local Ports

ComponentPort
Frontend, Docker Compose8080
Frontend, Vite5173
Auth service8081
Submission service8082
Judge service8084
Problem service8085
PostgreSQL5432
Kafka host listener29092

API Overview

All frontend requests use the /api prefix, which the frontend proxy removes before forwarding.

Method and pathPurposeAccess
POST /auth/signupCreate a userPublic
POST /auth/loginIssue an access tokenPublic
GET /auth/meReturn the authenticated userAuthenticated
GET /problemsList problemsPublic
GET /problems/{id}Get problem details and sample casesPublic
/admin/problems/**Manage problems and test casesAdmin
POST /submissions/runRun against sample or custom inputsAuthenticated
POST /submissionsQueue a judged submissionAuthenticated
GET /submissions/{id}Get an owned submissionAuthenticated
GET /submissionsList the current user's submissionsAuthenticated

Configuration

Important environment variables:

VariableUsed byPurpose
DB_URL, DB_USERNAME, DB_PASSWORDStateful backend servicesPostgreSQL connection
JWT_SECRETAuth, problem, submissionShared HS256 secret; must be at least 32 bytes
JWT_ISSUER, JWT_AUDIENCEAuth, problem, submissionJWT validation boundary
ADMIN_SEED_EMAIL, ADMIN_SEED_PASSWORDAuthBootstrap administrator
INTERNAL_TOKENProblemProtect hidden-case endpoint
PROBLEM_SERVICE_INTERNAL_TOKENJudgeAuthenticate to hidden-case endpoint
KAFKA_BOOTSTRAP_SERVERSSubmission, judgeKafka connection
JUDGE_EXECUTION_TIMEOUTJudgePer-execution timeout, for example PT30S
JUDGE_CONTAINER_MEMORY, JUDGE_CONTAINER_CPUSJudgeSandbox resource limits

The defaults in docker-compose.yml are for local development only. Replace all secrets before deploying.

Build and Test

Build all backend services:

mvn clean package

Build the frontend:

cd frontend
npm install
npm run build

With the full stack running, execute the end-to-end test:

python3 scripts/e2e_test.py

The E2E test creates a problem and user, verifies authorization boundaries, runs custom and sample inputs, submits correct and incorrect Python/Java/C++ solutions, polls verdicts, checks history, and removes the test problem.

Deployment

The production-oriented manifests under k8s/ deploy the five application services to a k3s cluster while PostgreSQL and Kafka run externally on the VM.

The delivery flow is:

Git push
-> Jenkins detects changed services
-> builds and pushes only affected images
-> updates image tags in k8s/kustomization.yaml
-> commits the tag change
-> ArgoCD syncs and self-heals the cluster

Secrets are intentionally excluded from Kustomize and ArgoCD. See k8s/README.md for the complete cluster setup and operational notes.

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
322 lines (243 loc) · 12.4 KB

File metadata and controls

322 lines (243 loc) · 12.4 KB

CodeBox

CodeBox is a full-stack online coding judge. Users can browse programming problems, write solutions in Python, Java, or C++, run code against sample or custom inputs, submit solutions against hidden test cases, and review their submission history. Administrators can create problems, define function signatures, and manage test cases from the web UI.

The project is a microservice monorepo built with Spring Boot, React, PostgreSQL, Kafka, and Docker-based execution sandboxes.

Features

  • Email/password signup and login with role-based JWT authentication
  • Public problem catalog with generated starter code
  • Admin-only problem and test-case management
  • Monaco-based browser code editor
  • Python 3.12, Java 21, and C++17 judging
  • Synchronous runs against sample or custom inputs
  • Asynchronous submissions against all test cases
  • Per-user submission history and verdict polling
  • Docker Compose development environment
  • Kubernetes manifests and a Jenkins-to-ArgoCD GitOps deployment flow

Tech Stack

LayerTechnologies
FrontendReact 19, Vite, React Router, Zustand, Axios, Monaco Editor, Tailwind CSS
BackendJava 21, Spring Boot 3.5, Spring Web, Spring Data JPA
Data and messagingPostgreSQL 16, Kafka 3.7
AuthenticationJWT (HS256), BCrypt password hashing
Judge sandboxDocker containers with network, memory, CPU, and time limits
DeliveryDocker Compose, Kubernetes/Kustomize, Jenkins, ArgoCD

System Design

High-Level Architecture

flowchart LR
User[Browser] --> Frontend[React frontend<br/>Nginx or Vite]
Frontend -->|/api/auth/*| Auth[Auth Service :8081]
Frontend -->|/api/problems/*<br/>/api/admin/*| Problem[Problem Service :8085]
Frontend -->|/api/submissions/*| Submission[Submission Service :8082]
Auth --> DB[(PostgreSQL)]
Problem --> DB
Submission --> DB
Submission -->|HTTP: Run| Judge[Judge Service :8084]
Submission -->|submission.created| Kafka[(Kafka)]
Kafka --> Judge
Judge -->|Internal HTTP:<br/>judge bundle| Problem
Judge -->|submission.judged| Kafka
Kafka --> Submission
Judge --> Docker[Isolated runtime containers<br/>Python / Java / C++]
Loading

The frontend is the public entry point. Its Nginx configuration, or the Vite development proxy, routes /api requests to the appropriate backend service. Backend services communicate over HTTP for immediate requests and Kafka for asynchronous submission processing.

Although the three stateful services currently connect to one PostgreSQL database, each service owns its own tables:

ServiceResponsibilityState
auth-serviceSignup, login, admin seeding, JWT issuance, and /auth/meusers
problem-serviceProblems, function signatures, starter code, sample cases, hidden cases, and admin APIsproblems, test_cases
submission-serviceUser-facing run/submit APIs, submission lifecycle, history, and verdict persistencesubmissions
judge-serviceHarness generation, sandboxed code execution, output parsing, and gradingStateless
frontend-serviceReact SPA and path-based API reverse proxyStateless

Authentication and Authorization

  1. auth-service verifies credentials and issues an HS256 access token containing the user ID, email, and role.
  2. The frontend stores the token in local storage and sends it as Authorization: Bearer <token>.
  3. auth-service, problem-service, and submission-service independently validate the token using the same secret, issuer, and audience.
  4. Public problem reads expose sample cases only. Admin problem APIs require the ADMIN role.
  5. The judge fetches hidden cases from problem-service through /internal/problems/{id}/judge-bundle, guarded by a separate internal service token.

Run Flow

The Run action is synchronous, is not persisted, and evaluates either user-provided custom inputs or the problem's public sample cases.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant P as Problem Service
participant J as Judge Service
participant D as Docker Sandbox
UI->>S: POST /submissions/run
S->>P: GET /problems/{id}
P-->>S: Signature, starter metadata, sample cases
S->>J: POST /run
J->>D: Compile/run generated harness + user code
D-->>J: stdout, stderr, exit status
J-->>S: Verdict and case outputs
S-->>UI: Immediate run result
Loading

Submit Flow

The Submit action is asynchronous. It persists a queued submission, publishes an event, and returns 202 Accepted. The frontend polls the submission API until a final verdict is available.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant K as Kafka
participant J as Judge Service
participant P as Problem Service
participant D as Docker Sandbox
UI->>S: POST /submissions
S->>S: Persist status = QUEUED
S->>K: codebox.submission.created
S-->>UI: 202 Accepted + submission ID
K->>J: Consume submission
J->>P: GET internal judge bundle
P-->>J: Signature + all test cases
J->>D: Compile/run in isolated container
D-->>J: Execution result
J->>K: codebox.submission.judged
K->>S: Consume verdict
S->>S: Persist final status
UI->>S: GET /submissions/{id}
S-->>UI: Final verdict
Loading

Judge and Sandbox

The judge generates a language-specific harness that invokes the submitted function for every test case and emits a JSON result array. Outputs are compared using JSON equivalence.

Each execution runs in a temporary Docker container with:

  • No network access
  • Configurable CPU and memory limits
  • A configurable execution timeout
  • Separate stdout and stderr capture
  • Cleanup after process exit with docker run --rm

Runtime images:

LanguageImageCommand
Pythonpython:3.12-alpinepython main.py
Javaeclipse-temurin:21-alpinejavac Main.java && java -cp . Main
C++gcc:13g++ -O2 -std=c++17 ...

Docker Compose gives the judge access to the host Docker socket. Kubernetes uses a privileged Docker-in-Docker sidecar with a persistent image cache.

Data Model

  • A User has an email, BCrypt password hash, and USER or ADMIN role.
  • A Problem stores its description, difficulty, tags, function signature, supported languages, and generated starter code.
  • A TestCase stores positional input arguments and expected output as JSONB, plus sample visibility and ordering.
  • A Submission stores the user, problem, language, source code, status, runtime, score, failure details, and timestamps.

Submission states are QUEUED, ACCEPTED, WRONG_ANSWER, RUNTIME_ERROR, COMPILE_ERROR, TIME_LIMIT_EXCEEDED, and INTERNAL_ERROR.

Design Tradeoffs

  • Kafka decouples user-facing submission creation from slower code execution and allows the judge to scale independently.
  • The current persist-then-publish flow does not use a transactional outbox. A process failure between those operations can leave a submission queued without an event.
  • Kafka delivery may be repeated, so verdict application is naturally overwrite-safe, but the judge can still execute a duplicate event more than once.
  • Sharing one PostgreSQL instance simplifies local operation, but separate databases or schemas would provide stronger service isolation.
  • Docker provides practical execution isolation for this project, but access to the host Docker socket and privileged Docker-in-Docker are high-trust capabilities that require careful production hardening.

Repository Layout

.
├── frontend/ # React SPA, Nginx proxy, frontend Dockerfile
├── services/
│ ├── auth-service/ # Identity and JWTs
│ ├── problem-service/ # Problem catalog and test cases
│ ├── submission-service/ # Runs, submissions, and history
│ └── judge-service/ # Sandboxed execution and grading
├── scripts/ # Local runner, stop script, and E2E test
├── k8s/ # Kustomize manifests and deployment guide
├── argocd/ # ArgoCD Application
├── docker-compose.yml # Complete local stack
├── Jenkinsfile # Changed-service image build and tag bump
└── pom.xml # Maven multi-module parent

Getting Started

Prerequisites

  • Docker with Docker Compose
  • JDK 21 and Maven for host-based backend development
  • Node.js 20+ and npm for host-based frontend development
  • Python 3 for the end-to-end test

Option 1: Run Everything with Docker Compose

From the repository root:

docker compose up --build

Open http://localhost:8080.

The default local admin account is:

Email: admin@codebox.dev
Password: admin12345

Stop the stack with:

docker compose down

The first judge request can be slower while Docker downloads the language runtime image.

Option 2: Run Applications on the Host

This mode runs PostgreSQL and Kafka in Docker, the Spring services as local Java processes, and the frontend with Vite:

scripts/run-local.sh

Open http://localhost:5173. Logs are written to .local-run/logs/.

scripts/stop-local.sh
scripts/stop-local.sh --infra # also stop PostgreSQL and Kafka

Use SKIP_BUILD=1 scripts/run-local.sh to reuse existing service JARs.

Local Ports

ComponentPort
Frontend, Docker Compose8080
Frontend, Vite5173
Auth service8081
Submission service8082
Judge service8084
Problem service8085
PostgreSQL5432
Kafka host listener29092

API Overview

All frontend requests use the /api prefix, which the frontend proxy removes before forwarding.

Method and pathPurposeAccess
POST /auth/signupCreate a userPublic
POST /auth/loginIssue an access tokenPublic
GET /auth/meReturn the authenticated userAuthenticated
GET /problemsList problemsPublic
GET /problems/{id}Get problem details and sample casesPublic
/admin/problems/**Manage problems and test casesAdmin
POST /submissions/runRun against sample or custom inputsAuthenticated
POST /submissionsQueue a judged submissionAuthenticated
GET /submissions/{id}Get an owned submissionAuthenticated
GET /submissionsList the current user's submissionsAuthenticated

Configuration

Important environment variables:

VariableUsed byPurpose
DB_URL, DB_USERNAME, DB_PASSWORDStateful backend servicesPostgreSQL connection
JWT_SECRETAuth, problem, submissionShared HS256 secret; must be at least 32 bytes
JWT_ISSUER, JWT_AUDIENCEAuth, problem, submissionJWT validation boundary
ADMIN_SEED_EMAIL, ADMIN_SEED_PASSWORDAuthBootstrap administrator
INTERNAL_TOKENProblemProtect hidden-case endpoint
PROBLEM_SERVICE_INTERNAL_TOKENJudgeAuthenticate to hidden-case endpoint
KAFKA_BOOTSTRAP_SERVERSSubmission, judgeKafka connection
JUDGE_EXECUTION_TIMEOUTJudgePer-execution timeout, for example PT30S
JUDGE_CONTAINER_MEMORY, JUDGE_CONTAINER_CPUSJudgeSandbox resource limits

The defaults in docker-compose.yml are for local development only. Replace all secrets before deploying.

Build and Test

Build all backend services:

mvn clean package

Build the frontend:

cd frontend
npm install
npm run build

With the full stack running, execute the end-to-end test:

python3 scripts/e2e_test.py

The E2E test creates a problem and user, verifies authorization boundaries, runs custom and sample inputs, submits correct and incorrect Python/Java/C++ solutions, polls verdicts, checks history, and removes the test problem.

Deployment

The production-oriented manifests under k8s/ deploy the five application services to a k3s cluster while PostgreSQL and Kafka run externally on the VM.

The delivery flow is:

Git push
-> Jenkins detects changed services
-> builds and pushes only affected images
-> updates image tags in k8s/kustomization.yaml
-> commits the tag change
-> ArgoCD syncs and self-heals the cluster

Secrets are intentionally excluded from Kustomize and ArgoCD. See k8s/README.md for the complete cluster setup and operational notes.

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

Latest commit

History

History
322 lines (243 loc) · 12.4 KB

File metadata and controls

322 lines (243 loc) · 12.4 KB

CodeBox

CodeBox is a full-stack online coding judge. Users can browse programming problems, write solutions in Python, Java, or C++, run code against sample or custom inputs, submit solutions against hidden test cases, and review their submission history. Administrators can create problems, define function signatures, and manage test cases from the web UI.

The project is a microservice monorepo built with Spring Boot, React, PostgreSQL, Kafka, and Docker-based execution sandboxes.

Features

  • Email/password signup and login with role-based JWT authentication
  • Public problem catalog with generated starter code
  • Admin-only problem and test-case management
  • Monaco-based browser code editor
  • Python 3.12, Java 21, and C++17 judging
  • Synchronous runs against sample or custom inputs
  • Asynchronous submissions against all test cases
  • Per-user submission history and verdict polling
  • Docker Compose development environment
  • Kubernetes manifests and a Jenkins-to-ArgoCD GitOps deployment flow

Tech Stack

LayerTechnologies
FrontendReact 19, Vite, React Router, Zustand, Axios, Monaco Editor, Tailwind CSS
BackendJava 21, Spring Boot 3.5, Spring Web, Spring Data JPA
Data and messagingPostgreSQL 16, Kafka 3.7
AuthenticationJWT (HS256), BCrypt password hashing
Judge sandboxDocker containers with network, memory, CPU, and time limits
DeliveryDocker Compose, Kubernetes/Kustomize, Jenkins, ArgoCD

System Design

High-Level Architecture

flowchart LR
User[Browser] --> Frontend[React frontend<br/>Nginx or Vite]
Frontend -->|/api/auth/*| Auth[Auth Service :8081]
Frontend -->|/api/problems/*<br/>/api/admin/*| Problem[Problem Service :8085]
Frontend -->|/api/submissions/*| Submission[Submission Service :8082]
Auth --> DB[(PostgreSQL)]
Problem --> DB
Submission --> DB
Submission -->|HTTP: Run| Judge[Judge Service :8084]
Submission -->|submission.created| Kafka[(Kafka)]
Kafka --> Judge
Judge -->|Internal HTTP:<br/>judge bundle| Problem
Judge -->|submission.judged| Kafka
Kafka --> Submission
Judge --> Docker[Isolated runtime containers<br/>Python / Java / C++]
Loading

The frontend is the public entry point. Its Nginx configuration, or the Vite development proxy, routes /api requests to the appropriate backend service. Backend services communicate over HTTP for immediate requests and Kafka for asynchronous submission processing.

Although the three stateful services currently connect to one PostgreSQL database, each service owns its own tables:

ServiceResponsibilityState
auth-serviceSignup, login, admin seeding, JWT issuance, and /auth/meusers
problem-serviceProblems, function signatures, starter code, sample cases, hidden cases, and admin APIsproblems, test_cases
submission-serviceUser-facing run/submit APIs, submission lifecycle, history, and verdict persistencesubmissions
judge-serviceHarness generation, sandboxed code execution, output parsing, and gradingStateless
frontend-serviceReact SPA and path-based API reverse proxyStateless

Authentication and Authorization

  1. auth-service verifies credentials and issues an HS256 access token containing the user ID, email, and role.
  2. The frontend stores the token in local storage and sends it as Authorization: Bearer <token>.
  3. auth-service, problem-service, and submission-service independently validate the token using the same secret, issuer, and audience.
  4. Public problem reads expose sample cases only. Admin problem APIs require the ADMIN role.
  5. The judge fetches hidden cases from problem-service through /internal/problems/{id}/judge-bundle, guarded by a separate internal service token.

Run Flow

The Run action is synchronous, is not persisted, and evaluates either user-provided custom inputs or the problem's public sample cases.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant P as Problem Service
participant J as Judge Service
participant D as Docker Sandbox
UI->>S: POST /submissions/run
S->>P: GET /problems/{id}
P-->>S: Signature, starter metadata, sample cases
S->>J: POST /run
J->>D: Compile/run generated harness + user code
D-->>J: stdout, stderr, exit status
J-->>S: Verdict and case outputs
S-->>UI: Immediate run result
Loading

Submit Flow

The Submit action is asynchronous. It persists a queued submission, publishes an event, and returns 202 Accepted. The frontend polls the submission API until a final verdict is available.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant K as Kafka
participant J as Judge Service
participant P as Problem Service
participant D as Docker Sandbox
UI->>S: POST /submissions
S->>S: Persist status = QUEUED
S->>K: codebox.submission.created
S-->>UI: 202 Accepted + submission ID
K->>J: Consume submission
J->>P: GET internal judge bundle
P-->>J: Signature + all test cases
J->>D: Compile/run in isolated container
D-->>J: Execution result
J->>K: codebox.submission.judged
K->>S: Consume verdict
S->>S: Persist final status
UI->>S: GET /submissions/{id}
S-->>UI: Final verdict
Loading

Judge and Sandbox

The judge generates a language-specific harness that invokes the submitted function for every test case and emits a JSON result array. Outputs are compared using JSON equivalence.

Each execution runs in a temporary Docker container with:

  • No network access
  • Configurable CPU and memory limits
  • A configurable execution timeout
  • Separate stdout and stderr capture
  • Cleanup after process exit with docker run --rm

Runtime images:

LanguageImageCommand
Pythonpython:3.12-alpinepython main.py
Javaeclipse-temurin:21-alpinejavac Main.java && java -cp . Main
C++gcc:13g++ -O2 -std=c++17 ...

Docker Compose gives the judge access to the host Docker socket. Kubernetes uses a privileged Docker-in-Docker sidecar with a persistent image cache.

Data Model

  • A User has an email, BCrypt password hash, and USER or ADMIN role.
  • A Problem stores its description, difficulty, tags, function signature, supported languages, and generated starter code.
  • A TestCase stores positional input arguments and expected output as JSONB, plus sample visibility and ordering.
  • A Submission stores the user, problem, language, source code, status, runtime, score, failure details, and timestamps.

Submission states are QUEUED, ACCEPTED, WRONG_ANSWER, RUNTIME_ERROR, COMPILE_ERROR, TIME_LIMIT_EXCEEDED, and INTERNAL_ERROR.

Design Tradeoffs

  • Kafka decouples user-facing submission creation from slower code execution and allows the judge to scale independently.
  • The current persist-then-publish flow does not use a transactional outbox. A process failure between those operations can leave a submission queued without an event.
  • Kafka delivery may be repeated, so verdict application is naturally overwrite-safe, but the judge can still execute a duplicate event more than once.
  • Sharing one PostgreSQL instance simplifies local operation, but separate databases or schemas would provide stronger service isolation.
  • Docker provides practical execution isolation for this project, but access to the host Docker socket and privileged Docker-in-Docker are high-trust capabilities that require careful production hardening.

Repository Layout

.
├── frontend/ # React SPA, Nginx proxy, frontend Dockerfile
├── services/
│ ├── auth-service/ # Identity and JWTs
│ ├── problem-service/ # Problem catalog and test cases
│ ├── submission-service/ # Runs, submissions, and history
│ └── judge-service/ # Sandboxed execution and grading
├── scripts/ # Local runner, stop script, and E2E test
├── k8s/ # Kustomize manifests and deployment guide
├── argocd/ # ArgoCD Application
├── docker-compose.yml # Complete local stack
├── Jenkinsfile # Changed-service image build and tag bump
└── pom.xml # Maven multi-module parent

Getting Started

Prerequisites

  • Docker with Docker Compose
  • JDK 21 and Maven for host-based backend development
  • Node.js 20+ and npm for host-based frontend development
  • Python 3 for the end-to-end test

Option 1: Run Everything with Docker Compose

From the repository root:

docker compose up --build

Open http://localhost:8080.

The default local admin account is:

Email: admin@codebox.dev
Password: admin12345

Stop the stack with:

docker compose down

The first judge request can be slower while Docker downloads the language runtime image.

Option 2: Run Applications on the Host

This mode runs PostgreSQL and Kafka in Docker, the Spring services as local Java processes, and the frontend with Vite:

scripts/run-local.sh

Open http://localhost:5173. Logs are written to .local-run/logs/.

scripts/stop-local.sh
scripts/stop-local.sh --infra # also stop PostgreSQL and Kafka

Use SKIP_BUILD=1 scripts/run-local.sh to reuse existing service JARs.

Local Ports

ComponentPort
Frontend, Docker Compose8080
Frontend, Vite5173
Auth service8081
Submission service8082
Judge service8084
Problem service8085
PostgreSQL5432
Kafka host listener29092

API Overview

All frontend requests use the /api prefix, which the frontend proxy removes before forwarding.

Method and pathPurposeAccess
POST /auth/signupCreate a userPublic
POST /auth/loginIssue an access tokenPublic
GET /auth/meReturn the authenticated userAuthenticated
GET /problemsList problemsPublic
GET /problems/{id}Get problem details and sample casesPublic
/admin/problems/**Manage problems and test casesAdmin
POST /submissions/runRun against sample or custom inputsAuthenticated
POST /submissionsQueue a judged submissionAuthenticated
GET /submissions/{id}Get an owned submissionAuthenticated
GET /submissionsList the current user's submissionsAuthenticated

Configuration

Important environment variables:

VariableUsed byPurpose
DB_URL, DB_USERNAME, DB_PASSWORDStateful backend servicesPostgreSQL connection
JWT_SECRETAuth, problem, submissionShared HS256 secret; must be at least 32 bytes
JWT_ISSUER, JWT_AUDIENCEAuth, problem, submissionJWT validation boundary
ADMIN_SEED_EMAIL, ADMIN_SEED_PASSWORDAuthBootstrap administrator
INTERNAL_TOKENProblemProtect hidden-case endpoint
PROBLEM_SERVICE_INTERNAL_TOKENJudgeAuthenticate to hidden-case endpoint
KAFKA_BOOTSTRAP_SERVERSSubmission, judgeKafka connection
JUDGE_EXECUTION_TIMEOUTJudgePer-execution timeout, for example PT30S
JUDGE_CONTAINER_MEMORY, JUDGE_CONTAINER_CPUSJudgeSandbox resource limits

The defaults in docker-compose.yml are for local development only. Replace all secrets before deploying.

Build and Test

Build all backend services:

mvn clean package

Build the frontend:

cd frontend
npm install
npm run build

With the full stack running, execute the end-to-end test:

python3 scripts/e2e_test.py

The E2E test creates a problem and user, verifies authorization boundaries, runs custom and sample inputs, submits correct and incorrect Python/Java/C++ solutions, polls verdicts, checks history, and removes the test problem.

Deployment

The production-oriented manifests under k8s/ deploy the five application services to a k3s cluster while PostgreSQL and Kafka run externally on the VM.

The delivery flow is:

Git push
-> Jenkins detects changed services
-> builds and pushes only affected images
-> updates image tags in k8s/kustomization.yaml
-> commits the tag change
-> ArgoCD syncs and self-heals the cluster

Secrets are intentionally excluded from Kustomize and ArgoCD. See k8s/README.md for the complete cluster setup and operational notes.

, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

Latest commit

History

History
322 lines (243 loc) · 12.4 KB

File metadata and controls

322 lines (243 loc) · 12.4 KB

CodeBox

CodeBox is a full-stack online coding judge. Users can browse programming problems, write solutions in Python, Java, or C++, run code against sample or custom inputs, submit solutions against hidden test cases, and review their submission history. Administrators can create problems, define function signatures, and manage test cases from the web UI.

The project is a microservice monorepo built with Spring Boot, React, PostgreSQL, Kafka, and Docker-based execution sandboxes.

Features

  • Email/password signup and login with role-based JWT authentication
  • Public problem catalog with generated starter code
  • Admin-only problem and test-case management
  • Monaco-based browser code editor
  • Python 3.12, Java 21, and C++17 judging
  • Synchronous runs against sample or custom inputs
  • Asynchronous submissions against all test cases
  • Per-user submission history and verdict polling
  • Docker Compose development environment
  • Kubernetes manifests and a Jenkins-to-ArgoCD GitOps deployment flow

Tech Stack

LayerTechnologies
FrontendReact 19, Vite, React Router, Zustand, Axios, Monaco Editor, Tailwind CSS
BackendJava 21, Spring Boot 3.5, Spring Web, Spring Data JPA
Data and messagingPostgreSQL 16, Kafka 3.7
AuthenticationJWT (HS256), BCrypt password hashing
Judge sandboxDocker containers with network, memory, CPU, and time limits
DeliveryDocker Compose, Kubernetes/Kustomize, Jenkins, ArgoCD

System Design

High-Level Architecture

flowchart LR
User[Browser] --> Frontend[React frontend<br/>Nginx or Vite]
Frontend -->|/api/auth/*| Auth[Auth Service :8081]
Frontend -->|/api/problems/*<br/>/api/admin/*| Problem[Problem Service :8085]
Frontend -->|/api/submissions/*| Submission[Submission Service :8082]
Auth --> DB[(PostgreSQL)]
Problem --> DB
Submission --> DB
Submission -->|HTTP: Run| Judge[Judge Service :8084]
Submission -->|submission.created| Kafka[(Kafka)]
Kafka --> Judge
Judge -->|Internal HTTP:<br/>judge bundle| Problem
Judge -->|submission.judged| Kafka
Kafka --> Submission
Judge --> Docker[Isolated runtime containers<br/>Python / Java / C++]
Loading

The frontend is the public entry point. Its Nginx configuration, or the Vite development proxy, routes /api requests to the appropriate backend service. Backend services communicate over HTTP for immediate requests and Kafka for asynchronous submission processing.

Although the three stateful services currently connect to one PostgreSQL database, each service owns its own tables:

ServiceResponsibilityState
auth-serviceSignup, login, admin seeding, JWT issuance, and /auth/meusers
problem-serviceProblems, function signatures, starter code, sample cases, hidden cases, and admin APIsproblems, test_cases
submission-serviceUser-facing run/submit APIs, submission lifecycle, history, and verdict persistencesubmissions
judge-serviceHarness generation, sandboxed code execution, output parsing, and gradingStateless
frontend-serviceReact SPA and path-based API reverse proxyStateless

Authentication and Authorization

  1. auth-service verifies credentials and issues an HS256 access token containing the user ID, email, and role.
  2. The frontend stores the token in local storage and sends it as Authorization: Bearer <token>.
  3. auth-service, problem-service, and submission-service independently validate the token using the same secret, issuer, and audience.
  4. Public problem reads expose sample cases only. Admin problem APIs require the ADMIN role.
  5. The judge fetches hidden cases from problem-service through /internal/problems/{id}/judge-bundle, guarded by a separate internal service token.

Run Flow

The Run action is synchronous, is not persisted, and evaluates either user-provided custom inputs or the problem's public sample cases.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant P as Problem Service
participant J as Judge Service
participant D as Docker Sandbox
UI->>S: POST /submissions/run
S->>P: GET /problems/{id}
P-->>S: Signature, starter metadata, sample cases
S->>J: POST /run
J->>D: Compile/run generated harness + user code
D-->>J: stdout, stderr, exit status
J-->>S: Verdict and case outputs
S-->>UI: Immediate run result
Loading

Submit Flow

The Submit action is asynchronous. It persists a queued submission, publishes an event, and returns 202 Accepted. The frontend polls the submission API until a final verdict is available.

sequenceDiagram
participant UI as Frontend
participant S as Submission Service
participant K as Kafka
participant J as Judge Service
participant P as Problem Service
participant D as Docker Sandbox
UI->>S: POST /submissions
S->>S: Persist status = QUEUED
S->>K: codebox.submission.created
S-->>UI: 202 Accepted + submission ID
K->>J: Consume submission
J->>P: GET internal judge bundle
P-->>J: Signature + all test cases
J->>D: Compile/run in isolated container
D-->>J: Execution result
J->>K: codebox.submission.judged
K->>S: Consume verdict
S->>S: Persist final status
UI->>S: GET /submissions/{id}
S-->>UI: Final verdict
Loading

Judge and Sandbox

The judge generates a language-specific harness that invokes the submitted function for every test case and emits a JSON result array. Outputs are compared using JSON equivalence.

Each execution runs in a temporary Docker container with:

  • No network access
  • Configurable CPU and memory limits
  • A configurable execution timeout
  • Separate stdout and stderr capture
  • Cleanup after process exit with docker run --rm

Runtime images:

LanguageImageCommand
Pythonpython:3.12-alpinepython main.py
Javaeclipse-temurin:21-alpinejavac Main.java && java -cp . Main
C++gcc:13g++ -O2 -std=c++17 ...

Docker Compose gives the judge access to the host Docker socket. Kubernetes uses a privileged Docker-in-Docker sidecar with a persistent image cache.

Data Model

  • A User has an email, BCrypt password hash, and USER or ADMIN role.
  • A Problem stores its description, difficulty, tags, function signature, supported languages, and generated starter code.
  • A TestCase stores positional input arguments and expected output as JSONB, plus sample visibility and ordering.
  • A Submission stores the user, problem, language, source code, status, runtime, score, failure details, and timestamps.

Submission states are QUEUED, ACCEPTED, WRONG_ANSWER, RUNTIME_ERROR, COMPILE_ERROR, TIME_LIMIT_EXCEEDED, and INTERNAL_ERROR.

Design Tradeoffs

  • Kafka decouples user-facing submission creation from slower code execution and allows the judge to scale independently.
  • The current persist-then-publish flow does not use a transactional outbox. A process failure between those operations can leave a submission queued without an event.
  • Kafka delivery may be repeated, so verdict application is naturally overwrite-safe, but the judge can still execute a duplicate event more than once.
  • Sharing one PostgreSQL instance simplifies local operation, but separate databases or schemas would provide stronger service isolation.
  • Docker provides practical execution isolation for this project, but access to the host Docker socket and privileged Docker-in-Docker are high-trust capabilities that require careful production hardening.

Repository Layout

.
├── frontend/ # React SPA, Nginx proxy, frontend Dockerfile
├── services/
│ ├── auth-service/ # Identity and JWTs
│ ├── problem-service/ # Problem catalog and test cases
│ ├── submission-service/ # Runs, submissions, and history
│ └── judge-service/ # Sandboxed execution and grading
├── scripts/ # Local runner, stop script, and E2E test
├── k8s/ # Kustomize manifests and deployment guide
├── argocd/ # ArgoCD Application
├── docker-compose.yml # Complete local stack
├── Jenkinsfile # Changed-service image build and tag bump
└── pom.xml # Maven multi-module parent

Getting Started

Prerequisites

  • Docker with Docker Compose
  • JDK 21 and Maven for host-based backend development
  • Node.js 20+ and npm for host-based frontend development
  • Python 3 for the end-to-end test

Option 1: Run Everything with Docker Compose

From the repository root:

docker compose up --build

Open http://localhost:8080.

The default local admin account is:

Email: admin@codebox.dev
Password: admin12345

Stop the stack with:

docker compose down

The first judge request can be slower while Docker downloads the language runtime image.

Option 2: Run Applications on the Host

This mode runs PostgreSQL and Kafka in Docker, the Spring services as local Java processes, and the frontend with Vite:

scripts/run-local.sh

Open http://localhost:5173. Logs are written to .local-run/logs/.

scripts/stop-local.sh
scripts/stop-local.sh --infra # also stop PostgreSQL and Kafka

Use SKIP_BUILD=1 scripts/run-local.sh to reuse existing service JARs.

Local Ports

ComponentPort
Frontend, Docker Compose8080
Frontend, Vite5173
Auth service8081
Submission service8082
Judge service8084
Problem service8085
PostgreSQL5432
Kafka host listener29092

API Overview

All frontend requests use the /api prefix, which the frontend proxy removes before forwarding.

Method and pathPurposeAccess
POST /auth/signupCreate a userPublic
POST /auth/loginIssue an access tokenPublic
GET /auth/meReturn the authenticated userAuthenticated
GET /problemsList problemsPublic
GET /problems/{id}Get problem details and sample casesPublic
/admin/problems/**Manage problems and test casesAdmin
POST /submissions/runRun against sample or custom inputsAuthenticated
POST /submissionsQueue a judged submissionAuthenticated
GET /submissions/{id}Get an owned submissionAuthenticated
GET /submissionsList the current user's submissionsAuthenticated

Configuration

Important environment variables:

VariableUsed byPurpose
DB_URL, DB_USERNAME, DB_PASSWORDStateful backend servicesPostgreSQL connection
JWT_SECRETAuth, problem, submissionShared HS256 secret; must be at least 32 bytes
JWT_ISSUER, JWT_AUDIENCEAuth, problem, submissionJWT validation boundary
ADMIN_SEED_EMAIL, ADMIN_SEED_PASSWORDAuthBootstrap administrator
INTERNAL_TOKENProblemProtect hidden-case endpoint
PROBLEM_SERVICE_INTERNAL_TOKENJudgeAuthenticate to hidden-case endpoint
KAFKA_BOOTSTRAP_SERVERSSubmission, judgeKafka connection
JUDGE_EXECUTION_TIMEOUTJudgePer-execution timeout, for example PT30S
JUDGE_CONTAINER_MEMORY, JUDGE_CONTAINER_CPUSJudgeSandbox resource limits

The defaults in docker-compose.yml are for local development only. Replace all secrets before deploying.

Build and Test

Build all backend services:

mvn clean package

Build the frontend:

cd frontend
npm install
npm run build

With the full stack running, execute the end-to-end test:

python3 scripts/e2e_test.py

The E2E test creates a problem and user, verifies authorization boundaries, runs custom and sample inputs, submits correct and incorrect Python/Java/C++ solutions, polls verdicts, checks history, and removes the test problem.

Deployment

The production-oriented manifests under k8s/ deploy the five application services to a k3s cluster while PostgreSQL and Kafka run externally on the VM.

The delivery flow is:

Git push
-> Jenkins detects changed services
-> builds and pushes only affected images
-> updates image tags in k8s/kustomization.yaml
-> commits the tag change
-> ArgoCD syncs and self-heals the cluster

Secrets are intentionally excluded from Kustomize and ArgoCD. See k8s/README.md for the complete cluster setup and operational notes.