Skip to content
Closed
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 @@ -5,6 +5,7 @@
import com.testify.Testify_Backend.repository.CandidateRepository;
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;
import com.testify.Testify_Backend.responses.candidate_management.CandidateProfile;
import com.testify.Testify_Backend.responses.candidate_management.OrganizationCandidateView;
import com.testify.Testify_Backend.service.CandidateService;
Expand Down Expand Up @@ -34,6 +35,12 @@ public ResponseEntity<List<CandidateExam>> getCandidateExams(@RequestParam(value
return ResponseEntity.ok(candidateExams);
}


@GetMapping("/search")
public List<CandidateResponse> getAllCandidatesForSearch() {
return candidateService.getAllCandidatesForSearch();
}

@GetMapping("/exams/{id}")
public ResponseEntity<CandidateExam> getCandidateExamDetails(@PathVariable Integer id) {
CandidateExam candidateExams;
Expand Down Expand Up @@ -87,6 +94,4 @@ public ResponseEntity deleteCandidateProfile(long id){

}



}
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,18 @@
import com.testify.Testify_Backend.requests.exam_management.*;
import com.testify.Testify_Backend.responses.GenericAddOrUpdateResponse;
import com.testify.Testify_Backend.responses.GenericDeleteResponse;
import com.testify.Testify_Backend.responses.GenericResponse;
import com.testify.Testify_Backend.responses.exam_management.*;
import com.testify.Testify_Backend.service.ExamManagementService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.ArrayList;
import java.util.List;

@Slf4j
@RestController
@RequestMapping("/api/v1/exam")
@RequiredArgsConstructor
Expand Down Expand Up @@ -119,9 +123,51 @@ public ResponseEntity<List<GradeResponse>> getGradesByExamId(@PathVariable Long
return examManagementService.getGradesByExamId(examId);
}


@PutMapping("/{examId}/grades")
public ResponseEntity<GenericAddOrUpdateResponse> updateGrades(@PathVariable Long examId, @RequestBody List<GradeRequest> gradeRequestList) {
return examManagementService.updateGrades(examId, gradeRequestList);
}

@PostMapping("/{examId}/proctors")
public ResponseEntity<GenericAddOrUpdateResponse> addOrUpdateProctors(
@PathVariable Long examId,
@RequestBody List<String> emails) {
log.info("Adding proctors to examId: " + examId);
log.info("Emails: " + emails);
return examManagementService.addProctorsToExam(examId, emails);
}

@GetMapping("/{examId}/proctors")
public ResponseEntity<List<ProctorResponse>> getProctorsByExamId(@PathVariable Long examId) {
return examManagementService.getProctorsByExamId(examId);
}

@PostMapping("/{examId}/update-candidates")
public ResponseEntity<GenericAddOrUpdateResponse<CandidateEmailListRequest>> updateExamCandidates(
@PathVariable Long examId,
@RequestBody CandidateEmailListRequest candidateEmailListRequest) {

log.info("Updating candidates for examId: " + examId);
log.info("Emails: " + candidateEmailListRequest.getEmails());
return examManagementService.updateExamCandidates(examId, candidateEmailListRequest.getEmails());
}

@GetMapping("/{examId}/candidates")
public ResponseEntity<List<CandidateResponse>> getExamCandidates(@PathVariable Long examId) {
List<CandidateResponse> candidates = examManagementService.getCandidatesByExamId(examId);
return ResponseEntity.ok(candidates);
}
@GetMapping("/{examId}/conflicting-exams")
public ResponseEntity<List<ConflictExamResponse>> getConflictingExams(@PathVariable Long examId) {
List<ConflictExamResponse> conflictingExams = examManagementService.getExamsScheduledBetween(examId);
return ResponseEntity.ok(conflictingExams);
}

@GetMapping("/{examId}/conflicting-candidates")
public ResponseEntity<List<CandidateConflictExamResponse>> getCandidateConflictingExams(@PathVariable Long examId) {
List<CandidateConflictExamResponse> response = examManagementService.getCandidateConflictingExams(examId);
return ResponseEntity.ok(response);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,10 @@
import com.testify.Testify_Backend.responses.GenericAddOrUpdateResponse;
import com.testify.Testify_Backend.responses.GenericDeleteResponse;
import com.testify.Testify_Backend.responses.courseModule.CourseModuleResponse;
import com.testify.Testify_Backend.responses.exam_management.CandidateGroupSearchResponse;
import com.testify.Testify_Backend.responses.exam_management.ExamResponse;
import com.testify.Testify_Backend.responses.exam_management.ExamSetterResponse;
import com.testify.Testify_Backend.responses.organization_management.ExamSetterSearchResponse;
import com.testify.Testify_Backend.service.ExamManagementService;
import com.testify.Testify_Backend.service.OrganizationService;
import com.testify.Testify_Backend.service.OrganizationServiceImpl;
Expand All @@ -26,6 +29,7 @@
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.List;
import java.util.Set;


Expand All @@ -35,6 +39,7 @@
public class OrganizationController {
private static final Logger logger = LoggerFactory.getLogger(OrganizationController.class);
private final OrganizationService organizationService;
private final ExamManagementService examManagementService;

@PostMapping("/{organizationId}/exam-setter")
public ResponseEntity<GenericAddOrUpdateResponse> addSetterToOrganization(@PathVariable long organizationId, @RequestBody AddExamSetterRequest addExamSetterRequest){
Expand Down Expand Up @@ -118,6 +123,11 @@ public ResponseEntity<Set<ExamSetter>> getExamSetters(@PathVariable long organiz
return ResponseEntity.ok(examSetters);
}

@GetMapping("/{organizationId}/search-exam-setters")
public ResponseEntity<?> getExamSettersByOrganizationId(@PathVariable Long organizationId) {
return organizationService.getExamSettersForSearchByOrganizationId(organizationId);
}

//get exam setter invited invitations
@GetMapping("/{organizationId}/invitations")
public ResponseEntity<Set<ExamSetterInvitation>> getExamSetterInvitations(@PathVariable long organizationId){
Expand All @@ -131,4 +141,11 @@ public ResponseEntity<Set<ExamResponse>> getExams(@PathVariable long organizatio
return ResponseEntity.ok(exams);
}

@GetMapping("/{organizationId}/candidate-groups-search")
public ResponseEntity<List<CandidateGroupSearchResponse>> getCandidateGroupsByOrganizationForSearch(
@PathVariable Long organizationId) {
List<CandidateGroupSearchResponse> candidateGroups = examManagementService.getCandidateGroupsByOrganizationForSearch(organizationId);
return ResponseEntity.ok(candidateGroups);
}

}
3 changes: 3 additions & 0 deletions src/main/java/com/testify/Testify_Backend/model/Exam.java
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,15 @@ public class Exam {
private Organization organization;

@Lob //Specifies that the column should be capable of storing large objects, allowing TEXT types in the database.
@Basic(fetch = FetchType.LAZY)
@Column(columnDefinition = "TEXT")
private String description;

@Lob
@Basic(fetch = FetchType.LAZY)
@Column(columnDefinition = "TEXT")
private String instructions;

private int duration;

@Column(nullable = false)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.testify.Testify_Backend.responses.CandidateGroupResponse;
import org.springframework.data.jpa.repository.JpaRepository;

import java.util.List;
import java.util.Set;

public interface CandidateGroupRepository extends JpaRepository<CandidateGroup, Long> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,26 @@

import com.testify.Testify_Backend.model.Candidate;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;


import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.List;
import java.util.Optional;
import java.util.Set;

public interface CandidateRepository extends JpaRepository<Candidate, Long> {
Optional<Candidate> findByEmail(String currentUserEmail);
Set<Candidate> findAllByEmailIn(List<String> emails);

@Query("SELECT c FROM Candidate c JOIN c.exams e " +
"WHERE e.id = :examId " +
"AND (e.startDatetime BETWEEN :startDatetime AND :endDatetime OR " +
"e.endDatetime BETWEEN :startDatetime AND :endDatetime)")
List<Candidate> findCandidatesAssignedToExamWithConflictingExams(Long examId, LocalDateTime startDatetime, LocalDateTime endDatetime);

boolean existsByEmail(String currentUserEmail);
Set<Candidate> findAllByEmailIn(List<String> emails);}
Set<Candidate> findAllByEmailIn(List<String> emails);
}

Original file line number Diff line number Diff line change
@@ -1,17 +1,28 @@
package com.testify.Testify_Backend.repository;


import com.testify.Testify_Backend.model.Candidate;
import com.testify.Testify_Backend.model.Exam;
import com.testify.Testify_Backend.responses.exam_management.ExamResponse;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Optional;
import org.springframework.data.repository.query.Param;

import java.util.List;
import java.util.Set;

public interface ExamRepository extends JpaRepository<Exam, Long> {
Set<Exam> findByOrganizationId(Long id);
Optional<Exam> findById(Long id);

@Query("SELECT e FROM Exam e WHERE e.organization.id = :organizationId " +
"AND e.id <> :examId " +
"AND ((e.startDatetime BETWEEN :startDatetime AND :endDatetime) " +
"OR (e.endDatetime BETWEEN :startDatetime AND :endDatetime))")
List<Exam> findExamsScheduledBetween(long organizationId, long examId, LocalDateTime startDatetime, LocalDateTime endDatetime);

// Fetch exams associated with a particular candidate
@Query("SELECT e FROM Exam e JOIN e.candidates c WHERE c.id = :candidateId")
Expand All @@ -21,3 +32,4 @@ public interface ExamRepository extends JpaRepository<Exam, Long> {
@Query("SELECT e FROM Exam e WHERE e.isPrivate = false")
List<Exam> findAllPublicExams();
}

Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,10 @@
import com.testify.Testify_Backend.model.ExamSetter;
import org.springframework.data.jpa.repository.JpaRepository;

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

public interface ExamSetterRepository extends JpaRepository<ExamSetter, Long> {
Optional<ExamSetter> findByEmail(String email);
List<ExamSetter> findByEmailIn(List<String> emails);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.testify.Testify_Backend.requests.exam_management;


import com.fasterxml.jackson.annotation.JsonFormat;
import com.testify.Testify_Backend.enums.OrderType;
import lombok.*;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.testify.Testify_Backend.responses.exam_management;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class CandidateConflictExamResponse {
private long studentId;
private String firstName;
private String lastName;

private long examId;
private String title;
private String description;
private String instructions;
private int duration;
private LocalDateTime startDatetime;
private LocalDateTime endDatetime;
}
Comment on lines +19 to +26
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 validation constraints and documentation for exam fields.

The exam-related fields would benefit from validation annotations to ensure data integrity, particularly for duration and datetime fields.

Consider adding the following:

+    /**
+     * Unique identifier of the exam
+     */
+    @NotNull
     private long examId;
+
+    /**
+     * Title of the exam
+     */
+    @NotBlank
     private String title;
+
+    /**
+     * Detailed description of the exam
+     */
     private String description;
+
+    /**
+     * Exam instructions for candidates
+     */
     private String instructions;
+
+    /**
+     * Duration of the exam in minutes
+     */
+    @Min(1)
     private int duration;
+
+    /**
+     * Scheduled start date and time of the exam
+     */
+    @NotNull
     private LocalDateTime startDatetime;
+
+    /**
+     * Scheduled end date and time of the exam
+     */
+    @NotNull
     private LocalDateTime endDatetime;

Additionally, consider adding a custom validation to ensure startDatetime is before endDatetime.

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

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.testify.Testify_Backend.responses.exam_management;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.Set;
@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class CandidateGroupSearchResponse {
private long id;
private String name;
private Set<CandidateResponse> candidates;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.testify.Testify_Backend.responses.exam_management;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ConflictExamResponse {
private long id;
private String title;
private String description;
private String instructions;
private int duration;
private LocalDateTime startDatetime;
private LocalDateTime endDatetime;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.testify.Testify_Backend.responses.exam_management;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class ProctorResponse {
private Long id;
private String firstName;
private String lastName;
private String email;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.testify.Testify_Backend.responses.organization_management;

import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
public class ExamSetterSearchResponse {
private Long id;
private String firstName;
private String lastName;
private String email;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,20 @@
import com.testify.Testify_Backend.model.Candidate;
import com.testify.Testify_Backend.model.Organization;
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;
import com.testify.Testify_Backend.responses.candidate_management.OrganizationCandidateView;

import java.util.List;

public interface CandidateService {
List<CandidateExam> getCandidateExams();

List<CandidateResponse> getAllCandidatesForSearch();
public List<CandidateExam> getCandidateExams(String status);
public CandidateExam getCandidateExamDetails(Integer examId);
public List<OrganizationCandidateView> getOrganizations();
public CandidateProfile getCandidateProfile();
public String updateCandidateProfile(Candidate candidate);
public String deleteCandidateProfile(long id);

}
Loading
Loading