Skip to content

feat: 콘텐츠 관리 - 콘텐츠 조회 구현#206

Merged
plzslp merged 32 commits into
devfrom
feature/21-content-get
Jun 29, 2026
Merged

feat: 콘텐츠 관리 - 콘텐츠 조회 구현#206
plzslp merged 32 commits into
devfrom
feature/21-content-get

Conversation

@plzslp

@plzslp plzslp commented Jun 28, 2026

Copy link
Copy Markdown
Collaborator

관련 이슈

작업 내용

  • Content 단건 조회, 목록 조회(페이지네이션) 기능을 구현했습니다.
  • Content 목록 조회 시에는 QueryDsl을 사용하여 구현하였습니다.
  • Content 단건 조회, 목록 조회에 대한 Controller, Service 테스트를 작성했습니다.
  • QueryDSL 구현체는 단위 테스트로는 검증할 수 없어 통합 테스트 작성 시 검증을 추가할 예정입니다.

변경 사항

  • Content 엔티티 contentTags에 배치사이즈 어노테이션 추가
  • QueryDsl 의존성 추가 및 Config 추가
  • 프론트 엔드 코드 확인 후 query param으로 들어오는 요청값 매핑용 WebConfig 추가
  • JPAQueryFactory 빈을 주입하는 기존 Repository Test 통과용 QueryDslTestConfig를 추가하였습니다.
    • content, watcher Repository 테스트는 BaseRepositoryTest를 상속받기에 BaseRepositoryTest에 QueryDslTestConfig import 추가
    • user, notification, follow Repository 테스트는 클래스 별로 QueryDslTestConfig import를 추가하였습니다.

테스트 내용

  • 테스트 코드 작성
  • 로컬 테스트 완료
  • API 테스트 완료
  • 기타 테스트 완료

체크리스트

  • 코드 컨벤션을 지켰습니다.
  • 불필요한 코드를 제거했습니다.
  • 주석이 필요한 부분에 추가했습니다.
  • 관련 문서를 수정했습니다.
  • 리뷰어가 이해하기 쉽도록 작성했습니다.

리뷰 포인트

  • 중점적으로 봐줬으면 하는 부분을 작성해주세요.

스크린샷 / 참고 자료

🔗 관련 이슈

  • close #측정하지 않음

📝 작업 내용

  • 콘텐츠 조회 기능에 단건 조회와 커서 기반 목록 조회를 추가했습니다.
  • 기존에는 조회 API가 없어서 상세/목록 화면에서 필요한 콘텐츠 데이터를 가져올 수 없었고, 목록 조회는 정렬/필터/커서 검증까지 함께 처리할 수 있는 기반이 부족했습니다.
  • 이번 변경으로 조회 흐름을 컨트롤러-서비스-리포지토리까지 일관되게 연결하고, 정렬/커서/응답 변환 규칙을 명확히 분리했습니다.

주요 변경사항

  1. 콘텐츠 단건 조회와 커서 기반 목록 조회 API를 새로 추가해, 조회 책임을 명확히 분리했습니다.
  2. 목록 조회는 QueryDSL 기반으로 구현해 타입별 필터, 키워드 검색, 태그 필터, 커서 조건, 정렬 기준을 한 번에 처리하도록 바꿨습니다.
  3. 커서 파라미터 검증, 정렬 방향 변환, 정렬 기준 enum, 예외 타입을 추가해 잘못된 요청을 초기에 차단하도록 했습니다.
  4. 조회 응답은 일반 리스트가 아니라 커서 응답 형태로 변환되도록 맞추고, hasNext/다음 커서 계산 로직을 서비스와 매퍼에 반영했습니다.
  5. 테스트는 컨트롤러/서비스/유효성 검증까지 추가해 단건 조회와 목록 조회 흐름을 함께 검증하도록 보강했습니다.

📊 변경 효과 요약 (가능한 경우에만)

  • 추가된 테스트 파일: 3개
  • 추가된 설정/구성 파일: 3개
  • 정량 지표는 이번 PR에서 측정하지 않음

@plzslp plzslp self-assigned this Jun 28, 2026
@coderabbitai

coderabbitai Bot commented Jun 28, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: db56e90c-9007-4065-9cd9-71b7a1551372

📥 Commits

Reviewing files that changed from the base of the PR and between 3eb0c61 and 140a20c.

📒 Files selected for processing (3)
  • src/main/java/com/codeit/team5/mopl/content/dto/request/ValidCursorValidator.java
  • src/main/java/com/codeit/team5/mopl/content/repository/querydsl/ContentQueryRepositoryImpl.java
  • src/test/java/com/codeit/team5/mopl/content/dto/request/ValidCursorValidatorTest.java
 __________________________________________
< Dollars to donuts, you didn't test this. >
 ------------------------------------------
  \
   \   (\__/)
       (•ㅅ•)
       /   づ
📝 Walkthrough

Walkthrough

QueryDSL 의존성을 빌드 설정에 추가하고 JPAQueryFactory 빈을 등록합니다. ContentSortByType enum과 ContentType JSON 매핑이 수정되고, ContentStats에 평균 평점 계산·검증 메서드가 추가됩니다. 커서 기반 조회 요청 DTO(ContentCursorRequest)와 @ValidCursor 커스텀 검증 애너테이션, ValidCursorValidator가 도입됩니다. ContentQueryRepository 인터페이스와 QueryDSL 구현체가 추가되어 필터·커서·정렬 조회를 지원합니다. ContentService에 단건 조회와 커서 목록 조회 메서드가, ContentController에 GET 엔드포인트 두 개가 추가됩니다. 알림 커서 예외 생성자가 무인자로 변경되고, 전 계층에 걸쳐 테스트가 작성됩니다.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • sb10-part4-team5/sb10-mopl-team5#111: 동일한 ContentControllerContentApi 코드 경로에서 콘텐츠 관리 기능을 추가한 PR로, 이번 조회 엔드포인트와 직접 맞물립니다.
  • sb10-part4-team5/sb10-mopl-team5#114: CursorResponse 레코드를 정의한 PR로, 이번 변경에서 ContentMapper.toCursorContentService.findContents가 해당 타입을 직접 조립·반환합니다.
  • sb10-part4-team5/sb10-mopl-team5#157: 동일한 ContentController·ContentApi에서 PATCH/DELETE 및 멀티파트 request 스키마를 조정해 API 정의 레벨에서 직접 겹칩니다.
🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 알림(Notification) 커서 예외/서비스 수정은 콘텐츠 조회 기능과 직접 관련이 없어 범위를 벗어납니다. 알림 관련 변경은 별도 PR로 분리하거나, 콘텐츠 조회와의 직접적인 의존 관계를 설명해 범위를 명확히 해주세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 22.32% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 콘텐츠 조회 구현이라는 핵심 변경점을 간결하게 담고 있어 변경 내용과 잘 맞습니다.
Description check ✅ Passed 필수 섹션이 대부분 채워져 있고 관련 이슈, 작업 내용, 테스트, 체크리스트가 포함되어 있어 템플릿을 충분히 따릅니다.
Linked Issues check ✅ Passed 단건 조회, 커서 기반 목록 조회, 정렬/페이징, 관련 컨트롤러·서비스·리포지토리 테스트가 모두 반영되어 #21, #102 요구를 충족합니다.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/21-content-get

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 9

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/codeit/team5/mopl/content/controller/api/ContentApi.java`:
- Around line 65-67: The multipart update wrapper currently exposes the wrong
DTO type, so the OpenAPI schema for `ContentUpdateMultipartRequest` does not
match the actual `patchContent` contract. Update the `request` field in
`ContentUpdateMultipartRequest` to use `ContentUpdateRequest` instead of
`ContentCreateRequest`, keeping the wrapper and the `patchContent` method
signature aligned for schema generation and docs.

In
`@src/main/java/com/codeit/team5/mopl/content/dto/request/ContentCursorRequest.java`:
- Around line 20-21: The `ContentCursorRequest` limit binding currently uses
`@NotNull int limit`, but query-bound requests aren’t being validated in
`getContents()`, so invalid values can still reach `request.limit()` and
`subList(...)`. Update the `ContentCursorRequest` record field to use a
validation setup that actually applies to request binding, such as `Integer`
with `@NotNull `@Positive``, and ensure the controller/request handler that
consumes `ContentCursorRequest` enables validation. If keeping `int`, remove
`@NotNull` and enforce positivity with active request validation.

In `@src/main/java/com/codeit/team5/mopl/content/entity/ContentSortByType.java`:
- Around line 9-22: `ContentSortByType`의 공개 sortBy 계약이 요구사항과 맞지 않아
`sortBy=rank`가 실패하고 응답 값도 달라집니다. `ContentSortByType.from()`과 enum 상수 정의를 확인해
`RATE`의 외부 값이 `rank`가 되도록 수정하고, 기존 클라이언트 호환이 필요하면 `from()`에서 `rank`와 `rate`를 모두
허용하도록 처리하세요.

In `@src/main/java/com/codeit/team5/mopl/content/entity/ContentType.java`:
- Around line 23-26: ContentType.from() only matches the serialized value
string, so uppercase enum names like MOVIE and TV_SERIES now fail to bind and
cause 400s. Update ContentType.from(String) to accept both the enum’s value
field and its name() (case-sensitive as needed) so existing requests remain
backward compatible. Keep the fix localized to ContentType.from and preserve the
current matching for movie, tvSeries, and sport.

In
`@src/main/java/com/codeit/team5/mopl/content/repository/querydsl/ContentRepositoryImpl.java`:
- Around line 106-143: `applyCursor` is silently skipping pagination when only
one of `cursor` or `idAfter` is present, and malformed values can still bubble
up as parsing exceptions. Move validation for the cursor pair and its format out
of `ContentRepositoryImpl.applyCursor` and into `ContentCursorRequest` or the
service layer so the repository only receives already-validated values. Ensure
invalid or partial cursor input is rejected as a request error instead of
falling back to the first page or throwing raw parse errors from
`UUID.fromString`, `Instant.parse`, `Long.parseLong`, or `Double.parseDouble`.
- Around line 133-154: The RATE sort is using stats.ratingSum instead of the
average rating exposed by ContentResponse, so the ordering and cursor semantics
are inconsistent. Update ContentRepositoryImpl.buildOrder and the RATE branch of
the cursor condition to sort and compare using the same average-rating
expression (or a dedicated persisted average field if you choose that approach),
and keep the tie-breaker on content.id. Also update ContentMapper.toCursor(...)
so its RATE cursor value is generated from the same metric, ensuring pagination
remains stable.

In `@src/main/java/com/codeit/team5/mopl/content/service/ContentService.java`:
- Around line 111-117: The pagination logic in ContentService.findContents
assumes request.limit() is at least 1, so add a lower-bound constraint to the
ContentCursorRequest.limit field and keep the service’s fetch/page calculation
unchanged. Update the request contract by annotating limit with a positive
minimum such as `@Min`(1) or `@Positive`, so ContentService.findContents,
fetchLimit, hasNext, and the subList paging logic never run with a zero limit.

In
`@src/test/java/com/codeit/team5/mopl/content/controller/ContentControllerTest.java`:
- Around line 645-651: The `typeEqual` test case is still using the enum
constant name instead of the public query value, so it does not verify the
frontend-facing mapping added in this PR. Update the `ContentControllerTest`
request in the `get("/api/contents")` scenario to use the actual
`ContentType.getValue()`-style value (for example, the lowercase public value)
or add a separate assertion that explicitly exercises that mapping. Keep the
focus on the `typeEqual` parameter path so this test fails if the converter or
query-parameter translation is missing.
- Around line 607-611: The controller test is asserting a different
sortDirection contract than the actual cursor mapping used by
ContentMapper.toCursor(...), so align them to one format only. Update the
response expectation in ContentControllerTest to match the real API contract
returned by the service path, and make the test data around the CursorResponse
construction use the same ASCENDING/DESCENDING vs ASC/DESC convention everywhere
it appears. If the intended contract is the service output, adjust the test
assertions and any related documentation/comments to expect that exact value
consistently.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: af4fbecd-a0de-49aa-afe0-6a94eb919758

📥 Commits

Reviewing files that changed from the base of the PR and between d7bfc79 and 9439d63.

📒 Files selected for processing (23)
  • build.gradle
  • src/main/java/com/codeit/team5/mopl/config/QueryDslConfig.java
  • src/main/java/com/codeit/team5/mopl/config/WebConfig.java
  • src/main/java/com/codeit/team5/mopl/content/controller/ContentController.java
  • src/main/java/com/codeit/team5/mopl/content/controller/api/ContentApi.java
  • src/main/java/com/codeit/team5/mopl/content/dto/request/ContentCursorRequest.java
  • src/main/java/com/codeit/team5/mopl/content/entity/Content.java
  • src/main/java/com/codeit/team5/mopl/content/entity/ContentSortByType.java
  • src/main/java/com/codeit/team5/mopl/content/entity/ContentType.java
  • src/main/java/com/codeit/team5/mopl/content/exception/ContentIncorrectSortByException.java
  • src/main/java/com/codeit/team5/mopl/content/mapper/ContentMapper.java
  • src/main/java/com/codeit/team5/mopl/content/repository/ContentRepository.java
  • src/main/java/com/codeit/team5/mopl/content/repository/querydsl/ContentRepositoryCustom.java
  • src/main/java/com/codeit/team5/mopl/content/repository/querydsl/ContentRepositoryImpl.java
  • src/main/java/com/codeit/team5/mopl/content/service/ContentService.java
  • src/main/java/com/codeit/team5/mopl/global/exception/InvalidSortDirectionException.java
  • src/test/java/com/codeit/team5/mopl/content/controller/ContentControllerTest.java
  • src/test/java/com/codeit/team5/mopl/content/service/ContentServiceTest.java
  • src/test/java/com/codeit/team5/mopl/follow/repository/FollowRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/global/support/base/BaseRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/global/support/config/QueryDslTestConfig.java
  • src/test/java/com/codeit/team5/mopl/notification/repository/NotificationRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/user/repository/UserRepositoryTest.java

Comment thread src/main/java/com/codeit/team5/mopl/content/controller/api/ContentApi.java Outdated
Comment thread src/main/java/com/codeit/team5/mopl/content/dto/request/ContentCursorRequest.java Outdated
Comment thread src/main/java/com/codeit/team5/mopl/content/entity/ContentType.java

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (1)
src/main/java/com/codeit/team5/mopl/content/controller/api/ContentApi.java (1)

113-122: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

목록 조회 200 응답에도 응답 스키마를 명시해주세요.

현재 getContents의 성공 응답은 description만 있어서 OpenAPI 문서/클라이언트 생성 시 실제 CursorResponse<ContentResponse> 계약이 비어 보일 수 있습니다. 단건 조회처럼 성공 응답의 content schema를 붙이면 API 사용자가 응답 구조를 바로 확인할 수 있습니다.

🔧 제안 수정
-            `@ApiResponse`(responseCode = "200", description = "성공"),
+            `@ApiResponse`(responseCode = "200", description = "성공",
+                    content = `@Content`(schema = `@Schema`(implementation = CursorResponse.class))),
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main/java/com/codeit/team5/mopl/content/controller/api/ContentApi.java`
around lines 113 - 122, The 200 success response in ContentApi#getContents is
missing an explicit response schema, so add the content/schema declaration for
the successful ApiResponse to match the actual CursorResponse<ContentResponse>
contract. Update the OpenAPI annotations in ContentApi so the 200 response
includes the response body type, similar to the other documented endpoints,
while keeping the existing error response schemas unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/codeit/team5/mopl/content/dto/request/ContentCursorRequest.java`:
- Around line 21-23: `ContentCursorRequest`의 `limit`은 `@Positive`만으로는 너무 큰 값이
허용되므로, 커서 페이지네이션 계약에 맞는 최대값 제약을 추가하세요. `limit` 필드에 상한 검증 어노테이션을 붙이거나 별도 검증을 넣어
`request.limit() + 1`와 QueryDSL `limit(...)`로 전달되기 전에 큰 요청을 차단하세요.
`ContentCursorRequest`와 이를 사용하는 커서 조회 로직에서 동일한 최대 페이지 크기 기준이 적용되도록 맞춰주세요.

In
`@src/main/java/com/codeit/team5/mopl/content/exception/InvalidCursorException.java`:
- Around line 7-8: The InvalidCursorException constructor currently appends the
raw cursor value into the BusinessException message, which can reflect invalid
input back to clients. Update InvalidCursorException so the super(...) message
is a fixed, generic BAD_REQUEST string without concatenating cursor, and if
needed log the cursor separately in the server-side handling path instead of
exposing it in the response.

In `@src/main/java/com/codeit/team5/mopl/content/service/ContentService.java`:
- Around line 214-219: The cursor parsing in ContentService’s switch for sortBy
RATE currently accepts non-finite values like NaN and Infinity, which can break
paging comparisons against stats.averageRating. Update the RATE branch to
validate the parsed double with Double.isFinite(...) after
Double.parseDouble(cursor), and reject or handle invalid values consistently
with the existing cursor parsing path. Keep the change localized to the cursor
parsing logic in ContentService.

In
`@src/main/resources/db/migration/V12__add_average_rating_to_content_stats.sql`:
- Line 1: The migration only adds average_rating with a default, so existing
content_stats rows are never backfilled and legacy data will stay at 0. Update
the V12__add_average_rating_to_content_stats.sql migration to populate
average_rating from rating_sum and review_count for existing rows before
enforcing NOT NULL/default, using the content_stats table so read paths like
averageRating and RATE sorting reflect stored historical ratings correctly.

---

Outside diff comments:
In `@src/main/java/com/codeit/team5/mopl/content/controller/api/ContentApi.java`:
- Around line 113-122: The 200 success response in ContentApi#getContents is
missing an explicit response schema, so add the content/schema declaration for
the successful ApiResponse to match the actual CursorResponse<ContentResponse>
contract. Update the OpenAPI annotations in ContentApi so the 200 response
includes the response body type, similar to the other documented endpoints,
while keeping the existing error response schemas unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 4371d1cc-bf42-40fb-ac46-76e5650d1223

📥 Commits

Reviewing files that changed from the base of the PR and between 9439d63 and 441b5ab.

📒 Files selected for processing (11)
  • src/main/java/com/codeit/team5/mopl/content/controller/ContentController.java
  • src/main/java/com/codeit/team5/mopl/content/controller/api/ContentApi.java
  • src/main/java/com/codeit/team5/mopl/content/dto/request/ContentCursorRequest.java
  • src/main/java/com/codeit/team5/mopl/content/entity/ContentStats.java
  • src/main/java/com/codeit/team5/mopl/content/exception/CursorIdAfterNotTogetherException.java
  • src/main/java/com/codeit/team5/mopl/content/exception/InvalidCursorException.java
  • src/main/java/com/codeit/team5/mopl/content/mapper/ContentMapper.java
  • src/main/java/com/codeit/team5/mopl/content/repository/querydsl/ContentRepositoryImpl.java
  • src/main/java/com/codeit/team5/mopl/content/service/ContentService.java
  • src/main/resources/db/migration/V12__add_average_rating_to_content_stats.sql
  • src/test/java/com/codeit/team5/mopl/content/controller/ContentControllerTest.java

Comment thread src/main/java/com/codeit/team5/mopl/content/exception/InvalidCursorException.java Outdated
Comment thread src/main/java/com/codeit/team5/mopl/content/service/ContentService.java Outdated
Comment thread src/main/resources/db/migration/V12__add_average_rating_to_content_stats.sql Outdated
@plzslp plzslp requested a review from metDaisy June 29, 2026 00:06
Comment thread src/main/java/com/codeit/team5/mopl/config/WebConfig.java
Comment thread src/main/java/com/codeit/team5/mopl/content/exception/InvalidCursorException.java Outdated
Comment thread src/main/java/com/codeit/team5/mopl/content/service/ContentService.java Outdated
Comment thread src/main/java/com/codeit/team5/mopl/content/entity/ContentStats.java Outdated
metDaisy
metDaisy previously approved these changes Jun 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@src/main/java/com/codeit/team5/mopl/notification/event/FollowingUserWatchingEvent.java`:
- Around line 5-11: The FollowingUserWatchingEvent record currently allows null
values for required notification fields, so add validation in the record itself
using a compact constructor to fail fast when receiverId, userNickname, or
contentName are missing. Keep the checks inside FollowingUserWatchingEvent so
onFollowingUserActivity() and NotificationService.create(...) can assume valid
inputs and avoid late failures or “null” text in notifications.

