From ee3caffa042f46466fea10b3642d7468b374264b Mon Sep 17 00:00:00 2001 From: coli Date: Thu, 4 Sep 2025 21:24:04 +0900 Subject: [PATCH 1/3] =?UTF-8?q?refactor:=20=EC=A7=88=EB=AC=B8=20=ED=95=84?= =?UTF-8?q?=EB=93=9C=EB=A5=BC=20VARCHAR=20>=20TEXT=EB=A1=9C=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/resources/application-mcp-client.yml | 1 + .../devoops/jpa/entity/github/answer/AnswerRankingEntity.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/gss-client/gss-mcp-client/src/main/resources/application-mcp-client.yml b/gss-client/gss-mcp-client/src/main/resources/application-mcp-client.yml index 5fac40d..6bccf5c 100644 --- a/gss-client/gss-mcp-client/src/main/resources/application-mcp-client.yml +++ b/gss-client/gss-mcp-client/src/main/resources/application-mcp-client.yml @@ -8,6 +8,7 @@ dev-oops: - "summaryDetail"은 변경 내용을 항목별로 요약한 제목(title) + 설명(description) 쌍으로 구성해. - "category"는 기술적인 관점에서 PR 코드 변경 내용을 반영하여 선택해 (예: 성능, 보안, 확장성, 유지보수성, 테스트 등) - "question"은 각 category에 대해 기술 면접에서 사용할 수 있는 질문이어야 해. + - "question"은 가능한 255자가 넘지 않도록 짧고 명료하게 질문해. - 각 질문들은 반드시 PR 코드 변경 내용("diff")을 인용해서 생성해. - "diff"를 굉장히 자세하게 분석하고 몇몇 질문에는 코드를 반영해서 만들어줘 - 질문 수는 카테고리마다 3개 이상 만들어. diff --git a/gss-domain/src/main/java/com/devoops/jpa/entity/github/answer/AnswerRankingEntity.java b/gss-domain/src/main/java/com/devoops/jpa/entity/github/answer/AnswerRankingEntity.java index c16b88b..3c94c01 100644 --- a/gss-domain/src/main/java/com/devoops/jpa/entity/github/answer/AnswerRankingEntity.java +++ b/gss-domain/src/main/java/com/devoops/jpa/entity/github/answer/AnswerRankingEntity.java @@ -26,7 +26,7 @@ public class AnswerRankingEntity { private long questionId; @NotBlank - @Column(name = "content") + @Column(name = "content", columnDefinition = "TEXT") private String questionContent; @Column(name = "pull_request_id") From 30b30c2f36ec9dce0863a81785545127666c2285 Mon Sep 17 00:00:00 2001 From: coli Date: Thu, 4 Sep 2025 21:29:24 +0900 Subject: [PATCH 2/3] =?UTF-8?q?refactor:=20=EB=8B=B5=EB=B3=80=EB=82=B4?= =?UTF-8?q?=EC=9A=A9=EC=9D=B4=20=EB=B9=84=EC=96=B4=EC=9E=88=EC=9C=BC?= =?UTF-8?q?=EB=A9=B4=20=EB=9E=AD=ED=82=B9=EC=97=90=20=EB=B0=98=EC=98=81?= =?UTF-8?q?=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../answerranking/AnswerRankingServiceTest.java | 17 +++++++++++++++++ .../domain/entity/github/answer/Answer.java | 5 +++++ .../answerranking/AnswerRankingService.java | 3 +++ 3 files changed, 25 insertions(+) diff --git a/gss-api-app/src/test/java/com/devoops/service/answerranking/AnswerRankingServiceTest.java b/gss-api-app/src/test/java/com/devoops/service/answerranking/AnswerRankingServiceTest.java index 4ce86bc..0f943c5 100644 --- a/gss-api-app/src/test/java/com/devoops/service/answerranking/AnswerRankingServiceTest.java +++ b/gss-api-app/src/test/java/com/devoops/service/answerranking/AnswerRankingServiceTest.java @@ -18,6 +18,8 @@ import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; import org.springframework.beans.factory.annotation.Autowired; class AnswerRankingServiceTest extends BaseServiceTest { @@ -47,6 +49,21 @@ class Push { assertThat(userRanking.getRankings()).hasSize(1); } + @Test + void 답변내용이_비어있으면_랭킹에_반영하지_않는다() { + User user = userGenerator.generate("김건우"); + GithubRepository repo = repoGenerator.generate(user, "건우의 레포"); + PullRequest pullRequest = pullRequestGenerator.generate("최초 PR", RecordStatus.PENDING, ProcessingStatus.DONE, + repo, LocalDateTime.now()); + Question question1 = questionGenerator.generate(pullRequest, "질문1"); + Answer answer = answerGenerator.generate(question1, ""); + + answerRankingService.push(answer, user.getId()); + + AnswerRankings userRanking = answerRankingDomainRepository.findAllByUserId(user.getId()); + assertThat(userRanking.getRankings()).isEmpty(); + } + @Test void 유저의_PR_랭킹을_모두_가져온다() { User user = userGenerator.generate("김건우"); diff --git a/gss-domain/src/main/java/com/devoops/domain/entity/github/answer/Answer.java b/gss-domain/src/main/java/com/devoops/domain/entity/github/answer/Answer.java index e4695ca..8c75543 100644 --- a/gss-domain/src/main/java/com/devoops/domain/entity/github/answer/Answer.java +++ b/gss-domain/src/main/java/com/devoops/domain/entity/github/answer/Answer.java @@ -1,5 +1,6 @@ package com.devoops.domain.entity.github.answer; +import io.micrometer.common.util.StringUtils; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -16,4 +17,8 @@ public class Answer { public static Answer initialize(long questionId) { return new Answer(null, questionId, INITIALIZED_ANSWER_CONTENT); } + + public boolean isBlank() { + return StringUtils.isBlank(content); + } } diff --git a/gss-domain/src/main/java/com/devoops/service/answerranking/AnswerRankingService.java b/gss-domain/src/main/java/com/devoops/service/answerranking/AnswerRankingService.java index 1b1b2b7..06b6bae 100644 --- a/gss-domain/src/main/java/com/devoops/service/answerranking/AnswerRankingService.java +++ b/gss-domain/src/main/java/com/devoops/service/answerranking/AnswerRankingService.java @@ -22,6 +22,9 @@ public AnswerRankings findUserRanking(long userId) { } public void push(Answer answer, long userId) { + if(answer.isBlank()) { + return; + } AnswerRankings answerRankings = findUserRanking(userId); Question question = questionDomainRepository.findById(answer.getQuestionId()); From ad72ca2f7d6b9c0d90b8077214c2af700eb6c601 Mon Sep 17 00:00:00 2001 From: coli Date: Thu, 4 Sep 2025 21:50:08 +0900 Subject: [PATCH 3/3] =?UTF-8?q?refactor:=20applicationEvent=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/devoops/event/AnalyzePrEvent.java | 17 ---------- .../com/devoops/event/UpdateAnswerEvent.java | 18 ++++++++++ .../event/listener/AnswerEventListener.java | 25 ++++++++++++++ .../event/publisher/PrAnalysisPublisher.java | 13 +------- .../service/facade/QuestionFacadeService.java | 9 +++-- .../facade/QuestionFacadeServiceTest.java | 33 +++++++++++++++++++ .../answer/AnswerRankingDomainRepository.java | 2 -- .../AnswerRankingDomainRepositoryImpl.java | 10 ------ 8 files changed, 84 insertions(+), 43 deletions(-) delete mode 100644 gss-api-app/src/main/java/com/devoops/event/AnalyzePrEvent.java create mode 100644 gss-api-app/src/main/java/com/devoops/event/UpdateAnswerEvent.java create mode 100644 gss-api-app/src/main/java/com/devoops/event/listener/AnswerEventListener.java diff --git a/gss-api-app/src/main/java/com/devoops/event/AnalyzePrEvent.java b/gss-api-app/src/main/java/com/devoops/event/AnalyzePrEvent.java deleted file mode 100644 index dbebb82..0000000 --- a/gss-api-app/src/main/java/com/devoops/event/AnalyzePrEvent.java +++ /dev/null @@ -1,17 +0,0 @@ -package com.devoops.event; - -import java.time.LocalDateTime; - -public record AnalyzePrEvent( - Boolean isMerged, - long pullRequestId, - String diffUrl, - String title, - String description, - String label, - long repositoryId, - long userId, - LocalDateTime mergedAt -) { - -} diff --git a/gss-api-app/src/main/java/com/devoops/event/UpdateAnswerEvent.java b/gss-api-app/src/main/java/com/devoops/event/UpdateAnswerEvent.java new file mode 100644 index 0000000..56dd095 --- /dev/null +++ b/gss-api-app/src/main/java/com/devoops/event/UpdateAnswerEvent.java @@ -0,0 +1,18 @@ +package com.devoops.event; + +import com.devoops.domain.entity.github.answer.Answer; +import lombok.Getter; +import org.springframework.context.ApplicationEvent; + +@Getter +public class UpdateAnswerEvent extends ApplicationEvent { + + private final Answer answer; + private final long userId; + + public UpdateAnswerEvent(Object source, Answer answer, long userId) { + super(source); + this.answer = answer; + this.userId = userId; + } +} diff --git a/gss-api-app/src/main/java/com/devoops/event/listener/AnswerEventListener.java b/gss-api-app/src/main/java/com/devoops/event/listener/AnswerEventListener.java new file mode 100644 index 0000000..ce50931 --- /dev/null +++ b/gss-api-app/src/main/java/com/devoops/event/listener/AnswerEventListener.java @@ -0,0 +1,25 @@ +package com.devoops.event.listener; + +import com.devoops.domain.entity.github.answer.Answer; +import com.devoops.event.UpdateAnswerEvent; +import com.devoops.service.answerranking.AnswerRankingService; +import lombok.RequiredArgsConstructor; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import org.springframework.transaction.event.TransactionPhase; +import org.springframework.transaction.event.TransactionalEventListener; + +@Component +@RequiredArgsConstructor +public class AnswerEventListener { + + private final AnswerRankingService answerRankingService; + + @Async + @TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT) + public void updateAnswer(UpdateAnswerEvent updateAnswerEvent) { + Answer answer = updateAnswerEvent.getAnswer(); + long userId = updateAnswerEvent.getUserId(); + answerRankingService.push(answer, userId); + } +} diff --git a/gss-api-app/src/main/java/com/devoops/event/publisher/PrAnalysisPublisher.java b/gss-api-app/src/main/java/com/devoops/event/publisher/PrAnalysisPublisher.java index 336e964..3146d20 100644 --- a/gss-api-app/src/main/java/com/devoops/event/publisher/PrAnalysisPublisher.java +++ b/gss-api-app/src/main/java/com/devoops/event/publisher/PrAnalysisPublisher.java @@ -2,11 +2,6 @@ import com.devoops.dto.AppWebhookEventRequest; -import com.devoops.exception.custom.GssException; -import com.devoops.exception.errorcode.ErrorCode; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; -import jakarta.annotation.PostConstruct; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; @@ -19,15 +14,9 @@ public class PrAnalysisPublisher { private final RedisTemplate redisTemplate; private final ChannelTopic channelTopic; - private final ObjectMapper objectMapper; public void publish(List eventList) { -// try { -// String message = objectMapper.writeValueAsString(eventList); - redisTemplate.convertAndSend(channelTopic.getTopic(), eventList); -// } catch (JsonProcessingException e) { -// throw new GssException(ErrorCode.REDIS_PUBLISH_ERROR); -// } + redisTemplate.convertAndSend(channelTopic.getTopic(), eventList); } } diff --git a/gss-api-app/src/main/java/com/devoops/service/facade/QuestionFacadeService.java b/gss-api-app/src/main/java/com/devoops/service/facade/QuestionFacadeService.java index 74473e8..85b7c30 100644 --- a/gss-api-app/src/main/java/com/devoops/service/facade/QuestionFacadeService.java +++ b/gss-api-app/src/main/java/com/devoops/service/facade/QuestionFacadeService.java @@ -7,13 +7,16 @@ import com.devoops.domain.entity.github.pr.RecordStatus; import com.devoops.domain.entity.user.User; import com.devoops.dto.request.AnswerPutRequests; +import com.devoops.event.UpdateAnswerEvent; import com.devoops.service.answer.AnswerService; import com.devoops.service.answerranking.AnswerRankingService; import com.devoops.service.pullrequest.PullRequestService; import com.devoops.service.question.QuestionService; import java.util.List; import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; @Service @RequiredArgsConstructor @@ -22,7 +25,7 @@ public class QuestionFacadeService { private final PullRequestService pullRequestService; private final QuestionService questionService; private final AnswerService answerService; - private final AnswerRankingService answerRankingService; + private final ApplicationEventPublisher eventPublisher; public Answer initializeAnswer(long questionId, User user) { PullRequest pullRequest = pullRequestService.findByQuestionId(questionId); @@ -32,12 +35,14 @@ public Answer initializeAnswer(long questionId, User user) { return questionService.initializeAnswer(questionId, user); } + @Transactional public Answer updateAnswer(long answerId, String updateContent, long userId) { Answer answer = questionService.updateAnswer(answerId, updateContent); - answerRankingService.push(answer, userId); + eventPublisher.publishEvent(new UpdateAnswerEvent(this, answer, userId)); return answer; } + @Transactional public Answers updateAllAnswers(AnswerPutRequests updateRequests) { List updateCommands = updateRequests.answers() .stream() diff --git a/gss-api-app/src/test/java/com/devoops/service/facade/QuestionFacadeServiceTest.java b/gss-api-app/src/test/java/com/devoops/service/facade/QuestionFacadeServiceTest.java index 4f41c1f..9baa776 100644 --- a/gss-api-app/src/test/java/com/devoops/service/facade/QuestionFacadeServiceTest.java +++ b/gss-api-app/src/test/java/com/devoops/service/facade/QuestionFacadeServiceTest.java @@ -14,11 +14,15 @@ import com.devoops.domain.repository.github.pr.PullRequestDomainRepository; import com.devoops.dto.request.AnswerPutRequest; import com.devoops.dto.request.AnswerPutRequests; +import com.devoops.event.UpdateAnswerEvent; import java.time.LocalDateTime; import java.util.List; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.event.ApplicationEvents; +import org.springframework.test.context.event.RecordApplicationEvents; class QuestionFacadeServiceTest extends BaseServiceTest { @@ -113,4 +117,33 @@ class UpdateAllAnswers { ); } } + + @Nested + @RecordApplicationEvents + class UpdateAnswers { + + @Autowired + private ApplicationEvents applicationEvents; + + @BeforeEach + void setUp() { + applicationEvents.clear(); + } + + @Test + void 회고를_업데이트_시_랭킹_갱신_이벤트를_발행한다() { + User user = userGenerator.generate("김건우"); + GithubRepository repo = repoGenerator.generate(user, "건우의 레포"); + PullRequest pr1 = pullRequestGenerator.generate("PR1", RecordStatus.PENDING, ProcessingStatus.DONE, repo, LocalDateTime.now()); + Question question1 = questionGenerator.generate(pr1, "질문1"); + Answer answer1 = answerGenerator.generate(question1, "answer1"); + + questionFacadeService.updateAnswer(answer1.getId(), "updateContent", user.getId()); + + assertThat(applicationEvents.stream(UpdateAnswerEvent.class)) + .hasSize(1) + .allMatch(event -> event.getAnswer().getId().equals(answer1.getId())) + .allMatch(event -> event.getUserId() == user.getId()); + } + } } diff --git a/gss-domain/src/main/java/com/devoops/domain/repository/github/answer/AnswerRankingDomainRepository.java b/gss-domain/src/main/java/com/devoops/domain/repository/github/answer/AnswerRankingDomainRepository.java index d25296d..dbb41c4 100644 --- a/gss-domain/src/main/java/com/devoops/domain/repository/github/answer/AnswerRankingDomainRepository.java +++ b/gss-domain/src/main/java/com/devoops/domain/repository/github/answer/AnswerRankingDomainRepository.java @@ -14,6 +14,4 @@ public interface AnswerRankingDomainRepository { AnswerRanking update(long pullRequestId, long questionId); void deleteById(long id); - - void deleteAllInPullRequests(PullRequests pullRequests); } diff --git a/gss-domain/src/main/java/com/devoops/jpa/repository/github/answer/AnswerRankingDomainRepositoryImpl.java b/gss-domain/src/main/java/com/devoops/jpa/repository/github/answer/AnswerRankingDomainRepositoryImpl.java index 55679d4..5e0e00f 100644 --- a/gss-domain/src/main/java/com/devoops/jpa/repository/github/answer/AnswerRankingDomainRepositoryImpl.java +++ b/gss-domain/src/main/java/com/devoops/jpa/repository/github/answer/AnswerRankingDomainRepositoryImpl.java @@ -74,16 +74,6 @@ public void deleteById(long id) { answerRankingJpaRepository.deleteById(id); } - @Override - @Transactional - public void deleteAllInPullRequests(PullRequests pullRequests) { - List pullRequestIds = pullRequests.getValues() - .stream() - .map(PullRequest::getId) - .toList(); - answerRankingJpaRepository.deleteByPullRequestIdIn(pullRequestIds); - } - private QuestionEntity findQuestionById(long questionId) { return questionJpaRepository.findById(questionId) .orElseThrow(() -> new GssException(ErrorCode.QUESTION_NOT_FOUND));