Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.testify.Testify_Backend.model.Organization;
import com.testify.Testify_Backend.repository.CandidateRepository;
import com.testify.Testify_Backend.requests.exam_management.CandidateExamAnswerRequest;
import com.testify.Testify_Backend.requests.exam_management.CandidateExamDetailsDTO;
import com.testify.Testify_Backend.responses.GenericResponse;
import com.testify.Testify_Backend.responses.candidate_management.CandidateExam;
import com.testify.Testify_Backend.responses.candidate_management.CandidateResponse;
Expand Down Expand Up @@ -104,4 +105,9 @@ public CandidateExamAnswerResponse getCandidateAnswers(@RequestBody CandidateExa
return examManagementService.getCandidateAnswers(request.getCandidateId());
}

@GetMapping("/{id}/exam-details")
public List<CandidateExamDetailsDTO> getCandidateExamDetails(@PathVariable String id) {
return candidateService.getCandidateExamDetails(id);
}
Comment on lines +108 to +111
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add security checks and error handling

The endpoint needs additional security measures and error handling:

  1. Add authentication/authorization checks using @PreAuthorize
  2. Wrap response in ResponseEntity to handle different HTTP status codes
  3. Add exception handling for invalid IDs or service errors

Consider refactoring to:

-@GetMapping("/{id}/exam-details")
-public List<CandidateExamDetailsDTO> getCandidateExamDetails(@PathVariable String id) {
-    return candidateService.getCandidateExamDetails(id);
-}
+@GetMapping("/{id}/exam-details")
+@PreAuthorize("hasRole('CANDIDATE') or hasRole('ADMIN')")
+public ResponseEntity<?> getCandidateExamDetails(@PathVariable String id) {
+    try {
+        List<CandidateExamDetailsDTO> details = candidateService.getCandidateExamDetails(id);
+        return ResponseEntity.ok(details);
+    } catch (EntityNotFoundException e) {
+        return ResponseEntity.notFound().build();
+    } catch (Exception e) {
+        return ResponseEntity.internalServerError().body("Error retrieving exam details");
+    }
+}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@GetMapping("/{id}/exam-details")
public List<CandidateExamDetailsDTO> getCandidateExamDetails(@PathVariable String id) {
return candidateService.getCandidateExamDetails(id);
}
@GetMapping("/{id}/exam-details")
@PreAuthorize("hasRole('CANDIDATE') or hasRole('ADMIN')")
public ResponseEntity<?> getCandidateExamDetails(@PathVariable String id) {
try {
List<CandidateExamDetailsDTO> details = candidateService.getCandidateExamDetails(id);
return ResponseEntity.ok(details);
} catch (EntityNotFoundException e) {
return ResponseEntity.notFound().build();
} catch (Exception e) {
return ResponseEntity.internalServerError().body("Error retrieving exam details");
}
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface ExamCandidateGradeRepository extends JpaRepository<ExamCandidateGrade, String> {
List<ExamCandidateGrade> findByCandidateID(String candidateId);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Correct method name to follow Java naming conventions.

The method name findByCandidateID should be updated to findByCandidateId to adhere to standard Java naming conventions and ensure consistency with entity field naming.

Apply this diff to correct the method name:

public interface ExamCandidateGradeRepository extends JpaRepository<ExamCandidateGrade, String> {
-    List<ExamCandidateGrade> findByCandidateID(String candidateId);
+    List<ExamCandidateGrade> findByCandidateId(String candidateId);
}

Committable suggestion skipped: line range outside the PR's diff.

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.testify.Testify_Backend.requests.exam_management;

import lombok.AllArgsConstructor;
import lombok.Data;

@Data
@AllArgsConstructor
public class CandidateExamDetailsDTO {
private String grade;
private String score;
private String examName;
private String organizationName;
Comment on lines +9 to +12
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Consider changing 'score' field to a numeric data type.

Since score represents a numeric value, it would be more appropriate to use a numeric data type such as double or BigDecimal instead of String. This change will facilitate numerical operations and comparisons.

Apply this diff to update the data type:

public class CandidateExamDetailsDTO {
    private String grade;
-   private String score;
+   private double score;
    private String examName;
    private String organizationName;
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
private String grade;
private String score;
private String examName;
private String organizationName;
private String grade;
private double score;
private String examName;
private String organizationName;

}
Original file line number Diff line number Diff line change
@@ -1,14 +1,20 @@
package com.testify.Testify_Backend.responses.exam_management;

import com.testify.Testify_Backend.enums.ExamType;
import lombok.AllArgsConstructor;
import lombok.Data;

import java.time.LocalDateTime;
import java.util.List;

@Data
@AllArgsConstructor
public class CandidateExamAnswerResponse {
private Long examId;
private String examName;
private ExamType examType;
private Long sessionId;
private LocalDateTime endTime;
private List<AnswerDetail> answers;

@Data
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.testify.Testify_Backend.model.Candidate;
import com.testify.Testify_Backend.model.Organization;
import com.testify.Testify_Backend.requests.exam_management.CandidateExamDetailsDTO;
import com.testify.Testify_Backend.responses.candidate_management.CandidateExam;
import com.testify.Testify_Backend.responses.candidate_management.CandidateResponse;
import com.testify.Testify_Backend.responses.candidate_management.CandidateProfile;
Expand All @@ -19,4 +20,5 @@ public interface CandidateService {
public CandidateProfile getCandidateProfile();
public String updateCandidateProfile(Candidate candidate);
public String deleteCandidateProfile(long id);
List<CandidateExamDetailsDTO> getCandidateExamDetails(String candidateId);
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
package com.testify.Testify_Backend.service;

import com.testify.Testify_Backend.enums.ExamStatus;
import com.testify.Testify_Backend.model.Candidate;
import com.testify.Testify_Backend.model.CandidateExamSession;
import com.testify.Testify_Backend.model.Exam;
import com.testify.Testify_Backend.model.Organization;
import com.testify.Testify_Backend.model.*;
import com.testify.Testify_Backend.repository.*;
import com.testify.Testify_Backend.requests.exam_management.CandidateExamDetailsDTO;
import com.testify.Testify_Backend.responses.GenericResponse;
import com.testify.Testify_Backend.responses.candidate_management.CandidateExam;
import com.testify.Testify_Backend.responses.candidate_management.CandidateResponse;
Expand Down Expand Up @@ -56,6 +54,8 @@ public class CandidateServiceImpl implements CandidateService {
@Autowired
private final ExamSessionRepository examSessionRepository;

private final ExamCandidateGradeRepository examCandidateGradeRepository;

@Override
public List<CandidateExam> getCandidateExams(String status) {
String currentUserEmail = SecurityContextHolder.getContext().getAuthentication().getName();
Expand Down Expand Up @@ -233,4 +233,31 @@ public String deleteCandidateProfile(long id){
}
}

@Override
@Transactional
public List<CandidateExamDetailsDTO> getCandidateExamDetails(String candidateId) {
// Retrieve all grade details for the candidate
List<ExamCandidateGrade> grades = examCandidateGradeRepository.findByCandidateID(candidateId);

// Map the results to CandidateExamDetailsDTO
return grades.stream().map(candidateGrade -> {
Optional<Exam> examOptional = examRepository.findById(Long.valueOf(candidateGrade.getExamID()));

if (examOptional.isPresent()) {
Exam exam = examOptional.get();
Organization organization = exam.getOrganization();

// Return the data in the DTO format
return new CandidateExamDetailsDTO(
candidateGrade.getGrade(),
candidateGrade.getScore(),
exam.getTitle(),
organization != null ? organization.getFirstName() : "No Organization"
);
}

return null; // Or handle cases where the exam is not found
}).collect(Collectors.toList());
}
Comment on lines +236 to +261
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add input validation and improve error handling

The method implementation has several areas for improvement:

  1. Missing input validation for candidateId
  2. Potential NPE in the stream when handling null values
  3. Inefficient Optional handling
  4. Missing logging for debugging purposes

Consider refactoring to:

 @Override
 @Transactional
 public List<CandidateExamDetailsDTO> getCandidateExamDetails(String candidateId) {
+    if (candidateId == null || candidateId.trim().isEmpty()) {
+        throw new IllegalArgumentException("Candidate ID cannot be null or empty");
+    }
+    
+    log.debug("Fetching exam details for candidate: {}", candidateId);
+    
     // Retrieve all grade details for the candidate
     List<ExamCandidateGrade> grades = examCandidateGradeRepository.findByCandidateID(candidateId);
 
     // Map the results to CandidateExamDetailsDTO
     return grades.stream()
+        .filter(Objects::nonNull)
         .map(candidateGrade -> {
-            Optional<Exam> examOptional = examRepository.findById(Long.valueOf(candidateGrade.getExamID()));
-
-            if (examOptional.isPresent()) {
-                Exam exam = examOptional.get();
+            try {
+                Exam exam = examRepository.findById(Long.valueOf(candidateGrade.getExamID()))
+                    .orElseThrow(() -> new EntityNotFoundException("Exam not found: " + candidateGrade.getExamID()));
+                
                 Organization organization = exam.getOrganization();
-
+                String orgName = Optional.ofNullable(organization)
+                    .map(Organization::getFirstName)
+                    .orElse("No Organization");
+                
                 // Return the data in the DTO format
                 return new CandidateExamDetailsDTO(
                     candidateGrade.getGrade(),
                     candidateGrade.getScore(),
                     exam.getTitle(),
-                    organization != null ? organization.getFirstName() : "No Organization"
+                    orgName
                 );
+            } catch (Exception e) {
+                log.error("Error processing exam details for grade: {}", candidateGrade.getExamID(), e);
+                return null;
             }
-
-            return null; // Or handle cases where the exam is not found
-        }).collect(Collectors.toList());
+        })
+        .filter(Objects::nonNull)
+        .collect(Collectors.toList());
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
@Transactional
public List<CandidateExamDetailsDTO> getCandidateExamDetails(String candidateId) {
// Retrieve all grade details for the candidate
List<ExamCandidateGrade> grades = examCandidateGradeRepository.findByCandidateID(candidateId);
// Map the results to CandidateExamDetailsDTO
return grades.stream().map(candidateGrade -> {
Optional<Exam> examOptional = examRepository.findById(Long.valueOf(candidateGrade.getExamID()));
if (examOptional.isPresent()) {
Exam exam = examOptional.get();
Organization organization = exam.getOrganization();
// Return the data in the DTO format
return new CandidateExamDetailsDTO(
candidateGrade.getGrade(),
candidateGrade.getScore(),
exam.getTitle(),
organization != null ? organization.getFirstName() : "No Organization"
);
}
return null; // Or handle cases where the exam is not found
}).collect(Collectors.toList());
}
@Override
@Transactional
public List<CandidateExamDetailsDTO> getCandidateExamDetails(String candidateId) {
if (candidateId == null || candidateId.trim().isEmpty()) {
throw new IllegalArgumentException("Candidate ID cannot be null or empty");
}
log.debug("Fetching exam details for candidate: {}", candidateId);
// Retrieve all grade details for the candidate
List<ExamCandidateGrade> grades = examCandidateGradeRepository.findByCandidateID(candidateId);
// Map the results to CandidateExamDetailsDTO
return grades.stream()
.filter(Objects::nonNull)
.map(candidateGrade -> {
try {
Exam exam = examRepository.findById(Long.valueOf(candidateGrade.getExamID()))
.orElseThrow(() -> new EntityNotFoundException("Exam not found: " + candidateGrade.getExamID()));
Organization organization = exam.getOrganization();
String orgName = Optional.ofNullable(organization)
.map(Organization::getFirstName)
.orElse("No Organization");
// Return the data in the DTO format
return new CandidateExamDetailsDTO(
candidateGrade.getGrade(),
candidateGrade.getScore(),
exam.getTitle(),
orgName
);
} catch (Exception e) {
log.error("Error processing exam details for grade: {}", candidateGrade.getExamID(), e);
return null;
}
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -1133,10 +1133,16 @@ public CandidateExamAnswerResponse getCandidateAnswers(Long candidateId) {

return new CandidateExamAnswerResponse(
session.getExam().getId(),
session.getExam().getTitle(),
session.getExam().getExamType(),
session.getId(),
session.getEndTime(),
answerDetails
);
}



public ResponseEntity<GenericAddOrUpdateResponse> addProctorsToExam(long examId, List<String> proctorEmails) {
Optional<Exam> optionalExam = examRepository.findById(examId);
GenericAddOrUpdateResponse response = new GenericAddOrUpdateResponse();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.testify.Testify_Backend.model.*;
import com.testify.Testify_Backend.repository.*;
import com.testify.Testify_Backend.requests.exam_management.CandidateExamDetailsDTO;
import com.testify.Testify_Backend.requests.exam_management.ExamCandidateGradeRequest;
import com.testify.Testify_Backend.responses.EssayDetailsResponse;
import com.testify.Testify_Backend.responses.exam_management.ExamCandidateGradeResponse;
Expand All @@ -14,6 +15,7 @@
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

@Service
Expand Down Expand Up @@ -151,4 +153,5 @@ public List<ExamCandidateGradeResponse> getExamCandidateGrade() {

return examCandidateGradeResponses;
}

}
Loading