From 5df9f5a6126817fe50a36980d73271328d2a956d Mon Sep 17 00:00:00 2001 From: C00LGIRL Date: Fri, 12 Jun 2026 11:45:18 +0300 Subject: [PATCH 1/3] feat: match results and rating notification via websocket --- .../backend/PreMatch/DTO/MatchResultDTO.java | 3 +++ .../backend/PreMatch/DTO/WebSocketDTO.java | 3 ++- .../PreMatch/MatchDeliveryListener.java | 25 ++++++++++++++++++- .../events/MatchResultNotifyEvent.java | 13 ++++++++++ .../backend/Rating/RatingService.java | 20 +++++++++++++-- 5 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 src/main/java/com/codzilla/backend/PreMatch/DTO/MatchResultDTO.java create mode 100644 src/main/java/com/codzilla/backend/PreMatch/events/MatchResultNotifyEvent.java 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..8b48b62 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.winnerId().toString(), + "/queue/match-result", + new WebSocketDTO( + WebSocketDTO.Status.MATCH_FINISHED, + new MatchResultDTO("WIN", event.winnerNewRating(), event.winnerRatingDelta()) + ) + ); + + messagingTemplate.convertAndSendToUser( + event.loserId().toString(), + "/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..5767bcd --- /dev/null +++ b/src/main/java/com/codzilla/backend/PreMatch/events/MatchResultNotifyEvent.java @@ -0,0 +1,13 @@ +package com.codzilla.backend.PreMatch.events; + +import java.util.UUID; + +public record MatchResultNotifyEvent( + UUID matchId, + UUID winnerId, + UUID loserId, + 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..a97509e 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,16 @@ 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.getRating(), + Math.abs(winner.getRating() - winnerOldRating), + loser.getRating(), + Math.abs(loser.getRating() - loserOldRating) + )); } private void applyResult(User user, Glicko2.Result r, Instant now) { From 526bf9f008413b044352f1e96f3f42583d4f6669 Mon Sep 17 00:00:00 2001 From: C00LGIRL Date: Fri, 12 Jun 2026 12:27:21 +0300 Subject: [PATCH 2/3] fix: route match result over websocket by email instead of uuid --- .../com/codzilla/backend/PreMatch/MatchDeliveryListener.java | 4 ++-- .../backend/PreMatch/events/MatchResultNotifyEvent.java | 2 ++ src/main/java/com/codzilla/backend/Rating/RatingService.java | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/codzilla/backend/PreMatch/MatchDeliveryListener.java b/src/main/java/com/codzilla/backend/PreMatch/MatchDeliveryListener.java index 8b48b62..bd8fb26 100644 --- a/src/main/java/com/codzilla/backend/PreMatch/MatchDeliveryListener.java +++ b/src/main/java/com/codzilla/backend/PreMatch/MatchDeliveryListener.java @@ -57,7 +57,7 @@ public void handleDraftSessionFinished(DraftSessionFinishedEvent event) { @EventListener public void handleMatchResult(MatchResultNotifyEvent event) { messagingTemplate.convertAndSendToUser( - event.winnerId().toString(), + event.winnerEmail(), "/queue/match-result", new WebSocketDTO( WebSocketDTO.Status.MATCH_FINISHED, @@ -66,7 +66,7 @@ public void handleMatchResult(MatchResultNotifyEvent event) { ); messagingTemplate.convertAndSendToUser( - event.loserId().toString(), + event.loserEmail(), "/queue/match-result", new WebSocketDTO( WebSocketDTO.Status.MATCH_FINISHED, diff --git a/src/main/java/com/codzilla/backend/PreMatch/events/MatchResultNotifyEvent.java b/src/main/java/com/codzilla/backend/PreMatch/events/MatchResultNotifyEvent.java index 5767bcd..31d612c 100644 --- a/src/main/java/com/codzilla/backend/PreMatch/events/MatchResultNotifyEvent.java +++ b/src/main/java/com/codzilla/backend/PreMatch/events/MatchResultNotifyEvent.java @@ -6,6 +6,8 @@ public record MatchResultNotifyEvent( UUID matchId, UUID winnerId, UUID loserId, + String winnerEmail, + String loserEmail, int winnerNewRating, int winnerRatingDelta, int loserNewRating, diff --git a/src/main/java/com/codzilla/backend/Rating/RatingService.java b/src/main/java/com/codzilla/backend/Rating/RatingService.java index a97509e..b047f87 100644 --- a/src/main/java/com/codzilla/backend/Rating/RatingService.java +++ b/src/main/java/com/codzilla/backend/Rating/RatingService.java @@ -66,6 +66,8 @@ public void recalculate(UUID matchId, UUID winnerId, UUID loserId) { matchId, winnerId, loserId, + winner.getEmail(), + loser.getEmail(), winner.getRating(), Math.abs(winner.getRating() - winnerOldRating), loser.getRating(), From 04a2d9c80fe83dabe95af38ead11dfa05e65f0f1 Mon Sep 17 00:00:00 2001 From: C00LGIRL Date: Fri, 12 Jun 2026 15:02:07 +0300 Subject: [PATCH 3/3] fix: RatingServiceTest --- .../backend/Rating/RatingServiceTest.java | 40 ++++++++++++++++++- 1 file changed, 38 insertions(+), 2 deletions(-) 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