Skip to content

[feat] 최근 게시물 조회 API 추가#101

Merged
k3vin7 merged 3 commits intomainfrom
feat/recentPosts
Mar 8, 2026
Merged

[feat] 최근 게시물 조회 API 추가#101
k3vin7 merged 3 commits intomainfrom
feat/recentPosts

Conversation

@k3vin7
Copy link
Contributor

@k3vin7 k3vin7 commented Mar 8, 2026

Summary

  • GET /posts/recent 엔드포인트 추가 (7일 이내 발행 게시물 최신순 반환)
  • FreePost / StoryPost / CurationPost 타입별 분리 쿼리로 N+1 방지
  • 기존 getAllStoryPostsWithPaging primaryComposer fetchJoin 누락 버그 수정
  • 커서 기반 페이지네이션, 로그인/비로그인 모두 지원

Test plan

  • 7일 이내 PUBLISHED 게시물만 반환
  • DRAFT 게시물 제외
  • FreePost / StoryPost / CurationPost 모두 반환
  • StoryPost / CurationPost 응답에 primaryComposer 포함
  • 로그인 유저 isLiked, isScrapped 값 포함
  • 비로그인 유저 isLiked, isScrapped null

k3vin7 and others added 3 commits March 8, 2026 20:02
- GET /posts/recent 엔드포인트 추가
- 7일 이내 발행된 게시물을 커서 기반 페이지네이션으로 반환
- 좋아요/스크랩 여부 배치 조회로 N+1 방지

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
@gemini-code-assist
Copy link

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

이 PR은 최근 게시물을 조회하는 새로운 API 엔드포인트를 도입하여 사용자 경험을 향상시킵니다. 효율적인 데이터 조회를 위해 N+1 문제를 해결하고, 기존 스토리 게시물 조회 로직의 버그를 수정하여 시스템 안정성을 높였습니다. 로그인 여부와 관계없이 모든 사용자가 최신 게시물을 원활하게 탐색할 수 있도록 지원합니다.

Highlights

  • 최근 게시물 조회 API 추가: GET /posts/recent 엔드포인트를 추가하여 7일 이내 발행된 게시물을 최신순으로 반환합니다.
  • N+1 문제 방지: FreePost, StoryPost, CurationPost 타입별로 분리된 쿼리를 사용하여 N+1 문제를 방지했습니다.
  • 기존 버그 수정: 기존 getAllStoryPostsWithPaging 메서드에서 primaryComposer fetchJoin 누락 버그를 수정했습니다.
  • 페이지네이션 지원: 커서 기반 페이지네이션을 지원하며, 로그인 및 비로그인 사용자 모두에게 서비스를 제공합니다.

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Changelog
  • src/main/java/com/daramg/server/post/application/PostQueryService.java
    • getRecentPosts 메서드가 추가되어 최근 게시물 목록을 조회하고 사용자 좋아요/스크랩 여부를 처리합니다.
  • src/main/java/com/daramg/server/post/presentation/PostQueryController.java
    • GET /posts/recent 엔드포인트가 추가되어 PostQueryServicegetRecentPosts를 호출합니다.
  • src/main/java/com/daramg/server/post/repository/PostQueryRepository.java
    • getRecentPostsWithPaging 추상 메서드가 인터페이스에 추가되었습니다.
  • src/main/java/com/daramg/server/post/repository/PostQueryRepositoryImpl.java
    • getRecentPostsWithPaging 구현체가 추가되어 7일 이내의 게시물을 FreePost, StoryPost, CurationPost 타입별로 조회하고 병합하여 반환합니다.
    • getAllStoryPostsWithPaging 메서드에 primaryComposer에 대한 fetchJoin이 추가되었습니다.
  • src/test/java/com/daramg/server/post/application/PostQueryServiceTest.java
    • getRecentPosts 기능에 대한 @Nested 테스트 클래스가 추가되었으며, 7일 이내 게시물 반환, DRAFT 게시물 제외, 모든 게시물 타입 반환, primaryComposer 포함 여부, 로그인/비로그인 사용자의 좋아요/스크랩 정보 포함 여부 등을 검증합니다.
Activity
  • 이 PR에 대한 활동은 아직 없습니다.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@k3vin7 k3vin7 merged commit 676b0c7 into main Mar 8, 2026
