PullTeamsFromCanvasJob.java

package edu.ucsb.cs156.frontiers.jobs;

import edu.ucsb.cs156.frontiers.entities.Course;
import edu.ucsb.cs156.frontiers.entities.RosterStudent;
import edu.ucsb.cs156.frontiers.entities.Team;
import edu.ucsb.cs156.frontiers.entities.TeamMember;
import edu.ucsb.cs156.frontiers.errors.DuplicateGroupException;
import edu.ucsb.cs156.frontiers.models.CanvasGroup;
import edu.ucsb.cs156.frontiers.repositories.CourseRepository;
import edu.ucsb.cs156.frontiers.repositories.TeamMemberRepository;
import edu.ucsb.cs156.frontiers.repositories.TeamRepository;
import edu.ucsb.cs156.frontiers.services.CanvasService;
import edu.ucsb.cs156.frontiers.services.jobs.JobContext;
import edu.ucsb.cs156.frontiers.services.jobs.JobContextConsumer;
import edu.ucsb.cs156.frontiers.utilities.CanonicalFormConverter;
import jakarta.transaction.Transactional;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import lombok.Builder;

@Builder
public class PullTeamsFromCanvasJob implements JobContextConsumer {

  Course course;
  String groupsetId;
  CanvasService canvasService;
  TeamRepository teamRepository;
  TeamMemberRepository teamMemberRepository;
  CourseRepository courseRepository;

  @Override
  @Transactional
  public void accept(JobContext ctx) throws Exception {
    // Prior to attempting to optimize this code or modify any repository calls, please see here:
    // https://github.com/ucsb-cs156/ucsb-cs156.github.io/blob/main/topics/spring_boot/spring_boot_entity_relationships.md
    Optional<Course> courseOpt = courseRepository.findById(course.getId());
    course = courseOpt.get();
    ctx.log("Processing...");
    List<CanvasGroup> groups = canvasService.getCanvasGroups(course, groupsetId);
    HashMap<String, RosterStudent> mappedStudents = new HashMap<>();
    HashMap<String, Team> teamsByName = new HashMap<>();
    HashMap<Integer, Team> teamsByCanvasId = new HashMap<>();
    course.getRosterStudents().forEach(student -> mappedStudents.put(student.getEmail(), student));
    course
        .getTeams()
        .forEach(
            team -> {
              teamsByName.put(team.getName(), team);
              if (team.getCanvasId() != null) {
                teamsByCanvasId.put(team.getCanvasId(), team);
              }
            });

    List<Team> createdTeams = new ArrayList<>();

    for (CanvasGroup group : groups) {
      Team linked;
      if (teamsByCanvasId.containsKey(group.getId())) {
        linked = teamsByCanvasId.get(group.getId());
      } else if (teamsByName.containsKey(group.getName().trim())) {
        linked = teamsByName.get(group.getName().trim());
      } else {
        linked =
            Team.builder()
                .name(group.getName())
                .teamMembers(new ArrayList<>())
                .course(course)
                .build();
      }
      if (linked.getCanvasId() != null && !linked.getCanvasId().equals(group.getId())) {
        ctx.log("Duplicate group found: " + group.getName() + " with canvasId: " + group.getId());
        throw new DuplicateGroupException();
      }

      HashSet<String> processedEmails = new HashSet<>();

      linked.setCanvasId(group.getId());
      ctx.log("Processing group: " + group.getName() + " with canvasId: " + group.getId());
      Team finalLinked = linked;
      group
          .getMembers()
          .forEach(
              email -> {
                RosterStudent student =
                    mappedStudents.get(CanonicalFormConverter.convertToValidEmail(email));
                if (student != null) {
                  processedEmails.add(student.getEmail());
                  if (student.getTeamMembers().stream()
                      .anyMatch(teamMember -> teamMember.getTeam().equals(finalLinked))) {
                    return;
                  } else {
                    finalLinked
                        .getTeamMembers()
                        .add(TeamMember.builder().team(finalLinked).rosterStudent(student).build());
                  }
                }
              });
      createdTeams.add(finalLinked);
      Set<TeamMember> removedMembers =
          finalLinked.getTeamMembers().stream()
              .filter(
                  teamMember -> !processedEmails.contains(teamMember.getRosterStudent().getEmail()))
              .collect(Collectors.toSet());
      ctx.log("Group members to be removed:" + removedMembers);
      removedMembers.forEach(
          teamMember -> {
            teamMember.getTeam().getTeamMembers().remove(teamMember);
            teamMember.getRosterStudent().getTeamMembers().remove(teamMember);
          });
      teamMemberRepository.deleteAll(removedMembers);
    }
    teamRepository.saveAll(createdTeams);
  }
}