Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,25 @@ record ClubFeedListResponse(
@Schema(description = "피드 썸네일 파일 이름", example = "filename.jpg")
String thumbnailFilename,
@Schema(description = "피드 타입", example = "IMAGE")
String feedType
String feedType,
@Schema(description = "조회수", example = "150")
long viewCount,
@Schema(description = "좋아요 수", example = "10")
long likeCount,
@Schema(description = "댓글 수", example = "5")
long commentCount
) {

public static ClubFeedListResponse from(FeedListQuery feedListQuery) {
return ClubFeedListResponse.builder()
.id(feedListQuery.id())
.thumbnailCdnUrl(feedListQuery.thumbnailCdnUrl())
.thumbnailOriginUrl(feedListQuery.thumbnailOriginUrl())
.thumbnailFilename(feedListQuery.thumbnailFileName())
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현 api 명세에 해당 필드는 제거해도 될 것 같아

.feedType(feedListQuery.feedType())
.viewCount(feedListQuery.viewCount())
.likeCount(feedListQuery.likeCount())
.commentCount(feedListQuery.commentCount())
.build();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,13 @@ public record FeedListResponse(
@Schema(description = "피드 썸네일 파일 이름", example = "filename.jpg")
String thumbnailFilename,
@Schema(description = "피드 타입", example = "IMAGE")
String feedType
String feedType,
@Schema(description = "조회수", example = "150")
long viewCount,
@Schema(description = "좋아요 수", example = "10")
long likeCount,
@Schema(description = "댓글 수", example = "5")
long commentCount
) {

public static FeedListResponse from(FeedListQuery query) {
Expand All @@ -45,6 +51,9 @@ public static FeedListResponse from(FeedListQuery query) {
.thumbnailCdnUrl(query.thumbnailCdnUrl())
.thumbnailFilename(query.thumbnailFileName())
.feedType(query.feedType())
.viewCount(query.viewCount())
.likeCount(query.likeCount())
.commentCount(query.commentCount())
.build();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package ddingdong.ddingdongBE.domain.feed.controller.dto.response;

import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedCommentQuery;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedQuery;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubProfileQuery;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedFileInfoQuery;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
import lombok.Builder;

@Builder
Expand All @@ -17,12 +21,18 @@ public record FeedResponse(
String feedType,
@Schema(description = "조회수", example = "150")
long viewCount,
@Schema(description = "좋아요 수", example = "10")
long likeCount,
@Schema(description = "댓글 수", example = "5")
long commentCount,
@Schema(description = "생성 날짜", example = "2024-08-31")
LocalDate createdDate,
@Schema(description = "URL 정보", implementation = FileUrlResponse.class)
FileUrlResponse fileUrls,
@Schema(description = "동아리 정보")
ClubProfileResponse clubProfile
ClubProfileResponse clubProfile,
@ArraySchema(schema = @Schema(description = "댓글 목록", implementation = CommentResponse.class))
List<CommentResponse> comments
) {

@Builder
Expand Down Expand Up @@ -73,15 +83,43 @@ public static FileUrlResponse from(FeedFileInfoQuery feedFileInfoQuery) {
}
}

@Builder
public record CommentResponse(
@Schema(description = "댓글 ID", example = "1")
Long id,
@Schema(description = "댓글 내용", example = "좋은 활동이네요!")
String content,
@Schema(description = "익명 이름", example = "익명1")
String anonymousName,
@Schema(description = "작성 일시")
LocalDateTime createdAt
) {

public static CommentResponse from(FeedCommentQuery query) {
return CommentResponse.builder()
.id(query.id())
.content(query.content())
.anonymousName(query.anonymousName())
.createdAt(query.createdAt())
.build();
}
}

public static FeedResponse from(FeedQuery query) {
List<CommentResponse> commentResponses = query.comments() != null
? query.comments().stream().map(CommentResponse::from).toList()
: List.of();
return FeedResponse.builder()
.id(query.id())
.clubProfile(ClubProfileResponse.from(query.clubProfileQuery()))
.activityContent(query.activityContent())
.fileUrls(FileUrlResponse.from(query.feedFileInfoQuery()))
.feedType(query.feedType())
.viewCount(query.viewCount())
.likeCount(query.likeCount())
.commentCount(query.commentCount())
.createdDate(query.createdDate())
.comments(commentResponses)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,16 @@
import java.util.List;
import lombok.Builder;

@Builder
public record MyFeedPageResponse(
@Schema(description = "총 피드 수", example = "15")
long feedCount,
@Schema(description = "총 조회수", example = "1200")
long totalViewCount,
@Schema(description = "이미지 피드 수", example = "10")
long imageCount,
@Schema(description = "영상 피드 수", example = "5")
long videoCount,
Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요구사항이 변경되어 해당 필드는 명세에서 삭제해도 될 것 같아

@ArraySchema(schema = @Schema(name = "동아리 피드 정보", implementation = MyFeedListResponse.class))
List<MyFeedListResponse> clubFeeds,
@Schema(name = "피드 페이지 정보", implementation = PagingResponse.class)
Expand All @@ -18,8 +27,14 @@ public static MyFeedPageResponse from(MyFeedPageQuery myFeedPageQuery) {
List<MyFeedListResponse> clubFeeds = myFeedPageQuery.feedListQueries().stream()
.map(MyFeedListResponse::from)
.toList();
return new MyFeedPageResponse(clubFeeds,
PagingResponse.from(myFeedPageQuery.pagingQuery()));
return MyFeedPageResponse.builder()
.feedCount(myFeedPageQuery.feedCount())
.totalViewCount(myFeedPageQuery.totalViewCount())
.imageCount(myFeedPageQuery.imageCount())
.videoCount(myFeedPageQuery.videoCount())
.clubFeeds(clubFeeds)
.pagingInfo(PagingResponse.from(myFeedPageQuery.pagingQuery()))
.build();
}

@Builder
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ddingdong.ddingdongBE.domain.feed.repository;

import ddingdong.ddingdongBE.domain.feed.entity.FeedComment;
import ddingdong.ddingdongBE.domain.feed.repository.dto.FeedCountDto;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
Expand All @@ -25,4 +26,13 @@ Optional<Integer> findAnonymousNumberByFeedIdAndUuid(
List<FeedComment> findAllByFeedIdOrderByCreatedAtAsc(Long feedId);

long countByFeedId(Long feedId);

@Query(value = """
SELECT fc.feed_id AS feedId, COUNT(*) AS cnt
FROM feed_comment fc
WHERE fc.feed_id IN (:feedIds)
AND fc.deleted_at IS NULL
GROUP BY fc.feed_id
""", nativeQuery = true)
List<FeedCountDto> countsByFeedIds(@Param("feedIds") List<Long> feedIds);
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
package ddingdong.ddingdongBE.domain.feed.repository;

import ddingdong.ddingdongBE.domain.feed.entity.FeedLike;
import ddingdong.ddingdongBE.domain.feed.repository.dto.FeedCountDto;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface FeedLikeRepository extends JpaRepository<FeedLike, Long> {

Expand All @@ -10,4 +14,12 @@ public interface FeedLikeRepository extends JpaRepository<FeedLike, Long> {
void deleteByFeedIdAndUuid(Long feedId, String uuid);

long countByFeedId(Long feedId);

@Query(value = """
SELECT fl.feed_id AS feedId, COUNT(*) AS cnt
FROM feed_like fl
WHERE fl.feed_id IN (:feedIds)
GROUP BY fl.feed_id
""", nativeQuery = true)
List<FeedCountDto> countsByFeedIds(@Param("feedIds") List<Long> feedIds);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import ddingdong.ddingdongBE.domain.feed.entity.Feed;
import ddingdong.ddingdongBE.domain.feed.repository.dto.MonthlyFeedRankingDto;
import ddingdong.ddingdongBE.domain.feed.repository.dto.MyFeedStatDto;
import java.util.List;
import org.springframework.data.domain.Slice;
import org.springframework.data.jpa.repository.JpaRepository;
Expand Down Expand Up @@ -99,4 +100,15 @@ List<MonthlyFeedRankingDto> findMonthlyRankingByClub(
@Param("month") int month
);

@Query(value = """
SELECT COUNT(f.id) AS feedCount,
COALESCE(SUM(f.view_count), 0) AS totalViewCount,
SUM(CASE WHEN f.feed_type = 'IMAGE' THEN 1 ELSE 0 END) AS imageCount,
SUM(CASE WHEN f.feed_type = 'VIDEO' THEN 1 ELSE 0 END) AS videoCount
FROM feed f
WHERE f.deleted_at IS NULL
AND f.club_id = :clubId
""", nativeQuery = true)
MyFeedStatDto findMyFeedStat(@Param("clubId") Long clubId);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ddingdong.ddingdongBE.domain.feed.repository.dto;

public interface FeedCountDto {

Long getFeedId();

Long getCnt();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ddingdong.ddingdongBE.domain.feed.repository.dto;

public interface MyFeedStatDto {

Long getFeedCount();

Long getTotalViewCount();

Long getImageCount();

Long getVideoCount();
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
import ddingdong.ddingdongBE.domain.club.entity.Club;
import ddingdong.ddingdongBE.domain.club.service.ClubService;
import ddingdong.ddingdongBE.domain.feed.entity.Feed;
import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository;
import ddingdong.ddingdongBE.domain.feed.repository.dto.MyFeedStatDto;
import ddingdong.ddingdongBE.domain.feed.service.dto.command.CreateFeedCommand;
import ddingdong.ddingdongBE.domain.feed.service.dto.command.UpdateFeedCommand;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedListQuery;
Expand All @@ -17,6 +19,7 @@
import ddingdong.ddingdongBE.sse.service.dto.SseEvent;
import ddingdong.ddingdongBE.sse.service.dto.SseVodProcessingNotificationDto;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
Expand All @@ -36,6 +39,7 @@ public class FacadeClubFeedServiceImpl implements FacadeClubFeedService {
private final VodProcessingJobService vodProcessingJobService;
private final SseConnectionService sseConnectionService;
private final FeedFileService feedFileService;
private final FeedRepository feedRepository;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

FeedRepository 직접 의존 — FeedService를 통해 접근해야 합니다.

FacadeFeedService와 동일한 문제입니다. findMyFeedStat()FeedService에 위임하면 Facade가 Repository에 직접 의존하지 않게 됩니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeClubFeedServiceImpl.java`
at line 42, Replace the direct FeedRepository dependency in
FacadeClubFeedServiceImpl with a FeedService dependency: remove or replace the
private final FeedRepository feedRepository field and constructor parameter with
FeedService feedService, and update all usages of
feedRepository.findMyFeedStat(...) to delegate to
feedService.findMyFeedStat(...). This keeps the facade from depending on the
repository directly and mirrors the fix already used in FacadeFeedService.


@Override
@Transactional
Expand Down Expand Up @@ -74,17 +78,18 @@ public void delete(Long feedId) {
@Override
public MyFeedPageQuery getMyFeedPage(User user, int size, Long currentCursorId) {
Club club = clubService.getByUserId(user.getId());
MyFeedStatDto stat = feedRepository.findMyFeedStat(club.getId());
Slice<Feed> feedPage = feedService.getFeedPageByClubId(club.getId(), size, currentCursorId);
if (feedPage == null) {
return MyFeedPageQuery.createEmpty();
return MyFeedPageQuery.of(stat, Collections.emptyList(), PagingQuery.createEmpty());
}
List<Feed> completeFeeds = feedPage.getContent();
List<FeedListQuery> feedListQueries = completeFeeds.stream()
.map(feedFileService::extractFeedThumbnailInfo)
.toList();
PagingQuery pagingQuery = PagingQuery.of(currentCursorId, completeFeeds, feedPage.hasNext());

return MyFeedPageQuery.of(feedListQueries, pagingQuery);
return MyFeedPageQuery.of(stat, feedListQueries, pagingQuery);
}

private void checkVodProcessingJobAndNotify(Feed feed) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
package ddingdong.ddingdongBE.domain.feed.service;

import ddingdong.ddingdongBE.domain.feed.entity.Feed;
import ddingdong.ddingdongBE.domain.feed.repository.FeedCommentRepository;
import ddingdong.ddingdongBE.domain.feed.repository.FeedLikeRepository;
import ddingdong.ddingdongBE.domain.feed.repository.dto.FeedCountDto;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubFeedPageQuery;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubProfileQuery;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedCommentQuery;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedFileInfoQuery;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedListQuery;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedQuery;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedPageQuery;
import ddingdong.ddingdongBE.domain.feed.service.dto.query.PagingQuery;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Slice;
import org.springframework.stereotype.Service;
Expand All @@ -21,16 +28,18 @@ public class FacadeFeedService {

private final FeedService feedService;
private final FeedFileService feedFileService;
private final FeedLikeService feedLikeService;
private final FeedCommentService feedCommentService;
private final FeedLikeRepository feedLikeRepository;
private final FeedCommentRepository feedCommentRepository;
Comment on lines +33 to +34
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major

Facade에서 Repository 직접 의존은 DDD 레이어링 위반입니다.

FacadeFeedServiceFeedLikeRepositoryFeedCommentRepository를 직접 주입받고 있습니다. Line 66-68의 상세 조회에서는 FeedLikeService/FeedCommentService를 통해 접근하면서, Line 82-85의 목록 조회에서는 Repository를 직접 사용하는 것은 일관성이 없습니다.

벌크 카운트 메서드(countsByFeedIds)를 각 도메인 서비스(FeedLikeService, FeedCommentService)로 위임하면 Repository 의존을 제거할 수 있습니다.

♻️ 제안
 public class FacadeFeedService {

     private final FeedService feedService;
     private final FeedFileService feedFileService;
     private final FeedLikeService feedLikeService;
     private final FeedCommentService feedCommentService;
-    private final FeedLikeRepository feedLikeRepository;
-    private final FeedCommentRepository feedCommentRepository;

그리고 FeedLikeService/FeedCommentService에 벌크 카운트 메서드를 추가:

// FeedLikeService
List<FeedCountDto> countsByFeedIds(List<Long> feedIds);

// FeedCommentService
List<FeedCountDto> countsByFeedIds(List<Long> feedIds);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedService.java`
around lines 33 - 34, FacadeFeedService currently depends directly on
FeedLikeRepository and FeedCommentRepository which breaks DDD layering and is
inconsistent with using FeedLikeService/FeedCommentService elsewhere; remove the
FeedLikeRepository and FeedCommentRepository fields from FacadeFeedService and
delegate the bulk count work to new methods countsByFeedIds(List<Long>) on
FeedLikeService and FeedCommentService (add these service methods and have them
call their repositories internally), then update the FacadeFeedService code
paths that used the repositories (the list retrieval around the existing
repository calls) to call FeedLikeService.countsByFeedIds(...) and
FeedCommentService.countsByFeedIds(...) instead.


public ClubFeedPageQuery getFeedPageByClub(Long clubId, int size, Long currentCursorId) {
Slice<Feed> feedPage = feedService.getFeedPageByClubId(clubId, size, currentCursorId);
if (feedPage == null) {
return ClubFeedPageQuery.createEmpty();
}
List<Feed> completeFeeds = feedPage.getContent();
List<FeedListQuery> feedListQueries = completeFeeds.stream()
.map(feedFileService::extractFeedThumbnailInfo)
.toList();
List<FeedListQuery> feedListQueries = buildFeedListQueriesWithCounts(completeFeeds);
PagingQuery pagingQuery = PagingQuery.of(currentCursorId, completeFeeds, feedPage.hasNext());

return ClubFeedPageQuery.of(feedListQueries, pagingQuery);
Expand All @@ -42,9 +51,7 @@ public FeedPageQuery getAllFeedPage(int size, Long currentCursorId) {
return FeedPageQuery.createEmpty();
}
List<Feed> completeFeeds = feedPage.getContent();

List<FeedListQuery> feedListQueries = completeFeeds.stream().map(feedFileService::extractFeedThumbnailInfo)
.toList();
List<FeedListQuery> feedListQueries = buildFeedListQueriesWithCounts(completeFeeds);
PagingQuery pagingQuery = PagingQuery.of(currentCursorId, completeFeeds, feedPage.hasNext());

return FeedPageQuery.of(feedListQueries, pagingQuery);
Expand All @@ -56,7 +63,39 @@ public FeedQuery getById(Long feedId) {
Feed feed = feedService.getById(feedId);
ClubProfileQuery clubProfileQuery = feedFileService.extractClubInfo(feed.getClub());
FeedFileInfoQuery feedFileInfoQuery = feedFileService.extractFeedFileInfo(feed);
return FeedQuery.of(feed, clubProfileQuery, feedFileInfoQuery);
long likeCount = feedLikeService.countByFeedId(feedId);
long commentCount = feedCommentService.countByFeedId(feedId);
List<FeedCommentQuery> comments = feedCommentService.getAllByFeedId(feedId);
return FeedQuery.of(feed, clubProfileQuery, feedFileInfoQuery, likeCount, commentCount, comments);
}

private List<FeedListQuery> buildFeedListQueriesWithCounts(List<Feed> feeds) {
List<FeedListQuery> feedListQueries = feeds.stream()
.map(feedFileService::extractFeedThumbnailInfo)
.toList();

List<Long> feedIds = feeds.stream().map(Feed::getId).toList();
if (feedIds.isEmpty()) {
return feedListQueries;
}

Map<Long, Long> likeCountMap = feedLikeRepository.countsByFeedIds(feedIds).stream()
.collect(Collectors.toMap(FeedCountDto::getFeedId, FeedCountDto::getCnt));
Map<Long, Long> commentCountMap = feedCommentRepository.countsByFeedIds(feedIds).stream()
.collect(Collectors.toMap(FeedCountDto::getFeedId, FeedCountDto::getCnt));

return feedListQueries.stream()
.map(q -> FeedListQuery.builder()
.id(q.id())
.thumbnailCdnUrl(q.thumbnailCdnUrl())
.thumbnailOriginUrl(q.thumbnailOriginUrl())
.feedType(q.feedType())
.thumbnailFileName(q.thumbnailFileName())
.viewCount(q.viewCount())
.likeCount(likeCountMap.getOrDefault(q.id(), 0L))
.commentCount(commentCountMap.getOrDefault(q.id(), 0L))
.build())
.toList();
}

}
Loading