1 check passed
Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This PR introduces an API to retrieve recent posts and addresses the N+1 problem in the existing story post retrieval logic. While the overall code is well-written with good test coverage, the getRecentPostsWithPaging method introduces a significant performance flaw that could lead to a Denial of Service (DoS) vulnerability. This method retrieves all matching records into memory before applying pagination, which can exhaust server resources under high load or an intentional attack. It is strongly recommended to refactor this to perform paging at the database level. Additionally, there's an opportunity to improve duplicate sorting logic using a Comparator.

Comment on lines +232 to +287
public List<Post> getRecentPostsWithPaging(PageRequestDto pageRequest) {
Instant sevenDaysAgo = Instant.now().minus(7, ChronoUnit.DAYS);

List<FreePost> freePosts = queryFactory
.selectFrom(freePost)
.leftJoin(freePost._super.user, user).fetchJoin()
.where(
freePost._super.isBlocked.isFalse()
.and(freePost._super.postStatus.eq(PostStatus.PUBLISHED))
.and(freePost._super.createdAt.goe(sevenDaysAgo))
)
.orderBy(freePost._super.createdAt.desc(), freePost._super.id.desc())
.fetch();

List<StoryPost> storyPosts = queryFactory
.selectFrom(storyPost)
.leftJoin(storyPost._super.user, user).fetchJoin()
.leftJoin(storyPost.primaryComposer).fetchJoin()
.where(
storyPost._super.isBlocked.isFalse()
.and(storyPost._super.postStatus.eq(PostStatus.PUBLISHED))
.and(storyPost._super.createdAt.goe(sevenDaysAgo))
)
.orderBy(storyPost._super.createdAt.desc(), storyPost._super.id.desc())
.fetch();

List<CurationPost> curationPosts = queryFactory
.selectFrom(curationPost)
.leftJoin(curationPost._super.user, user).fetchJoin()
.leftJoin(curationPost.primaryComposer).fetchJoin()
.where(
curationPost._super.isBlocked.isFalse()
.and(curationPost._super.postStatus.eq(PostStatus.PUBLISHED))
.and(curationPost._super.createdAt.goe(sevenDaysAgo))
)
.orderBy(curationPost._super.createdAt.desc(), curationPost._super.id.desc())
.fetch();

List<Post> allPosts = new ArrayList<>();
allPosts.addAll(freePosts);
allPosts.addAll(storyPosts);
allPosts.addAll(curationPosts);

allPosts.sort((p1, p2) -> {
int dateCompare = p2.getCreatedAt().compareTo(p1.getCreatedAt());
if (dateCompare != 0) return dateCompare;
return Long.compare(p2.getId(), p1.getId());
});

return pagingUtils.applyCursorPaginationToList(
allPosts,
pageRequest,
Post::getCreatedAt,
Post::getId
);
}

Choose a reason for hiding this comment

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

security-high high

The getRecentPostsWithPaging method fetches all posts from the last 7 days for each post type (FreePost, StoryPost, CurationPost) into memory using .fetch() without any database-level limit. This approach, where application-level sorting and paging are applied after loading all data, can lead to excessive memory consumption and CPU usage, potentially causing an OutOfMemoryError and crashing the application under high load. This constitutes a Denial of Service (DoS) vulnerability.

To remediate this, it is strongly recommended to perform pagination at the database level. A more scalable approach involves:

  1. Querying the base Post entity with cursor-based paging to retrieve a single page of Post entities.
  2. To avoid N+1 queries, group the returned Post entities by their specific type.
  3. For each type group, execute separate queries using WHERE id IN (...) and fetchJoin to retrieve associated relationships. This populates the persistence context, preventing additional queries for related data.

Additionally, the orderBy clauses currently used when fetching data for each post type are unnecessary, as sorting is re-applied in memory. Removing these can reduce database load.

Comment on lines +275 to +279
allPosts.sort((p1, p2) -> {
int dateCompare = p2.getCreatedAt().compareTo(p1.getCreatedAt());
if (dateCompare != 0) return dateCompare;
return Long.compare(p2.getId(), p1.getId());
});

Choose a reason for hiding this comment

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

medium

이 정렬 로직은 getPostsByComposerIdWithPaging 메서드(189-195행)에서도 동일하게 사용되고 있습니다. 코드 중복을 피하고 가독성을 높이기 위해 Comparator를 사용하는 것이 좋습니다. Comparator.comparing()thenComparing()을 연결하여 사용하면 더 선언적이고 읽기 좋은 코드를 작성할 수 있습니다.

        allPosts.sort(java.util.Comparator.comparing(Post::getCreatedAt, java.util.Comparator.reverseOrder())
                .thenComparing(Post::getId, java.util.Comparator.reverseOrder()));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant