Skip to content

Commit 3ef099b

Browse files
authored
Merge pull request #34 from mayademcom/BAATP-57-Create-Leaderboard-REST-Endpoints
BAATP-57:Create Leaderboard REST Endpoints
2 parents f88e194 + 6a9744b commit 3ef099b

11 files changed

Lines changed: 201 additions & 49 deletions

src/main/java/com/mayadem/battlearena/api/controller/BattleController.java

Lines changed: 52 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
package com.mayadem.battlearena.api.controller;
2+
23
import java.util.List;
34

45
import org.springframework.http.HttpStatus;
@@ -15,19 +16,24 @@
1516
import org.springframework.web.bind.annotation.RestController;
1617
import com.mayadem.battlearena.api.dto.BattleHistorySummaryDto;
1718
import com.mayadem.battlearena.api.dto.BattleRoomDto;
19+
import com.mayadem.battlearena.api.dto.LeaderboardEntryDto;
20+
import com.mayadem.battlearena.api.dto.LeaderboardStatsDto;
1821
import com.mayadem.battlearena.api.dto.StartBattleRequestDto;
1922
import com.mayadem.battlearena.api.dto.SubmitBattleResultRequestDto;
2023
import com.mayadem.battlearena.api.entity.Warrior;
2124
import com.mayadem.battlearena.api.exception.ResourceNotFoundException;
2225
import com.mayadem.battlearena.api.service.BattleCompletionService;
2326
import com.mayadem.battlearena.api.service.BattleRoomService;
24-
import com.mayadem.battlearena.api.dto.BattleHistoryPageDto;
27+
import com.mayadem.battlearena.api.service.LeaderboardService;
28+
import com.mayadem.battlearena.api.dto.BattleHistoryPageDto;
2529
import com.mayadem.battlearena.api.entity.enums.BattleType;
2630
import com.mayadem.battlearena.api.service.BattleHistoryService;
27-
import org.springframework.data.domain.PageRequest;
28-
import org.springframework.data.domain.Pageable;
31+
import org.springframework.data.domain.PageRequest;
32+
import org.springframework.data.domain.Pageable;
2933
import java.util.Optional;
3034
import jakarta.validation.Valid;
35+
36+
import com.mayadem.battlearena.api.dto.ArenaLeaderboardDto;
3137
import com.mayadem.battlearena.api.dto.BattleHistoryDto;
3238

3339
@RestController
@@ -37,12 +43,15 @@ public class BattleController {
3743
private final BattleRoomService battleRoomService;
3844
private final BattleCompletionService battleCompletionService;
3945
private final BattleHistoryService battleHistoryService;
46+
private final LeaderboardService leaderboardService;
4047
private static final int MAX_PAGE_SIZE = 100;
4148

42-
public BattleController(BattleRoomService battleRoomService, BattleCompletionService battleCompletionService, BattleHistoryService battleHistoryService) {
49+
public BattleController(BattleRoomService battleRoomService, BattleCompletionService battleCompletionService,
50+
BattleHistoryService battleHistoryService, LeaderboardService leaderboardService) {
4351
this.battleRoomService = battleRoomService;
4452
this.battleCompletionService = battleCompletionService;
4553
this.battleHistoryService = battleHistoryService;
54+
this.leaderboardService = leaderboardService;
4655
}
4756

4857
@PostMapping
@@ -77,7 +86,7 @@ public ResponseEntity<List<BattleRoomDto>> getAvailableBattles() {
7786

7887
@PostMapping("/submit-result")
7988
public ResponseEntity<Object> submitBattleResult(@Valid @RequestBody SubmitBattleResultRequestDto request,
80-
@AuthenticationPrincipal Warrior requester){
89+
@AuthenticationPrincipal Warrior requester) {
8190
try {
8291
Object result = battleCompletionService.submitAndProcessScore(request, requester);
8392

@@ -99,25 +108,25 @@ public ResponseEntity<String> getBattleStatus(@PathVariable Long battleRoomId) {
99108
.map(ResponseEntity::ok)
100109
.orElse(ResponseEntity.badRequest().body("BattleRoom not found."));
101110
}
111+
102112
@GetMapping("/history")
103113
public ResponseEntity<BattleHistoryPageDto> getBattleHistory(
104-
@RequestParam(defaultValue = "0") int page,
105-
@RequestParam(defaultValue = "10") int size,
106-
@RequestParam(required = false) BattleType battleType,
107-
@AuthenticationPrincipal Warrior warrior) {
114+
@RequestParam(defaultValue = "0") int page,
115+
@RequestParam(defaultValue = "10") int size,
116+
@RequestParam(required = false) BattleType battleType,
117+
@AuthenticationPrincipal Warrior warrior) {
108118

109-
validatePageableParameters(page, size);
119+
validatePageableParameters(page, size);
110120

111-
Pageable pageable = PageRequest.of(page, size);
121+
Pageable pageable = PageRequest.of(page, size);
112122

113-
BattleHistoryPageDto historyPage = battleHistoryService.getWarriorBattleHistory(
114-
warrior,
115-
Optional.ofNullable(battleType),
116-
pageable
117-
);
123+
BattleHistoryPageDto historyPage = battleHistoryService.getWarriorBattleHistory(
124+
warrior,
125+
Optional.ofNullable(battleType),
126+
pageable);
118127

119-
return ResponseEntity.ok(historyPage);
120-
}
128+
return ResponseEntity.ok(historyPage);
129+
}
121130

122131
private void validatePageableParameters(int page, int size) {
123132
if (page < 0) {
@@ -130,17 +139,41 @@ private void validatePageableParameters(int page, int size) {
130139
throw new IllegalArgumentException("Page size must not be greater than " + MAX_PAGE_SIZE);
131140
}
132141
}
133-
@GetMapping("/history/{battleRoomId}")
142+
143+
@GetMapping("/history/{battleRoomId}")
134144
public ResponseEntity<BattleHistoryDto> getBattleDetails(
135145
@PathVariable Long battleRoomId,
136146
@AuthenticationPrincipal Warrior warrior) {
137147

138148
BattleHistoryDto battleDetails = battleHistoryService.getBattleDetails(warrior, battleRoomId);
139149
return ResponseEntity.ok(battleDetails);
140150
}
151+
141152
@GetMapping("/history/stats")
142153
public ResponseEntity<BattleHistorySummaryDto> getBattleStats(@AuthenticationPrincipal Warrior warrior) {
143154
BattleHistorySummaryDto summary = battleHistoryService.getBattleHistorySummary(warrior);
144155
return ResponseEntity.ok(summary);
145156
}
157+
158+
// Ana leaderboard
159+
@GetMapping("/leaderboard")
160+
public ResponseEntity<ArenaLeaderboardDto> getArenaLeaderboard(@AuthenticationPrincipal Warrior currentWarrior) {
161+
ArenaLeaderboardDto dto = leaderboardService.getArenaLeaderboard(currentWarrior.getId());
162+
return ResponseEntity.ok(dto);
163+
}
164+
165+
@GetMapping("/leaderboard/around-me")
166+
public ResponseEntity<List<LeaderboardEntryDto>> getLeaderboardAroundMe(
167+
@RequestParam(defaultValue = "5") int range,
168+
@AuthenticationPrincipal Warrior currentWarrior) {
169+
List<LeaderboardEntryDto> around = leaderboardService.getWarriorsAroundMe(currentWarrior.getId(), range);
170+
return ResponseEntity.ok(around);
171+
}
172+
173+
// Global stats
174+
@GetMapping("/leaderboard/stats")
175+
public ResponseEntity<LeaderboardStatsDto> getLeaderboardStats() {
176+
LeaderboardStatsDto stats = leaderboardService.getGlobalStats();
177+
return ResponseEntity.ok(stats);
178+
}
146179
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package com.mayadem.battlearena.api.dto;
2+
3+
import com.mayadem.battlearena.api.entity.enums.BattleType;
4+
5+
public record BattleTypeStatsDto(
6+
BattleType battleType,
7+
int battles,
8+
int victories,
9+
int defeats,
10+
int draws,
11+
double winRate,
12+
int bestScore,
13+
double averageScore,
14+
int rankPointsGained
15+
) {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.mayadem.battlearena.api.dto;
2+
3+
public record GlobalComparisonDto(
4+
double averageWinRate,
5+
double averageScore,
6+
int averageBattles
7+
) {}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.mayadem.battlearena.api.dto;
2+
3+
import com.mayadem.battlearena.api.dto.enums.StreakType;
4+
5+
public record OverallStatsDto(
6+
int totalBattles,
7+
int victories,
8+
int defeats,
9+
int draws,
10+
double winRate,
11+
int bestScore,
12+
double averageScore,
13+
int totalRankPointsGained,
14+
int currentStreak,
15+
StreakType streakType,
16+
int longestWinStreak
17+
) {}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.mayadem.battlearena.api.dto;
2+
3+
import java.util.Map;
4+
5+
import com.mayadem.battlearena.api.dto.enums.PerformanceTrend;
6+
7+
public record RecentPerformanceDto(
8+
int battlesLast30Days,
9+
int victoriesLast30Days,
10+
double winRateLast30Days,
11+
int rankPointsChangeLast30Days,
12+
PerformanceTrend performanceTrend,
13+
Map<String, Integer> dailyStats
14+
) {}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package com.mayadem.battlearena.api.dto;
2+
3+
import java.util.List;
4+
5+
public record WarriorDetailedStatsDto(
6+
String username,
7+
String displayName,
8+
int currentRankPoints,
9+
Integer currentRank,
10+
OverallStatsDto overallStats,
11+
List<BattleTypeStatsDto> statsByType,
12+
RecentPerformanceDto recentPerformance,
13+
GlobalComparisonDto globalComparison
14+
) {}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.mayadem.battlearena.api.dto.enums;
2+
3+
public enum PerformanceTrend {
4+
IMPROVING,
5+
STEADY,
6+
DECLINING
7+
}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package com.mayadem.battlearena.api.dto.enums;
2+
3+
public enum StreakType {
4+
WIN,
5+
LOSS,
6+
NONE
7+
}
Lines changed: 30 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.mayadem.battlearena.api.repository;
22

33
import com.mayadem.battlearena.api.entity.ArenaLeaderboard;
4-
import com.mayadem.battlearena.api.dto.LeaderboardStatsDto;
54
import org.springframework.data.domain.Pageable;
65
import org.springframework.data.jpa.repository.JpaRepository;
76
import org.springframework.data.jpa.repository.Query;
@@ -12,24 +11,38 @@
1211

1312
public interface ArenaLeaderboardRepository extends JpaRepository<ArenaLeaderboard, Long> {
1413

14+
interface LeaderboardStatsProjection {
15+
Long getTotalActiveWarriors();
16+
17+
Double getAverageRankPoints();
18+
19+
Integer getHighestRankPoints();
20+
21+
String getTopWarriorUsername();
22+
}
23+
1524
// En iyi 100 oyuncu (JPQL'de LIMIT yok → Pageable kullanılmalı)
1625
@Query("SELECT a FROM ArenaLeaderboard a ORDER BY a.rankPoints DESC")
1726
List<ArenaLeaderboard> findTopPlayers(Pageable pageable);
1827

19-
// Belirli oyuncunun pozisyonu
20-
@Query("SELECT a FROM ArenaLeaderboard a WHERE a.id = :warriorId")
21-
Optional<ArenaLeaderboard> findWarriorPosition(@Param("warriorId") Long warriorId);
22-
23-
// Global istatistikler
24-
@Query("SELECT new com.mayadem.battlearena.api.dto.LeaderboardStatsDto( " +
25-
"COUNT(a), AVG(a.rankPoints), MAX(a.rankPoints), MAX(a.username)) " +
26-
"FROM ArenaLeaderboard a")
27-
LeaderboardStatsDto findGlobalStats();
28-
29-
// Rank aralığındaki oyuncular
30-
@Query("SELECT a FROM ArenaLeaderboard a " +
31-
"WHERE a.rankPosition BETWEEN :startRank AND :endRank " +
32-
"ORDER BY a.rankPosition ASC")
33-
List<ArenaLeaderboard> findWarriorsAroundRank(@Param("startRank") int startRank,
34-
@Param("endRank") int endRank);
28+
// Belirli oyuncunun pozisyonu
29+
@Query("SELECT a FROM ArenaLeaderboard a WHERE a.id = :warriorId")
30+
Optional<ArenaLeaderboard> findWarriorPosition(@Param("warriorId") Long warriorId);
31+
32+
// Global istatistikler
33+
@Query(value = """
34+
SELECT COUNT(*) AS total_active_warriors,
35+
AVG(rank_points) AS average_rank_points,
36+
MAX(rank_points) AS highest_rank_points,
37+
(SELECT username FROM arena_leaderboard ORDER BY rank_points DESC LIMIT 1) AS top_warrior_username
38+
FROM arena_leaderboard
39+
""", nativeQuery = true)
40+
LeaderboardStatsProjection findGlobalStatsProjection();
41+
42+
// Rank aralığındaki oyuncular
43+
@Query("SELECT a FROM ArenaLeaderboard a " +
44+
"WHERE a.rankPosition BETWEEN :startRank AND :endRank " +
45+
"ORDER BY a.rankPosition ASC")
46+
List<ArenaLeaderboard> findWarriorsAroundRank(@Param("startRank") int startRank,
47+
@Param("endRank") int endRank);
3548
}

src/main/java/com/mayadem/battlearena/api/service/BattleCompletionService.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,15 +27,19 @@ public class BattleCompletionService {
2727
private final WarriorRepository warriorRepository;
2828
private final BattleRoomRepository battleRoomRepository;
2929
private final BattleParticipantRepository battleParticipantRepository;
30+
private final LeaderboardService leaderboardService;
3031

3132
public BattleCompletionService(ScoringService scoringService,
3233
WarriorRepository warriorRepository,
3334
BattleRoomRepository battleRoomRepository,
34-
BattleParticipantRepository battleParticipantRepository) {
35+
BattleParticipantRepository battleParticipantRepository,
36+
LeaderboardService leaderboardService) {
3537
this.scoringService = scoringService;
3638
this.warriorRepository = warriorRepository;
3739
this.battleRoomRepository = battleRoomRepository;
3840
this.battleParticipantRepository = battleParticipantRepository;
41+
this.leaderboardService = leaderboardService;
42+
3943
}
4044

4145
@Transactional
@@ -55,11 +59,11 @@ public Object submitAndProcessScore(SubmitBattleResultRequestDto request, Warrio
5559

5660
currentParticipant.setFinalScore(request.getScore());
5761
battleParticipantRepository.save(currentParticipant);
58-
62+
5963
BattleRoom updatedBattleRoom = battleRoomRepository.findById(request.getBattleRoomId())
6064
.orElseThrow(() -> new IllegalStateException("Impossible state: BattleRoom with ID "
6165
+ request.getBattleRoomId() + " disappeared mid-transaction."));
62-
66+
6367
boolean allScoresSubmitted = updatedBattleRoom.getParticipants().stream()
6468
.allMatch(p -> p.getFinalScore() != null);
6569

@@ -114,6 +118,9 @@ private BattleResultResponseDto finalizeBattle(BattleRoom battleRoom, BattlePart
114118
warriorRepository.save(me.getWarrior());
115119
warriorRepository.save(opponent.getWarrior());
116120

121+
leaderboardService.updateFromWarrior(me.getWarrior());
122+
leaderboardService.updateFromWarrior(opponent.getWarrior());
123+
117124
battleRoom.setStatus(BattleStatus.COMPLETED);
118125
battleRoom.setCompletedAt(Instant.now());
119126
battleRoomRepository.save(battleRoom);
@@ -122,7 +129,8 @@ private BattleResultResponseDto finalizeBattle(BattleRoom battleRoom, BattlePart
122129
return BattleResultResponseDto.from(battleRoom, me, opponentDto, myRankPointsChange);
123130
}
124131

125-
private void updateParticipantStats(BattleParticipant participant, BattleResult result, int score, int rankPointsChange) {
132+
private void updateParticipantStats(BattleParticipant participant, BattleResult result, int score,
133+
int rankPointsChange) {
126134
Warrior warrior = participant.getWarrior();
127135
participant.setRankPointsBefore(warrior.getRankPoints());
128136
warrior.setRankPoints(Math.max(0, warrior.getRankPoints() + rankPointsChange));

0 commit comments

Comments
 (0)