Skip to content

feat: 마이페이지 기능 구현#54

Merged
Bumnote merged 3 commits intodevfrom
feat/member-mypage
Mar 22, 2026
Merged

feat: 마이페이지 기능 구현#54
Bumnote merged 3 commits intodevfrom
feat/member-mypage

Conversation

@Bumnote
Copy link
Copy Markdown
Member

@Bumnote Bumnote commented Mar 22, 2026

#️⃣ 연관된 이슈

#51

📝 작업 내용

image

Feat

  • 마이페이지 기능을 구현했습니다.
  • 유저 닉네임, 최고 점수, 최고 등수를 확인할 수 있습니다.
  • 유저의 현재 타이핑 이력을 확인할 수 있습니다.
  • 유저의 주간 활동 즉, 주간 최고 점수들로 이루어진 꺾은 그래프를 확인할 수 있습니다.

Refactor

  • 주간 최고 기록의 의미가 다소 명확하지 않아 일별 최고 기록으로 내용을 변경했습니다.
  • 이에 대한 서비스 로직 및 조회 쿼리도 변경했습니다.

Test

  • 마이 페이지에 사용되는 데이터를 정확하게 불러오는지 테스트합니다.
  • 타이핑 이력이 최신순으로 정렬되어있는지 테스트합니다.
  • 주간 최고 점수 데이터들로만 반환하는지 테스트합니다.

💬 리뷰 요구사항

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


Bumnote added 3 commits March 22, 2026 22:21
- 마이페이지 기능을 구현했습니다.
- 유저 닉네임, 최고 점수, 최고 등수를 확인할 수 있습니다.
- 유저의 현재 타이핑 이력을 확인할 수 있습니다.
- 유저의 주간 활동 즉, 주간 최고 점수들로 이루어진 꺾은 그래프를 확인할 수 있습니다.

issue #51
- 마이 페이지에 사용되는 데이터를 정확하게 불러오는지 테스트합니다.
- 타이핑 이력이 최신순으로 정렬되어있는지 테스트합니다.
- 주간 최고 점수 데이터들로만 반환하는지 테스트합니다.

issue #51
- 주간 최고 기록의 의미가 다소 명확하지 않아 일별 최고 기록으로 내용을 변경했습니다.
- 이에 대한 서비스 로직 및 조회 쿼리도 변경했습니다.

issue #51
@Bumnote Bumnote requested a review from Copilot March 22, 2026 13:49
@Bumnote Bumnote self-assigned this Mar 22, 2026
@Bumnote Bumnote added bug Something isn't working enhancement New feature or request labels Mar 22, 2026
@Bumnote Bumnote merged commit e55944e into dev Mar 22, 2026
2 checks passed
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

