diff --git a/src/main/java/org/bytefight/webserver/competition/application/PlayerCompetitionService.java b/src/main/java/org/bytefight/webserver/competition/application/PlayerCompetitionService.java new file mode 100644 index 00000000..e1363fe7 --- /dev/null +++ b/src/main/java/org/bytefight/webserver/competition/application/PlayerCompetitionService.java @@ -0,0 +1,53 @@ +package org.bytefight.webserver.competition.application; + +import lombok.RequiredArgsConstructor; + +import java.util.List; + +import org.bytefight.webserver.competition.domain.dto.PlayerCompetitionDto; +import org.bytefight.webserver.glicko.infra.TeamStatsRepository; +import org.bytefight.webserver.player.domain.Player; +import org.bytefight.webserver.team.domain.Team; +import org.bytefight.webserver.team.domain.TeamMember; +import org.bytefight.webserver.team.infra.TeamMemberRepository; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class PlayerCompetitionService { + private final TeamMemberRepository teamMemberRepository; + private final TeamStatsRepository teamStatsRepository; + + public List getPlayerCompetitions(Player player) { + List memberships = teamMemberRepository.findByPlayerAndTeamIsDeletedFalse(player); + + return memberships.stream() + .map(membership -> { + Team team = membership.getTeam(); + var competition = team.getCompetition(); + + List memberDtos = + teamMemberRepository.findByTeam(team).stream() + .map(TeamMember::getPlayer) + .map(p -> new PlayerCompetitionDto.MemberDto( + p.getUser().getUuid().toString(), p.getUsername())) + .toList(); + + List rankingDtos = + teamStatsRepository + .findTeamRanksByCompetition(competition.getId(), team.getId()) + .stream() + .map(r -> new PlayerCompetitionDto.RankingDto(r.getLadder(), r.getRank())) + .toList(); + + return new PlayerCompetitionDto( + competition.getSlug(), + competition.getName(), + team.getName(), + team.getUuid().toString(), + memberDtos, + rankingDtos); + }) + .toList(); + } +} diff --git a/src/main/java/org/bytefight/webserver/competition/domain/dto/PlayerCompetitionDto.java b/src/main/java/org/bytefight/webserver/competition/domain/dto/PlayerCompetitionDto.java new file mode 100644 index 00000000..e9e0313c --- /dev/null +++ b/src/main/java/org/bytefight/webserver/competition/domain/dto/PlayerCompetitionDto.java @@ -0,0 +1,28 @@ +package org.bytefight.webserver.competition.domain.dto; + +import jakarta.validation.constraints.NotNull; +import lombok.Value; + +import java.util.List; + +@Value +public class PlayerCompetitionDto { + @NotNull String competitionSlug; + @NotNull String competitionName; + @NotNull String teamName; + @NotNull String teamUuid; + @NotNull List members; + @NotNull List rankings; + + @Value + public static class MemberDto { + @NotNull String uuid; + @NotNull String username; + } + + @Value + public static class RankingDto { + @NotNull String ladder; + Integer rank; + } +} diff --git a/src/main/java/org/bytefight/webserver/glicko/domain/TeamRankRow.java b/src/main/java/org/bytefight/webserver/glicko/domain/TeamRankRow.java new file mode 100644 index 00000000..bc658730 --- /dev/null +++ b/src/main/java/org/bytefight/webserver/glicko/domain/TeamRankRow.java @@ -0,0 +1,7 @@ +package org.bytefight.webserver.glicko.domain; + +public interface TeamRankRow { + String getLadder(); + + Integer getRank(); +} diff --git a/src/main/java/org/bytefight/webserver/glicko/infra/TeamStatsRepository.java b/src/main/java/org/bytefight/webserver/glicko/infra/TeamStatsRepository.java index 4fbe3ea1..e4a979ac 100644 --- a/src/main/java/org/bytefight/webserver/glicko/infra/TeamStatsRepository.java +++ b/src/main/java/org/bytefight/webserver/glicko/infra/TeamStatsRepository.java @@ -5,6 +5,7 @@ import java.util.Optional; import org.bytefight.webserver.competition.domain.Competition; +import org.bytefight.webserver.glicko.domain.TeamRankRow; import org.bytefight.webserver.glicko.domain.TeamStats; import org.bytefight.webserver.glicko.domain.TeamStatsAggregate; import org.bytefight.webserver.leaderboard.domain.LeaderboardRow; @@ -39,6 +40,30 @@ List findAllByCompetitionAndLadderOrderByGlickoRatingDesc( TeamStatsAggregate getTeamStatsAggregateByTeamAndLadder( @Param("team") Team team, @Param("ladders") Collection ladders); + @Query( + value = + """ + SELECT ladder, rank FROM ( + SELECT + ts.ladder AS ladder, + ts.team_id AS team_id, + CASE + WHEN ts.matches_played = 0 THEN NULL + ELSE DENSE_RANK() OVER ( + PARTITION BY ts.ladder + ORDER BY (CASE WHEN ts.matches_played = 0 THEN NULL ELSE ts.glicko_rating END) DESC NULLS LAST + ) + END AS rank + FROM team_stats ts + JOIN teams t ON t.id = ts.team_id AND t.is_deleted = false + WHERE ts.competition_id = :competitionId + ) AS subquery + WHERE team_id = :teamId + """, + nativeQuery = true) + List findTeamRanksByCompetition( + @Param("competitionId") Long competitionId, @Param("teamId") Long teamId); + @Query( value = """ diff --git a/src/main/java/org/bytefight/webserver/player/infra/PrivatePlayerController.java b/src/main/java/org/bytefight/webserver/player/infra/PrivatePlayerController.java index 5bab51bf..4085ef35 100644 --- a/src/main/java/org/bytefight/webserver/player/infra/PrivatePlayerController.java +++ b/src/main/java/org/bytefight/webserver/player/infra/PrivatePlayerController.java @@ -5,6 +5,10 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import java.util.List; + +import org.bytefight.webserver.competition.application.PlayerCompetitionService; +import org.bytefight.webserver.competition.domain.dto.PlayerCompetitionDto; import org.bytefight.webserver.player.application.PlayerService; import org.bytefight.webserver.player.domain.Player; import org.bytefight.webserver.player.domain.SelfPlayerDto; @@ -14,6 +18,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.transaction.annotation.Transactional; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PatchMapping; @@ -30,6 +35,7 @@ public class PrivatePlayerController { private final PlayerService playerService; private final TeamService teamService; + private final PlayerCompetitionService playerCompetitionService; @Operation(operationId = "getCurrentPlayer", summary = "Get current player profile") @GetMapping("/me") @@ -41,6 +47,19 @@ public ResponseEntity getCurrentPlayer(@AuthenticationPrincipal U .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)))); } + @Operation(operationId = "getMyCompetitions", summary = "Get all competitions the current player has participated in") + @GetMapping("/me/competitions") + @Transactional + public ResponseEntity> getMyCompetitions( + @AuthenticationPrincipal User user) { + Player player = + playerService + .getPlayer(user) + .orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); + + return ResponseEntity.ok(playerCompetitionService.getPlayerCompetitions(player)); + } + @Operation(operationId = "updateCurrentPlayer", summary = "Update current player profile") @PatchMapping("/me") public ResponseEntity updateCurrentPlayer( diff --git a/src/main/java/org/bytefight/webserver/team/infra/TeamMemberRepository.java b/src/main/java/org/bytefight/webserver/team/infra/TeamMemberRepository.java index 7c1842de..2a7bcae4 100644 --- a/src/main/java/org/bytefight/webserver/team/infra/TeamMemberRepository.java +++ b/src/main/java/org/bytefight/webserver/team/infra/TeamMemberRepository.java @@ -15,6 +15,8 @@ @Repository public interface TeamMemberRepository extends JpaRepository { + List findByPlayerAndTeamIsDeletedFalse(Player player); + boolean existsByCompetitionAndPlayerAndTeamIsDeletedFalse(Competition competition, Player player); Optional findByCompetitionAndPlayerAndTeamIsDeletedFalse(