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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
import com.testify.Testify_Backend.model.Candidate;
import com.testify.Testify_Backend.model.Organization;
import com.testify.Testify_Backend.repository.CandidateRepository;
import com.testify.Testify_Backend.requests.exam_management.CandidateExamAnswerRequest;
import com.testify.Testify_Backend.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.responses.exam_management.CandidateExamAnswerResponse;
import com.testify.Testify_Backend.service.CandidateService;
import com.testify.Testify_Backend.service.ExamManagementService;
import com.testify.Testify_Backend.service.ExamManagementServiceImpl;
import com.testify.Testify_Backend.utils.VarList;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand All @@ -23,6 +27,7 @@
@RequiredArgsConstructor
public class CandidateController {
private final CandidateService candidateService;
private final ExamManagementService examManagementService;

@GetMapping("/exams")
public ResponseEntity<List<CandidateExam>> getCandidateExams(@RequestParam(value = "status", required = false) String status) {
Expand Down Expand Up @@ -94,4 +99,9 @@ public ResponseEntity deleteCandidateProfile(long id){

}

@PostMapping("/check-active-session")
public CandidateExamAnswerResponse getCandidateAnswers(@RequestBody CandidateExamAnswerRequest request) {
return examManagementService.getCandidateAnswers(request.getCandidateId());
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ public ResponseEntity<QuestionListResponse> getAllQuestionsByExamId(@PathVariabl
return examManagementService.getAllQuestionsByExamId(examId);
}

@GetMapping("/{examId}/questions/{sessionId}/answers")
public ResponseEntity<QuestionListResponse> getAllQuestionsAnswersByExamId(
@PathVariable long examId,
@PathVariable long sessionId) {

return examManagementService.getAllQuestionsAnswersByExamId(examId, sessionId);
}

@PutMapping("/question/{questionId}")
public ResponseEntity<GenericDeleteResponse<Void>> deleteQuestion(@PathVariable long questionId){
return examManagementService.deleteQuestion(questionId);
Expand Down Expand Up @@ -153,6 +161,12 @@ public ResponseEntity<String> saveAnswer(@RequestBody SaveAnswerRequest request)
return new ResponseEntity<>("Error saving answer: " + e.getMessage(), HttpStatus.BAD_REQUEST);
}
}

@PutMapping("/{sessionId}/submit")
public ResponseEntity<String> submitExam(@PathVariable Long sessionId) {
examManagementService.markSessionAsComplete(sessionId);
return ResponseEntity.ok("Exam submitted successfully.");
}

@PostMapping("/{examId}/proctors")
public ResponseEntity<GenericAddOrUpdateResponse> addOrUpdateProctors(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package com.testify.Testify_Backend.controller;

import com.testify.Testify_Backend.model.CandidateExamSession;
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.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;

@RestController
@RequestMapping("/api/v1/grade")
Expand All @@ -33,4 +35,22 @@ public ResponseEntity<List<Grade>> getGradingScheme(@PathVariable Long examId) {
return ResponseEntity.ok(grades);
}

@GetMapping("/{sessionId}/mcq-details")
public ResponseEntity<List<Map<String, String>>> getResultsBySessionId(
@PathVariable Long sessionId) {

List<Map<String, String>> results = gradingService.getQuestionAndOptionBySessionId(sessionId);

return ResponseEntity.ok(results);
}

@PostMapping("/setExamCandidateGrade")
public ResponseEntity<String> setExamCandidateGrade(@RequestBody ExamCandidateGradeRequest examCandidateGradeRequest) {
String response = gradingService.setExamCandidateGrade(examCandidateGradeRequest);
return ResponseEntity.ok(response);
}




}
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public enum ExamStatus {
UPCOMING,
ONGOING,
EXPIRED
AVAILABLE,
COMPLETED, EXPIRED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@
public class MCQAnswer extends CandidateExamAnswer {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "option_id")
@JoinColumn(name = "option_id", nullable = true)
private MCQOption option; // Reference to the selected Option
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.testify.Testify_Backend.repository;

import com.testify.Testify_Backend.model.CandidateExamAnswer;
import com.testify.Testify_Backend.model.CandidateExamSession;
import com.testify.Testify_Backend.model.MCQOption;
import com.testify.Testify_Backend.model.Question;
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;
import java.util.Optional;

@Repository
Expand All @@ -14,4 +18,13 @@ public interface CandidateExamAnswerRepository extends JpaRepository<CandidateEx
Optional<CandidateExamAnswer> findByCandidateExamSessionIdAndQuestionId (Long sessionId, Long questionId);
@Query("SELECT e.answerText FROM EssayAnswer e WHERE e.id = :id")
String findEssayAnswerTextById(@Param("id") Long id);

@Query("SELECT e.option FROM MCQAnswer e WHERE e.id = :id")
MCQOption findMcqAnswerTextById(@Param("id") Long id);

List<CandidateExamAnswer> findByCandidateExamSessionId(Long sessionId);



CandidateExamAnswer findByCandidateExamSessionIdAndQuestionId(Long candidateExamSession_id, long question_id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.testify.Testify_Backend.repository;

import com.testify.Testify_Backend.model.ExamCandidateGrade;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ExamCandidateGradeRepository extends JpaRepository<ExamCandidateGrade, String> {
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.testify.Testify_Backend.model.CandidateExamSession;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

import java.util.Optional;
Expand All @@ -10,5 +11,8 @@
public interface ExamSessionRepository extends JpaRepository<CandidateExamSession, Long> {
Optional<CandidateExamSession> findByCandidateIdAndExamIdAndInProgress(Long candidateId, Long examId, Boolean inProgress);
Optional<CandidateExamSession> findByExamIdAndCandidateId(Long examId, Long candidateId);

@Query("SELECT s FROM CandidateExamSession s WHERE s.candidate.id = :candidateId AND s.inProgress = true AND s.endTime >= CURRENT_TIMESTAMP")
CandidateExamSession findInProgressSession(Long candidateId);
}

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

import lombok.Data;

@Data
public class CandidateExamAnswerRequest {
private Long candidateId;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.testify.Testify_Backend.responses;

import lombok.Builder;
import lombok.Data;

import java.util.List;

@Data
@Builder
public class McqDetailsResponse {
private Long questionId;
private String questionText;
private String questionType;
private String difficultyLevel; // Could be nullable if not required
private List<OptionResponse> options;
private Long userAnswer;

@Data
@Builder
public static class OptionResponse {

private Long optionId;
private String optionText;
private boolean correct;
private Double marks;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.testify.Testify_Backend.responses.exam_management;

import lombok.AllArgsConstructor;
import lombok.Data;

import java.util.List;

@Data
@AllArgsConstructor
public class CandidateExamAnswerResponse {
private Long examId;
private List<AnswerDetail> answers;

@Data
@AllArgsConstructor
public static class AnswerDetail {
private Long questionId;
private String questionType;
private Object answer;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,10 @@

import com.testify.Testify_Backend.enums.ExamStatus;
import com.testify.Testify_Backend.model.Candidate;
import com.testify.Testify_Backend.model.CandidateExamSession;
import com.testify.Testify_Backend.model.Exam;
import com.testify.Testify_Backend.model.Organization;
import com.testify.Testify_Backend.repository.CandidateRepository;
import com.testify.Testify_Backend.repository.ExamRepository;
import com.testify.Testify_Backend.repository.OrganizationRepository;
import com.testify.Testify_Backend.repository.UserRepository;
import com.testify.Testify_Backend.repository.*;
import com.testify.Testify_Backend.responses.GenericResponse;
import com.testify.Testify_Backend.responses.candidate_management.CandidateExam;
import com.testify.Testify_Backend.responses.candidate_management.CandidateResponse;
Expand Down Expand Up @@ -55,6 +53,9 @@ public class CandidateServiceImpl implements CandidateService {
@Autowired
private GenericResponse genericResponse;

@Autowired
private final ExamSessionRepository examSessionRepository;

@Override
public List<CandidateExam> getCandidateExams(String status) {
String currentUserEmail = SecurityContextHolder.getContext().getAuthentication().getName();
Expand All @@ -65,10 +66,10 @@ public List<CandidateExam> getCandidateExams(String status) {
Candidate candidate = candidateRepository.findByEmail(currentUserEmail)
.orElseThrow(() -> new EntityNotFoundException("Candidate not found"));

// Get exams directly associated with the candidate

List<Exam> candidateExams = examRepository.findExamsByCandidateId(candidate.getId());

// Get all public exams

List<Exam> publicExams = examRepository.findAllPublicExams();

// Combine both lists, ensuring no duplicates
Expand All @@ -83,10 +84,11 @@ public List<CandidateExam> getCandidateExams(String status) {
// Filter exams based on the provided status
return allExams.stream()
.map(exam -> {
return getCandidateExam(exam, now);
CandidateExamSession candidateExamSession = examSessionRepository.findByExamIdAndCandidateId(exam.getId(), candidate.getId())
.orElse(null);
return getCandidateExam(exam, now, candidateExamSession);
})
.filter(candidateExam -> {
// Apply the status filter
if (status == null) {
return true;
}
Expand All @@ -99,7 +101,6 @@ public List<CandidateExam> getCandidateExams(String status) {

@Override
public CandidateExam getCandidateExamDetails(Integer examId) {
// Get the username (email) from SecurityContextHolder
String currentUserEmail = SecurityContextHolder.getContext().getAuthentication().getName();

if (currentUserEmail == null) {
Expand All @@ -125,19 +126,25 @@ public CandidateExam getCandidateExamDetails(Integer examId) {

// Determine the status of the exam
LocalDateTime now = LocalDateTime.now();
return getCandidateExam(exam, now);
CandidateExamSession candidateExamSession = examSessionRepository.findByExamIdAndCandidateId(exam.getId(), candidate.getId())
.orElse(null);
return getCandidateExam(exam, now, candidateExamSession);
}

@NotNull
private CandidateExam getCandidateExam(Exam exam, LocalDateTime now) {
private CandidateExam getCandidateExam(Exam exam, LocalDateTime now, CandidateExamSession session) {
CandidateExam candidateExam = modelMapper.map(exam, CandidateExam.class);

if (now.isBefore(exam.getStartDatetime())) {
candidateExam.setStatus(ExamStatus.UPCOMING);
} else if (now.isAfter(exam.getEndDatetime())) {
} else if (session == null && now.isAfter(exam.getEndDatetime())) {
candidateExam.setStatus(ExamStatus.EXPIRED);
} else {
} else if (session == null) {
candidateExam.setStatus(ExamStatus.AVAILABLE);
} else if (session.getInProgress()) {
candidateExam.setStatus(ExamStatus.ONGOING);
} else {
candidateExam.setStatus(ExamStatus.COMPLETED);
}

return candidateExam;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ public interface ExamManagementService {
ExamResponse getExamById(long examId);
ExamSessionResponse startExam(StartExamRequest request);
void saveAnswer(Long sessionId, Long questionId, Long optionId, String answerText);
void markSessionAsComplete(Long sessionId);
ResponseEntity<QuestionListResponse> getAllQuestionsAnswersByExamId(long examId, long sessionId);
CandidateExamAnswerResponse getCandidateAnswers(Long candidateId);

GenericAddOrUpdateResponse<MCQUpdateRequest> updateMCQQuestion(MCQUpdateRequest mcqUpdateRequest);
GenericAddOrUpdateResponse<MCQRequest> saveMCQ(MCQRequest mcqRequest);
Expand Down
Loading
Loading