마이페이지 API를 추가해 사용자 기본 정보(닉네임/총 타이핑 수/최고점/현재 랭킹), 타이핑 이력, 그리고 최근 90일 일별 최고점(daily scores) 데이터를 조회할 수 있도록 구성한 PR입니다. (이슈 #51의 Member 도메인 “마이페이지 기능” 항목에 해당)

Changes:

  • /api/v1/mypage 엔드포인트 및 MyPageService 조회 로직 추가
  • 타이핑 랭킹 산정용 TypingRepository.findRanking 네이티브 쿼리 변경(스코어 기준)
  • 마이페이지 응답 DTO 및 서비스 테스트 추가

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/main/java/dasi/typing/api/controller/mypage/MyPageController.java 마이페이지 조회 API 엔드포인트 추가
src/main/java/dasi/typing/api/service/mypage/MyPageService.java 마이페이지 집계 로직(기본정보/이력/일별 최고점/랭킹 fallback) 구현
src/main/java/dasi/typing/api/controller/mypage/response/MyPageResponse.java 마이페이지 응답 스키마(typingHistories, dailyScores 포함) 추가
src/main/java/dasi/typing/api/controller/mypage/response/TypingHistoryResponse.java 타이핑 이력 응답 DTO 추가
src/main/java/dasi/typing/api/controller/mypage/response/DailyScoreResponse.java 일별 최고점 응답 DTO 추가
src/main/java/dasi/typing/api/controller/mypage/response/WeeklyScoreResponse.java 주간 최고점 DTO 추가(현재 코드에서는 미사용)
src/main/java/dasi/typing/domain/typing/TypingRepository.java 랭킹 계산 쿼리 수정
src/test/java/dasi/typing/api/service/mypage/MyPageServiceTest.java 마이페이지 서비스 통합 테스트 추가

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +19 to +21

@JsonFormat(pattern = "yyyy-MM-dd")
private final LocalDateTime createdDate;
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

createdDate 타입이 LocalDateTime인데 @JsonFormat(pattern = "yyyy-MM-dd")로 날짜만 직렬화하도록 되어 있어 시간 정보가 손실됩니다. 의도가 날짜만 노출이라면 필드를 LocalDate로 바꾸고, 시간까지 필요하다면 패턴을 date-time 형식으로 수정해 주세요.

Copilot uses AI. Check for mistakes.
Comment on lines +7 to +18
public record WeeklyScoreResponse(
@JsonFormat(pattern = "yyyy-MM-dd")
LocalDate weekStartDate,
Integer highestScore
) {

@Builder
public WeeklyScoreResponse(LocalDate weekStartDate, Integer highestScore) {
this.weekStartDate = weekStartDate;
this.highestScore = highestScore;
}
}
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

WeeklyScoreResponse가 추가됐지만 현재 main 코드에서 참조되는 곳이 없어(dead code) DailyScoreResponse/dailyScores와 혼재되어 혼란을 줄 수 있습니다. 주간 API를 제공하지 않는 방향이라면 이 DTO를 제거하고, 제공할 계획이라면 MyPageResponse/서비스 로직과 함께 실제로 사용되도록 연결해 주세요.

Suggested change
public record WeeklyScoreResponse(
@JsonFormat(pattern = "yyyy-MM-dd")
LocalDate weekStartDate,
Integer highestScore
) {
@Builder
public WeeklyScoreResponse(LocalDate weekStartDate, Integer highestScore) {
this.weekStartDate = weekStartDate;
this.highestScore = highestScore;
}
}
// WeeklyScoreResponse DTO was removed because it was not referenced anywhere in the main codebase
// and could cause confusion alongside DailyScoreResponse/dailyScores.
// If a weekly API is introduced in the future, reintroduce an appropriate DTO and wire it into
// MyPageResponse and the corresponding service logic.

Copilot uses AI. Check for mistakes.
Comment on lines +124 to +131
// then
assertThat(response.nickname()).isEqualTo("빈데이터유저");
assertThat(response.totalTypingCount()).isZero();
assertThat(response.highestScore()).isNull();
assertThat(response.currentRanking()).isNull();
assertThat(response.typingHistories()).isEmpty();
assertThat(response.weeklyScores()).isEmpty();
}
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

MyPageResponsedailyScores()를 제공하는데, 테스트에서는 weeklyScores()를 호출하고 있어 컴파일이 깨집니다. 서비스/응답 DTO에 맞춰 테스트를 dailyScores() 기반으로 수정하거나, 응답 DTO를 weeklyScores로 유지하려면 MyPageResponse/MyPageService 쪽을 함께 변경해 일관성을 맞춰주세요.

Copilot uses AI. Check for mistakes.

// then
assertThat(weeklyScores).isNotEmpty();
assertThat(weeklyScores).extracting(WeeklyScoreResponse::getHighestScore)
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

WeeklyScoreResponse는 record라서 접근자가 highestScore() 형태인데, 테스트에서 WeeklyScoreResponse::getHighestScore를 참조하고 있어 컴파일이 깨집니다. record 접근자에 맞게 수정하거나 DTO를 class(+getter)로 변경해 주세요.

