Truman Costello

Truman Costello

Computer Science, Game Development, Mathematics @ USC

Code Judge Platform

Leetscope

This was the first project I’ve done where project management and careful planning was integral to execution. We used the Waterfall method to linearly approach project development

Overview

LeetScope was an online code judge platform for teachers and students to create, manage, submit, grade, and review programmings assignment. We supported C++ file uploads.

Front End

For users, there are three page states:

Guest

guest Guests could view assignments and register/login. registration

Teacher

teacher Teachers were able to create assignments where each had set custom deadlines, allowed attempts, and an expected output in txt format. They were also able to manage and edit all paramaters of the assignments (postponing the deadline, increasing the number of allowed attempts, etc).

Student

student Students could submit C++ and Java files to be graded for each individual assignment. They were intended to take some input from the console (which the backend would provide) and print an output into a console (which the backend would then process).

Project Flow

The general project flow from front to back end was the following:

Vue.js → Axios → Controller (SpringBoot) → Service (SpringBoot) → Mapper (MyBatis Plus) → DataBase (MySQL) → Response to Controller → Axios → Vue.js

User opens client: Vue.js + Element Plus → User has a request → Vue.js uses Axios send a request to backend → SpringBoot receives the request in controller → Controller calls Springboot service to process and get a result → SpringBoot send back the response to Axios → Axios process the response in callback function → Vue.js updates the web app after information is updated by callback function

Networking

We used cross-origin resource sharing to ensure security when transferring data between the browser and server.

public class CorsConfig implements WebMvcConfigurer {
    @Override
    public void addCorsMappings(CorsRegistry registry) {
        registry.addMapping("/**")
            .allowedOriginPatterns(*)
            .allowedMethods("GET", "HEAD", "POST", "PUT", "DELETE", "OPTIONS")
            .allowedCredentials(true)
            .maxAge(3600)
            .allowedHeaders(*)
    }
}

Database

The database had a fairly straightforward design and flow. MySQL would be called via the JDBC API which would flow to MyBatisPlus into Springboot and eventually the frontend.

database

Login Security

We did basic passcode hasing using MD5

user.setPassword(DigestUtils.md5DigestAsHex(user.getPassword().getBytes()));

Back End

As aformentioned, we would automatically grade students’ assignments in the background using sample input and output teachers provided.

Storage

To handle programs students uploaded, we used SpringBoot’s storage service to handle file upload from client to locally store uploaded code in text format, and then we would delete the code once grading was complete.

submission.setIsPass(false);
storageService.store("program", String.valueOf(submission.getId()), file.getOriginalFilename(), file);
return NoDataResult.success();

Grading

We created individual threads for eaching student upload using a GradUtil class, which would assign submissions to a GradeThread where the submission would be run and compared to the expected output

public void gradeNewAssignment(int submissionId) {
    Submission submission = submissionMapper.selectById(submissionId);
    Assignment assignment = assignmentMapper.selectById(submission.getAssignmentId());
    Thread graderThread = new GraderThread(assignment, submission, assignmentMapper, submissionMapper, storageProperties);
    graderThreads.add(thread);
    thread.start();
}

The run function in GraderThread would call compile(), execute(), and grade() and then set graded to true.

private void compile() {
    try {
        Files.createDirectories(binaryFile.getParent());
        String[] commands = {
            compilerLocation.toString(),
            programFile.toString(),
            "-o",
            binaryFile.toString()
        }
        Runtime runtime = Runtime.getRuntime();
        Process process = runtime.exec(commands);
        process.waitFor();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private void execute() {
    try {
            Files.createFile(userOutputFile);
            ProcessBuilder pb = new ProcessBuilder(binaryFile.toString());
            pb.redirectInput(new File(inputFile.toString()));
            pb.redirectOutput(new File(userOutputFile.toString()));
            Process process = pb.start();
            process.waitFor();
    } catch (Exception e) {
        e.printStackTrace();
    }
}

private void grade() {
    try {
        submission.setIsPass(Files.mismatch(userOuputFile, outputFile));
        submissionMapper.updateById(submission);
    } catch (IOException e) {
        e.printStackTrace();
    }
}