In
`@src/main/java/com/codeit/team5/mopl/notification/event/PlaylistSubscribedEvent.java`:
- Around line 5-11: `PlaylistSubscribedEvent` should enforce its construction
contract the same way `DirectMessageSentEvent` does, because empty or null
`receiverId`, `subscriberNickname`, or `playlistName` can slip through and fail
later in listeners. Add creation-time validation inside the
`PlaylistSubscribedEvent` record so these fields cannot be blank or null when
the event is instantiated, using the record’s constructor/validation logic
rather than relying on downstream handling.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: ef5d2a49-162e-4f57-898b-0a18a7e3d7f3

📥 Commits

Reviewing files that changed from the base of the PR and between ad85af5 and 82f09ba.

📒 Files selected for processing (15)
  • src/main/java/com/codeit/team5/mopl/notification/event/DirectMessageSentEvent.java
  • src/main/java/com/codeit/team5/mopl/notification/event/FollowingUserWatchingEvent.java
  • src/main/java/com/codeit/team5/mopl/notification/event/NotificationCreatedEvent.java
  • src/main/java/com/codeit/team5/mopl/notification/event/PlaylistSubscribedEvent.java
  • src/main/java/com/codeit/team5/mopl/notification/event/PlaylistUpdatedEvent.java
  • src/main/java/com/codeit/team5/mopl/notification/event/RoleChangedEvent.java
  • src/main/java/com/codeit/team5/mopl/notification/event/UserFollowedEvent.java
  • src/main/java/com/codeit/team5/mopl/notification/eventlistener/NotificationEventListener.java
  • src/main/java/com/codeit/team5/mopl/notification/eventlistener/SseNotificationListener.java
  • src/main/java/com/codeit/team5/mopl/notification/exception/InvalidContentException.java
  • src/main/java/com/codeit/team5/mopl/notification/exception/InvalidNicknameException.java
  • src/main/java/com/codeit/team5/mopl/notification/exception/InvalidReceiverIdException.java
  • src/test/java/com/codeit/team5/mopl/notification/event/DirectMessageSentEventTest.java
  • src/test/java/com/codeit/team5/mopl/notification/eventlistener/NotificationEventListenerIntegrationTest.java
  • src/test/java/com/codeit/team5/mopl/notification/eventlistener/NotificationEventListenerTest.java