Suggested change
assertThat(weeklyScores).extracting(WeeklyScoreResponse::getHighestScore)
assertThat(weeklyScores).extracting(WeeklyScoreResponse::highestScore)

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +160
@Test
@DisplayName("주간 최고 점수 데이터가 반환된다.")
void getMyPageWeeklyScoresTest() {
// given
Member member = memberRepository.save(new Member("kakao_weekly", "주간테스트"));
Phrase phrase = phraseRepository.save(createPhrase("주간 점수 테스트 문장"));

typingRepository.save(createTyping(100, 0.90, 20, 120, member, phrase));
typingRepository.save(createTyping(300, 0.95, 60, 320, member, phrase));
typingRepository.save(createTyping(200, 0.88, 40, 220, member, phrase));

// when
MyPageResponse response = myPageService.getMyPage("kakao_weekly");
List<WeeklyScoreResponse> weeklyScores = response.weeklyScores();

// then
assertThat(weeklyScores).isNotEmpty();
assertThat(weeklyScores).extracting(WeeklyScoreResponse::getHighestScore)
.allMatch(score -> score > 0);
}
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

현재 서비스는 일별 최고점(dailyScores)을 반환하는데 테스트 케이스는 ‘주간’ 최고점/WeeklyScoreResponse를 가정하고 있습니다. 리팩터링 의도(주간→일별)에 맞춰 테스트명/검증 대상 DTO를 DailyScoreResponse로 변경하고, 반환 리스트가 날짜 오름차순/일별 1개로 집계되는지도 함께 검증하는 편이 좋습니다.

Copilot uses AI. Check for mistakes.
Comment on lines +115 to 119
SELECT COUNT(DISTINCT t.score) + 1 AS ranking
FROM typing t
WHERE t.score > :targetScore
""", nativeQuery = true)
Long findRanking(@Param("targetScore") int targetScore);
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

findRanking이 이제 점수만으로 랭킹을 계산(COUNT(DISTINCT t.score))해서 동점(같은 score)에서 max_cpm/acc tie-break가 사라졌습니다. Redis/DB의 다른 랭킹 로직은 (score, maxCpm, acc)로 정렬하고 있어 결과가 불일치할 수 있으니, 메서드 시그니처에 targetMaxCpm, targetAcc를 추가해 튜플 비교로 계산하거나(이전 의도 유지), 아니면 메서드/응답 필드명을 ‘scoreRank’처럼 명확히 바꿔 의미를 고정해 주세요.

Suggested change
SELECT COUNT(DISTINCT t.score) + 1 AS ranking
FROM typing t
WHERE t.score > :targetScore
""", nativeQuery = true)
Long findRanking(@Param("targetScore") int targetScore);
SELECT COUNT(DISTINCT t.score, t.max_cpm, t.acc) + 1 AS ranking
FROM typing t
WHERE t.score > :targetScore
OR (t.score = :targetScore AND t.max_cpm > :targetMaxCpm)
OR (t.score = :targetScore AND t.max_cpm = :targetMaxCpm AND t.acc > :targetAcc)
""", nativeQuery = true)
Long findRanking(
@Param("targetScore") int targetScore,
@Param("targetMaxCpm") int targetMaxCpm,
@Param("targetAcc") double targetAcc
);

Copilot uses AI. Check for mistakes.
Comment on lines +70 to +75
private List<TypingHistoryResponse> getTypingHistories(Member member) {
List<Typing> typings = typingRepository.findByMemberOrderByCreatedDateDesc(member);
return typings.stream()
.map(TypingHistoryResponse::from)
.toList();
}
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

getTypingHistories에서 typing.getPhrase().getSentence()를 접근하면서 Typing.phrase가 LAZY라면 타이핑 건수만큼 추가 쿼리가 발생(N+1)합니다. TypingRepository에 phrase를 fetch join 하거나, 필요한 컬럼만 조회하는 projection DTO 쿼리를 추가해서 한 번의 쿼리로 가져오도록 개선하는 게 좋습니다.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants