Skip to content

Commit 20f6d46

Browse files
authored
[feat] 유저 닉네임을 통해 랭킹 조회 (#91)
1 parent 4245a04 commit 20f6d46

File tree

4 files changed

+106
-3
lines changed

4 files changed

+106
-3
lines changed

backend/src/main/java/io/f1/backend/domain/stat/api/StatController.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
import org.springframework.data.web.PageableDefault;
1212
import org.springframework.http.ResponseEntity;
1313
import org.springframework.web.bind.annotation.GetMapping;
14+
import org.springframework.web.bind.annotation.PathVariable;
1415
import org.springframework.web.bind.annotation.RequestMapping;
1516
import org.springframework.web.bind.annotation.RestController;
1617

@@ -29,4 +30,13 @@ public ResponseEntity<StatPageResponse> getRankings(
2930

3031
return ResponseEntity.ok().body(response);
3132
}
33+
34+
@LimitPageSize
35+
@GetMapping("/rankings/{nickname}")
36+
public ResponseEntity<StatPageResponse> getRankingsByNickname(
37+
@PathVariable String nickname, @PageableDefault Pageable pageable) {
38+
StatPageResponse response =
39+
statService.getRanksByNickname(nickname, pageable.getPageSize());
40+
return ResponseEntity.ok().body(response);
41+
}
3242
}

backend/src/main/java/io/f1/backend/domain/stat/app/StatService.java

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,49 @@
55
import io.f1.backend.domain.stat.dao.StatRepository;
66
import io.f1.backend.domain.stat.dto.StatPageResponse;
77
import io.f1.backend.domain.stat.dto.StatWithNickname;
8+
import io.f1.backend.global.exception.CustomException;
9+
import io.f1.backend.global.exception.errorcode.RoomErrorCode;
810

911
import lombok.RequiredArgsConstructor;
1012

1113
import org.springframework.data.domain.Page;
14+
import org.springframework.data.domain.PageRequest;
1215
import org.springframework.data.domain.Pageable;
16+
import org.springframework.data.domain.Sort;
17+
import org.springframework.data.domain.Sort.Direction;
1318
import org.springframework.stereotype.Service;
19+
import org.springframework.transaction.annotation.Transactional;
1420

1521
@Service
1622
@RequiredArgsConstructor
1723
public class StatService {
1824

1925
private final StatRepository statRepository;
2026

27+
@Transactional(readOnly = true)
2128
public StatPageResponse getRanks(Pageable pageable) {
22-
Page<StatWithNickname> stats = statRepository.findWithUser(pageable);
29+
Page<StatWithNickname> stats = statRepository.findAllStatsWithUser(pageable);
2330
return toStatListPageResponse(stats);
2431
}
32+
33+
@Transactional(readOnly = true)
34+
public StatPageResponse getRanksByNickname(String nickname, int pageSize) {
35+
36+
Page<StatWithNickname> stats =
37+
statRepository.findAllStatsWithUser(getPageableFromNickname(nickname, pageSize));
38+
39+
return toStatListPageResponse(stats);
40+
}
41+
42+
private Pageable getPageableFromNickname(String nickname, int pageSize) {
43+
long score =
44+
statRepository
45+
.findScoreByNickname(nickname)
46+
.orElseThrow(() -> new CustomException(RoomErrorCode.PLAYER_NOT_FOUND));
47+
48+
long rowNum = statRepository.countByScoreGreaterThan(score);
49+
50+
int pageNumber = rowNum > 0 ? (int) (rowNum / pageSize) : 0;
51+
return PageRequest.of(pageNumber, pageSize, Sort.by(Direction.DESC, "score"));
52+
}
2553
}

backend/src/main/java/io/f1/backend/domain/stat/dao/StatRepository.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@
88
import org.springframework.data.jpa.repository.JpaRepository;
99
import org.springframework.data.jpa.repository.Query;
1010

11+
import java.util.Optional;
12+
1113
public interface StatRepository extends JpaRepository<Stat, Long> {
1214

1315
@Query(
@@ -18,5 +20,10 @@ public interface StatRepository extends JpaRepository<Stat, Long> {
1820
FROM
1921
Stat s JOIN s.user u
2022
""")
21-
Page<StatWithNickname> findWithUser(Pageable pageable);
23+
Page<StatWithNickname> findAllStatsWithUser(Pageable pageable);
24+
25+
@Query("SELECT s.score FROM Stat s WHERE s.user.nickname = :nickname")
26+
Optional<Long> findScoreByNickname(String nickname);
27+
28+
long countByScoreGreaterThan(Long score);
2229
}

backend/src/test/java/io/f1/backend/domain/stat/StatBrowserTest.java

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package io.f1.backend.domain.stat;
22

33
import static io.f1.backend.global.exception.errorcode.CommonErrorCode.INVALID_PAGINATION;
4+
import static io.f1.backend.global.exception.errorcode.RoomErrorCode.PLAYER_NOT_FOUND;
45

56
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
67
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
@@ -35,7 +36,7 @@ void totalRankingForSingleUser() throws Exception {
3536

3637
@Test
3738
@DisplayName("100을 넘는 페이지 크기 요청이 오면 예외를 발생시킨다")
38-
void totalRankingForSingleUserWithInvalidPageSize() throws Exception {
39+
void totalRankingWithInvalidPageSize() throws Exception {
3940
// when
4041
ResultActions result = mockMvc.perform(get("/stats/rankings").param("size", "101"));
4142

@@ -86,4 +87,61 @@ void totalRankingForThreeUserWithPageSize2() throws Exception {
8687
jsonPath("$.totalElements").value(1),
8788
jsonPath("$.ranks.length()").value(1));
8889
}
90+
91+
@Test
92+
@DataSet("datasets/stat/three-user-stat.yml")
93+
@DisplayName("랭킹 페이지에서 존재하지 않는 닉네임을 검색하면 예외를 발생시킨다.")
94+
void totalRankingWithUnregisteredNickname() throws Exception {
95+
// given
96+
String nickname = "UNREGISTERED";
97+
98+
// when
99+
ResultActions result = mockMvc.perform(get("/stats/rankings/" + nickname));
100+
101+
// then
102+
result.andExpectAll(
103+
status().isNotFound(), jsonPath("$.code").value(PLAYER_NOT_FOUND.getCode()));
104+
}
105+
106+
@Test
107+
@DataSet("datasets/stat/three-user-stat.yml")
108+
@DisplayName("총 유저 수가 3명이고 페이지 크기가 2일 때 1위 유저의 닉네임을 검색하면 첫 번째 페이지에 2개의 결과를 반환한다")
109+
void totalRankingForThreeUserWithFirstRankedNickname() throws Exception {
110+
// given
111+
String nickname = "USER3";
112+
113+
// when
114+
ResultActions result =
115+
mockMvc.perform(get("/stats/rankings/" + nickname).param("size", "2"));
116+
117+
// then
118+
result.andExpectAll(
119+
status().isOk(),
120+
jsonPath("$.totalPages").value(2),
121+
jsonPath("$.currentPage").value(1),
122+
jsonPath("$.totalElements").value(2),
123+
jsonPath("$.ranks.length()").value(2),
124+
jsonPath("$.ranks[0].nickname").value(nickname));
125+
}
126+
127+
@Test
128+
@DataSet("datasets/stat/three-user-stat.yml")
129+
@DisplayName("총 유저 수가 3명이고 페이지 크기가 2일 때 3위 유저의 닉네임을 검색하면 두 번째 페이지에 1개의 결과를 반환한다")
130+
void totalRankingForThreeUserWithLastRankedNickname() throws Exception {
131+
// given
132+
String nickname = "USER1";
133+
134+
// when
135+
ResultActions result =
136+
mockMvc.perform(get("/stats/rankings/" + nickname).param("size", "2"));
137+
138+
// then
139+
result.andExpectAll(
140+
status().isOk(),
141+
jsonPath("$.totalPages").value(2),
142+
jsonPath("$.currentPage").value(2),
143+
jsonPath("$.totalElements").value(1),
144+
jsonPath("$.ranks.length()").value(1),
145+
jsonPath("$.ranks[0].nickname").value(nickname));
146+
}
89147
}

0 commit comments

Comments
 (0)