Skip to content
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ dependencies {
implementation 'de.unistuttgart.iste.meitrex:course_service:1.1.0rc2'
implementation 'de.unistuttgart.iste.meitrex:user_service:1.0.0rc1'
implementation 'com.google.code.gson:gson:2.13.1'
implementation 'com.github.slugify:slugify:3.0.7'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-graphql'
implementation 'org.springframework.boot:spring-boot-starter-validation'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ public ExternalCourse getExternalCourse(@Argument final UUID courseId, @ContextV

@QueryMapping
public List<Grading> getGradingsForAssignment(@Argument final UUID assessmentId, @ContextValue final LoggedInUser currentUser) {
log.info("[GRADING-FLOW] GraphQL query received: getGradingsForAssignment for assessmentId={}, userId={}",
assessmentId, currentUser.getId());
return gradingService.getGradingsForAssignment(assessmentId, currentUser);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,17 @@ public class GradingService {
private final ExternalCourseRepository externalCourseRepository;

public List<Grading> getGradingsForAssignment(final UUID assignmentId, final LoggedInUser currentUser) {
log.info("[GRADING-FLOW] Starting getGradingsForAssignment for assignmentId={}, userId={}",
assignmentId, currentUser.getId());

final AssignmentEntity assignment = assignmentService.requireAssignmentExists(assignmentId);

LoggedInUser.CourseMembership courseMembership = currentUser.getCourseMemberships().stream()
.filter(membership -> membership.getCourseId().equals(assignment.getCourseId())).findFirst()
.orElseThrow(() -> new NoAccessToCourseException(assignment.getCourseId(), "User is not a member of the course."));

if (assignment.getAssignmentType() == AssignmentType.CODE_ASSIGNMENT){
log.info("[GRADING-FLOW] Processing CODE_ASSIGNMENT type");
List<Grading> gradings = new ArrayList<>();

// Always get the current student's grading so that they see it in student's view (even if the user is a tutor/admin)
Expand Down Expand Up @@ -189,6 +193,9 @@ private List<Grading> getCodeAssignmentGradingForAdmin(final AssignmentEntity as


private List<Grading> getCodeAssignmentGradingForStudent(final AssignmentEntity assignment, final LoggedInUser currentUser) {
log.info("[GRADING-FLOW] >>> getCodeAssignmentGradingForStudent START - assignmentId={}, studentId={}",
assignment.getId(), currentUser.getId());

GradingEntity gradingEntity = ensureGradingEntityExists(assignment.getId(), currentUser.getId());

findAndSetRepositoryLinkIfMissing(gradingEntity, assignment, currentUser);
Expand All @@ -197,7 +204,9 @@ private List<Grading> getCodeAssignmentGradingForStudent(final AssignmentEntity
syncAndUpdateGrading(gradingEntity, assignment, currentUser);
}

log.info("[GRADING-FLOW] Saving grading entity to database");
gradingEntity = gradingRepository.save(gradingEntity);
log.info("[GRADING-FLOW] <<< getCodeAssignmentGradingForStudent END - returning grading");
return List.of(assignmentMapper.gradingEntityToDto(gradingEntity));
}

Expand All @@ -208,8 +217,9 @@ private List<Grading> getCodeAssignmentGradingForStudent(final AssignmentEntity
private GradingEntity ensureGradingEntityExists(final UUID assignmentId, final UUID studentId) {
final GradingEntity.PrimaryKey pk = new GradingEntity.PrimaryKey(assignmentId, studentId);
GradingEntity gradingEntity = gradingRepository.findById(pk).orElse(null);

if (gradingEntity == null) {
log.info("[GRADING-FLOW] Creating new grading entity for first-time access");
gradingEntity = GradingEntity.builder().primaryKey(pk).build();

CodeAssignmentGradingMetadataEntity metadata = CodeAssignmentGradingMetadataEntity.builder()
Expand Down Expand Up @@ -243,6 +253,7 @@ private void findAndSetRepositoryLinkIfMissing(final GradingEntity gradingEntity
return;
}

log.info("[GRADING-FLOW] Repository link not found, attempting to find student repository");
try {
String assignmentName = contentServiceClient.queryContentsOfCourse(currentUser.getId(), assignment.getCourseId()).stream()
.filter(assignmentDto -> assignmentDto.getId().equals(assignment.getId()))
Expand All @@ -253,11 +264,14 @@ private void findAndSetRepositoryLinkIfMissing(final GradingEntity gradingEntity
String courseTitle = courseServiceClient.queryCourseById(assignment.getCourseId()).getTitle();
String organizationName = externalCourseRepository.findById(courseTitle).get().getOrganizationName();

log.info("[GRADING-FLOW] Calling findRepository with assignmentName={}, organizationName={}",
assignmentName, organizationName);
String repoLink = codeAssessmentProvider.findRepository(assignmentName, organizationName, currentUser);
log.info("[GRADING-FLOW] Repository link found: {}", repoLink != null ? repoLink : "NULL");
gradingEntity.getCodeAssignmentGradingMetadata().setRepoLink(repoLink);
} catch (ExternalPlatformConnectionException | UserServiceConnectionException |
ContentServiceConnectionException | CourseServiceConnectionException e) {
log.error("Failed to find repository for assignment {} and student {}: {}",
log.error("[GRADING-FLOW] ERROR: Failed to find repository for assignment {} and student {}: {}",
assignment.getId(), currentUser.getId(), e.toString());
}
}
Expand All @@ -268,12 +282,16 @@ private void findAndSetRepositoryLinkIfMissing(final GradingEntity gradingEntity
private void syncAndUpdateGrading(final GradingEntity gradingEntity,
final AssignmentEntity assignment,
final LoggedInUser currentUser) {
log.info("[GRADING-FLOW] Repository link exists: {}",
gradingEntity.getCodeAssignmentGradingMetadata().getRepoLink());
ExternalGrading externalGrading;
try {
externalGrading = codeAssessmentProvider.syncGradeForStudent(
gradingEntity.getCodeAssignmentGradingMetadata().getRepoLink(), currentUser);
log.info("[GRADING-FLOW] syncGradeForStudent completed - achievedPoints={}, totalPoints={}, status={}",
externalGrading.achievedPoints(), externalGrading.totalPoints(), externalGrading.status());
} catch (ExternalPlatformConnectionException | UserServiceConnectionException e) {
log.error("Failed to sync student grade for assignment {} and student {}: {}",
log.error("[GRADING-FLOW] ERROR: Failed to sync student grade for assignment {} and student {}: {}",
assignment.getId(), currentUser.getId(), e.toString());
return;
}
Expand Down Expand Up @@ -318,20 +336,32 @@ private void handleCodeSubmissionEvent(final GradingEntity gradingEntity,
return;
}

log.info("[GRADING-FLOW] Attempting to fetch student code from repository");
try {
if (codeAssessmentProvider instanceof de.unistuttgart.iste.meitrex.assignment_service.service.code_assignment.GithubClassroom githubClassroom) {
log.info("[GRADING-FLOW] Calling fetchStudentCode for repoLink={}",
metadata.getRepoLink());

de.unistuttgart.iste.meitrex.assignment_service.service.code_assignment.StudentCodeSubmission codeSubmission =
githubClassroom.fetchStudentCode(metadata.getRepoLink(), currentUser);

log.info("[GRADING-FLOW] fetchStudentCode completed successfully - files count: {}, commit: {}",
codeSubmission.getFiles().size(), codeSubmission.getCommitSha());

codeSubmission.setAssignmentId(assignment.getId());
codeSubmission.setCourseId(assignment.getCourseId());

log.info("[GRADING-FLOW] Publishing StudentCodeSubmittedEvent");
publishStudentCodeSubmittedEvent(codeSubmission);
log.info("[GRADING-FLOW] StudentCodeSubmittedEvent published successfully");

metadata.setLastProcessedCommitSha(currentCommit != null ? currentCommit : "NO_COMMIT_SHA_PROCESSED");
} else {
log.warn("[GRADING-FLOW] Code assessment provider is not GithubClassroom instance: {}",
codeAssessmentProvider.getClass().getName());
}
} catch (ExternalPlatformConnectionException | UserServiceConnectionException e) {
log.error("Failed to fetch student code for assignment {} and student {}: {}",
log.error("[GRADING-FLOW] ERROR: Failed to fetch student code for assignment {} and student {}: {}",
assignment.getId(), currentUser.getId(), e.toString());
}
}
Expand Down Expand Up @@ -365,6 +395,7 @@ private boolean shouldSendCodeSubmissionEvent(final String lastProcessedCommit,
}
}
}
}

/**
* Returns gradings for the current user on the given assignment.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package de.unistuttgart.iste.meitrex.assignment_service.service.code_assignment;

import com.google.gson.*;
import com.github.slugify.Slugify;
import de.unistuttgart.iste.meitrex.assignment_service.exception.ExternalPlatformConnectionException;
import de.unistuttgart.iste.meitrex.assignment_service.persistence.entity.assignment.ExternalCodeAssignmentEntity;
import de.unistuttgart.iste.meitrex.assignment_service.persistence.repository.AssignmentRepository;
Expand Down Expand Up @@ -511,13 +512,17 @@ private String extractGradingTableAsHtml(String logs) {
@Override
public String findRepository(final String assignmentName, final String organizationName, final LoggedInUser currentUser)
throws ExternalPlatformConnectionException, UserServiceConnectionException {
log.info("[GITHUB-API] >>> findRepository START - assignmentName={}, organizationName={}, userId={}",
assignmentName, organizationName, currentUser.getId());
try {
AccessToken queryTokenResponse = userServiceClient.queryAccessToken(currentUser, NAME);
String token = queryTokenResponse.getAccessToken();
String githubUsername = queryTokenResponse.getExternalUserId();

String slug = assignmentName.toLowerCase().replaceAll("\\s+", "-");
final Slugify slg = Slugify.builder().build();
String slug = slg.slugify(assignmentName);
String repoName = slug + "-" + githubUsername;
log.info("[GITHUB-API] Constructed repository name: {}", repoName);

HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(basePath + "/repos/" + organizationName + "/" + repoName))
Expand All @@ -528,6 +533,7 @@ public String findRepository(final String assignmentName, final String organizat
.build();

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
log.info("[GITHUB-API] GitHub API response status: {}", response.statusCode());

if (response.statusCode() == 404) {
return null;
Expand All @@ -542,8 +548,10 @@ public String findRepository(final String assignmentName, final String organizat

} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("[GITHUB-API] Interrupted while fetching student repo link");
throw new ExternalPlatformConnectionException("Interrupted while fetching student repo link", e);
} catch (IOException e) {
log.error("[GITHUB-API] IOException while fetching student repo link");
throw new ExternalPlatformConnectionException("Error fetching student repo link", e);
}

Expand Down Expand Up @@ -612,17 +620,20 @@ public ExternalCourse getExternalCourse(final String courseTitle, final LoggedIn
*/
public StudentCodeSubmission fetchStudentCode(String repoLink, LoggedInUser currentUser)
throws ExternalPlatformConnectionException, UserServiceConnectionException {
log.info("[GITHUB-API] >>> fetchStudentCode START - repoLink={}, userId={}", repoLink, currentUser.getId());
try {
AccessToken tokenResponse = userServiceClient.queryAccessToken(currentUser, NAME);
String token = tokenResponse.getAccessToken();

URI uri = URI.create(repoLink);
String[] parts = uri.getPath().split("/");
if (parts.length < 3) {
log.error("[GITHUB-API] Invalid repo URL format: {}, parts count: {}", repoLink, parts.length);
throw new ExternalPlatformConnectionException("Invalid repo URL: " + repoLink);
}
String owner = parts[1];
String repo = parts[2];
log.info("[GITHUB-API] Parsed repository - owner={}, repo={}", owner, repo);

HttpRequest repoRequest = HttpRequest.newBuilder()
.uri(URI.create(basePath + "/repos/" + owner + "/" + repo))
Expand All @@ -633,7 +644,11 @@ public StudentCodeSubmission fetchStudentCode(String repoLink, LoggedInUser curr
.build();

HttpResponse<String> repoResponse = client.send(repoRequest, HttpResponse.BodyHandlers.ofString());
log.info("[GITHUB-API] Repository info response status: {}", repoResponse.statusCode());

if (repoResponse.statusCode() != 200) {
log.error("[GITHUB-API] Failed to fetch repository info: status={}, body={}",
repoResponse.statusCode(), repoResponse.body());
throw new ExternalPlatformConnectionException("Failed to fetch repository info: " + repoResponse.body());
}

Expand All @@ -649,7 +664,11 @@ public StudentCodeSubmission fetchStudentCode(String repoLink, LoggedInUser curr
.build();

HttpResponse<String> commitResponse = client.send(commitRequest, HttpResponse.BodyHandlers.ofString());
log.info("[GITHUB-API] Commit info response status: {}", commitResponse.statusCode());

if (commitResponse.statusCode() != 200) {
log.error("[GITHUB-API] Failed to fetch commit info: status={}, body={}",
commitResponse.statusCode(), commitResponse.body());
throw new ExternalPlatformConnectionException("Failed to fetch commit info: " + commitResponse.body());
}

Expand All @@ -661,7 +680,9 @@ public StudentCodeSubmission fetchStudentCode(String repoLink, LoggedInUser curr
OffsetDateTime commitDate = OffsetDateTime.parse(commitDateStr);

Map<String, String> files = new HashMap<>();
log.info("[GITHUB-API] Starting recursive file fetch from repository");
fetchFilesRecursively(owner, repo, defaultBranch, "", token, files);
log.info("[GITHUB-API] Finished fetching files - total files retrieved: {}", files.size());

return StudentCodeSubmission.builder()
.studentId(currentUser.getId())
Expand All @@ -674,9 +695,14 @@ public StudentCodeSubmission fetchStudentCode(String repoLink, LoggedInUser curr

} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.error("[GITHUB-API] Interrupted while fetching student code from {}", repoLink);
throw new ExternalPlatformConnectionException("Interrupted while fetching student code", e);
} catch (IOException e) {
log.error("[GITHUB-API] IOException while fetching student code from {}", repoLink);
throw new ExternalPlatformConnectionException("Error fetching student code", e);
} catch (Exception e) {
log.error("[GITHUB-API] Unexpected error while fetching student code from {}", repoLink);
throw new ExternalPlatformConnectionException("Unexpected error fetching student code: " + e.getMessage());
}
}

Expand Down Expand Up @@ -705,7 +731,8 @@ private void fetchFilesRecursively(String owner, String repo, String branch, Str

HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200) {
log.warn("Failed to fetch contents for path '{}': {}", path, response.body());
log.warn("[GITHUB-API] Failed to fetch contents for path '{}': status={}, body={}",
path, response.statusCode(), response.body());
return;
}

Expand All @@ -728,7 +755,7 @@ private void fetchFilesRecursively(String owner, String repo, String branch, Str
if (fileResponse.statusCode() == 200) {
files.put(itemPath, fileResponse.body());
} else {
log.warn("Failed to fetch file content for: {}", itemPath);
log.warn("[GITHUB-API] Failed to fetch file content for: {}, status={}", itemPath, fileResponse.statusCode());
}
} else if ("dir".equals(type)) {
fetchFilesRecursively(owner, repo, branch, itemPath, token, files);
Expand Down
Loading