Skip to content

[FEAT] 학습 조회 API 구현 (3/3) - 학습 홈 · 커리큘럼 조회 · 단계별 상세 조회 · 실전 반주법 전체보기 - #55

Merged
ownue merged 19 commits into
developfrom
feat/#40-learning-api
Jul 27, 2026
Merged

[FEAT] 학습 조회 API 구현 (3/3) - 학습 홈 · 커리큘럼 조회 · 단계별 상세 조회 · 실전 반주법 전체보기#55
ownue merged 19 commits into
developfrom
feat/#40-learning-api

Conversation

@p1001q

@p1001q p1001q commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

📍 개요

학습 화면 조회 API 6개 중 나머지 4개(학습 홈, 커리큘럼 조회, 단계별 상세 조회, 실전 반주법 전체보기) 구현

이슈 #40 작업의 마지막 PR입니다.
PR #50(1/3)에서 학습 주제 전체보기·실습 데이터 조회 2개를 먼저 병합했고
원래 계획했던 후속 PR 2개(2/3, 3/3)는 진행률 계산 로직을 공유하는 API들이라 하나로 묶어 이번 PR(3/3)로 한 번에 올립니다.

⛓️‍💥 관련 이슈


🛠️ 작업 내용

  • GET /api/learnings/home
    (학습 홈 조회): 최근 학습 이어서 하기(currentLearning), 학습 주제 대표 3개(난이도별), 실전 반주법 대표 3개 조회
  • GET /api/learnings/{learningId}
    (학습 커리큘럼 조회): 패키지 상세 정보, 전체 진행률(progress), 단계별 목록(상태·점수 포함) 조회
  • GET /api/learnings/{learningId}/steps/{learningStepId}
    (학습 단계별 상세 조회): 단계별 이론 설명, 연습 팁, 모범 연주 예시(있으면), 코드 예시 조회
  • GET /api/learnings/accompaniment
    (실전 반주법 전체보기): 난이도 구분 없이 제목순으로 전체 목록 조회, progressRate는 진행률(%)만 반환
  • 목록성 API(홈, 반주법 전체보기)는 학습별 진행률 계산 시 N+1을 피하기 위해 LearningStepRepository/UserLearningProgressRepository에 배치 집계 쿼리 추가
  • 학습 커리큘럼 조회에 유저 존재 검증(ensureUserExists) 추가 — 홈/주제/반주법 조회와 동작 일치
  • 학습 홈 조회 currentLearningnextStepId 추가 — [이어서 학습하기]가 커리큘럼 페이지가 아니라 단계 상세로 바로 이동하도록 확정, 진행률 0%/100%면 카드 자체를 숨기도록 변경
  • 홈 화면 "최근 학습"에서 비활성화(is_active=false)된 패키지는 건너뛰고 그다음으로 최근인 활성 패키지를 조회하도록 수정
  • 학습 API 전체 + 공통 응답(ApiResponse) Swagger @Schema 응답/요청 예시값 추가
  • 각 API 컨트롤러 테스트 및 서비스 테스트 추가

🔥 리뷰 요청 사항

  • 목록 API의 진행률 집계를 배치 쿼리(GROUP BY learning.id)로 처리한 방식이, 개별 조회 대비 적절한 트레이드오프인지
  • 학습 단계별 상세 조회/단계별 연습 실행 정보 조회userId를 안 받아서 유저 존재 검증이 구조적으로 불가능한데(로그인은 여전히 필요), 이 판단이 맞는지
  • nextStepId 계산 시, 정방향(마지막 학습 단계 뒤)에 미완료가 없으면 패키지 전체에서 가장 이른 미완료로 폴백하는 방식이 타당한지

✅ 체크리스트

  • 코드 컨벤션을 준수했습니다.
  • 불필요한 코드 및 import를 제거했습니다.
  • 예외 처리를 적용했습니다.
  • 테스트를 완료했습니다.
  • 관련 Issue를 연결했습니다.

📎 참고 사항

학습 홈 화면 api를 추가하거나 수정해야돼서 잠깐 막아놨습니다. > 풀었어용

Summary by CodeRabbit

  • 새 기능
    • 학습 홈, 커리큘럼, 단계 상세, 반주법 패키지 목록 조회 API를 추가했습니다.
    • 단계 진행 상태/점수 및 전체 진행률을 제공하고, 단계 상세에 연주 정보와 코드 예시를 포함합니다.
    • 홈에서 “다음에 이어서 할 학습”과 추천 이론·반주법 패키지를 표시합니다.
  • 문서화
    • 요청/응답 및 공통 응답 포맷의 API 문서와 예시를 보강했습니다.
  • 개선
    • 인증된 사용자 식별 방식을 일관되게 정리했습니다.
    • 학습 진행률 및 다음 단계 선택 로직을 개선했습니다.
    • 학습 결과 응답의 완료 시각 표기를 명확히 했습니다.

