diff --git a/src/main/java/hello/cluebackend/application/quizbattle/dto/RevealAnswerResponse.java b/src/main/java/hello/cluebackend/application/quizbattle/dto/RevealAnswerResponse.java new file mode 100644 index 0000000..c4a0bcf --- /dev/null +++ b/src/main/java/hello/cluebackend/application/quizbattle/dto/RevealAnswerResponse.java @@ -0,0 +1,15 @@ +package hello.cluebackend.application.quizbattle.dto; + +import lombok.Builder; +import lombok.Getter; + +import java.util.Map; + +@Getter +@Builder +public class RevealAnswerResponse { + private final int correctAnswer; + private final String explanation; + private final Map statistics; + private final int totalAnswers; +} \ No newline at end of file diff --git a/src/main/java/hello/cluebackend/application/quizbattle/dto/SubmitAnswerResponse.java b/src/main/java/hello/cluebackend/application/quizbattle/dto/SubmitAnswerResponse.java new file mode 100644 index 0000000..23eb2fe --- /dev/null +++ b/src/main/java/hello/cluebackend/application/quizbattle/dto/SubmitAnswerResponse.java @@ -0,0 +1,12 @@ +package hello.cluebackend.application.quizbattle.dto; + +import hello.cluebackend.domain.quizbattle.model.QuizAnswer; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class SubmitAnswerResponse { + private final QuizAnswer answer; + private final int totalAnswers; +} diff --git a/src/main/java/hello/cluebackend/domain/quizbattle/model/QuizParticipant.java b/src/main/java/hello/cluebackend/domain/quizbattle/model/QuizParticipant.java index d1682cc..aa9879f 100644 --- a/src/main/java/hello/cluebackend/domain/quizbattle/model/QuizParticipant.java +++ b/src/main/java/hello/cluebackend/domain/quizbattle/model/QuizParticipant.java @@ -20,6 +20,7 @@ public class QuizParticipant implements Serializable { private Integer correctAnswers; private Boolean isReady; private Long joinedAt; + private String profileImage; public void addScore(int points) { this.score = (this.score != null ? this.score : 0) + points; diff --git a/src/main/java/hello/cluebackend/domain/quizbattle/service/QuizBattleService.java b/src/main/java/hello/cluebackend/domain/quizbattle/service/QuizBattleService.java index 6ceda82..8fc8cba 100644 --- a/src/main/java/hello/cluebackend/domain/quizbattle/service/QuizBattleService.java +++ b/src/main/java/hello/cluebackend/domain/quizbattle/service/QuizBattleService.java @@ -1,6 +1,8 @@ package hello.cluebackend.domain.quizbattle.service; import hello.cluebackend.application.agent.dto.response.AgentResponse; +import hello.cluebackend.application.quizbattle.dto.SubmitAnswerResponse; +import hello.cluebackend.application.quizbattle.dto.RevealAnswerResponse; import hello.cluebackend.application.quizbattle.dto.QuizGenerationRequest; import hello.cluebackend.application.quizbattle.dto.QuizGenerationResponse; import hello.cluebackend.domain.classroom.model.ClassRoom; @@ -93,7 +95,7 @@ public QuizRoom createRoom( return savedRoom; } - public QuizParticipant joinRoom(String roomCode, UUID userId, String sessionId) { + public QuizParticipant joinRoom(String roomCode, UUID userId, String sessionId, String profileImage) { QuizRoom room = quizRoomRepository.findByRoomCode(roomCode) .orElseThrow(() -> new IllegalArgumentException("Room not found: " + roomCode)); @@ -117,6 +119,7 @@ public QuizParticipant joinRoom(String roomCode, UUID userId, String sessionId) .correctAnswers(0) .isReady(false) .joinedAt(System.currentTimeMillis()) + .profileImage(profileImage) .build(); redisService.addParticipant(roomCode, participant); @@ -205,7 +208,7 @@ private List generateQuestions(int count, UUID documentId, int tim } } - public QuizAnswer submitAnswer( + public SubmitAnswerResponse submitAnswer( String roomCode, UUID userId, int questionNumber, int answerIndex, long submittedAt, int timeSpent) { QuizRoom room = quizRoomRepository.findByRoomCode(roomCode) @@ -263,7 +266,13 @@ public QuizAnswer submitAnswer( log.info("User {} submitted answer for question {} in room {}, correct: {}, points: {}", userId, questionNumber, roomCode, isCorrect, answer.getPoints()); - return answer; + Map statistics = redisService.getAnswerStatistics(roomCode, questionNumber); + int totalAnswers = statistics.values().stream().mapToInt(Integer::intValue).sum(); + + return SubmitAnswerResponse.builder() + .answer(answer) + .totalAnswers(totalAnswers) + .build(); } public List getRankings(String roomCode) { @@ -378,7 +387,7 @@ public void setQuestionActive(String roomCode, int questionNumber) { log.info("Set question {} to ACTIVE in room {}", questionNumber, roomCode); } - public Map revealAnswer(String roomCode, int questionNumber) { + public RevealAnswerResponse revealAnswer(String roomCode, int questionNumber) { QuizQuestion question = redisService.getQuestion(roomCode, questionNumber); if (question == null) { throw new IllegalArgumentException("Question not found: " + questionNumber); @@ -394,15 +403,14 @@ public Map revealAnswer(String roomCode, int questionNumber) { Map statistics = redisService.getAnswerStatistics(roomCode, questionNumber); int totalAnswers = statistics.values().stream().mapToInt(Integer::intValue).sum(); - Map result = new HashMap<>(); - result.put("correctAnswer", question.getCorrectAnswer()); - result.put("explanation", question.getExplanation()); - result.put("statistics", statistics); - result.put("totalAnswers", totalAnswers); - log.info("Revealed answer for question {} in room {}", questionNumber, roomCode); - return result; + return RevealAnswerResponse.builder() + .correctAnswer(question.getCorrectAnswer()) + .explanation(question.getExplanation()) + .statistics(statistics) + .totalAnswers(totalAnswers) + .build(); } public void nextQuestion(String roomCode) { diff --git a/src/main/java/hello/cluebackend/presentation/websocket/quizbattle/QuizBattleWebSocketController.java b/src/main/java/hello/cluebackend/presentation/websocket/quizbattle/QuizBattleWebSocketController.java index 25a1539..e027dcd 100644 --- a/src/main/java/hello/cluebackend/presentation/websocket/quizbattle/QuizBattleWebSocketController.java +++ b/src/main/java/hello/cluebackend/presentation/websocket/quizbattle/QuizBattleWebSocketController.java @@ -2,7 +2,9 @@ import hello.cluebackend.common.utils.JWTUtil; import hello.cluebackend.domain.quizbattle.model.*; +import hello.cluebackend.application.quizbattle.dto.SubmitAnswerResponse; import hello.cluebackend.domain.quizbattle.service.QuizBattleService; +import hello.cluebackend.application.quizbattle.dto.RevealAnswerResponse; import hello.cluebackend.domain.quizbattle.service.QuizTimerService; import hello.cluebackend.presentation.websocket.quizbattle.dto.*; import lombok.RequiredArgsConstructor; @@ -122,13 +124,14 @@ public RoomCreatedMessage createRoom( @MessageMapping("/quiz/join/{roomCode}") public void joinRoom( @DestinationVariable String roomCode, + @Payload JoinRoomRequest request, SimpMessageHeaderAccessor headerAccessor ) { try { UUID userId = getUserIdFromHeader(headerAccessor); String sessionId = headerAccessor.getSessionId(); - QuizParticipant participant = quizBattleService.joinRoom(roomCode, userId, sessionId); + QuizParticipant participant = quizBattleService.joinRoom(roomCode, userId, sessionId, request.getProfileImage()); List allParticipants = quizBattleService.getParticipants(roomCode); ParticipantJoinedMessage message = ParticipantJoinedMessage.builder() @@ -225,7 +228,7 @@ public void submitAnswer( ) { try { UUID userId = getUserIdFromHeader(headerAccessor); - QuizAnswer answer = quizBattleService.submitAnswer( + SubmitAnswerResponse resultData = quizBattleService.submitAnswer( roomCode, userId, request.getQuestionNumber(), @@ -234,6 +237,9 @@ public void submitAnswer( request.getTimeSpent() ); + QuizAnswer answer = resultData.getAnswer(); + int totalAnswers = resultData.getTotalAnswers(); + AnswerResultMessage result = AnswerResultMessage.builder() .questionNumber(answer.getQuestionNumber()) .isCorrect(answer.getIsCorrect()) @@ -247,8 +253,19 @@ public void submitAnswer( result ); - log.info("User {} submitted answer for question {} in room {}", - userId, request.getQuestionNumber(), roomCode); + // Notify everyone in the room about the new answer count + int totalParticipants = quizBattleService.getParticipants(roomCode).size(); + AnswerCountMessage countMessage = AnswerCountMessage.builder() + .status("ANSWER_SUBMITTED") + .message("An answer was submitted.") + .questionNumber(request.getQuestionNumber()) + .totalAnswers(totalAnswers) + .totalParticipants(totalParticipants) + .build(); + messagingTemplate.convertAndSend("/topic/quiz/" + roomCode + "/game", countMessage); + + log.info("User {} submitted answer for question {} in room {}, total answers now {}", + userId, request.getQuestionNumber(), roomCode, totalAnswers); } catch (Exception e) { log.error("Error submitting answer", e); @@ -284,14 +301,14 @@ public void revealAnswer( quizTimerService.cancelQuestionTimer(roomCode, currentQuestionNum); - Map result = quizBattleService.revealAnswer(roomCode, currentQuestionNum); + RevealAnswerResponse result = quizBattleService.revealAnswer(roomCode, currentQuestionNum); AnswerRevealMessage message = AnswerRevealMessage.builder() .questionNumber(currentQuestionNum) - .correctAnswer((Integer) result.get("correctAnswer")) - .explanation((String) result.get("explanation")) - .statistics((Map) result.get("statistics")) - .totalAnswers((Integer) result.get("totalAnswers")) + .correctAnswer(result.getCorrectAnswer()) + .explanation(result.getExplanation()) + .statistics(result.getStatistics()) + .totalAnswers(result.getTotalAnswers()) .status("success") .message("Answer revealed") .build(); @@ -478,14 +495,14 @@ private void sendQuestionToRoom(String roomCode, QuizQuestion question) { // 시간이 끝나면 정답만 공개하고, 다음 문제로는 넘어가지 않음 Integer currentQuestionNum = quizBattleService.getCurrentQuestionNumber(roomCode); if (currentQuestionNum != null) { - Map result = quizBattleService.revealAnswer(roomCode, currentQuestionNum); + RevealAnswerResponse result = quizBattleService.revealAnswer(roomCode, currentQuestionNum); AnswerRevealMessage message = AnswerRevealMessage.builder() .questionNumber(currentQuestionNum) - .correctAnswer((Integer) result.get("correctAnswer")) - .explanation((String) result.get("explanation")) - .statistics((Map) result.get("statistics")) - .totalAnswers((Integer) result.get("totalAnswers")) + .correctAnswer(result.getCorrectAnswer()) + .explanation(result.getExplanation()) + .statistics(result.getStatistics()) + .totalAnswers(result.getTotalAnswers()) .status("success") .message("Time's up! Answer revealed") .build(); diff --git a/src/main/java/hello/cluebackend/presentation/websocket/quizbattle/dto/AnswerCountMessage.java b/src/main/java/hello/cluebackend/presentation/websocket/quizbattle/dto/AnswerCountMessage.java new file mode 100644 index 0000000..34c37b0 --- /dev/null +++ b/src/main/java/hello/cluebackend/presentation/websocket/quizbattle/dto/AnswerCountMessage.java @@ -0,0 +1,14 @@ +package hello.cluebackend.presentation.websocket.quizbattle.dto; + +import lombok.Builder; +import lombok.Data; + +@Data +@Builder +public class AnswerCountMessage { + private String status; + private String message; + private int questionNumber; + private int totalAnswers; + private int totalParticipants; +} diff --git a/src/main/java/hello/cluebackend/presentation/websocket/quizbattle/dto/JoinRoomRequest.java b/src/main/java/hello/cluebackend/presentation/websocket/quizbattle/dto/JoinRoomRequest.java new file mode 100644 index 0000000..ec6c179 --- /dev/null +++ b/src/main/java/hello/cluebackend/presentation/websocket/quizbattle/dto/JoinRoomRequest.java @@ -0,0 +1,8 @@ +package hello.cluebackend.presentation.websocket.quizbattle.dto; + +import lombok.Data; + +@Data +public class JoinRoomRequest { + private String profileImage; +}