Components
This page describes each part of the system and what it is responsible for. File paths are relative to the repo root.
Frontend (frontend/)
Compose Multiplatform. One codebase, two build targets:
- Web (
wasmJs): compiled to WebAssembly and bundled into the backend jar as static files. This is what a student uses in the browser. It is served from the backend root. - Desktop (
jvm): a native desktop app the student installs and runs.
The shared code lives in frontend/src/commonMain/kotlin/. Platform-specific pieces use Kotlin’s expect/actual mechanism, with implementations under desktopMain/ and wasmJsMain/. The main areas are the code editor, the problem viewer, login/auth, the API client, and the lockdown (proctoring) controller.
The two targets differ mainly in login. The web app is same-origin with the backend, so after OAuth the backend redirects back to / with the token in the URL. The desktop app opens the system browser for OAuth and catches the redirect on a temporary localhost callback server, then uses the same Bearer token for API calls.
Backend (backend/)
Spring Boot 3, Kotlin. Serves the REST API and the web frontend. The entry point is backend/src/main/app/Application.kt.
Controllers
The controllers are in backend/src/main/controller/.
| Controller | Base path | What it does |
|---|---|---|
OAuthController |
/login, /callback, /api/* |
Student Google OAuth login and callback, logout, and the session heartbeat (/api/check-session) |
TaOAuthController |
/ta/login, /ta/callback, /api/ta/* |
TA Google OAuth, verified against the course’s TA email |
ProblemController |
/api/problems |
Lists the problems in the active lab and serves problem statements (HTML, CSS, assets) |
LabController |
/api/labs |
Lists a student’s labs and reports remaining time for a lab |
CodeController |
/api/code |
run, submit, and listing past submissions |
AutosaveController |
/api/autosave |
Saves and reads the student’s in-progress code |
ActivityController |
/api/activity |
Receives proctoring events and commits the activity log |
TaController |
/api/ta |
TA dashboard: sections, active sessions, stats, labs, and per-student activity |
LabHealthController |
/api/admin/lab-health |
A readiness check for TAs before a lab |
HealthController |
/health |
Liveness for the CI deploy gate; returns {"status":"ok"} |
Services
The services in backend/src/main/service/ hold the logic.
Identity and sessions
StudentIdentityServiceresolves who is making a request. It reads theAuthorization: Bearerheader and nothing else. It never trusts an email in the request body or query. It also does a log-only check of whether the token is being used from the same IP it was issued to; a mismatch is logged but never blocks the request.ApiTokenStoreis the session store. Despite the name it is not an in-memory map. It is backed by thelogin_sessionstable throughLoginSessionRepository. It issues tokens (one row per login), enforces one active session per student, refreshes the TTL on heartbeat, and ends sessions. It runs a scheduled sweep every 60 seconds to end sessions that stopped heartbeating. All the ways a session can end (explicit logout, heartbeat finding it expired, background sweep) funnel through one privateendSessionmethod, which publishes aLogoutEventbefore marking the row logged out.
Code execution
CodeServicevalidates enrollment and the lab window, maps the language name to a file extension and a judge language code, and orchestrates the judge call plus the git save. It uses an atomic per-student lock so a double-click cannot start two runs for the same student at once. On submit it calls the judge first, then saves the code and result together so they share one timestamp.JudgeServiceis the HTTP client to the judge. It sends JSON toPOST /runandPOST /submitatjudge.url. It pins HTTP/1.1 (a leftover from the Python judge, which ran on uvicorn).
Storage and content
GitServicedoes all filesystem and git work: writing autosaves, submissions, and activity logs into the student repo and committing them, and building problem statements into the problem repo. It serializes writes per repository with a lock because the whole working tree and a single.git/index.lockcannot be shared by concurrent git processes.ProblemServicereads problem statements (index.html,problem.css, assets) from the problem git repo, with path-traversal guards, and rewrites relative image URLs to go through the asset endpoint.
Other services
CourseService and LabService back the CLI admin commands. ActivityLogService formats proctoring events as CSV rows. DatabaseBackupService takes a daily database backup. LabHealthService backs the TA readiness check. LogoutActivityLogHook listens for the LogoutEvent and commits the activity log when a session ends.
Config
IpWhitelistFilter(backend/src/main/config/) blocks requests from IPs outside an allowed list. The list comes fromcs30.allowed-ips. If that setting is empty, the filter allows everything. When it blocks a request it returns a 403 with a styled “Access Restricted” page.WebConfigwires up static file serving for the web app.
CLI (cli/)
Picocli. The entry point is cli/src/main/Main.kt. This module is what produces the shipping jar (cs30-1.0-SNAPSHOT.jar).
The first argument decides the mode. serve starts the backend web server. Anything else is treated as an admin command with the web server disabled. The admin subcommands include addcourse, addstudent, removecourse, setta, addproblem, addproblems, removeproblem, updateproblemlanguage, cancellab, and validatecourse. addcourse reads a course definition from YAML. The CLI shares Spring beans, models, and repositories with the backend, which is why it can be in the same jar.
Data (data/)
Kotlin Multiplatform. Holds the serializable DTOs that both the frontend and backend use, so the two sides stay in agreement about request and response shapes. Examples include SubmissionInfo and the lockdown event types.
Judge (kt-judge/)
Spring Boot 3, Kotlin. A standalone service. Its controller (JudgeController) exposes:
POST /runandPOST /submitto execute codeGET /health,GET /ready, andGET /selftestfor health and readiness checks
JudgeRunner builds and runs the hardened docker run command for one job. Each job runs in a throwaway container with --network=none, --cap-drop=ALL, --security-opt=no-new-privileges, a read-only root, tmpfs mounts for /work and /tmp, and limits on memory, CPU, process count, and file size. It runs as a non-root user (uid 1000). The student’s source and a Python orchestrator (incontainer.py) are mounted read-only into the container. The orchestrator drives bapctools to compile once and run the test cases, then prints a JSON result that JudgeRunner parses back into per-testcase verdicts (AC, WA, TLE, RTE, MLE, CE). Sandbox limits and concurrency are configured through JudgeProperties (prefix judge. in the properties file).
The older Python judge in judge/ implements the same HTTP contract and the same sandbox flags. It is not the one we build in CI.