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
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.jiwon.mylog.domain.post.controller;

import com.jiwon.mylog.domain.post.dto.response.PostNavigationResponse;
import com.jiwon.mylog.domain.post.service.PostService;
import com.jiwon.mylog.domain.post.service.PostViewService;
import com.jiwon.mylog.global.security.auth.annotation.AllUser;
Expand Down Expand Up @@ -122,6 +123,24 @@ public ResponseEntity<PageResponse> getPosts(
return new ResponseEntity<>(response, HttpStatus.OK);
}

@GetMapping("/posts/{postId}/navigation")
@Operation(
summary = "현재 게시글의 정보(카테고리 ID, 카테고리 내에서의 현재 page, offset)를 획득한다."
)
public ResponseEntity<PostNavigationResponse> getPostNavigation(@PathVariable("postId") Long postId) {
PostNavigationResponse response = postService.getPostNavigation(postId);
return ResponseEntity.ok(response);
}

@GetMapping("/categories/{categoryId}/posts")
public ResponseEntity<PageResponse> getCategorizedPosts(
@PathVariable("categoryId") Long categoryId,
@RequestParam Long userId,
@PageableDefault(size = 5, sort = "createdAt", direction = Direction.DESC) Pageable pageable) {
PageResponse response = postService.getCategorizedPosts(categoryId, userId, pageable);
return ResponseEntity.ok(response);
}

