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,18 +1,26 @@
package com.techfork.domain.post.controller;

import com.techfork.domain.post.dto.CompanyListResponse;
import com.techfork.domain.post.dto.PostListResponse;
import com.techfork.domain.post.enums.EPostSortType;
import com.techfork.domain.post.service.PostQueryService;
import com.techfork.global.common.code.SuccessCode;
import com.techfork.global.response.BaseResponse;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.List;

@Tag(name = "Post V2", description = "게시글 API V2")
@Slf4j
@RestController
Expand All @@ -36,4 +44,65 @@ public ResponseEntity<BaseResponse<CompanyListResponse>> getCompanies() {
CompanyListResponse response = postQueryService.getCompaniesV2();
return BaseResponse.of(SuccessCode.OK, response);
}

@Operation(
summary = "기업별 게시글 조회 (V2)",
description = """
여러 기업의 게시글을 무한 스크롤 방식으로 조회합니다.
companies 파라미터가 없으면 전체 게시글을 조회합니다.
초기에는 lastPublishedAt과 lastPostId를 빈 채로 호출하고,
페이징을 할 땐 lastPublishedAt과 lastPostId를 둘 다 동시에 보내주셔야 합니다.
페이징 관련 값은 응답으로 반환됩니다.
"""
)
@GetMapping("/by-company")
public ResponseEntity<BaseResponse<PostListResponse>> getPostsByCompany(
@Parameter(description = "회사명 필터 (선택, 없으면 전체 조회)")
@RequestParam(required = false) List<String> companies,

@Parameter(description = "마지막 게시글 발행시간 (커서 1, 선택)")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@RequestParam(required = false) LocalDateTime lastPublishedAt,

@Parameter(description = "마지막 게시글 ID (커서 2, 선택)")
@RequestParam(required = false) Long lastPostId,

@Parameter(description = "페이지 크기 (기본값: 20)")
@RequestParam(defaultValue = "20") int size
) {
PostListResponse response = postQueryService.getPostsByCompanyV2(companies, lastPublishedAt, lastPostId, size);
return BaseResponse.of(SuccessCode.OK, response);
}

@Operation(
summary = "최근 게시글 조회 (V2)",
description = """
최근 생성된 게시글을 무한 스크롤 방식으로 조회합니다.
sortBy로 정렬 기준을 선택할 수 있습니다.
- LATEST: publishedAt 기준 정렬, lastPublishedAt과 lastPostId 필요
- POPULAR: viewCount 기준 정렬, lastViewCount와 lastPostId 필요
초기 요청 시에는 커서 파라미터를 비워두고, 페이징 시 응답에서 받은 값을 모두 전달해주셔야 합니다.
"""
)
@GetMapping("/recent")
public ResponseEntity<BaseResponse<PostListResponse>> getRecentPosts(
@Parameter(description = "정렬 기준 (LATEST: 최신순, POPULAR: 인기순, 기본값: LATEST)")
@RequestParam(defaultValue = "LATEST") EPostSortType sortBy,

@Parameter(description = "마지막 게시글 조회수 (커서, POPULAR 정렬 시 필요)")
@RequestParam(required = false) Integer lastViewCount,

@Parameter(description = "마지막 게시글 발행시간 (커서, LATEST 정렬 시 필요)")
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME)
@RequestParam(required = false) LocalDateTime lastPublishedAt,

@Parameter(description = "마지막 게시글 ID (커서, 선택)")
@RequestParam(required = false) Long lastPostId,

