Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -31,4 +31,15 @@ List<AdminClubFeedRankingResponse> getClubFeedRanking(
@RequestParam("year") @Min(value = 2000, message = "year는 2000 이상이어야 합니다.") @Max(value = 2100, message = "year는 2100 이하여야 합니다.") int year,
@RequestParam("month") @Min(value = 1, message = "month는 1 이상이어야 합니다.") @Max(value = 12, message = "month는 12 이하여야 합니다.") int month
);

@Operation(summary = "총동연 피드 랭킹 스냅샷 조회 API")
@ApiResponse(responseCode = "200", description = "동아리별 피드 랭킹 스냅샷 조회 성공",
content = @Content(array = @ArraySchema(schema = @Schema(implementation = AdminClubFeedRankingResponse.class))))
@ResponseStatus(HttpStatus.OK)
@SecurityRequirement(name = "AccessToken")
@GetMapping("/ranking/snapshot")
List<AdminClubFeedRankingResponse> getClubFeedRankingSnapshot(
@RequestParam("year") @Min(value = 2000, message = "year는 2000 이상이어야 합니다.") @Max(value = 2100, message = "year는 2100 이하여야 합니다.") int year,
@RequestParam("month") @Min(value = 1, message = "month는 1 이상이어야 합니다.") @Max(value = 12, message = "month는 12 이하여야 합니다.") int month
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,4 +21,11 @@ public List<AdminClubFeedRankingResponse> getClubFeedRanking(int year, int month
feedRankingService.getClubFeedRanking(year, month)
);
}

