diff --git a/src/main/java/com/codzilla/backend/PreMatch/DTO/MatchResultDTO.java b/src/main/java/com/codzilla/backend/PreMatch/DTO/MatchResultDTO.java new file mode 100644 index 0000000..edf669c --- /dev/null +++ b/src/main/java/com/codzilla/backend/PreMatch/DTO/MatchResultDTO.java @@ -0,0 +1,3 @@ +package com.codzilla.backend.PreMatch.DTO; + +public record MatchResultDTO(String outcome, int newRating, int ratingDelta) {} \ No newline at end of file diff --git a/src/main/java/com/codzilla/backend/PreMatch/DTO/WebSocketDTO.java b/src/main/java/com/codzilla/backend/PreMatch/DTO/WebSocketDTO.java index dc0bc7c..b034ee2 100644 --- a/src/main/java/com/codzilla/backend/PreMatch/DTO/WebSocketDTO.java +++ b/src/main/java/com/codzilla/backend/PreMatch/DTO/WebSocketDTO.java @@ -11,7 +11,8 @@ public class WebSocketDTO { public enum Status { MATCH_STARTED_REDIRECT, DRAFT, - LIVE + LIVE, + MATCH_FINISHED } public WebSocketDTO(Status status, Object payload) { diff --git a/src/main/java/com/codzilla/backend/PreMatch/MatchDeliveryListener.java b/src/main/java/com/codzilla/backend/PreMatch/MatchDeliveryListener.java index 2c78e01..bd8fb26 100644 --- a/src/main/java/com/codzilla/backend/PreMatch/MatchDeliveryListener.java +++ b/src/main/java/com/codzilla/backend/PreMatch/MatchDeliveryListener.java @@ -1,8 +1,9 @@ package com.codzilla.backend.PreMatch; import com.codzilla.backend.PreMatch.DTO.DraftSessionResponseDTO; +import com.codzilla.backend.PreMatch.DTO.MatchResultDTO; import com.codzilla.backend.PreMatch.DTO.WebSocketDTO; -import com.codzilla.backend.PreMatch.MatchRoom.Match; +import com.codzilla.backend.PreMatch.events.MatchResultNotifyEvent; import com.codzilla.backend.PreMatch.MatchRoom.MatchService; import com.codzilla.backend.PreMatch.events.DraftSessionFinishedEvent; import com.codzilla.backend.PreMatch.events.DraftSessionUpdatedEvent; @@ -51,4 +52,26 @@ public void handleDraftSessionFinished(DraftSessionFinishedEvent event) { ) ); } + + @Async + @EventListener + public void handleMatchResult(MatchResultNotifyEvent event) { + messagingTemplate.convertAndSendToUser( + event.winnerEmail(), + "/queue/match-result", + new WebSocketDTO( + WebSocketDTO.Status.MATCH_FINISHED, + new MatchResultDTO("WIN", event.winnerNewRating(), event.winnerRatingDelta()) + ) + ); + + messagingTemplate.convertAndSendToUser( + event.loserEmail(), + "/queue/match-result", + new WebSocketDTO( + WebSocketDTO.Status.MATCH_FINISHED, + new MatchResultDTO("LOSE", event.loserNewRating(), event.loserRatingDelta()) + ) + ); + } } diff --git a/src/main/java/com/codzilla/backend/PreMatch/events/MatchResultNotifyEvent.java b/src/main/java/com/codzilla/backend/PreMatch/events/MatchResultNotifyEvent.java new file mode 100644 index 0000000..31d612c --- /dev/null +++ b/src/main/java/com/codzilla/backend/PreMatch/events/MatchResultNotifyEvent.java @@ -0,0 +1,15 @@ +package com.codzilla.backend.PreMatch.events; + +import java.util.UUID; + +public record MatchResultNotifyEvent( + UUID matchId, + UUID winnerId, + UUID loserId, + String winnerEmail, + String loserEmail, + int winnerNewRating, + int winnerRatingDelta, + int loserNewRating, + int loserRatingDelta +) {} diff --git a/src/main/java/com/codzilla/backend/Rating/RatingService.java b/src/main/java/com/codzilla/backend/Rating/RatingService.java index f54c85b..b047f87 100644 --- a/src/main/java/com/codzilla/backend/Rating/RatingService.java +++ b/src/main/java/com/codzilla/backend/Rating/RatingService.java @@ -1,9 +1,11 @@ package com.codzilla.backend.Rating; +import com.codzilla.backend.PreMatch.events.MatchResultNotifyEvent; import com.codzilla.backend.User.User; import com.codzilla.backend.User.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -18,15 +20,16 @@ public class RatingService { private final UserRepository userRepository; + private final ApplicationEventPublisher eventPublisher; private final Glicko2 glicko2 = new Glicko2(); @EventListener @Transactional public void onMatchFinished(MatchFinishedEvent event) { - recalculate(event.winnerId(), event.loserId()); + recalculate(event.matchId(), event.winnerId(), event.loserId()); } - public void recalculate(UUID winnerId, UUID loserId) { + public void recalculate(UUID matchId, UUID winnerId, UUID loserId) { User winner = userRepository.findById(winnerId).orElse(null); User loser = userRepository.findById(loserId).orElse(null); if (winner == null || loser == null) { @@ -34,6 +37,9 @@ public void recalculate(UUID winnerId, UUID loserId) { return; } + int winnerOldRating = winner.getRating(); + int loserOldRating = loser.getRating(); + Glicko2.Opponent loserAsOpponentOfWinner = new Glicko2.Opponent(loser.getRating().doubleValue(), loser.getRatingDeviation(), 1.0); Glicko2.Opponent winnerAsOpponentOfLoser = @@ -55,6 +61,18 @@ public void recalculate(UUID winnerId, UUID loserId) { log.info("Rating updated: winner {} -> {}, loser {} -> {}", winnerId, winner.getRating(), loserId, loser.getRating()); + + eventPublisher.publishEvent(new MatchResultNotifyEvent( + matchId, + winnerId, + loserId, + winner.getEmail(), + loser.getEmail(), + winner.getRating(), + Math.abs(winner.getRating() - winnerOldRating), + loser.getRating(), + Math.abs(loser.getRating() - loserOldRating) + )); } private void applyResult(User user, Glicko2.Result r, Instant now) { diff --git a/src/test/java/com/codzilla/backend/Rating/RatingServiceTest.java b/src/test/java/com/codzilla/backend/Rating/RatingServiceTest.java index 1516e57..8ff54c9 100644 --- a/src/test/java/com/codzilla/backend/Rating/RatingServiceTest.java +++ b/src/test/java/com/codzilla/backend/Rating/RatingServiceTest.java @@ -1,11 +1,14 @@ package com.codzilla.backend.Rating; +import com.codzilla.backend.PreMatch.events.MatchResultNotifyEvent; import com.codzilla.backend.User.User; import com.codzilla.backend.User.UserRepository; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.context.ApplicationEventPublisher; import java.util.Optional; import java.util.UUID; @@ -19,6 +22,9 @@ class RatingServiceTest { @Mock private UserRepository userRepository; + @Mock + private ApplicationEventPublisher eventPublisher; + private User newUser(UUID id, int rating) { User u = User.builder() .email(id + "@test.com") @@ -32,7 +38,7 @@ private User newUser(UUID id, int rating) { @Test void matchFinishedEvent_winnerGains_loserLoses() { - RatingService service = new RatingService(userRepository); + RatingService service = new RatingService(userRepository, eventPublisher); UUID winnerId = UUID.randomUUID(); UUID loserId = UUID.randomUUID(); @@ -59,9 +65,38 @@ void matchFinishedEvent_winnerGains_loserLoses() { verify(userRepository).save(loser); } + @Test + void matchFinishedEvent_publishesNotifyEventWithEmailsAndDeltas() { + RatingService service = new RatingService(userRepository, eventPublisher); + + UUID winnerId = UUID.randomUUID(); + UUID loserId = UUID.randomUUID(); + User winner = newUser(winnerId, 1500); + User loser = newUser(loserId, 1500); + + when(userRepository.findById(winnerId)).thenReturn(Optional.of(winner)); + when(userRepository.findById(loserId)).thenReturn(Optional.of(loser)); + + service.onMatchFinished(new MatchFinishedEvent(UUID.randomUUID(), winnerId, loserId)); + + ArgumentCaptor captor = + ArgumentCaptor.forClass(MatchResultNotifyEvent.class); + verify(eventPublisher).publishEvent(captor.capture()); + MatchResultNotifyEvent published = captor.getValue(); + + assertThat(published.winnerEmail()).isEqualTo(winner.getEmail()); + assertThat(published.loserEmail()).isEqualTo(loser.getEmail()); + + assertThat(published.winnerNewRating()).isEqualTo(winner.getRating()); + assertThat(published.loserNewRating()).isEqualTo(loser.getRating()); + + assertThat(published.winnerRatingDelta()).isEqualTo(162); + assertThat(published.loserRatingDelta()).isEqualTo(162); + } + @Test void unknownUser_skipsWithoutSaving() { - RatingService service = new RatingService(userRepository); + RatingService service = new RatingService(userRepository, eventPublisher); UUID winnerId = UUID.randomUUID(); UUID loserId = UUID.randomUUID(); @@ -71,5 +106,6 @@ void unknownUser_skipsWithoutSaving() { service.onMatchFinished(new MatchFinishedEvent(UUID.randomUUID(), winnerId, loserId)); verify(userRepository, never()).save(any()); + verify(eventPublisher, never()).publishEvent(any()); } } \ No newline at end of file