Data flow
This page traces the main things a student does, request by request. Each flow is backed by the controllers and services described on the components page.
Login
Login is Google OAuth. The interesting parts are that the backend checks enrollment before issuing a token, refuses a second concurrent session, and hands the token back in the redirect URL.
sequenceDiagram
participant C as Client
participant O as OAuthController
participant G as Google
participant DB as login_sessions (DB)
C->>O: GET /login
O-->>C: 302 to Google
C->>G: authenticate
G-->>C: 302 /callback?code
C->>O: GET /callback?code
O->>G: exchange code, get user info
G-->>O: email, name
O->>DB: enrolled in any course?
alt not enrolled
O-->>C: 302 ...?error=not_enrolled
else already has an active session
O-->>C: 302 ...?error=session_exists
else ok
O->>DB: insert login_sessions row, get token
O-->>C: 302 ...?name&email&api_token
end
A few details worth knowing:
- The OAuth request pins
hd=sjsu.edu, so only SJSU accounts are offered. - Enrollment is checked with
courseRepository.findByStudentEmail. A student not in any course is turned away witherror=not_enrolled. - One active session per student is enforced by
tokenStore.hasActiveSession. A second login while one is active getserror=session_exists. - Issuing the token is the
login_sessionsinsert itself. If that write fails, login fails, because a token with no row could never resolve to a user. - Web and desktop use the same flow. The desktop app passes an
app_callback(alocalhostURL) and astatevalue, and the backend redirects the token there instead of to/.
Opening a problem
Once logged in, the client asks for the problems in the currently active lab, then fetches a specific problem’s statement.
sequenceDiagram
participant C as Client
participant P as ProblemController
participant I as StudentIdentityService
participant S as ProblemService
participant R as Problem git repo
C->>P: GET /api/problems/lab (Bearer token)
P->>I: resolve identity from token
I-->>P: email
P->>S: list active-lab problems for this student
S-->>P: problem list
P-->>C: problems
C->>P: GET /api/problems/{course}/section/{s}/lab/{l}/{slug}
P->>S: access check + read statement
S->>R: read index.html, problem.css, assets
S-->>P: statement content
P-->>C: HTML + CSS
ProblemService enforces access on every read: the student must be enrolled, in the right section, the lab must be active, and the problem must belong to that lab. It reads statement files from the problem git repo and guards against path traversal. Relative image URLs in the statement are rewritten to point at the asset endpoint.
Running and submitting code
run and submit share most of their path. The difference is what the judge grades against and whether the result is saved.
sequenceDiagram
participant C as Client
participant CC as CodeController
participant CS as CodeService
participant J as JudgeService
participant JS as Judge sandbox
participant G as GitService
participant R as Student git repo
C->>CC: POST /api/code/run or /submit (Bearer token)
CC->>CS: request (identity resolved from token)
CS->>CS: check enrollment + lab window
CS->>CS: claim per-student lock
CS->>J: run/submit (problem, pool path, language, source)
J->>JS: HTTP JSON to judge
JS-->>J: per-testcase verdicts
J-->>CS: result
alt submit
CS->>G: save code + result together
G->>R: write files, git commit
end
CS-->>CC: response
CC-->>C: test output or verdict
Things that matter here:
- Identity comes from the token. Any
studentEmailin the request body is ignored. CodeServiceholds a per-student lock (an atomic claim keyed by the resolved email) shared between run and submit, so a fast double-click cannot fire two judge runs at once.- The lab window is checked with
checkLabDeadline. If the lab has not started or has ended, the request is rejected before reaching the judge. runsends the sample tests plus any custom inputs the student typed, and saves nothing.submitgrades against all tests (sample and hidden). It calls the judge first, then saves the code and the result JSON together undersubmissions/so they share one timestamp. If the judge call fails, the student sees a generic error and nothing is graded.- The language string from the course or request is mapped to a file extension (for the saved file) and to a judge language code (for the sandbox).
Autosave
While a student edits, the client autosaves in the background about once a minute, and again when the lab ends.
sequenceDiagram
participant C as Client
participant A as AutosaveController
participant G as GitService
participant R as Student git repo
loop about every 60s and on lab end
C->>A: POST /api/autosave (code + location)
A->>A: resolve identity, validate enrollment + active lab
A->>G: write autosaved-solution.<ext>, commit
G->>R: file + git commit
A-->>C: 202 accepted (or 401/403/404)
end
If the client gets a 401 back, the session is gone and it stops the autosave loop.
Proctoring (lockdown)
During a lab the client watches for focus loss, fullscreen exit, copy, paste, and devtools, and heartbeats. These events are queued on the client and drained to the backend, which appends them to a CSV. The CSV is committed to git when the session ends.
sequenceDiagram
participant L as Lockdown controller (client)
participant AC as ActivityController
participant AL as ActivityLogService
participant G as GitService
participant R as Student git repo
L->>AC: POST /api/activity/event?problem=slug
AC->>AL: record event
AL->>G: append CSV row (no commit yet)
G->>R: write row
Note over L,R: on session end
L->>AC: POST /api/activity/commit
AC->>AL: commit
AL->>G: git add + commit
G->>R: commit
Session end also commits the log through a different path: when ApiTokenStore ends a session it publishes a LogoutEvent, and LogoutActivityLogHook commits the activity log in response. That listener runs synchronously before the session is marked logged out, so if the commit fails the logout is blocked rather than silently losing the log.