CanvasService.java

package edu.ucsb.cs156.frontiers.services;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import edu.ucsb.cs156.frontiers.entities.Course;
import edu.ucsb.cs156.frontiers.entities.RosterStudent;
import edu.ucsb.cs156.frontiers.models.CanvasStudent;
import edu.ucsb.cs156.frontiers.validators.HasLinkedCanvasCourse;
import java.util.List;
import org.springframework.graphql.client.HttpSyncGraphQlClient;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.client.RestClient;

@Service
@Validated
public class CanvasService {

  private HttpSyncGraphQlClient graphQlClient;
  private ObjectMapper mapper;

  private static final String CANVAS_GRAPHQL_URL = "https://ucsb.instructure.com/api/graphql";

  public CanvasService(ObjectMapper mapper, RestClient.Builder builder) {
    this.graphQlClient =
        HttpSyncGraphQlClient.builder(builder.baseUrl(CANVAS_GRAPHQL_URL).build()).build();
    this.mapper = mapper;
  }

  /**
   * Fetches the roster of students from Canvas for the given course.
   *
   * @param course the Course entity containing canvasApiToken and canvasCourseId
   * @return list of RosterStudent objects from Canvas
   */
  public List<RosterStudent> getCanvasRoster(@HasLinkedCanvasCourse Course course) {
    String query =
        """
              query GetRoster($courseId: ID!) {
              course(id: $courseId) {
                usersConnection(filter: {enrollmentTypes: StudentEnrollment}) {
                  edges {
                    node {
                      firstName
                      lastName
                      sisId
                      email
                      integrationId
                    }
                  }
                }
              }
            }
            """;

    HttpSyncGraphQlClient authedClient =
        graphQlClient
            .mutate()
            .header("Authorization", "Bearer " + course.getCanvasApiToken())
            .build();

    List<CanvasStudent> students =
        authedClient
            .document(query)
            .variable("courseId", course.getCanvasCourseId())
            .retrieveSync("course.usersConnection.edges")
            .toEntityList(JsonNode.class)
            .stream()
            .map(node -> mapper.convertValue(node.get("node"), CanvasStudent.class))
            .toList();

    return students.stream()
        .map(
            student ->
                RosterStudent.builder()
                    .firstName(student.getFirstName())
                    .lastName(student.getLastName())
                    .studentId(student.getStudentId())
                    .email(student.getEmail())
                    .build())
        .toList();
  }
}