diff --git a/src/main/java/com/codzilla/backend/MatchRoom/Match.java b/src/main/java/com/codzilla/backend/MatchRoom/Match.java index 37d7b6c..5501989 100644 --- a/src/main/java/com/codzilla/backend/MatchRoom/Match.java +++ b/src/main/java/com/codzilla/backend/MatchRoom/Match.java @@ -48,6 +48,10 @@ public enum Status { boolean ratingApplied = false; + Integer firstUserRating; + + Integer secondUserRating; + @JdbcTypeCode(SqlTypes.JSON) @Column(name = "options", columnDefinition = "jsonb") Map options; diff --git a/src/main/java/com/codzilla/backend/MatchRoom/MatchController.java b/src/main/java/com/codzilla/backend/MatchRoom/MatchController.java new file mode 100644 index 0000000..1b0dc3c --- /dev/null +++ b/src/main/java/com/codzilla/backend/MatchRoom/MatchController.java @@ -0,0 +1,25 @@ +package com.codzilla.backend.MatchRoom; + +import com.codzilla.backend.MatchRoom.dto.MatchHistoryEntryDTO; +import com.codzilla.backend.User.User; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequestMapping("/match") +@RequiredArgsConstructor +public class MatchController { + + private final MatchService matchService; + + @GetMapping("/history") + public ResponseEntity> getMatchHistory(@AuthenticationPrincipal User user) { + return ResponseEntity.ok(matchService.getMatchHistory(user.getId())); + } +} diff --git a/src/main/java/com/codzilla/backend/MatchRoom/MatchRepository.java b/src/main/java/com/codzilla/backend/MatchRoom/MatchRepository.java index 043855c..27678fc 100644 --- a/src/main/java/com/codzilla/backend/MatchRoom/MatchRepository.java +++ b/src/main/java/com/codzilla/backend/MatchRoom/MatchRepository.java @@ -1,8 +1,19 @@ package com.codzilla.backend.MatchRoom; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; +import java.util.List; import java.util.UUID; public interface MatchRepository extends JpaRepository { + + @Query(""" + SELECT m FROM Match m + WHERE m.status = com.codzilla.backend.MatchRoom.Match.Status.FINISHED + AND (m.firstUserId = :userId OR m.secondUserId = :userId) + ORDER BY m.finishedAt DESC + """) + List findFinishedByUser(@Param("userId") UUID userId); } diff --git a/src/main/java/com/codzilla/backend/MatchRoom/MatchService.java b/src/main/java/com/codzilla/backend/MatchRoom/MatchService.java index 59d7ee6..27261e3 100644 --- a/src/main/java/com/codzilla/backend/MatchRoom/MatchService.java +++ b/src/main/java/com/codzilla/backend/MatchRoom/MatchService.java @@ -1,5 +1,6 @@ package com.codzilla.backend.MatchRoom; +import com.codzilla.backend.MatchRoom.dto.MatchHistoryEntryDTO; import com.codzilla.backend.Rating.MatchFinishedEvent; import com.codzilla.backend.PreMatch.DraftSession.DraftSession; import com.codzilla.backend.PreMatch.DraftSession.DraftSessionService; @@ -10,6 +11,8 @@ import com.codzilla.backend.judge.problem.Problem; import com.codzilla.backend.judge.problem.ProblemRepository; import com.codzilla.backend.judge.problem.ProblemService; +import com.codzilla.backend.User.User; +import com.codzilla.backend.User.UserRepository; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.messaging.simp.SimpMessagingTemplate; @@ -18,6 +21,7 @@ import org.springframework.context.ApplicationEventPublisher; import java.time.Instant; +import java.util.List; import java.util.Map; import java.util.UUID; @@ -33,6 +37,7 @@ public class MatchService { private final ProblemRepository problemRepository; private final SqlServiceClient sqlServiceClient; private final ProblemService problemService; + private final UserRepository userRepository; private final ApplicationEventPublisher eventPublisher; public UUID startMatch(UUID firstUserId, UUID secondUserId) { @@ -113,4 +118,27 @@ public Problem pickProblemOfOptions(Map options) { public Match getMatchById(UUID matchId) { return matchRepository.getReferenceById(matchId); } + + @Transactional(readOnly = true) + public List getMatchHistory(UUID userId) { + return matchRepository.findFinishedByUser(userId).stream() + .map(match -> { + UUID opponentId = match.opponentOf(userId); + String opponentNickname = userRepository.findById(opponentId) + .map(User::getNickname) + .orElse("Неизвестный соперник"); + boolean won = userId.equals(match.getWinnerId()); + Integer rating = userId.equals(match.getFirstUserId()) + ? match.getFirstUserRating() + : match.getSecondUserRating(); + return new MatchHistoryEntryDTO( + match.getId(), + opponentNickname, + won, + rating, + match.getFinishedAt() + ); + }) + .toList(); + } } diff --git a/src/main/java/com/codzilla/backend/MatchRoom/dto/MatchHistoryEntryDTO.java b/src/main/java/com/codzilla/backend/MatchRoom/dto/MatchHistoryEntryDTO.java new file mode 100644 index 0000000..8faf503 --- /dev/null +++ b/src/main/java/com/codzilla/backend/MatchRoom/dto/MatchHistoryEntryDTO.java @@ -0,0 +1,12 @@ +package com.codzilla.backend.MatchRoom.dto; + +import java.time.Instant; +import java.util.UUID; + +public record MatchHistoryEntryDTO( + UUID matchId, + String opponentNickname, + boolean won, + Integer rating, + Instant finishedAt +) {} diff --git a/src/main/java/com/codzilla/backend/Rating/RatingService.java b/src/main/java/com/codzilla/backend/Rating/RatingService.java index b047f87..b9c935b 100644 --- a/src/main/java/com/codzilla/backend/Rating/RatingService.java +++ b/src/main/java/com/codzilla/backend/Rating/RatingService.java @@ -1,5 +1,7 @@ package com.codzilla.backend.Rating; +import com.codzilla.backend.MatchRoom.Match; +import com.codzilla.backend.MatchRoom.MatchRepository; import com.codzilla.backend.PreMatch.events.MatchResultNotifyEvent; import com.codzilla.backend.User.User; import com.codzilla.backend.User.UserRepository; @@ -20,6 +22,7 @@ public class RatingService { private final UserRepository userRepository; + private final MatchRepository matchRepository; private final ApplicationEventPublisher eventPublisher; private final Glicko2 glicko2 = new Glicko2(); @@ -59,6 +62,8 @@ public void recalculate(UUID matchId, UUID winnerId, UUID loserId) { userRepository.save(winner); userRepository.save(loser); + saveRatingSnapshot(matchId, winnerId, winner.getRating(), loser.getRating()); + log.info("Rating updated: winner {} -> {}, loser {} -> {}", winnerId, winner.getRating(), loserId, loser.getRating()); @@ -75,6 +80,22 @@ public void recalculate(UUID matchId, UUID winnerId, UUID loserId) { )); } + private void saveRatingSnapshot(UUID matchId, UUID winnerId, int winnerRating, int loserRating) { + Match match = matchRepository.findById(matchId).orElse(null); + if (match == null) { + log.warn("saveRatingSnapshot: match {} not found", matchId); + return; + } + if (winnerId.equals(match.getFirstUserId())) { + match.setFirstUserRating(winnerRating); + match.setSecondUserRating(loserRating); + } else { + match.setFirstUserRating(loserRating); + match.setSecondUserRating(winnerRating); + } + matchRepository.save(match); + } + private void applyResult(User user, Glicko2.Result r, Instant now) { user.setRating((int) Math.round(r.rating())); user.setRatingDeviation(r.rd()); diff --git a/src/test/java/com/codzilla/backend/Rating/RatingServiceTest.java b/src/test/java/com/codzilla/backend/Rating/RatingServiceTest.java index 8ff54c9..56003ee 100644 --- a/src/test/java/com/codzilla/backend/Rating/RatingServiceTest.java +++ b/src/test/java/com/codzilla/backend/Rating/RatingServiceTest.java @@ -1,5 +1,6 @@ package com.codzilla.backend.Rating; +import com.codzilla.backend.MatchRoom.MatchRepository; import com.codzilla.backend.PreMatch.events.MatchResultNotifyEvent; import com.codzilla.backend.User.User; import com.codzilla.backend.User.UserRepository; @@ -22,6 +23,9 @@ class RatingServiceTest { @Mock private UserRepository userRepository; + @Mock + private MatchRepository matchRepository; + @Mock private ApplicationEventPublisher eventPublisher; @@ -38,7 +42,7 @@ private User newUser(UUID id, int rating) { @Test void matchFinishedEvent_winnerGains_loserLoses() { - RatingService service = new RatingService(userRepository, eventPublisher); + RatingService service = new RatingService(userRepository, matchRepository, eventPublisher); UUID winnerId = UUID.randomUUID(); UUID loserId = UUID.randomUUID(); @@ -67,7 +71,7 @@ void matchFinishedEvent_winnerGains_loserLoses() { @Test void matchFinishedEvent_publishesNotifyEventWithEmailsAndDeltas() { - RatingService service = new RatingService(userRepository, eventPublisher); + RatingService service = new RatingService(userRepository, matchRepository, eventPublisher); UUID winnerId = UUID.randomUUID(); UUID loserId = UUID.randomUUID(); @@ -96,7 +100,7 @@ void matchFinishedEvent_publishesNotifyEventWithEmailsAndDeltas() { @Test void unknownUser_skipsWithoutSaving() { - RatingService service = new RatingService(userRepository, eventPublisher); + RatingService service = new RatingService(userRepository, matchRepository, eventPublisher); UUID winnerId = UUID.randomUUID(); UUID loserId = UUID.randomUUID();