-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 화상 면접 재시도 기능 구현 (#83) #85
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<UUID> questionIds) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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<SessionDetailResponse.QuestionFeedback> buildQuestionFeedbacks(UUID sessionId) { | ||
| List<InterviewQuestion> questions = questionRepository.findByInterviewSessionId(sessionId); | ||
| List<InterviewQuestion> questions = questionRepository.findByInterviewSessionIdOrderByCreatedAtAsc(sessionId); | ||
| Map<UUID, InterviewAnswer> answerMap = getAnswerMap(sessionId); | ||
| Map<UUID, List<InterviewFeedback>> feedbackMap = getFeedbackMap(sessionId); | ||
|
|
||
|
|
@@ -76,7 +77,7 @@ public List<SessionDetailResponse.QuestionFeedback> buildQuestionFeedbacks(UUID | |
| } | ||
|
|
||
| List<InterviewFeedback> 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<SessionDetailResponse.QuestionFeedback> 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<InterviewQuestion> oldQuestions = questionRepository.findByInterviewSessionIdOrderByCreatedAtAsc(oldSessionId); | ||
| InterviewSession newSession = sessionRepository.save(new InterviewSession(memberId, oldQuestions.size())); | ||
|
Comment on lines
+107
to
+108
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이전 세션( List<InterviewQuestion> oldQuestions = questionRepository.findByInterviewSessionIdOrderByCreatedAtAsc(oldSessionId);
if (oldQuestions.isEmpty()) {
throw new BusinessException(ErrorCode.QUESTION_NOT_FOUND);
}
InterviewSession newSession = sessionRepository.save(new InterviewSession(memberId, oldQuestions.size())); |
||
|
|
||
| List<UUID> newQuestionIds = oldQuestions.stream() | ||
| .map(q -> cloneQuestion(q, newSession).getId()) | ||
| .toList(); | ||
|
Comment on lines
+110
to
+112
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 스트림( 다음과 같이 List<InterviewQuestion> newQuestions = oldQuestions.stream()
.map(q -> new InterviewQuestion(
q.getMemberId(),
q.getQuestion(),
q.getQuestionType(),
q.getQuestionTag(),
q.getJobPosting(),
newSession
))
.toList();
List<UUID> newQuestionIds = questionRepository.saveAll(newQuestions).stream()
.map(InterviewQuestion::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,15 +189,15 @@ private Map<UUID, List<InterviewFeedback>> getFeedbackMap(UUID sessionId) { | |
| .collect(Collectors.groupingBy(f -> f.getInterviewAnswer().getId())); | ||
| } | ||
|
|
||
| private InterviewFeedback findOptionalFeedback(List<InterviewFeedback> feedbacks, FeedbackType type) { | ||
| private InterviewFeedback findFeedback(List<InterviewFeedback> feedbacks, FeedbackType type) { | ||
| return feedbacks.stream() | ||
| .filter(f -> f.getFeedbackType() == type) | ||
| .findFirst() | ||
| .orElse(null); | ||
| } | ||
|
|
||
| private String getFeedbackText(List<InterviewFeedback> feedbacks, FeedbackType type) { | ||
| InterviewFeedback feedback = findOptionalFeedback(feedbacks, type); | ||
| InterviewFeedback feedback = findFeedback(feedbacks, type); | ||
| return feedback == null ? null : feedback.getFeedback(); | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
현재 API 경로 설계에서
POST /api/interviews/sessions/{sessionId}와POST /api/interviews/sessions/{sessionId}/questions/{questionId}는 기존 세션의 정보를 바탕으로 새로운 세션을 생성하고 있습니다.하지만 이 경로는 기존 세션 리소스를 수정하거나 기존 세션 하위에 질문을 생성하는 것처럼 오해를 불러일으킬 수 있어 RESTful한 설계 관점에서 아쉬움이 있습니다.
다음과 같은 대안을 고려해 보시는 것을 추천합니다:
POST /api/interviews/sessions/{sessionId}/retry및POST /api/interviews/sessions/{sessionId}/questions/{questionId}/retry와 같이 행위(retry)를 경로에 명시합니다.POST /api/interviews/sessions엔드포인트에 요청 바디(DTO)로parentSessionId와 선택적으로questionId를 전달하여 새로운 세션을 생성하도록 단일화합니다.