diff --git a/pom.xml b/pom.xml
index d65ac4c..ad6c1a1 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,9 +14,9 @@
Testify-Backend
Spring Boot Backend for Testify
- 21
- 21
- 21
+ 22
+ 22
+ 22
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 c129757..1403c64 100644
--- a/src/main/java/com/testify/Testify_Backend/controller/ExamManagementController.java
+++ b/src/main/java/com/testify/Testify_Backend/controller/ExamManagementController.java
@@ -13,6 +13,7 @@
import com.testify.Testify_Backend.service.ExamManagementService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
@@ -169,9 +170,7 @@ public ResponseEntity submitExam(@PathVariable Long sessionId) {
}
@PostMapping("/{examId}/proctors")
- public ResponseEntity addOrUpdateProctors(
- @PathVariable Long examId,
- @RequestBody List emails) {
+ 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);
@@ -209,4 +208,87 @@ public ResponseEntity> getCandidateConflicti
return ResponseEntity.ok(response);
}
+ @PutMapping("/{examId}/real-time-monitoring")
+ public ResponseEntity updateRealTimeMonitoring(
+ @PathVariable Long examId,
+ @RequestBody RealTimeMonitoringRequest dto) {
+ try {
+ // Delegate to the service and return the response directly
+ GenericResponse response = examManagementService.updateRealTimeMonitoring(examId, dto);
+ return ResponseEntity.ok(response);
+ } catch (Exception ex) {
+ return ResponseEntity.status(500).body(new GenericResponse("false", "Error: " + ex.getMessage()));
+ }
+ }
+
+ @GetMapping("/{examId}/real-time-monitoring")
+ public ResponseEntity getRealTimeMonitoringStatus(@PathVariable Long examId) {
+ try {
+ RealTimeMonitoringResponse response = examManagementService.getRealTimeMonitoringStatus(examId);
+ return ResponseEntity.ok(response);
+ } catch (Exception ex) {
+ return ResponseEntity.status(500).body(null);
+ }
+ }
+
+ @PutMapping("/{examId}/browser-lockdown")
+ public ResponseEntity updateBrowserLockdown(
+ @PathVariable Long examId,
+ @RequestParam boolean browserLockdown) {
+ try {
+ GenericResponse response = examManagementService.updateBrowserLockdown(examId, browserLockdown);
+ return ResponseEntity.ok(response);
+ } catch (Exception ex) {
+ return ResponseEntity.status(500).body(new GenericResponse("false", "Error: " + ex.getMessage()));
+ }
+ }
+
+ @GetMapping("/{examId}/browser-lockdown")
+ public ResponseEntity getBrowserLockdown(@PathVariable Long examId) {
+ try {
+ boolean browserLockdown = examManagementService.getBrowserLockdownStatus(examId);
+ return ResponseEntity.ok(new BrowserLockdownResponse(browserLockdown));
+ } catch (Exception ex) {
+ return ResponseEntity.status(500).body(null); // Handle errors gracefully
+ }
+ }
+
+ @PutMapping("/{examId}/hosted")
+ public ResponseEntity updateHosted(@PathVariable Long examId, @RequestParam boolean hosted) {
+ try {
+ GenericResponse response = examManagementService.updateHostedStatus(examId, hosted);
+ return ResponseEntity.ok(response);
+ } catch (Exception ex) {
+ return ResponseEntity.status(500).body(new GenericResponse("false", "Error: " + ex.getMessage()));
+ }
+ }
+
+ @GetMapping("/{examId}/hosted")
+ public ResponseEntity getHosted(@PathVariable Long examId) {
+ try {
+ boolean hosted = examManagementService.getHostedStatus(examId);
+ log.info(String.valueOf(hosted));
+ return ResponseEntity.ok(new HostedResponse(hosted));
+ } catch (Exception ex) {
+ return ResponseEntity.status(500).body(null);
+ }
+ }
+
+ @PostMapping("/{examId}/set-moderator")
+ public ResponseEntity setModerator(@PathVariable Long examId, @RequestBody ModeratorRequest moderatorRequest) {
+ log.info("Setting moderator for examId: " + examId);
+ try {
+ examManagementService.assignModerator(examId, moderatorRequest.getModeratorEmail());
+ return ResponseEntity.ok("Moderator assigned successfully.");
+ } catch (RuntimeException ex) {
+ return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());
+ }
+ }
+
+ @GetMapping("/{examId}/moderator")
+ public ResponseEntity getModerator(@PathVariable Long examId) {
+ ModeratorResponse moderatorResponse = examManagementService.getModeratorDetails(examId);
+ return ResponseEntity.ok(moderatorResponse); // Returns null in the body if no moderator exists
+ }
+
}
diff --git a/src/main/java/com/testify/Testify_Backend/controller/ExamSetterController.java b/src/main/java/com/testify/Testify_Backend/controller/ExamSetterController.java
index 36c0e89..0f4f59a 100644
--- a/src/main/java/com/testify/Testify_Backend/controller/ExamSetterController.java
+++ b/src/main/java/com/testify/Testify_Backend/controller/ExamSetterController.java
@@ -36,4 +36,10 @@ public ResponseEntity addSetterToOrganization(@PathV
GenericAddOrUpdateResponse response = examSetterService.addSetterToOrganization(token);
return ResponseEntity.ok(response);
}
+
+ @PutMapping("/{setterId}/{organizationId}/deleteSetter")
+ public ResponseEntity deleteSetter(@PathVariable("setterId") String setterId, @PathVariable("organizationId") String organizationId) {
+ GenericAddOrUpdateResponse response = examSetterService.deleteSetter(setterId, organizationId);
+ return ResponseEntity.ok(response);
+ }
}
diff --git a/src/main/java/com/testify/Testify_Backend/controller/GradingController.java b/src/main/java/com/testify/Testify_Backend/controller/GradingController.java
index 33bcdff..3128c15 100644
--- a/src/main/java/com/testify/Testify_Backend/controller/GradingController.java
+++ b/src/main/java/com/testify/Testify_Backend/controller/GradingController.java
@@ -1,17 +1,17 @@
package com.testify.Testify_Backend.controller;
import com.testify.Testify_Backend.model.CandidateExamSession;
+import com.testify.Testify_Backend.model.ExamCandidateGrade;
import com.testify.Testify_Backend.model.Grade;
+import com.testify.Testify_Backend.requests.exam_management.ExamCandidateGradeRequest;
import com.testify.Testify_Backend.responses.EssayDetailsResponse;
import com.testify.Testify_Backend.responses.McqDetailsResponse;
+import com.testify.Testify_Backend.responses.exam_management.ExamCandidateGradeResponse;
import com.testify.Testify_Backend.service.GradingService;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PathVariable;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
import java.util.List;
import java.util.Map;
@@ -46,5 +46,16 @@ public ResponseEntity>> getResultsBySessionId(
return ResponseEntity.ok(results);
}
+ @PostMapping("/setExamCandidateGrade")
+ public ResponseEntity setExamCandidateGrade(@RequestBody ExamCandidateGradeRequest examCandidateGradeRequest) {
+ String response = gradingService.setExamCandidateGrade(examCandidateGradeRequest);
+ return ResponseEntity.ok(response);
+ }
+
+ @GetMapping("/getExamCandidateGrade")
+ public ResponseEntity> getExamCandidateGrade() {
+ List response = gradingService.getExamCandidateGrade();
+ return ResponseEntity.ok(response);
+ }
}
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 90cc009..0cf2e03 100644
--- a/src/main/java/com/testify/Testify_Backend/model/Exam.java
+++ b/src/main/java/com/testify/Testify_Backend/model/Exam.java
@@ -92,6 +92,7 @@ public class Exam {
joinColumns = @JoinColumn(name = "exam_id"),
inverseJoinColumns = @JoinColumn(name = "candidate_id")
)
+
private Set candidates;
@OneToMany(mappedBy = "exam", cascade = CascadeType.ALL)
@@ -104,4 +105,17 @@ public class Exam {
@JsonIgnore
private List gradings;
+ @Column(nullable = false)
+ private boolean realTimeMonitoring = false;
+
+ @Column
+ private String zoomLink;
+
+ @Column(nullable = false)
+ private boolean browserLockdown = false;
+
+ @Column(nullable = false)
+ private boolean hosted = false;
+
+
}
diff --git a/src/main/java/com/testify/Testify_Backend/model/ExamCandidateGrade.java b/src/main/java/com/testify/Testify_Backend/model/ExamCandidateGrade.java
new file mode 100644
index 0000000..3d52a44
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/model/ExamCandidateGrade.java
@@ -0,0 +1,40 @@
+package com.testify.Testify_Backend.model;
+
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import lombok.*;
+import org.springframework.data.annotation.Id;
+
+@Entity
+@Data
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class ExamCandidateGrade {
+
+ @Setter
+ @jakarta.persistence.Id
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY) // Auto-generate the ID
+ @Column
+ private Long id;
+
+ @Column(nullable = false)
+ private String examID;
+
+ @Column(nullable = false)
+ private String candidateID;
+
+ private String status;
+
+ private String grade;
+
+ private String score;
+
+ public Long getId() {
+ return id;
+ }
+}
diff --git a/src/main/java/com/testify/Testify_Backend/model/ExamSetter.java b/src/main/java/com/testify/Testify_Backend/model/ExamSetter.java
index a24b8dc..37f625b 100644
--- a/src/main/java/com/testify/Testify_Backend/model/ExamSetter.java
+++ b/src/main/java/com/testify/Testify_Backend/model/ExamSetter.java
@@ -43,4 +43,6 @@ public class ExamSetter extends User{
@OneToMany(mappedBy = "moderator")
private Set moderatedExams;
+
+
}
diff --git a/src/main/java/com/testify/Testify_Backend/model/ExamSetterOrganization.java b/src/main/java/com/testify/Testify_Backend/model/ExamSetterOrganization.java
new file mode 100644
index 0000000..ade0bdb
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/model/ExamSetterOrganization.java
@@ -0,0 +1,37 @@
+package com.testify.Testify_Backend.model;
+
+import jakarta.annotation.Nullable;
+import jakarta.persistence.Column;
+import jakarta.persistence.Entity;
+import jakarta.persistence.GeneratedValue;
+import jakarta.persistence.GenerationType;
+import lombok.*;
+import org.springframework.data.annotation.Id;
+
+@Entity
+@Data
+@Getter
+@Setter
+@NoArgsConstructor
+@AllArgsConstructor
+public class ExamSetterOrganization {
+ @Setter
+ @jakarta.persistence.Id
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY) // Auto-generate the ID
+ @Column
+ private Long id;
+
+ @Column(nullable = false)
+ private String organizationID;
+
+ @Column(nullable = false)
+ private String examSetterID;
+
+ private boolean isDeleted = false;
+
+ public Long getId() {
+ return id;
+ }
+
+}
diff --git a/src/main/java/com/testify/Testify_Backend/repository/ExamCandidateGradeRepository.java b/src/main/java/com/testify/Testify_Backend/repository/ExamCandidateGradeRepository.java
new file mode 100644
index 0000000..7889aaa
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/repository/ExamCandidateGradeRepository.java
@@ -0,0 +1,16 @@
+package com.testify.Testify_Backend.repository;
+
+import com.testify.Testify_Backend.model.ExamCandidateGrade;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface ExamCandidateGradeRepository extends JpaRepository {
+
+// write a query to get the exam candidate grade details along with exam name and candidate name
+ @Query("SELECT ecg FROM ExamCandidateGrade ecg")
+ List getExamCandidateGradeDetails();
+}
diff --git a/src/main/java/com/testify/Testify_Backend/repository/ExamSetterOrganizationRepository.java b/src/main/java/com/testify/Testify_Backend/repository/ExamSetterOrganizationRepository.java
new file mode 100644
index 0000000..f6149e0
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/repository/ExamSetterOrganizationRepository.java
@@ -0,0 +1,22 @@
+package com.testify.Testify_Backend.repository;
+
+import com.testify.Testify_Backend.model.ExamSetterOrganization;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
+import org.springframework.data.repository.query.Param;
+import org.springframework.stereotype.Repository;
+
+import java.util.List;
+
+@Repository
+public interface ExamSetterOrganizationRepository extends JpaRepository {
+// write a method to update is deleted to true by organizationID and examSetterID
+// write a method to find by organizationID and examSetterID
+ ExamSetterOrganization findByOrganizationIDAndExamSetterID(String organizationID, String examSetterID);
+ List findByOrganizationID(String organizationID);
+ ExamSetterOrganization findByExamSetterID(String examSetterID);
+
+ @Query("SELECT e.examSetterID FROM ExamSetterOrganization e WHERE e.organizationID = :organizationId AND e.isDeleted = false")
+ List findActiveExamSetterIDs(@Param("organizationId") String organizationId);
+
+}
diff --git a/src/main/java/com/testify/Testify_Backend/requests/exam_management/ExamCandidateGradeRequest.java b/src/main/java/com/testify/Testify_Backend/requests/exam_management/ExamCandidateGradeRequest.java
new file mode 100644
index 0000000..177db92
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/requests/exam_management/ExamCandidateGradeRequest.java
@@ -0,0 +1,17 @@
+package com.testify.Testify_Backend.requests.exam_management;
+
+import lombok.*;
+
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@Getter
+@Setter
+public class ExamCandidateGradeRequest
+{
+ private String examID;
+ private String candidateID;
+ private String status;
+ private String grade;
+ private String score;
+}
diff --git a/src/main/java/com/testify/Testify_Backend/requests/exam_management/ModeratorRequest.java b/src/main/java/com/testify/Testify_Backend/requests/exam_management/ModeratorRequest.java
new file mode 100644
index 0000000..5d7ddcd
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/requests/exam_management/ModeratorRequest.java
@@ -0,0 +1,14 @@
+package com.testify.Testify_Backend.requests.exam_management;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class ModeratorRequest {
+ private String moderatorEmail;
+}
diff --git a/src/main/java/com/testify/Testify_Backend/requests/exam_management/RealTimeMonitoringRequest.java b/src/main/java/com/testify/Testify_Backend/requests/exam_management/RealTimeMonitoringRequest.java
new file mode 100644
index 0000000..934625a
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/requests/exam_management/RealTimeMonitoringRequest.java
@@ -0,0 +1,15 @@
+package com.testify.Testify_Backend.requests.exam_management;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class RealTimeMonitoringRequest {
+ private boolean realTimeMonitoring;
+ private String zoomLink;
+}
diff --git a/src/main/java/com/testify/Testify_Backend/responses/GenericResponse.java b/src/main/java/com/testify/Testify_Backend/responses/GenericResponse.java
index ffe12aa..755ad7d 100644
--- a/src/main/java/com/testify/Testify_Backend/responses/GenericResponse.java
+++ b/src/main/java/com/testify/Testify_Backend/responses/GenericResponse.java
@@ -9,7 +9,6 @@
@AllArgsConstructor
@Data
public class GenericResponse {
- private String code;
+ private String success;
private String message;
- private Object content;
}
diff --git a/src/main/java/com/testify/Testify_Backend/responses/exam_management/BrowserLockdownResponse.java b/src/main/java/com/testify/Testify_Backend/responses/exam_management/BrowserLockdownResponse.java
new file mode 100644
index 0000000..73bc8db
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/responses/exam_management/BrowserLockdownResponse.java
@@ -0,0 +1,14 @@
+package com.testify.Testify_Backend.responses.exam_management;
+
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+
+@Getter
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class BrowserLockdownResponse {
+ private boolean browserLockdown;
+}
diff --git a/src/main/java/com/testify/Testify_Backend/responses/exam_management/ExamCandidateGradeResponse.java b/src/main/java/com/testify/Testify_Backend/responses/exam_management/ExamCandidateGradeResponse.java
new file mode 100644
index 0000000..12a5319
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/responses/exam_management/ExamCandidateGradeResponse.java
@@ -0,0 +1,23 @@
+package com.testify.Testify_Backend.responses.exam_management;
+
+
+import com.testify.Testify_Backend.model.Candidate;
+import com.testify.Testify_Backend.model.Exam;
+import lombok.*;
+
+@Data
+@Setter
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class ExamCandidateGradeResponse {
+
+ private String examID;
+ private String examTitle;
+ private String candidateName;
+ private String candidateID;
+ private String status;
+ private String grade;
+ private String score;
+}
diff --git a/src/main/java/com/testify/Testify_Backend/responses/exam_management/ExamResponse.java b/src/main/java/com/testify/Testify_Backend/responses/exam_management/ExamResponse.java
index 1ea95f9..2f949a3 100644
--- a/src/main/java/com/testify/Testify_Backend/responses/exam_management/ExamResponse.java
+++ b/src/main/java/com/testify/Testify_Backend/responses/exam_management/ExamResponse.java
@@ -1,6 +1,7 @@
package com.testify.Testify_Backend.responses.exam_management;
import com.testify.Testify_Backend.enums.OrderType;
+import jakarta.persistence.Column;
import lombok.*;
import java.time.LocalDateTime;
@@ -31,4 +32,8 @@ public class ExamResponse {
private OrderType orderType;
private FixedOrderResponse fixedOrder;
private RandomOrderResponse randomOrder;
+ private boolean realTimeMonitoring;
+ private String zoomLink;
+ private boolean browserLockdown;
+ private boolean hosted;
}
diff --git a/src/main/java/com/testify/Testify_Backend/responses/exam_management/HostedResponse.java b/src/main/java/com/testify/Testify_Backend/responses/exam_management/HostedResponse.java
new file mode 100644
index 0000000..c971218
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/responses/exam_management/HostedResponse.java
@@ -0,0 +1,14 @@
+package com.testify.Testify_Backend.responses.exam_management;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+public class HostedResponse {
+ private boolean hosted;
+}
diff --git a/src/main/java/com/testify/Testify_Backend/responses/exam_management/ModeratorResponse.java b/src/main/java/com/testify/Testify_Backend/responses/exam_management/ModeratorResponse.java
new file mode 100644
index 0000000..482c95a
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/responses/exam_management/ModeratorResponse.java
@@ -0,0 +1,14 @@
+package com.testify.Testify_Backend.responses.exam_management;
+
+import lombok.*;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class ModeratorResponse {
+ private String email;
+ private String firstName;
+ private String lastName;
+}
diff --git a/src/main/java/com/testify/Testify_Backend/responses/exam_management/RealTimeMonitoringResponse.java b/src/main/java/com/testify/Testify_Backend/responses/exam_management/RealTimeMonitoringResponse.java
new file mode 100644
index 0000000..1db9ae8
--- /dev/null
+++ b/src/main/java/com/testify/Testify_Backend/responses/exam_management/RealTimeMonitoringResponse.java
@@ -0,0 +1,13 @@
+package com.testify.Testify_Backend.responses.exam_management;
+
+import lombok.*;
+
+@Getter
+@Setter
+@AllArgsConstructor
+@NoArgsConstructor
+@Builder
+public class RealTimeMonitoringResponse {
+ private boolean realTimeMonitoring;
+ private String zoomLink;
+}
diff --git a/src/main/java/com/testify/Testify_Backend/service/AuthenticationService.java b/src/main/java/com/testify/Testify_Backend/service/AuthenticationService.java
index 7e3951e..5167401 100644
--- a/src/main/java/com/testify/Testify_Backend/service/AuthenticationService.java
+++ b/src/main/java/com/testify/Testify_Backend/service/AuthenticationService.java
@@ -54,6 +54,8 @@ public class AuthenticationService {
private final VerificationRequestRepository verificationRequestRepository;
+ private final ExamSetterOrganizationRepository examSetterOrganizationRepository;
+
private User user;
@@ -114,6 +116,14 @@ public RegisterResponse register(@ModelAttribute RegistrationRequest request, bo
invitation.setAccepted(true);
examSetterInvitationRepository.save(invitation);
});
+
+// add to examSetterOrganization
+ ExamSetterOrganization examSetterOrganization = new ExamSetterOrganization();
+ examSetterOrganization.setOrganizationID(String.valueOf(organization.getId()));
+ examSetterOrganization.setExamSetterID(String.valueOf(examSetter.getId()));
+ examSetterOrganizationRepository.save(examSetterOrganization);
+
+
} else if (request.getRole().equals(UserRole.ORGANIZATION)) {
System.out.println("Organization");
Organization organization = Organization.builder()
@@ -217,10 +227,10 @@ public RegisterResponse register(@ModelAttribute RegistrationRequest request, bo
// Log the confirmation link
log.info("Confirmation link: {}", link);
-// emailSender.send(
-// request.getEmail(),
-// buildEmail(request.getEmail(), link)
-// );
+ emailSender.send(
+ request.getEmail(),
+ buildEmail(request.getEmail(), link)
+ );
//TODO: save jwt token
var jwtToken = jwtService.generateToken(savedUser);
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 840874e..adc3303 100644
--- a/src/main/java/com/testify/Testify_Backend/service/CandidateService.java
+++ b/src/main/java/com/testify/Testify_Backend/service/CandidateService.java
@@ -10,6 +10,7 @@
import java.util.List;
public interface CandidateService {
+
List getAllCandidatesForSearch();
// temp comment
public List getCandidateExams(String status);
diff --git a/src/main/java/com/testify/Testify_Backend/service/EmailSenderImpl.java b/src/main/java/com/testify/Testify_Backend/service/EmailSenderImpl.java
index d4bb81d..086ec60 100644
--- a/src/main/java/com/testify/Testify_Backend/service/EmailSenderImpl.java
+++ b/src/main/java/com/testify/Testify_Backend/service/EmailSenderImpl.java
@@ -19,24 +19,24 @@ public class EmailSenderImpl implements EmailSender {
private final static Logger LOGGER = LoggerFactory
.getLogger(EmailSenderImpl.class);
-// private final JavaMailSender mailSender;
+ private final JavaMailSender mailSender;
@Override
@Async
public void send(String to, String email) {
-// try {
-// MimeMessage mimeMessage = mailSender.createMimeMessage();
-// MimeMessageHelper helper =
-// new MimeMessageHelper(mimeMessage, "utf-8");
-// helper.setText(email, true);
-// helper.setTo(to);
-// helper.setSubject("Confirm your email");
-// helper.setFrom("hello@amigoscode.com");
-// mailSender.send(mimeMessage);
-// } catch (MessagingException e) {
-// LOGGER.error("failed to send email", e);
-// throw new IllegalStateException("failed to send email");
-// }
+ try {
+ MimeMessage mimeMessage = mailSender.createMimeMessage();
+ MimeMessageHelper helper =
+ new MimeMessageHelper(mimeMessage, "utf-8");
+ helper.setText(email, true);
+ helper.setTo(to);
+ helper.setSubject("Confirm your email");
+ helper.setFrom("hello@amigoscode.com");
+ mailSender.send(mimeMessage);
+ } catch (MessagingException e) {
+ LOGGER.error("failed to send email", e);
+ throw new IllegalStateException("failed to send email");
+ }
System.out.println("Skipping email sending during development.");
}
}
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 9ab7944..88ba83c 100644
--- a/src/main/java/com/testify/Testify_Backend/service/ExamManagementService.java
+++ b/src/main/java/com/testify/Testify_Backend/service/ExamManagementService.java
@@ -51,4 +51,16 @@ public interface ExamManagementService {
List getExamsScheduledBetween(Long examId);
List getCandidateConflictingExams(Long examId);
+
+ GenericResponse updateRealTimeMonitoring(Long examId, RealTimeMonitoringRequest dto);
+ RealTimeMonitoringResponse getRealTimeMonitoringStatus(Long examId);
+
+ GenericResponse updateBrowserLockdown(Long examId, boolean browserLockdown);
+ boolean getBrowserLockdownStatus(Long examId);
+
+ GenericResponse updateHostedStatus(Long examId, boolean hosted);
+ boolean getHostedStatus(Long examId);
+
+ void assignModerator(Long examId, String moderatorEmail);
+ ModeratorResponse getModeratorDetails(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 89c297c..9786df4 100644
--- a/src/main/java/com/testify/Testify_Backend/service/ExamManagementServiceImpl.java
+++ b/src/main/java/com/testify/Testify_Backend/service/ExamManagementServiceImpl.java
@@ -46,6 +46,7 @@ public class ExamManagementServiceImpl implements ExamManagementService {
private final CandidateExamAnswerRepository candidateExamAnswerRepository;
private final MCQOptionRepository mcqOptionRepository;
private final CandidateGroupRepository candidateGroupRepository;
+ private final EmailSender emailSender;
private final ModelMapper modelMapper;
@@ -79,6 +80,9 @@ public GenericAddOrUpdateResponse createExam(ExamRequest examReques
.endDatetime(examRequest.getEndDatetime())
.isPrivate(true)
.orderType(examRequest.getOrderType() != null ? examRequest.getOrderType() : OrderType.FIXED)
+ .browserLockdown(false)
+ .realTimeMonitoring(false)
+ .hosted(false)
.build();
exam = examRepository.save(exam);
@@ -789,6 +793,11 @@ public ExamResponse getExamById(long examId) {
// Handle question sequence (assuming it's never null)
.questionSequence(exam.getQuestionSequence())
+ .browserLockdown(exam.isBrowserLockdown())
+ .realTimeMonitoring(exam.isRealTimeMonitoring())
+ .zoomLink(exam.getZoomLink())
+ .hosted(exam.isHosted())
+
.build();
return examResponse;
@@ -924,29 +933,24 @@ 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");
- }
-
- gradeResponses = grades.stream()
+ // Convert grades to responses, even if the list is empty
+ List gradeResponses = grades.stream()
.map(grade -> new GradeResponse(grade.getId(), grade.getGrade(), grade.getMinMarks(), grade.getMaxMarks()))
.collect(Collectors.toList());
return ResponseEntity.ok(gradeResponses);
- } catch (RuntimeException ex) {
- return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);
} catch (Exception ex) {
+ log.error("An error occurred while fetching grades for examId {}: {}", examId, ex.getMessage());
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(null);
}
}
+
@Transactional
public ResponseEntity updateGrades(Long examId, List gradeRequestList) {
GenericAddOrUpdateResponse response = new GenericAddOrUpdateResponse();
@@ -1335,7 +1339,154 @@ public List getCandidateConflictingExams(Long exa
return responseList; // This will return a list of conflicting exams or empty if none found
}
+ @Transactional
+ public GenericResponse updateRealTimeMonitoring(Long examId, RealTimeMonitoringRequest dto) {
+ // Fetch the exam by ID
+ Exam exam = examRepository.findById(examId)
+ .orElseThrow(() -> new RuntimeException("Exam not found with ID: " + examId));
+
+ // Update the fields
+ exam.setRealTimeMonitoring(dto.isRealTimeMonitoring());
+ if (dto.isRealTimeMonitoring()) {
+ // If activating, set the Zoom link
+ exam.setZoomLink(dto.getZoomLink());
+ } else {
+ // If deactivating, clear the Zoom link
+ exam.setZoomLink(null);
+ }
+
+ // Save the updated exam
+ examRepository.save(exam);
+
+ // Create and return a success response
+ String message = dto.isRealTimeMonitoring() ?
+ "Real-time monitoring activated successfully." :
+ "Real-time monitoring deactivated successfully.";
+ return new GenericResponse("true", message);
+ }
+
+ public RealTimeMonitoringResponse getRealTimeMonitoringStatus(Long examId) {
+ // Find the exam by ID
+ Exam exam = examRepository.findById(examId).orElseThrow(() -> new RuntimeException("Exam not found"));
+
+ // Return the real-time monitoring status and Zoom link
+ return new RealTimeMonitoringResponse(exam.isRealTimeMonitoring(), exam.getZoomLink());
+ }
+
+ @Transactional
+ public GenericResponse updateBrowserLockdown(Long examId, boolean browserLockdown) {
+ // Fetch the exam by ID
+ Exam exam = examRepository.findById(examId)
+ .orElseThrow(() -> new RuntimeException("Exam not found with ID: " + examId));
+
+ // Update the browserLockdown status
+ exam.setBrowserLockdown(browserLockdown);
+ examRepository.save(exam);
+
+ // Construct and return the response
+ String message = browserLockdown ?
+ "Browser lockdown activated successfully." :
+ "Browser lockdown deactivated successfully.";
+ return new GenericResponse("true", message);
+ }
+
+ @Transactional(readOnly = true)
+ public boolean getBrowserLockdownStatus(Long examId) {
+ Exam exam = examRepository.findById(examId)
+ .orElseThrow(() -> new RuntimeException("Exam not found with ID: " + examId));
+ return exam.isBrowserLockdown();
+ }
+
+ @Transactional
+ public GenericResponse updateHostedStatus(Long examId, boolean hosted) {
+ Exam exam = examRepository.findById(examId)
+ .orElseThrow(() -> new RuntimeException("Exam not found with ID: " + examId));
+
+ exam.setHosted(hosted);
+ examRepository.save(exam);
+
+ if (hosted) {
+ notifyCandidates(exam);
+ }
+
+ String message = hosted ? "Exam hosting activated successfully." : "Exam hosting deactivated successfully.";
+ return new GenericResponse("true", message);
+ }
+
+ private void notifyCandidates(Exam exam) {
+ Set candidates = exam.getCandidates();
+ if (candidates == null || candidates.isEmpty()) {
+ return; // No candidates to notify
+ }
+
+ String emailSubject = "Exam Hosted Notification";
+ String emailContent = String.format(
+ """
+ Dear Candidate,
+
+ The exam "%s" has been hosted. Please log in to your account for further details.
+
+ Exam Details:
+ Title: %s
+ Start Date and Time: %s
+ End Date and Time: %s
+
+ Best regards,
+ Testify Team
+ """,
+ exam.getTitle(),
+ exam.getTitle(),
+ exam.getStartDatetime(),
+ exam.getEndDatetime()
+ );
+
+ for (Candidate candidate : candidates) {
+ String email = candidate.getEmail(); // Assuming `Candidate` inherits `email` from `User`
+ try {
+ emailSender.send(email, emailContent);
+ } catch (Exception e) {
+ log.error("Failed to send email to candidate: {}", email, e);
+ }
+ }
+ }
+
+ @Transactional(readOnly = true)
+ public boolean getHostedStatus(Long examId) {
+ Exam exam = examRepository.findById(examId)
+ .orElseThrow(() -> new RuntimeException("Exam not found with ID: " + examId));
+ return exam.isHosted();
+ }
+ @Transactional
+ public void assignModerator(Long examId, String moderatorEmail) {
+ Exam exam = examRepository.findById(examId)
+ .orElseThrow(() -> new RuntimeException("Exam not found with ID: " + examId));
+
+ log.info("Exam found with ID: {}", examId);
+ log.info("Searching for moderator with email: {}", moderatorEmail);
+
+ ExamSetter moderator = examSetterRepository.findByEmail(moderatorEmail)
+ .orElseThrow(() -> new RuntimeException("Exam setter not found with email: " + moderatorEmail));
+
+ log.info("Moderator found with email: {}", moderatorEmail);
+
+
+ exam.setModerator(moderator);
+ examRepository.save(exam);
+ }
+
+ @Transactional
+ public ModeratorResponse getModeratorDetails(Long examId) {
+ Exam exam = examRepository.findById(examId)
+ .orElseThrow(() -> new RuntimeException("Exam not found with ID: " + examId));
+
+ ExamSetter moderator = exam.getModerator();
+ if (moderator == null) {
+ return null; // Return null if no moderator is assigned
+ }
+
+ return new ModeratorResponse(moderator.getEmail(), moderator.getFirstName(), moderator.getLastName());
+ }
}
diff --git a/src/main/java/com/testify/Testify_Backend/service/ExamSetterService.java b/src/main/java/com/testify/Testify_Backend/service/ExamSetterService.java
index 1e06c7f..9389938 100644
--- a/src/main/java/com/testify/Testify_Backend/service/ExamSetterService.java
+++ b/src/main/java/com/testify/Testify_Backend/service/ExamSetterService.java
@@ -11,4 +11,6 @@ public interface ExamSetterService {
long checkSetterRegistration(String token);
GenericAddOrUpdateResponse addSetterToOrganization(String token);
+
+ GenericAddOrUpdateResponse deleteSetter(String setterId, String organizationId);
}
diff --git a/src/main/java/com/testify/Testify_Backend/service/ExamSetterServiceImpl.java b/src/main/java/com/testify/Testify_Backend/service/ExamSetterServiceImpl.java
index 7239611..e54b033 100644
--- a/src/main/java/com/testify/Testify_Backend/service/ExamSetterServiceImpl.java
+++ b/src/main/java/com/testify/Testify_Backend/service/ExamSetterServiceImpl.java
@@ -2,8 +2,10 @@
import com.testify.Testify_Backend.model.ExamSetter;
import com.testify.Testify_Backend.model.ExamSetterInvitation;
+import com.testify.Testify_Backend.model.ExamSetterOrganization;
import com.testify.Testify_Backend.model.Organization;
import com.testify.Testify_Backend.repository.ExamSetterInvitationRepository;
+import com.testify.Testify_Backend.repository.ExamSetterOrganizationRepository;
import com.testify.Testify_Backend.repository.ExamSetterRepository;
import com.testify.Testify_Backend.repository.OrganizationRepository;
import com.testify.Testify_Backend.responses.GenericAddOrUpdateResponse;
@@ -27,10 +29,13 @@ public class ExamSetterServiceImpl implements ExamSetterService {
@Autowired
private ModelMapper modelMapper;
+ @Autowired
+ private ExamSetterOrganizationRepository examSetterOrganizationRepository;
@Override
public Set getOrganizations(long setterId) {
Optional examSetter = examSetterRepository.findById(setterId);
+
Set organizationResponses = new HashSet<>();
if (examSetter.isPresent()) {
Set organizations = examSetter.get().getOrganizations();
@@ -63,6 +68,12 @@ public GenericAddOrUpdateResponse addSetterToOrganization(String token) {
examSetter.getOrganizations().add(organization);
organization.getExamSetters().add(examSetter);
+// add to examSetterOrganization
+ ExamSetterOrganization examSetterOrganization = new ExamSetterOrganization();
+ examSetterOrganization.setOrganizationID(String.valueOf(organization.getId()));
+ examSetterOrganization.setExamSetterID(String.valueOf(examSetter.getId()));
+ examSetterOrganizationRepository.save(examSetterOrganization);
+
examSetterRepository.save(examSetter);
organizationRepository.save(organization);
@@ -70,4 +81,21 @@ public GenericAddOrUpdateResponse addSetterToOrganization(String token) {
response.setMessage("Successfully added an organization");
return response;
}
+
+ @Override
+ public GenericAddOrUpdateResponse deleteSetter(String setterId, String organizationId) {
+ GenericAddOrUpdateResponse response = new GenericAddOrUpdateResponse();
+ ExamSetterOrganization examSetter = examSetterOrganizationRepository.findByOrganizationIDAndExamSetterID(organizationId, setterId);
+ try{
+ examSetter.setDeleted(true);
+ examSetterOrganizationRepository.save(examSetter);
+ response.setSuccess(true);
+ response.setMessage("Successfully deleted the setter");
+ }
+ catch (Exception e){
+ response.setSuccess(false);
+ response.setMessage("Failed to delete the setter");
+ }
+ return response;
+ }
}
diff --git a/src/main/java/com/testify/Testify_Backend/service/GradingService.java b/src/main/java/com/testify/Testify_Backend/service/GradingService.java
index 654fae6..3200ad8 100644
--- a/src/main/java/com/testify/Testify_Backend/service/GradingService.java
+++ b/src/main/java/com/testify/Testify_Backend/service/GradingService.java
@@ -1,8 +1,11 @@
package com.testify.Testify_Backend.service;
import com.testify.Testify_Backend.model.CandidateExamSession;
+import com.testify.Testify_Backend.model.ExamCandidateGrade;
import com.testify.Testify_Backend.model.Grade;
+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;
import java.util.List;
import java.util.Map;
@@ -11,4 +14,8 @@ public interface GradingService {
List getEssayDetails(Long examId, Long userId);
List getGradingSchemeForExam(Long examId);
List