plzslp added 21 commits June 29, 2026 14:07
- ContentRepositoryCustom: 구현체를 위한 인터페이스
- ContentRepositoryImpl: QueryDsl Repository 구현체
- ContentRepository: ContentRepositoryCustom 상속으로 QueryDsl Repository 메서드 사용 가능
- fetchLimit, hasNext, page, totalCount를 Mapper로 전달
- 파라미터를 받아 CursorResponse로 변환하여 반환

# Conflicts:
#	src/main/java/com/codeit/team5/mopl/content/mapper/ContentMapper.java
- BaseRepositoryTest, NotificationRepositoryTest, UserRepositoryTest import 추가
@plzslp plzslp force-pushed the feature/21-content-get branch from 82f09ba to 53a45db Compare June 29, 2026 05:46

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/codeit/team5/mopl/content/entity/ContentStats.java`:
- Around line 39-42: `ContentStats.updateRating()` currently accepts invalid
aggregate state, so enforce the domain invariants at this boundary before
assigning `ratingSum` and `reviewCount`. Add validation in `updateRating` to
reject negative `newReviewCount`, reject non-finite or negative `newRatingSum`,
and require `newReviewCount == 0` only when `newRatingSum == 0`; keep the checks
close to `getAverageRating()` and the `ContentStats` entity so bad values cannot
flow into rating-based pagination metadata.

In
`@src/main/java/com/codeit/team5/mopl/content/exception/ContentIncorrectSortByException.java`:
- Around line 8-9: `ContentIncorrectSortByException` 생성자에서
`Map.of("incorrectSortBy", sortBy)`가 `sortBy`가 null일 때 NPE를 일으키는 문제가 있습니다.
`ContentIncorrectSortByException(String sortBy)`에서 details를 만들 때 null이면 빈 map을
사용하거나 `Map<String, Object>`를 조건적으로 구성하도록 바꿔서, `BaseException` 호출이 항상
`BAD_REQUEST`로 정상 생성되게 수정하세요.

In
`@src/main/java/com/codeit/team5/mopl/content/repository/querydsl/ContentQueryRepositoryImpl.java`:
- Around line 123-129: Fix the rating sort/cursor logic in
ContentQueryRepositoryImpl so queries do not divide by zero when reviewCount is
0. Extract a shared helper used by both applyCursor and buildOrder that returns
a NumberExpression<Double> matching ContentStats.getAverageRating() semantics,
i.e. 0.0 when reviewCount is zero and ratingSum / reviewCount otherwise. Update
the cursor comparison and ordering expressions to use this helper instead of
directly dividing stats.ratingSum by stats.reviewCount.doubleValue(), so the
sort order and next cursor stay consistent.

In
`@src/main/java/com/codeit/team5/mopl/global/exception/InvalidSortDirectionException.java`:
- Around line 7-8: The error message in InvalidSortDirectionException does not
match the actual accepted values handled by the sort conversion logic. Update
the exception message in InvalidSortDirectionException to reflect the real
allowed inputs used by WebConfig’s sort-direction mapping, including ASC/DESC if
they are valid, or otherwise standardize the parsing so the message and
conversion rules use the same canonical values. Keep the message aligned with
the behavior of the sort direction converter so callers get accurate guidance.

In `@src/test/java/com/codeit/team5/mopl/content/service/ContentServiceTest.java`:
- Around line 466-468: `ContentServiceTest`의 `CursorResponse` 픽스처가 실제
`ContentMapper.toCursor(...)` 계약과 불일치합니다. `expectedResponse`를 만드는 테스트들에서
`sortDirection` 값을 `"DESC"` 대신 실제 반환값인 `"DESCENDING"`(필요 시 `"ASCENDING"`도 동일
기준)으로 맞추거나, 서비스 슬라이싱만 검증하도록 해당 문자열 비교를 덜 구체적으로 조정해 `ContentMapper.toCursor`의 계약과
테스트 기대값이 일치하도록 수정하세요.
- Around line 453-542: `findContents()` 테스트에 커서 검증 실패 경로를 추가해, 잘못된
`cursor`/`idAfter` 조합이 들어오면 `validateCursor(request)`에서 예외가 발생하고
`contentRepository`가 호출되지 않는 계약을 확인하세요. `ContentServiceTest`의 `findContents()`
관련 테스트들 옆에 음수 케이스를 넣고, `contentService.findContents(request)` 호출 시 예외를 assert 한
뒤 `verify(contentRepository, never())`로 조회가 차단되는지 검증하면 됩니다. `validateCursor`,
`findContents`, `ContentCursorRequest`를 기준으로 위치를 찾으면 됩니다.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 6a9aed0a-4953-44ec-8a28-fbcaf5d88987

📥 Commits

Reviewing files that changed from the base of the PR and between 82f09ba and 53a45db.

📒 Files selected for processing (28)
  • build.gradle
  • src/main/java/com/codeit/team5/mopl/config/QueryDslConfig.java
  • src/main/java/com/codeit/team5/mopl/config/WebConfig.java
  • src/main/java/com/codeit/team5/mopl/content/controller/ContentController.java
  • src/main/java/com/codeit/team5/mopl/content/controller/api/ContentApi.java
  • src/main/java/com/codeit/team5/mopl/content/dto/request/ContentCursorRequest.java
  • src/main/java/com/codeit/team5/mopl/content/entity/Content.java
  • src/main/java/com/codeit/team5/mopl/content/entity/ContentSortByType.java
  • src/main/java/com/codeit/team5/mopl/content/entity/ContentStats.java
  • src/main/java/com/codeit/team5/mopl/content/entity/ContentType.java
  • src/main/java/com/codeit/team5/mopl/content/exception/ContentIncorrectSortByException.java
  • src/main/java/com/codeit/team5/mopl/content/exception/CursorIdAfterNotTogetherException.java
  • src/main/java/com/codeit/team5/mopl/content/exception/InvalidCursorException.java
  • src/main/java/com/codeit/team5/mopl/content/mapper/ContentMapper.java
  • src/main/java/com/codeit/team5/mopl/content/repository/ContentRepository.java
  • src/main/java/com/codeit/team5/mopl/content/repository/querydsl/ContentQueryRepository.java
  • src/main/java/com/codeit/team5/mopl/content/repository/querydsl/ContentQueryRepositoryImpl.java
  • src/main/java/com/codeit/team5/mopl/content/service/ContentService.java
  • src/main/java/com/codeit/team5/mopl/global/exception/InvalidSortDirectionException.java
  • src/main/java/com/codeit/team5/mopl/notification/exception/InvalidCursorException.java
  • src/main/java/com/codeit/team5/mopl/notification/service/NotificationService.java
  • src/test/java/com/codeit/team5/mopl/content/controller/ContentControllerTest.java
  • src/test/java/com/codeit/team5/mopl/content/service/ContentServiceTest.java
  • src/test/java/com/codeit/team5/mopl/follow/repository/FollowRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/global/support/base/BaseRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/global/support/config/QueryDslTestConfig.java
  • src/test/java/com/codeit/team5/mopl/notification/repository/NotificationRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/user/repository/UserRepositoryTest.java

Comment thread src/main/java/com/codeit/team5/mopl/content/service/ContentService.java Outdated
metDaisy
metDaisy previously approved these changes Jun 29, 2026
Comment thread src/main/java/com/codeit/team5/mopl/config/WebConfig.java
metDaisy
metDaisy previously approved these changes Jun 29, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/main/java/com/codeit/team5/mopl/content/controller/api/ContentApi.java`:
- Around line 132-133: `ContentApi`의 `sortDirection` Swagger `@Schema` 허용값이 실제
변환 계약보다 좁게 정의되어 있습니다. `WebConfig`의 정렬 파서가 `ASC`/`DESC`도 받아들이는 만큼, `ContentApi`의
`sortDirection` 문서도 `ASC`, `DESC`, `ASCENDING`, `DESCENDING`을 모두 반영하거나, 반대로 실제
파서를 문서와 동일하게 긴 형식만 허용하도록 맞춰서 계약을 하나로 통일해 주세요.

