diff --git a/build.gradle b/build.gradle index b58f065..81baa60 100644 --- a/build.gradle +++ b/build.gradle @@ -35,6 +35,10 @@ dependencies { // REDIS CACHE implementation 'org.springframework.boot:spring-boot-starter-data-redis' implementation 'org.springframework.boot:spring-boot-starter-cache' + +// https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter + implementation 'org.redisson:redisson-spring-boot-starter:3.46.0' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' diff --git a/src/main/java/com/yscp/catchtable/application/queue/StoreQueueService.java b/src/main/java/com/yscp/catchtable/application/queue/StoreQueueService.java index 0ff2f00..a3ec888 100644 --- a/src/main/java/com/yscp/catchtable/application/queue/StoreQueueService.java +++ b/src/main/java/com/yscp/catchtable/application/queue/StoreQueueService.java @@ -1,30 +1,28 @@ package com.yscp.catchtable.application.queue; import com.yscp.catchtable.application.queue.dto.StoreQueueDto; -import com.yscp.catchtable.exception.BadRequestError; -import com.yscp.catchtable.exception.CatchTableException; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; -import java.time.Duration; - @RequiredArgsConstructor @Service public class StoreQueueService { private final RedisTemplate redisTemplate; public void registerWaiting(StoreQueueDto storeQueueDto) { - - Boolean result = redisTemplate.opsForZSet().addIfAbsent(storeQueueDto.key(), + redisTemplate.opsForZSet().add(storeQueueDto.key(), storeQueueDto.value(), storeQueueDto.score()); + } - if (Boolean.FALSE.equals(result)) { - throw new CatchTableException(BadRequestError.ALREADY_REGISTER_WAITING); - } - - redisTemplate.expire(storeQueueDto.key(), Duration.ofMinutes(7L)); + public boolean isValidWaitingUser(StoreQueueDto storeQueueDto) { + Double score = redisTemplate.opsForZSet().score(storeQueueDto.key(), storeQueueDto.value()); + long now = System.currentTimeMillis(); + return now - score <= 7 * 60 * 1000; + } + public void delete(StoreQueueDto storeQueueDto) { + redisTemplate.opsForZSet().remove(storeQueueDto.key(), storeQueueDto.value()); } } diff --git a/src/main/java/com/yscp/catchtable/application/queue/dto/StoreQueueDto.java b/src/main/java/com/yscp/catchtable/application/queue/dto/StoreQueueDto.java index ef8f999..6f7b405 100644 --- a/src/main/java/com/yscp/catchtable/application/queue/dto/StoreQueueDto.java +++ b/src/main/java/com/yscp/catchtable/application/queue/dto/StoreQueueDto.java @@ -6,7 +6,8 @@ public record StoreQueueDto( String storeReserveIdx, String userIdx ) { - private static final String WAITING_KEY_FORMAT = "store:%s:waiting:v1:%s"; + private static final String WAITING_KEY_FORMAT = "store:%s:waiting:%s"; + private static final String RESERVE_KEY_FORMAT = "store:%s:reserve:%s"; public String key() { return String.format(WAITING_KEY_FORMAT, storeReserveIdx, userIdx); @@ -19,4 +20,8 @@ public String value() { public double score() { return Instant.now().toEpochMilli(); } + + public String reserveKey() { + return String.format(RESERVE_KEY_FORMAT, storeReserveIdx, userIdx); + } } diff --git a/src/main/java/com/yscp/catchtable/application/redis/RedisLockService.java b/src/main/java/com/yscp/catchtable/application/redis/RedisLockService.java new file mode 100644 index 0000000..66cd484 --- /dev/null +++ b/src/main/java/com/yscp/catchtable/application/redis/RedisLockService.java @@ -0,0 +1,38 @@ +package com.yscp.catchtable.application.redis; + +import com.yscp.catchtable.exception.BadRequestError; +import com.yscp.catchtable.exception.CatchTableException; +import lombok.RequiredArgsConstructor; +import org.redisson.api.RLock; +import org.redisson.api.RedissonClient; +import org.springframework.stereotype.Service; + +import java.util.concurrent.TimeUnit; + +@RequiredArgsConstructor +@Service +public class RedisLockService { + private final RedissonClient redissonClient; + + public void lock(Runnable runnable, + String key, + long waitTime, + long leaseTime, + TimeUnit unit) { + RLock lock = redissonClient.getLock(key); + + try { + if (lock.tryLock(waitTime, leaseTime, unit)) { + try { + runnable.run(); + } finally { + lock.unlock(); + } + } else { + throw new CatchTableException(BadRequestError.ALREADY_RESERVE); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + } +} diff --git a/src/main/java/com/yscp/catchtable/application/reserve/ReserveService.java b/src/main/java/com/yscp/catchtable/application/reserve/ReserveService.java index 9b76fb0..3198c48 100644 --- a/src/main/java/com/yscp/catchtable/application/reserve/ReserveService.java +++ b/src/main/java/com/yscp/catchtable/application/reserve/ReserveService.java @@ -1,9 +1,10 @@ package com.yscp.catchtable.application.reserve; +import com.yscp.catchtable.application.queue.StoreQueueService; import com.yscp.catchtable.application.reserve.dto.ReserveDto; import com.yscp.catchtable.application.reserve.dto.StoreReserveDto; -import com.yscp.catchtable.domain.reserve.entity.ReserveData; -import com.yscp.catchtable.domain.reserve.repository.ReserveRepository; +import com.yscp.catchtable.domain.reserve.entity.StoreReserve; +import com.yscp.catchtable.domain.reserve.repository.StoreReserveRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -11,13 +12,15 @@ import java.time.LocalDate; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; @Transactional @RequiredArgsConstructor @Service public class ReserveService { - private final ReserveRepository repository; + private final StoreReserveRepository repository; + private final StoreQueueService storeQueueService; public Map> findReserveDtoMapByStores(List idxes, LocalDate maxDate) { List storeReserveDtos = repository.findStoreReserveDtoListBeforeMaxDate(idxes, maxDate); @@ -33,7 +36,11 @@ public List findReserveDtos(Long idx, LocalDate localDate) { } public ReservesInDayDto getReservesInDay(Long storeIdx, LocalDate date) { - List reserveDates = repository.findByStore_IdxAndReserveDate(storeIdx, date); + List reserveDates = repository.findByStore_IdxAndReserveDate(storeIdx, date); return ReservesInDayDto.from(reserveDates); } + + public Optional findWithStoreByIdx(Long storeReserveIdx) { + return repository.findWithStoreByIdx(storeReserveIdx); + } } diff --git a/src/main/java/com/yscp/catchtable/application/reserve/ReservesInDayDto.java b/src/main/java/com/yscp/catchtable/application/reserve/ReservesInDayDto.java index 38c04c8..0b4c4d7 100644 --- a/src/main/java/com/yscp/catchtable/application/reserve/ReservesInDayDto.java +++ b/src/main/java/com/yscp/catchtable/application/reserve/ReservesInDayDto.java @@ -1,7 +1,7 @@ package com.yscp.catchtable.application.reserve; import com.yscp.catchtable.application.reserve.dto.ReserveInDayDto; -import com.yscp.catchtable.domain.reserve.entity.ReserveData; +import com.yscp.catchtable.domain.reserve.entity.StoreReserve; import org.springframework.util.CollectionUtils; import java.util.ArrayList; @@ -10,7 +10,7 @@ public record ReservesInDayDto( List reserves ) { - public static ReservesInDayDto from(List reserveDates) { + public static ReservesInDayDto from(List reserveDates) { if (CollectionUtils.isEmpty(reserveDates)) { return new ReservesInDayDto(new ArrayList<>()); } @@ -18,7 +18,7 @@ public static ReservesInDayDto from(List reserveDates) { return new ReservesInDayDto(convert(reserveDates)); } - private static List convert(List reserveDates) { + private static List convert(List reserveDates) { return reserveDates.stream() .map(reserveData -> new ReserveInDayDto( reserveData.getIdx(), diff --git a/src/main/java/com/yscp/catchtable/application/reserve/UserReserveAggregateService.java b/src/main/java/com/yscp/catchtable/application/reserve/UserReserveAggregateService.java new file mode 100644 index 0000000..3e6a66a --- /dev/null +++ b/src/main/java/com/yscp/catchtable/application/reserve/UserReserveAggregateService.java @@ -0,0 +1,33 @@ +package com.yscp.catchtable.application.reserve; + +import com.yscp.catchtable.application.queue.dto.StoreQueueDto; +import com.yscp.catchtable.application.redis.RedisLockService; +import com.yscp.catchtable.application.reserve.mapper.StoreReserveMapper; +import com.yscp.catchtable.presentation.reserve.dto.StoreReserveRequestDto; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.concurrent.TimeUnit; + +@RequiredArgsConstructor +@Service +public class UserReserveAggregateService { + private final UserReserveService userReserveService; + private final RedisLockService redisLockService; + + public void reserve(StoreReserveRequestDto storeReserveRequestDto) { + StoreQueueDto storeQueueDto = new StoreQueueDto(storeReserveRequestDto.storeReserveIdx().toString(), + storeReserveRequestDto.userIdx().toString()); + + redisLockService.lock( + () -> userReserveService.reserve(StoreReserveMapper.toDto(storeReserveRequestDto, LocalDateTime.now())), + storeQueueDto.reserveKey(), + 2L, + 3L, + TimeUnit.SECONDS + ); + + + } +} diff --git a/src/main/java/com/yscp/catchtable/application/reserve/UserReserveService.java b/src/main/java/com/yscp/catchtable/application/reserve/UserReserveService.java new file mode 100644 index 0000000..1641a71 --- /dev/null +++ b/src/main/java/com/yscp/catchtable/application/reserve/UserReserveService.java @@ -0,0 +1,68 @@ +package com.yscp.catchtable.application.reserve; + +import com.yscp.catchtable.application.queue.StoreQueueService; +import com.yscp.catchtable.application.queue.dto.StoreQueueDto; +import com.yscp.catchtable.application.reserve.dto.StoreReserveRegisterDto; +import com.yscp.catchtable.application.reserve.mapper.UserReserveDataMapper; +import com.yscp.catchtable.domain.reserve.entity.StoreReserve; +import com.yscp.catchtable.domain.reserve.entity.UserReserve; +import com.yscp.catchtable.domain.reserve.repository.UserReserveRepository; +import com.yscp.catchtable.exception.BadRequestError; +import com.yscp.catchtable.exception.CatchTableException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; + +@Transactional +@RequiredArgsConstructor +@Service +public class UserReserveService { + private final StoreQueueService storeQueueService; + private final ReserveService reserveService; + private final UserReserveRepository userReserveDataRepository; + + public void reserve(StoreReserveRegisterDto storeReserveRegisterDto) { + + StoreQueueDto storeQueueDto = new StoreQueueDto(storeReserveRegisterDto.storeReserveIdx().toString(), + storeReserveRegisterDto.userIdx().toString()); + + if (!isValidWaitingUser(storeQueueDto)) { + throw new CatchTableException(BadRequestError.EXPIRED_TICKET); + } + + Optional reserveDataOptional = reserveService.findWithStoreByIdx(storeReserveRegisterDto.storeReserveIdx()); + + reserveDataOptional.ifPresentOrElse( + reserveData -> { + saveUserReserveData( + storeReserveRegisterDto, + reserveData, + storeQueueDto); + }, + () -> { + throw new CatchTableException(BadRequestError.NULL_EXCEPTION); + } + ); + } + + private void saveUserReserveData(StoreReserveRegisterDto dto, + StoreReserve reserveData, + StoreQueueDto storeQueueDto) { + + reserveData.userReserve(dto.requestDatetime(), dto.userIdx()); + + UserReserve userReserveData = UserReserveDataMapper.toEntity(reserveData, dto); + + if (userReserveData != null) { + userReserveDataRepository.save(userReserveData); + } + + storeQueueService.delete(storeQueueDto); + } + + private boolean isValidWaitingUser(StoreQueueDto queueDto) { + return storeQueueService.isValidWaitingUser(queueDto); + } +} diff --git a/src/main/java/com/yscp/catchtable/application/reserve/dto/StoreReserveRegisterDto.java b/src/main/java/com/yscp/catchtable/application/reserve/dto/StoreReserveRegisterDto.java new file mode 100644 index 0000000..fef0ba9 --- /dev/null +++ b/src/main/java/com/yscp/catchtable/application/reserve/dto/StoreReserveRegisterDto.java @@ -0,0 +1,14 @@ +package com.yscp.catchtable.application.reserve.dto; + +import java.time.LocalDateTime; + +public record StoreReserveRegisterDto( + Long userIdx, + Long storeReserveIdx, + String reservePayType, + String transactionNo, + String purpose, + Integer reservationNumberOfPeople, + LocalDateTime requestDatetime +) { +} diff --git a/src/main/java/com/yscp/catchtable/application/reserve/mapper/StoreReserveMapper.java b/src/main/java/com/yscp/catchtable/application/reserve/mapper/StoreReserveMapper.java new file mode 100644 index 0000000..5e23f8d --- /dev/null +++ b/src/main/java/com/yscp/catchtable/application/reserve/mapper/StoreReserveMapper.java @@ -0,0 +1,23 @@ +package com.yscp.catchtable.application.reserve.mapper; + +import com.yscp.catchtable.application.reserve.dto.StoreReserveRegisterDto; +import com.yscp.catchtable.presentation.reserve.dto.StoreReserveRequestDto; +import lombok.experimental.UtilityClass; + +import java.time.LocalDateTime; + +@UtilityClass +public class StoreReserveMapper { + public static StoreReserveRegisterDto toDto(StoreReserveRequestDto storeReserveRequestDto, + LocalDateTime reserveDatetime) { + return new StoreReserveRegisterDto( + storeReserveRequestDto.userIdx(), + storeReserveRequestDto.storeReserveIdx(), + storeReserveRequestDto.reserveType(), + storeReserveRequestDto.transactionNo(), + storeReserveRequestDto.purpose(), + storeReserveRequestDto.reservationNumberOfPeople(), + reserveDatetime + ); + } +} diff --git a/src/main/java/com/yscp/catchtable/application/reserve/mapper/UserReserveDataMapper.java b/src/main/java/com/yscp/catchtable/application/reserve/mapper/UserReserveDataMapper.java new file mode 100644 index 0000000..d930d9c --- /dev/null +++ b/src/main/java/com/yscp/catchtable/application/reserve/mapper/UserReserveDataMapper.java @@ -0,0 +1,25 @@ +package com.yscp.catchtable.application.reserve.mapper; + +import com.yscp.catchtable.application.reserve.dto.StoreReserveRegisterDto; +import com.yscp.catchtable.domain.reserve.entity.StoreReserve; +import com.yscp.catchtable.domain.reserve.entity.UserReserve; +import com.yscp.catchtable.domain.reserve.entity.value.ReservePayType; +import com.yscp.catchtable.domain.reserve.entity.value.ReserveStatus; +import com.yscp.catchtable.domain.user.entity.User; + +public class UserReserveDataMapper { + public static UserReserve toEntity(StoreReserve reserveData, StoreReserveRegisterDto dto) { + User requestUser = User.builder() + .idx(dto.userIdx()) + .build(); + + return UserReserve.builder() + .user(requestUser) + .storeReserve(reserveData) + .reserveStatus(ReserveStatus.RESERVE) + .reservePayType(ReservePayType.from(dto.reservePayType())) + .regIdx(dto.userIdx()) + .regDatetime(dto.requestDatetime()) + .build(); + } +} diff --git a/src/main/java/com/yscp/catchtable/domain/reserve/entity/ReserveData.java b/src/main/java/com/yscp/catchtable/domain/reserve/entity/ReserveData.java deleted file mode 100644 index cfabe48..0000000 --- a/src/main/java/com/yscp/catchtable/domain/reserve/entity/ReserveData.java +++ /dev/null @@ -1,47 +0,0 @@ -package com.yscp.catchtable.domain.reserve.entity; - -import com.yscp.catchtable.domain.reserve.entity.value.StoreReserveDataStatus; -import com.yscp.catchtable.domain.store.entity.Store; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.hibernate.annotations.Comment; - -import java.time.LocalDate; -import java.time.LocalDateTime; - -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@Entity -@Table(name = "store_reserve_data") -public class ReserveData { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long idx; - - @Comment("상점") - @JoinColumn(name = "store_idx") - @ManyToOne(fetch = FetchType.LAZY) - private Store store; - - @Comment("예약 일자") - private LocalDate reserveDate; - @Comment("예약 시간") - private String reserveTime; - @Comment("최소 인원") - private Integer minUserCount; - @Comment("최대 인원") - private Integer maxUserCount; - @Comment("예약 가능 횟수") - private Integer canReserveCount; - @Comment("예약된 횟수") - private Integer reservedCount; - @Enumerated(EnumType.STRING) - private StoreReserveDataStatus reserveStatus; - private LocalDateTime regDatetime; - private Long regIdx; - private LocalDateTime modDatetime; - private Long modIdx; -} diff --git a/src/main/java/com/yscp/catchtable/domain/reserve/entity/StoreReserve.java b/src/main/java/com/yscp/catchtable/domain/reserve/entity/StoreReserve.java new file mode 100644 index 0000000..d8c3b2e --- /dev/null +++ b/src/main/java/com/yscp/catchtable/domain/reserve/entity/StoreReserve.java @@ -0,0 +1,89 @@ +package com.yscp.catchtable.domain.reserve.entity; + +import com.yscp.catchtable.domain.reserve.entity.value.StoreReserveStatus; +import com.yscp.catchtable.domain.store.entity.Store; +import com.yscp.catchtable.exception.BadRequestError; +import com.yscp.catchtable.exception.CatchTableException; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.Comment; + +import java.time.LocalDate; +import java.time.LocalDateTime; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +@Table(name = "store_reserve") +public class StoreReserve { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long idx; + + @Comment("상점") + @JoinColumn(name = "store_idx") + @ManyToOne(fetch = FetchType.LAZY) + private Store store; + + @Comment("예약 일자") + private LocalDate reserveDate; + @Comment("예약 시간") + private String reserveTime; + @Comment("최소 인원") + private Integer minUserCount; + @Comment("최대 인원") + private Integer maxUserCount; + @Comment("예약 가능 횟수") + private Integer canReserveCount; + @Comment("예약된 횟수") + private Integer reservedCount; + @Enumerated(EnumType.STRING) + private StoreReserveStatus reserveStatus; + private LocalDateTime regDatetime; + private Long regIdx; + private LocalDateTime modDatetime; + private Long modIdx; + + @Builder + public StoreReserve(Long idx, + Store store, + LocalDate reserveDate, + String reserveTime, + Integer minUserCount, + Integer maxUserCount, + Integer canReserveCount, + Integer reservedCount, + StoreReserveStatus reserveStatus, + LocalDateTime regDatetime, + Long regIdx, + LocalDateTime modDatetime, + Long modIdx) { + this.idx = idx; + this.store = store; + this.reserveDate = reserveDate; + this.reserveTime = reserveTime; + this.minUserCount = minUserCount; + this.maxUserCount = maxUserCount; + this.canReserveCount = canReserveCount; + this.reservedCount = reservedCount; + this.reserveStatus = reserveStatus; + this.regDatetime = regDatetime; + this.regIdx = regIdx; + this.modDatetime = modDatetime; + this.modIdx = modIdx; + } + + public void userReserve(LocalDateTime userReserveDatetime, Long userIdx) { + if (reservedCount >= canReserveCount) { + throw new CatchTableException(BadRequestError.STORE_RESERVATION_MAX); + } + + reservedCount += 1; + modDatetime = userReserveDatetime; + modIdx = userIdx; + } +} diff --git a/src/main/java/com/yscp/catchtable/domain/reserve/entity/UserReserve.java b/src/main/java/com/yscp/catchtable/domain/reserve/entity/UserReserve.java new file mode 100644 index 0000000..be846be --- /dev/null +++ b/src/main/java/com/yscp/catchtable/domain/reserve/entity/UserReserve.java @@ -0,0 +1,60 @@ +package com.yscp.catchtable.domain.reserve.entity; + +import com.yscp.catchtable.domain.reserve.entity.value.ReservePayType; +import com.yscp.catchtable.domain.reserve.entity.value.ReserveStatus; +import com.yscp.catchtable.domain.user.entity.User; +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Getter +@Entity +public class UserReserve { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long idx; + + @JoinColumn(name = "user_idx") + @ManyToOne(fetch = FetchType.LAZY) + private User user; + + @JoinColumn(name = "store_reserve_idx") + @ManyToOne(fetch = FetchType.LAZY) + private StoreReserve storeReserve; + + @Enumerated(EnumType.STRING) + private ReserveStatus reserveStatus; + @Enumerated(EnumType.STRING) + private ReservePayType reservePayType; + private LocalDateTime regDatetime; + private Long regIdx; + private LocalDateTime modDatetime; + private Long modIdx; + + @Builder + public UserReserve(Long idx, + User user, + StoreReserve storeReserve, + ReserveStatus reserveStatus, + ReservePayType reservePayType, + LocalDateTime regDatetime, + Long regIdx, + LocalDateTime modDatetime, + Long modIdx) { + this.idx = idx; + this.user = user; + this.storeReserve = storeReserve; + this.reserveStatus = reserveStatus; + this.reservePayType = reservePayType; + this.regDatetime = regDatetime; + this.regIdx = regIdx; + this.modDatetime = modDatetime; + this.modIdx = modIdx; + } +} diff --git a/src/main/java/com/yscp/catchtable/domain/reserve/entity/UserReserveData.java b/src/main/java/com/yscp/catchtable/domain/reserve/entity/UserReserveData.java deleted file mode 100644 index cd05761..0000000 --- a/src/main/java/com/yscp/catchtable/domain/reserve/entity/UserReserveData.java +++ /dev/null @@ -1,36 +0,0 @@ -package com.yscp.catchtable.domain.reserve.entity; - -import com.yscp.catchtable.domain.reserve.entity.value.ReserveStatus; -import com.yscp.catchtable.domain.user.entity.User; -import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.time.LocalDateTime; - -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Getter -@Entity -public class UserReserveData { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long idx; - - @JoinColumn(name = "user_idx") - @ManyToOne(fetch = FetchType.LAZY) - private User user; - - @JoinColumn(name = "reserve_data_idx") - @ManyToOne(fetch = FetchType.LAZY) - private ReserveData reserveData; - - @Enumerated(EnumType.STRING) - private ReserveStatus reserveStatus; - private LocalDateTime regDatetime; - private Long regIdx; - private LocalDateTime modDatetime; - private Long modIdx; - -} diff --git a/src/main/java/com/yscp/catchtable/domain/reserve/entity/value/ReservePayType.java b/src/main/java/com/yscp/catchtable/domain/reserve/entity/value/ReservePayType.java new file mode 100644 index 0000000..d5af7d8 --- /dev/null +++ b/src/main/java/com/yscp/catchtable/domain/reserve/entity/value/ReservePayType.java @@ -0,0 +1,33 @@ +package com.yscp.catchtable.domain.reserve.entity.value; + +import com.yscp.catchtable.exception.BadRequestError; +import com.yscp.catchtable.exception.CatchTableException; + +import java.util.Arrays; +import java.util.Map; +import java.util.stream.Collectors; + +public enum ReservePayType { + CARD, + CATCH_PAY; + + private static final Map MAPPING; + + static { + MAPPING = Arrays.stream(ReservePayType.values()) + .collect(Collectors.toMap(Enum::name, e -> e)); + } + + public static ReservePayType from(String reserveTypeString) { + if (reserveTypeString == null || reserveTypeString.isEmpty()) { + throw new CatchTableException(BadRequestError.INVALID_RESERVE_PAY_TYPE); + } + ReservePayType reservePayType = MAPPING.get(reserveTypeString.toUpperCase()); + + if (reservePayType == null) { + throw new CatchTableException(BadRequestError.INVALID_RESERVE_PAY_TYPE); + } + + return reservePayType; + } +} diff --git a/src/main/java/com/yscp/catchtable/domain/reserve/entity/value/ReserveStatus.java b/src/main/java/com/yscp/catchtable/domain/reserve/entity/value/ReserveStatus.java index f05f18d..ea7879d 100644 --- a/src/main/java/com/yscp/catchtable/domain/reserve/entity/value/ReserveStatus.java +++ b/src/main/java/com/yscp/catchtable/domain/reserve/entity/value/ReserveStatus.java @@ -1,4 +1,8 @@ package com.yscp.catchtable.domain.reserve.entity.value; public enum ReserveStatus { + RESERVE, + SUCCESS, + DELETE, + CANCEL } diff --git a/src/main/java/com/yscp/catchtable/domain/reserve/entity/value/StoreReserveDataStatus.java b/src/main/java/com/yscp/catchtable/domain/reserve/entity/value/StoreReserveStatus.java similarity index 76% rename from src/main/java/com/yscp/catchtable/domain/reserve/entity/value/StoreReserveDataStatus.java rename to src/main/java/com/yscp/catchtable/domain/reserve/entity/value/StoreReserveStatus.java index 060e1b4..34a4d91 100644 --- a/src/main/java/com/yscp/catchtable/domain/reserve/entity/value/StoreReserveDataStatus.java +++ b/src/main/java/com/yscp/catchtable/domain/reserve/entity/value/StoreReserveStatus.java @@ -1,6 +1,6 @@ package com.yscp.catchtable.domain.reserve.entity.value; -public enum StoreReserveDataStatus { +public enum StoreReserveStatus { // 활성화 ACTIVE, // 일시정지 diff --git a/src/main/java/com/yscp/catchtable/domain/reserve/repository/ReserveRepository.java b/src/main/java/com/yscp/catchtable/domain/reserve/repository/StoreReserveRepository.java similarity index 71% rename from src/main/java/com/yscp/catchtable/domain/reserve/repository/ReserveRepository.java rename to src/main/java/com/yscp/catchtable/domain/reserve/repository/StoreReserveRepository.java index ef2b323..fea9b1b 100644 --- a/src/main/java/com/yscp/catchtable/domain/reserve/repository/ReserveRepository.java +++ b/src/main/java/com/yscp/catchtable/domain/reserve/repository/StoreReserveRepository.java @@ -1,15 +1,16 @@ package com.yscp.catchtable.domain.reserve.repository; import com.yscp.catchtable.application.reserve.dto.StoreReserveDto; -import com.yscp.catchtable.domain.reserve.entity.ReserveData; +import com.yscp.catchtable.domain.reserve.entity.StoreReserve; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; import java.time.LocalDate; import java.util.List; +import java.util.Optional; -public interface ReserveRepository extends JpaRepository { +public interface StoreReserveRepository extends JpaRepository { @Query( value = """ @@ -17,7 +18,7 @@ public interface ReserveRepository extends JpaRepository { reserve_data AS date, SUM(reserved_count) AS reserve, store_idx - FROM store_reserve_data + FROM store_reserve WHERE store_idx IN :storeIdxes AND reserve_data <= :date GROUP BY store_idx,date @@ -31,7 +32,7 @@ public interface ReserveRepository extends JpaRepository { reserve_data AS date, SUM(reserved_count) AS reserve, store_idx - FROM store_reserve_data + FROM store_reserve WHERE store_idx = :idx AND reserve_data <= :date GROUP BY store_idx,date @@ -39,5 +40,13 @@ public interface ReserveRepository extends JpaRepository { , nativeQuery = true) List getStoreReserveDtoBeforeMaxDate(@Param("idx") Long idx, @Param("date") LocalDate date); - List findByStore_IdxAndReserveDate(Long idx, LocalDate reserveDate); + List findByStore_IdxAndReserveDate(Long idx, LocalDate reserveDate); + + @Query(value = """ + SELECT rd + FROM StoreReserve rd + JOIN FETCH rd.store s + WHERE rd.idx = :idx + """) + Optional findWithStoreByIdx(Long idx); } diff --git a/src/main/java/com/yscp/catchtable/domain/reserve/repository/UserReserveRepository.java b/src/main/java/com/yscp/catchtable/domain/reserve/repository/UserReserveRepository.java new file mode 100644 index 0000000..e10a643 --- /dev/null +++ b/src/main/java/com/yscp/catchtable/domain/reserve/repository/UserReserveRepository.java @@ -0,0 +1,13 @@ +package com.yscp.catchtable.domain.reserve.repository; + +import com.yscp.catchtable.domain.reserve.entity.StoreReserve; +import com.yscp.catchtable.domain.reserve.entity.UserReserve; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface UserReserveRepository extends JpaRepository { + List findByUser_Idx(Long userIdx); + + List findByStoreReserve(StoreReserve storeReserve); +} diff --git a/src/main/java/com/yscp/catchtable/domain/user/entity/User.java b/src/main/java/com/yscp/catchtable/domain/user/entity/User.java index 556ca23..e1871a1 100644 --- a/src/main/java/com/yscp/catchtable/domain/user/entity/User.java +++ b/src/main/java/com/yscp/catchtable/domain/user/entity/User.java @@ -5,6 +5,7 @@ import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -31,6 +32,7 @@ public class User { private LocalDateTime modDatetime; + @Builder public User(Long idx, String email, String password, String phone, String nickname, LocalDateTime regDatetime, LocalDateTime modDatetime) { this.idx = idx; this.email = email; diff --git a/src/main/java/com/yscp/catchtable/exception/BadRequestError.java b/src/main/java/com/yscp/catchtable/exception/BadRequestError.java index faf3101..1fec6c4 100644 --- a/src/main/java/com/yscp/catchtable/exception/BadRequestError.java +++ b/src/main/java/com/yscp/catchtable/exception/BadRequestError.java @@ -12,11 +12,15 @@ public enum BadRequestError implements CustomError { * 0 ~ 100 Common */ NULL_EXCEPTION("%s", "1", true), + INVALID_RESERVE_PAY_TYPE("지불 방식이 잘못됐습니다.", "2", false), /** - * 200 ~ 300 Waiting + * 101 ~ 200 예약 */ - ALREADY_REGISTER_WAITING("이미 예약을 진행하고 있습니다.", "200" , false); + EXPIRED_TICKET("예약 가능 시간을 초과하였습니다. \n 다시 예약 요청을 진행 해주세요.", "101", false), + STORE_RESERVATION_MAX("모든 예약이 완료된 상태입니다. \n 다음에 이용해주세요.", "102", false), + ALREADY_RESERVE("현재 예약 요청을 진행할 수 없습니다. \n 다시 요청해주세요.", "103", false), + ; private final HttpStatus httpStatus = HttpStatus.BAD_REQUEST; private final String message; diff --git a/src/main/java/com/yscp/catchtable/exception/ServerError.java b/src/main/java/com/yscp/catchtable/exception/ServerError.java new file mode 100644 index 0000000..27c8c19 --- /dev/null +++ b/src/main/java/com/yscp/catchtable/exception/ServerError.java @@ -0,0 +1,25 @@ +package com.yscp.catchtable.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; + +@RequiredArgsConstructor +@Getter +public enum ServerError implements CustomError { + + /** + * 0 ~ 100 Common + */ + SERVER_ERROR("캐치테이블 서버에 에러가 발생했습니다.", "1", true), + ; + private final HttpStatus httpStatus = HttpStatus.INTERNAL_SERVER_ERROR; + private final String message; + private final String errorCode; + private final Boolean isCustomMessage; + + @Override + public Boolean isCustomMessage() { + return isCustomMessage; + } +} diff --git a/src/main/java/com/yscp/catchtable/infra/utils/PointUtils.java b/src/main/java/com/yscp/catchtable/infra/utils/PointUtils.java new file mode 100644 index 0000000..c7c3ea6 --- /dev/null +++ b/src/main/java/com/yscp/catchtable/infra/utils/PointUtils.java @@ -0,0 +1,19 @@ +package com.yscp.catchtable.infra.utils; + +import com.yscp.catchtable.exception.CatchTableException; +import com.yscp.catchtable.exception.ServerError; +import org.locationtech.jts.geom.Point; +import org.locationtech.jts.io.WKTReader; + +public class PointUtils { + + public static Point convertPoint(Double longitude, Double latitude) { + try { + String pointWKT = String.format("POINT(%s %s)", longitude, latitude); + return (Point) new WKTReader().read(pointWKT); + + } catch (Exception e) { + throw new CatchTableException(ServerError.SERVER_ERROR); + } + } +} diff --git a/src/main/java/com/yscp/catchtable/presentation/reserve/ReserveController.java b/src/main/java/com/yscp/catchtable/presentation/reserve/ReserveController.java index 6795b85..3aae555 100644 --- a/src/main/java/com/yscp/catchtable/presentation/reserve/ReserveController.java +++ b/src/main/java/com/yscp/catchtable/presentation/reserve/ReserveController.java @@ -2,28 +2,31 @@ import com.yscp.catchtable.application.reserve.ReserveService; import com.yscp.catchtable.application.reserve.ReservesInDayDto; +import com.yscp.catchtable.application.reserve.UserReserveAggregateService; +import com.yscp.catchtable.presentation.reserve.dto.StoreReserveRequestDto; import com.yscp.catchtable.presentation.reserve.dto.response.ReserveInDayResponseDtos; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import java.time.LocalDate; @RequiredArgsConstructor @RestController public class ReserveController { - + private final UserReserveAggregateService userReserveAggregateService; private final ReserveService reserveService; @GetMapping("/store/reserves/{storeIdx}") public ResponseEntity getReservesInDay(@PathVariable Long storeIdx, @RequestParam LocalDate date) { - ReservesInDayDto reservesInDay = reserveService.getReservesInDay(storeIdx, date); - return ResponseEntity.ok(ReserveInDayResponseDtos.from(reservesInDay)); } + + @PostMapping("/store/reserves") + public ResponseEntity reserve(@RequestBody StoreReserveRequestDto storeReserveRequestDto) { + userReserveAggregateService.reserve(storeReserveRequestDto); + return ResponseEntity.ok().build(); + } } diff --git a/src/main/java/com/yscp/catchtable/presentation/reserve/dto/StoreReserveRequestDto.java b/src/main/java/com/yscp/catchtable/presentation/reserve/dto/StoreReserveRequestDto.java new file mode 100644 index 0000000..6a891b0 --- /dev/null +++ b/src/main/java/com/yscp/catchtable/presentation/reserve/dto/StoreReserveRequestDto.java @@ -0,0 +1,11 @@ +package com.yscp.catchtable.presentation.reserve.dto; + +public record StoreReserveRequestDto( + Long userIdx, + Long storeReserveIdx, + String reserveType, + String transactionNo, + String purpose, + Integer reservationNumberOfPeople +) { +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 09d6eac..cf61287 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -14,8 +14,13 @@ spring: jpa: hibernate: - ddl-auto: update + ddl-auto: none open-in-view: false show-sql: true database-platform: org.hibernate.spatial.dialect.mysql.MySQL56InnoDBSpatialDialect + +redisson: + config: + singleServerConfig: + address: "redis://localhost:6379" diff --git a/src/test/java/com/yscp/catchtable/application/reserve/UserReserveAggregateServiceTest.java b/src/test/java/com/yscp/catchtable/application/reserve/UserReserveAggregateServiceTest.java new file mode 100644 index 0000000..8403915 --- /dev/null +++ b/src/test/java/com/yscp/catchtable/application/reserve/UserReserveAggregateServiceTest.java @@ -0,0 +1,131 @@ +package com.yscp.catchtable.application.reserve; + +import com.yscp.catchtable.application.queue.StoreQueueService; +import com.yscp.catchtable.application.queue.dto.StoreQueueDto; +import com.yscp.catchtable.domain.category.entitry.StoreCategory; +import com.yscp.catchtable.domain.category.entitry.value.StoreCategoryCode; +import com.yscp.catchtable.domain.reserve.entity.StoreReserve; +import com.yscp.catchtable.domain.reserve.entity.UserReserve; +import com.yscp.catchtable.domain.reserve.entity.value.StoreReserveStatus; +import com.yscp.catchtable.domain.reserve.repository.StoreReserveRepository; +import com.yscp.catchtable.domain.reserve.repository.UserReserveRepository; +import com.yscp.catchtable.domain.store.entity.Store; +import com.yscp.catchtable.domain.store.repository.StoreRepository; +import com.yscp.catchtable.infra.utils.PointUtils; +import com.yscp.catchtable.presentation.reserve.dto.StoreReserveRequestDto; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +@SpringBootTest +class UserReserveAggregateServiceTest { + + @Autowired + private UserReserveAggregateService userReserveAggregateService; + + @Autowired + private StoreQueueService storeQueueService; + + @Autowired + private StoreRepository storeRepository; + + @Autowired + private StoreReserveRepository storeReserveRepository; + + @Autowired + private UserReserveRepository userReserveRepository; + + Store store; + + StoreReserve storeReserve; + + @BeforeEach + void setUp() { + // 상점 등록 + store = Store.builder() + .name("Test 상점") + .holiday(List.of("MON")) + .addressCode("강남1단지") + .category(StoreCategory.builder() + .idx(1L) + .code(StoreCategoryCode.SUSHI) + .build()) + .point(PointUtils.convertPoint(37.5189323, 126.88222735)) + .introduce("테스트 상점입니다.") + .directions("길안내") + .build(); + + storeRepository.save(store); + + storeReserve = StoreReserve.builder() + .reserveDate(LocalDate.now().plusDays(1)) + .reserveTime("1230") + .maxUserCount(10) + .minUserCount(2) + .canReserveCount(10) + .reservedCount(0) + .reserveStatus(StoreReserveStatus.ACTIVE) + .store(store) + .regDatetime(LocalDateTime.now()) + .build(); + + storeReserveRepository.save(storeReserve); + } + + @AfterEach + void afterEach() { + storeRepository.delete(store); + storeReserveRepository.delete(storeReserve); + } + + @DisplayName("동시요청 테스트") + @Test + void name() throws InterruptedException { + // 대기열에 등록 + StoreQueueDto storeQueueDto = new StoreQueueDto(storeReserve.getIdx().toString(), "1"); + storeQueueService.registerWaiting(storeQueueDto); + + StoreReserveRequestDto storeReserveRequestDto = new StoreReserveRequestDto(1L, + storeReserve.getIdx(), + "CATCH_PAY", + "Test", + "데이트", + 4 + ); + + + int threadCount = 10; + ExecutorService executor = Executors.newFixedThreadPool(threadCount); + CountDownLatch latch = new CountDownLatch(threadCount); + + + for (int i = 0; i < threadCount; i++) { + executor.execute(() -> { + try { + userReserveAggregateService.reserve(storeReserveRequestDto); + System.out.println("예약 성공"); + } catch (Exception e) { + System.out.println("예약 실패: " + e.getMessage()); + } finally { + latch.countDown(); + } + }); + } + + latch.await(); + + List byUserIdx = userReserveRepository.findByStoreReserve(storeReserve); + Assertions.assertThat(byUserIdx.size()).isEqualTo(1); + } +} diff --git a/src/test/java/com/yscp/catchtable/application/reserve/UserReserveServiceTest.java b/src/test/java/com/yscp/catchtable/application/reserve/UserReserveServiceTest.java new file mode 100644 index 0000000..2640ae1 --- /dev/null +++ b/src/test/java/com/yscp/catchtable/application/reserve/UserReserveServiceTest.java @@ -0,0 +1,116 @@ +package com.yscp.catchtable.application.reserve; + +import com.yscp.catchtable.application.queue.StoreQueueService; +import com.yscp.catchtable.application.queue.dto.StoreQueueDto; +import com.yscp.catchtable.application.reserve.dto.StoreReserveRegisterDto; +import com.yscp.catchtable.domain.reserve.entity.StoreReserve; +import com.yscp.catchtable.domain.reserve.entity.UserReserve; +import com.yscp.catchtable.domain.reserve.repository.UserReserveRepository; +import com.yscp.catchtable.exception.CatchTableException; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import java.time.LocalDateTime; +import java.util.Optional; + +import static org.mockito.ArgumentMatchers.any; + +@DisplayName("UserReserveService ") +@ExtendWith(MockitoExtension.class) +class UserReserveServiceTest { + @Mock + private StoreQueueService storeQueueService; + @Mock + private ReserveService reserveService; + @Mock + private UserReserveRepository userReserveDataRepository; + @InjectMocks + private UserReserveService userReserveService; + + private StoreReserveRegisterDto storeReserveRegisterDto; + + @DisplayName("reserve 메소드는") + @Nested + class Describe_with_reserve { + + @BeforeEach + void setUp() { + storeReserveRegisterDto = new StoreReserveRegisterDto( + 1L, + 1L, + "CARD", + "234123", + "DATE", + 4, + LocalDateTime.now() + ); + } + + @DisplayName("대기열에 존재하는 유저가 요청 했을 경우 ") + @Nested + class Context_with_valid_waitingUser { + + @DisplayName("예약 정보를 저장한다.") + @Test + public void save_user_reserve_data() { + StoreReserve mockReserveData = StoreReserve.builder() + .idx(1L) + .reserveTime("1320") + .reservedCount(10) + .canReserveCount(20) + .build(); + + Mockito.when(reserveService.findWithStoreByIdx(any())).thenReturn(Optional.of(mockReserveData)); + + Mockito.when(storeQueueService.isValidWaitingUser(Mockito.any(StoreQueueDto.class))).thenReturn(Boolean.TRUE); + + userReserveService.reserve(storeReserveRegisterDto); + + Mockito.verify(userReserveDataRepository, Mockito.times(1)).save(any(UserReserve.class)); + Mockito.verify(storeQueueService, Mockito.times(1)).delete(any(StoreQueueDto.class)); + } + } + + + @DisplayName("대기열에 존재하지 않는 유저가 예약 요청을 했을 경우") + @Nested + class Context_with_invalid_waitingUser { + + @DisplayName("에러를 던진다.") + @Test + public void save_user_reserve_data() { + Mockito.when(storeQueueService.isValidWaitingUser(Mockito.any(StoreQueueDto.class))).thenReturn(Boolean.TRUE); + + Mockito.when(reserveService.findWithStoreByIdx(any())).thenReturn(Optional.empty()); + + Assertions.assertThatThrownBy(() -> userReserveService.reserve(storeReserveRegisterDto)) + .isInstanceOf(CatchTableException.class); + } + } + + + @DisplayName("상점 예약 정보가 존재하지 않을 경우") + @Nested + class Context_with_not_found_store_reserve_data { + + @DisplayName("에러를 던진다.") + @Test + public void save_user_reserve_data() { + Mockito.when(storeQueueService.isValidWaitingUser(Mockito.any(StoreQueueDto.class))).thenReturn(Boolean.FALSE); + + Assertions.assertThatThrownBy(() -> userReserveService.reserve(storeReserveRegisterDto)) + .isInstanceOf(CatchTableException.class); + } + } + } + + +} diff --git a/src/test/java/com/yscp/catchtable/domain/reserve/entity/StoreReserveTest.java b/src/test/java/com/yscp/catchtable/domain/reserve/entity/StoreReserveTest.java new file mode 100644 index 0000000..b1e1cda --- /dev/null +++ b/src/test/java/com/yscp/catchtable/domain/reserve/entity/StoreReserveTest.java @@ -0,0 +1,57 @@ +package com.yscp.catchtable.domain.reserve.entity; + +import com.yscp.catchtable.exception.CatchTableException; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.time.LocalDateTime; + +class StoreReserveTest { + + @DisplayName("userReserve 메소드는") + @Nested + class Describe_with_userReserve { + + @DisplayName("유저가 예약을 했을 경우") + @Nested + class Context_with_valid_user_reserve { + + @DisplayName("예약 카운트를 증가한다.") + @Test + public void plus_reserve_count() { + StoreReserve reserveData = StoreReserve.builder() + .idx(1L) + .canReserveCount(5) + .reservedCount(1) + .build(); + + reserveData.userReserve(LocalDateTime.now(), 1L); + + Assertions.assertThat(reserveData.getReservedCount()).isEqualTo(2); + } + } + + @DisplayName("예약 횟수가 가득찼을 경우") + @Nested + class Context_with_max_reserve_count { + + @DisplayName("에러를 던진다.") + @Test + public void plus_reserve_count() { + StoreReserve reserveData = StoreReserve.builder() + .idx(1L) + .canReserveCount(5) + .reservedCount(5) + .build(); + + Assertions.assertThatThrownBy(() -> reserveData.userReserve(LocalDateTime.now(), 1L)) + .isInstanceOf(CatchTableException.class); + + } + } + } + + +} diff --git a/src/test/java/com/yscp/catchtable/domain/reserve/repository/ReserveRepositoryTest.java b/src/test/java/com/yscp/catchtable/domain/reserve/repository/StoreReserveRepositoryTest.java similarity index 80% rename from src/test/java/com/yscp/catchtable/domain/reserve/repository/ReserveRepositoryTest.java rename to src/test/java/com/yscp/catchtable/domain/reserve/repository/StoreReserveRepositoryTest.java index 9352320..f016774 100644 --- a/src/test/java/com/yscp/catchtable/domain/reserve/repository/ReserveRepositoryTest.java +++ b/src/test/java/com/yscp/catchtable/domain/reserve/repository/StoreReserveRepositoryTest.java @@ -10,10 +10,10 @@ import java.util.List; @SpringBootTest -class ReserveRepositoryTest { +class StoreReserveRepositoryTest { @Autowired - private ReserveRepository reserveRepository; + private StoreReserveRepository reserveRepository; @DisplayName("resultDtos") @Test @@ -34,7 +34,13 @@ void getStoreReserveDtoBeforeMaxDate() { void findByStore_IdxAndReserveDate() { Assertions.assertThatCode(() -> reserveRepository.findByStore_IdxAndReserveDate(1L, LocalDate.of(2025, 6, 20))) .doesNotThrowAnyException(); + } + @DisplayName("findWithStoreByIdx") + @Test + void findWithStoreByIdx() { + Assertions.assertThatCode(() -> reserveRepository.findWithStoreByIdx(5L)) + .doesNotThrowAnyException(); } } diff --git a/src/test/java/com/yscp/catchtable/infra/utils/PointUtilsTest.java b/src/test/java/com/yscp/catchtable/infra/utils/PointUtilsTest.java new file mode 100644 index 0000000..bde1c59 --- /dev/null +++ b/src/test/java/com/yscp/catchtable/infra/utils/PointUtilsTest.java @@ -0,0 +1,20 @@ +package com.yscp.catchtable.infra.utils; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.locationtech.jts.geom.Point; + +class PointUtilsTest { + + + @DisplayName("포인트 생성 테스트") + @Test + void createPoint() { + double x = 37.5189323; + double y = 126.88222735; + Point point = PointUtils.convertPoint(x, y); + Assertions.assertThat(point.getX()).isEqualTo(x); + Assertions.assertThat(point.getY()).isEqualTo(y); + } +}