-
Notifications
You must be signed in to change notification settings - Fork 0
feat: 아티클 카테고리 조회 구현 #27
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c504a7c
3acbcf6
f5e7906
3f05983
07587c3
0b27013
b5ffe7d
03a940f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| package com.pinback.pinback_server.domain.article.domain.repository.dto; | ||
|
|
||
| import org.springframework.data.domain.Page; | ||
|
|
||
| import com.pinback.pinback_server.domain.article.domain.entity.Article; | ||
| import com.querydsl.core.annotations.QueryProjection; | ||
|
|
||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
|
|
||
| @Getter | ||
| @NoArgsConstructor | ||
| public class ArticlesWithUnreadCount { | ||
| private Long unReadCount; | ||
| private Page<Article> article; | ||
|
|
||
| @QueryProjection | ||
| public ArticlesWithUnreadCount(Long unReadCount, Page<Article> article) { | ||
| this.unReadCount = unReadCount; | ||
| this.article = article; | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -45,4 +45,14 @@ public ResponseDto<ArticleAllResponse> getAll(@CurrentUser User user, @RequestPa | |||||||||||||||||||||||||||||||||||||
| return ResponseDto.ok(response); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @GetMapping("/{categoryId}") | ||||||||||||||||||||||||||||||||||||||
| public ResponseDto<ArticleAllResponse> getAllByCategory(@CurrentUser User user, @RequestParam Long categoryId, | ||||||||||||||||||||||||||||||||||||||
| @RequestParam int pageNumber, | ||||||||||||||||||||||||||||||||||||||
| @RequestParam int pageSize) { | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| ArticleAllResponse response = articleManagementUsecase.getAllArticlesByCategory(user, categoryId, pageNumber, | ||||||||||||||||||||||||||||||||||||||
| pageSize); | ||||||||||||||||||||||||||||||||||||||
| return ResponseDto.ok(response); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+48
to
+56
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix annotation mismatch for categoryId parameter. The endpoint path uses - public ResponseDto<ArticleAllResponse> getAllByCategory(@CurrentUser User user, @RequestParam Long categoryId,
+ public ResponseDto<ArticleAllResponse> getAllByCategory(@CurrentUser User user, @PathVariable Long categoryId,📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -116,6 +116,7 @@ void getAllArticle() { | |
|
|
||
| //then | ||
| assertThat(responses.articles()).hasSize(5); | ||
| assertThat(responses.articles().get(0).createdAt()).isNotNull(); | ||
| assertThat(responses.totalArticle()).isEqualTo(12); | ||
|
|
||
| } | ||
|
|
@@ -141,4 +142,97 @@ void getAllArticleOrderByCreatedAtDesc() { | |
| .isSortedAccordingTo(Comparator.reverseOrder()); | ||
| } | ||
|
|
||
| @DisplayName("읽지 않은 게시글 수도 알려주어야 한다.") | ||
| @Test | ||
| void getAllWithUnReadArticles() { | ||
| //given | ||
| User user = userRepository.save(user()); | ||
| Category category = categoryRepository.save(category(user)); | ||
|
|
||
| for (int i = 0; i < 5; i++) { | ||
| articleRepository.save(article(user, "article" + i, category)); | ||
| } | ||
|
|
||
| for (int i = 0; i < 3; i++) { | ||
| articleRepository.save(readArticle(user, "article2" + i, category)); | ||
| } | ||
|
|
||
| //when | ||
| ArticleAllResponse responses = articleManagementUsecase.getAllArticles(user, 0, 8); | ||
|
|
||
| //then | ||
| assertThat(responses.articles()) | ||
| .hasSize(8) | ||
| .extracting(ArticlesResponse::articleId) | ||
| .isSortedAccordingTo(Comparator.reverseOrder()); | ||
|
|
||
| assertThat(responses.totalUnreadArticle()) | ||
| .isEqualTo(5); | ||
| } | ||
|
|
||
| @DisplayName("카테고리 별로 게시글을 조회할 수 있다.") | ||
| @Test | ||
| void getByCategory() { | ||
| //given | ||
| User user = userRepository.save(user()); | ||
| Category category = categoryRepository.save(category(user)); | ||
| Category category2 = categoryRepository.save(category(user)); | ||
|
|
||
| for (int i = 0; i < 5; i++) { | ||
| articleRepository.save(article(user, "article" + i, category)); | ||
| } | ||
|
|
||
| for (int i = 0; i < 5; i++) { | ||
| articleRepository.save(article(user, "article2" + i, category2)); | ||
| } | ||
|
|
||
| //when | ||
|
|
||
| ArticleAllResponse responses = articleManagementUsecase.getAllArticlesByCategory(user, category.getId(), 0, 5); | ||
|
|
||
| //then | ||
|
|
||
| assertThat(responses.articles()) | ||
| .hasSize(5) | ||
| .extracting(ArticlesResponse::articleId) | ||
| .isSortedAccordingTo(Comparator.reverseOrder()); | ||
|
|
||
| assertThat(responses.totalArticle()) | ||
| .isEqualTo(5); | ||
|
|
||
| } | ||
|
|
||
| @DisplayName("카테고리 별로 게시글을 조회할 수 있다.") | ||
| @Test | ||
| void getByCategoryWithUnreadCount() { | ||
| //given | ||
| User user = userRepository.save(user()); | ||
| Category category = categoryRepository.save(category(user)); | ||
| Category category2 = categoryRepository.save(category(user)); | ||
|
|
||
| for (int i = 0; i < 3; i++) { | ||
| articleRepository.save(article(user, "article" + i, category)); | ||
| } | ||
|
|
||
| for (int i = 0; i < 5; i++) { | ||
| articleRepository.save(article(user, "article2" + i, category2)); | ||
| } | ||
|
|
||
| for (int i = 0; i < 5; i++) { | ||
| articleRepository.save(readArticle(user, "article3" + i, category)); | ||
| } | ||
|
|
||
| //when | ||
|
|
||
| ArticleAllResponse responses = articleManagementUsecase.getAllArticlesByCategory(user, category.getId(), 0, 5); | ||
|
|
||
| //then | ||
|
|
||
| assertThat(responses.totalUnreadArticle()).isEqualTo(3); | ||
| assertThat(responses.totalArticle()) | ||
| .isEqualTo(8); | ||
| assertThat(responses.articles().get(0).createdAt()).isNotNull(); | ||
|
|
||
| } | ||
|
Comment on lines
+205
to
+236
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion Add security test for cross-user access prevention The current tests don't verify that users cannot access articles from other users in the same category. This is critical given the security issue identified in the repository layer. Add a test case like this to ensure proper user authorization: @DisplayName("사용자는 다른 사용자의 아티클을 카테고리로 조회할 수 없다.")
@Test
void getCategoryArticlesWithDifferentUser() {
//given
User user1 = userRepository.save(user());
User user2 = userRepository.save(userWithEmail("user2@test.com"));
Category category = categoryRepository.save(category(user1));
// User1 creates articles in the category
for (int i = 0; i < 3; i++) {
articleRepository.save(article(user1, "user1-article" + i, category));
}
// User2 should not see User1's articles
ArticleAllResponse response = articleManagementUsecase.getAllArticlesByCategory(user2, category.getId(), 0, 10);
//then
assertThat(response.articles()).isEmpty();
assertThat(response.totalArticle()).isEqualTo(0);
}🤖 Prompt for AI Agents |
||
|
|
||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical Security Issue: Missing user authorization in category-based queries
The
findAllByCategorymethod has a serious security flaw - all queries filter only bycategoryIdbut omit theuserIdfilter. This allows users to access articles from other users if they happen to be in the same category.Apply this fix to add user authorization to all queries:
🤖 Prompt for AI Agents