Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
4 changes: 4 additions & 0 deletions docs/api-specs/home-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
- `editorPicks`: 에디터 픽 배틀 목록
- `trendingBattles`: 트렌딩 배틀 목록
- `bestBattles`: 베스트 배틀 목록
- `mostDiscussedBattles`: 댓글 수가 많은 배틀 목록
- `todayQuizzes`: 오늘의 퀴즈 목록
- `todayVotes`: 오늘의 투표(Poll) 목록
- `newBattles`: 신규 배틀 목록
Expand Down Expand Up @@ -56,3 +57,6 @@

- 오늘의 퀴즈/투표는 서버에서 조회 및 정렬을 확정해 응답
- 옵션 순서는 `displayOrder -> label -> id` 기준 오름차순으로 고정
- `bestBattles[].badgeLabel`은 `BEST`, `trendingBattles[].badgeLabel`은 `TRENDING`, `mostDiscussedBattles[].badgeLabel`은 `최다 토론`이다.
- 배틀 카드 응답은 태그와 소요 시간(초)을 포함한다. 에디터 픽에도 `audioDuration`을 제공한다.
- `mostDiscussedBattles`는 공개 배틀 중 댓글 수 내림차순으로 최대 3건이며 댓글이 없는 배틀은 제외한다.
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,12 @@ public Battle toEntity(AdminBattleCreateRequest request, User admin) {
}

public TodayBattleResponse toTodayResponse(Battle battle, List<Tag> tags, List<BattleOption> options) {
return toTodayResponse(battle, tags, options,
battle.getCommentCount() == null ? 0L : battle.getCommentCount());
}

