Implement secure quiz question retrieval

This commit is contained in:
jieun committed 2026-10-20 20:49:28 +09:00
1 parent 7b5d091989
commit c03a7e6362
1 file changed
+47
@@ -1,5 +1,6 @@
package com.example.questionbank.service.quiz; package com.example.questionbank.service.quiz;
import com.example.questionbank.domain.Choice;
import com.example.questionbank.domain.ExamAttempt; import com.example.questionbank.domain.ExamAttempt;
import com.example.questionbank.domain.Question; import com.example.questionbank.domain.Question;
import com.example.questionbank.domain.Subject; import com.example.questionbank.domain.Subject;
@@ -9,8 +10,11 @@ import com.example.questionbank.repository.ExamAttemptRepository;
import com.example.questionbank.repository.QuestionRepository; import com.example.questionbank.repository.QuestionRepository;
import com.example.questionbank.repository.SubjectRepository; import com.example.questionbank.repository.SubjectRepository;
import com.example.questionbank.repository.UserRepository; import com.example.questionbank.repository.UserRepository;
import com.example.questionbank.web.rest.dto.ChoiceView;
import com.example.questionbank.web.rest.dto.ExamAttemptStartResult; import com.example.questionbank.web.rest.dto.ExamAttemptStartResult;
import com.example.questionbank.web.rest.dto.ExamQuestionView;
import java.time.Instant; import java.time.Instant;
import java.util.Comparator;
import java.util.List; import java.util.List;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
@@ -37,6 +41,7 @@ public class QuizAttemptService {
this.userRepository = userRepository; this.userRepository = userRepository;
} }
// 1. 시험 응시 시작
@Transactional @Transactional
public ExamAttemptStartResult start(Long subjectId, String login) { public ExamAttemptStartResult start(Long subjectId, String login) {
Subject subject = subjectRepository Subject subject = subjectRepository
@@ -65,6 +70,48 @@ public class QuizAttemptService {
attempt.setScore(0); attempt.setScore(0);
ExamAttempt saved = attemptRepository.save(attempt); ExamAttempt saved = attemptRepository.save(attempt);
return new ExamAttemptStartResult(saved.getId(), saved.getStartedAt()); return new ExamAttemptStartResult(saved.getId(), saved.getStartedAt());
} }
// 2. 시험 문제 조회 (정답은 숨김)
@Transactional(readOnly = true)
public List<ExamQuestionView> getQuestions(Long attemptId, String login) {
ExamAttempt attempt = getOwnedAttempt(attemptId, login);
if (attempt.getStatus() != AttemptStatus.IN_PROGRESS) {
throw new ResponseStatusException(HttpStatus.CONFLICT, "진행 중인 응시만 문제를 조회할 수 있습니다.");
}
return questionRepository
.findAllBySubjectIdAndActiveTrue(attempt.getSubject().getId())
.stream()
.sorted(Comparator.comparing(Question::getId))
.map(question ->
new ExamQuestionView(
question.getId(),
question.getPrompt(),
question
.getChoices()
.stream()
.sorted(Comparator.comparing(Choice::getDisplayOrder))
.map(choice -> new ChoiceView(choice.getOptionKey(), choice.getContent(), choice.getDisplayOrder()))
.toList()
)
)
.toList();
}
// 3. 응시 기록이 로그인한 사용자 본인의 것인지 확인
private ExamAttempt getOwnedAttempt(Long attemptId, String login) {
ExamAttempt attempt = attemptRepository
.findById(attemptId)
.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND, "응시를 찾을 수 없습니다."));
if (!attempt.getUser().getLogin().equals(login)) {
throw new ResponseStatusException(HttpStatus.NOT_FOUND, "응시를 찾을 수 없습니다.");
}
return attempt;
}
} }