@Override
public List<AdminClubFeedRankingResponse> getClubFeedRankingSnapshot(int year, int month) {
return AdminClubFeedRankingResponse.from(
feedRankingService.getClubFeedRankingSnapshot(year, month)
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,7 @@ public interface FeedMonthlyRankingRepository extends JpaRepository<FeedMonthlyR

List<FeedMonthlyRanking> findAllByTargetYearAndTargetMonthAndRanking(
int targetYear, int targetMonth, int ranking);

List<FeedMonthlyRanking> findAllByTargetYearAndTargetMonthOrderByRankingAsc(
int targetYear, int targetMonth);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@ public interface FeedRankingService {
List<ClubFeedRankingQuery> getClubFeedRanking(int year, int month);

ClubMonthlyStatusQuery getClubMonthlyStatus(Long userId, int year, int month);

List<ClubFeedRankingQuery> getClubFeedRankingSnapshot(int year, int month);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import ddingdong.ddingdongBE.domain.club.entity.Club;
import ddingdong.ddingdongBE.domain.club.service.ClubService;
import ddingdong.ddingdongBE.domain.feed.entity.FeedMonthlyRanking;
import ddingdong.ddingdongBE.domain.feed.repository.FeedMonthlyRankingRepository;
import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository;
import ddingdong.ddingdongBE.domain.feed.repository.dto.MonthlyFeedRankingDto;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubFeedRankingQuery;
Expand All @@ -24,6 +26,7 @@ public class GeneralFeedRankingService implements FeedRankingService {
private static final int COMMENT_WEIGHT = 5;

private final FeedRepository feedRepository;
private final FeedMonthlyRankingRepository feedMonthlyRankingRepository;
private final ClubService clubService;

@Override
Expand Down Expand Up @@ -82,16 +85,39 @@ private int getLastMonthRank(Long clubId, int year, int month) {
.orElse(0);
}

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;
@Override
public List<ClubFeedRankingQuery> getClubFeedRankingSnapshot(int year, int month) {
List<FeedMonthlyRanking> snapshots = feedMonthlyRankingRepository
.findAllByTargetYearAndTargetMonthOrderByRankingAsc(year, month);

return snapshots.stream()
.map(this::toClubFeedRankingQueryFromSnapshot)
.toList();
}

private ClubFeedRankingQuery toClubFeedRankingQuery(int rank, Long clubId, String clubName,
long feedCount, long viewCount, long likeCount, long commentCount) {
long feedScore = feedCount * FEED_WEIGHT;
long viewScore = viewCount * VIEW_WEIGHT;
long likeScore = likeCount * LIKE_WEIGHT;
long commentScore = commentCount * COMMENT_WEIGHT;
long totalScore = feedScore + viewScore + likeScore + commentScore;
return ClubFeedRankingQuery.of(rank, rawRanking.getClubId(), rawRanking.getClubName(),
return ClubFeedRankingQuery.of(rank, clubId, clubName,
feedScore, viewScore, likeScore, commentScore, totalScore);
}

private ClubFeedRankingQuery toClubFeedRankingQueryFromSnapshot(FeedMonthlyRanking snapshot) {
return toClubFeedRankingQuery(snapshot.getRanking(), snapshot.getClubId(), snapshot.getClubName(),
snapshot.getFeedCount(), snapshot.getViewCount(),
snapshot.getLikeCount(), snapshot.getCommentCount());
}

private ClubFeedRankingQuery toClubFeedRankingQuery(int rank, MonthlyFeedRankingDto rawRanking) {
return toClubFeedRankingQuery(rank, rawRanking.getClubId(), rawRanking.getClubName(),
rawRanking.getFeedCount(), rawRanking.getViewCount(),
rawRanking.getLikeCount(), rawRanking.getCommentCount());
}

private long calculateScore(MonthlyFeedRankingDto rawRanking) {
return rawRanking.getFeedCount() * FEED_WEIGHT
+ rawRanking.getViewCount() * VIEW_WEIGHT
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@

import ddingdong.ddingdongBE.common.fixture.ClubFixture;
import ddingdong.ddingdongBE.common.fixture.FeedFixture;
import ddingdong.ddingdongBE.common.fixture.FeedMonthlyRankingFixture;
import ddingdong.ddingdongBE.common.fixture.UserFixture;
import ddingdong.ddingdongBE.common.support.TestContainerSupport;
import ddingdong.ddingdongBE.domain.club.entity.Club;
import ddingdong.ddingdongBE.domain.club.repository.ClubRepository;
import ddingdong.ddingdongBE.domain.feed.entity.Feed;
import ddingdong.ddingdongBE.domain.feed.entity.FeedMonthlyRanking;
import ddingdong.ddingdongBE.domain.feed.repository.FeedCommentRepository;
import ddingdong.ddingdongBE.domain.feed.repository.FeedMonthlyRankingRepository;
import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubFeedRankingQuery;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubMonthlyStatusQuery;
Expand Down Expand Up @@ -41,6 +44,9 @@ class GeneralFeedRankingServiceTest extends TestContainerSupport {
@Autowired
private FeedCommentRepository feedCommentRepository;

@Autowired
private FeedMonthlyRankingRepository feedMonthlyRankingRepository;

@Autowired
private UserRepository userRepository;

Expand Down Expand Up @@ -384,4 +390,64 @@ void getClubMonthlyStatus_januaryLooksAtDecember() {
softly.assertThat(result.lastMonthRank()).isEqualTo(1);
});
}

@DisplayName("피드 랭킹 스냅샷 조회 - 성공: 스냅샷이 있으면 ranking 순서대로 조회된다")
@Test
void getClubFeedRankingSnapshot_sortedByRanking() {
// given
FeedMonthlyRanking rank1 = FeedMonthlyRankingFixture.create(
1L, "동아리A", 10, 100, 50, 20, 2026, 2, 1);
FeedMonthlyRanking rank2 = FeedMonthlyRankingFixture.create(
2L, "동아리B", 5, 50, 25, 10, 2026, 2, 2);
FeedMonthlyRanking rank3 = FeedMonthlyRankingFixture.create(
3L, "동아리C", 3, 30, 15, 5, 2026, 2, 3);
feedMonthlyRankingRepository.saveAll(List.of(rank1, rank2, rank3));

// when
List<ClubFeedRankingQuery> result = feedRankingService.getClubFeedRankingSnapshot(2026, 2);

// then
assertThat(result).hasSize(3);
assertSoftly(softly -> {
softly.assertThat(result.get(0).rank()).isEqualTo(1);
softly.assertThat(result.get(0).clubName()).isEqualTo("동아리A");
softly.assertThat(result.get(1).rank()).isEqualTo(2);
softly.assertThat(result.get(1).clubName()).isEqualTo("동아리B");
softly.assertThat(result.get(2).rank()).isEqualTo(3);
softly.assertThat(result.get(2).clubName()).isEqualTo("동아리C");
});
}

@DisplayName("피드 랭킹 스냅샷 조회 - 성공: 스냅샷이 없으면 빈 리스트가 반환된다")
@Test
void getClubFeedRankingSnapshot_emptyWhenNoSnapshot() {
// when
List<ClubFeedRankingQuery> result = feedRankingService.getClubFeedRankingSnapshot(2026, 2);

// then
assertThat(result).isEmpty();
}

@DisplayName("피드 랭킹 스냅샷 조회 - 성공: 가중치 점수가 정확히 계산된다")
@Test
void getClubFeedRankingSnapshot_calculatesWeightedScores() {
// given — feedCount=10, viewCount=100, likeCount=50, commentCount=20
FeedMonthlyRanking snapshot = FeedMonthlyRankingFixture.create(
1L, "동아리A", 10, 100, 50, 20, 2026, 2, 1);
feedMonthlyRankingRepository.save(snapshot);

// when
List<ClubFeedRankingQuery> result = feedRankingService.getClubFeedRankingSnapshot(2026, 2);

// then
// feedScore=10*10=100, viewScore=100*1=100, likeScore=50*3=150, commentScore=20*5=100, total=450
assertThat(result).hasSize(1);
assertSoftly(softly -> {
softly.assertThat(result.get(0).feedScore()).isEqualTo(100L);
softly.assertThat(result.get(0).viewScore()).isEqualTo(100L);
softly.assertThat(result.get(0).likeScore()).isEqualTo(150L);
softly.assertThat(result.get(0).commentScore()).isEqualTo(100L);
softly.assertThat(result.get(0).totalScore()).isEqualTo(450L);
});
}
}