From 14c70804d275c278a57648bec18fe491ed5fa4bc Mon Sep 17 00:00:00 2001 From: KoSeonJe <127813439+KoSeonJe@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:36:19 +0900 Subject: [PATCH 1/9] =?UTF-8?q?refactor:=20[DDING-000]=20=EC=9D=B4?= =?UTF-8?q?=EB=8B=AC=EC=9D=98=20=ED=98=84=ED=99=A9=20API=20=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=20=ED=95=84=EB=93=9C=EB=A5=BC=20=EA=B0=80=EC=A4=91?= =?UTF-8?q?=EC=B9=98=20=EC=A0=90=EC=88=98=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EC=A0=80=EB=B2=88=20=EB=8B=AC=20=EC=88=9C?= =?UTF-8?q?=EC=9C=84=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 응답 필드를 개수(Count)에서 가중치 점수(Score)로 변경 (feedScore/viewScore/likeScore/commentScore/totalScore) - 저번 달 순위(lastMonthRank) 필드 추가 - 1월 조회 시 전년도 12월 순위를 조회하도록 처리 Co-Authored-By: Claude Opus 4.6 --- .../domain/feed/api/ClubFeedApi.java | 2 +- .../response/ClubMonthlyStatusResponse.java | 22 +++++---- .../service/GeneralFeedRankingService.java | 16 ++++++- .../dto/query/ClubMonthlyStatusQuery.java | 46 ++++++++++++------- 4 files changed, 58 insertions(+), 28 deletions(-) diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java index ca60bc25..a800185f 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java @@ -72,7 +72,7 @@ MyFeedPageResponse getMyFeedPage( ); @Operation(summary = "동아리 이달의 현황 조회 API") - @ApiResponse(responseCode = "200", description = "동아리 이달의 현황 조회 성공", + @ApiResponse(responseCode = "200", description = "동아리 이달의 현황 조회 성공 - 가중치 점수(feedScore/viewScore/likeScore/commentScore/totalScore)와 저번 달 순위(lastMonthRank) 반환", content = @Content(schema = @Schema(implementation = ClubMonthlyStatusResponse.class))) @ResponseStatus(HttpStatus.OK) @SecurityRequirement(name = "AccessToken") diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/ClubMonthlyStatusResponse.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/ClubMonthlyStatusResponse.java index bee4d13f..ea04be30 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/ClubMonthlyStatusResponse.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/ClubMonthlyStatusResponse.java @@ -8,11 +8,12 @@ public record ClubMonthlyStatusResponse( int year, int month, int rank, - long feedCount, - long viewCount, - long likeCount, - long commentCount, - long score + int lastMonthRank, + long feedScore, + long viewScore, + long likeScore, + long commentScore, + long totalScore ) { public static ClubMonthlyStatusResponse from(ClubMonthlyStatusQuery query) { @@ -20,11 +21,12 @@ public static ClubMonthlyStatusResponse from(ClubMonthlyStatusQuery query) { .year(query.year()) .month(query.month()) .rank(query.rank()) - .feedCount(query.feedCount()) - .viewCount(query.viewCount()) - .likeCount(query.likeCount()) - .commentCount(query.commentCount()) - .score(query.score()) + .lastMonthRank(query.lastMonthRank()) + .feedScore(query.feedScore()) + .viewScore(query.viewScore()) + .likeScore(query.likeScore()) + .commentScore(query.commentScore()) + .totalScore(query.totalScore()) .build(); } } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java index e8896a99..71577886 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java @@ -53,15 +53,29 @@ public List getClubFeedRanking(int year, int month) { public ClubMonthlyStatusQuery getClubMonthlyStatus(Long userId, int year, int month) { Club club = clubService.getByUserId(userId); List rankings = getClubFeedRanking(year, month); + int lastMonthRank = getLastMonthRank(club.getId(), year, month); return rankings.stream() .filter(rankingQuery -> rankingQuery.clubId().equals(club.getId())) .findFirst() .filter(rankingQuery -> rankingQuery.score() > 0) - .map(rankingQuery -> ClubMonthlyStatusQuery.from(year, month, rankingQuery)) + .map(rankingQuery -> ClubMonthlyStatusQuery.from(year, month, rankingQuery, lastMonthRank)) .orElse(ClubMonthlyStatusQuery.createEmpty(year, month)); } + private int getLastMonthRank(Long clubId, int year, int month) { + int lastYear = month == 1 ? year - 1 : year; + int lastMonth = month == 1 ? 12 : month - 1; + + List lastMonthRankings = getClubFeedRanking(lastYear, lastMonth); + return lastMonthRankings.stream() + .filter(ranking -> ranking.clubId().equals(clubId)) + .filter(ranking -> ranking.score() > 0) + .findFirst() + .map(ClubFeedRankingQuery::rank) + .orElse(0); + } + private long calculateScore(MonthlyFeedRankingDto dto) { return dto.getFeedCount() * FEED_WEIGHT + dto.getViewCount() * VIEW_WEIGHT diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java index da956cd8..32d2c03a 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java @@ -7,23 +7,36 @@ public record ClubMonthlyStatusQuery( int year, int month, int rank, - long feedCount, - long viewCount, - long likeCount, - long commentCount, - long score + int lastMonthRank, + long feedScore, + long viewScore, + long likeScore, + long commentScore, + long totalScore ) { - public static ClubMonthlyStatusQuery from(int year, int month, ClubFeedRankingQuery ranking) { + private static final int FEED_WEIGHT = 10; + private static final int VIEW_WEIGHT = 1; + private static final int LIKE_WEIGHT = 3; + private static final int COMMENT_WEIGHT = 5; + + public static ClubMonthlyStatusQuery from(int year, int month, ClubFeedRankingQuery ranking, + int lastMonthRank) { + long feedScore = ranking.feedCount() * FEED_WEIGHT; + long viewScore = ranking.viewCount() * VIEW_WEIGHT; + long likeScore = ranking.likeCount() * LIKE_WEIGHT; + long commentScore = ranking.commentCount() * COMMENT_WEIGHT; + return ClubMonthlyStatusQuery.builder() .year(year) .month(month) .rank(ranking.rank()) - .feedCount(ranking.feedCount()) - .viewCount(ranking.viewCount()) - .likeCount(ranking.likeCount()) - .commentCount(ranking.commentCount()) - .score(ranking.score()) + .lastMonthRank(lastMonthRank) + .feedScore(feedScore) + .viewScore(viewScore) + .likeScore(likeScore) + .commentScore(commentScore) + .totalScore(feedScore + viewScore + likeScore + commentScore) .build(); } @@ -32,11 +45,12 @@ public static ClubMonthlyStatusQuery createEmpty(int year, int month) { .year(year) .month(month) .rank(0) - .feedCount(0) - .viewCount(0) - .likeCount(0) - .commentCount(0) - .score(0) + .lastMonthRank(0) + .feedScore(0) + .viewScore(0) + .likeScore(0) + .commentScore(0) + .totalScore(0) .build(); } } From fa3fb3e8a549751ecc73a38dafcd8f9283dc9560 Mon Sep 17 00:00:00 2001 From: KoSeonJe <127813439+KoSeonJe@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:36:25 +0900 Subject: [PATCH 2/9] =?UTF-8?q?test:=20[DDING-000]=20=EC=9D=B4=EB=8B=AC?= =?UTF-8?q?=EC=9D=98=20=ED=98=84=ED=99=A9=20=EA=B0=80=EC=A4=91=EC=B9=98=20?= =?UTF-8?q?=EC=A0=90=EC=88=98=20=EB=B0=8F=20=EC=A0=80=EB=B2=88=20=EB=8B=AC?= =?UTF-8?q?=20=EC=88=9C=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98?= =?UTF-8?q?=EC=A0=95/=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 3개 테스트: feedCount→feedScore 등 필드명 변경 + lastMonthRank 검증 추가 - 신규 3개 테스트: 저번 달 피드 있을 때/없을 때, 1월→전년도 12월 순위 조회 Co-Authored-By: Claude Opus 4.6 --- .../GeneralFeedRankingServiceTest.java | 116 ++++++++++++++++-- 1 file changed, 103 insertions(+), 13 deletions(-) diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java index 456c3466..8c70b3e8 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java @@ -17,12 +17,15 @@ import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubMonthlyStatusQuery; import ddingdong.ddingdongBE.domain.user.entity.User; import ddingdong.ddingdongBE.domain.user.repository.UserRepository; +import java.sql.Timestamp; import java.time.LocalDate; +import java.time.LocalDateTime; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.jdbc.core.JdbcTemplate; @SpringBootTest class GeneralFeedRankingServiceTest extends TestContainerSupport { @@ -45,6 +48,9 @@ class GeneralFeedRankingServiceTest extends TestContainerSupport { @Autowired private UserRepository userRepository; + @Autowired + private JdbcTemplate jdbcTemplate; + @DisplayName("동아리별 피드 랭킹 조회 - 성공: 점수 높은 순서로 정렬된다") @Test void getClubFeedRanking_sortedByScore() { @@ -197,7 +203,7 @@ void getClubFeedRanking_withLikesAndComments() { }); } - @DisplayName("동아리 이달의 현황 조회 - 성공: 피드가 있으면 내 동아리 통계와 rank가 반환된다") + @DisplayName("동아리 이달의 현황 조회 - 성공: 피드가 있으면 가중치 점수와 rank가 반환된다") @Test void getClubMonthlyStatus_withFeeds() { // given @@ -213,17 +219,21 @@ void getClubMonthlyStatus_withFeeds() { ClubMonthlyStatusQuery result = feedRankingService.getClubMonthlyStatus(user.getId(), year, month); // then - // score = feedCount(2)*10 = 20 + // feedScore = feedCount(2)*10 = 20, viewScore = 0, likeScore = 0, commentScore = 0 assertSoftly(softly -> { softly.assertThat(result.year()).isEqualTo(year); softly.assertThat(result.month()).isEqualTo(month); softly.assertThat(result.rank()).isEqualTo(1); - softly.assertThat(result.feedCount()).isEqualTo(2L); - softly.assertThat(result.score()).isEqualTo(20L); + softly.assertThat(result.feedScore()).isEqualTo(20L); + softly.assertThat(result.viewScore()).isEqualTo(0L); + softly.assertThat(result.likeScore()).isEqualTo(0L); + softly.assertThat(result.commentScore()).isEqualTo(0L); + softly.assertThat(result.totalScore()).isEqualTo(20L); + softly.assertThat(result.lastMonthRank()).isEqualTo(0); }); } - @DisplayName("동아리 이달의 현황 조회 - 성공: 피드가 없으면 모든 값 0, rank=0으로 반환된다") + @DisplayName("동아리 이달의 현황 조회 - 성공: 피드가 없으면 모든 값 0으로 반환된다") @Test void getClubMonthlyStatus_noFeeds() { // given @@ -238,11 +248,12 @@ void getClubMonthlyStatus_noFeeds() { softly.assertThat(result.year()).isEqualTo(2000); softly.assertThat(result.month()).isEqualTo(1); softly.assertThat(result.rank()).isEqualTo(0); - softly.assertThat(result.feedCount()).isEqualTo(0L); - softly.assertThat(result.viewCount()).isEqualTo(0L); - softly.assertThat(result.likeCount()).isEqualTo(0L); - softly.assertThat(result.commentCount()).isEqualTo(0L); - softly.assertThat(result.score()).isEqualTo(0L); + softly.assertThat(result.lastMonthRank()).isEqualTo(0); + softly.assertThat(result.feedScore()).isEqualTo(0L); + softly.assertThat(result.viewScore()).isEqualTo(0L); + softly.assertThat(result.likeScore()).isEqualTo(0L); + softly.assertThat(result.commentScore()).isEqualTo(0L); + softly.assertThat(result.totalScore()).isEqualTo(0L); }); } @@ -265,11 +276,90 @@ void getClubMonthlyStatus_rankAccuracy() { // when — 동아리B 회장이 조회 ClubMonthlyStatusQuery result = feedRankingService.getClubMonthlyStatus(userB.getId(), year, month); - // then — 동아리A(score=20) > 동아리B(score=10), 동아리B는 rank=2 + // then — 동아리A(totalScore=20) > 동아리B(totalScore=10), 동아리B는 rank=2 assertSoftly(softly -> { softly.assertThat(result.rank()).isEqualTo(2); - softly.assertThat(result.feedCount()).isEqualTo(1L); - softly.assertThat(result.score()).isEqualTo(10L); + softly.assertThat(result.feedScore()).isEqualTo(10L); + softly.assertThat(result.totalScore()).isEqualTo(10L); + }); + } + + @DisplayName("동아리 이달의 현황 조회 - 성공: 저번 달에 피드가 있으면 lastMonthRank가 반환된다") + @Test + void getClubMonthlyStatus_withLastMonthRank() { + // given + User user = userRepository.save(UserFixture.createClubUser()); + Club club = clubRepository.save(ClubFixture.createClub(user)); + + // 저번 달 피드 생성 + LocalDate lastMonth = LocalDate.now().minusMonths(1); + Feed lastMonthFeed = feedRepository.save(FeedFixture.createImageFeed(club, "저번달 피드")); + jdbcTemplate.update("UPDATE feed SET created_at = ? WHERE id = ?", + Timestamp.valueOf(lastMonth.atStartOfDay()), lastMonthFeed.getId()); + + // 이번 달 피드 생성 + feedRepository.save(FeedFixture.createImageFeed(club, "이번달 피드")); + + int year = LocalDate.now().getYear(); + int month = LocalDate.now().getMonthValue(); + + // when + ClubMonthlyStatusQuery result = feedRankingService.getClubMonthlyStatus(user.getId(), year, month); + + // then — 저번 달에도 피드가 있으므로 lastMonthRank > 0 + assertSoftly(softly -> { + softly.assertThat(result.rank()).isEqualTo(1); + softly.assertThat(result.lastMonthRank()).isEqualTo(1); + }); + } + + @DisplayName("동아리 이달의 현황 조회 - 성공: 저번 달에 피드가 없으면 lastMonthRank는 0이다") + @Test + void getClubMonthlyStatus_noLastMonthFeeds() { + // given + User user = userRepository.save(UserFixture.createClubUser()); + clubRepository.save(ClubFixture.createClub(user)); + + // 이번 달 피드만 생성 (저번 달 피드 없음) + feedRepository.save(FeedFixture.createImageFeed( + clubRepository.findAll().get(0), "이번달 피드")); + + int year = LocalDate.now().getYear(); + int month = LocalDate.now().getMonthValue(); + + // when + ClubMonthlyStatusQuery result = feedRankingService.getClubMonthlyStatus(user.getId(), year, month); + + // then — 저번 달 피드가 없으므로 lastMonthRank = 0 + assertThat(result.lastMonthRank()).isEqualTo(0); + } + + @DisplayName("동아리 이달의 현황 조회 - 성공: 1월 조회 시 전년도 12월 순위가 반환된다") + @Test + void getClubMonthlyStatus_januaryLooksAtDecember() { + // given + User user = userRepository.save(UserFixture.createClubUser()); + Club club = clubRepository.save(ClubFixture.createClub(user)); + + // 전년도 12월 피드 생성 + int currentYear = LocalDate.now().getYear(); + Feed decemberFeed = feedRepository.save(FeedFixture.createImageFeed(club, "12월 피드")); + jdbcTemplate.update("UPDATE feed SET created_at = ? WHERE id = ?", + Timestamp.valueOf(LocalDateTime.of(currentYear - 1, 12, 15, 10, 0)), decemberFeed.getId()); + + // 1월 피드 생성 + Feed januaryFeed = feedRepository.save(FeedFixture.createImageFeed(club, "1월 피드")); + jdbcTemplate.update("UPDATE feed SET created_at = ? WHERE id = ?", + Timestamp.valueOf(LocalDateTime.of(currentYear, 1, 15, 10, 0)), januaryFeed.getId()); + + // when — 1월 조회 + ClubMonthlyStatusQuery result = feedRankingService.getClubMonthlyStatus( + user.getId(), currentYear, 1); + + // then — 전년도 12월 순위가 lastMonthRank로 반환된다 + assertSoftly(softly -> { + softly.assertThat(result.rank()).isEqualTo(1); + softly.assertThat(result.lastMonthRank()).isEqualTo(1); }); } } From 4059aed638a0383da763aeb1255a305459c899e0 Mon Sep 17 00:00:00 2001 From: KoSeonJe <127813439+KoSeonJe@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:37:03 +0900 Subject: [PATCH 3/9] =?UTF-8?q?docs:=20[DDING-000]=20CLAUDE.md=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8/PR=20=EC=BB=A8=EB=B2=A4=EC=85=98=20=EB=B3=B4?= =?UTF-8?q?=EA=B0=95=20=EB=B0=8F=20=EB=A6=AC=EB=B7=B0=20=EC=8A=A4=ED=82=AC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .claude/skills/review-fix-plan/SKILL.md | 88 +++++++++++++++++++++++++ CLAUDE.md | 4 ++ 2 files changed, 92 insertions(+) create mode 100644 .claude/skills/review-fix-plan/SKILL.md diff --git a/.claude/skills/review-fix-plan/SKILL.md b/.claude/skills/review-fix-plan/SKILL.md new file mode 100644 index 00000000..3621ca7c --- /dev/null +++ b/.claude/skills/review-fix-plan/SKILL.md @@ -0,0 +1,88 @@ +--- +name: review-fix-plan +description: | + 코드 리뷰 후 수정 계획 문서를 생성하는 스킬. + 코드 리뷰 결과를 구조화된 마크다운 문서로 docs/local/ 하위에 저장한다. + "리뷰 문서 만들어줘", "코드리뷰 정리해줘", "/review-fix-plan" 요청 시 사용. +--- + +# Review Fix Plan + +코드 리뷰 결과를 **문제상황 → 문제원인 → 해결 방법 → 결과** 4단계 구조로 정리하여 +`docs/local/` 하위에 마크다운 문서로 저장한다. + +## When to Use + +- 코드 리뷰 수행 후 이슈를 문서로 정리하고 싶을 때 +- "리뷰 문서 만들어줘", "코드리뷰 정리해줘" 요청 시 +- "/review-fix-plan" 슬래시 커맨드 입력 시 +- PR 리뷰 후 수정 사항을 추적하고 싶을 때 + +## Process + +1. **리뷰 결과 수집**: 현재 대화에서 발견된 코드 리뷰 이슈 파악 + - 각 이슈의 심각도 판단 (CRITICAL / WARNING / LOW) + - 관련 파일과 코드 위치 식별 + +2. **문서 작성**: 아래 템플릿에 맞춰 각 이슈를 정리 + +3. **파일 저장**: `docs/local/{도메인}-review-fixes.md` 경로에 저장 + - 기존 파일이 있으면 덮어쓰기 (같은 PR/작업 단위) + +4. **완료 보고**: 저장된 파일 경로를 사용자에게 알림 + +## Template + +```markdown +# {작업명} 코드 리뷰 이슈 분석 ({날짜}) + +## 수정 파일 + +- `{파일 경로 1}` +- `{파일 경로 2}` + +--- + +## [{심각도 코드}] {위치} — {이슈 한줄 요약} + +**심각도**: {이모지} {CRITICAL | WARNING | LOW} + +**문제 상황**: {어떤 동작이 문제인지 사용자 관점에서 서술} + +**문제 원인**: {왜 이 문제가 발생했는지 코드 레벨에서 분석} + +```java +// AS-IS — 문제 코드 +{기존 코드} +``` + +**해결 방법**: {구체적인 수정 방안 서술} + +```java +// TO-BE — 개선 코드 +{수정 코드} +``` + +**결과**: {수정 완료 여부, 수정 후 기대 효과} + +--- + +## 패턴 정리 + +| 상황 | 패턴 | +|------|------| +| {이번 리뷰에서 얻은 교훈} | {재발 방지를 위한 규칙} | +``` + +## Severity Guide + +| 코드 | 이모지 | 기준 | +|------|--------|------| +| C-N | 🔴 CRITICAL | 잘못된 데이터 반환, 런타임 에러, 보안 취약점 | +| W-N | 🟡 WARNING | 성능 저하, 중복 코드, 유지보수 위험 | +| L-N | 🟢 LOW | dead code, 네이밍, 스타일 | + +## Naming Convention + +- 파일명: `{도메인}-review-fixes.md` (예: `feed-modify-existing-review-fixes.md`) +- 이슈 ID: `[{심각도 첫글자}-{번호}]` (예: `[C-1]`, `[W-2]`, `[L-1]`) diff --git a/CLAUDE.md b/CLAUDE.md index 3a2ef39c..a389347d 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -31,6 +31,9 @@ - 버그 수정/기능 추가 시 반드시 테스트 추가 - TestContainers 사용 — Docker 실행 상태 필요 - **FixtureMonkey 사용 금지** — 테스트 데이터는 `common/fixture/` 의 static 메서드로만 생성한다 +- `@DisplayName`에 메서드명을 넣지 않고, **테스트만 보고 요구사항을 파악할 수 있는 문장**으로 작성한다 + - 좋은 예: `"피드가 없으면 모든 집계가 0이다"`, `"삭제된 피드는 조회되지 않는다"` + - 나쁜 예: `"findMyFeedStat - 피드가 없으면 0"`, `"getAllFeeds는 삭제된 피드 제외"` --- @@ -39,6 +42,7 @@ - 브랜치명: `{type}/{DDING-이슈번호}-{설명}` (예: `feat/DDING-123-club-search`) - 커밋 메시지: 한국어, `[DDING-000] 작업 내용` 형식 - PR 템플릿: 🚀 작업 내용 / 🤔 고민했던 내용 / 💬 리뷰 중점사항 +- PR 본문 작성 시 특정 클래스명, 메서드명을 나열하지 않고 **작업 내용 중심의 자연스러운 글**로 작성한다 (가독성 우선) ### API 단위 브랜치 전략 (PR 크기 관리) From 7d296b67a0c7389e61045b5c766e3d2d30e703c6 Mon Sep 17 00:00:00 2001 From: KoSeonJe <127813439+KoSeonJe@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:46:59 +0900 Subject: [PATCH 4/9] =?UTF-8?q?refactor:=20[DDING-000]=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EB=A6=AC=EB=B7=B0=20=EB=B0=98=EC=98=81=20-=20?= =?UTF-8?q?=EA=B0=80=EC=A4=91=EC=B9=98=20=EA=B3=84=EC=82=B0=20Service=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99=20=EB=B0=8F=20Swagger=20=EC=84=A4=EB=AA=85?= =?UTF-8?q?=20=EA=B0=84=EA=B2=B0=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Swagger description 원복 (간결하게) - 가중치 계산 로직을 Query DTO에서 Service로 이동 (도메인 로직은 DTO 책임 아님) - Query DTO의 from()을 of()로 변경하여 계산된 값을 직접 받도록 수정 Co-Authored-By: Claude Opus 4.6 --- .../ddingdongBE/domain/feed/api/ClubFeedApi.java | 2 +- .../feed/service/GeneralFeedRankingService.java | 13 ++++++++++++- .../dto/query/ClubMonthlyStatusQuery.java | 16 +++------------- 3 files changed, 16 insertions(+), 15 deletions(-) diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java index a800185f..ca60bc25 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/api/ClubFeedApi.java @@ -72,7 +72,7 @@ MyFeedPageResponse getMyFeedPage( ); @Operation(summary = "동아리 이달의 현황 조회 API") - @ApiResponse(responseCode = "200", description = "동아리 이달의 현황 조회 성공 - 가중치 점수(feedScore/viewScore/likeScore/commentScore/totalScore)와 저번 달 순위(lastMonthRank) 반환", + @ApiResponse(responseCode = "200", description = "동아리 이달의 현황 조회 성공", content = @Content(schema = @Schema(implementation = ClubMonthlyStatusResponse.class))) @ResponseStatus(HttpStatus.OK) @SecurityRequirement(name = "AccessToken") diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java index 71577886..02274213 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java @@ -59,10 +59,21 @@ public ClubMonthlyStatusQuery getClubMonthlyStatus(Long userId, int year, int mo .filter(rankingQuery -> rankingQuery.clubId().equals(club.getId())) .findFirst() .filter(rankingQuery -> rankingQuery.score() > 0) - .map(rankingQuery -> ClubMonthlyStatusQuery.from(year, month, rankingQuery, lastMonthRank)) + .map(rankingQuery -> toMonthlyStatus(year, month, rankingQuery, lastMonthRank)) .orElse(ClubMonthlyStatusQuery.createEmpty(year, month)); } + private ClubMonthlyStatusQuery toMonthlyStatus(int year, int month, + ClubFeedRankingQuery ranking, int lastMonthRank) { + long feedScore = ranking.feedCount() * FEED_WEIGHT; + long viewScore = ranking.viewCount() * VIEW_WEIGHT; + long likeScore = ranking.likeCount() * LIKE_WEIGHT; + long commentScore = ranking.commentCount() * COMMENT_WEIGHT; + + return ClubMonthlyStatusQuery.of(year, month, ranking.rank(), lastMonthRank, + feedScore, viewScore, likeScore, commentScore); + } + private int getLastMonthRank(Long clubId, int year, int month) { int lastYear = month == 1 ? year - 1 : year; int lastMonth = month == 1 ? 12 : month - 1; diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java index 32d2c03a..8d36a064 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java @@ -15,22 +15,12 @@ public record ClubMonthlyStatusQuery( long totalScore ) { - private static final int FEED_WEIGHT = 10; - private static final int VIEW_WEIGHT = 1; - private static final int LIKE_WEIGHT = 3; - private static final int COMMENT_WEIGHT = 5; - - public static ClubMonthlyStatusQuery from(int year, int month, ClubFeedRankingQuery ranking, - int lastMonthRank) { - long feedScore = ranking.feedCount() * FEED_WEIGHT; - long viewScore = ranking.viewCount() * VIEW_WEIGHT; - long likeScore = ranking.likeCount() * LIKE_WEIGHT; - long commentScore = ranking.commentCount() * COMMENT_WEIGHT; - + public static ClubMonthlyStatusQuery of(int year, int month, int rank, int lastMonthRank, + long feedScore, long viewScore, long likeScore, long commentScore) { return ClubMonthlyStatusQuery.builder() .year(year) .month(month) - .rank(ranking.rank()) + .rank(rank) .lastMonthRank(lastMonthRank) .feedScore(feedScore) .viewScore(viewScore) From e1ef80ec6167dd4c12ce11d15569bc0eb7f055e1 Mon Sep 17 00:00:00 2001 From: KoSeonJe <127813439+KoSeonJe@users.noreply.github.com> Date: Mon, 23 Feb 2026 17:53:32 +0900 Subject: [PATCH 5/9] =?UTF-8?q?fix:=20[DDING-000]=20E2E=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=ED=95=84=EB=93=9C=EB=AA=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD=20=EB=B0=98=EC=98=81=20(feedCount=E2=86=92feedScore?= =?UTF-8?q?=20=EB=93=B1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .../domain/feed/controller/ClubFeedStatusE2ETest.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java index 713c8639..53d98bda 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java @@ -91,14 +91,16 @@ void getFeedStatus_success() { .extract() .as(Map.class); + // feedScore = 2*10=20, viewScore = 0, likeScore = 1*3=3, commentScore = 1*5=5, totalScore = 28 assertSoftly(softly -> { softly.assertThat(response.get("year")).isEqualTo(year); softly.assertThat(response.get("month")).isEqualTo(month); - softly.assertThat(((Number) response.get("feedCount")).longValue()).isEqualTo(2L); - softly.assertThat(((Number) response.get("likeCount")).longValue()).isEqualTo(1L); - softly.assertThat(((Number) response.get("commentCount")).longValue()).isEqualTo(1L); - softly.assertThat(((Number) response.get("score")).longValue()).isEqualTo(28L); + softly.assertThat(((Number) response.get("feedScore")).longValue()).isEqualTo(20L); + softly.assertThat(((Number) response.get("likeScore")).longValue()).isEqualTo(3L); + softly.assertThat(((Number) response.get("commentScore")).longValue()).isEqualTo(5L); + softly.assertThat(((Number) response.get("totalScore")).longValue()).isEqualTo(28L); softly.assertThat(((Number) response.get("rank")).intValue()).isEqualTo(1); + softly.assertThat(((Number) response.get("lastMonthRank")).intValue()).isEqualTo(0); }); } From cd74c48724a09f86a7ed9d0f2ace983af27e9ae5 Mon Sep 17 00:00:00 2001 From: KoSeonJe <127813439+KoSeonJe@users.noreply.github.com> Date: Mon, 23 Feb 2026 18:51:13 +0900 Subject: [PATCH 6/9] =?UTF-8?q?fix:=20[DDING-000]=20createEmpty=20fallback?= =?UTF-8?q?=EC=97=90=EC=84=9C=20lastMonthRank=20=EC=9C=A0=EC=8B=A4=20?= =?UTF-8?q?=EB=B2=84=EA=B7=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이번 달 피드가 없는 동아리가 createEmpty 분기로 빠질 때 계산된 lastMonthRank가 전달되지 않고 항상 0으로 반환되던 버그를 수정했다. 테스트에서 clubRepository.findAll().get(0) 대신 save 반환값을 직접 사용하도록 개선했다. Co-Authored-By: Claude Opus 4.6 --- .../service/GeneralFeedRankingService.java | 2 +- .../dto/query/ClubMonthlyStatusQuery.java | 4 +-- .../GeneralFeedRankingServiceTest.java | 32 +++++++++++++++++-- 3 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java index 02274213..5da77eaf 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java @@ -60,7 +60,7 @@ public ClubMonthlyStatusQuery getClubMonthlyStatus(Long userId, int year, int mo .findFirst() .filter(rankingQuery -> rankingQuery.score() > 0) .map(rankingQuery -> toMonthlyStatus(year, month, rankingQuery, lastMonthRank)) - .orElse(ClubMonthlyStatusQuery.createEmpty(year, month)); + .orElse(ClubMonthlyStatusQuery.createEmpty(year, month, lastMonthRank)); } private ClubMonthlyStatusQuery toMonthlyStatus(int year, int month, diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java index 8d36a064..c03418a4 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubMonthlyStatusQuery.java @@ -30,12 +30,12 @@ public static ClubMonthlyStatusQuery of(int year, int month, int rank, int lastM .build(); } - public static ClubMonthlyStatusQuery createEmpty(int year, int month) { + public static ClubMonthlyStatusQuery createEmpty(int year, int month, int lastMonthRank) { return ClubMonthlyStatusQuery.builder() .year(year) .month(month) .rank(0) - .lastMonthRank(0) + .lastMonthRank(lastMonthRank) .feedScore(0) .viewScore(0) .likeScore(0) diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java index 8c70b3e8..75fd2a19 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java @@ -318,11 +318,10 @@ void getClubMonthlyStatus_withLastMonthRank() { void getClubMonthlyStatus_noLastMonthFeeds() { // given User user = userRepository.save(UserFixture.createClubUser()); - clubRepository.save(ClubFixture.createClub(user)); + Club club = clubRepository.save(ClubFixture.createClub(user)); // 이번 달 피드만 생성 (저번 달 피드 없음) - feedRepository.save(FeedFixture.createImageFeed( - clubRepository.findAll().get(0), "이번달 피드")); + feedRepository.save(FeedFixture.createImageFeed(club, "이번달 피드")); int year = LocalDate.now().getYear(); int month = LocalDate.now().getMonthValue(); @@ -334,6 +333,33 @@ void getClubMonthlyStatus_noLastMonthFeeds() { assertThat(result.lastMonthRank()).isEqualTo(0); } + @DisplayName("동아리 이달의 현황 조회 - 성공: 이번 달 피드가 없어도 저번 달 순위가 반환된다") + @Test + void getClubMonthlyStatus_noCurrentMonthFeed_butHasLastMonthRank() { + // given + User user = userRepository.save(UserFixture.createClubUser()); + Club club = clubRepository.save(ClubFixture.createClub(user)); + + // 저번 달 피드만 생성 (이번 달 피드 없음) + LocalDate lastMonth = LocalDate.now().minusMonths(1); + Feed lastMonthFeed = feedRepository.save(FeedFixture.createImageFeed(club, "저번달 피드")); + jdbcTemplate.update("UPDATE feed SET created_at = ? WHERE id = ?", + Timestamp.valueOf(lastMonth.atStartOfDay()), lastMonthFeed.getId()); + + int year = LocalDate.now().getYear(); + int month = LocalDate.now().getMonthValue(); + + // when + ClubMonthlyStatusQuery result = feedRankingService.getClubMonthlyStatus(user.getId(), year, month); + + // then — 이번 달 score=0이어도 저번 달 순위가 정상 반환되어야 한다 + assertSoftly(softly -> { + softly.assertThat(result.rank()).isEqualTo(0); + softly.assertThat(result.lastMonthRank()).isEqualTo(1); + softly.assertThat(result.totalScore()).isEqualTo(0L); + }); + } + @DisplayName("동아리 이달의 현황 조회 - 성공: 1월 조회 시 전년도 12월 순위가 반환된다") @Test void getClubMonthlyStatus_januaryLooksAtDecember() { From 0ba7a302f6aec35974c95ffe0d29ba8a1043749e Mon Sep 17 00:00:00 2001 From: KoSeonJe <127813439+KoSeonJe@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:23:54 +0900 Subject: [PATCH 7/9] =?UTF-8?q?refactor:=20[DDING-000]=20Admin=20=ED=94=BC?= =?UTF-8?q?=EB=93=9C=20=EB=9E=AD=ED=82=B9=20API=20=EC=9D=91=EB=8B=B5=20?= =?UTF-8?q?=ED=95=84=EB=93=9C=20Score=EB=A1=9C=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .../AdminClubFeedRankingResponse.java | 27 ++++++++++--------- .../AdminFeedControllerE2ETest.java | 19 ++++++++++--- 2 files changed, 30 insertions(+), 16 deletions(-) diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminClubFeedRankingResponse.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminClubFeedRankingResponse.java index c7f5bc06..86c58173 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminClubFeedRankingResponse.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminClubFeedRankingResponse.java @@ -7,26 +7,29 @@ @Builder public record AdminClubFeedRankingResponse( int rank, - Long clubId, String clubName, - long feedCount, - long viewCount, - long likeCount, - long commentCount, - long score + long feedScore, + long viewScore, + long likeScore, + long commentScore, + long totalScore ) { + private static final int FEED_WEIGHT = 10; + private static final int VIEW_WEIGHT = 1; + private static final int LIKE_WEIGHT = 3; + private static final int COMMENT_WEIGHT = 5; + public static List from(List queries) { return queries.stream() .map(query -> AdminClubFeedRankingResponse.builder() .rank(query.rank()) - .clubId(query.clubId()) .clubName(query.clubName()) - .feedCount(query.feedCount()) - .viewCount(query.viewCount()) - .likeCount(query.likeCount()) - .commentCount(query.commentCount()) - .score(query.score()) + .feedScore(query.feedCount() * FEED_WEIGHT) + .viewScore(query.viewCount() * VIEW_WEIGHT) + .likeScore(query.likeCount() * LIKE_WEIGHT) + .commentScore(query.commentCount() * COMMENT_WEIGHT) + .totalScore(query.score()) .build()) .toList(); } diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java index 2aabe9f0..ebd2a4e7 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java @@ -97,10 +97,21 @@ void getClubFeedRanking_success() { .getList(".", AdminClubFeedRankingResponse.class); assertThat(response).hasSize(2); - assertThat(response.get(0).clubName()).isEqualTo("동아리B"); - assertThat(response.get(0).rank()).isEqualTo(1); - assertThat(response.get(1).clubName()).isEqualTo("동아리A"); - assertThat(response.get(1).rank()).isEqualTo(2); + + AdminClubFeedRankingResponse first = response.get(0); + assertThat(first.clubName()).isEqualTo("동아리B"); + assertThat(first.rank()).isEqualTo(1); + assertThat(first.feedScore()).isEqualTo(2 * 10); + assertThat(first.viewScore()).isEqualTo(0); + assertThat(first.likeScore()).isEqualTo(1 * 3); + assertThat(first.commentScore()).isEqualTo(0); + assertThat(first.totalScore()).isEqualTo(23); + + AdminClubFeedRankingResponse second = response.get(1); + assertThat(second.clubName()).isEqualTo("동아리A"); + assertThat(second.rank()).isEqualTo(2); + assertThat(second.feedScore()).isEqualTo(1 * 10); + assertThat(second.totalScore()).isEqualTo(10); } @DisplayName("총동연 피드 랭킹 조회 API - 성공: 데이터 없으면 빈 리스트 반환") From 5bde7e72d265d6758cca1ac96603fbaa42d6048e Mon Sep 17 00:00:00 2001 From: KoSeonJe <127813439+KoSeonJe@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:35:30 +0900 Subject: [PATCH 8/9] =?UTF-8?q?refactor:=20[DDING-000]=20=EA=B0=80?= =?UTF-8?q?=EC=A4=91=EC=B9=98=20=EA=B3=84=EC=82=B0=EC=9D=84=20Response?= =?UTF-8?q?=EC=97=90=EC=84=9C=20Service=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- .../AdminClubFeedRankingResponse.java | 15 ++++------ .../service/GeneralFeedRankingService.java | 29 +++++++++++-------- .../dto/query/ClubFeedRankingQuery.java | 28 +++++++++--------- .../GeneralFeedRankingServiceTest.java | 26 ++++++++--------- 4 files changed, 49 insertions(+), 49 deletions(-) diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminClubFeedRankingResponse.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminClubFeedRankingResponse.java index 86c58173..07f714cf 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminClubFeedRankingResponse.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/response/AdminClubFeedRankingResponse.java @@ -15,21 +15,16 @@ public record AdminClubFeedRankingResponse( long totalScore ) { - private static final int FEED_WEIGHT = 10; - private static final int VIEW_WEIGHT = 1; - private static final int LIKE_WEIGHT = 3; - private static final int COMMENT_WEIGHT = 5; - public static List from(List queries) { return queries.stream() .map(query -> AdminClubFeedRankingResponse.builder() .rank(query.rank()) .clubName(query.clubName()) - .feedScore(query.feedCount() * FEED_WEIGHT) - .viewScore(query.viewCount() * VIEW_WEIGHT) - .likeScore(query.likeCount() * LIKE_WEIGHT) - .commentScore(query.commentCount() * COMMENT_WEIGHT) - .totalScore(query.score()) + .feedScore(query.feedScore()) + .viewScore(query.viewScore()) + .likeScore(query.likeScore()) + .commentScore(query.commentScore()) + .totalScore(query.totalScore()) .build()) .toList(); } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java index 5da77eaf..d5078735 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java @@ -38,12 +38,12 @@ public List getClubFeedRanking(int year, int month) { long previousScore = Long.MAX_VALUE; int rank = 1; for (int i = 0; i < sorted.size(); i++) { - long score = calculateScore(sorted.get(i)); - if (i > 0 && score < previousScore) { + long totalScore = calculateScore(sorted.get(i)); + if (i > 0 && totalScore < previousScore) { rank = i + 1; } - result.add(ClubFeedRankingQuery.of(rank, sorted.get(i), score)); - previousScore = score; + result.add(toClubFeedRankingQuery(rank, sorted.get(i))); + previousScore = totalScore; } return result; @@ -58,20 +58,15 @@ public ClubMonthlyStatusQuery getClubMonthlyStatus(Long userId, int year, int mo return rankings.stream() .filter(rankingQuery -> rankingQuery.clubId().equals(club.getId())) .findFirst() - .filter(rankingQuery -> rankingQuery.score() > 0) + .filter(rankingQuery -> rankingQuery.totalScore() > 0) .map(rankingQuery -> toMonthlyStatus(year, month, rankingQuery, lastMonthRank)) .orElse(ClubMonthlyStatusQuery.createEmpty(year, month, lastMonthRank)); } private ClubMonthlyStatusQuery toMonthlyStatus(int year, int month, ClubFeedRankingQuery ranking, int lastMonthRank) { - long feedScore = ranking.feedCount() * FEED_WEIGHT; - long viewScore = ranking.viewCount() * VIEW_WEIGHT; - long likeScore = ranking.likeCount() * LIKE_WEIGHT; - long commentScore = ranking.commentCount() * COMMENT_WEIGHT; - return ClubMonthlyStatusQuery.of(year, month, ranking.rank(), lastMonthRank, - feedScore, viewScore, likeScore, commentScore); + ranking.feedScore(), ranking.viewScore(), ranking.likeScore(), ranking.commentScore()); } private int getLastMonthRank(Long clubId, int year, int month) { @@ -81,12 +76,22 @@ private int getLastMonthRank(Long clubId, int year, int month) { List lastMonthRankings = getClubFeedRanking(lastYear, lastMonth); return lastMonthRankings.stream() .filter(ranking -> ranking.clubId().equals(clubId)) - .filter(ranking -> ranking.score() > 0) + .filter(ranking -> ranking.totalScore() > 0) .findFirst() .map(ClubFeedRankingQuery::rank) .orElse(0); } + private ClubFeedRankingQuery toClubFeedRankingQuery(int rank, MonthlyFeedRankingDto dto) { + long feedScore = dto.getFeedCount() * FEED_WEIGHT; + long viewScore = dto.getViewCount() * VIEW_WEIGHT; + long likeScore = dto.getLikeCount() * LIKE_WEIGHT; + long commentScore = dto.getCommentCount() * COMMENT_WEIGHT; + long totalScore = feedScore + viewScore + likeScore + commentScore; + return ClubFeedRankingQuery.of(rank, dto.getClubId(), dto.getClubName(), + feedScore, viewScore, likeScore, commentScore, totalScore); + } + private long calculateScore(MonthlyFeedRankingDto dto) { return dto.getFeedCount() * FEED_WEIGHT + dto.getViewCount() * VIEW_WEIGHT diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubFeedRankingQuery.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubFeedRankingQuery.java index cb533746..a8e23eeb 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubFeedRankingQuery.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/dto/query/ClubFeedRankingQuery.java @@ -1,6 +1,5 @@ package ddingdong.ddingdongBE.domain.feed.service.dto.query; -import ddingdong.ddingdongBE.domain.feed.repository.dto.MonthlyFeedRankingDto; import lombok.Builder; @Builder @@ -8,23 +7,24 @@ public record ClubFeedRankingQuery( int rank, Long clubId, String clubName, - long feedCount, - long viewCount, - long likeCount, - long commentCount, - long score + long feedScore, + long viewScore, + long likeScore, + long commentScore, + long totalScore ) { - public static ClubFeedRankingQuery of(int rank, MonthlyFeedRankingDto dto, long score) { + public static ClubFeedRankingQuery of(int rank, Long clubId, String clubName, + long feedScore, long viewScore, long likeScore, long commentScore, long totalScore) { return ClubFeedRankingQuery.builder() .rank(rank) - .clubId(dto.getClubId()) - .clubName(dto.getClubName()) - .feedCount(dto.getFeedCount()) - .viewCount(dto.getViewCount()) - .likeCount(dto.getLikeCount()) - .commentCount(dto.getCommentCount()) - .score(score) + .clubId(clubId) + .clubName(clubName) + .feedScore(feedScore) + .viewScore(viewScore) + .likeScore(likeScore) + .commentScore(commentScore) + .totalScore(totalScore) .build(); } } diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java index 75fd2a19..2fb2b82d 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java @@ -76,10 +76,10 @@ void getClubFeedRanking_sortedByScore() { assertSoftly(softly -> { softly.assertThat(result.get(0).clubName()).isEqualTo("동아리B"); softly.assertThat(result.get(0).rank()).isEqualTo(1); - softly.assertThat(result.get(0).score()).isEqualTo(20L); + softly.assertThat(result.get(0).totalScore()).isEqualTo(20L); softly.assertThat(result.get(1).clubName()).isEqualTo("동아리A"); softly.assertThat(result.get(1).rank()).isEqualTo(2); - softly.assertThat(result.get(1).score()).isEqualTo(10L); + softly.assertThat(result.get(1).totalScore()).isEqualTo(10L); }); } @@ -108,12 +108,12 @@ void getClubFeedRanking_tieRanking() { assertThat(result).hasSize(3); assertSoftly(softly -> { softly.assertThat(result.get(0).rank()).isEqualTo(1); - softly.assertThat(result.get(0).score()).isEqualTo(20L); + softly.assertThat(result.get(0).totalScore()).isEqualTo(20L); // 동점자 2명 → 둘 다 2위 softly.assertThat(result.get(1).rank()).isEqualTo(2); - softly.assertThat(result.get(1).score()).isEqualTo(10L); + softly.assertThat(result.get(1).totalScore()).isEqualTo(10L); softly.assertThat(result.get(2).rank()).isEqualTo(2); - softly.assertThat(result.get(2).score()).isEqualTo(10L); + softly.assertThat(result.get(2).totalScore()).isEqualTo(10L); }); } @@ -136,9 +136,9 @@ void getClubFeedRanking_includesClubsWithNoFeeds() { assertThat(result).hasSize(2); assertSoftly(softly -> { softly.assertThat(result.get(0).clubName()).isEqualTo("피드있는동아리"); - softly.assertThat(result.get(0).score()).isEqualTo(10L); + softly.assertThat(result.get(0).totalScore()).isEqualTo(10L); softly.assertThat(result.get(1).clubName()).isEqualTo("피드없는동아리"); - softly.assertThat(result.get(1).score()).isEqualTo(0L); + softly.assertThat(result.get(1).totalScore()).isEqualTo(0L); }); } @@ -156,8 +156,8 @@ void getClubFeedRanking_noFeedsInMonth() { assertThat(result).hasSize(1); assertSoftly(softly -> { softly.assertThat(result.get(0).clubName()).isEqualTo("동아리"); - softly.assertThat(result.get(0).feedCount()).isEqualTo(0); - softly.assertThat(result.get(0).score()).isEqualTo(0L); + softly.assertThat(result.get(0).feedScore()).isEqualTo(0); + softly.assertThat(result.get(0).totalScore()).isEqualTo(0L); }); } @@ -196,10 +196,10 @@ void getClubFeedRanking_withLikesAndComments() { assertThat(result).hasSize(1); assertSoftly(softly -> { softly.assertThat(result.get(0).clubName()).isEqualTo("활발한동아리"); - softly.assertThat(result.get(0).feedCount()).isEqualTo(1); - softly.assertThat(result.get(0).likeCount()).isEqualTo(2); - softly.assertThat(result.get(0).commentCount()).isEqualTo(1); - softly.assertThat(result.get(0).score()).isEqualTo(21L); + softly.assertThat(result.get(0).feedScore()).isEqualTo(10L); + softly.assertThat(result.get(0).likeScore()).isEqualTo(6L); + softly.assertThat(result.get(0).commentScore()).isEqualTo(5L); + softly.assertThat(result.get(0).totalScore()).isEqualTo(21L); }); } From df9ada5d8df2f40d8f8bd825c7d81af2b9d2b8d9 Mon Sep 17 00:00:00 2001 From: KoSeonJe <127813439+KoSeonJe@users.noreply.github.com> Date: Mon, 23 Feb 2026 19:39:47 +0900 Subject: [PATCH 9/9] =?UTF-8?q?refactor:=20[DDING-000]=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=EB=AA=85=20=EB=AA=85=ED=99=95=ED=99=94=20=EB=B0=8F=20?= =?UTF-8?q?CLAUDE.md=20=EB=B3=80=EC=88=98=EB=AA=85=20=EC=A7=80=EC=B9=A8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.6 --- CLAUDE.md | 1 + .../service/GeneralFeedRankingService.java | 22 +++++++++---------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index a389347d..3971f734 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -17,6 +17,7 @@ - 완전히 실행 가능한 코드만 제공한다 (의사코드 금지) - `@Valid`, `@NotNull` 등 DTO 검증 어노테이션 적용 - secrets/환경변수 하드코딩 절대 금지 +- **변수명은 의미를 알 수 있도록 작성한다** — `dto`, `r`, `p` 같은 축약 금지, 역할이 드러나는 이름 사용 (예: `dto` → `rawRanking`, `r` → `ranking`) ### 새 기능 추가 시 필수 순서 1. (DB 변경 시) `resources/db/migration/`에 Flyway 마이그레이션 파일 추가 diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java index d5078735..bf03f89d 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingService.java @@ -82,20 +82,20 @@ private int getLastMonthRank(Long clubId, int year, int month) { .orElse(0); } - private ClubFeedRankingQuery toClubFeedRankingQuery(int rank, MonthlyFeedRankingDto dto) { - long feedScore = dto.getFeedCount() * FEED_WEIGHT; - long viewScore = dto.getViewCount() * VIEW_WEIGHT; - long likeScore = dto.getLikeCount() * LIKE_WEIGHT; - long commentScore = dto.getCommentCount() * COMMENT_WEIGHT; + private ClubFeedRankingQuery toClubFeedRankingQuery(int rank, MonthlyFeedRankingDto rawRanking) { + long feedScore = rawRanking.getFeedCount() * FEED_WEIGHT; + long viewScore = rawRanking.getViewCount() * VIEW_WEIGHT; + long likeScore = rawRanking.getLikeCount() * LIKE_WEIGHT; + long commentScore = rawRanking.getCommentCount() * COMMENT_WEIGHT; long totalScore = feedScore + viewScore + likeScore + commentScore; - return ClubFeedRankingQuery.of(rank, dto.getClubId(), dto.getClubName(), + return ClubFeedRankingQuery.of(rank, rawRanking.getClubId(), rawRanking.getClubName(), feedScore, viewScore, likeScore, commentScore, totalScore); } - private long calculateScore(MonthlyFeedRankingDto dto) { - return dto.getFeedCount() * FEED_WEIGHT - + dto.getViewCount() * VIEW_WEIGHT - + dto.getLikeCount() * LIKE_WEIGHT - + dto.getCommentCount() * COMMENT_WEIGHT; + private long calculateScore(MonthlyFeedRankingDto rawRanking) { + return rawRanking.getFeedCount() * FEED_WEIGHT + + rawRanking.getViewCount() * VIEW_WEIGHT + + rawRanking.getLikeCount() * LIKE_WEIGHT + + rawRanking.getCommentCount() * COMMENT_WEIGHT; } }