@GetMapping("/users/{userId}/categories/{categoryId}/posts")
@Operation(
summary = "특정 유저의 카테고리별 게시글 조회 (태그 필터링 및 키워드 검색 포함)",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class PostDetailResponse {
private CategoryResponse category;
private List<TagResponse> tags;
private List<CommentResponse> comments;
private RelatedPostResponse previousPost;
private RelatedPostResponse nextPost;

public PostDetailResponse(Long postId, String title, String content, String contentPreview, int views, PostStatus postStatus, Visibility visibility, LocalDateTime createdAt, boolean pinned, PostType type, UserResponse user, CategoryResponse category) {
this.postId = postId;
Expand Down Expand Up @@ -75,6 +77,11 @@ public static PostDetailResponse fromPost(Post post) {
.build();
}

public void setRelatedPosts(RelatedPostResponse previous, RelatedPostResponse next) {
this.previousPost = previous;
this.nextPost = next;
}

public void setViews(int views) {
this.views = views;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.jiwon.mylog.domain.post.dto.response;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Builder
@AllArgsConstructor
@Getter
public class PostNavigationResponse {
private final Long postId;
private final Long userId;
private final Long categoryId;
private final Long currentOffset;
private final Long currentPage;
private final Long totalPosts;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.jiwon.mylog.domain.post.dto.response;

import lombok.Builder;
import lombok.Getter;

import java.time.LocalDateTime;

@Getter
@Builder
public class RelatedPostResponse {
private final Long postId;
private final String title;
private final LocalDateTime createdAt;

public RelatedPostResponse(Long postId, String title, LocalDateTime createdAt) {
this.postId = postId;
this.title = title;
this.createdAt = createdAt;
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.jiwon.mylog.domain.post.repository;

import com.jiwon.mylog.domain.post.dto.response.PostDetailResponse;
import com.jiwon.mylog.domain.post.dto.response.PostNavigationResponse;
import com.jiwon.mylog.domain.post.dto.response.PostSummaryResponse;
import com.jiwon.mylog.domain.post.entity.Post;
import com.jiwon.mylog.domain.post.dto.response.RelatedPostResponse;
import com.jiwon.mylog.domain.user.dto.response.UserActivityResponse;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
Expand All @@ -19,4 +20,8 @@ public interface PostRepositoryCustom {
Optional<PostDetailResponse> findPostDetail(Long postId);

List<UserActivityResponse> findUserActivities(Long userId, LocalDate start, LocalDate end);

PostNavigationResponse findPostNavigation(Long postId);

Page<RelatedPostResponse> findCategorizedPosts(Long categoryId, Long userId, Pageable pageable);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@
import com.jiwon.mylog.domain.image.entity.QProfileImage;
import com.jiwon.mylog.domain.like.QLike;
import com.jiwon.mylog.domain.post.dto.response.PostDetailResponse;
import com.jiwon.mylog.domain.post.dto.response.PostNavigationResponse;
import com.jiwon.mylog.domain.post.dto.response.PostSummaryResponse;
import com.jiwon.mylog.domain.post.dto.response.RelatedPostResponse;
import com.jiwon.mylog.domain.post.entity.QPost;
import com.jiwon.mylog.domain.tag.dto.response.TagResponse;
import com.jiwon.mylog.domain.tag.entity.QPostTag;
Expand All @@ -17,6 +19,7 @@
import com.jiwon.mylog.domain.user.dto.response.UserSummaryResponse;
import com.jiwon.mylog.domain.user.entity.QUser;
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.Tuple;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.core.types.dsl.DateTemplate;
Expand All @@ -27,6 +30,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Repository;

Expand All @@ -47,6 +51,9 @@ public class PostRepositoryImpl implements PostRepositoryCustom {

private final JPAQueryFactory jpaQueryFactory;
private static final QPost POST = QPost.post;
private static final QPost PREV_POST = new QPost("prev");
private static final QPost NEXT_POST = new QPost("next");
private static final QPost SUB_POST = new QPost("sub");
private static final QUser USER = QUser.user;
private static final QProfileImage PROFILE_IMAGE = QProfileImage.profileImage;
private static final QCategory CATEGORY = QCategory.category;
Expand Down Expand Up @@ -153,7 +160,21 @@ public Optional<PostDetailResponse> findPostDetail(Long postId) {
return Optional.empty();
}

List<TagResponse> tags = jpaQueryFactory
List<TagResponse> tags = getTagsByPostId(postId);
List<CommentResponse> comments = getCommentsByPostId(postId);

Tuple postData = getPostData(postId);
RelatedPostResponse previousPost = findPreviousPost(postData);
RelatedPostResponse nextPost = findNextPost(postData);

postDetailResponse.setTagsAndComments(tags, comments);
postDetailResponse.setRelatedPosts(previousPost, nextPost);

return Optional.of(postDetailResponse);
}

private List<TagResponse> getTagsByPostId(Long postId) {
return jpaQueryFactory
.select(Projections.constructor(TagResponse.class,
TAG.id,
TAG.name
Expand All @@ -163,8 +184,10 @@ public Optional<PostDetailResponse> findPostDetail(Long postId) {
.where(POST_TAG.post.id.eq(postId))
.orderBy(POST_TAG.id.asc())
.fetch();
}

List<CommentResponse> comments = jpaQueryFactory
private List<CommentResponse> getCommentsByPostId(Long postId) {
return jpaQueryFactory
.select(Projections.constructor(CommentResponse.class,
COMMENT.id,
COMMENT.parent.id,
Expand All @@ -190,9 +213,138 @@ public Optional<PostDetailResponse> findPostDetail(Long postId) {
.where(COMMENT.post.id.eq(postId))
.orderBy(COMMENT.createdAt.desc())
.fetch();
}

postDetailResponse.setTagsAndComments(tags, comments);
return Optional.of(postDetailResponse);
@Override
public PostNavigationResponse findPostNavigation(Long postId) {

Tuple postData = getPostData(postId);
if (postData == null) {
return null;
}

Long defaultPageSize = 5L;

Long userId = postData.get(0, Long.class);
Long categoryId = postData.get(1, Long.class);
LocalDateTime createdAt = postData.get(2, LocalDateTime.class);

BooleanBuilder conditions = createCategorizedDefaultConditions(userId, categoryId);
Long total = createCountQuery(conditions);
Long offset = createCountQuery(conditions.and(POST.createdAt.gt(createdAt)));

long currentPage = offset / defaultPageSize;
long currentPageOffset = offset - (offset % defaultPageSize);

return PostNavigationResponse.builder()
.postId(postId)
.userId(userId)
.categoryId(categoryId)
.currentOffset(currentPageOffset)
.currentPage(currentPage)
.totalPosts(total)
.build();
}

@Override
public Page<RelatedPostResponse> findCategorizedPosts(Long categoryId, Long userId, Pageable pageable) {
BooleanBuilder conditions = createCategorizedDefaultConditions(userId, categoryId);
List<RelatedPostResponse> categorizedPosts =
createdCategorizedPostsQuery(conditions, pageable);
Long total = createCountQuery(conditions);
return new PageImpl<>(categorizedPosts, pageable, total);
}

private BooleanBuilder createCategorizedDefaultConditions(Long userId, Long categoryId) {
BooleanBuilder conditions = new BooleanBuilder()
.and(POST.user.id.eq(userId))
.and(POST.deletedAt.isNull());

if (categoryId != null && categoryId > 0L) {
conditions.and(POST.category.id.eq(categoryId));
} else {
conditions.and(POST.category.isNull());
}

return conditions;
}

private List<RelatedPostResponse> createdCategorizedPostsQuery(BooleanBuilder conditions, Pageable pageable) {
return jpaQueryFactory
.select(Projections.constructor(RelatedPostResponse.class,
POST.id, POST.title, POST.createdAt
)
)
.from(POST)
.where(conditions)
.orderBy(POST.createdAt.desc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.fetch();
}

private RelatedPostResponse findPreviousPost(Tuple currentPost) {

if (currentPost == null) {
return null;
}

Long userId = currentPost.get(0, Long.class);
Long categoryId = currentPost.get(1, Long.class);
LocalDateTime createdAt = currentPost.get(2, LocalDateTime.class);

BooleanBuilder conditions =
createCategorizedDefaultConditions(userId, categoryId)
.and(POST.createdAt.lt(createdAt));

return jpaQueryFactory
.select(Projections.constructor(RelatedPostResponse.class,
POST.id, POST.title, POST.createdAt
)
)
.from(POST)
.where(conditions)
.orderBy(POST.createdAt.desc())
.limit(1)
.fetchOne();
}

private RelatedPostResponse findNextPost(Tuple currentPost) {

if (currentPost == null) {
return null;
}

Long userId = currentPost.get(0, Long.class);
Long categoryId = currentPost.get(1, Long.class);
LocalDateTime createdAt = currentPost.get(2, LocalDateTime.class);

BooleanBuilder conditions =
createCategorizedDefaultConditions(userId, categoryId)
.and(POST.createdAt.gt(createdAt));

return jpaQueryFactory
.select(Projections.constructor(RelatedPostResponse.class,
POST.id, POST.title, POST.createdAt
)
)
.from(POST)
.where(conditions)
.orderBy(POST.createdAt.asc())
.limit(1)
.fetchOne();
}

private Tuple getPostData(Long postId) {
Tuple currentPost = jpaQueryFactory
.select(POST.user.id,
POST.category.id.coalesce(-1L),
POST.createdAt
)
.from(POST)
.where(POST.id.eq(postId))
.fetchOne();
return currentPost;
}

@Override
Expand Down
19 changes: 19 additions & 0 deletions src/main/java/com/jiwon/mylog/domain/post/service/PostService.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
import com.jiwon.mylog.domain.post.dto.response.MainPostResponse;
import com.jiwon.mylog.domain.post.dto.response.NoticePostResponse;
import com.jiwon.mylog.domain.post.dto.response.PostDetailResponse;
import com.jiwon.mylog.domain.post.dto.response.PostNavigationResponse;
import com.jiwon.mylog.domain.post.dto.response.RelatedPostResponse;
import com.jiwon.mylog.global.common.entity.PageResponse;
import com.jiwon.mylog.domain.post.dto.response.PostSummaryResponse;
import com.jiwon.mylog.domain.post.entity.Post;
Expand Down Expand Up @@ -196,6 +198,23 @@ public PageResponse getPosts(Pageable pageable) {
postPage.getTotalElements());
}

@Transactional(readOnly = true)
public PostNavigationResponse getPostNavigation(Long postId) {
return postRepository.findPostNavigation(postId);
}

@Transactional(readOnly = true)
public PageResponse getCategorizedPosts(Long categoryId, Long userId, Pageable pageable) {
Page<RelatedPostResponse> postPage = postRepository.findCategorizedPosts(categoryId, userId, pageable);
return PageResponse.from(
postPage.getContent(),
postPage.getNumber(),
postPage.getSize(),
postPage.getTotalPages(),
postPage.getTotalElements()
);
}

@Cacheable(value = "post::filter", keyGenerator = "postCacheKeyGenerator")
@Transactional(readOnly = true)
public PageResponse getFilteredPosts(
Expand Down