-
Notifications
You must be signed in to change notification settings - Fork 1
Kaumadi #68
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
WalkthroughThe changes in this pull request involve the enhancement of exam management functionalities within the Changes
Possibly related PRs
Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media? 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
🧹 Outside diff range and nitpick comments (9)
src/main/java/com/testify/Testify_Backend/service/ExamSetterService.java (2)
19-20: Consider adding parameter validation and documentationThe method signature looks good, but consider:
- Adding
@NotNullannotations for the parameters- Adding Javadoc to document the method's purpose, parameters, and possible exceptions
+ /** + * Retrieves exams assigned to a specific proctor within an organization. + * + * @param proctorId The ID of the proctor + * @param organizationId The ID of the organization + * @return List of exam responses + * @throws EntityNotFoundException if proctor or organization not found + */ + List<ExamResponse> getExamsForProctor(@NotNull Long proctorId, @NotNull Long organizationId);
24-24: Add validation and consider security implicationsFor the comment addition method:
- Add content length validation
- Consider adding a user/proctor parameter for audit purposes
- Consider returning a response object instead of void to confirm success
- void addCommentToCandidate(Long candidateId, Long examId, String content); + /** + * Adds a proctor's comment to a candidate's exam record. + * + * @param candidateId The ID of the candidate + * @param examId The ID of the exam + * @param content The comment content + * @param proctorId The ID of the proctor making the comment + * @return CommentResponse containing the created comment details + * @throws EntityNotFoundException if candidate or exam not found + * @throws ValidationException if content exceeds maximum length + */ + CommentResponse addCommentToCandidate( + @NotNull Long candidateId, + @NotNull Long examId, + @NotNull @Size(max = 1000) String content, + @NotNull Long proctorId + );src/main/java/com/testify/Testify_Backend/service/ExamSetterServiceImpl.java (2)
92-100: Optimize transaction management and simplify code using streams
- Add
readOnly = trueto the@Transactionalannotation to optimize performance for read-only operations.- Simplify the mapping from
List<Exam>toList<ExamResponse>using streams for cleaner code.Apply this diff:
@Override -@Transactional +@Transactional(readOnly = true) public List<ExamResponse> getExamsForProctor(Long proctorId, Long organizationId) { List<Exam> exams = examRepository.findByProctorIdAndOrganizationId(proctorId, organizationId); // Ensure repository method returns a List - List<ExamResponse> examResponses = new ArrayList<>(); - for (Exam exam : exams) { - examResponses.add(modelMapper.map(exam, ExamResponse.class)); - } + List<ExamResponse> examResponses = exams.stream() + .map(exam -> modelMapper.map(exam, ExamResponse.class)) + .collect(Collectors.toList()); return examResponses; }
104-112: Simplify collection mapping with streamsUse streams to convert
Set<Candidate>toSet<CandidateResponse>more concisely.Apply this diff:
@Override public Set<CandidateResponse> getCandidatesForExam(Long examId) { Set<Candidate> candidates = candidateRepository.findByExamId(examId); - Set<CandidateResponse> candidateResponses = new HashSet<>(); - for (Candidate candidate : candidates) { - candidateResponses.add(modelMapper.map(candidate, CandidateResponse.class)); - } + Set<CandidateResponse> candidateResponses = candidates.stream() + .map(candidate -> modelMapper.map(candidate, CandidateResponse.class)) + .collect(Collectors.toSet()); return candidateResponses; }src/main/java/com/testify/Testify_Backend/model/ProctorComment.java (2)
29-29: Enforce non-null constraint oncontentfieldAdd
@Column(nullable = false)to ensure that thecontentfield is not null in the database.Apply this diff:
+ @Column(nullable = false) private String content;
31-31: Automatically setcreatedAtusing@PrePersistUse the
@PrePersistannotation to automatically setcreatedAtbefore persisting.Add the following method to the class:
@PrePersist public void prePersist() { this.createdAt = LocalDateTime.now(); }src/main/java/com/testify/Testify_Backend/model/Candidate.java (1)
30-31: UseSetinstead ofListforcommentsto prevent duplicatesChange
commentsto aSetto avoid duplicate entries and improve performance.Apply this diff:
@OneToMany(mappedBy = "candidate", cascade = CascadeType.ALL, orphanRemoval = true) - private List<ProctorComment> comments; + private Set<ProctorComment> comments;Don't forget to import
java.util.Set.src/main/java/com/testify/Testify_Backend/repository/CandidateRepository.java (1)
27-28: Simplify repository method using derived queryReplace the custom
@Querywith a derived query method provided by Spring Data JPA.Apply this diff:
- @Query("SELECT c FROM Candidate c JOIN c.exams e WHERE e.id = :examId") - Set<Candidate> findByExamId(@Param("examId") Long examId); + Set<Candidate> findAllByExamsId(Long examId);src/main/java/com/testify/Testify_Backend/controller/ExamSetterController.java (1)
Line range hint
48-75: Add API documentation.Consider adding OpenAPI/Swagger annotations to document the new endpoints, their parameters, request/response models, and possible error scenarios.
Example for one endpoint:
@Operation(summary = "Get exams for proctor", description = "Retrieves all exams assigned to a proctor in an organization") @ApiResponses(value = { @ApiResponse(responseCode = "200", description = "Successfully retrieved exams"), @ApiResponse(responseCode = "400", description = "Invalid proctor or organization ID"), @ApiResponse(responseCode = "404", description = "Proctor or organization not found") }) @GetMapping("/proctor/{proctorId}/{organizationId}") public ResponseEntity<List<ExamResponse>> getExamsForProctor( @Parameter(description = "ID of the proctor") @PathVariable Long proctorId, @Parameter(description = "ID of the organization") @PathVariable Long organizationId ) { // ... existing code ... }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
📒 Files selected for processing (9)
src/main/java/com/testify/Testify_Backend/controller/ExamSetterController.java(4 hunks)src/main/java/com/testify/Testify_Backend/model/Candidate.java(2 hunks)src/main/java/com/testify/Testify_Backend/model/Exam.java(1 hunks)src/main/java/com/testify/Testify_Backend/model/ProctorComment.java(1 hunks)src/main/java/com/testify/Testify_Backend/repository/CandidateRepository.java(2 hunks)src/main/java/com/testify/Testify_Backend/repository/ExamRepository.java(1 hunks)src/main/java/com/testify/Testify_Backend/repository/ProctorCommentRepository.java(1 hunks)src/main/java/com/testify/Testify_Backend/service/ExamSetterService.java(2 hunks)src/main/java/com/testify/Testify_Backend/service/ExamSetterServiceImpl.java(5 hunks)
✅ Files skipped from review due to trivial changes (1)
- src/main/java/com/testify/Testify_Backend/repository/ProctorCommentRepository.java
🔇 Additional comments (4)
src/main/java/com/testify/Testify_Backend/service/ExamSetterService.java (2)
4-5: LGTM! Import statements are appropriate
The new imports align with the response types used in the added method signatures.
21-23: Consider using consistent collection types
The interface uses both List and Set for collections. Consider standardizing to one type unless there's a specific reason for the difference.
src/main/java/com/testify/Testify_Backend/repository/ExamRepository.java (1)
35-36: Validate the necessity of custom query
Ensure that the custom @Query is necessary and cannot be replaced with a derived query for maintainability.
Since you are joining on a collection, the custom query might be required. If possible, consider a derived query like findByProctorsIdAndOrganizationId(Long proctorId, Long organizationId).
src/main/java/com/testify/Testify_Backend/controller/ExamSetterController.java (1)
27-27: 🛠️ Refactor suggestion
Remove unused service dependency.
The examManagementService field appears to be unused in the controller. Either remove it or document its intended use.
- private final ExamManagementService examManagementService;Likely invalid or redundant comment.
| public void addCommentToCandidate(Long candidateId, Long examId, String content) { | ||
| Candidate candidate = candidateRepository.findById(candidateId) | ||
| .orElseThrow(() -> new RuntimeException("Candidate not found")); | ||
| Exam exam = examRepository.findById(examId).orElseThrow(() -> new RuntimeException("Exam not found")); | ||
|
|
||
| ProctorComment comment = new ProctorComment(); | ||
| comment.setCandidate(candidate); | ||
| comment.setExam(exam); | ||
| comment.setContent(content); | ||
| comment.setCreatedAt(LocalDateTime.now()); | ||
|
|
||
| proctorCommentRepository.save(comment); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enhance exception handling and transaction management
- Annotate the method with
@Transactionalto ensure atomicity of database operations. - Replace generic
RuntimeExceptionwith more specific exceptions likeEntityNotFoundExceptionfor clearer error handling.
Apply this diff:
+@Transactional
public void addCommentToCandidate(Long candidateId, Long examId, String content) {
Candidate candidate = candidateRepository.findById(candidateId)
- .orElseThrow(() -> new RuntimeException("Candidate not found"));
+ .orElseThrow(() -> new EntityNotFoundException("Candidate not found with id: " + candidateId));
Exam exam = examRepository.findById(examId)
- .orElseThrow(() -> new RuntimeException("Exam not found"));
+ .orElseThrow(() -> new EntityNotFoundException("Exam not found with id: " + examId));
ProctorComment comment = new ProctorComment();
comment.setCandidate(candidate);
comment.setExam(exam);
comment.setContent(content);
comment.setCreatedAt(LocalDateTime.now());
proctorCommentRepository.save(comment);
}Ensure to import jakarta.persistence.EntityNotFoundException or define a custom exception class if appropriate.
📝 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.
| public void addCommentToCandidate(Long candidateId, Long examId, String content) { | |
| Candidate candidate = candidateRepository.findById(candidateId) | |
| .orElseThrow(() -> new RuntimeException("Candidate not found")); | |
| Exam exam = examRepository.findById(examId).orElseThrow(() -> new RuntimeException("Exam not found")); | |
| ProctorComment comment = new ProctorComment(); | |
| comment.setCandidate(candidate); | |
| comment.setExam(exam); | |
| comment.setContent(content); | |
| comment.setCreatedAt(LocalDateTime.now()); | |
| proctorCommentRepository.save(comment); | |
| } | |
| @Transactional | |
| public void addCommentToCandidate(Long candidateId, Long examId, String content) { | |
| Candidate candidate = candidateRepository.findById(candidateId) | |
| .orElseThrow(() -> new EntityNotFoundException("Candidate not found with id: " + candidateId)); | |
| Exam exam = examRepository.findById(examId) | |
| .orElseThrow(() -> new EntityNotFoundException("Exam not found with id: " + examId)); | |
| ProctorComment comment = new ProctorComment(); | |
| comment.setCandidate(candidate); | |
| comment.setExam(exam); | |
| comment.setContent(content); | |
| comment.setCreatedAt(LocalDateTime.now()); | |
| proctorCommentRepository.save(comment); | |
| } |
| @OneToMany(mappedBy = "exam", cascade = CascadeType.ALL, orphanRemoval = true) | ||
| private List<ProctorComment> proctorComments; | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider initializing the List and managing JSON serialization.
While the relationship mapping is correct, consider these improvements:
- Initialize the List to prevent potential NPEs
- Add @JsonIgnore to prevent infinite recursion during serialization
- Add a helper method to manage the bidirectional relationship
Apply this diff:
@OneToMany(mappedBy = "exam", cascade = CascadeType.ALL, orphanRemoval = true)
+ @JsonIgnore
- private List<ProctorComment> proctorComments;
+ private List<ProctorComment> proctorComments = new ArrayList<>();
+
+ public void addProctorComment(ProctorComment comment) {
+ proctorComments.add(comment);
+ comment.setExam(this);
+ }
+
+ public void removeProctorComment(ProctorComment comment) {
+ proctorComments.remove(comment);
+ comment.setExam(null);
+ }📝 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.
| @OneToMany(mappedBy = "exam", cascade = CascadeType.ALL, orphanRemoval = true) | |
| private List<ProctorComment> proctorComments; | |
| @OneToMany(mappedBy = "exam", cascade = CascadeType.ALL, orphanRemoval = true) | |
| @JsonIgnore | |
| private List<ProctorComment> proctorComments = new ArrayList<>(); | |
| public void addProctorComment(ProctorComment comment) { | |
| proctorComments.add(comment); | |
| comment.setExam(this); | |
| } | |
| public void removeProctorComment(ProctorComment comment) { | |
| proctorComments.remove(comment); | |
| comment.setExam(null); | |
| } |
| @PostMapping("/{candidateId}/{examId}/proctorComments") | ||
| public ResponseEntity<String> addComment(@PathVariable Long candidateId, @PathVariable Long examId, @RequestBody String content) { | ||
| examSetterService.addCommentToCandidate(candidateId, examId, content); | ||
| return ResponseEntity.ok("Comment added successfully"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Enhance POST endpoint with proper request mapping and validation.
The endpoint needs:
- Proper @RequestBody mapping with a DTO
- Content-Type specification
- Input validation
- Better error handling
Apply these improvements:
@PostMapping("/{candidateId}/{examId}/proctorComments")
+ @Consumes(MediaType.APPLICATION_JSON_VALUE)
- public ResponseEntity<String> addComment(@PathVariable Long candidateId, @PathVariable Long examId, @RequestBody String content) {
+ public ResponseEntity<String> addComment(
+ @PathVariable Long candidateId,
+ @PathVariable Long examId,
+ @Valid @RequestBody ProctorCommentRequest request
+ ) {
+ if (candidateId == null || candidateId <= 0 || examId == null || examId <= 0) {
+ return ResponseEntity.badRequest().body("Invalid candidate or exam ID");
+ }
+ if (request.getContent() == null || request.getContent().trim().isEmpty()) {
+ return ResponseEntity.badRequest().body("Comment content cannot be empty");
+ }
- examSetterService.addCommentToCandidate(candidateId, examId, content);
+ examSetterService.addCommentToCandidate(candidateId, examId, request.getContent());
return ResponseEntity.ok("Comment added successfully");
}Create a new DTO:
@Data
public class ProctorCommentRequest {
@NotBlank(message = "Comment content is required")
private String content;
}| @GetMapping("/proctor/{proctorId}/{organizationId}") | ||
| public ResponseEntity<List<ExamResponse>> getExamsForProctor(@PathVariable Long proctorId, @PathVariable Long organizationId) { | ||
| List<ExamResponse> exams = examSetterService.getExamsForProctor(proctorId, organizationId); | ||
| return ResponseEntity.ok(exams); | ||
| } |
There was a problem hiding this comment.
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 error handling.
The endpoint needs validation for:
- Non-null and positive IDs
- Existence of proctor and organization
- Proctor's association with the organization
Add validation and error handling:
@GetMapping("/proctor/{proctorId}/{organizationId}")
public ResponseEntity<List<ExamResponse>> getExamsForProctor(@PathVariable Long proctorId, @PathVariable Long organizationId) {
+ if (proctorId == null || proctorId <= 0 || organizationId == null || organizationId <= 0) {
+ return ResponseEntity.badRequest().build();
+ }
List<ExamResponse> exams = examSetterService.getExamsForProctor(proctorId, organizationId);
return ResponseEntity.ok(exams);
}Committable suggestion skipped: line range outside the PR's diff.
| @GetMapping("/{examId}/candidates") | ||
| public ResponseEntity<Set<CandidateResponse>> getCandidatesForExam(@PathVariable Long examId) { | ||
| Set<CandidateResponse> candidates = examSetterService.getCandidatesForExam(examId); | ||
| return ResponseEntity.ok(candidates); | ||
| } |
There was a problem hiding this comment.
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 error handling.
The endpoint needs validation for:
- Non-null and positive exam ID
- Existence of exam
Add validation and error handling:
@GetMapping("/{examId}/candidates")
public ResponseEntity<Set<CandidateResponse>> getCandidatesForExam(@PathVariable Long examId) {
+ if (examId == null || examId <= 0) {
+ return ResponseEntity.badRequest().build();
+ }
Set<CandidateResponse> candidates = examSetterService.getCandidatesForExam(examId);
return ResponseEntity.ok(candidates);
}Committable suggestion skipped: line range outside the PR's diff.
Summary by CodeRabbit
Release Notes
New Features
ProctorCommententity allows multiple comments to be associated with candidates and exams.Enhancements
These changes improve the exam management experience, making it easier to track candidates and comments associated with exams.