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 with error=not_enrolled.
  • One active session per student is enforced by tokenStore.hasActiveSession. A second login while one is active gets error=session_exists.
  • Issuing the token is the login_sessions insert 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 (a localhost URL) and a state value, 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 studentEmail in the request body is ignored.
  • CodeService holds 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.
  • run sends the sample tests plus any custom inputs the student typed, and saves nothing.
  • submit grades against all tests (sample and hidden). It calls the judge first, then saves the code and the result JSON together under submissions/ 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.