In
`@src/main/java/com/codeit/team5/mopl/content/dto/request/ValidCursorValidator.java`:
- Around line 15-20: `ValidCursorValidator`에서 `cursor`만 있고 `idAfter`가 없을 때
`UUID.fromString(idAfter)`가 NPE를 낼 수 있습니다. `isValid` 로직에서 파싱 전에
`cursor`/`idAfter`가 둘 다 비어 있거나 둘 다 존재하는지 먼저 검증하고, `idAfter`가 null이면 false를 반환하도록
처리해 `UUID.fromString`은 항상 non-null 입력만 받게 수정하세요.

In
`@src/main/java/com/codeit/team5/mopl/content/repository/querydsl/ContentQueryRepositoryImpl.java`:
- Around line 23-24: The TODO in ContentQueryRepositoryImpl should be replaced
with real integration coverage before merge. Add `@DataJpaTest` tests backed by
Testcontainers PostgreSQL for the query logic that uses CaseBuilder, cursor
comparisons, and UUID tie-breaking, and verify CREATED_AT, WATCHER_COUNT, and
RATE sorting with first page, next page, tie-break, and reviewCount = 0
scenarios. Focus the tests on the repository behavior in
ContentQueryRepositoryImpl so DB-specific ordering and pagination semantics are
locked in.
- Around line 37-44: `ContentQueryRepositoryImpl.findContents(...)` currently
fetch-joins only `stats`, but `ContentMapper.toDto(...)` also reads
`thumbnail.url` and `thumbnail.uploadStatus`, causing N+1 on list mapping.
Update the query to eagerly load the single-valued `thumbnail` association as
well (alongside `stats`) so the list API doesn’t trigger per-row selects, while
keeping collection associations like tags out of this fetch path and handling
them via batch fetching or a separate step if needed.

