Skip to content

Comments

[OT-73][FEAT]: 백오피스 콘텐츠 관리 API 추가#55

Merged
phonil merged 8 commits intodevelopfrom
OT-73-feature/backoffice-contents
Feb 22, 2026
Merged

[OT-73][FEAT]: 백오피스 콘텐츠 관리 API 추가#55
phonil merged 8 commits intodevelopfrom
OT-73-feature/backoffice-contents

Conversation

@phonil
Copy link
Contributor

@phonil phonil commented Feb 22, 2026

📝 작업 내용

이번 PR에서 작업한 내용을 적어주세요

  • 콘텐츠 목록 조회
  • 콘텐츠 상세 조회
  • 시리즈 검색 (텍스트) (콘텐츠 업로드 모달에서 사용)

📷 스크린샷

[콘텐츠 목록 조회]
image

[콘텐츠 상세 조회]
image

[시리즈 검색(텍스트) (콘텐츠 업로드 모달에서 사용)]
image

image

☑️ 체크 리스트

체크 리스트를 확인해주세요

  • 테스트는 잘 통과했나요?
  • 충돌을 해결했나요?
  • 이슈는 등록했나요?
  • 라벨은 등록했나요?

#️⃣ 연관된 이슈

ex) # 이슈번호

closes #38

💬 리뷰 요구사항

리뷰어가 특별히 봐주었으면 하는 부분이 있다면 작성해주세요

ex) 예외 처리를 이렇게 해도 괜찮을까요? / ~~부분 주의 깊게 봐주세요

  1. 백오피스 API 경로
  • 기본: /back-office, 관리자만: + /admin, 에디터만: + /editor, 둘 다 가능: /
  • 제가 반영하지 않은 클래스도 있기 때문에 노션 개발 이슈 관리 페이지에 적어놨습니다.
  1. 응답 시 mediaId vs seriesId
  • 시리즈와 마찬가지로 콘텐츠 목록 조회 시에도 mediaId를 반환합니다. 따라서 mediaId를 통해 시리즈/콘텐츠 상세 조회를 진행합니다.
    • 해당 방식에 대해 '시리즈면 seriesId 반환, 콘텐츠면 contentsId 반환' vs '목록 조회 시에는 mediaId 반환' 고민되는 지점입니다. 답은 없는 것 같은데, 후자를 선택했고, 사용자 작업 흐름 상 문제되지 않도록 후속 작업도 개발했습니다.
  1. Projection 관련
  • 콘텐츠 모달 창에서 사용하는 시리즈 검색(제목만 응답) API는 제목만 필요해서 프로젝션을 사용하려 했습니다.
  • 문제: domain 모듈에 프로젝션을 위한 DTO 필요.
  • 고민: domain 모듈에 프로젝션을 위한 DTO 놓아도 되는지?
  • 현재 상황: 프로젝션 적용 X, 따라서 domain 모듈에 DTO 존재 x
    다른 방식으로 프로젝션 사용해도 괜찮은데, 우선 DTO 방식에 대해서 생각해봤습니다. (인터페이스 사용 방식도 마찬가지)

Summary by CodeRabbit

  • 새로운 기능

    • 백오피스 관리자용 콘텐츠 목록 조회(페이징, 검색어, 공개상태 필터) 추가
    • 콘텐츠 상세 조회(포스터·썸네일·메타데이터·업로더·태그 등) 추가
    • 콘텐츠 업로드용 시리즈 제목 목록(페이징) 조회 추가
  • 개선

    • 관리 API 경로 구조 정리로 엔드포인트 가독성 향상

@phonil phonil self-assigned this Feb 22, 2026
@phonil phonil added the feat 새로운 기능 구현 label Feb 22, 2026
@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

📝 Walkthrough

Walkthrough

백오피스용 콘텐츠 목록 조회, 상세 조회 및 시리즈 제목 검색 API가 추가되고 이를 지원하는 DTO/매퍼/서비스와 QueryDSL 기반 커스텀 리포지토리 구현이 도입되었습니다.

Changes

Cohort / File(s) Summary
콘텐츠 API 인터페이스·컨트롤러
apps/api-admin/src/main/java/com/ott/api_admin/content/controller/BackOfficeContentsApi.java, apps/api-admin/src/main/java/com/ott/api_admin/content/controller/BackOfficeContentsController.java
백오피스 콘텐츠 목록(/back-office/admin/contents) 및 상세(/back-office/admin/contents/{mediaId}) 엔드포인트 추가 및 컨트롤러 구현(파라미터 바인딩·응답 래핑).
콘텐츠 DTOs
apps/api-admin/src/main/java/com/ott/api_admin/content/dto/response/ContentsListResponse.java, apps/api-admin/src/main/java/com/ott/api_admin/content/dto/response/ContentsDetailResponse.java
목록/상세 응답용 record DTO 추가(미디어 메타, 태그 목록, 공개 상태, 업로드일 등).
콘텐츠 매퍼·서비스
apps/api-admin/src/main/java/com/ott/api_admin/content/mapper/BackOfficeContentsMapper.java, apps/api-admin/src/main/java/com/ott/api_admin/content/service/BackOfficeContentsService.java
엔티티→DTO 매핑과 페이징/상세 조회 비즈니스 로직 구현, 리포지토리 호출 및 예외 처리.
시리즈 API·매퍼·서비스 확장
apps/api-admin/src/main/java/com/ott/api_admin/series/controller/BackOfficeSeriesApi.java, apps/api-admin/src/main/java/com/ott/api_admin/series/controller/BackOfficeSeriesController.java, apps/api-admin/src/main/java/com/ott/api_admin/series/dto/response/SeriesTitleListResponse.java, apps/api-admin/src/main/java/com/ott/api_admin/series/mapper/BackOfficeSeriesMapper.java, apps/api-admin/src/main/java/com/ott/api_admin/series/service/BackOfficeSeriesService.java
시리즈 제목 검색 엔드포인트(/back-office/admin/series/titles) 추가 및 매퍼/서비스·DTO 확장, 컨트롤러 경로 정리.
멤버 컨트롤러 경로 조정
apps/api-admin/src/main/java/com/ott/api_admin/member/controller/BackOfficeMemberController.java
멤버 컨트롤러의 RequestMapping을 /back-office/admin/members로 변경(경로 정리).
콘텐츠 도메인 리포지토리 추가/구현
modules/domain/src/main/java/com/ott/domain/contents/repository/ContentsRepository.java, .../ContentsRepositoryCustom.java, .../ContentsRepositoryImpl.java
Contents용 커스텀 리포지토리 인터페이스 및 QueryDSL 구현 추가, media+uploader(및 series) fetch-join 조회 메서드 추가.
미디어 도메인 리포지토리 변경
modules/domain/src/main/java/com/ott/domain/media/repository/MediaRepositoryCustom.java, .../MediaRepositoryImpl.java
검색 메서드 명/분리 및 publicStatus 필터 추가(신규 메서드 및 조건 헬퍼 도입).
시리즈 도메인 리포지토리 확장
modules/domain/src/main/java/com/ott/domain/series/repository/SeriesRepositoryCustom.java, .../SeriesRepositoryImpl.java
검색어 기반 시리즈 페이징 조회 메서드 추가(미디어 조인·페이징 처리, QueryDSL + count query).

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as BackOfficeContentsController
    participant Service as BackOfficeContentsService
    participant MediaRepo as MediaRepository
    participant Mapper as BackOfficeContentsMapper

    Client->>Controller: GET /back-office/admin/contents?page=&size=&searchWord=&publicStatus=
    Controller->>Service: getContents(page, size, searchWord, publicStatus)
    Service->>MediaRepo: findMediaListByMediaTypeAndSearchWordAndPublicStatus(pageable, CONTENTS, searchWord, publicStatus)
    MediaRepo-->>Service: Page<Media>
    Service->>Mapper: toContentsListResponse(media) [for each]
    Mapper-->>Service: ContentsListResponse
    Service-->>Controller: PageResponse<ContentsListResponse>
    Controller-->>Client: 200 OK (SuccessResponse)
Loading
sequenceDiagram
    participant Client
    participant Controller as BackOfficeContentsController
    participant Service as BackOfficeContentsService
    participant ContentsRepo as ContentsRepository
    participant TagRepo as MediaTagRepository
    participant Mapper as BackOfficeContentsMapper

    Client->>Controller: GET /back-office/admin/contents/{mediaId}
    Controller->>Service: getContentsDetail(mediaId)
    Service->>ContentsRepo: findWithMediaAndUploaderByMediaId(mediaId)
    ContentsRepo-->>Service: Optional<Contents> (with Media, Uploader, Series)
    Service->>TagRepo: findByMediaId(mediaId)
    TagRepo-->>Service: List<MediaTag>
    Service->>Mapper: toContentsDetailResponse(contents, media, uploaderNickname, seriesTitle, mediaTagList)
    Mapper-->>Service: ContentsDetailResponse
    Service-->>Controller: ContentsDetailResponse
    Controller-->>Client: 200 OK (SuccessResponse)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • marulog
  • arlen02-01

Poem

