StudyCod is an educational platform for learning programming in which practice plays the central role: write code → run it → pass tests → identify the mistake → fix it → receive an explanation. It was created in response to the widespread problem of formalized learning, where attention is focused on memorizing syntax rather than developing algorithmic and engineering thinking.
Here, learning resembles a real developer workflow, adapted for education: clear requirements, tests, rapid feedback, and transparent assessment.
This README is the single, complete reference for the project — product vision, system architecture, every service, the data model, the API surface, configuration, local setup, testing, and deployment. If you only read one document, read this one.
- Project Idea
- What Makes StudyCod Distinctive
- Operating Modes
- System Architecture
- Repository Layout
- Technology Stack
- Backend
- The Judge (code execution & sandboxing)
- AI Layer
- EDU Live Classroom (code-aware video lessons)
- Frontend
- AI Service & Cloudflare Worker
- Configuration Reference (environment variables)
- Local Development Setup
- Testing
- Observability & Operations
- Deployment
- Security Model
- Target Audience
- License
In programming, the most difficult part is not learning language constructs, but learning how to think: decomposing a problem into parts, designing a solution, considering edge cases, and refining the code to correctness and stability.
StudyCod shifts the emphasis from "reading theory" to "doing by hand." The platform helps train:
- problem analysis;
- algorithm construction;
- careful implementation;
- working with errors and tests;
- the habit of improving a solution instead of stopping at the first attempt.
At the core of the platform are practical problem solving and objective automated evaluation.
- The user works with code in a convenient interface and submits a solution.
- The solution is checked against a set of tests, making the result repeatable and transparent.
- After evaluation, it is clear what works and what does not: exactly where the solution fails.
- Artificial intelligence is used as a learning assistant: it provides explanations and guidance rather than simply giving the answer.
- The platform spans personal practice, classroom teaching, contests, and live code-aware video lessons in one product.
A mode for independent practice at one's own pace:
- task selection;
- repeated attempts and improvements;
- progress tracking;
- returning to difficult topics.
The goal is steady growth through systematic practice and short cycles of "attempt → feedback → correction."
A mode for an organized learning process in classes or groups:
- structure around topics and lessons (
LESSON/CONTROLwork); - assignment distribution to a group;
- quizzes and assessments with deadlines and time limits;
- a gradebook and clear progress tracking for each student;
- grade appeals workflow between students and teachers;
- a live monitor that shows, in real time, which students are stuck / working / passed / idle.
The goal is to make learning manageable for the instructor and transparent for the student.
A competitive engine with problems, participants, submissions, and standings. It supports
multiple scoring strategies (scoringMode, IOI by default) and a unified standings view.
Contest-mode users are isolated from the regular learning surface by a dedicated guard.
StudyCod is a monorepo composed of cooperating services. The backend is the hub; the judge runs as a sandboxed child process; AI calls are brokered through OpenRouter (with an optional Cloudflare Workers AI fallback); the live classroom uses a self-hosted LiveKit SFU.
┌──────────────────────────────────────────────┐
│ Frontend (Vite/React 19) │
│ Personal · EDU · Contest · Live classroom UI │
└───────────────┬────────────────────────────────┘
│ HTTPS (REST, /api/*) WebRTC (media)
▼ │
┌───────────────────────────────────────────────────────────┐ │
│ Backend (Express 5 + TypeORM) │ │
│ │ │
│ middleware: auth · maintenance · placement · rate limit │ │
│ routes: auth, tasks, edu, topics, theory, contests, │ │
│ library, playground, admin, support, certificate │ │
│ services: judgeWorker, llm, grading, integrity, replay, │ │
│ translation, certificates, redis, edu/live... │ │
└───┬─────────────┬──────────────┬──────────────┬────────────┘ │
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌─────────┐ ┌─────────┐ ┌───────────┐ ┌──────────┐ ┌───────────────┐
│ MySQL │ │ Redis │ │ Judge │ │OpenRouter│ │ LiveKit (SFU) │
│(TypeORM)│ │sessions │ │ (nsjail │ │ LLMs │ │ self-hosted │
│ │ │ queue │ │ sandbox) │ │ + CF AI │ │ │
│ │ │ ratelim │ │ child proc│ │ fallback │ │ │
└─────────┘ └─────────┘ └───────────┘ └──────────┘ └───────────────┘
Key architectural choices:
- Single backend, dual route mount. Every router is mounted twice — at
/<name>and at/api/<name>— so the same API works behind a path-prefixing reverse proxy or directly. - Judge as an isolated child process. Untrusted user code never runs in the API process;
it is executed by a separate worker entry under an
nsjailsandbox. - Redis is optional but unlocks scale. With Redis enabled the backend uses a distributed execution queue, Redis-backed sessions, and Redis rate-limit stores; without it everything falls back to safe in-process behavior (ideal for local dev).
- Feature gating by env. AI, translation, live classroom, web tasks, and metrics each
degrade gracefully (e.g. live classroom returns
503 LIVE_CLASSROOM_DISABLED) when their configuration is absent, instead of breaking the whole process. - Graceful, bounded shutdown.
SIGTERM/SIGINTdrain in-flight HTTP, then release Redis, with a deadline so a slow request can't block a deploy.
studycod_platform/
├── backend/ Express + TypeORM API (the hub)
│ ├── src/
│ │ ├── index.ts App bootstrap, middleware wiring, health/metrics, shutdown
│ │ ├── env.ts Zod-validated environment schema (single source of truth)
│ │ ├── config.ts Derived runtime config exports
│ │ ├── data-source.ts TypeORM DataSource + entity/migration registration
│ │ ├── entities/ Database entities (TypeORM)
│ │ ├── migrations/ Schema migrations (+ legacy history bootstrap)
│ │ ├── middleware/ auth, maintenance, placement gate, rate limits, role guard...
│ │ ├── routes/ HTTP routers (auth, tasks, edu/*, topics, contests, admin...)
│ │ ├── services/ Business logic (judgeWorker, llm, grading, integrity, edu/...)
│ │ ├── observability/ Health checks + Prometheus metrics rendering
│ │ ├── ai/ AI evaluator
│ │ └── utils/ Loggers, formula evaluator, grading scale, seeders...
│ └── scripts/ DB migration / bootstrap CLI helpers
├── judge/ Sandboxed code-execution worker
│ ├── index.ts Worker entry (also serves --health)
│ ├── engine/ compiler, executor, runner, limits, result types
│ ├── languages/ Per-language config: c, cpp, csharp, java, kotlin, python
│ ├── checkers/ Output comparators: exact, float, whitespace, normalize...
│ └── sandbox/nsjail.cfg nsjail sandbox profile
├── frontend/ Vite + React 19 SPA
│ └── src/
│ ├── pages/ Grouped: auth, core, edu, contest, library, profile, public
│ ├── components/ CodeEditor, LiveClassMonitor, ClassLiveOverview, editors...
│ ├── lib/api/ Typed API clients per domain
│ ├── locales/ en.ts, uk.ts (i18next)
│ └── App.tsx Route table
├── ai-service/ Optional standalone AI processing service
│ └── cloudflare-ai-worker/ Cloudflare Workers AI proxy (auth'd by shared secret)
├── shared/ Code shared across services
├── docker/
│ └── livekit/ docker-compose + config for the live-classroom SFU
├── docs/ Design docs (e.g. edu-live-classroom-plan.md)
├── theories/ · topics/ Curriculum content
└── README.md You are here
| Area | Technology |
|---|---|
| Backend runtime | Node.js, TypeScript, tsx (dev), tsc (build) |
| Web framework | Express 5 |
| ORM / DB | TypeORM + MySQL 8 (mysql2 driver) |
| Cache / queue | Redis (sessions, distributed execution queue, rate-limit stores) |
| Auth | JWT (Bearer), Passport (Google OAuth 2.0), express-session |
| Code execution | Custom judge + nsjail sandbox; C, C++, C#, Java, Kotlin, Python |
| AI | OpenRouter (primary), Cloudflare Workers AI (optional fallback) |
| Live video | LiveKit (self-hosted SFU) + @livekit/components-react |
| Frontend | React 19, Vite 7, React Router 7, Tailwind CSS 4, Framer Motion |
| Editor | Monaco (@monaco-editor/react) |
| Charts / math | Recharts, KaTeX, react-markdown + remark/rehype |
| i18n | i18next / react-i18next (English + Ukrainian) |
| Validation | Zod (env + payloads), express-validator |
| Security | Helmet, CORS allowlist, express-rate-limit, Cloudflare Turnstile (optional) |
| Nodemailer / Brevo, IMAP ingest (imapflow + mailparser) | |
| PDF / assets | Playwright (certificates), QRCode |
The backend is an Express 5 application defined in backend/src/index.ts. It validates its
entire environment up front through a Zod schema (backend/src/env.ts), initializes the
TypeORM data source, runs startup migrations (with legacy-history auto-recovery), optionally
seeds curriculum topics, and then begins listening.
In order, an inbound request passes through:
- Helmet — strict API-only CSP (
default-src 'none',frame-ancestors 'none'). - Global rate limit (production only) — 300 req/min, keyed by IP for anonymous and by
IP + signed principal idfor authenticated users (so a whole NATed classroom isn't one bucket, and a replayed token can't DoS the victim's bucket)./healthis skipped. - CORS — strict origin allowlist resolved once at boot from
CORS_ORIGIN/CORS_ORIGINS; credentials enabled;*is forbidden in production. - Per-route body limits — tight
256kbdefault;50mbonly for routes that legitimately need it (/library,/admin,/topics,/contests). Configurable viaBODY_LIMIT_*. - Request context — correlation/metadata middleware.
- Maintenance gate — short-circuits traffic when maintenance mode is on.
- Session + Passport — Redis-backed (or in-memory) sessions; skipped for sessionless
paths (
/health,/ready,/metrics,/internal/*). - Route guards —
authMiddleware,forbidContestModeUsers,placementGateare applied per router (see below). - Centralized error handler — normalizes
HttpError/status, addsRetry-Afteron judge-overload503s, and never leaks internals in production.
Every router below is mounted at both /<name> and /api/<name>.
| Mount | Router | Guards | Purpose |
|---|---|---|---|
/auth |
routes/auth.ts |
— | Login, registration, Google OAuth exchange, JWT issue |
/profile |
routes/profile.ts |
— | User profile, settings |
/tasks |
routes/tasks.ts |
auth · no-contest · placement | Personal practice tasks, run & submit |
/grades |
routes/gradeRoutes.ts |
auth · no-contest · placement | Personal grades |
/topics |
routes/topics.ts |
auth · no-contest · placement | Curriculum topics & progress |
/theory |
routes/theory.ts |
auth · no-contest · placement | Theory blocks (with translation) |
/streak |
routes/streak.ts |
auth · no-contest · placement | Learning streaks |
/birthday |
routes/birthday.ts |
auth · no-contest · placement | Birthday greetings |
/edu |
routes/edu.ts (+ routes/edu/*) |
mixed (teacher/student) | Full EDU suite (see below) |
/library |
routes/library.ts |
auth · no-contest | Library task bank (large bodies) |
/contests |
routes/contests.ts |
own guards | Contest engine, submissions, standings |
/playground |
routes/playground.ts |
— | Free-form code snippets |
/support |
routes/support.ts |
— | Support tickets & conversations |
/certificate |
routes/certificate.ts |
— | Certificate generation (Playwright) |
/emails |
routes/emails.ts |
— | Email flows |
/admin |
routes/admin.ts (+ adminBroadcast, adminLibrary, adminMail, adminMaintenance, adminMaterials, adminSupport) |
auth · no-contest | Administration |
EDU sub-routers (backend/src/routes/edu/): studentAuth, announcements,
classStudents, students, lessons, tasks, testData, grading, appeals,
insights, gradebook, and liveClassroom (the live, code-aware classroom).
Operational endpoints (sessionless):
GET /health,/api/health— liveness (build SHA, version, env).GET /ready,/api/ready— readiness (DB reachable →200, else503with per-dep detail).GET /metrics,/api/metrics— Prometheus text (prod requiresMETRICS_ENABLED=1).GET /health/judge— runs annsjail --healthprobe (cached, coalesced).GET /internal/load— judge queue/scheduler metrics (non-prod by default).GET /internal/ai/openrouter— OpenRouter runtime diagnostics (non-prod by default).
backend/src/services/ holds the business logic. Highlights:
judgeWorker/— spawns and supervises the judge child process, applies a concurrency semaphore, and exposes execution metrics.JudgeBusyErrorsurfaces overload.execution/— the execution queue, with a distributed (Redis) mode for multi-replica deployments and a local (in-process) mode for single instances; includes claim TTLs, retries, and a dead-letter queue.codeExecutionService.ts— high-level run/compare helpers used by tasks and EDU.llm/+openRouterService.ts+openRouterClient.ts+openRouterKeys.ts— the LLM provider abstraction: model selection, fallbacks, backup API keys, reasoning toggle, and runtime diagnostics.ai/andservices/ai/host higher-level features (failure hints, AI-code detection, safe AI call wrappers).grading/+edu/controlWorkGrading.ts— automated and control-work grading; grade notifications viaedu/gradeNotificationService.ts.integrity/(+SubmissionIntegrityentity) — academic-integrity signals for proctoring.replay/— solve-session replays (time-travel through a student's code history).edu/liveMonitor.ts+edu/liveClassroom.ts— the live class snapshot/heatmap and the LiveKit token minting / session lifecycle.translation/— uk→en theory translation (self-hosted/CF worker; public fallback off by default to avoid exfiltrating content).certificates/— async certificate queue worker (started at boot).redis/sharedRedis.ts— the single shared Redis client + key-prefix conventions.- Plus:
generateTestDataService,placementAssessmentService,plagiarism/,visualizer/,reports/,calibration/,emailService,studycodMailService.
TypeORM entities live in backend/src/entities/ and are registered in data-source.ts.
Grouped by domain:
- Identity & access:
User,Student,Class. - Curriculum:
Topic,TopicNew,TopicTask,TopicProgress,TheoryBlock,TaskTheory,Task,TestData. - EDU teaching:
EduLesson(LESSON/CONTROL),EduTask,ControlWork,LessonAttempt,EduGrade,SummaryGrade,Grade,ClassAnnouncement,EduHintFeedback,ConceptReviewState. - EDU live:
EduLiveSession(a live room bound to a class and optionally a lesson, with a uniqueroomName,LIVE/ENDEDstatus,startedBy,ended_at). - Grade appeals:
GradeAppeal,GradeAppealMessage. - Contests:
Contest,ContestProblem,ContestParticipant,ContestSubmission. - Library & playground:
LibraryTask,LibraryTaskAttempt,LibraryTaskRevision,PlaygroundSnippet,SolveSession. - Integrity:
SubmissionIntegrity. - Support:
SupportTicket,SupportConversation,SupportMessage,SupportAttachment. - Operations:
MaintenanceState.
Migrations are in backend/src/migrations/ and run automatically on startup
(RUN_MIGRATIONS_ON_STARTUP, default on). The bootstrap logic is resilient to legacy
schema drift: if a CREATE TABLE fails because the table already exists (locale-independent
detection via MySQL error code/errno/SQLSTATE), it can auto-stamp the migration history
(AUTO_BOOTSTRAP_MIGRATION_HISTORY_ON_STARTUP, default on outside production).
CLI helpers:
npm run db:migrate # apply pending migrations
npm run db:bootstrap-migration-history # stamp legacy schema into migration history (once)The judge (judge/) is a separate worker process invoked by the backend's judgeWorker
service. Untrusted user code is never executed inside the API process.
- Entry:
judge/index.ts. Invoked with--healthit returns a JSON health payload (used by/health/judge); otherwise it reads aJudgeRequestand emits aJudgeResponseon stdout (stdout is reserved strictly for JSON; logs go to stderr). - Engine (
judge/engine/):compiler.ts,executor.ts,runner.ts,limits.ts,result.ts, plusstderr.ts/userFacingErrors.tsto turn raw failures into user-friendly messages. - Languages (
judge/languages/):c,cpp,csharp,java,kotlin,python(each defines compile/run commands and limits;types.tsis the shared contract). - Checkers (
judge/checkers/): output comparators —exact,float(tolerance),whitespace,normalize,nonempty. - Sandbox:
nsjail(config atjudge/sandbox/nsjail.cfg). In production the judge runs in config mode; per-language chroots are configurable. Resource caps (input size, test count, per-test I/O bytes, file count, source bytes) are enforced and surfaced in the health payload.
Load control: the backend bounds concurrency with MAX_CONCURRENT_EXECUTIONS
(per-instance) and MAX_GLOBAL_CONCURRENT_EXECUTIONS (cluster-wide, via the distributed
queue), an execution queue with MAX_EXECUTION_QUEUE_SIZE, retries, and a dead-letter queue.
Overload returns 503 with a Retry-After header.
Contract tests:
npm run test:judge-contract # backend ↔ judge request/response contract
npm run test:db-contract # DB contractAI is a learning enhancer, not an answer machine. It assists in creating learning materials and practical assignments, generates varied test data, explains typical mistakes, suggests a direction of reasoning, and helps a learner understand why a solution failed.
- Provider: OpenRouter via
services/llm/OpenRouterProviderandopenRouterService.ts. Supports a primary model plus configurable fallbacks (OPENROUTER_FALLBACK_MODELS/OPENROUTER_MODEL_FALLBACKS), separate text/JSON models, an optional reasoning mode, and a pool of backup API keys (OPENROUTER_BACKUP_API_KEYS) for resilience. - Fallback: an optional Cloudflare Workers AI worker (
CLOUDFLARE_AI_URL), protected by a shared secret (CLOUDFLARE_AI_INTERNAL_SECRET) so it isn't an open inference proxy. - Orchestration tunables:
LLM_TASK_*env vars control timeouts, max tokens, theory / previous-task context windows, and an anchor cache. - Safety:
services/ai/safeAICallwraps calls so AI failures never crash a request;services/ai/aiCodeDetectorflags likely AI-generated submissions for integrity. - Diagnostics:
GET /internal/ai/openrouterexposes provider runtime state (gated in prod behindEXPOSE_INTERNAL_AI_DIAGNOSTICS=1).
The objective is to improve feedback quality and reduce routine instructor workload while preserving fairness of evaluation.
The standout EDU capability: a single screen that combines live video + a real-time heatmap of every student's code state + AI — something a generic LMS/meeting tool can't do, because it has no code-execution engine in its core.
Video stack: self-hosted LiveKit (open-source SFU). The teacher is the room host;
students join as participants. Roles and tokens are bound to a Class/EduLesson via the
EduLiveSession entity.
Backend (services/edu/liveClassroom.ts, routes/edu/liveClassroom.ts):
POST /edu/classes/:classId/live-sessions— teacher opens/reopens a session (oneLIVEsession per class), returns a host token.GET /edu/classes/:classId/live-sessions/active— the active session (teacher or class student), without a token.POST /edu/live-sessions/:id/join— mints a token (teacher → host, class student → participant).POST /edu/live-sessions/:id/end— teacher ends the session.GET /edu/classes/:classId/live-overview— per-student latest code activity within a 3h window (stuck / working / passed / idle, verdict, tests, current task). Teacher/admin only; reuses the purebuildLiveSnapshotfromservices/edu/liveMonitor.ts.
Tokens are minted with livekit-server-sdk and TTL LIVEKIT_TOKEN_TTL_MINUTES. The feature
is gated: without LIVEKIT_URL / LIVEKIT_API_KEY / LIVEKIT_API_SECRET the routes
return 503 LIVE_CLASSROOM_DISABLED.
Frontend: pages/edu/LiveClassroomPage.tsx (lobby + room via
@livekit/components-react's VideoConference), components/ClassLiveOverview.tsx (the live
heatmap, polling ~5s, shown beside the video for the teacher), client in
lib/api/liveClassroom.ts, route /edu/classes/:classId/live, with entry buttons in
ClassDetailsPage (teacher) and StudentLessonsPage (student).
Run LiveKit locally:
cd docker/livekit
docker compose upThen set in backend/.env and restart the backend:
LIVEKIT_URL=ws://localhost:7880
LIVEKIT_API_KEY=devkey
LIVEKIT_API_SECRET=devsecret_change_me_at_least_32_characters_long
LIVEKIT_TOKEN_TTL_MINUTES=240
The full feature backlog (AI co-host, live challenges, shared cursor, time-travel debugging,
proctoring, class economy/streaks, offline lesson capsules) is curated in
docs/edu-live-classroom-plan.md.
A Vite + React 19 single-page app (frontend/).
- Routing:
src/App.tsx(React Router 7). Pages are grouped undersrc/pages/:auth/— login, registration, OAuth.core/— Home, Tasks, Grades, IAD, solve replay.edu/— teacher & student dashboards, class details, lessons, gradebook, control work, appeals, summary grades, topic/lesson authoring, andLiveClassroomPage.contest/— contests, problem solving, scoreboard.library/,profile/,public/,system/.
- Editor: Monaco-based
CodeEditorandMultiFileEditor;WebPreviewPanefor web tasks. - EDU live UI:
LiveClassMonitorandClassLiveOverview(real-time class state). - AI UX:
DebugMentorChat,ErrorExplainButton,FailureRecoveryCard,TaskGenerationOverlay. - API clients: typed per-domain in
src/lib/api/(auth,tasks,edu,contests,library,grades,learning,playground,liveClassroom,admin,support,theory,profile) over a sharedclient.tswith retry support. - i18n: i18next, English + Ukrainian (
src/locales/en.ts,uk.ts). - Styling: Tailwind CSS 4, Framer Motion, KaTeX, react-markdown (+ remark-gfm / remark-math / rehype-katex), Recharts.
Scripts:
npm run dev # Vite dev server (default http://localhost:5173)
npm run build # production build + sitemap generation
npm run preview # preview the production buildai-service/— an optional standalone Node/Express service for internal AI processing. It reuses backend code via module aliases (entities,services,utils,config,@shared) configured in itspackage.jsonandtsconfig.ai-service/cloudflare-ai-worker/— a Cloudflare Worker that proxies Workers AI inference. It authenticates callers with anx-internal-secretheader that must matchCLOUDFLARE_AI_INTERNAL_SECRET; without it the worker would be an open paid-inference proxy. This same worker can also serve uk→en translation for theory blocks.
Both are optional — the core platform runs without them.
All backend configuration is declared and validated in backend/src/env.ts. Create a
backend/.env. Below are the most relevant variables; defaults apply when omitted.
| Variable | Default | Notes |
|---|---|---|
NODE_ENV |
— | production enforces strict checks below |
PORT |
4000 |
HTTP port |
FRONTEND_URL |
http://localhost:5173 |
|
BACKEND_PUBLIC_URL |
http://localhost:4000 |
|
CORS_ORIGIN / CORS_ORIGINS |
http://localhost:5173 |
Comma-list allowlist; * forbidden in prod |
TRUST_PROXY |
1 in prod, 0 otherwise |
Express trust-proxy setting |
JWT_SECRET |
— | Required in prod, ≥ 32 chars |
SESSION_SECRET |
— | Required in prod, ≥ 32 chars |
SESSION_STORE |
memory |
redis to use Redis-backed sessions |
| Variable | Notes |
|---|---|
DATABASE_URL |
Full connection URL (alternative to discrete vars) |
DB_HOST / DB_PORT / DB_USER / DB_PASS / DB_NAME |
Discrete config; DB_PASS required in prod when no DATABASE_URL |
DB_POOL_SIZE / DB_CONNECT_TIMEOUT_MS / DB_ACQUIRE_TIMEOUT_MS / DB_POOL_QUEUE_LIMIT |
Pool tuning |
RUN_MIGRATIONS_ON_STARTUP |
Default true |
AUTO_BOOTSTRAP_MIGRATION_HISTORY_ON_STARTUP |
Default on outside prod |
SEED_TOPICS_ON_STARTUP |
Default on in dev/test, off in prod |
| Variable | Default | Notes |
|---|---|---|
REDIS_URL |
redis://127.0.0.1:6379 |
|
REDIS_ENABLED |
inferred | Auto-on if REDIS_URL set or sessions/queue want Redis |
REDIS_KEY_PREFIX |
studycod: |
| Variable | Notes |
|---|---|
JUDGE_WORKER_ENTRY |
Path to the judge worker entry |
NSJAIL_PATH |
Default /usr/bin/nsjail |
NSJAIL_CONFIG |
Sandbox config path (enables config mode) |
NSJAIL_USE_CONFIG |
Force config mode (prod always config mode) |
NSJAIL_CWD |
Default /work |
NSJAIL_CHROOT / _JAVA / _CPP / _PYTHON |
Per-language chroots |
JUDGE_LOCK_PATH / JUDGE_LOCK_STALE_MS |
Judge lock |
JUDGE_MAX_* |
Input/test/output/file size & count caps |
| Variable | Default | Notes |
|---|---|---|
MAX_CONCURRENT_EXECUTIONS |
12 |
Per-instance |
MAX_GLOBAL_CONCURRENT_EXECUTIONS |
0 (= per-instance) |
Cluster-wide cap |
MAX_EXECUTION_QUEUE_SIZE |
50 |
|
EXECUTION_QUEUE_MODE |
inferred | local or distributed (Redis) |
EXECUTION_QUEUE_* |
— | Poll/claim/result TTLs, retries, DLQ size |
RATE_LIMIT_SHORT_* / RATE_LIMIT_LONG_* |
5/10s, 20/60s | Per-user submission limits |
OVERLOAD_RETRY_AFTER_SECONDS |
3 |
Retry-After on overload 503 |
BODY_LIMIT_DEFAULT / BODY_LIMIT_LARGE |
256kb / 50mb |
Per-route body caps |
| Variable | Notes |
|---|---|
OPENROUTER_API_KEY |
Primary key |
OPENROUTER_BACKUP_API_KEYS |
Comma-list of backup keys |
OPENROUTER_MODEL / OPENROUTER_TEXT_MODEL / OPENROUTER_JSON_MODEL |
Model selection |
OPENROUTER_FALLBACK_MODELS / OPENROUTER_MODEL_FALLBACKS |
Fallback chain |
OPENROUTER_REASONING_ENABLED |
Reasoning mode |
OPENROUTER_URL / OPENROUTER_REFERER |
Endpoint overrides |
LLM_TASK_* |
Timeout, max tokens, context windows, anchor cache |
CLOUDFLARE_AI_URL / CLOUDFLARE_AI_INTERNAL_SECRET |
CF Workers AI fallback + shared secret |
EXPOSE_INTERNAL_AI_DIAGNOSTICS |
Expose /internal/ai/openrouter in prod |
TRANSLATE_UK_EN_URL, TRANSLATE_UK_EN_TIMEOUT_MS, TRANSLATE_UK_EN_MAX_CHUNK_CHARS,
TRANSLATE_ALLOW_PUBLIC_FALLBACK (off by default — public translators would exfiltrate
content).
LIVEKIT_URL, LIVEKIT_API_KEY, LIVEKIT_API_SECRET (all three required to enable),
LIVEKIT_TOKEN_TTL_MINUTES (default 240, clamped 5–720).
TURNSTILE_SECRET_KEY, TURNSTILE_VERIFY_URL, TURNSTILE_ENFORCE_AUTH,
TURNSTILE_ENFORCE_CONTEST_SUBMIT (Cloudflare Turnstile), METRICS_ENABLED.
WEB_TASKS_ENABLED, WEB_TASK_MAX_FILE_SIZE, WEB_TASK_MAX_TOTAL_SIZE,
WEB_TASK_PREVIEW_RATE_LIMIT.
- Node.js (LTS) and npm
- MySQL 8 (running, with a database created)
- Redis (optional — recommended for sessions/queue testing)
nsjail(for real sandboxed execution; Windows treats binary existence as executable)- Docker (optional — for the LiveKit live classroom)
cd backend
npm install
# create .env (see Configuration Reference). At minimum for local dev:
# DB_HOST / DB_PORT / DB_USER / DB_PASS / DB_NAME (or DATABASE_URL)
# JWT_SECRET, SESSION_SECRET (any value works outside production)
npm run db:migrate # apply migrations (also runs on startup by default)
npm run dev # tsx watch on http://localhost:4000cd frontend
npm install
npm run dev # Vite on http://localhost:5173cd docker/livekit
docker compose up
# then add LIVEKIT_* to backend/.env and restart the backendcd ai-service
npm install
npm run devHealth check once the backend is up:
curl http://localhost:4000/health
curl http://localhost:4000/ready
curl http://localhost:4000/health/judgeBackend tests use Node's built-in test runner against the compiled output:
cd backend
npm test # NODE_ENV=test → build → node --test on dist/**/*.test.js
npm run test:judge-contract
npm run test:db-contract
npm run loadtest # load test harnessTest files live next to their subjects (e.g. middleware/*.test.ts,
services/edu/liveMonitor.test.ts, routes/auth.googleExchange.test.ts). In test mode the
environment forces in-memory sessions, Redis disabled, and the local execution queue.
- Logging: structured logger (
utils/logger);morgan('dev')in non-production. - Health/readiness:
/health(liveness) and/ready(DB-gated readiness for rolling deploys — route traffic only on200). - Metrics: Prometheus text at
/metrics(METRICS_ENABLED=1in prod). Judge scheduler metrics at/internal/load. - Judge health:
/health/judgeruns (and caches/coalesces) annsjail --healthprobe. - Resilience: disconnect errors (EPIPE/ECONNRESET) are ignored rather than crashing the
process; unhandled rejections are logged and only trigger a drain+restart if they exceed a
rolling-window threshold (
UNHANDLED_REJECTION_FATAL_THRESHOLD). - Graceful shutdown:
SIGTERM/SIGINTdrain in-flight HTTP (bounded bySHUTDOWN_DRAIN_TIMEOUT_MS, default 15s) and then release Redis.
A typical production deployment:
- Build backend (
npm run build→dist/) and frontend (npm run build→ static assets). - Serve the frontend as static assets behind a CDN/reverse proxy; proxy
/api/*to the backend (both/<name>and/api/<name>mounts are supported). - MySQL + Redis provisioned; set
SESSION_STORE=redisand a Redis URL for multi-replica setups (also enables the distributed execution queue). - Judge: ensure
nsjailis installed andJUDGE_WORKER_ENTRY/NSJAIL_*are set; verify via/health/judge. In production the judge always runs in config mode. - LiveKit: for live classrooms, run the SFU behind TLS (
wss://), open the UDP media port range, and setuse_external_ip: true(or configure TURN) for NAT traversal. - Secrets: strong
JWT_SECRETandSESSION_SECRET(≥ 32 chars), real DB credentials, non-*CORS_ORIGIN. The process fails fast at boot if these prod invariants aren't met. - Scaling: run N backend replicas with Redis + a global execution cap
(
MAX_GLOBAL_CONCURRENT_EXECUTIONS); gate load-balancer traffic on/ready.
- AuthN: JWT Bearer tokens + Passport Google OAuth 2.0; signed, http-only session cookies.
- AuthZ: per-router guards (
authMiddleware,rolesGuard,forbidContestModeUsers,placementGate) and teacher/student/admin checks inside EDU routes. - Transport: Helmet with a strict API CSP; CORS allowlist with credentials;
*forbidden in production. - Abuse control: global + per-user rate limits (Redis-backed in prod), optional Cloudflare Turnstile on auth and contest submit, per-route body-size caps.
- Untrusted code: executed only in the
nsjail-sandboxed judge child process with strict resource limits — never in the API process. - Academic integrity:
SubmissionIntegritysignals + AI-code detection feed the EDU live panel and grading. - Data minimization for AI: translation/AI calls don't fall back to public third-party hosts unless explicitly opted in; the CF AI worker requires a shared secret.
- Pupils and students — to learn not only to write code but to solve problems and think algorithmically.
- Teachers — to prepare materials faster, provide practice, observe progress in real time, and maintain transparent assessment.
- Educational initiatives — as a foundation for courses, clubs, distance learning, and research in the field of EdTech and AI.
See LICENSE.