Skip to content

Commit ec41f02

Browse files
Merge branch 'dev' into feat/150
2 parents f65fa78 + 7174f8e commit ec41f02

25 files changed

+827
-128
lines changed

.github/workflows/test.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ jobs:
1111
PROJECT_DIR: backend
1212
KAKAO_CLIENT: ${{ secrets.KAKAO_CLIENT }}
1313
KAKAO_SECRET: ${{ secrets.KAKAO_SECRET }}
14+
REDIS_HOST: ${{ secrets.REDIS_HOST }}
15+
REDIS_PORT: ${{ secrets.REDIS_PORT }}
1416
steps:
1517
- uses: actions/checkout@v4
1618

backend/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ dependencies {
2929
implementation 'org.springframework.boot:spring-boot-starter-web'
3030
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
3131
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
32+
implementation 'org.redisson:redisson-spring-boot-starter:3.50.0'
3233
implementation 'org.springframework.boot:spring-boot-starter-websocket'
3334
implementation 'org.springframework.boot:spring-boot-starter-security'
3435
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'

backend/src/main/java/io/f1/backend/domain/game/app/GameService.java

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import io.f1.backend.global.exception.CustomException;
3636
import io.f1.backend.global.exception.errorcode.GameErrorCode;
3737
import io.f1.backend.global.exception.errorcode.RoomErrorCode;
38+
import io.f1.backend.global.lock.DistributedLock;
3839

3940
import lombok.RequiredArgsConstructor;
4041
import lombok.extern.slf4j.Slf4j;
@@ -64,6 +65,7 @@ public class GameService {
6465
private final RoomRepository roomRepository;
6566
private final ApplicationEventPublisher eventPublisher;
6667

68+
@DistributedLock(prefix = "room", key = "#roomId", waitTime = 0)
6769
public void gameStart(Long roomId, UserPrincipal principal) {
6870

6971
String destination = getDestination(roomId);
@@ -101,6 +103,7 @@ public void gameStart(Long roomId, UserPrincipal principal) {
101103
public void onCorrectAnswer(GameCorrectAnswerEvent event) {
102104

103105
Room room = event.room();
106+
log.debug(room.getId() + "번 방 채팅으로 정답! 현재 라운드 : " + room.getCurrentRound());
104107
String sessionId = event.sessionId();
105108
ChatMessage chatMessage = event.chatMessage();
106109
String answer = event.answer();
@@ -140,6 +143,8 @@ public void onCorrectAnswer(GameCorrectAnswerEvent event) {
140143
@EventListener
141144
public void onTimeout(GameTimeoutEvent event) {
142145
Room room = event.room();
146+
log.debug(room.getId() + "번 방 타임아웃! 현재 라운드 : " + room.getCurrentRound());
147+
143148
String destination = getDestination(room.getId());
144149

145150
messageSender.sendBroadcast(
@@ -231,6 +236,7 @@ private void updateRank(Room room, GameResultListResponse gameResultListResponse
231236
}
232237
}
233238

239+
@DistributedLock(prefix = "room", key = "#roomId")
234240
public void handlePlayerReady(Long roomId, String sessionId) {
235241

236242
Room room = findRoom(roomId);
@@ -241,9 +247,8 @@ public void handlePlayerReady(Long roomId, String sessionId) {
241247

242248
String destination = getDestination(roomId);
243249

244-
PlayerListResponse playerListResponse = toPlayerListResponse(room);
245-
log.info(playerListResponse.toString());
246-
messageSender.sendBroadcast(destination, MessageType.PLAYER_LIST, playerListResponse);
250+
messageSender.sendBroadcast(
251+
destination, MessageType.PLAYER_LIST, toPlayerListResponse(room));
247252
}
248253

249254
public void changeGameSetting(
@@ -276,7 +281,7 @@ private void validateRoomStart(Room room, UserPrincipal principal) {
276281
// 라운드 수만큼 랜덤 Question 추출
277282
private List<Question> prepareQuestions(Room room, Quiz quiz) {
278283
Long quizId = quiz.getId();
279-
Integer round = room.getGameSetting().getRound();
284+
Integer round = room.getRound();
280285
return quizService.getRandomQuestionsWithoutAnswer(quizId, round);
281286
}
282287

backend/src/main/java/io/f1/backend/domain/game/app/TimerService.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,20 +4,23 @@
44
import io.f1.backend.domain.game.model.Room;
55

66
import lombok.RequiredArgsConstructor;
7+
import lombok.extern.slf4j.Slf4j;
78

89
import org.springframework.context.ApplicationEventPublisher;
910
import org.springframework.stereotype.Service;
1011

1112
import java.util.concurrent.ScheduledFuture;
1213
import java.util.concurrent.TimeUnit;
1314

15+
@Slf4j
1416
@Service
1517
@RequiredArgsConstructor
1618
public class TimerService {
1719

1820
private final ApplicationEventPublisher eventPublisher;
1921

2022
public void startTimer(Room room, int delaySec) {
23+
log.debug(room.getId() + "번 방 타이머 시작 ! 현재 라운드 : " + room.getCurrentRound());
2124
cancelTimer(room);
2225

2326
ScheduledFuture<?> timer =
@@ -43,6 +46,7 @@ public boolean validateCurrentRound(Room room) {
4346

4447
public boolean cancelTimer(Room room) {
4548
// 정답 맞혔어요 ~ 타이머 캔슬 부탁
49+
log.debug(room.getId() + "번 방 타이머 취소 ! 현재 라운드 : " + room.getCurrentRound());
4650
ScheduledFuture<?> timer = room.getTimer();
4751
if (timer != null && !timer.isDone()) {
4852
return timer.cancel(false);

backend/src/main/java/io/f1/backend/domain/game/sse/app/SseService.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public class SseService {
1717
private final SseEmitterRepository emitterRepository;
1818

1919
public SseEmitter subscribe() {
20-
SseEmitter emitter = new SseEmitter(30_000L);
20+
SseEmitter emitter = new SseEmitter(1_800_000L);
2121
emitterRepository.save(emitter);
2222

2323
try {

backend/src/main/java/io/f1/backend/domain/question/api/QuestionController.java

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
package io.f1.backend.domain.question.api;
22

33
import io.f1.backend.domain.question.app.QuestionService;
4-
import io.f1.backend.domain.question.dto.QuestionUpdateRequest;
54

65
import lombok.RequiredArgsConstructor;
76

87
import org.springframework.http.ResponseEntity;
98
import org.springframework.web.bind.annotation.DeleteMapping;
109
import org.springframework.web.bind.annotation.PathVariable;
11-
import org.springframework.web.bind.annotation.PutMapping;
12-
import org.springframework.web.bind.annotation.RequestBody;
1310
import org.springframework.web.bind.annotation.RequestMapping;
1411
import org.springframework.web.bind.annotation.RestController;
1512

@@ -20,21 +17,6 @@ public class QuestionController {
2017

2118
private final QuestionService questionService;
2219

23-
@PutMapping("/{questionId}")
24-
public ResponseEntity<Void> updateQuestion(
25-
@PathVariable Long questionId, @RequestBody QuestionUpdateRequest request) {
26-
27-
if (request.content() != null) {
28-
questionService.updateQuestionContent(questionId, request.content());
29-
}
30-
31-
if (request.content() != null) {
32-
questionService.updateQuestionAnswer(questionId, request.answer());
33-
}
34-
35-
return ResponseEntity.noContent().build();
36-
}
37-
3820
@DeleteMapping("/{questionId}")
3921
public ResponseEntity<Void> deleteQuestion(@PathVariable Long questionId) {
4022
questionService.deleteQuestion(questionId);

backend/src/main/java/io/f1/backend/domain/question/app/QuestionService.java

Lines changed: 9 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,23 @@
22

33
import static io.f1.backend.domain.question.mapper.QuestionMapper.questionRequestToQuestion;
44
import static io.f1.backend.domain.question.mapper.TextQuestionMapper.questionRequestToTextQuestion;
5+
import static io.f1.backend.domain.quiz.app.QuizService.verifyUserAuthority;
56

67
import io.f1.backend.domain.question.dao.QuestionRepository;
78
import io.f1.backend.domain.question.dao.TextQuestionRepository;
89
import io.f1.backend.domain.question.dto.QuestionRequest;
10+
import io.f1.backend.domain.question.dto.QuestionUpdateRequest;
911
import io.f1.backend.domain.question.entity.Question;
1012
import io.f1.backend.domain.question.entity.TextQuestion;
1113
import io.f1.backend.domain.quiz.entity.Quiz;
1214
import io.f1.backend.global.exception.CustomException;
13-
import io.f1.backend.global.exception.errorcode.AuthErrorCode;
1415
import io.f1.backend.global.exception.errorcode.QuestionErrorCode;
15-
import io.f1.backend.global.security.enums.Role;
16-
import io.f1.backend.global.util.SecurityUtils;
1716

1817
import lombok.RequiredArgsConstructor;
1918

2019
import org.springframework.stereotype.Service;
2120
import org.springframework.transaction.annotation.Transactional;
2221

23-
import java.util.Objects;
24-
2522
@Service
2623
@RequiredArgsConstructor
2724
public class QuestionService {
@@ -41,46 +38,22 @@ public void saveQuestion(Quiz quiz, QuestionRequest request) {
4138
question.addTextQuestion(textQuestion);
4239
}
4340

44-
@Transactional
45-
public void updateQuestionContent(Long questionId, String content) {
46-
47-
validateContent(content);
41+
public void updateQuestions(Quiz quiz, QuestionUpdateRequest request) {
4842

49-
Question question =
50-
questionRepository
51-
.findById(questionId)
52-
.orElseThrow(
53-
() -> new CustomException(QuestionErrorCode.QUESTION_NOT_FOUND));
54-
55-
verifyUserAuthority(question.getQuiz());
56-
57-
TextQuestion textQuestion = question.getTextQuestion();
58-
textQuestion.changeContent(content);
59-
}
60-
61-
private static void verifyUserAuthority(Quiz quiz) {
62-
if (SecurityUtils.getCurrentUserRole() == Role.ADMIN) {
43+
if (request.getId() == null) {
44+
saveQuestion(quiz, QuestionRequest.of(request));
6345
return;
6446
}
65-
if (!Objects.equals(SecurityUtils.getCurrentUserId(), quiz.getCreator().getId())) {
66-
throw new CustomException(AuthErrorCode.FORBIDDEN);
67-
}
68-
}
69-
70-
@Transactional
71-
public void updateQuestionAnswer(Long questionId, String answer) {
72-
73-
validateAnswer(answer);
7447

7548
Question question =
7649
questionRepository
77-
.findById(questionId)
50+
.findById(request.getId())
7851
.orElseThrow(
7952
() -> new CustomException(QuestionErrorCode.QUESTION_NOT_FOUND));
8053

81-
verifyUserAuthority(question.getQuiz());
82-
83-
question.changeAnswer(answer);
54+
TextQuestion textQuestion = question.getTextQuestion();
55+
textQuestion.changeContent(request.getContent());
56+
question.changeAnswer(request.getAnswer());
8457
}
8558

8659
@Transactional
@@ -96,16 +69,4 @@ public void deleteQuestion(Long questionId) {
9669

9770
questionRepository.delete(question);
9871
}
99-
100-
private void validateAnswer(String answer) {
101-
if (answer.trim().length() < 5 || answer.trim().length() > 30) {
102-
throw new CustomException(QuestionErrorCode.INVALID_ANSWER_LENGTH);
103-
}
104-
}
105-
106-
private void validateContent(String content) {
107-
if (content.trim().length() < 5 || content.trim().length() > 30) {
108-
throw new CustomException(QuestionErrorCode.INVALID_CONTENT_LENGTH);
109-
}
110-
}
11172
}

backend/src/main/java/io/f1/backend/domain/question/dto/QuestionRequest.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,12 @@ public class QuestionRequest {
1919
@TrimmedSize(min = 1, max = 30)
2020
@NotBlank(message = "정답을 입력해주세요.")
2121
private String answer;
22+
23+
public static QuestionRequest of(QuestionUpdateRequest request) {
24+
QuestionRequest questionRequest = new QuestionRequest();
25+
questionRequest.content = request.getContent();
26+
questionRequest.answer = request.getAnswer();
27+
28+
return questionRequest;
29+
}
2230
}
Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
11
package io.f1.backend.domain.question.dto;
22

3-
public record QuestionUpdateRequest(String content, String answer) {}
3+
import io.f1.backend.global.validation.TrimmedSize;
4+
5+
import jakarta.validation.constraints.NotBlank;
6+
7+
import lombok.AccessLevel;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
11+
@Getter
12+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
13+
public class QuestionUpdateRequest {
14+
15+
private Long id;
16+
17+
@TrimmedSize(min = 5, max = 30)
18+
@NotBlank(message = "문제를 입력해주세요.")
19+
private String content;
20+
21+
@TrimmedSize(min = 1, max = 30)
22+
@NotBlank(message = "정답을 입력해주세요.")
23+
private String answer;
24+
}

backend/src/main/java/io/f1/backend/domain/quiz/api/QuizController.java

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -57,15 +57,9 @@ public ResponseEntity<Void> deleteQuiz(@PathVariable Long quizId) {
5757
public ResponseEntity<Void> updateQuiz(
5858
@PathVariable Long quizId,
5959
@RequestPart(required = false) MultipartFile thumbnailFile,
60-
@RequestPart QuizUpdateRequest request) {
60+
@Valid @RequestPart QuizUpdateRequest request) {
6161

62-
if (request.title() != null) {
63-
quizService.updateQuizTitle(quizId, request.title());
64-
}
65-
66-
if (request.description() != null) {
67-
quizService.updateQuizDesc(quizId, request.description());
68-
}
62+
quizService.updateQuizAndQuestions(quizId, request);
6963

7064
if (thumbnailFile != null && !thumbnailFile.isEmpty()) {
7165
quizService.updateThumbnail(quizId, thumbnailFile);

0 commit comments

Comments
 (0)