🐇 당근 들고 깡충 뛰는 토끼가 말하네,
목록엔 포스터, 상세엔 태그가 빛나네.
쿼리와 매퍼가 손잡고 춤을 추니,
시리즈 제목도 속삭이며 다가오네.
백오피스에 당근 한 토막 축하해요 🥕

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning 일부 범위 외 변경이 있습니다: BackOfficeMemberController와 BackOfficeSeriesController의 라우팅 경로 변경, MediaRepositoryCustom의 메서드 이름 변경 및 시리즈 제목 검색 기능 추가가 명시된 요구사항 외에 포함되어 있습니다. 라우팅 경로 변경과 기존 메서드 이름 변경은 PR의 주요 목표와 직접적인 관련이 없으므로 별도의 PR로 분리하거나 범위 변경을 문서화해야 합니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목은 '[OT-73][FEAT]: 백오피스 콘텐츠 관리 API 추가'로, 변경 사항의 주요 내용인 백오피스 콘텐츠 관리 API 추가를 명확하게 설명합니다.
Linked Issues check ✅ Passed PR은 연결된 이슈 #38의 모든 목표를 충족합니다: 콘텐츠 목록 조회, 콘텐츠 상세 조회, 시리즈 검색 API가 모두 구현되었습니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch OT-73-feature/backoffice-contents

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

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: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/api-admin/src/main/java/com/ott/api_admin/content/controller/BackOfficeContentsApi.java`:
- Around line 50-67: The 400-response description in the Swagger annotations for
BackOfficeContentsApi.getContentsDetail is incorrect ("시리즈 상세 조회 실패"); update
the ApiResponse description for responseCode = "400" under the getContentsDetail
method to a correct phrase like "콘텐츠 상세 조회 실패" so the error message matches the
endpoint semantics.

In
`@apps/api-admin/src/main/java/com/ott/api_admin/content/dto/response/ContentsListResponse.java`:
- Around line 12-16: ContentsListResponse currently declares a field poster_url
that may not match the API spec (PR mentions poster_uri); update the DTO so the
JSON property matches the contract: either rename the Java field to
posterUri/poster_uri to match the spec or add an explicit mapping annotation
(e.g., `@JsonProperty`("poster_uri")) on the poster_url field in
ContentsListResponse to guarantee the serialized/deserialized name matches the
client expectation.

In
`@modules/domain/src/main/java/com/ott/domain/contents/repository/ContentsRepositoryImpl.java`:
- Around line 23-26: The query in ContentsRepositoryImpl reuses the same entity
type alias for two joins which causes JPQL alias collisions; update the
leftJoin(series.media).fetchJoin() to supply an explicit, unique alias (e.g.,
seriesMedia) and declare/use a corresponding Q-type variable (e.g., QMedia
seriesMedia) when building the QueryDSL query so the join becomes
leftJoin(series.media, seriesMedia).fetchJoin(); ensure the new alias name does
not conflict with the existing media variable and is referenced consistently in
the method.

@phonil
Copy link
Contributor Author

phonil commented Feb 22, 2026

코드래빗 리뷰 수정 후 반영했어요~

@Schema(type = "Integer", description = "영상 길이(초)", example = "7200")
Integer duration,

@Schema(type = "Integer", description = "영상 크기(바이트)", example = "1048576")
Copy link
Collaborator

@marulog marulog Feb 22, 2026

Choose a reason for hiding this comment

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

30분짜리 영상이 2에서3GB가 넘는걸로 알고 있고
추후 러닝타임이 긴 영상 업로드 시 문제가 발생할 것 같습니다.
Integer 대신 Long을 써보시는건 어떠신가요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

오 생각하지 못한 부분이네요!!

해당 DTO에서 Integer로 한 이유는

  1. Contents Entity에 Integer로 선언되어 있음 (db도 INT 타입)
  2. Integer: ~21억 => 1억 초 == 3년
    입니다.
    그래서 안 바꿔도 괜찮을 것 같은데 어떤가요??

아랫줄 영상 크기 문구는 스웨거 문서에 바이트가 아니라 KB로 바꿔야겠네요

Copy link
Collaborator

Choose a reason for hiding this comment

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

아 넵넵 확인했습니다

Copy link
Collaborator

@marulog marulog left a comment

Choose a reason for hiding this comment

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

수고하셨습니다 :)

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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/api-admin/src/main/java/com/ott/api_admin/content/dto/response/ContentsDetailResponse.java`:
- Around line 12-13: The response field name in ContentsDetailResponse (the
field contentsId) doesn't match the request path parameter name mediaId and
differs from ContentsListResponse which uses mediaId; update
ContentsDetailResponse to either rename contentsId to mediaId or add an
additional mediaId field while keeping contentsId (to preserve compatibility),
and update related serializers/constructors/factory methods that populate
ContentsDetailResponse (look for usages of the ContentsDetailResponse class and
its constructor/mapper) so the returned JSON contains mediaId consistently with
the endpoint and ContentsListResponse.

Comment on lines +12 to +13
@Schema(type = "Long", description = "콘텐츠 ID", example = "1")
Long contentsId,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's explore the directory structure to understand the codebase layout
find apps/api-admin/src -type f -name "*.java" | grep -E "(content|Controller)" | head -20

Repository: OpenTheTaste/backend

Length of output: 914


🏁 Script executed:

# Let's read the ContentsDetailResponse.java file to see its full content
cat -n apps/api-admin/src/main/java/com/ott/api_admin/content/dto/response/ContentsDetailResponse.java

Repository: OpenTheTaste/backend

Length of output: 2421


🏁 Script executed:

# Search for API endpoints related to content detail or media
rg -n "contentsId|mediaId" apps/api-admin/src --type java -B 2 -A 2 | head -50

Repository: OpenTheTaste/backend

Length of output: 6192


🏁 Script executed:

# Look for the Controller that handles content/media detail requests
find apps/api-admin/src -type f -name "*Controller.java" | xargs grep -l "mediaId\|contentsId" 2>/dev/null

Repository: OpenTheTaste/backend

Length of output: 260


🏁 Script executed:

# Read the BackOfficeContentsController to see the endpoint definition
cat -n apps/api-admin/src/main/java/com/ott/api_admin/content/controller/BackOfficeContentsController.java

Repository: OpenTheTaste/backend

Length of output: 2360


🏁 Script executed:

# Read the BackOfficeContentsMapper to understand how data is mapped
cat -n apps/api-admin/src/main/java/com/ott/api_admin/content/mapper/BackOfficeContentsMapper.java

Repository: OpenTheTaste/backend

Length of output: 2617


🏁 Script executed:

# Check the Contents entity and Media entity structure
find apps -type f -name "Contents.java" -o -name "Media.java" | head -10

Repository: OpenTheTaste/backend

Length of output: 46


🏁 Script executed:

# Look at the method that retrieves contents detail
grep -n "getContentsDetail\|toContentsDetailResponse" apps/api-admin/src/main/java/com/ott/api_admin/content/service/BackOfficeContentsService.java -A 10

Repository: OpenTheTaste/backend

Length of output: 783


🏁 Script executed:

# Let's check the ContentsListResponse to compare ID naming
cat -n apps/api-admin/src/main/java/com/ott/api_admin/content/dto/response/ContentsListResponse.java

Repository: OpenTheTaste/backend

Length of output: 1090


🏁 Script executed:

# Check what the Contents and Media entities look like to understand their relationship
find apps/domain -type f \( -name "Contents.java" -o -name "Media.java" \) | head -5

Repository: OpenTheTaste/backend

Length of output: 110


🏁 Script executed:

# Let's verify the API contract one more time by looking at how mediaId is used
rg -n "mediaId" apps/api-admin/src/main/java/com/ott/api_admin/content -A 2 -B 2 | grep -E "(getContentsDetail|ContentsDetailResponse|@Schema|Long)"

Repository: OpenTheTaste/backend

Length of output: 1758


요청 경로의 mediaId와 응답 필드의 contentsId 명칭 불일치 해결 필요

엔드포인트는 /{mediaId}로 호출하는데 응답은 contentsId를 반환합니다. 같은 콘텐츠 도메인의 목록 조회(ContentsListResponse)에서는 mediaId를 사용하므로, 일관성을 위해 상세 조회도 mediaId로 통일하거나 mediaId 필드를 추가로 제공하는 것을 권장합니다.

수정 예시
-        `@Schema`(type = "Long", description = "콘텐츠 ID", example = "1")
-        Long contentsId,
+        `@Schema`(type = "Long", description = "미디어 ID", example = "1")
+        Long mediaId,
📝 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
@Schema(type = "Long", description = "콘텐츠 ID", example = "1")
Long contentsId,
`@Schema`(type = "Long", description = "미디어 ID", example = "1")
Long mediaId,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/api-admin/src/main/java/com/ott/api_admin/content/dto/response/ContentsDetailResponse.java`
around lines 12 - 13, The response field name in ContentsDetailResponse (the
field contentsId) doesn't match the request path parameter name mediaId and
differs from ContentsListResponse which uses mediaId; update
ContentsDetailResponse to either rename contentsId to mediaId or add an
additional mediaId field while keeping contentsId (to preserve compatibility),
and update related serializers/constructors/factory methods that populate
ContentsDetailResponse (look for usages of the ContentsDetailResponse class and
its constructor/mapper) so the returned JSON contains mediaId consistently with
the endpoint and ContentsListResponse.

@phonil phonil merged commit 3189824 into develop Feb 22, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

feat 새로운 기능 구현

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[OT-73] [FEAT]: 백오피스 콘텐츠 관리 API 개발

2 participants