public TodayBattleResponse toTodayResponse(Battle battle, List<Tag> tags, List<BattleOption> options,
Long commentCount) {
return new TodayBattleResponse(
battle.getId(),
battle.getTitle(),
Expand All @@ -56,7 +62,8 @@ public TodayBattleResponse toTodayResponse(Battle battle, List<Tag> tags, List<B
battle.getTotalParticipantsCount() == null ? 0L : battle.getTotalParticipantsCount(),
battle.getAudioDuration() == null ? 0 : battle.getAudioDuration(),
toTagResponses(tags, null),
toTodayOptionResponses(options)
toTodayOptionResponses(options),
commentCount == null ? 0L : commentCount
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,13 @@ public record TodayBattleResponse(
Long participantsCount,
Integer audioDuration,
List<BattleTagResponse> tags,
List<TodayOptionResponse> options
) {}
List<TodayOptionResponse> options,
Long commentCount
) {
public TodayBattleResponse(Long battleId, String title, String summary, String thumbnailUrl,
Integer viewCount, Long participantsCount, Integer audioDuration,
List<BattleTagResponse> tags, List<TodayOptionResponse> options) {
this(battleId, title, summary, thumbnailUrl, viewCount, participantsCount, audioDuration,
tags, options, 0L);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,18 @@ public interface BattleRepository extends JpaRepository<Battle, Long> {
"ORDER BY (battle.totalParticipantsCount + (battle.commentCount * 5)) DESC")
List<Battle> findBestBattles(Pageable pageable);

@Query("""
SELECT battle, COUNT(comment) FROM Battle battle
LEFT JOIN Perspective perspective ON perspective.battle = battle
AND perspective.status = com.swyp.picke.domain.perspective.enums.PerspectiveStatus.PUBLISHED
LEFT JOIN PerspectiveComment comment ON comment.perspective = perspective AND comment.hidden = false
WHERE battle.status = 'PUBLISHED' AND battle.deletedAt IS NULL
GROUP BY battle
HAVING COUNT(comment) > 0
ORDER BY COUNT(comment) DESC, battle.totalParticipantsCount DESC, battle.createdAt DESC
""")
List<Object[]> findMostDiscussedBattles(Pageable pageable);

// 4. 오늘의 Pické
@Query("SELECT battle FROM Battle battle " +
"WHERE battle.targetDate = :today " +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ public interface BattleService {

List<TodayBattleResponse> getBestBattles();

List<TodayBattleResponse> getMostDiscussedBattles();

List<TodayBattleResponse> getTodayPicks();

List<TodayBattleResponse> getNewBattles(List<Long> excludeIds);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ public class BattleServiceImpl implements BattleService {
private static final int HOME_EDITOR_PICK_LIMIT = 10;
private static final int HOME_TRENDING_LIMIT = 4;
private static final int HOME_BEST_LIMIT = 3;
private static final int HOME_MOST_DISCUSSION_LIMIT = 3;
private static final int HOME_TODAY_PICK_LIMIT = 1;
private static final int HOME_NEW_LIMIT = 3;
private static final int PUBLISH_BATCH_SIZE = 100;
Expand Down Expand Up @@ -122,6 +123,15 @@ public List<TodayBattleResponse> getBestBattles() {
return loadBestBattles(HOME_BEST_LIMIT);
}

@Override
public List<TodayBattleResponse> getMostDiscussedBattles() {
List<Object[]> rows = battleRepository.findMostDiscussedBattles(PageRequest.of(0, HOME_MOST_DISCUSSION_LIMIT));
List<Battle> battles = rows.stream().map(row -> (Battle) row[0]).toList();
Map<Long, Long> commentCounts = rows.stream().collect(Collectors.toMap(
row -> ((Battle) row[0]).getId(), row -> ((Number) row[1]).longValue()));
return convertToTodayResponses(battles, commentCounts);
}

@Override
@Transactional
public List<TodayBattleResponse> getTodayPicks() {
Expand Down Expand Up @@ -640,6 +650,10 @@ public void afterCommit() {
}

private List<TodayBattleResponse> convertToTodayResponses(List<Battle> battles) {
return convertToTodayResponses(battles, Map.of());
}

private List<TodayBattleResponse> convertToTodayResponses(List<Battle> battles, Map<Long, Long> commentCountOverrides) {
if (battles == null || battles.isEmpty()) return Collections.emptyList();

Map<Long, List<BattleOption>> optionsMap = battleOptionRepository.findByBattleIn(battles)
Expand All @@ -654,7 +668,10 @@ private List<TodayBattleResponse> convertToTodayResponses(List<Battle> battles)
return battles.stream().map(battle -> {
List<Tag> tags = tagsMap.getOrDefault(battle.getId(), Collections.emptyList());
List<BattleOption> options = optionsMap.getOrDefault(battle.getId(), Collections.emptyList());
return battleConverter.toTodayResponse(battle, tags, options);
Long commentCount = commentCountOverrides.get(battle.getId());
return commentCount == null
? battleConverter.toTodayResponse(battle, tags, options)
: battleConverter.toTodayResponse(battle, tags, options, commentCount);
}).toList();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.List;

public record HomeBestBattleResponse(
String badgeLabel,
Long battleId,
String philosopherA,
String philosopherB,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public record HomeEditorPickResponse(
String title,
String summary,
List<BattleTagResponse> tags,
Integer viewCount
Integer viewCount,
Integer audioDuration
) {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.swyp.picke.domain.home.dto.response;

import com.swyp.picke.domain.battle.dto.response.BattleTagResponse;
import java.util.List;

public record HomeMostDiscussedBattleResponse(
String badgeLabel,
Long battleId,
String thumbnailUrl,
String title,
List<BattleTagResponse> tags,
Integer audioDuration,
Long commentCount,
Integer viewCount
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ public record HomeResponse(
List<HomeEditorPickResponse> editorPicks,
List<HomeTrendingResponse> trendingBattles,
List<HomeBestBattleResponse> bestBattles,
List<HomeMostDiscussedBattleResponse> mostDiscussedBattles,
List<HomeTodayQuizResponse> todayQuizzes,
List<HomeTodayVoteResponse> todayVotes,
List<HomeNewBattleResponse> newBattles
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.util.List;

public record HomeTrendingResponse(
String badgeLabel,
Long battleId,
String thumbnailUrl,
String title,
Expand Down
23 changes: 21 additions & 2 deletions src/main/java/com/swyp/picke/domain/home/service/HomeService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import com.swyp.picke.domain.home.dto.response.HomeBestBattleResponse;
import com.swyp.picke.domain.home.dto.response.HomeEditorPickResponse;
import com.swyp.picke.domain.home.dto.response.HomeNewBattleResponse;
import com.swyp.picke.domain.home.dto.response.HomeMostDiscussedBattleResponse;
import com.swyp.picke.domain.home.dto.response.HomeResponse;
import com.swyp.picke.domain.home.dto.response.HomeTodayQuizResponse;
import com.swyp.picke.domain.home.dto.response.HomeTodayVoteOptionResponse;
Expand Down Expand Up @@ -57,17 +58,19 @@ public HomeResponse getHome(Long userId) {
List<TodayBattleResponse> editorPickRaw = battleService.getEditorPicks();
List<TodayBattleResponse> trendingRaw = battleService.getTrendingBattles();
List<TodayBattleResponse> bestRaw = battleService.getBestBattles();
List<TodayBattleResponse> mostDiscussedRaw = battleService.getMostDiscussedBattles();
List<Quiz> quizRaw = quizService.getTodayPicks(HOME_TODAY_PICK_LIMIT);
List<Poll> pollRaw = pollService.getTodayPicks(HOME_TODAY_PICK_LIMIT);

List<Long> excludeIds = collectBattleIds(editorPickRaw, trendingRaw, bestRaw);
List<Long> excludeIds = collectBattleIds(editorPickRaw, trendingRaw, bestRaw, mostDiscussedRaw);
List<TodayBattleResponse> newRaw = battleService.getNewBattles(excludeIds);

return new HomeResponse(
newNotice,
editorPickRaw.stream().map(this::toEditorPick).toList(),
trendingRaw.stream().map(this::toTrending).toList(),
bestRaw.stream().map(this::toBestBattle).toList(),
mostDiscussedRaw.stream().map(this::toMostDiscussed).toList(),
quizRaw.stream().map(this::toTodayQuiz).toList(),
pollRaw.stream().map(this::toTodayVote).toList(),
newRaw.stream().map(this::toNewBattle).toList()
Expand All @@ -83,12 +86,14 @@ private HomeEditorPickResponse toEditorPick(TodayBattleResponse battle) {
battle.title(),
battle.summary(),
battle.tags(),
battle.viewCount()
battle.viewCount(),
battle.audioDuration()
);
}

private HomeTrendingResponse toTrending(TodayBattleResponse battle) {
return new HomeTrendingResponse(
"TRENDING",
battle.battleId(),
battle.thumbnailUrl(),
battle.title(),
Expand All @@ -100,6 +105,7 @@ private HomeTrendingResponse toTrending(TodayBattleResponse battle) {

private HomeBestBattleResponse toBestBattle(TodayBattleResponse battle) {
return new HomeBestBattleResponse(
"BEST",
battle.battleId(),
findOptionRepresentative(battle.options(), 0),
findOptionRepresentative(battle.options(), 1),
Expand All @@ -110,6 +116,19 @@ private HomeBestBattleResponse toBestBattle(TodayBattleResponse battle) {
);
}

private HomeMostDiscussedBattleResponse toMostDiscussed(TodayBattleResponse battle) {
return new HomeMostDiscussedBattleResponse(
"최다 토론",
battle.battleId(),
battle.thumbnailUrl(),
battle.title(),
battle.tags(),
battle.audioDuration(),
battle.commentCount(),
battle.viewCount()
);
}

private HomeTodayQuizResponse toTodayQuiz(Quiz quiz) {
List<QuizOption> options = quizService.getOptions(quiz);
long participantsCount = quizService.countVotes(quiz);
Expand Down
Loading