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 @@ -83,6 +83,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,13 @@ public ResponseEntity<String> saveAnswer(@RequestBody SaveAnswerRequest request)
} catch (Exception e) {
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")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
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.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 java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api/v1/grade")
@RequiredArgsConstructor
public class GradingController {

private final GradingService gradingService;

@GetMapping("/{examId}/users/{userId}/essay-details")
public ResponseEntity<List<EssayDetailsResponse>> getEssayDetails(
@PathVariable Long examId,
@PathVariable Long userId) {
List<EssayDetailsResponse> response = gradingService.getEssayDetails(examId, userId);
return ResponseEntity.ok(response);
}

@GetMapping("/{examId}/grading-scheme")
public ResponseEntity<List<Grade>> getGradingScheme(@PathVariable Long examId) {
List<Grade> grades = gradingService.getGradingSchemeForExam(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);
}


}
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
Expand Up @@ -13,5 +13,5 @@
public class EssayAnswer extends CandidateExamAnswer {

@Column(name = "answer_text")
private String answerText; // The text answer for the essay question
private String answerText;
}
2 changes: 2 additions & 0 deletions src/main/java/com/testify/Testify_Backend/model/Exam.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package com.testify.Testify_Backend.model;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.testify.Testify_Backend.converter.QuestionSequenceConverter;
import com.testify.Testify_Backend.enums.ExamType;
import com.testify.Testify_Backend.enums.OrderType;
Expand Down Expand Up @@ -101,6 +102,7 @@ public class Exam {
private List<Long> questionSequence;

@OneToMany(mappedBy = "exam", cascade = CascadeType.ALL)
@JsonIgnore
private List<Grade> gradings;

@Column(nullable = false)
Expand Down
2 changes: 2 additions & 0 deletions src/main/java/com/testify/Testify_Backend/model/Grade.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.testify.Testify_Backend.model;

import com.fasterxml.jackson.annotation.JsonIgnore;
import jakarta.persistence.*;
import lombok.AllArgsConstructor;
import lombok.Getter;
Expand All @@ -24,6 +25,7 @@ public class Grade {

@ManyToOne
@JoinColumn(name = "exam_id")
@JsonIgnore
private Exam exam;

}
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,10 +1,30 @@
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
public interface CandidateExamAnswerRepository extends JpaRepository<CandidateExamAnswer, Long> {

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
Expand Up @@ -9,5 +9,6 @@
@Repository
public interface ExamSessionRepository extends JpaRepository<CandidateExamSession, Long> {
Optional<CandidateExamSession> findByCandidateIdAndExamIdAndInProgress(Long candidateId, Long examId, Boolean inProgress);
Optional<CandidateExamSession> findByExamIdAndCandidateId(Long examId, Long candidateId);
}

Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@
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 GradeRepository extends JpaRepository<Grade, Long> {
@Query("SELECT g FROM Grade g WHERE g.exam.id = :examId")
List<Grade> findByExamId(@Param("examId") Long examId);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
package com.testify.Testify_Backend.repository;

import com.testify.Testify_Backend.model.Essay;
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
public interface QuestionRepository extends JpaRepository<Question, Long>{
@Query("SELECT q FROM Question q WHERE q.exam.id = :examId AND q.isDeleted = false")
List<Question> findAllActiveQuestionsByExamId(@Param("examId") long examId);
List<Question> findByExamId(Long examId);

@Query("SELECT e FROM Essay e WHERE e.exam.id = :examId")
List<Essay> findAllEssaysByExamId(@Param("examId") Long examId);

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

import lombok.Builder;
import lombok.Data;

import java.util.List;

@Data
@Builder
public class EssayDetailsResponse {
private String questionText;
private List<CoverPointDto> coverPoints;
private String userAnswer;

@Data
@Builder
public static class CoverPointDto {
private String coverPointText;
private double marks;
}
}

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
Expand Up @@ -10,9 +10,7 @@
import java.util.List;

public interface CandidateService {

List<CandidateResponse> getAllCandidatesForSearch();

// temp comment
public List<CandidateExam> getCandidateExams(String status);
public CandidateExam getCandidateExamDetails(Integer examId);
Expand Down
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,18 +53,23 @@ public class CandidateServiceImpl implements CandidateService {
@Autowired
private GenericResponse genericResponse;

@Autowired
private final ExamSessionRepository examSessionRepository;

@Override
public List<CandidateExam> getCandidateExams(String status) {
// Get the current user's email (you can adapt this to your actual method of getting the logged-in user's email)
String currentUserEmail = UserUtil.getCurrentUserName();
log.info("Current user email: {}", currentUserEmail);
String currentUserEmail = SecurityContextHolder.getContext().getAuthentication().getName();

if (currentUserEmail == null) {
throw new IllegalStateException("No authenticated user found");
}
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 @@ -81,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 @@ -97,7 +101,12 @@ public List<CandidateExam> getCandidateExams(String status) {

@Override
public CandidateExam getCandidateExamDetails(Integer examId) {
String currentUserEmail = UserUtil.getCurrentUserName();
String currentUserEmail = SecurityContextHolder.getContext().getAuthentication().getName();

if (currentUserEmail == null) {
throw new IllegalStateException("No authenticated user found");
}

Candidate candidate = candidateRepository.findByEmail(currentUserEmail)
.orElseThrow(() -> new EntityNotFoundException("Candidate not found"));

Expand All @@ -118,19 +127,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,8 @@ 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);

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