Skip to content

Commit 5f9c398

Browse files
committed
feat: 화상 면접 재시도 기능 구현 (#83)
1 parent 795efb3 commit 5f9c398

5 files changed

Lines changed: 101 additions & 7 deletions

File tree

src/main/java/io/wisoft/prepair/prepair_api/common/exception/ErrorCode.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public enum ErrorCode {
2525

2626
// Session
2727
SESSION_NOT_FOUND(HttpStatus.NOT_FOUND, "세션을 찾을 수 없습니다."),
28+
SESSION_NOT_FINISHED(HttpStatus.BAD_REQUEST, "아직 진행 중인 세션입니다."),
2829

2930
// Question
3031
QUESTION_NOT_FOUND(HttpStatus.NOT_FOUND, "질문을 찾을 수 없습니다."),

src/main/java/io/wisoft/prepair/prepair_api/interview/question/repository/QuestionRepository.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ public interface QuestionRepository extends JpaRepository<InterviewQuestion, UUI
2020

2121
Optional<InterviewQuestion> findByIdAndMemberId(UUID id, UUID memberId);
2222

23-
List<InterviewQuestion> findByInterviewSessionId(UUID sessionId);
23+
Optional<InterviewQuestion> findByIdAndMemberIdAndInterviewSessionId(UUID id, UUID memberId, UUID sessionId);
24+
25+
List<InterviewQuestion> findByInterviewSessionIdOrderByCreatedAtAsc(UUID sessionId);
2426

2527
@Modifying(clearAutomatically = true)
2628
@Query("UPDATE InterviewQuestion q SET q.latestScore = :score " +

src/main/java/io/wisoft/prepair/prepair_api/interview/session/controller/SessionController.java

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,18 @@
11
package io.wisoft.prepair.prepair_api.interview.session.controller;
22

33
import io.wisoft.prepair.prepair_api.common.response.ApiResponse;
4+
import io.wisoft.prepair.prepair_api.interview.session.dto.response.CreateSessionResponse;
45
import io.wisoft.prepair.prepair_api.interview.session.dto.response.SessionDetailResponse;
56
import io.wisoft.prepair.prepair_api.interview.session.dto.response.SessionResponse;
67
import io.wisoft.prepair.prepair_api.interview.session.service.SessionService;
78
import lombok.RequiredArgsConstructor;
9+
import org.springframework.http.HttpStatus;
810
import org.springframework.web.bind.annotation.GetMapping;
911
import org.springframework.web.bind.annotation.PathVariable;
12+
import org.springframework.web.bind.annotation.PostMapping;
1013
import org.springframework.web.bind.annotation.RequestHeader;
1114
import org.springframework.web.bind.annotation.RequestMapping;
15+
import org.springframework.web.bind.annotation.ResponseStatus;
1216
import org.springframework.web.bind.annotation.RestController;
1317

1418
import java.util.List;
@@ -37,4 +41,25 @@ public ApiResponse<SessionDetailResponse> getSession(
3741
SessionDetailResponse data = sessionService.getSessionDetail(sessionId, memberId);
3842
return ApiResponse.ok(data, "세션 결과를 조회했습니다.");
3943
}
44+
45+
@PostMapping("/{sessionId}")
46+
@ResponseStatus(HttpStatus.CREATED)
47+
public ApiResponse<CreateSessionResponse> createSession(
48+
@PathVariable UUID sessionId,
49+
@RequestHeader("X-User-Id") UUID memberId
50+
) {
51+
CreateSessionResponse data = sessionService.createSession(sessionId, memberId);
52+
return ApiResponse.created(data, "세션이 생성되었습니다.");
53+
}
54+
55+
@PostMapping("/{sessionId}/questions/{questionId}")
56+
@ResponseStatus(HttpStatus.CREATED)
57+
public ApiResponse<CreateSessionResponse> createQuestion(
58+
@PathVariable UUID sessionId,
59+
@PathVariable UUID questionId,
60+
@RequestHeader("X-User-Id") UUID memberId
61+
) {
62+
CreateSessionResponse data = sessionService.createQuestion(sessionId, questionId, memberId);
63+
return ApiResponse.created(data, "세션이 생성되었습니다.");
64+
}
4065
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package io.wisoft.prepair.prepair_api.interview.session.dto.response;
2+
3+
import java.util.List;
4+
import java.util.UUID;
5+
6+
public record CreateSessionResponse(UUID sessionId, List<UUID> questionIds) {
7+
}

src/main/java/io/wisoft/prepair/prepair_api/interview/session/service/SessionService.java

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import io.wisoft.prepair.prepair_api.interview.answer.repository.FeedbackRepository;
1010
import io.wisoft.prepair.prepair_api.interview.question.entity.InterviewQuestion;
1111
import io.wisoft.prepair.prepair_api.interview.question.repository.QuestionRepository;
12+
import io.wisoft.prepair.prepair_api.interview.session.dto.response.CreateSessionResponse;
1213
import io.wisoft.prepair.prepair_api.interview.session.dto.response.SessionDetailResponse;
1314
import io.wisoft.prepair.prepair_api.interview.session.dto.response.SessionResponse;
1415
import io.wisoft.prepair.prepair_api.interview.session.entity.InterviewSession;
@@ -60,7 +61,7 @@ public SessionDetailResponse getSessionDetail(UUID sessionId, UUID memberId) {
6061
}
6162

6263
public List<SessionDetailResponse.QuestionFeedback> buildQuestionFeedbacks(UUID sessionId) {
63-
List<InterviewQuestion> questions = questionRepository.findByInterviewSessionId(sessionId);
64+
List<InterviewQuestion> questions = questionRepository.findByInterviewSessionIdOrderByCreatedAtAsc(sessionId);
6465
Map<UUID, InterviewAnswer> answerMap = getAnswerMap(sessionId);
6566
Map<UUID, List<InterviewFeedback>> feedbackMap = getFeedbackMap(sessionId);
6667

@@ -76,7 +77,7 @@ public List<SessionDetailResponse.QuestionFeedback> buildQuestionFeedbacks(UUID
7677
}
7778

7879
List<InterviewFeedback> answerFeedbacks = feedbackMap.getOrDefault(answer.getId(), List.of());
79-
InterviewFeedback combined = findOptionalFeedback(answerFeedbacks, FeedbackType.COMBINED);
80+
InterviewFeedback combined = findFeedback(answerFeedbacks, FeedbackType.COMBINED);
8081

8182
result.add(new SessionDetailResponse.QuestionFeedback(
8283
question.getId(),
@@ -91,10 +92,68 @@ public List<SessionDetailResponse.QuestionFeedback> buildQuestionFeedbacks(UUID
9192
return result;
9293
}
9394

95+
@Transactional
96+
public CreateSessionResponse createSession(UUID oldSessionId, UUID memberId) {
97+
InterviewSession oldSession = sessionRepository.findById(oldSessionId)
98+
.orElseThrow(() -> new BusinessException(ErrorCode.SESSION_NOT_FOUND));
99+
100+
if (!oldSession.getMemberId().equals(memberId)) {
101+
throw new BusinessException(ErrorCode.FORBIDDEN);
102+
}
103+
if (oldSession.getStatus() == SessionStatus.IN_PROGRESS) {
104+
throw new BusinessException(ErrorCode.SESSION_NOT_FINISHED);
105+
}
106+
107+
List<InterviewQuestion> oldQuestions = questionRepository.findByInterviewSessionIdOrderByCreatedAtAsc(oldSessionId);
108+
InterviewSession newSession = sessionRepository.save(new InterviewSession(memberId, oldQuestions.size()));
109+
110+
List<UUID> newQuestionIds = oldQuestions.stream()
111+
.map(q -> cloneQuestion(q, newSession).getId())
112+
.toList();
113+
114+
return new CreateSessionResponse(newSession.getId(), newQuestionIds);
115+
}
116+
117+
@Transactional
118+
public CreateSessionResponse createQuestion(UUID oldSessionId, UUID oldQuestionId, UUID memberId) {
119+
InterviewSession oldSession = sessionRepository.findById(oldSessionId)
120+
.orElseThrow(() -> new BusinessException(ErrorCode.SESSION_NOT_FOUND));
121+
122+
if (!oldSession.getMemberId().equals(memberId)) {
123+
throw new BusinessException(ErrorCode.FORBIDDEN);
124+
}
125+
if (oldSession.getStatus() == SessionStatus.IN_PROGRESS) {
126+
throw new BusinessException(ErrorCode.SESSION_NOT_FINISHED);
127+
}
128+
129+
InterviewQuestion oldQuestion = questionRepository.findByIdAndMemberIdAndInterviewSessionId(
130+
oldQuestionId,
131+
memberId,
132+
oldSessionId
133+
)
134+
.orElseThrow(() -> new BusinessException(ErrorCode.QUESTION_NOT_FOUND));
135+
136+
InterviewSession newSession = sessionRepository.save(new InterviewSession(memberId, 1));
137+
InterviewQuestion newQuestion = cloneQuestion(oldQuestion, newSession);
138+
139+
return new CreateSessionResponse(newSession.getId(), List.of(newQuestion.getId()));
140+
}
141+
142+
private InterviewQuestion cloneQuestion(InterviewQuestion source, InterviewSession newSession) {
143+
return questionRepository.save(new InterviewQuestion(
144+
source.getMemberId(),
145+
source.getQuestion(),
146+
source.getQuestionType(),
147+
source.getQuestionTag(),
148+
source.getJobPosting(),
149+
newSession
150+
));
151+
}
152+
94153
@Transactional
95154
public void saveCompletedSession(UUID sessionId, int finalScore, String finalFeedback) {
96155
InterviewSession session = sessionRepository.findById(sessionId)
97-
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 세션입니다."));
156+
.orElseThrow(() -> new BusinessException(ErrorCode.SESSION_NOT_FOUND));
98157

99158
if (session.getStatus() == SessionStatus.COMPLETED) {
100159
return;
@@ -106,7 +165,7 @@ public void saveCompletedSession(UUID sessionId, int finalScore, String finalFee
106165
@Transactional
107166
public void saveFailedSession(UUID sessionId) {
108167
InterviewSession session = sessionRepository.findById(sessionId)
109-
.orElseThrow(() -> new IllegalArgumentException("존재하지 않는 세션입니다."));
168+
.orElseThrow(() -> new BusinessException(ErrorCode.SESSION_NOT_FOUND));
110169

111170
if (session.getStatus() != SessionStatus.IN_PROGRESS) {
112171
return;
@@ -130,15 +189,15 @@ private Map<UUID, List<InterviewFeedback>> getFeedbackMap(UUID sessionId) {
130189
.collect(Collectors.groupingBy(f -> f.getInterviewAnswer().getId()));
131190
}
132191

133-
private InterviewFeedback findOptionalFeedback(List<InterviewFeedback> feedbacks, FeedbackType type) {
192+
private InterviewFeedback findFeedback(List<InterviewFeedback> feedbacks, FeedbackType type) {
134193
return feedbacks.stream()
135194
.filter(f -> f.getFeedbackType() == type)
136195
.findFirst()
137196
.orElse(null);
138197
}
139198

140199
private String getFeedbackText(List<InterviewFeedback> feedbacks, FeedbackType type) {
141-
InterviewFeedback feedback = findOptionalFeedback(feedbacks, type);
200+
InterviewFeedback feedback = findFeedback(feedbacks, type);
142201
return feedback == null ? null : feedback.getFeedback();
143202
}
144203
}

0 commit comments

Comments
 (0)