Skip to content
Merged

ddd #80

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
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.example.cp_main_be.domain.social.feed.dto.response;

import com.example.cp_main_be.domain.social.avatarpost.domain.AvatarPost;
import com.example.cp_main_be.domain.social.diary.domain.Diary;
import com.fasterxml.jackson.annotation.JsonIgnore;
import java.time.LocalDateTime;

/**
* Diary, AvatarPost 등 다양한 타입의 게시물을 통합 피드에서 공통된 형식으로 표현하기 위한 DTO입니다. 이 하나의 클래스로 통합하여 관리의 용이성과 코드의
* 일관성을 높입니다.
*/
public record FeedResponse(
Long postId, PostType postType, String imageUrl, @JsonIgnore LocalDateTime createdAt) {

public static FeedResponse from(Diary diary) {
String imageUrl = diary.getDiaryImage() != null ? diary.getDiaryImage().getImageUrl() : null;
return new FeedResponse(diary.getId(), PostType.DIARY, imageUrl, diary.getCreatedAt());
}

public static FeedResponse from(AvatarPost avatarPost) {
// AvatarPost에 imageUrl 필드가 있다고 가정합니다. 필드명은 실제 코드에 맞게 수정해주세요.
return new FeedResponse(
avatarPost.getId(),
PostType.AVATAR_POST,
avatarPost.getImageUrl(),
avatarPost.getCreatedAt());
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.example.cp_main_be.domain.social.feed.dto.response;

public enum PostType {
DIARY,
AVATAR_POST
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
package com.example.cp_main_be.domain.social.feed.presentation;

import com.example.cp_main_be.domain.member.user.domain.User;
import com.example.cp_main_be.domain.social.feed.dto.response.FeedResponse;
import com.example.cp_main_be.domain.social.feed.service.FeedService;
import com.example.cp_main_be.global.common.ApiResponse;
import com.example.cp_main_be.global.dto.FeedItemResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import java.util.List;
Expand All @@ -25,12 +25,14 @@ public class FeedController {

@Operation(summary = "피드 조회", description = "통합 피드를 불러옵니다")
@GetMapping
public ResponseEntity<ApiResponse<List<FeedItemResponse>>> getFeed(
public ResponseEntity<ApiResponse<List<FeedResponse>>> getFeed(
@AuthenticationPrincipal User user, // 인증된 사용자 정보
@RequestParam(required = false) String filter) {
@RequestParam(required = false) String filter,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {

// 서비스 메서드에 현재 사용자의 UUID와 필터 값을 전달
List<FeedItemResponse> feedItems = feedService.getFeed(user.getUuid(), filter);
List<FeedResponse> feedItems = feedService.getFeed(user.getUuid(), filter, page, size);

// List<Object>가 아닌 List<FeedItemResponse>로 응답 타입을 명확히 합니다.
return ResponseEntity.ok(ApiResponse.success(feedItems));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,13 @@
import com.example.cp_main_be.domain.member.user.domain.User;
import com.example.cp_main_be.domain.member.user.domain.repository.UserRepository;
import com.example.cp_main_be.domain.member.userblock.UserBlockRepository;
import com.example.cp_main_be.domain.social.avatarpost.domain.AvatarPost;
import com.example.cp_main_be.domain.social.avatarpost.domain.repository.AvatarPostRepository;
import com.example.cp_main_be.domain.social.avatarpost.dto.AvatarPostFeedItemResponse;
import com.example.cp_main_be.domain.social.diary.domain.Diary;
import com.example.cp_main_be.domain.social.diary.domain.Repository.DiaryRepository;
import com.example.cp_main_be.domain.social.diary.dto.response.DiaryFeedItemResponse;
import com.example.cp_main_be.domain.social.feed.dto.response.FeedResponse;
import com.example.cp_main_be.domain.social.follow.domain.Follow;
import com.example.cp_main_be.domain.social.follow.domain.repository.FollowRepository;
import com.example.cp_main_be.global.dto.FeedItemResponse;
import com.example.cp_main_be.global.exception.UserNotFoundException;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.UUID;
Expand All @@ -35,8 +32,13 @@ public class FeedService {
private final AvatarPostRepository avatarPostRepository;
private final UserBlockRepository userBlockRepository;

public List<FeedItemResponse> getFeed(UUID currentUserUuid, String filter) {
Pageable pageable = PageRequest.of(0, 100, Sort.by(Sort.Direction.DESC, "createdAt"));
public List<FeedResponse> getFeed(UUID currentUserUuid, String filter, int page, int size) {
// 올바른 페이지네이션을 위해, 각 소스에서 요청된 페이지의 끝까지 데이터를 충분히 가져옵니다.
// 예: 2페이지(page=1) 20개(size=20) 요청 시, (1+1)*20=40개의 후보를 가져옵니다.
// 이는 메모리 사용량과 성능에 영향을 줄 수 있으므로, 매우 깊은 페이지네이션에는 다른 전략(커서 기반)이 더 좋습니다.
int limit = (page + 1) * size;
Pageable candidatePageable =
PageRequest.of(0, limit, Sort.by(Sort.Direction.DESC, "createdAt"));

User currentUser =
userRepository
Expand All @@ -46,38 +48,51 @@ public List<FeedItemResponse> getFeed(UUID currentUserUuid, String filter) {
// [추가] 1. 현재 사용자가 차단한 유저 ID 목록을 먼저 조회합니다.
List<Long> blockedUserIds = userBlockRepository.findBlockedUserIdsByBlocker(currentUser);

List<Diary> diaries;
List<AvatarPost> avatarPosts;
Stream<FeedResponse> diaryStream;
Stream<FeedResponse> avatarPostStream;

if ("following".equalsIgnoreCase(filter)) {
List<User> followingUsers =
followRepository.findByFollower(currentUser).stream().map(Follow::getFollowing).toList();

// [수정] 2. 차단된 유저를 제외하고 조회
diaries =
diaryRepository.findByUserInAndIsPublicIsTrueAndUser_IdNotIn(
followingUsers, blockedUserIds, pageable);
avatarPosts =
avatarPostRepository.findByUserInAndUser_IdNotIn(
followingUsers, blockedUserIds, pageable);
diaryStream =
diaryRepository
.findByUserInAndIsPublicIsTrueAndUser_IdNotIn(
followingUsers, blockedUserIds, candidatePageable)
.stream()
.map(FeedResponse::from);
avatarPostStream =
avatarPostRepository
.findByUserInAndUser_IdNotIn(followingUsers, blockedUserIds, candidatePageable)
.stream()
.map(FeedResponse::from);

} else {
// [수정] 2. 차단된 유저를 제외하고 조회
diaries = diaryRepository.findByIsPublicIsTrueAndUser_IdNotIn(blockedUserIds, pageable);
avatarPosts = avatarPostRepository.findAllByUser_IdNotIn(blockedUserIds, pageable);
diaryStream =
diaryRepository
.findByIsPublicIsTrueAndUser_IdNotIn(blockedUserIds, candidatePageable)
.stream()
.map(FeedResponse::from);
avatarPostStream =
avatarPostRepository.findAllByUser_IdNotIn(blockedUserIds, candidatePageable).stream()
.map(FeedResponse::from);
}

// 2. 각 게시물을 해당하는 DTO로 변환
Stream<DiaryFeedItemResponse> diaryStream = diaries.stream().map(DiaryFeedItemResponse::new);
Stream<AvatarPostFeedItemResponse> avatarPostStream =
avatarPosts.stream().map(AvatarPostFeedItemResponse::new);

// 3. 두 스트림을 하나로 합친 후, 생성 시간(createdAt) 기준으로 내림차순 정렬
List<FeedItemResponse> combinedFeed =
// 3. 두 스트림을 합치고, 전체 목록을 생성 시간 기준으로 다시 정렬합니다.
List<FeedResponse> sortedFeed =
Stream.concat(diaryStream, avatarPostStream)
.sorted(Comparator.comparing(FeedItemResponse::getCreatedAt).reversed())
.sorted(Comparator.comparing(FeedResponse::createdAt).reversed())
.toList();

return combinedFeed;
// 4. 정렬된 전체 목록에서 요청된 페이지에 해당하는 부분만 잘라내어 반환합니다.
int start = page * size;
if (start >= sortedFeed.size()) {
return Collections.emptyList(); // 요청된 페이지가 데이터 범위를 벗어난 경우 빈 리스트 반환
}
int end = Math.min(start + size, sortedFeed.size());

return sortedFeed.subList(start, end);
}
}
5 changes: 4 additions & 1 deletion src/main/resources/application.yml
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
server:
forward-headers-strategy: framework

spring:
profiles:
active: local
Expand Down Expand Up @@ -76,4 +79,4 @@ springdoc:
swagger-ui:
servers:
- url: https://api.napulnapul.com
description: Production Server
description: Production Server