diff --git a/backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java b/backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java index 865841c1..cc18db6a 100644 --- a/backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java +++ b/backend/src/main/java/org/sejongisc/backend/betting/controller/BettingController.java @@ -10,7 +10,6 @@ import org.sejongisc.backend.betting.entity.UserBet; import org.sejongisc.backend.betting.service.BettingService; import org.sejongisc.backend.common.auth.springsecurity.CustomUserDetails; -import org.sejongisc.backend.user.entity.User; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.validation.annotation.Validated; @@ -67,4 +66,11 @@ public ResponseEntity cancelUserBet( return ResponseEntity.noContent().build(); } + @GetMapping("/user-bets/history") + public ResponseEntity> getAllUserBets( + @AuthenticationPrincipal CustomUserDetails principal){ + List userBets = bettingService.getAllMyBets(principal.getUserId()); + + return ResponseEntity.ok(userBets); + } } diff --git a/backend/src/main/java/org/sejongisc/backend/betting/entity/UserBet.java b/backend/src/main/java/org/sejongisc/backend/betting/entity/UserBet.java index a0bd3ca8..693e84fc 100644 --- a/backend/src/main/java/org/sejongisc/backend/betting/entity/UserBet.java +++ b/backend/src/main/java/org/sejongisc/backend/betting/entity/UserBet.java @@ -10,6 +10,16 @@ import java.util.UUID; @Entity +@Table( + uniqueConstraints = @UniqueConstraint( + name = "uk_user_bet_round_user", + columnNames = {"round_id", "user_id"} + ), + indexes = { + @Index(name = "idx_user_bet_user", columnList = "user_id"), + @Index(name = "idx_user_bet_round", columnList = "round_id") + } +) @Getter @Builder @NoArgsConstructor @AllArgsConstructor public class UserBet extends BasePostgresEntity { diff --git a/backend/src/main/java/org/sejongisc/backend/betting/repository/UserBetRepository.java b/backend/src/main/java/org/sejongisc/backend/betting/repository/UserBetRepository.java index 991e8e96..a1e81052 100644 --- a/backend/src/main/java/org/sejongisc/backend/betting/repository/UserBetRepository.java +++ b/backend/src/main/java/org/sejongisc/backend/betting/repository/UserBetRepository.java @@ -4,10 +4,14 @@ import org.sejongisc.backend.betting.entity.UserBet; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; import java.util.Optional; import java.util.UUID; public interface UserBetRepository extends JpaRepository { boolean existsByRoundAndUserId(BetRound round, UUID userId); Optional findByUserBetIdAndUserId(UUID userBetId, UUID userId); + + + List findAllByUserIdOrderByRound_SettleAtDesc(UUID userId); } diff --git a/backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java b/backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java index 82a33565..638408e2 100644 --- a/backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java +++ b/backend/src/main/java/org/sejongisc/backend/betting/service/BettingService.java @@ -11,6 +11,7 @@ import org.sejongisc.backend.point.entity.PointOrigin; import org.sejongisc.backend.point.entity.PointReason; import org.sejongisc.backend.point.service.PointHistoryService; +import org.springframework.dao.DataIntegrityViolationException; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -54,6 +55,11 @@ public boolean setAllowFree(){ return random.nextDouble() < 0.2; } + public List getAllMyBets(UUID userId) { + // TODO : 필요 시 필터링, 정렬, 검색 로직 추가 + return userBetRepository.findAllByUserIdOrderByRound_SettleAtDesc(userId); + } + public void createBetRound(Scope scope) { Stock stock = getStock(); LocalDateTime now = LocalDateTime.now(); @@ -88,7 +94,8 @@ public UserBet postUserBet(UUID userId, UserBetRequest userBetRequest) { LocalDateTime now = LocalDateTime.now(); - if (now.isBefore(betRound.getOpenAt()) || now.isAfter(betRound.getLockAt())) { + // 허용 구간: [openAt, lockAt) + if (now.isBefore(betRound.getOpenAt()) || !now.isBefore(betRound.getLockAt())) { throw new CustomException(ErrorCode.BET_TIME_INVALID); } @@ -114,7 +121,12 @@ public UserBet postUserBet(UUID userId, UserBetRequest userBetRequest) { .betStatus(BetStatus.ACTIVE) .build(); - return userBetRepository.save(userBet); + try { + return userBetRepository.save(userBet); + } catch (DataIntegrityViolationException e) { + // DB 유니크 제약(중복 베팅) 위반 시 + throw new CustomException(ErrorCode.BET_DUPLICATE); + } } @Transactional @@ -124,7 +136,7 @@ public void cancelUserBet(UUID userId, UUID userBetId) { BetRound betRound = userBet.getRound(); - if (LocalDateTime.now().isAfter(betRound.getLockAt())){ + if (!LocalDateTime.now().isBefore(betRound.getLockAt())){ throw new CustomException(ErrorCode.BET_ROUND_CLOSED); } diff --git a/backend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTest.java b/backend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTest.java index 76b84ba0..26d26a20 100644 --- a/backend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTest.java +++ b/backend/src/test/java/org/sejongisc/backend/betting/service/BettingServiceTest.java @@ -234,6 +234,51 @@ void findActiveRound_Weekly_Success() { verify(betRoundRepository, times(1)).findByStatusTrueAndScope(Scope.WEEKLY); } + @Test + @DisplayName("getAllMyBets() - 유저 ID로 조회 시 Repository 호출 및 결과 반환 확인") + void getAllMyBets_Success() { + // given + UUID userId = UUID.randomUUID(); + BetRound round = BetRound.builder() + .title("테스트 라운드") + .openAt(LocalDateTime.now().minusHours(2)) + .lockAt(LocalDateTime.now().plusHours(1)) + .settleAt(LocalDateTime.now().plusHours(2)) + .build(); + + UserBet bet1 = UserBet.builder() + .userBetId(UUID.randomUUID()) + .round(round) + .userId(userId) + .option(BetOption.RISE) + .stakePoints(100) + .isFree(false) + .build(); + + UserBet bet2 = UserBet.builder() + .userBetId(UUID.randomUUID()) + .round(round) + .userId(userId) + .option(BetOption.FALL) + .stakePoints(50) + .isFree(true) + .build(); + + List mockResult = List.of(bet1, bet2); + when(userBetRepository.findAllByUserIdOrderByRound_SettleAtDesc(userId)) + .thenReturn(mockResult); + + // when + List result = bettingService.getAllMyBets(userId); + + // then + verify(userBetRepository, times(1)) + .findAllByUserIdOrderByRound_SettleAtDesc(userId); + assertThat(result).hasSize(2); + assertThat(result.get(0).getUserId()).isEqualTo(userId); + assertThat(result.get(1).getRound().getTitle()).isEqualTo("테스트 라운드"); + } + private BetRound openRoundNow() { LocalDateTime now = LocalDateTime.now(); return BetRound.builder()