From c03a7e63625835e30278cc58e4d474f3efd9091c Mon Sep 17 00:00:00 2001 From: jieun Date: Tue, 20 Oct 2026 20:49:28 +0900 Subject: [PATCH] Implement secure quiz question retrieval --- .../service/quiz/QuizAttemptService.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/src/main/java/com/example/questionbank/service/quiz/QuizAttemptService.java b/src/main/java/com/example/questionbank/service/quiz/QuizAttemptService.java index 74548e3..eb8b158 100644 --- a/src/main/java/com/example/questionbank/service/quiz/QuizAttemptService.java +++ b/src/main/java/com/example/questionbank/service/quiz/QuizAttemptService.java @@ -1,5 +1,6 @@ package com.example.questionbank.service.quiz; +import com.example.questionbank.domain.Choice; import com.example.questionbank.domain.ExamAttempt; import com.example.questionbank.domain.Question; 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.SubjectRepository; 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.ExamQuestionView; import java.time.Instant; +import java.util.Comparator; import java.util.List; import org.springframework.http.HttpStatus; import org.springframework.stereotype.Service; @@ -37,6 +41,7 @@ public class QuizAttemptService { this.userRepository = userRepository; } + // 1. 시험 응시 시작 @Transactional public ExamAttemptStartResult start(Long subjectId, String login) { Subject subject = subjectRepository @@ -65,6 +70,48 @@ public class QuizAttemptService { attempt.setScore(0); ExamAttempt saved = attemptRepository.save(attempt); + return new ExamAttemptStartResult(saved.getId(), saved.getStartedAt()); } + + // 2. 시험 문제 조회 (정답은 숨김) + @Transactional(readOnly = true) + public List 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; + } }