Skip to content

Commit 570162c

Browse files
committed
feat: 유저 닉네임을 통해 랭킹 조회
1 parent ceca5e6 commit 570162c

File tree

4 files changed

+108
-1
lines changed

4 files changed

+108
-1
lines changed

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

Lines changed: 11 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,14 @@ 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,
38+
@PageableDefault Pageable pageable
39+
) {
40+
StatPageResponse response = statService.getRanksByNickname(nickname, pageable.getPageSize());
41+
return ResponseEntity.ok().body(response);
42+
}
3243
}

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,50 @@
66
import io.f1.backend.domain.stat.dto.StatPageResponse;
77
import io.f1.backend.domain.stat.dto.StatWithNickname;
88

9+
import io.f1.backend.global.exception.CustomException;
10+
import io.f1.backend.global.exception.errorcode.ErrorCode;
11+
import io.f1.backend.global.exception.errorcode.RoomErrorCode;
12+
13+
import java.util.Optional;
914
import lombok.RequiredArgsConstructor;
1015

1116
import org.springframework.data.domain.Page;
17+
import org.springframework.data.domain.PageRequest;
1218
import org.springframework.data.domain.Pageable;
19+
import org.springframework.data.domain.Sort;
20+
import org.springframework.data.domain.Sort.Direction;
1321
import org.springframework.stereotype.Service;
22+
import org.springframework.transaction.annotation.Transactional;
1423

1524
@Service
25+
@Transactional
1626
@RequiredArgsConstructor
1727
public class StatService {
1828

1929
private final StatRepository statRepository;
2030

31+
@Transactional(readOnly = true)
2132
public StatPageResponse getRanks(Pageable pageable) {
2233
Page<StatWithNickname> stats = statRepository.findWithUser(pageable);
2334
return toStatListPageResponse(stats);
2435
}
36+
37+
@Transactional(readOnly = true)
38+
public StatPageResponse getRanksByNickname(String nickname, int pageSize) {
39+
40+
Page<StatWithNickname> stats = statRepository.findWithUser(
41+
getPageableFromNickname(nickname, pageSize));
42+
43+
return toStatListPageResponse(stats);
44+
}
45+
46+
private Pageable getPageableFromNickname(String nickname, int pageSize) {
47+
long score = statRepository.findScoreByNickname(nickname)
48+
.orElseThrow(() -> new CustomException(RoomErrorCode.PLAYER_NOT_FOUND));
49+
50+
long rowNum = statRepository.countByScoreGreaterThan(score);
51+
52+
int pageNumber = rowNum > 0 ? (int) (rowNum / pageSize) : 0;
53+
return PageRequest.of(pageNumber, pageSize, Sort.by(Direction.DESC, "score"));
54+
}
2555
}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import io.f1.backend.domain.stat.dto.StatWithNickname;
44
import io.f1.backend.domain.stat.entity.Stat;
55

6+
import java.util.Optional;
67
import org.springframework.data.domain.Page;
78
import org.springframework.data.domain.Pageable;
89
import org.springframework.data.jpa.repository.JpaRepository;
@@ -19,4 +20,9 @@ public interface StatRepository extends JpaRepository<Stat, Long> {
1920
Stat s JOIN s.user u
2021
""")
2122
Page<StatWithNickname> findWithUser(Pageable pageable);
23+
24+
@Query("SELECT s.score FROM Stat s WHERE s.user.nickname = :nickname")
25+
Optional<Long> findScoreByNickname(String nickname);
26+
27+
long countByScoreGreaterThan(Long score);
2228
}

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

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22

33
import static io.f1.backend.global.exception.errorcode.CommonErrorCode.INVALID_PAGINATION;
44

5+
import static io.f1.backend.global.exception.errorcode.RoomErrorCode.PLAYER_NOT_FOUND;
56
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
67
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
78
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
89

910
import com.github.database.rider.core.api.dataset.DataSet;
1011

12+
import io.f1.backend.global.exception.errorcode.ErrorCode;
13+
import io.f1.backend.global.exception.errorcode.RoomErrorCode;
1114
import io.f1.backend.global.template.BrowserTestTemplate;
1215

1316
import org.junit.jupiter.api.DisplayName;
@@ -35,7 +38,7 @@ void totalRankingForSingleUser() throws Exception {
3538

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

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

0 commit comments

Comments
 (0)