diff --git a/src/main/java/io/wisoft/prepair/prepair_api/common/exception/ErrorCode.java b/src/main/java/io/wisoft/prepair/prepair_api/common/exception/ErrorCode.java index 45092dc..4a694cc 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/common/exception/ErrorCode.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/common/exception/ErrorCode.java @@ -25,6 +25,7 @@ public enum ErrorCode { // Session SESSION_NOT_FOUND(HttpStatus.NOT_FOUND, "세션을 찾을 수 없습니다."), + SESSION_NOT_FINISHED(HttpStatus.BAD_REQUEST, "아직 진행 중인 세션입니다."), // Question QUESTION_NOT_FOUND(HttpStatus.NOT_FOUND, "질문을 찾을 수 없습니다."), diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/question/repository/QuestionRepository.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/question/repository/QuestionRepository.java index c6a713f..afd6219 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/question/repository/QuestionRepository.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/question/repository/QuestionRepository.java @@ -20,7 +20,9 @@ public interface QuestionRepository extends JpaRepository findByIdAndMemberId(UUID id, UUID memberId); - List findByInterviewSessionId(UUID sessionId); + Optional findByIdAndMemberIdAndInterviewSessionId(UUID id, UUID memberId, UUID sessionId); + + List findByInterviewSessionIdOrderByCreatedAtAsc(UUID sessionId); @Modifying(clearAutomatically = true) @Query("UPDATE InterviewQuestion q SET q.latestScore = :score " + diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/session/controller/SessionController.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/session/controller/SessionController.java index 6c01e58..86c98ca 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/session/controller/SessionController.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/session/controller/SessionController.java @@ -1,14 +1,18 @@ package io.wisoft.prepair.prepair_api.interview.session.controller; import io.wisoft.prepair.prepair_api.common.response.ApiResponse; +import io.wisoft.prepair.prepair_api.interview.session.dto.response.CreateSessionResponse; import io.wisoft.prepair.prepair_api.interview.session.dto.response.SessionDetailResponse; import io.wisoft.prepair.prepair_api.interview.session.dto.response.SessionResponse; import io.wisoft.prepair.prepair_api.interview.session.service.SessionService; import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestHeader; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import java.util.List; @@ -37,4 +41,25 @@ public ApiResponse getSession( SessionDetailResponse data = sessionService.getSessionDetail(sessionId, memberId); return ApiResponse.ok(data, "세션 결과를 조회했습니다."); } + + @PostMapping("/{sessionId}") + @ResponseStatus(HttpStatus.CREATED) + public ApiResponse createSession( + @PathVariable UUID sessionId, + @RequestHeader("X-User-Id") UUID memberId + ) { + CreateSessionResponse data = sessionService.createSession(sessionId, memberId); + return ApiResponse.created(data, "세션이 생성되었습니다."); + } + + @PostMapping("/{sessionId}/questions/{questionId}") + @ResponseStatus(HttpStatus.CREATED) + public ApiResponse createQuestion( + @PathVariable UUID sessionId, + @PathVariable UUID questionId, + @RequestHeader("X-User-Id") UUID memberId + ) { + CreateSessionResponse data = sessionService.createQuestion(sessionId, questionId, memberId); + return ApiResponse.created(data, "세션이 생성되었습니다."); + } } diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/session/dto/response/CreateSessionResponse.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/session/dto/response/CreateSessionResponse.java new file mode 100644 index 0000000..858df78 --- /dev/null +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/session/dto/response/CreateSessionResponse.java @@ -0,0 +1,7 @@ +package io.wisoft.prepair.prepair_api.interview.session.dto.response; + +import java.util.List; +import java.util.UUID; + +public record CreateSessionResponse(UUID sessionId, List questionIds) { +} diff --git a/src/main/java/io/wisoft/prepair/prepair_api/interview/session/service/SessionService.java b/src/main/java/io/wisoft/prepair/prepair_api/interview/session/service/SessionService.java index ab938ff..9357956 100644 --- a/src/main/java/io/wisoft/prepair/prepair_api/interview/session/service/SessionService.java +++ b/src/main/java/io/wisoft/prepair/prepair_api/interview/session/service/SessionService.java @@ -9,6 +9,7 @@ import io.wisoft.prepair.prepair_api.interview.answer.repository.FeedbackRepository; import io.wisoft.prepair.prepair_api.interview.question.entity.InterviewQuestion; import io.wisoft.prepair.prepair_api.interview.question.repository.QuestionRepository; +import io.wisoft.prepair.prepair_api.interview.session.dto.response.CreateSessionResponse; import io.wisoft.prepair.prepair_api.interview.session.dto.response.SessionDetailResponse; import io.wisoft.prepair.prepair_api.interview.session.dto.response.SessionResponse; import io.wisoft.prepair.prepair_api.interview.session.entity.InterviewSession; @@ -60,7 +61,7 @@ public SessionDetailResponse getSessionDetail(UUID sessionId, UUID memberId) { } public List buildQuestionFeedbacks(UUID sessionId) { - List questions = questionRepository.findByInterviewSessionId(sessionId); + List questions = questionRepository.findByInterviewSessionIdOrderByCreatedAtAsc(sessionId); Map answerMap = getAnswerMap(sessionId); Map> feedbackMap = getFeedbackMap(sessionId); @@ -76,7 +77,7 @@ public List buildQuestionFeedbacks(UUID } List answerFeedbacks = feedbackMap.getOrDefault(answer.getId(), List.of()); - InterviewFeedback combined = findOptionalFeedback(answerFeedbacks, FeedbackType.COMBINED); + InterviewFeedback combined = findFeedback(answerFeedbacks, FeedbackType.COMBINED); result.add(new SessionDetailResponse.QuestionFeedback( question.getId(), @@ -91,10 +92,68 @@ public List buildQuestionFeedbacks(UUID return result; } + @Transactional + public CreateSessionResponse createSession(UUID oldSessionId, UUID memberId) { + InterviewSession oldSession = sessionRepository.findById(oldSessionId) + .orElseThrow(() -> new BusinessException(ErrorCode.SESSION_NOT_FOUND)); + + if (!oldSession.getMemberId().equals(memberId)) { + throw new BusinessException(ErrorCode.FORBIDDEN); + } + if (oldSession.getStatus() == SessionStatus.IN_PROGRESS) { + throw new BusinessException(ErrorCode.SESSION_NOT_FINISHED); + } + + List oldQuestions = questionRepository.findByInterviewSessionIdOrderByCreatedAtAsc(oldSessionId); + InterviewSession newSession = sessionRepository.save(new InterviewSession(memberId, oldQuestions.size())); + + List newQuestionIds = oldQuestions.stream() + .map(q -> cloneQuestion(q, newSession).getId()) + .toList(); + + return new CreateSessionResponse(newSession.getId(), newQuestionIds); + } + + @Transactional + public CreateSessionResponse createQuestion(UUID oldSessionId, UUID oldQuestionId, UUID memberId) { + InterviewSession oldSession = sessionRepository.findById(oldSessionId) + .orElseThrow(() -> new BusinessException(ErrorCode.SESSION_NOT_FOUND)); + + if (!oldSession.getMemberId().equals(memberId)) { + throw new BusinessException(ErrorCode.FORBIDDEN); + } + if (oldSession.getStatus() == SessionStatus.IN_PROGRESS) { + throw new BusinessException(ErrorCode.SESSION_NOT_FINISHED); + } + + InterviewQuestion oldQuestion = questionRepository.findByIdAndMemberIdAndInterviewSessionId( + oldQuestionId, + memberId, + oldSessionId + ) + .orElseThrow(() -> new BusinessException(ErrorCode.QUESTION_NOT_FOUND)); + + InterviewSession newSession = sessionRepository.save(new InterviewSession(memberId, 1)); + InterviewQuestion newQuestion = cloneQuestion(oldQuestion, newSession); + + return new CreateSessionResponse(newSession.getId(), List.of(newQuestion.getId())); + } + + private InterviewQuestion cloneQuestion(InterviewQuestion source, InterviewSession newSession) { + return questionRepository.save(new InterviewQuestion( + source.getMemberId(), + source.getQuestion(), + source.getQuestionType(), + source.getQuestionTag(), + source.getJobPosting(), + newSession + )); + } + @Transactional public void saveCompletedSession(UUID sessionId, int finalScore, String finalFeedback) { InterviewSession session = sessionRepository.findById(sessionId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 세션입니다.")); + .orElseThrow(() -> new BusinessException(ErrorCode.SESSION_NOT_FOUND)); if (session.getStatus() == SessionStatus.COMPLETED) { return; @@ -106,7 +165,7 @@ public void saveCompletedSession(UUID sessionId, int finalScore, String finalFee @Transactional public void saveFailedSession(UUID sessionId) { InterviewSession session = sessionRepository.findById(sessionId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 세션입니다.")); + .orElseThrow(() -> new BusinessException(ErrorCode.SESSION_NOT_FOUND)); if (session.getStatus() != SessionStatus.IN_PROGRESS) { return; @@ -130,7 +189,7 @@ private Map> getFeedbackMap(UUID sessionId) { .collect(Collectors.groupingBy(f -> f.getInterviewAnswer().getId())); } - private InterviewFeedback findOptionalFeedback(List feedbacks, FeedbackType type) { + private InterviewFeedback findFeedback(List feedbacks, FeedbackType type) { return feedbacks.stream() .filter(f -> f.getFeedbackType() == type) .findFirst() @@ -138,7 +197,7 @@ private InterviewFeedback findOptionalFeedback(List feedbacks } private String getFeedbackText(List feedbacks, FeedbackType type) { - InterviewFeedback feedback = findOptionalFeedback(feedbacks, type); + InterviewFeedback feedback = findFeedback(feedbacks, type); return feedback == null ? null : feedback.getFeedback(); } }