From 363f139925d35158de01ed1d4a154ad8e1af89b5 Mon Sep 17 00:00:00 2001 From: Tharindra Fernando Date: Mon, 25 Nov 2024 12:55:17 +0530 Subject: [PATCH] Temp commit --- .../controller/CandidateController.java | 6 + .../controller/ExamManagementController.java | 46 ++++ .../controller/OrganizationController.java | 17 ++ .../testify/Testify_Backend/model/Exam.java | 3 + .../repository/CandidateGroupRepository.java | 1 + .../repository/CandidateRepository.java | 9 + .../repository/ExamRepository.java | 15 ++ .../repository/ExamSetterRepository.java | 2 + .../requests/exam_management/ExamRequest.java | 1 + .../CandidateConflictExamResponse.java | 26 ++ .../CandidateGroupSearchResponse.java | 17 ++ .../exam_management/ConflictExamResponse.java | 22 ++ .../exam_management/ProctorResponse.java | 17 ++ .../ExamSetterSearchResponse.java | 17 ++ .../service/CandidateService.java | 5 +- .../service/CandidateServiceImpl.java | 19 ++ .../service/ExamManagementService.java | 13 + .../service/ExamManagementServiceImpl.java | 246 ++++++++++++++++++ .../service/OrganizationService.java | 2 + .../service/OrganizationServiceImpl.java | 44 +++- 20 files changed, 521 insertions(+), 7 deletions(-) create mode 100644 src/main/java/com/testify/Testify_Backend/responses/exam_management/CandidateConflictExamResponse.java create mode 100644 src/main/java/com/testify/Testify_Backend/responses/exam_management/CandidateGroupSearchResponse.java create mode 100644 src/main/java/com/testify/Testify_Backend/responses/exam_management/ConflictExamResponse.java create mode 100644 src/main/java/com/testify/Testify_Backend/responses/exam_management/ProctorResponse.java create mode 100644 src/main/java/com/testify/Testify_Backend/responses/organization_management/ExamSetterSearchResponse.java diff --git a/src/main/java/com/testify/Testify_Backend/controller/CandidateController.java b/src/main/java/com/testify/Testify_Backend/controller/CandidateController.java index 39d52bb..bfd71f0 100644 --- a/src/main/java/com/testify/Testify_Backend/controller/CandidateController.java +++ b/src/main/java/com/testify/Testify_Backend/controller/CandidateController.java @@ -1,6 +1,7 @@ package com.testify.Testify_Backend.controller; import com.testify.Testify_Backend.responses.candidate_management.CandidateExam; +import com.testify.Testify_Backend.responses.candidate_management.CandidateResponse; import com.testify.Testify_Backend.service.CandidateService; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; @@ -22,4 +23,9 @@ public ResponseEntity> getCandidateExams() { return ResponseEntity.ok(candidateExams); } + @GetMapping("/search") + public List getAllCandidatesForSearch() { + return candidateService.getAllCandidatesForSearch(); + } + } diff --git a/src/main/java/com/testify/Testify_Backend/controller/ExamManagementController.java b/src/main/java/com/testify/Testify_Backend/controller/ExamManagementController.java index 1d1634d..ab268e2 100644 --- a/src/main/java/com/testify/Testify_Backend/controller/ExamManagementController.java +++ b/src/main/java/com/testify/Testify_Backend/controller/ExamManagementController.java @@ -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 @@ -119,9 +123,51 @@ public ResponseEntity> getGradesByExamId(@PathVariable Long return examManagementService.getGradesByExamId(examId); } + @PutMapping("/{examId}/grades") public ResponseEntity updateGrades(@PathVariable Long examId, @RequestBody List gradeRequestList) { return examManagementService.updateGrades(examId, gradeRequestList); } + @PostMapping("/{examId}/proctors") + public ResponseEntity addOrUpdateProctors( + @PathVariable Long examId, + @RequestBody List emails) { + log.info("Adding proctors to examId: " + examId); + log.info("Emails: " + emails); + return examManagementService.addProctorsToExam(examId, emails); + } + + @GetMapping("/{examId}/proctors") + public ResponseEntity> getProctorsByExamId(@PathVariable Long examId) { + return examManagementService.getProctorsByExamId(examId); + } + + @PostMapping("/{examId}/update-candidates") + public ResponseEntity> 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> getExamCandidates(@PathVariable Long examId) { + List candidates = examManagementService.getCandidatesByExamId(examId); + return ResponseEntity.ok(candidates); + } + @GetMapping("/{examId}/conflicting-exams") + public ResponseEntity> getConflictingExams(@PathVariable Long examId) { + List conflictingExams = examManagementService.getExamsScheduledBetween(examId); + return ResponseEntity.ok(conflictingExams); + } + + @GetMapping("/{examId}/conflicting-candidates") + public ResponseEntity> getCandidateConflictingExams(@PathVariable Long examId) { + List response = examManagementService.getCandidateConflictingExams(examId); + return ResponseEntity.ok(response); + } + } diff --git a/src/main/java/com/testify/Testify_Backend/controller/OrganizationController.java b/src/main/java/com/testify/Testify_Backend/controller/OrganizationController.java index 3c88589..1f13416 100644 --- a/src/main/java/com/testify/Testify_Backend/controller/OrganizationController.java +++ b/src/main/java/com/testify/Testify_Backend/controller/OrganizationController.java @@ -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; @@ -26,6 +29,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.util.List; import java.util.Set; @@ -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 addSetterToOrganization(@PathVariable long organizationId, @RequestBody AddExamSetterRequest addExamSetterRequest){ @@ -118,6 +123,11 @@ public ResponseEntity> 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> getExamSetterInvitations(@PathVariable long organizationId){ @@ -131,4 +141,11 @@ public ResponseEntity> getExams(@PathVariable long organizatio return ResponseEntity.ok(exams); } + @GetMapping("/{organizationId}/candidate-groups-search") + public ResponseEntity> getCandidateGroupsByOrganizationForSearch( + @PathVariable Long organizationId) { + List candidateGroups = examManagementService.getCandidateGroupsByOrganizationForSearch(organizationId); + return ResponseEntity.ok(candidateGroups); + } + } diff --git a/src/main/java/com/testify/Testify_Backend/model/Exam.java b/src/main/java/com/testify/Testify_Backend/model/Exam.java index 6372514..001742a 100644 --- a/src/main/java/com/testify/Testify_Backend/model/Exam.java +++ b/src/main/java/com/testify/Testify_Backend/model/Exam.java @@ -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) diff --git a/src/main/java/com/testify/Testify_Backend/repository/CandidateGroupRepository.java b/src/main/java/com/testify/Testify_Backend/repository/CandidateGroupRepository.java index 3327277..6ef6611 100644 --- a/src/main/java/com/testify/Testify_Backend/repository/CandidateGroupRepository.java +++ b/src/main/java/com/testify/Testify_Backend/repository/CandidateGroupRepository.java @@ -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 { diff --git a/src/main/java/com/testify/Testify_Backend/repository/CandidateRepository.java b/src/main/java/com/testify/Testify_Backend/repository/CandidateRepository.java index 5450df8..fffe5a8 100644 --- a/src/main/java/com/testify/Testify_Backend/repository/CandidateRepository.java +++ b/src/main/java/com/testify/Testify_Backend/repository/CandidateRepository.java @@ -2,7 +2,9 @@ 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.List; import java.util.Optional; import java.util.Set; @@ -10,4 +12,11 @@ public interface CandidateRepository extends JpaRepository { Optional findByEmail(String currentUserEmail); Set findAllByEmailIn(List 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 findCandidatesAssignedToExamWithConflictingExams(Long examId, LocalDateTime startDatetime, LocalDateTime endDatetime); + } diff --git a/src/main/java/com/testify/Testify_Backend/repository/ExamRepository.java b/src/main/java/com/testify/Testify_Backend/repository/ExamRepository.java index 55a3282..8df330d 100644 --- a/src/main/java/com/testify/Testify_Backend/repository/ExamRepository.java +++ b/src/main/java/com/testify/Testify_Backend/repository/ExamRepository.java @@ -1,12 +1,27 @@ 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 java.util.Set; public interface ExamRepository extends JpaRepository { Set findByOrganizationId(Long id); + Optional 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 findExamsScheduledBetween(long organizationId, long examId, LocalDateTime startDatetime, LocalDateTime endDatetime); + + } + diff --git a/src/main/java/com/testify/Testify_Backend/repository/ExamSetterRepository.java b/src/main/java/com/testify/Testify_Backend/repository/ExamSetterRepository.java index 81b6f29..70f84ee 100644 --- a/src/main/java/com/testify/Testify_Backend/repository/ExamSetterRepository.java +++ b/src/main/java/com/testify/Testify_Backend/repository/ExamSetterRepository.java @@ -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 { Optional findByEmail(String email); + List findByEmailIn(List emails); } diff --git a/src/main/java/com/testify/Testify_Backend/requests/exam_management/ExamRequest.java b/src/main/java/com/testify/Testify_Backend/requests/exam_management/ExamRequest.java index f210836..9cf7573 100644 --- a/src/main/java/com/testify/Testify_Backend/requests/exam_management/ExamRequest.java +++ b/src/main/java/com/testify/Testify_Backend/requests/exam_management/ExamRequest.java @@ -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.*; diff --git a/src/main/java/com/testify/Testify_Backend/responses/exam_management/CandidateConflictExamResponse.java b/src/main/java/com/testify/Testify_Backend/responses/exam_management/CandidateConflictExamResponse.java new file mode 100644 index 0000000..c27b89f --- /dev/null +++ b/src/main/java/com/testify/Testify_Backend/responses/exam_management/CandidateConflictExamResponse.java @@ -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; +} diff --git a/src/main/java/com/testify/Testify_Backend/responses/exam_management/CandidateGroupSearchResponse.java b/src/main/java/com/testify/Testify_Backend/responses/exam_management/CandidateGroupSearchResponse.java new file mode 100644 index 0000000..b9377ef --- /dev/null +++ b/src/main/java/com/testify/Testify_Backend/responses/exam_management/CandidateGroupSearchResponse.java @@ -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 candidates; +} diff --git a/src/main/java/com/testify/Testify_Backend/responses/exam_management/ConflictExamResponse.java b/src/main/java/com/testify/Testify_Backend/responses/exam_management/ConflictExamResponse.java new file mode 100644 index 0000000..75a2511 --- /dev/null +++ b/src/main/java/com/testify/Testify_Backend/responses/exam_management/ConflictExamResponse.java @@ -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; +} diff --git a/src/main/java/com/testify/Testify_Backend/responses/exam_management/ProctorResponse.java b/src/main/java/com/testify/Testify_Backend/responses/exam_management/ProctorResponse.java new file mode 100644 index 0000000..b86dc8c --- /dev/null +++ b/src/main/java/com/testify/Testify_Backend/responses/exam_management/ProctorResponse.java @@ -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; +} diff --git a/src/main/java/com/testify/Testify_Backend/responses/organization_management/ExamSetterSearchResponse.java b/src/main/java/com/testify/Testify_Backend/responses/organization_management/ExamSetterSearchResponse.java new file mode 100644 index 0000000..de6b4df --- /dev/null +++ b/src/main/java/com/testify/Testify_Backend/responses/organization_management/ExamSetterSearchResponse.java @@ -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; +} diff --git a/src/main/java/com/testify/Testify_Backend/service/CandidateService.java b/src/main/java/com/testify/Testify_Backend/service/CandidateService.java index 4d24967..2c21906 100644 --- a/src/main/java/com/testify/Testify_Backend/service/CandidateService.java +++ b/src/main/java/com/testify/Testify_Backend/service/CandidateService.java @@ -2,9 +2,12 @@ import com.testify.Testify_Backend.model.Candidate; import com.testify.Testify_Backend.responses.candidate_management.CandidateExam; +import com.testify.Testify_Backend.responses.candidate_management.CandidateResponse; import java.util.List; public interface CandidateService { - public List getCandidateExams(); + List getCandidateExams(); + + List getAllCandidatesForSearch(); } diff --git a/src/main/java/com/testify/Testify_Backend/service/CandidateServiceImpl.java b/src/main/java/com/testify/Testify_Backend/service/CandidateServiceImpl.java index 0a49508..c1d0717 100644 --- a/src/main/java/com/testify/Testify_Backend/service/CandidateServiceImpl.java +++ b/src/main/java/com/testify/Testify_Backend/service/CandidateServiceImpl.java @@ -3,6 +3,7 @@ import com.testify.Testify_Backend.model.Candidate; import com.testify.Testify_Backend.repository.CandidateRepository; import com.testify.Testify_Backend.responses.candidate_management.CandidateExam; +import com.testify.Testify_Backend.responses.candidate_management.CandidateResponse; import com.testify.Testify_Backend.utils.UserUtil; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -11,6 +12,7 @@ import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; @Service @RequiredArgsConstructor @@ -27,4 +29,21 @@ public List getCandidateExams(){ return candidate.getExams().stream().map(exam -> modelMapper.map(exam, CandidateExam.class)).toList(); } + + public List getAllCandidatesForSearch() { + List candidates = candidateRepository.findAll(); + + if (candidates.isEmpty()) { + return null; + } + + return candidates.stream() + .map(candidate -> new CandidateResponse( + candidate.getId(), + candidate.getFirstName(), + candidate.getLastName(), + candidate.getEmail() + )) + .collect(Collectors.toList()); + } } \ No newline at end of file diff --git a/src/main/java/com/testify/Testify_Backend/service/ExamManagementService.java b/src/main/java/com/testify/Testify_Backend/service/ExamManagementService.java index 92d7f7f..ee0d3b2 100644 --- a/src/main/java/com/testify/Testify_Backend/service/ExamManagementService.java +++ b/src/main/java/com/testify/Testify_Backend/service/ExamManagementService.java @@ -4,6 +4,7 @@ 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 org.springframework.http.ResponseEntity; @@ -32,6 +33,18 @@ public interface ExamManagementService { GenericAddOrUpdateResponse updateOrder(long examId, OrderChangeRequest orderRequest); ResponseEntity getExamOrderTypeAndValue(Long examId); + ResponseEntity addProctorsToExam(long examId, List proctorEmails); + ResponseEntity> getProctorsByExamId(Long examId); + + ResponseEntity> updateExamCandidates(Long examId, List candidateEmails); + List getCandidatesByExamId(Long examId); + + List getCandidateGroupsByOrganizationForSearch(Long organizationId); + GenericAddOrUpdateResponse addCandidatesToExam(long examId, CandidateEmailListRequest request); + + List getExamsScheduledBetween(Long examId); + + List getCandidateConflictingExams(Long examId); } diff --git a/src/main/java/com/testify/Testify_Backend/service/ExamManagementServiceImpl.java b/src/main/java/com/testify/Testify_Backend/service/ExamManagementServiceImpl.java index 8f402c9..9c79734 100644 --- a/src/main/java/com/testify/Testify_Backend/service/ExamManagementServiceImpl.java +++ b/src/main/java/com/testify/Testify_Backend/service/ExamManagementServiceImpl.java @@ -4,8 +4,10 @@ import com.testify.Testify_Backend.model.*; import com.testify.Testify_Backend.repository.*; import com.testify.Testify_Backend.requests.exam_management.*; +import com.testify.Testify_Backend.responses.CandidateGroupResponse; 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.utils.UserUtil; import lombok.RequiredArgsConstructor; @@ -35,6 +37,7 @@ public class ExamManagementServiceImpl implements ExamManagementService { private final RandomOrderRepository randomOrderRepository; private final FixedOrderRepository fixedOrderRepository; private final GradeRepository gradeRepository; + private final CandidateGroupRepository candidateGroupRepository; private final ModelMapper modelMapper; @@ -548,6 +551,7 @@ public GenericAddOrUpdateResponse addCandidatesToExam return response; } + @Transactional public ExamResponse getExamById(long examId) { Exam exam = examRepository.findById(examId) .orElseThrow(() -> new IllegalArgumentException("Exam not found with id: " + examId)); @@ -759,12 +763,15 @@ public ResponseEntity saveGrades(Long examId, List> getGradesByExamId(Long examId) { List gradeResponses; try { List grades = gradeRepository.findByExamId(examId); + log.info("Grades: {}", grades); + if (grades.isEmpty()) { throw new RuntimeException("No grades found for the given exam ID"); } @@ -812,6 +819,245 @@ public ResponseEntity updateGrades(Long examId, List return ResponseEntity.ok(response); } + @Override + @Transactional + public ResponseEntity addProctorsToExam(long examId, List proctorEmails) { + Optional optionalExam = examRepository.findById(examId); + GenericAddOrUpdateResponse response = new GenericAddOrUpdateResponse(); + + if (!optionalExam.isPresent()) { + response.setSuccess(false); + response.setMessage("Exam not found"); + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); + } + + Exam exam = optionalExam.get(); + log.info("Exam found with ID: {}", examId); + + // If the email list is empty, clear all existing proctors and save + if (proctorEmails == null || proctorEmails.isEmpty()) { + log.info("Received empty proctor email list. Removing all existing proctors for exam ID: {}", examId); + exam.getProctors().clear(); + examRepository.save(exam); + + response.setSuccess(true); + response.setMessage("All proctors removed successfully"); + return ResponseEntity.ok(response); + } + + // Clear existing proctors + Set existingProctors = exam.getProctors(); + log.info("Existing proctors: {}", existingProctors); + existingProctors.clear(); + + // Fetch new proctors based on email + Set newProctors = new HashSet<>(); + for (String email : proctorEmails) { + Optional examSetterOpt = examSetterRepository.findByEmail(email); + if (examSetterOpt.isPresent()) { + log.info("Proctor found with email: {}", email); + newProctors.add(examSetterOpt.get()); + } else { + response.setSuccess(false); + response.setMessage("Proctor with email " + email + " not found"); + return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(response); + } + } + + // Add new proctors to the exam + log.info("New proctors: {}", newProctors); + exam.setProctors(newProctors); + examRepository.save(exam); + + response.setSuccess(true); + response.setMessage("Proctors updated successfully"); + return ResponseEntity.ok(response); + } + + + @Transactional(readOnly = true) + public ResponseEntity> getProctorsByExamId(Long examId) { + try { + // Try to fetch the exam + Exam exam = examRepository.findById(examId) + .orElseThrow(() -> new RuntimeException("Exam not found with ID: " + examId)); + + // Convert the exam setters (proctors) to ProctorResponse + List proctors = exam.getProctors().stream() + .map(proctor -> new ProctorResponse( + proctor.getId(), + proctor.getFirstName(), + proctor.getLastName(), + proctor.getEmail())) + .collect(Collectors.toList()); + + // Return the list of proctors + return ResponseEntity.ok(proctors); + + } catch (RuntimeException ex) { + // Return 404 if the exam is not found + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(null); + } catch (Exception ex) { + // Return 500 if an unexpected error occurs + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(null); + } + } + + @Transactional + public ResponseEntity> updateExamCandidates( + Long examId, List candidateEmails) { + + GenericAddOrUpdateResponse response = new GenericAddOrUpdateResponse<>(); + + try { + // Find the exam by ID + Exam exam = examRepository.findById(examId) + .orElseThrow(() -> new RuntimeException("Exam not found with id: " + examId)); + + // Clear existing candidates from the exam + exam.getCandidates().clear(); + + // Find candidates by email and add them to the exam + Set newCandidates = candidateEmails.stream() + .map(email -> candidateRepository.findByEmail(email) + .orElseThrow(() -> new RuntimeException("Candidate not found with email: " + email))) + .collect(Collectors.toSet()); + + // Add new candidates and save the updated exam + exam.getCandidates().addAll(newCandidates); + examRepository.save(exam); + + // Success response + response.setSuccess(true); + response.setMessage("Candidates updated successfully."); + response.setId(examId); + + return ResponseEntity.ok(response); + + } catch (RuntimeException e) { + // Handle exam or candidate not found exceptions + response.setSuccess(false); + response.setMessage(e.getMessage()); + response.setId(examId); + + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(response); + + } catch (Exception e) { + // Handle any other unexpected exceptions + response.setSuccess(false); + response.setMessage("An unexpected error occurred: " + e.getMessage()); + response.setId(examId); + + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(response); + } + } + + @Transactional + public List getCandidatesByExamId(Long examId) { + Exam exam = examRepository.findById(examId) + .orElseThrow(() -> new RuntimeException("Exam not found with id: " + examId)); + + // Return an empty list if there are no candidates + return exam.getCandidates() == null || exam.getCandidates().isEmpty() + ? List.of() + : exam.getCandidates().stream() + .map(candidate -> new CandidateResponse( + candidate.getId(), + candidate.getEmail(), + candidate.getFirstName(), + candidate.getLastName() + )) + .collect(Collectors.toList()); + } + + @Transactional + public List getCandidateGroupsByOrganizationForSearch(Long organizationId) { + + Set candidateGroups = candidateGroupRepository.findByOrganizationId(organizationId); + + // Return null if no candidate groups are found + if (candidateGroups.isEmpty()) { + return null; + } + + return candidateGroups.stream() + .map(group -> new CandidateGroupSearchResponse( + group.getId(), + group.getName(), + group.getCandidates().stream() + .map(candidate -> new CandidateResponse( + candidate.getId(), + candidate.getEmail(), + candidate.getFirstName(), + candidate.getLastName())) + .collect(Collectors.toSet()))) // Collect as a Set + .collect(Collectors.toList()); + } + + @Transactional + public List getExamsScheduledBetween(Long examId) { + Exam currentExam = examRepository.findById(examId) + .orElseThrow(() -> new RuntimeException("Exam not found with ID: " + examId)); + + List conflictingExams = examRepository.findExamsScheduledBetween( + currentExam.getOrganization().getId(), + examId, + currentExam.getStartDatetime(), + currentExam.getEndDatetime() + ); + + return conflictingExams.stream() + .map(exam -> ConflictExamResponse.builder() + .id(exam.getId()) + .title(exam.getTitle()) + .description(exam.getDescription()) + .instructions(exam.getInstructions()) + .duration(exam.getDuration()) + .startDatetime(exam.getStartDatetime()) + .endDatetime(exam.getEndDatetime()) + .build()) + .collect(Collectors.toList()); + } + + @Transactional + public List getCandidateConflictingExams(Long examId) { + Exam currentExam = examRepository.findById(examId) + .orElseThrow(() -> new RuntimeException("Exam not found with ID: " + examId)); + + // Fetch candidates assigned to the current exam + List assignedCandidates = candidateRepository.findCandidatesAssignedToExamWithConflictingExams( + examId, + currentExam.getStartDatetime(), + currentExam.getEndDatetime() + ); + + List responseList = new ArrayList<>(); + for (Candidate candidate : assignedCandidates) { + // Check the exams of the candidate for conflicts + candidate.getExams().stream() + .filter(exam -> ( + exam.getStartDatetime().isBefore(currentExam.getEndDatetime()) && + exam.getEndDatetime().isAfter(currentExam.getStartDatetime()))) + .forEach(conflictingExam -> responseList.add(CandidateConflictExamResponse.builder() + .studentId(candidate.getId()) + .firstName(candidate.getFirstName()) + .lastName(candidate.getLastName()) + .examId(conflictingExam.getId()) + .title(conflictingExam.getTitle()) + .description(conflictingExam.getDescription()) + .instructions(conflictingExam.getInstructions()) + .duration(conflictingExam.getDuration()) + .startDatetime(conflictingExam.getStartDatetime()) + .endDatetime(conflictingExam.getEndDatetime()) + .build())); + } + + return responseList; // This will return a list of conflicting exams or empty if none found + } + + diff --git a/src/main/java/com/testify/Testify_Backend/service/OrganizationService.java b/src/main/java/com/testify/Testify_Backend/service/OrganizationService.java index e67fcf1..7473e58 100644 --- a/src/main/java/com/testify/Testify_Backend/service/OrganizationService.java +++ b/src/main/java/com/testify/Testify_Backend/service/OrganizationService.java @@ -44,4 +44,6 @@ public interface OrganizationService { Set getExamSetterInvitations(long organizationId); Set getExams(long organizationId); + + ResponseEntity getExamSettersForSearchByOrganizationId(Long organizationId); } diff --git a/src/main/java/com/testify/Testify_Backend/service/OrganizationServiceImpl.java b/src/main/java/com/testify/Testify_Backend/service/OrganizationServiceImpl.java index 4532995..821a250 100644 --- a/src/main/java/com/testify/Testify_Backend/service/OrganizationServiceImpl.java +++ b/src/main/java/com/testify/Testify_Backend/service/OrganizationServiceImpl.java @@ -2,7 +2,6 @@ import com.testify.Testify_Backend.model.*; import com.testify.Testify_Backend.repository.*; -import com.testify.Testify_Backend.requests.VerificationRequestRequest; import com.testify.Testify_Backend.requests.organization_management.AddExamSetterRequest; import com.testify.Testify_Backend.requests.organization_management.CandidateGroupRequest; import com.testify.Testify_Backend.requests.organization_management.CourseModuleRequest; @@ -13,11 +12,9 @@ import com.testify.Testify_Backend.responses.courseModule.CourseModuleResponse; import com.testify.Testify_Backend.responses.exam_management.ExamResponse; -import com.testify.Testify_Backend.utils.FileUploadUtil; +import com.testify.Testify_Backend.responses.organization_management.ExamSetterSearchResponse; import jakarta.transaction.Transactional; -import com.testify.Testify_Backend.utils.FileUtil; - import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.modelmapper.ModelMapper; @@ -26,9 +23,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; import java.util.*; import java.util.stream.Collectors; @@ -281,6 +276,7 @@ public ResponseEntity inviteExamSetter(long organiza String token = UUID.randomUUID().toString(); String invitationLink = "http://127.0.0.1:4500/auth/signup/examSetter?invitation=" + token; + log.info("Invitation link: " + invitationLink); ExamSetterInvitation invitation = new ExamSetterInvitation(); invitation.setEmail(request.getEmail()); @@ -325,6 +321,42 @@ public String confirmToken(String Token){ return "Invitation accepted"; } + @Transactional + public ResponseEntity getExamSettersForSearchByOrganizationId(Long organizationId) { + try { + // Find the organization by ID + Organization organization = organizationRepository.findById(organizationId) + .orElseThrow(() -> new IllegalArgumentException("Organization not found with id: " + organizationId)); + + log.debug("Organization: " + organization); + + // Get the set of exam setters + Set examSetters = organization.getExamSetters(); + log.info("Exam setters: " + examSetters); + + // Map each ExamSetter to ExamSetterSearchResponse and collect as a List + List responseList = examSetters.stream() + .map(examSetter -> new ExamSetterSearchResponse( + examSetter.getId(), + examSetter.getFirstName(), + examSetter.getLastName(), + examSetter.getEmail() + )) + .collect(Collectors.toList()); + + // Return a 200 OK response with the list of exam setters + return ResponseEntity.ok(responseList); + } catch (IllegalArgumentException e) { + // Return a 404 NOT FOUND response with an error message + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(e.getMessage()); + } catch (Exception e) { + // Handle any other exceptions with a 500 INTERNAL SERVER ERROR response + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("An unexpected error occurred."); + } + } + + +