John-sCC / jcc_backend

Eat up!
1 stars 0 forks source link

Assignment Object - Final Progress Before N@tM 3 #39

Closed drewreed2005 closed 6 months ago

drewreed2005 commented 6 months ago

Prior Planning

(AJ's contributions were on the frontend using an existing post method, so they will not be discussed in this PR.)

Drew's Ticket

Focused on implementing specialized Assignment and AssignmentSubmission methods for frontend data display and grading.

Raymond's Ticket

Raymond's edits are mostly focused on file uploading and sending file previews to the frontend, which was a big mystery until he took it on.

API Method Additions/Changes

User Info

Role Determination

This is used to determine the person's relation to the assignment. If no relation is determined, a 400 error is returned.

      HashMap<String, Object> assignmentData = new HashMap<>();
      assignmentData.put("role", null);
      // good ID, so continue to check relationship
      for (ClassPeriod cp : classService.getClassPeriodsByAssignment(assignment)) {
          for (Person student : cp.getStudents()) {
              if (student.getEmail().equals(existingPerson.getEmail())) {
                  assignmentData.put("role", "student"); // person has teacher access to the assignment
              }
          }
          for (Person leader : cp.getLeaders()) {
              if (leader.getEmail().equals(existingPerson.getEmail())) {
                  assignmentData.put("role", "teacher"); // person has teacher access to the assignment
              }
          }
      }
      // handler for invalid user accessing data
      if (assignmentData.get("role") == null) {
          return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
      }

Role-Based Data Returning

We didn't want any additional student submission information to be sent unless the person was a teacher, so we did the following in the info fetching method:

        // if the student is determined to have no relationship to the assignment, null indicates they cannot access w/ role
        // manually adding to data to prevent student from getting submission access
        HashMap<String, Object> assignmentDetails = new HashMap<>();
        assignmentDetails.put("allowedFileTypes", assignment.getAllowedFileTypes());
        assignmentDetails.put("allowedSubmissions", assignment.getAllowedSubmissions());
        assignmentDetails.put("content", assignment.getContent());
        assignmentDetails.put("dateDue", assignment.getDateDue());
        assignmentDetails.put("id", assignment.getId());
        assignmentDetails.put("name", assignment.getName());
        assignmentDetails.put("points", assignment.getPoints());
        if (assignmentData.get("role").equals("teacher")) {
            assignmentDetails.put("submissions", assignment.getSubmissions());
            // retrieving the list of ClassPeriod objects for the given assignment
            List<ClassPeriod> classPeriods = classService.getClassPeriodsByAssignment(assignment);
            // making a HashSet to store unique Person objects
            Set<Person> uniqueStudents = new LinkedHashSet<>(); // SORT ALL THESE STUDENTS ALPHABETICALLY
            // iterating through each ClassPeriod object
            for (ClassPeriod classPeriod : classPeriods) {
                Collection<Person> students = classPeriod.getStudents();
                for (Person student : students) {
                    uniqueStudents.add(student);
                }
            }
            // SORTING ALPHABETICALLY when added
            assignmentDetails.put("allAssignees", sortLinkedHashSetByName(uniqueStudents));
            // new set for unique submissions
            Set<Person> uniqueSubmitters = new HashSet<>();
            // iterating through each submission
            for (AssignmentSubmission submission : assignment.getSubmissions()) {
                uniqueSubmitters.add(submission.getSubmitter());
            }
            assignmentDetails.put("allSubmitters", uniqueSubmitters);
        }

Assignment Grading

The method (shortened below) is used for grading. The actual grade change is made by a new AssignmentSubmissionDetailsService method that does exactly what the name describes, exactly as expected (get submission by ID, then use setter).

    @PostMapping("/cookie/{id}/grading")
    public ResponseEntity<?> getAssignmentSubmissionsWithCookie(@CookieValue("jwt") String jwtToken,
                                                                @PathVariable long id,
                                                                @RequestParam int score) {
        // JWT and submission verification takes place here...
        // then, teacher verification, shown below...
        boolean isTeacher = false;
        // good ID, so continue to check relationship
        for (ClassPeriod cp : classService.getClassPeriodsByAssignment(assignmentDetailsService.getBySubmission(submission))) {
            for (Person leader : cp.getLeaders()) {
                if (leader.getEmail().equals(existingPerson.getEmail())) {
                    isTeacher = true; // person has teacher access to the assignment
                }
            }
        }
        // handler for invalid user accessing data
        if (!(isTeacher)) {
            return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
        }
        // if determined to be teacher, the grade is updated
        subDetailsService.scoreSubmission(submission.getId(), score);

Revised AssignmentSubmission Method

This method is a revision of Raymond's original /upload/ method that was used for more basic testing of file upload. Some basic fetching and verification of the submission are skipped for brevity, but notice the file upload aspects.

    @PostMapping("/submit/{id}/{submissionTimeString}")
    public ResponseEntity<Object> handleFileUpload(@CookieValue("jwt") String jwtToken,
                                                   @RequestPart("file") MultipartFile file,
                                                   @PathVariable long id,
                                                   @PathVariable String submissionTimeString) {
        // VERIFICATION OF USER, ASSIGNMENT, ETC. SKIPPED HERE...
        // blah blah blah...
        // processing file upload if the user has been verified (RAYMOND CODE)
        try {
            //check if file type is null: edge case
            String contentType = file.getContentType();

            if (contentType == null) {
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("File content type is not supported");
            }

            String fileExtension = getFileExtension(file.getOriginalFilename());

            if (!isValidFileType(submittedAssignment.getAllowedFileTypes(), fileExtension, contentType)) {
                return ResponseEntity.status(HttpStatus.BAD_REQUEST).body("File type is not supported");
            }

            // Create the temporary upload directory if it doesn't exist
            File tempDirectory = new File(tempUploadDir);
            if (!tempDirectory.exists()) {
                tempDirectory.mkdirs();
            }

            // Save the file to the temporary upload directory
            String tempFilePath = tempUploadDir + File.separator + file.getOriginalFilename();
            file.transferTo(new File(tempFilePath));

            // Ensure unique file name in the final upload directory
            String uniqueFileName = ensureUniqueFileName(uploadDir, file.getOriginalFilename());

            // Move the file to the final destination
            String finalFilePath = uploadDir + File.separator + uniqueFileName;
            new File(tempFilePath).renameTo(new File(finalFilePath));

            // saving the new assignment submission following (DREW CODE)
            AssignmentSubmission submission = new AssignmentSubmission(existingPerson, finalFilePath, submissionTime, submissionNumber);
            subDetailsService.save(submission); // saving the new submission
            assignmentDetailsService.addSubmissionToAssignment(submittedAssignment, submission); // adding the submission to the assignment
            assignmentDetailsService.save(submittedAssignment);
            return new ResponseEntity<>("Submission to the assignment \"" + submittedAssignment.getName() + "\" was successful!", HttpStatus.CREATED);
            // finished processing!!! wow!!!
        } catch (IOException e) {
            return ResponseEntity.status(500).body("Failed to upload file");
        }
    }

Prevention of Duplicate Files

In earlier renditions, if a file has the same name as another file, it would not be uploaded and the reference made in the database would be to another person's submission. This is a big error and even bigger privacy concern! Here's how Drew fixed it:

    // Method to ensure unique file name
    private String ensureUniqueFileName(String uploadDir, String originalFilename) {
        File file = new File(uploadDir + File.separator + originalFilename);
        String name = originalFilename;
        String extension = "";
        int dotIndex = originalFilename.lastIndexOf('.');
        if (dotIndex != -1) {
            name = originalFilename.substring(0, dotIndex);
            extension = originalFilename.substring(dotIndex);
        }

        int counter = 0;
        while (file.exists()) {
            counter++;
            String newName = name + " (" + counter + ")" + extension;
            file = new File(uploadDir + File.separator + newName);
        }

        return file.getName();
    }

Here's proof of it working:

image

Additional Notes