In
`@src/main/java/com/codeit/team5/mopl/notification/service/NotificationService.java`:
- Around line 65-68: The cursor parsing in NotificationService currently throws
a new InvalidCursorException without preserving the DateTimeParseException
cause, so add an InvalidCursorException(String cursor, Throwable cause) overload
and use it in the catch block to keep the original stack trace and cursor
context. Update InvalidCursorException to store the cursor in its message and
link the cause via initCause(cause), matching the exception-wrapping pattern
used by other lower-level errors. Reference the NotificationService cursor
parsing logic and the InvalidCursorException constructors when making the
change.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 90815b04-ff8e-410e-8e59-628ea016a14b

📥 Commits

Reviewing files that changed from the base of the PR and between 82f09ba and 3eb0c61.

📒 Files selected for processing (30)
  • build.gradle
  • src/main/java/com/codeit/team5/mopl/config/QueryDslConfig.java
  • src/main/java/com/codeit/team5/mopl/config/WebConfig.java
  • src/main/java/com/codeit/team5/mopl/content/controller/ContentController.java
  • src/main/java/com/codeit/team5/mopl/content/controller/api/ContentApi.java
  • src/main/java/com/codeit/team5/mopl/content/dto/request/ContentCursorRequest.java
  • src/main/java/com/codeit/team5/mopl/content/dto/request/ValidCursor.java
  • src/main/java/com/codeit/team5/mopl/content/dto/request/ValidCursorValidator.java
  • src/main/java/com/codeit/team5/mopl/content/entity/Content.java
  • src/main/java/com/codeit/team5/mopl/content/entity/ContentSortByType.java
  • src/main/java/com/codeit/team5/mopl/content/entity/ContentStats.java
  • src/main/java/com/codeit/team5/mopl/content/entity/ContentType.java
  • src/main/java/com/codeit/team5/mopl/content/exception/ContentIncorrectSortByException.java
  • src/main/java/com/codeit/team5/mopl/content/exception/InvalidContentStatsException.java
  • src/main/java/com/codeit/team5/mopl/content/mapper/ContentMapper.java
  • src/main/java/com/codeit/team5/mopl/content/repository/ContentRepository.java
  • src/main/java/com/codeit/team5/mopl/content/repository/querydsl/ContentQueryRepository.java
  • src/main/java/com/codeit/team5/mopl/content/repository/querydsl/ContentQueryRepositoryImpl.java
  • src/main/java/com/codeit/team5/mopl/content/service/ContentService.java
  • src/main/java/com/codeit/team5/mopl/global/exception/InvalidSortDirectionException.java
  • src/main/java/com/codeit/team5/mopl/notification/exception/InvalidCursorException.java
  • src/main/java/com/codeit/team5/mopl/notification/service/NotificationService.java
  • src/test/java/com/codeit/team5/mopl/content/controller/ContentControllerTest.java
  • src/test/java/com/codeit/team5/mopl/content/dto/request/ValidCursorValidatorTest.java
  • src/test/java/com/codeit/team5/mopl/content/service/ContentServiceTest.java
  • src/test/java/com/codeit/team5/mopl/follow/repository/FollowRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/global/support/base/BaseRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/global/support/config/QueryDslTestConfig.java
  • src/test/java/com/codeit/team5/mopl/notification/repository/NotificationRepositoryTest.java
  • src/test/java/com/codeit/team5/mopl/user/repository/UserRepositoryTest.java

@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

1 similar comment
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{}

@plzslp plzslp requested a review from shong9124 June 29, 2026 07:37
@plzslp plzslp merged commit 9b2ce06 into dev Jun 29, 2026
2 checks passed
@plzslp plzslp deleted the feature/21-content-get branch June 29, 2026 07:40
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.

[Test] 콘텐츠 조회 테스트 코드 작성 [Feat] 콘텐츠 관리 - 콘텐츠 조회

3 participants