@coderabbitai

coderabbitai Bot commented Jul 26, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 1d45cbe0-d69f-4b6f-a1ab-1895d9aa48ff

📥 Commits

Reviewing files that changed from the base of the PR and between f902ca1 and 9f85c77.

📒 Files selected for processing (1)
  • src/main/java/com/mr/domain/learning/repository/UserLearningProgressRepository.java
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/com/mr/domain/learning/repository/UserLearningProgressRepository.java

📝 Walkthrough

Walkthrough

학습 홈·커리큘럼·단계 상세·반주법 목록 조회 API를 추가하고, 응답 DTO·저장소 조회·진행률 계산·Swagger 문서·테스트를 확장했습니다.

Changes

학습 조회 API

Layer / File(s) Summary
조회 응답 계약과 API 스키마
src/main/java/com/mr/domain/learning/dto/..., src/main/java/com/mr/global/apipayload/ApiResponse.java
홈, 커리큘럼, 단계 상세, 반주법 목록 DTO와 기존 요청·응답의 Swagger 메타데이터를 추가했습니다.
데이터 조회와 진행률 집계
src/main/java/com/mr/domain/learning/repository/...
학습·단계·코드 예시·사용자 진행 기록 조회 및 배치 집계 메서드를 추가했습니다.
조회 서비스와 상태 조립
src/main/java/com/mr/domain/learning/service/LearningService.java
커리큘럼·단계 상세·반주법·홈 응답과 진행률, 단계 상태, 다음 단계 선택 로직을 구현했습니다.
컨트롤러 엔드포인트
src/main/java/com/mr/domain/learning/controller/LearningController.java
인증 사용자 식별을 SecurityUtil로 통일하고 네 개의 GET 엔드포인트를 연결했습니다.
시나리오 테스트
src/test/java/com/mr/domain/learning/...
조회 성공·실패·빈 결과와 진행률·상세 응답·다음 단계 규칙을 검증하고 테스트 표시명을 정리했습니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant LearningController
  participant LearningService
  participant LearningRepository
  participant LearningStepRepository
  participant UserLearningProgressRepository
  Client->>LearningController: 학습 조회 GET 요청
  LearningController->>LearningService: 사용자 ID와 학습 식별자 전달
  LearningService->>LearningRepository: 활성 학습 및 패키지 조회
  LearningService->>LearningStepRepository: 단계 목록 및 총 단계 수 조회
  LearningService->>UserLearningProgressRepository: 진행 기록과 완료 단계 집계
  LearningService-->>LearningController: 응답 DTO 반환
  LearningController-->>Client: ApiResponse 성공 응답
Loading

Possibly related PRs

  • Musereview/BE#45: 학습 결과 저장·진행률 조회 컨트롤러의 사용자 식별 방식 변경과 직접 연결됩니다.
  • Musereview/BE#46: 기존 학습 컨트롤러의 사용자 ID 전달 흐름과 직접 맞물립니다.
  • Musereview/BE#50: 이론 목록 및 관련 응답 DTO 확장과 연결됩니다.

Poem

홈에서 시작해 단계로 가고
진행률은 반듯하게 빛나며
코드 예시는 줄을 맞추고
DTO는 문서를 입었네
학습의 길, API로 찰칵! 🎵

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning 이 PR은 #40의 홈·커리큘럼·단계 상세·반주법 조회는 구현했지만, 요구사항인 practice-data 조회 API는 보이지 않습니다. GET /api/learnings/{learningId}/steps/{learningStepId}/practice-data 엔드포인트와 응답 DTO/검증을 추가해 #40의 조회 API 범위를 완성하세요.
Docstring Coverage ⚠️ Warning Docstring coverage is 22.39% 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 제목이 학습 홈·커리큘럼·단계 상세·반주법 전체보기 추가라는 핵심 변경을 간결하게 담고 있습니다.
Out of Scope Changes check ✅ Passed Swagger @Schema, 테스트 정리, 배치 조회 리포지토리 추가는 모두 학습 조회 API를 지원하는 보조 변경으로 보이며 별도 기능은 없습니다.
✨ 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 feat/#40-learning-api

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.

@p1001q
p1001q marked this pull request as draft July 26, 2026 01:07

@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/mr/domain/learning/dto/res/LearningHomeResponseDTO.java`:
- Around line 15-16: Update the `@Schema` description on
LearningHomeResponseDTO.theoryPackages to state that the list contains up to
three items and excludes difficulty levels without available data; do not
describe it as always containing three items.

In `@src/main/java/com/mr/domain/learning/service/LearningService.java`:
- Around line 186-222: Update buildCurrentLearning and its latest-progress
repository query to select only learning records whose associated package is
active, so an inactive most-recent record is skipped in favor of the next active
record or null when none exists. Add tests covering both fallback to the next
active learning and returning null when all recent records are inactive.
🪄 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.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 88de6f68-31c9-45d1-bdce-880672272f1d

📥 Commits

Reviewing files that changed from the base of the PR and between a5ff3a9 and ef39bf7.

📒 Files selected for processing (18)
  • src/main/java/com/mr/domain/learning/controller/LearningController.java
  • src/main/java/com/mr/domain/learning/dto/req/LearningResultSaveRequestDTO.java
  • src/main/java/com/mr/domain/learning/dto/res/LearningAccompanimentListResponseDTO.java
  • src/main/java/com/mr/domain/learning/dto/res/LearningCurriculumResponseDTO.java
  • src/main/java/com/mr/domain/learning/dto/res/LearningHomeResponseDTO.java
  • src/main/java/com/mr/domain/learning/dto/res/LearningPracticeDataResponseDTO.java
  • src/main/java/com/mr/domain/learning/dto/res/LearningProgressResponseDTO.java
  • src/main/java/com/mr/domain/learning/dto/res/LearningResultResponseDTO.java
  • src/main/java/com/mr/domain/learning/dto/res/LearningStepDetailResponseDTO.java
  • src/main/java/com/mr/domain/learning/dto/res/LearningTheoryListResponseDTO.java
  • src/main/java/com/mr/domain/learning/repository/ChordExampleRepository.java
  • src/main/java/com/mr/domain/learning/repository/LearningRepository.java
  • src/main/java/com/mr/domain/learning/repository/LearningStepRepository.java
  • src/main/java/com/mr/domain/learning/repository/UserLearningProgressRepository.java
  • src/main/java/com/mr/domain/learning/service/LearningService.java
  • src/main/java/com/mr/global/apipayload/ApiResponse.java
  • src/test/java/com/mr/domain/learning/controller/LearningControllerTest.java
  • src/test/java/com/mr/domain/learning/service/LearningServiceTest.java

Comment thread src/main/java/com/mr/domain/learning/dto/res/LearningHomeResponseDTO.java Outdated
Comment thread src/main/java/com/mr/domain/learning/service/LearningService.java
@p1001q
p1001q marked this pull request as ready for review July 26, 2026 06:18
Comment thread src/main/java/com/mr/domain/learning/service/LearningService.java
Comment thread src/main/java/com/mr/domain/learning/service/LearningService.java
Comment thread src/main/java/com/mr/domain/learning/controller/LearningController.java Outdated

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

🤖 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/mr/domain/learning/controller/LearningController.java`:
- Around line 121-126: LearningController.getTheoryList의 difficulty 요청 파라미터를 실제
필수 값으로 검증하세요. `@RequestParam의` required 설정을 true로 변경하거나 기본값을 사용해 파라미터 누락 시 서비스 호출
전에 컨트롤러가 400을 반환하도록 Swagger 계약과 구현을 일치시키세요.
🪄 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.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 87e87a6e-817d-4c8e-b1dc-3cd888570859

📥 Commits

Reviewing files that changed from the base of the PR and between 6332b06 and f902ca1.

📒 Files selected for processing (5)
  • src/main/java/com/mr/domain/learning/controller/LearningController.java
  • src/main/java/com/mr/domain/learning/dto/res/LearningCurriculumResponseDTO.java
  • src/main/java/com/mr/domain/learning/service/LearningService.java
  • src/test/java/com/mr/domain/learning/controller/LearningControllerTest.java
  • src/test/java/com/mr/domain/learning/service/LearningServiceTest.java
💤 Files with no reviewable changes (1)
  • src/test/java/com/mr/domain/learning/controller/LearningControllerTest.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/com/mr/domain/learning/dto/res/LearningCurriculumResponseDTO.java
  • src/test/java/com/mr/domain/learning/service/LearningServiceTest.java
  • src/main/java/com/mr/domain/learning/service/LearningService.java

Comment on lines 121 to +126
public ApiResponse<LearningTheoryListResponseDTO.TheoryListResultDTO> getTheoryList(
@AuthenticationPrincipal CustomUserDetails userDetails,
@Parameter(description = "BEGINNER | INTERMEDIATE | ADVANCED", required = true)
@RequestParam(required = false) String difficulty
) {
LearningTheoryListResponseDTO.TheoryListResultDTO result =
learningService.getTheoryList(userDetails.getUserId(), difficulty);
learningService.getTheoryList(SecurityUtil.getCurrentUserId(), difficulty);

@coderabbitai coderabbitai Bot Jul 26, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

difficulty를 실제 요청에서도 필수로 검증해 주세요.

Swagger와 설명은 필수 파라미터로 선언했지만 @RequestParam(required = false)라서 누락 요청이 null로 서비스에 전달됩니다. GET /api/learnings/theory에서 파라미터가 없으면 컨트롤러가 즉시 400을 반환하도록 required = true(기본값)로 맞춰 API 계약과 구현을 일치시키는 편이 안전합니다.

-            `@RequestParam`(required = false) String difficulty
+            `@RequestParam` String difficulty

PR 목표의 difficulty 필수 계약과 현재 컨트롤러 선언을 대조한 결과입니다.

📝 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
public ApiResponse<LearningTheoryListResponseDTO.TheoryListResultDTO> getTheoryList(
@AuthenticationPrincipal CustomUserDetails userDetails,
@Parameter(description = "BEGINNER | INTERMEDIATE | ADVANCED", required = true)
@RequestParam(required = false) String difficulty
) {
LearningTheoryListResponseDTO.TheoryListResultDTO result =
learningService.getTheoryList(userDetails.getUserId(), difficulty);
learningService.getTheoryList(SecurityUtil.getCurrentUserId(), difficulty);
public ApiResponse<LearningTheoryListResponseDTO.TheoryListResultDTO> getTheoryList(
`@Parameter`(description = "BEGINNER | INTERMEDIATE | ADVANCED", required = true)
`@RequestParam` String difficulty
) {
LearningTheoryListResponseDTO.TheoryListResultDTO result =
learningService.getTheoryList(SecurityUtil.getCurrentUserId(), difficulty);
🤖 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/mr/domain/learning/controller/LearningController.java`
around lines 121 - 126, LearningController.getTheoryList의 difficulty 요청 파라미터를 실제
필수 값으로 검증하세요. `@RequestParam의` required 설정을 true로 변경하거나 기본값을 사용해 파라미터 누락 시 서비스 호출
전에 컨트롤러가 400을 반환하도록 Swagger 계약과 구현을 일치시키세요.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@p1001q 해당 리뷰 확인해 주시면 좋을 것 가타요

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

확인 감사합니다. 다만 이 부분은 의도적으로 required=false로 둔 것이라 그대로 유지하려 합니다.

GlobalExceptionHandlerMissingServletRequestParameterException 전용 핸들러가 없는 상태라, required=true로 바꾸면 파라미터 누락 시 @ExceptionHandler(Exception.class) catch-all로 빠져서 400이 아니라 500이 나가게 됩니다.

지금은 required=false로 받은 뒤 LearningService.parseDifficulty()에서 검증해 LEARNING_400_02(400)로 응답하도록 처리하고 있고, 이 계약은 LearningControllerTest.getTheoryList_missingDifficulty_throws400으로 테스트도 돼 있습니다.

private LearningDifficulty parseDifficulty(String difficulty) {
    if (difficulty == null) {
        throw new GeneralException(LearningErrorStatus.INVALID_DIFFICULTY);
    }
    ...
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

@p1001q
p1001q requested a review from ownue July 26, 2026 17:56

@ownue ownue left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

수고하셨어요!!

# Conflicts:
#	src/main/java/com/mr/domain/learning/repository/UserLearningProgressRepository.java

@kimyw1018 kimyw1018 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

너무 잘 해주셨습니다! 최고~

Comment on lines +64 to +74
public static ProgressInfo of(long completedStepCount, long totalStepCount) {
int progressRate;
if (totalStepCount == 0) {
progressRate = 0;
} else {
int rawProgressRate = (int) Math.round((double) completedStepCount / totalStepCount * 100);
progressRate = Math.min(100, Math.max(0, rawProgressRate));
}
return new ProgressInfo((int) completedStepCount, (int) totalStepCount, progressRate);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

비지니스 로직 일부가 dto에 있는데 이부분 서비스 단으로 빼는건 어떨까요??
사실 상관없을것 같긴 합니다

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

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

✨ Feature - 학습 화면 조회 API 구현

3 participants