Skip to content

feat: 카테고리별 아티클 조회 시 읽음 여부를 포함하여 조회하도록 구현 #100

Merged
rootTiket merged 5 commits intodevfrom
feat/#99
Sep 13, 2025
Merged

feat: 카테고리별 아티클 조회 시 읽음 여부를 포함하여 조회하도록 구현 #100
rootTiket merged 5 commits intodevfrom
feat/#99

Conversation

@rootTiket
Copy link
Contributor

@rootTiket rootTiket commented Sep 13, 2025

🚀 PR 요약

카테고리별 아티클 조회시 읽음 여부를 포함하여 조회하도록 구현했습니다.

✨ PR 상세 내용

  1. 카테고리별 아티클 조회시 조회조건 변경
  2. cors 설정에 api 경로가 누락되어 있는 부분을 수정

🚨 주의 사항

없습니다

✅ 체크 리스트

  • 리뷰어 설정했나요?
  • Label 설정했나요?
  • 제목 양식 맞췄나요? (ex. feat: 기능 추가)
  • 변경 사항에 대한 테스트를 진행했나요?

Summary by CodeRabbit

  • New Features

    • Category article listing now supports an optional read-status filter so users can view read or unread articles.
  • Chores

@rootTiket rootTiket requested a review from ose0221 September 13, 2025 16:11
@rootTiket rootTiket self-assigned this Sep 13, 2025
@coderabbitai
Copy link

coderabbitai bot commented Sep 13, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

Adds an isRead (read-status) boolean filter to "get articles by category" across controller, application ports/usecase, service, and repository; repository query now includes article.isRead predicate. Also adds https://api.pinack.today to allowed CORS origins.

Changes

Cohort / File(s) Summary
API: Controller param
api/.../article/controller/ArticleController.java
Adds @RequestParam(name = "read-status") boolean isRead to category fetch endpoint and forwards it to the use case.
Config: CORS
api/.../config/cors/CorsConfig.java
Adds https://api.pinack.today to allowed origin patterns.
Application: Port-In & Usecase
application/.../article/port/in/GetArticlePort.java, application/.../article/usecase/query/GetArticleUsecase.java
Adds boolean isRead to getAllArticlesByCategory signature and propagates it through the use case.
Application/Infra: Port-Out & Service
application/.../article/port/out/ArticleGetServicePort.java, infrastructure/.../article/service/ArticleGetService.java
Adds boolean isRead to findAllByCategory signature and forwards it to repository. Minor formatting changes.
Infrastructure: Repository API & Impl
infrastructure/.../article/repository/ArticleRepositoryCustom.java, infrastructure/.../article/repository/ArticleRepositoryCustomImpl.java
Adds boolean isRead to findAllByCategory; implementation updates WHERE clause to include article.isRead = :isRead.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Client
  participant Controller as ArticleController
  participant Usecase as GetArticleUsecase
  participant Service as ArticleGetServicePort
  participant Repo as ArticleRepositoryCustom
  participant DB

  Client->>Controller: GET /articles?categoryId&read-status
  Controller->>Usecase: getAllArticlesByCategory(user, categoryId, isRead, page)
  Usecase->>Service: findAllByCategory(user, category, isRead, pageReq)
  Service->>Repo: findAllByCategory(userId, categoryId, isRead, pageable)
  Repo->>DB: SELECT ... WHERE category_id=? AND is_read=?
  DB-->>Repo: Rows + counts
  Repo-->>Service: ArticlesWithUnreadCount
  Service-->>Usecase: DTO
  Usecase-->>Controller: ArticlesPageResponse
  Controller-->>Client: 200 OK (filtered list)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

feat

Suggested reviewers

  • ose0221

Poem

I hop through categories, nimble and keen,
Sorting read from unread, tidy and clean.
A whisker-twitch filter, so crisp, so light—
Now queries nibble just what’s right.
CORS gates widened, sky so blue— carrots for code, and releases anew. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title "feat: 카테고리별 아티클 조회 시 읽음 여부를 포함하여 조회하도록 구현" succinctly and accurately describes the primary change in the PR—adding read-status filtering to category-based article retrieval. It uses the conventional "feat:" prefix, is specific to the main behavior change, and avoids file lists or vague wording.
Description Check ✅ Passed The PR description follows the repository template and includes the PR summary, detailed change list (read-status filter and CORS fix), a "주의 사항" section, and a completed checklist, providing sufficient context and intent for reviewers. The description clearly communicates the high-level changes and purpose.

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 946b0a1 and 829fc8c.

📒 Files selected for processing (2)
  • application/src/test/java/com/pinback/application/article/usecase/query/GetArticleUsecaseTest.java (1 hunks)
  • infrastructure/src/test/java/com/pinback/infrastructure/article/service/ArticleGetServiceTest.java (1 hunks)

Tip

👮 Agentic pre-merge checks are now available in preview!

Pro plan users can now enable pre-merge checks in their settings to enforce checklists before merging PRs.

  • Built-in checks – Quickly apply ready-made checks to enforce title conventions, require pull request descriptions that follow templates, validate linked issues for compliance, and more.
  • Custom agentic checks – Define your own rules using CodeRabbit’s advanced agentic capabilities to enforce organization-specific policies and workflows. For example, you can instruct CodeRabbit’s agent to verify that API documentation is updated whenever API schema files are modified in a PR. Note: Upto 5 custom checks are currently allowed during the preview period. Pricing for this feature will be announced in a few weeks.

Please see the documentation for more information.

Example:

reviews:
  pre_merge_checks:
    custom_checks:
      - name: "Undocumented Breaking Changes"
        mode: "warning"
        instructions: |
          Pass/fail criteria: All breaking changes to public APIs, CLI flags, environment variables, configuration keys, database schemas, or HTTP/GraphQL endpoints must be documented in the "Breaking Change" section of the PR description and in CHANGELOG.md. Exclude purely internal or private changes (e.g., code not exported from package entry points or explicitly marked as internal).

Please share your feedback with us on this Discord post.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rootTiket rootTiket merged commit 2ba49c5 into dev Sep 13, 2025
4 checks passed
@rootTiket rootTiket deleted the feat/#99 branch September 13, 2025 16:19
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
api/src/main/java/com/pinback/api/config/cors/CorsConfig.java (1)

36-47: Remove empty header entry from allowed headers

The empty string at Line 46 is invalid and may yield unpredictable CORS behavior.

Apply:

         configuration.setAllowedHeaders(Arrays.asList(
           "Authorization",
           "Content-Type",
           "X-Requested-With",
           "Accept",
           "Origin",
           "Access-Control-Request-Method",
           "Access-Control-Request-Headers",
           "X-Forwarded-For",
-          "X-Real-IP",
-          ""
+          "X-Real-IP"
         ));
infrastructure/src/main/java/com/pinback/infrastructure/article/repository/ArticleRepositoryCustomImpl.java (1)

61-88: Data leak + incorrect unread count in category query

  • Missing userId predicate: returns articles across all users in the category.
  • unReadCount is computed with the same isRead-filtered conditions; it becomes 0 when isRead=true, and it ignores the user's scope.

Apply:

-    public ArticlesWithUnreadCount findAllByCategory(UUID userId, long categoryId, boolean isRead, Pageable pageable) {
-
-        BooleanExpression conditions = article.category.id.eq(categoryId)
-            .and(article.isRead.eq(isRead));
+    public ArticlesWithUnreadCount findAllByCategory(UUID userId, long categoryId, boolean isRead, Pageable pageable) {
+        // Scope to user + category first
+        BooleanExpression baseConditions = article.user.id.eq(userId)
+            .and(article.category.id.eq(categoryId));
+        // Then apply read-status filter for the page content
+        BooleanExpression conditions = baseConditions.and(article.isRead.eq(isRead));
 
         List<Article> articles = queryFactory
             .selectFrom(article)
             .join(article.category, category).fetchJoin()
             .where(conditions)
             .offset(pageable.getOffset())
             .limit(pageable.getPageSize())
             .orderBy(article.createdAt.desc())
             .fetch();
 
         JPAQuery<Long> countQuery = queryFactory
             .select(article.count())
             .from(article)
             .where(conditions);
 
-        Long unReadCount = queryFactory
+        // Unread count should ignore the requested isRead filter
+        Long unReadCount = queryFactory
             .select(article.count())
             .from(article)
-            .where(conditions.and(article.isRead.isFalse()))
+            .where(baseConditions.and(article.isRead.isFalse()))
             .fetchOne();
 
         return new ArticlesWithUnreadCount(unReadCount,
             PageableExecutionUtils.getPage(articles, pageable, countQuery::fetchOne));
     }
🧹 Nitpick comments (2)
application/src/main/java/com/pinback/application/article/port/out/ArticleGetServicePort.java (1)

35-36: Consistency check (optional)

Both remind methods use Boolean isRead; controller passes primitive. This is fine, but consider aligning both to primitive if null is never a valid state.

infrastructure/src/main/java/com/pinback/infrastructure/article/repository/ArticleRepositoryCustom.java (1)

16-17: Parameter name is misleading

articleId should be categoryId.

Apply:

-    ArticlesWithUnreadCount findAllByCategory(UUID userId, long articleId, boolean isRead, Pageable pageable);
+    ArticlesWithUnreadCount findAllByCategory(UUID userId, long categoryId, boolean isRead, Pageable pageable);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 314133c and 946b0a1.

📒 Files selected for processing (8)
  • api/src/main/java/com/pinback/api/article/controller/ArticleController.java (1 hunks)
  • api/src/main/java/com/pinback/api/config/cors/CorsConfig.java (1 hunks)
  • application/src/main/java/com/pinback/application/article/port/in/GetArticlePort.java (1 hunks)
  • application/src/main/java/com/pinback/application/article/port/out/ArticleGetServicePort.java (1 hunks)
  • application/src/main/java/com/pinback/application/article/usecase/query/GetArticleUsecase.java (1 hunks)
  • infrastructure/src/main/java/com/pinback/infrastructure/article/repository/ArticleRepositoryCustom.java (1 hunks)
  • infrastructure/src/main/java/com/pinback/infrastructure/article/repository/ArticleRepositoryCustomImpl.java (2 hunks)
  • infrastructure/src/main/java/com/pinback/infrastructure/article/service/ArticleGetService.java (2 hunks)
🔇 Additional comments (5)
application/src/main/java/com/pinback/application/article/port/out/ArticleGetServicePort.java (1)

29-29: Signature update looks good

Propagates isRead through the port cleanly. Ensure implementors are updated (compile will catch).

application/src/main/java/com/pinback/application/article/port/in/GetArticlePort.java (1)

19-19: Interface change acknowledged

Adding isRead matches the new API surface.

infrastructure/src/main/java/com/pinback/infrastructure/article/repository/ArticleRepositoryCustom.java (1)

18-23: Remind queries: API shape LGTM

Optional read filter is exposed as Boolean consistently across custom methods.

application/src/main/java/com/pinback/application/article/usecase/query/GetArticleUsecase.java (1)

70-75: Use-case propagation LGTM

Correctly passes isRead through to the service port; response mapping unchanged.

infrastructure/src/main/java/com/pinback/infrastructure/article/service/ArticleGetService.java (1)

57-61: Service wiring LGTM

  • Category listing forwards isRead to repository.
  • Remind endpoints pass the date window and read filter correctly.

Also applies to: 76-87

Comment on lines +96 to 102
@Parameter(description = "읽음 상태 (true: 읽음, false: 안읽음)", example = "true") @RequestParam(name = "read-status") boolean isRead,
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "8") int size
) {
PageQuery query = new PageQuery(page, size);
ArticlesPageResponse response = getArticlePort.getAllArticlesByCategory(user, categoryId, query);
ArticlesPageResponse response = getArticlePort.getAllArticlesByCategory(user, categoryId, isRead, query);
return ResponseDto.ok(response);
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

Avoid breaking clients: make read-status optional with a default

Primitive boolean without a default makes the new query param mandatory and will 400 existing callers.

Apply:

-        @Parameter(description = "읽음 상태 (true: 읽음, false: 안읽음)", example = "true") @RequestParam(name = "read-status") boolean isRead,
+        @Parameter(description = "읽음 상태 (true: 읽음, false: 안읽음). 미지정 시 기본값 false") 
+        @RequestParam(name = "read-status", required = false, defaultValue = "false") boolean isRead,

If the FE is already updated to always send it, confirm and keep as-is.


🏁 Script executed:

#!/bin/bash
set -euo pipefail

target="api/src/main/java/com/pinback/api/article/controller/ArticleController.java"
if [ -f "$target" ]; then
  echo "FOUND:$target"
  nl -ba "$target" | sed -n '1,220p'
  exit 0
fi

file=$(rg -n --hidden --no-ignore-vcs -S "class\s+ArticleController" -g 'api/**' -l || true)
if [ -n "$file" ]; then
  echo "FOUND_SEARCH:$file"
  nl -ba "$file" | sed -n '1,220p'
  exit 0
fi

echo "NOT_FOUND"

Length of output: 122


🏁 Script executed:

#!/bin/bash
set -euo pipefail
f="api/src/main/java/com/pinback/api/article/controller/ArticleController.java"
if [ ! -f "$f" ]; then
  echo "NOT_FOUND:$f"
  rg -n --hidden --no-ignore-vcs -S "class\s+ArticleController" -g 'api/**' || true
  exit 0
fi
echo "FILE:$f"
awk '{printf "%5d: %s\n", NR, $0}' "$f" | sed -n '1,240p'
echo "----@RequestParam lines----"
awk '/@RequestParam/ {printf "%5d: %s\n", NR, $0}' "$f" || true

Length of output: 9764


Make read-status query param optional (default false)

Primitive boolean params are required by default and will 400 callers that don't send them. Two occurrences found in api/src/main/java/com/pinback/api/article/controller/ArticleController.java — update both:

  • getAllArticlesByCategory(...)
  • getRemindArticles(...)

Apply (or equivalent) for each occurrence:

- @Parameter(description = "읽음 상태 (true: 읽음, false: 안읽음)", example = "true") @RequestParam(name = "read-status") boolean isRead,
+ @Parameter(description = "읽음 상태 (true: 읽음, false: 안읽음). 미지정 시 기본값 false", example = "true")
+ @RequestParam(name = "read-status", required = false, defaultValue = "false") boolean isRead,
- @Parameter(description = "읽음 상태 (true: 읽음, false: 안읽음)", example = "true") @RequestParam(name = "read-status") boolean readStatus,
+ @Parameter(description = "읽음 상태 (true: 읽음, false: 안읽음). 미지정 시 기본값 false", example = "true")
+ @RequestParam(name = "read-status", required = false, defaultValue = "false") boolean readStatus,

If the frontend is guaranteed to always send this param, confirm and keep as-is.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Parameter(description = "읽음 상태 (true: 읽음, false: 안읽음)", example = "true") @RequestParam(name = "read-status") boolean isRead,
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "8") int size
) {
PageQuery query = new PageQuery(page, size);
ArticlesPageResponse response = getArticlePort.getAllArticlesByCategory(user, categoryId, query);
ArticlesPageResponse response = getArticlePort.getAllArticlesByCategory(user, categoryId, isRead, query);
return ResponseDto.ok(response);
@Parameter(description = "읽음 상태 (true: 읽음, false: 안읽음). 미지정 시 기본값 false", example = "true")
@RequestParam(name = "read-status", required = false, defaultValue = "false") boolean isRead,
@Parameter(description = "페이지 번호 (0부터 시작)") @RequestParam(defaultValue = "0") int page,
@Parameter(description = "페이지 크기") @RequestParam(defaultValue = "8") int size
) {
PageQuery query = new PageQuery(page, size);
ArticlesPageResponse response = getArticlePort.getAllArticlesByCategory(user, categoryId, isRead, query);
return ResponseDto.ok(response);

Comment on lines +28 to 30
"https://www.pinback.today",
"https://api.pinack.today"
));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Fix origin typo: api.pinack.today → api.pinback.today

Likely a blocking CORS bug for the production API domain.

Apply:

-            "https://api.pinack.today"
+            "https://api.pinback.today"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"https://www.pinback.today",
"https://api.pinack.today"
));
"https://www.pinback.today",
"https://api.pinback.today"
));
🤖 Prompt for AI Agents
In api/src/main/java/com/pinback/api/config/cors/CorsConfig.java around lines
28-30, the allowed origins list contains a typo "https://api.pinack.today" that
should be "https://api.pinback.today"; update that string to the correct domain
and run the app/CORS tests to verify the production API origin is accepted (also
scan the file for any other occurrences of the misspelled domain and correct
them).

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant