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
14 changes: 14 additions & 0 deletions src/docs/asciidoc/posts.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,20 @@ operation::post-controller-test/create-post[snippets='http-request,curl-request,
[[게시글-상세-조회]]
=== `GET` 게시글 상세 조회

공유 키 관련
|===
|공개범위(scope)|유저|shareKey 일치 여부|응답 여부

|PUBLIC|작성자|O|O
|PUBLIC|작성자|X|O
|PUBLIC|다른 사용자|O|O
|PUBLIC|다른 사용자|X|O
|PRIVATE|작성자|O|O
|PRIVATE|작성자|X|O
|PRIVATE|다른 사용자|O|O
|PRIVATE|다른 사용자|X|400에러
|===

operation::post-controller-test/find-post[snippets='http-request,curl-request,path-parameters,http-response,response-fields']

[[개사굴-공유-url-조회]]
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/chooz/common/exception/ErrorCode.java
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public enum ErrorCode {
INVALID_ONBOARDING_STEP("유효하지 않은 온보딩 단계."),
NICKNAME_LENGTH_EXCEEDED("닉네임 길이 초과"),
NOTIFICATION_NOT_FOUND("존재하지 않는 알림 입니다."),
POST_NOT_REVEALABLE("공개 불가능한 게시글입니다."),

//401
EXPIRED_TOKEN("토큰이 만료됐습니다."),
Expand Down
120 changes: 120 additions & 0 deletions src/main/java/com/chooz/post/application/MyPagePostManager.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package com.chooz.post.application;

import com.chooz.common.dto.CursorBasePaginatedResponse;
import com.chooz.post.application.dto.PollChoiceVoteInfo;
import com.chooz.post.application.dto.PostWithVoteCount;
import com.chooz.post.domain.PollChoiceRepository;
import com.chooz.post.domain.PostRepository;
import com.chooz.post.presentation.dto.MostVotedPollChoiceDto;
import com.chooz.post.presentation.dto.MyPagePostResponse;
import com.chooz.vote.application.RatioCalculator;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Component;

import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Component
@RequiredArgsConstructor
public class MyPagePostManager {

private final PostRepository postRepository;
private final PollChoiceRepository pollChoiceRepository;
private final RatioCalculator ratioCalculator;

public CursorBasePaginatedResponse<MyPagePostResponse> getUserPosts(
Long userId,
Long myPageUserId,
Long cursor,
Pageable pageable
) {
Slice<PostWithVoteCount> postSlice = postRepository.findPostsWithVoteCountByUserId(
userId,
myPageUserId,
cursor,
pageable
);

return getMyPageCursoredResponse(postSlice);
}

public CursorBasePaginatedResponse<MyPagePostResponse> getVotedPosts(
Long userId,
Long myPageUserId,
Long cursor,
Pageable pageable
) {
Slice<PostWithVoteCount> postSlice = postRepository.findVotedPostsWithVoteCount(
userId,
myPageUserId,
cursor,
pageable
);

return getMyPageCursoredResponse(postSlice);
}

private CursorBasePaginatedResponse<MyPagePostResponse> getMyPageCursoredResponse(Slice<PostWithVoteCount> postSlice) {
if (postSlice.isEmpty()) {
return CursorBasePaginatedResponse.of(new SliceImpl<>(
List.of(),
postSlice.getPageable(),
false
));
}

List<Long> postIds = getPostIds(postSlice);
Map<Long, PollChoiceVoteInfo> mostVotedPollChoiceByPostId = getMostVotedPollChoiceByPostId(postIds);

List<MyPagePostResponse> responses = getMyPagePostResponses(postSlice, mostVotedPollChoiceByPostId);

Copy link
Contributor

Choose a reason for hiding this comment

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

이부분 리스트라 다른 명명규칙 썼던 것처럼 복수로 하는게 좋을거 같습니다!
그리고, getMyPagePostResponses에서 필터에 걸러지면 postSlice hasNext랑 달라지는 현상이 생기진 않을까요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오 그 부분은 생각 못했는데 감사합니다! 수정했습니다

return CursorBasePaginatedResponse.of(new SliceImpl<>(
responses,
postSlice.getPageable(),
postSlice.hasNext()
));
}

private Map<Long, PollChoiceVoteInfo> getMostVotedPollChoiceByPostId(List<Long> postIds) {
List<PollChoiceVoteInfo> pollChoiceWithVoteInfo = pollChoiceRepository.findPollChoiceWithVoteInfo(postIds);
return pollChoiceWithVoteInfo.stream()
.collect(Collectors.groupingBy(
PollChoiceVoteInfo::postId,
Collectors.collectingAndThen(
Collectors.toList(),
choices -> choices.stream()
.max(Comparator.comparing(PollChoiceVoteInfo::voteCounts))
.orElse(null)
)
));
}

private List<MyPagePostResponse> getMyPagePostResponses(
Slice<PostWithVoteCount> postSlice,
Map<Long, PollChoiceVoteInfo> mostVotedPollChoiceByPostId
) {
return postSlice.getContent()
.stream()
.map(postWithVoteCount -> {
var pollChoiceVoteInfo = mostVotedPollChoiceByPostId.get(postWithVoteCount.post().getId());
var mostVotedPollChoiceInfo = MostVotedPollChoiceDto.of(
pollChoiceVoteInfo,
ratioCalculator.calculate(postWithVoteCount.voteCount(), pollChoiceVoteInfo.voteCounts())
);
return MyPagePostResponse.of(postWithVoteCount, mostVotedPollChoiceInfo);
})
.toList();
}

private List<Long> getPostIds(Slice<PostWithVoteCount> postSlice) {
return postSlice.getContent()
.stream()
.map(postWithVoteCount -> postWithVoteCount.post().getId())
.toList();
}
}
100 changes: 21 additions & 79 deletions src/main/java/com/chooz/post/application/PostQueryService.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,60 +4,53 @@
import com.chooz.common.dto.CursorBasePaginatedResponse;
import com.chooz.common.exception.BadRequestException;
import com.chooz.common.exception.ErrorCode;
import com.chooz.post.application.dto.PollChoiceVoteInfo;
import com.chooz.post.application.dto.PostWithVoteCount;
import com.chooz.post.application.dto.FeedDto;
import com.chooz.post.domain.PollChoice;
import com.chooz.post.domain.PollChoiceRepository;
import com.chooz.post.domain.Post;
import com.chooz.post.domain.PostRepository;
import com.chooz.post.presentation.UpdatePostResponse;
import com.chooz.post.presentation.dto.AuthorDto;
import com.chooz.post.application.dto.FeedDto;
import com.chooz.post.presentation.dto.FeedResponse;
import com.chooz.post.presentation.dto.MostVotedPollChoiceDto;
import com.chooz.post.presentation.dto.MyPagePostResponse;
import com.chooz.post.presentation.dto.PollChoiceVoteResponse;
import com.chooz.post.presentation.dto.PostResponse;
import com.chooz.post.presentation.dto.UpdatePostResponse;
import com.chooz.user.domain.User;
import com.chooz.user.domain.UserRepository;
import com.chooz.vote.application.RatioCalculator;
import com.chooz.vote.domain.Vote;
import com.chooz.vote.domain.VoteRepository;
import jakarta.annotation.Nullable;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class PostQueryService {

private final PostRepository postRepository;
private final PollChoiceRepository pollChoiceRepository;
private final UserRepository userRepository;
private final VoteRepository voteRepository;
private final CommentRepository commentRepository;
private final RatioCalculator ratioCalculator;
private final MyPagePostManager myPagePostManager;

public PostResponse findByShareUrl(Long userId, String shareUrl) {
Post post = postRepository.findByShareUrlFetchPollChoices(shareUrl)
.orElseThrow(() -> new BadRequestException(ErrorCode.POST_NOT_FOUND));
return createPostResponse(userId, post);
}

public PostResponse findById(Long userId, Long postId) {
public PostResponse findById(Long userId, Long postId, @Nullable String shareKey) {
Post post = postRepository.findByIdFetchPollChoices(postId)
.orElseThrow(() -> new BadRequestException(ErrorCode.POST_NOT_FOUND));
if (!post.isRevealable(userId, shareKey)) {
throw new BadRequestException(ErrorCode.POST_NOT_REVEALABLE);
}
return createPostResponse(userId, post);
}

Expand Down Expand Up @@ -100,73 +93,22 @@ private Long getVoteId(List<Vote> voteList, Long pollChoiceId, Long userId) {
.orElse(null);
}

public CursorBasePaginatedResponse<MyPagePostResponse> findUserPosts(Long userId, Long cursor, int size) {
Slice<PostWithVoteCount> postSlice = postRepository.findPostsWithVoteCountByUserId(userId, cursor, Pageable.ofSize(size));

return getCursorPaginatedResponse(postSlice);
}

public CursorBasePaginatedResponse<MyPagePostResponse> findVotedPosts(Long userId, Long cursor, int size) {
Slice<PostWithVoteCount> postSlice = postRepository.findVotedPostsWithVoteCount(userId, cursor, Pageable.ofSize(size));

return getCursorPaginatedResponse(postSlice);
}

private CursorBasePaginatedResponse<MyPagePostResponse> getCursorPaginatedResponse(Slice<PostWithVoteCount> postSlice) {
if (postSlice.isEmpty()) {
return CursorBasePaginatedResponse.of(new SliceImpl<>(
List.of(),
postSlice.getPageable(),
false
));
}

Map<Long, PollChoiceVoteInfo> mostVotedPollChoiceByPostId = getMostVotedPollChoiceByPostId(getPostIds(postSlice));

List<MyPagePostResponse> response = getMyPagePostResponses(postSlice, mostVotedPollChoiceByPostId);

return CursorBasePaginatedResponse.of(new SliceImpl<>(
response,
postSlice.getPageable(),
postSlice.hasNext()
));
}

private List<MyPagePostResponse> getMyPagePostResponses(
Slice<PostWithVoteCount> postSlice,
Map<Long, PollChoiceVoteInfo> mostVotedPollChoiceByPostId
public CursorBasePaginatedResponse<MyPagePostResponse> findUserPosts(
Long userId,
Long myPageUserId,
Long cursor,
int size
) {
return postSlice.getContent().stream()
.map(postWithVoteCount -> {
var pollChoiceVoteInfo = mostVotedPollChoiceByPostId.get(postWithVoteCount.post().getId());
var mostVotedPollChoiceInfo = MostVotedPollChoiceDto.of(
pollChoiceVoteInfo,
ratioCalculator.calculate(postWithVoteCount.voteCount(), pollChoiceVoteInfo.voteCounts())
);
return MyPagePostResponse.of(postWithVoteCount, mostVotedPollChoiceInfo);
})
.toList();
}

private Map<Long, PollChoiceVoteInfo> getMostVotedPollChoiceByPostId(List<Long> postIds) {
List<PollChoiceVoteInfo> pollChoiceWithVoteInfo = pollChoiceRepository.findPollChoiceWithVoteInfo(postIds);
return pollChoiceWithVoteInfo.stream()
.collect(Collectors.groupingBy(
PollChoiceVoteInfo::postId,
Collectors.collectingAndThen(
Collectors.toList(),
choices -> choices.stream()
.max(Comparator.comparing(PollChoiceVoteInfo::voteCounts))
.orElse(null)
)
));
return myPagePostManager.getUserPosts(userId, myPageUserId, cursor, Pageable.ofSize(size));
}

private List<Long> getPostIds(Slice<PostWithVoteCount> postSlice) {
return postSlice.getContent()
.stream()
.map(postWithVoteCount -> postWithVoteCount.post().getId())
.toList();
public CursorBasePaginatedResponse<MyPagePostResponse> findVotedPosts(
Long userId,
Long myPageUserId,
Long cursor,
int size
) {
return myPagePostManager.getVotedPosts(userId, myPageUserId, cursor, Pageable.ofSize(size));
}

public CursorBasePaginatedResponse<FeedResponse> findFeed(Long userId, Long cursor, int size) {
Expand Down
24 changes: 17 additions & 7 deletions src/main/java/com/chooz/post/application/PostService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.chooz.post.application;

import com.chooz.common.dto.CursorBasePaginatedResponse;
import com.chooz.post.presentation.UpdatePostResponse;
import com.chooz.post.presentation.dto.UpdatePostResponse;
import com.chooz.post.presentation.dto.CreatePostRequest;
import com.chooz.post.presentation.dto.CreatePostResponse;
import com.chooz.post.presentation.dto.FeedResponse;
Expand Down Expand Up @@ -40,16 +40,26 @@ public void update(Long userId, Long postId, UpdatePostRequest request) {
postCommandService.update(userId, postId, request);
}

public PostResponse findById(Long userId, Long postId) {
return postQueryService.findById(userId, postId);
public PostResponse findById(Long userId, Long postId, String shareKey) {
return postQueryService.findById(userId, postId, shareKey);
}

public CursorBasePaginatedResponse<MyPagePostResponse> findUserPosts(Long userId, Long cursor, int size) {
return postQueryService.findUserPosts(userId, cursor, size);
public CursorBasePaginatedResponse<MyPagePostResponse> findUserPosts(
Long userId,
Long myPageUserId,
Long cursor,
int size
) {
return postQueryService.findUserPosts(userId, myPageUserId, cursor, size);
}

public CursorBasePaginatedResponse<MyPagePostResponse> findVotedPosts(Long userId, Long cursor, int size) {
return postQueryService.findVotedPosts(userId, cursor, size);
public CursorBasePaginatedResponse<MyPagePostResponse> findVotedPosts(
Long userId,
Long myPageUserId,
Long cursor,
int size
) {
return postQueryService.findVotedPosts(userId, myPageUserId, cursor, size);
}

public PostResponse findByShareUrl(Long userId, String shareUrl) {
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/com/chooz/post/domain/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -222,4 +222,13 @@ public void delete(Long userId) {
}
this.delete();
}

public boolean isRevealable(Long userId) {
return this.pollOption.getScope().equals(Scope.PUBLIC) ||
this.userId.equals(userId);
}

public boolean isRevealable(Long userId, String shareUrl) {
return isRevealable(userId) || this.shareUrl.equals(shareUrl);
}
}
4 changes: 2 additions & 2 deletions src/main/java/com/chooz/post/domain/PostRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ public interface PostRepository {

Optional<CommentActive> findCommentActiveByPostId(Long postId);

Slice<PostWithVoteCount> findPostsWithVoteCountByUserId(Long userId, Long postId, Pageable pageable);
Slice<PostWithVoteCount> findPostsWithVoteCountByUserId(Long userId, Long authorId, Long postId, Pageable pageable);

Slice<PostWithVoteCount> findVotedPostsWithVoteCount(Long userId, Long postId, Pageable pageable);
Slice<PostWithVoteCount> findVotedPostsWithVoteCount(Long userId, Long authorId, Long postId, Pageable pageable);

Optional<Post> findByIdAndUserId(Long postId, Long userId);
}
Loading