@Parameter(description = "페이지 크기 (기본값: 20)")
@RequestParam(defaultValue = "20") int size
) {
PostListResponse response = postQueryService.getRecentPostsV2(sortBy, lastViewCount, lastPublishedAt, lastPostId, size);
return BaseResponse.of(SuccessCode.OK, response);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.techfork.domain.post.dto.*;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.util.List;

@Component
Expand All @@ -25,11 +26,22 @@ public PostListResponse toPostListResponse(List<PostInfoDto> posts, int requeste
boolean hasNext = posts.size() > requestedSize;
List<PostInfoDto> content = hasNext ? posts.subList(0, requestedSize) : posts;

Long lastPostId = content.isEmpty() ? null : content.get(content.size() - 1).id();
Long lastPostId = null;
Long lastViewCount = null;
LocalDateTime lastPublishedAt = null;

if (!content.isEmpty()) {
PostInfoDto lastPost = content.get(content.size() - 1);
lastPostId = lastPost.id();
lastViewCount = lastPost.viewCount();
lastPublishedAt = lastPost.publishedAt();
}

return PostListResponse.builder()
.posts(content)
.lastPostId(lastPostId)
.lastViewCount(lastViewCount)
.lastPublishedAt(lastPublishedAt)
.hasNext(hasNext)
.build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;

import java.time.LocalDateTime;
import java.util.List;

@Builder
@Schema(name = "PostListResponse")
public record PostListResponse(
List<PostInfoDto> posts,
Long lastPostId,
Long lastViewCount,
LocalDateTime lastPublishedAt,
boolean hasNext
) {
}
6 changes: 3 additions & 3 deletions src/main/java/com/techfork/domain/post/entity/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,9 @@

@Entity
@Table(name = "posts", indexes = {
@Index(name = "idx_post_published_at", columnList = "publishedAt"),
@Index(name = "idx_post_view_count_id", columnList = "viewCount, publishedAt"),
@Index(name = "idx_post_company_published_at", columnList = "company, publishedAt")
@Index(name = "idx_post_published_at_id", columnList = "publishedAt, id"),
@Index(name = "idx_post_view_count_id", columnList = "viewCount, id"),
@Index(name = "idx_post_company_published_at_id", columnList = "company, publishedAt, id")
})
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.techfork.domain.post.dto.PostInfoDto;
import com.techfork.domain.post.entity.Post;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
Expand Down Expand Up @@ -62,6 +63,20 @@ List<PostInfoDto> findByCompanyWithCursor(
Pageable pageable
);

@Query("""
SELECT new com.techfork.domain.post.dto.PostInfoDto(
p.id, p.title, p.company, p.url, p.logoUrl, p.publishedAt, p.viewCount, null)
FROM Post p
WHERE (:companies IS NULL OR p.company IN :companies)
AND (
:lastPublishedAt IS NULL OR
p.publishedAt < :lastPublishedAt OR
(p.publishedAt = :lastPublishedAt AND p.id < :lastPostId)
)
ORDER BY p.publishedAt DESC, p.id DESC
""")
List<PostInfoDto> findByCompanyNamesWithCursor(List<String> companies, LocalDateTime lastPublishedAt, Long lastPostId, PageRequest pageRequest);

@Query("""
SELECT new com.techfork.domain.post.dto.PostInfoDto(
p.id, p.title, p.company, p.url, p.logoUrl, p.publishedAt, p.viewCount, null)
Expand All @@ -74,6 +89,23 @@ List<PostInfoDto> findRecentPostsWithCursor(
Pageable pageable
);

@Query("""
SELECT new com.techfork.domain.post.dto.PostInfoDto(
p.id, p.title, p.company, p.url, p.logoUrl, p.publishedAt, p.viewCount, null)
FROM Post p
WHERE (
:lastPublishedAt IS NULL OR
p.publishedAt < :lastPublishedAt OR
(p.publishedAt = :lastPublishedAt AND p.id < :lastPostId)
)
ORDER BY p.publishedAt DESC, p.id DESC
""")
List<PostInfoDto> findRecentPostsWithCursorV2(
@Param("lastPublishedAt") LocalDateTime lastPublishedAt,
@Param("lastPostId") Long lastPostId,
Pageable pageable
);

@Query("""
SELECT new com.techfork.domain.post.dto.PostInfoDto(
p.id, p.title, p.company, p.url, p.logoUrl, p.publishedAt, p.viewCount, null)
Expand All @@ -86,6 +118,23 @@ List<PostInfoDto> findPopularPostsWithCursor(
Pageable pageable
);

@Query("""
SELECT new com.techfork.domain.post.dto.PostInfoDto(
p.id, p.title, p.company, p.url, p.logoUrl, p.publishedAt, p.viewCount, null)
FROM Post p
WHERE (
:lastViewCount IS NULL OR
p.viewCount < :lastViewCount OR
(p.viewCount = :lastViewCount AND p.id < :lastPostId)
)
ORDER BY p.viewCount DESC, p.id DESC
""")
List<PostInfoDto> findPopularPostsWithCursorV2(
@Param("lastViewCount") Integer lastViewCount,
@Param("lastPostId") Long lastPostId,
Pageable pageable
);

@Query("""
SELECT new com.techfork.domain.post.dto.PostDetailDto(
p.id, p.title, p.summary, p.company, p.url, p.logoUrl, p.publishedAt, p.viewCount, null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -45,6 +46,13 @@ public PostListResponse getPostsByCompany(String company, Long lastPostId, int s
return postConverter.toPostListResponse(postsWithKeywords, size);
}

public PostListResponse getPostsByCompanyV2(List<String> companies, LocalDateTime lastPublishedAt, Long lastPostId, int size) {
PageRequest pageRequest = PageRequest.of(0, size + 1);
List<PostInfoDto> posts = postRepository.findByCompanyNamesWithCursor(companies, lastPublishedAt, lastPostId, pageRequest);
List<PostInfoDto> postsWithKeywords = attachKeywordsToPostInfoList(posts);
return postConverter.toPostListResponse(postsWithKeywords, size);
}

public PostListResponse getRecentPosts(EPostSortType sortBy, Long lastPostId, int size) {
PageRequest pageRequest = PageRequest.of(0, size + 1);
List<PostInfoDto> posts;
Expand All @@ -59,6 +67,20 @@ public PostListResponse getRecentPosts(EPostSortType sortBy, Long lastPostId, in
return postConverter.toPostListResponse(postsWithKeywords, size);
}

public PostListResponse getRecentPostsV2(EPostSortType sortBy, Integer lastViewCount, LocalDateTime lastPublishedAt, Long lastPostId, int size) {
PageRequest pageRequest = PageRequest.of(0, size + 1);
List<PostInfoDto> posts;

if (sortBy == EPostSortType.POPULAR) {
posts = postRepository.findPopularPostsWithCursorV2(lastViewCount, lastPostId, pageRequest);
} else {
posts = postRepository.findRecentPostsWithCursorV2(lastPublishedAt, lastPostId, pageRequest);
}

List<PostInfoDto> postsWithKeywords = attachKeywordsToPostInfoList(posts);
return postConverter.toPostListResponse(postsWithKeywords, size);
}

public PostDetailDto getPostDetail(Long postId) {
PostDetailDto postDetail = postRepository.findByIdWithTechBlog(postId)
.orElseThrow(() -> new GeneralException(CommonErrorCode.NOT_FOUND));
Expand Down
Loading
Loading