CourseStaffCSVController.java

package edu.ucsb.cs156.frontiers.controllers;

import com.opencsv.CSVReader;
import com.opencsv.exceptions.CsvException;
import edu.ucsb.cs156.frontiers.entities.Course;
import edu.ucsb.cs156.frontiers.entities.CourseStaff;
import edu.ucsb.cs156.frontiers.enums.OrgStatus;
import edu.ucsb.cs156.frontiers.errors.EntityNotFoundException;
import edu.ucsb.cs156.frontiers.repositories.CourseRepository;
import edu.ucsb.cs156.frontiers.repositories.CourseStaffRepository;
import edu.ucsb.cs156.frontiers.services.UpdateUserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Map;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.server.ResponseStatusException;

@Tag(name = "CourseStaff")
@RequestMapping("/api/coursestaff")
@RestController("CourseStaffCSVController")
@Slf4j
public class CourseStaffCSVController extends ApiController {

  @Autowired private CourseStaffRepository courseStaffRepository;

  @Autowired private CourseRepository courseRepository;

  @Autowired private UpdateUserService updateUserService;

  public static final String STAFF_CSV_HEADERS = "firstName,lastName,email";

  @Operation(summary = "Upload Course Staff from CSV")
  @PreAuthorize("@CourseSecurity.hasInstructorPermissions(#root, #courseId)")
  @PostMapping(
      value = "/upload/csv",
      consumes = {"multipart/form-data"})
  public Map<String, Integer> uploadStaffCSV(
      @Parameter(name = "courseId") @RequestParam Long courseId,
      @Parameter(name = "file") @RequestParam("file") MultipartFile file)
      throws IOException, CsvException {

    Course course =
        courseRepository
            .findById(courseId)
            .orElseThrow(() -> new EntityNotFoundException(Course.class, courseId.toString()));

    int count = 0;

    try (InputStream inputStream = new BufferedInputStream(file.getInputStream());
        InputStreamReader reader = new InputStreamReader(inputStream);
        CSVReader csvReader = new CSVReader(reader)) {

      String[] headers = csvReader.readNext();
      if (headers == null) {
        throw new ResponseStatusException(HttpStatus.BAD_REQUEST, "CSV file is empty");
      }

      validateHeaders(headers);

      String[] row;
      while ((row = csvReader.readNext()) != null) {
        if (row.length < 3) {
          throw new ResponseStatusException(
              HttpStatus.BAD_REQUEST,
              String.format(
                  "CSV row does not have enough columns. Expected at least 3, got %d", row.length));
        }

        CourseStaff courseStaff =
            CourseStaff.builder()
                .firstName(row[0].trim())
                .lastName(row[1].trim())
                .email(row[2].trim())
                .course(course)
                .build();

        if (course.getInstallationId() != null) {
          courseStaff.setOrgStatus(OrgStatus.JOINCOURSE);
        } else {
          courseStaff.setOrgStatus(OrgStatus.PENDING);
        }

        CourseStaff savedCourseStaff = courseStaffRepository.save(courseStaff);
        updateUserService.attachUserToCourseStaff(savedCourseStaff);
        count++;
      }
    }

    return Map.of("count", count);
  }

  static void validateHeaders(String[] headers) {
    String[] expectedHeaders = STAFF_CSV_HEADERS.split(",");
    if (headers.length < expectedHeaders.length) {
      throw new ResponseStatusException(
          HttpStatus.BAD_REQUEST,
          String.format(
              "CSV headers do not match expected format. Expected: %s", STAFF_CSV_HEADERS));
    }
    for (int i = 0; i < expectedHeaders.length; i++) {
      if (!expectedHeaders[i].trim().equalsIgnoreCase(headers[i].trim())) {
        throw new ResponseStatusException(
            HttpStatus.BAD_REQUEST,
            String.format(
                "CSV headers do not match expected format. Expected: %s", STAFF_CSV_HEADERS));
      }
    }
  }
}