From ffe7a1d6119b689b02174647b93a26ed41e2a26b Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 8 Feb 2026 16:48:03 +0900 Subject: [PATCH 1/7] =?UTF-8?q?refactor:=20=EB=A9=B1=EB=93=B1=ED=82=A4=20?= =?UTF-8?q?=EC=A0=80=EC=9E=A5=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EA=B3=B5?= =?UTF-8?q?=ED=86=B5=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/dto/IdempotencyResponse.java | 13 ++++++++++ .../redis/WaitingIdempotencyRepository.java | 26 ++++--------------- .../waiting/service/WaitingService.java | 18 +++++++++---- .../waiting/service/WaitingServiceTest.java | 10 +++---- 4 files changed, 36 insertions(+), 31 deletions(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/IdempotencyResponse.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/IdempotencyResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/IdempotencyResponse.java new file mode 100644 index 0000000..00ccb5c --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/IdempotencyResponse.java @@ -0,0 +1,13 @@ +package com.nowait.applicationuser.waiting.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class IdempotencyResponse { + private String state; + private Object response; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java index 3fe4a3d..41b03e3 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java @@ -7,8 +7,7 @@ import org.springframework.stereotype.Repository; import com.fasterxml.jackson.databind.ObjectMapper; -import com.nowait.applicationuser.waiting.dto.CancelWaitingResponse; -import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; +import com.nowait.applicationuser.waiting.dto.IdempotencyResponse; import com.nowait.applicationuser.waiting.dto.WaitingCancelIdempotencyValue; import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; @@ -58,30 +57,15 @@ public Optional findByCancelKey(String key) { } - // 멱등키 저장 메서드 - 대기 등록 - public void saveIdempotencyValue(String key, RegisterWaitingResponse response) { - WaitingIdempotencyValue waitingIdempotencyValue = new WaitingIdempotencyValue( + // 멱등키 저장 메서드 + public void saveIdempotencyResponse(String key, Object response) { + IdempotencyResponse idempotencyResponse = new IdempotencyResponse( "COMPLETED", response ); try { - String jsonValue = objectMapper.writeValueAsString(waitingIdempotencyValue); - redisTemplate.opsForValue().set(key, jsonValue, TTL); - } catch (Exception e) { - throw new IllegalArgumentException("Failed to serialize value for Redis", e); - } - } - - // 멱등키 저장 메서드 - 대기 취소 - public void saveCancelIdempotencyValue(String key, CancelWaitingResponse response) { - WaitingCancelIdempotencyValue waitingIdempotencyValue = new WaitingCancelIdempotencyValue( - "COMPLETED", - response - ); - - try { - String jsonValue = objectMapper.writeValueAsString(waitingIdempotencyValue); + String jsonValue = objectMapper.writeValueAsString(idempotencyResponse); redisTemplate.opsForValue().set(key, jsonValue, TTL); } catch (Exception e) { throw new IllegalArgumentException("Failed to serialize value for Redis", e); diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index cfcd247..b3e2507 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.Optional; import org.springframework.context.ApplicationEventPublisher; import org.springframework.stereotype.Service; @@ -52,8 +53,6 @@ public class WaitingService { /** * 최초 대기 등록 - * @param publicCode - * @param waitingRequest */ // 대기열 리팩토링 서비스 메서드 @Transactional @@ -148,7 +147,7 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p .build(); // 멱등키가 있다면 멱등 응답 저장 - waitingIdempotencyRepository.saveCancelIdempotencyValue(httpServletRequest.getHeader("Idempotency-Key"), response); + saveIdempotencyResponse(httpServletRequest.getHeader("Idempotency-Key"), response); return response; } @@ -174,10 +173,19 @@ private CancelWaitingResponse validateCancelIdempotency(HttpServletRequest httpS .orElse(null); } + // TODO 공통 멱등키 검증 메서드로 리팩토링 필요 + private Optional validateIdempotency_(HttpServletRequest httpServletRequest) { + String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); + + // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 + // TODO 멱등성 검증 로직 점검 필요 + return waitingIdempotencyRepository.findByKey(idempotentKey); + } + // 멱등키 응답 저장 메서드 - private void saveIdempotencyResponse(String idempotentKey, RegisterWaitingResponse response) { + private void saveIdempotencyResponse(String idempotentKey, Object response) { if (idempotentKey != null && !idempotentKey.isBlank()) { - waitingIdempotencyRepository.saveIdempotencyValue(idempotentKey, response); + waitingIdempotencyRepository.saveIdempotencyResponse(idempotentKey, response); } } diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java index cb5b8f8..edc05f3 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java @@ -95,7 +95,7 @@ void registerWaiting_idempotentKeyExists() { verify(waitingRedisRepository, never()).incrementAndCheckWaitingLimit(anyLong(), anyLong()); verify(reservationRepository, never()).save(any(Reservation.class)); verify(eventPublisher, never()).publishEvent(any()); - verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + verify(waitingIdempotencyRepository, never()).saveIdempotencyResponse(anyString(), any(RegisterWaitingResponse.class)); } @Test @@ -114,7 +114,7 @@ void registerWaiting_idempotentKeyNotExists() { User user = User.builder().id(userId).build(); when(storeRepository.findByPublicCodeAndDeletedFalse(publicCode)).thenReturn(java.util.Optional.of(store)); - when(customOAuth2User.getUserId()).thenReturn(10L); + when(customOAuth2User.getUserId()).thenReturn(userId); when(userRepository.findById(userId)).thenReturn(java.util.Optional.of(user)); doNothing() @@ -184,7 +184,7 @@ void registerWaiting_success() { verify(waitingRedisRepository).incrementAndCheckWaitingLimit(userId, 3L); verify(reservationRepository).save(any(Reservation.class)); verify(eventPublisher).publishEvent(any(AddWaitingRegisterEvent.class)); - verify(waitingIdempotencyRepository).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + verify(waitingIdempotencyRepository).saveIdempotencyResponse(anyString(), any(RegisterWaitingResponse.class)); } @Test @@ -222,7 +222,7 @@ void registerWaiting_dbSaveException() { )).isInstanceOf(RuntimeException.class); verify(eventPublisher, never()).publishEvent(any(AddWaitingRegisterEvent.class)); - verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + verify(waitingIdempotencyRepository, never()).saveIdempotencyResponse(anyString(), any(RegisterWaitingResponse.class)); } @Test @@ -262,7 +262,7 @@ void registerWaiting_exceedWaitingLimit() { verify(reservationRepository, never()).save(any(Reservation.class)); verify(eventPublisher, never()).publishEvent(any()); - verify(waitingIdempotencyRepository, never()).saveIdempotencyValue(anyString(), any(RegisterWaitingResponse.class)); + verify(waitingIdempotencyRepository, never()).saveIdempotencyResponse(anyString(), any(RegisterWaitingResponse.class)); verify(waitingRedisRepository).incrementAndCheckWaitingLimit(10L, 3L); } From 4d52fa53f77fdc37dfffacdca4d49185909d072c Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Sun, 8 Feb 2026 16:48:40 +0900 Subject: [PATCH 2/7] =?UTF-8?q?refactor:=20todo=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../applicationuser/waiting/service/WaitingService.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index b3e2507..a432390 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -173,12 +173,10 @@ private CancelWaitingResponse validateCancelIdempotency(HttpServletRequest httpS .orElse(null); } - // TODO 공통 멱등키 검증 메서드로 리팩토링 필요 + // TODO : 공통 멱등키 검증 메서드로 리팩토링 필요!!!!!!!!! private Optional validateIdempotency_(HttpServletRequest httpServletRequest) { String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); - // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 - // TODO 멱등성 검증 로직 점검 필요 return waitingIdempotencyRepository.findByKey(idempotentKey); } From 7c23b1ff63687001116058dbed7ba1c7f2dfb0dc Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 17:20:28 +0900 Subject: [PATCH 3/7] =?UTF-8?q?feat:=20=EB=A9=B1=EB=93=B1=ED=82=A4=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=EC=A4=91=20=EC=97=90=EB=9F=AC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 10 ++++++++++ .../waiting/exception/WorkInProgressException.java | 7 +++++++ .../java/com/nowait/common/exception/ErrorMessage.java | 3 +++ 3 files changed, 20 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/exception/WorkInProgressException.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java index 3eb701b..96a95d9 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java @@ -21,6 +21,7 @@ import com.nowait.applicationuser.security.exception.ResourceNotFoundException; import com.nowait.applicationuser.security.exception.UnauthorizedException; +import com.nowait.applicationuser.waiting.exception.WorkInProgressException; import com.nowait.common.exception.ErrorMessage; import com.nowait.common.exception.ErrorResponse; import com.nowait.discord.service.DiscordAlarmService; @@ -292,6 +293,15 @@ public ErrorResponse handleReservationAddUnauthorizedException( return new ErrorResponse(e.getMessage(), RESERVATION_ADD_UNAUTHORIZED.getCode()); } + @ResponseStatus(CONFLICT) + @ExceptionHandler(WorkInProgressException.class) + public ErrorResponse handleWorkInProgressException( + WorkInProgressException e, WebRequest request) { + alarm(e, request); + log.error("handleWorkInProgressException", e); + return new ErrorResponse(e.getMessage(), WORK_IN_PROGRESS.getCode()); + } + // 공통 에러 Map 생성 private static Map getErrors(MethodArgumentNotValidException e) { return e.getBindingResult() diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/exception/WorkInProgressException.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/exception/WorkInProgressException.java new file mode 100644 index 0000000..8ea38bc --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/exception/WorkInProgressException.java @@ -0,0 +1,7 @@ +package com.nowait.applicationuser.waiting.exception; + +import com.nowait.common.exception.ErrorMessage; + +public class WorkInProgressException extends RuntimeException { + public WorkInProgressException() { super(ErrorMessage.WORK_IN_PROGRESS.getMessage()); } +} diff --git a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java index ba58c78..6aa6267 100644 --- a/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java +++ b/nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java @@ -93,6 +93,9 @@ public enum ErrorMessage { // search SEARCH_PARAMETER_EMPTY("검색어가 비어있습니다.", "search001"), + // idempotency + WORK_IN_PROGRESS("해당 요청이 이미 처리 중입니다. 잠시 후 다시 시도해주세요.", "idempotency001"), + // common UNEXPECTED_ERROR("예상하지 못한 오류가 발생했습니다.", "common999"); From 4177debce516e39c76bfc2d9d707de3db21a03ff Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 17:20:42 +0900 Subject: [PATCH 4/7] =?UTF-8?q?refactor:=20=EB=A9=B1=EB=93=B1=ED=82=A4=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/IdempotencyService.java | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java new file mode 100644 index 0000000..7c039bf --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/IdempotencyService.java @@ -0,0 +1,51 @@ +package com.nowait.applicationuser.waiting.service; + +import java.util.Optional; + +import org.springframework.stereotype.Service; + +import com.nowait.applicationuser.waiting.dto.WaitingCancelIdempotencyValue; +import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; +import com.nowait.applicationuser.waiting.redis.WaitingIdempotencyRepository; + +import jakarta.servlet.http.HttpServletRequest; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +public class IdempotencyService { + + private final WaitingIdempotencyRepository waitingIdempotencyRepository; + + // 멱등키 검증 메서드 + // TODO : 공통 멱등키 검증 메서드로 리팩토링 필요!!!!!!!!! + public Optional validateIdempotency(HttpServletRequest httpServletRequest) { + String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); + + return waitingIdempotencyRepository.findByRegisterKey(idempotentKey); + } + + public WaitingCancelIdempotencyValue validateIdempotencyCancel(HttpServletRequest httpServletRequest) { + String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); + + return waitingIdempotencyRepository.findByCancelKey(idempotentKey); + } + + // 멱등키 응답 저장 메서드 + public void saveIdempotencyResponse(String idempotentKey, Object response) { + if (idempotentKey != null && !idempotentKey.isBlank()) { + waitingIdempotencyRepository.saveIdempotencyResponse(idempotentKey, response); + } + } + + // 멱등키 PROCESSING 상태로 최초 저장 + public void saveIdempotencyKeyInProgress(String idempotentKey) { + if (idempotentKey != null && !idempotentKey.isBlank()) { + log.info("Saving idempotency key in progress: {}", idempotentKey); + waitingIdempotencyRepository.saveIdempotencyInProgress(idempotentKey); + } + } + +} From 3e2f978efdcdeef2dda4e3db80d30be3594856d3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 17:21:01 +0900 Subject: [PATCH 5/7] =?UTF-8?q?refactor:=20=EB=A9=B1=EB=93=B1=ED=82=A4=20?= =?UTF-8?q?=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20=EB=B6=84?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../redis/WaitingIdempotencyRepository.java | 32 +++++++--- .../waiting/service/WaitingService.java | 64 ++++++------------- 2 files changed, 42 insertions(+), 54 deletions(-) diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java index 41b03e3..505e158 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/redis/WaitingIdempotencyRepository.java @@ -12,9 +12,11 @@ import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; @Repository @RequiredArgsConstructor +@Slf4j public class WaitingIdempotencyRepository { private final RedisTemplate redisTemplate; @@ -23,7 +25,7 @@ public class WaitingIdempotencyRepository { private static final Duration TTL = Duration.ofMinutes(10); // 멱등키 조회 메서드 - public Optional findByKey(String key) { + public Optional findByRegisterKey(String key) { String idempotencyValue = redisTemplate.opsForValue().get(key); if (idempotencyValue == null) { @@ -31,26 +33,24 @@ public Optional findByKey(String key) { } try { - return Optional.of( - objectMapper.readValue(idempotencyValue, WaitingIdempotencyValue.class) - ); + log.info("Idempotency value found in Redis for key {}: {}", key, idempotencyValue); + return Optional.of(objectMapper.readValue(idempotencyValue, WaitingIdempotencyValue.class)); } catch (Exception e) { throw new IllegalArgumentException("Failed to deserialize value from Redis", e); } } // 멱등키 조회 메서드 - public Optional findByCancelKey(String key) { + public WaitingCancelIdempotencyValue findByCancelKey(String key) { String idempotencyValue = redisTemplate.opsForValue().get(key); if (idempotencyValue == null) { - return Optional.empty(); + return null; } try { - return Optional.of( - objectMapper.readValue(idempotencyValue, WaitingCancelIdempotencyValue.class) - ); + log.info("Idempotency value found in Redis for key {}: {}", key, idempotencyValue); + return objectMapper.readValue(idempotencyValue, WaitingCancelIdempotencyValue.class); } catch (Exception e) { throw new IllegalArgumentException("Failed to deserialize value from Redis", e); } @@ -71,4 +71,18 @@ public void saveIdempotencyResponse(String key, Object response) { throw new IllegalArgumentException("Failed to serialize value for Redis", e); } } + + public void saveIdempotencyInProgress(String key) { + IdempotencyResponse idempotencyResponse = new IdempotencyResponse( + "IN-PROGRESS", + null + ); + + try { + String jsonValue = objectMapper.writeValueAsString(idempotencyResponse); + redisTemplate.opsForValue().set(key, jsonValue, TTL); + } catch (Exception e) { + throw new IllegalArgumentException("Failed to serialize value for Redis", e); + } + } } diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index a432390..897c7cc 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -16,7 +16,7 @@ import com.nowait.applicationuser.waiting.dto.WaitingCancelIdempotencyValue; import com.nowait.applicationuser.waiting.dto.WaitingIdempotencyValue; import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; -import com.nowait.applicationuser.waiting.redis.WaitingIdempotencyRepository; +import com.nowait.applicationuser.waiting.exception.WorkInProgressException; import com.nowait.common.enums.ReservationStatus; import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.exception.DepartmentNotFoundException; @@ -43,13 +43,13 @@ @Slf4j public class WaitingService { + private final IdempotencyService idempotencyService; private final ReservationRepository reservationRepository; private final WaitingRedisRepository waitingRedisRepository; private final StoreRepository storeRepository; private final UserRepository userRepository; private final DepartmentRepository departmentRepository; private final ApplicationEventPublisher eventPublisher; - private final WaitingIdempotencyRepository waitingIdempotencyRepository; /** * 최초 대기 등록 @@ -59,10 +59,15 @@ public class WaitingService { public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, String publicCode, RegisterWaitingRequest waitingRequest, HttpServletRequest httpServletRequest) { // TODO 멱등키 동시성 처리 로직 고려 필요 (분산락 등) - RegisterWaitingResponse registerWaitingResponse = validateIdempotency(httpServletRequest); - if (registerWaitingResponse != null) { + Optional idempotencyResponse = idempotencyService.validateIdempotency(httpServletRequest); + if (idempotencyResponse.isPresent() && idempotencyResponse.getClass().equals("IN-PROGRESS")) { + throw new WorkInProgressException(); + } else if (idempotencyResponse.isPresent() && idempotencyResponse.getClass().equals("COMPLETED")) { log.info("Idempotent request detected. Returning existing response."); - return registerWaitingResponse; + return idempotencyResponse.get().getResponse(); + } else { + // TODO : DB 저장 실패 시 롤백 처리 필요 + idempotencyService.saveIdempotencyKeyInProgress(httpServletRequest.getHeader("Idempotency-Key")); } // TODO 유저 및 주점 존재 검증은 공통으로 많이 쓰이니 AOP로 빼는게 좋을 듯 @@ -109,7 +114,7 @@ public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, Stri .build(); // TODO 멱등키 응답 실패 시 어떻게 처리할 지 점검 필요 - saveIdempotencyResponse(httpServletRequest.getHeader("Idempotency-Key"), response); + idempotencyService.saveIdempotencyResponse(httpServletRequest.getHeader("Idempotency-Key"), response); return response; } @@ -117,10 +122,14 @@ public RegisterWaitingResponse registerWaiting(CustomOAuth2User oAuth2User, Stri @Transactional public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String publicCode, CancelWaitingRequest request, HttpServletRequest httpServletRequest) { // TODO 멱등키 동시성 처리 로직 고려 필요 (분산락 등) - CancelWaitingResponse cancelWaitingResponse = validateCancelIdempotency(httpServletRequest); - if (cancelWaitingResponse != null) { + WaitingCancelIdempotencyValue idempotencyResponse = idempotencyService.validateIdempotencyCancel(httpServletRequest); + if (idempotencyResponse != null && idempotencyResponse.getState().equals("IN-PROGRESS")) { + throw new WorkInProgressException(); + } else if (idempotencyResponse != null && idempotencyResponse.getState().equals("COMPLETED")) { log.info("Idempotent request detected. Returning existing response."); - return cancelWaitingResponse; + return idempotencyResponse.getResponse(); + } else { + idempotencyService.saveIdempotencyKeyInProgress(httpServletRequest.getHeader("Idempotency-Key")); } Store store = storeRepository.findByPublicCodeAndDeletedFalse(publicCode).orElseThrow(StoreNotFoundException::new); @@ -147,46 +156,11 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p .build(); // 멱등키가 있다면 멱등 응답 저장 - saveIdempotencyResponse(httpServletRequest.getHeader("Idempotency-Key"), response); + idempotencyService.saveIdempotencyResponse(httpServletRequest.getHeader("Idempotency-Key"), response); return response; } - // 멱등키 검증 메서드 - private RegisterWaitingResponse validateIdempotency(HttpServletRequest httpServletRequest) { - String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); - - // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 - // TODO 멱등성 검증 로직 점검 필요 - return waitingIdempotencyRepository.findByKey(idempotentKey) - .map(WaitingIdempotencyValue::getResponse) - .orElse(null); - } - - private CancelWaitingResponse validateCancelIdempotency(HttpServletRequest httpServletRequest) { - String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); - - // 멱등키 검증 - 이미 동일한 멱등키로 등록된 웨이팅이 있는지 확인 - // TODO 멱등성 검증 로직 점검 필요 - return waitingIdempotencyRepository.findByCancelKey(idempotentKey) - .map(WaitingCancelIdempotencyValue::getResponse) - .orElse(null); - } - - // TODO : 공통 멱등키 검증 메서드로 리팩토링 필요!!!!!!!!! - private Optional validateIdempotency_(HttpServletRequest httpServletRequest) { - String idempotentKey = httpServletRequest.getHeader("Idempotency-Key"); - - return waitingIdempotencyRepository.findByKey(idempotentKey); - } - - // 멱등키 응답 저장 메서드 - private void saveIdempotencyResponse(String idempotentKey, Object response) { - if (idempotentKey != null && !idempotentKey.isBlank()) { - waitingIdempotencyRepository.saveIdempotencyResponse(idempotentKey, response); - } - } - // 현재 대기 인원 수 조회 public GetWaitingSizeResponse getWaitingCount(CustomOAuth2User oAuth2User, String publicCode) { From 86d03aca531daa0222c78722ee70a607531aa5c3 Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 17:21:13 +0900 Subject: [PATCH 6/7] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=88=98=EC=A0=95=20=EC=A4=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/service/WaitingServiceTest.java | 45 ++++++++++++++++--- 1 file changed, 39 insertions(+), 6 deletions(-) diff --git a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java index edc05f3..afbd23b 100644 --- a/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java +++ b/nowait-app-user-api/src/test/java/com/nowait/applicationuser/waiting/service/WaitingServiceTest.java @@ -5,6 +5,7 @@ import java.util.Optional; + import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -20,6 +21,7 @@ import com.nowait.applicationuser.waiting.event.AddWaitingRegisterEvent; import com.nowait.applicationuser.waiting.redis.WaitingIdempotencyRepository; import com.nowait.domaincorerdb.reservation.entity.Reservation; +import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; import com.nowait.domaincorerdb.store.entity.Store; import com.nowait.domaincorerdb.store.exception.StoreNotFoundException; @@ -38,6 +40,8 @@ class WaitingServiceTest { @InjectMocks private WaitingService waitingService; + @InjectMocks + private IdempotencyService idempotencyService; @Mock private ApplicationEventPublisher eventPublisher; @Mock @@ -73,7 +77,7 @@ void registerWaiting_idempotentKeyExists() { .partySize(4) .build(); - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.of(new WaitingIdempotencyValue( "COMPLETED", idempotentResponse @@ -98,6 +102,35 @@ void registerWaiting_idempotentKeyExists() { verify(waitingIdempotencyRepository, never()).saveIdempotencyResponse(anyString(), any(RegisterWaitingResponse.class)); } + @Test + @DisplayName("멱등키가 In-PROGRESS 상태이면 DuplicateReservationException 발생") + void registerWaiting_idempotentKeyInProgress() { + // given + RegisterWaitingRequest request = new RegisterWaitingRequest(4); + + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) + .thenReturn(Optional.of(new WaitingIdempotencyValue( + "IN-PROGRESS", + null + ))); + + // when & then + assertThatThrownBy(() -> waitingService.registerWaiting( + customOAuth2User, + "ZiVXAD1vVr5b", + request, + httpServletRequest + )).isInstanceOf(DuplicateReservationException.class); + + verify(storeRepository, never()).findByPublicCodeAndDeletedFalse(anyString()); + verify(userRepository, never()).findById(anyLong()); + verify(waitingRedisRepository, never()).incrementAndCheckWaitingLimit(anyLong(), anyLong()); + verify(reservationRepository, never()).save(any(Reservation.class)); + verify(eventPublisher, never()).publishEvent(any()); + verify(waitingIdempotencyRepository, never()).saveIdempotencyResponse(anyString(), + any(RegisterWaitingResponse.class)); + } + @Test @DisplayName("Idempotency-Key가 blank이면 멱등 로직을 타지 않는다") void registerWaiting_idempotentKeyNotExists() { @@ -149,7 +182,7 @@ void registerWaiting_success() { RegisterWaitingRequest request = new RegisterWaitingRequest(4); when(httpServletRequest.getHeader("Idempotency-Key")).thenReturn(IDEMPOTENCY_KEY); - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); Long userId = 10L; @@ -191,7 +224,7 @@ void registerWaiting_success() { @DisplayName("DB 저장 중 예외 발생 시 이벤트 발행 및 멱등 저장이 수행되지 않음") void registerWaiting_dbSaveException() { // given - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); RegisterWaitingRequest request = new RegisterWaitingRequest(4); @@ -233,7 +266,7 @@ void registerWaiting_exceedWaitingLimit() { RegisterWaitingRequest request = new RegisterWaitingRequest(4); when(httpServletRequest.getHeader("Idempotency-Key")).thenReturn(IDEMPOTENCY_KEY); - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); @@ -270,7 +303,7 @@ void registerWaiting_exceedWaitingLimit() { @DisplayName("존재하지 않는 publicCode이면 StoreNotFoundException 발생") void registerWaiting_storeNotFound() { // given - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); RegisterWaitingRequest request = new RegisterWaitingRequest(4); @@ -296,7 +329,7 @@ void registerWaiting_storeNotFound() { @DisplayName("존재하지 않는 userId이면 UserNotFoundException 발생") void registerWaiting_userNotFound() { // given - when(waitingIdempotencyRepository.findByKey(IDEMPOTENCY_KEY)) + when(waitingIdempotencyRepository.findByRegisterKey(IDEMPOTENCY_KEY)) .thenReturn(Optional.empty()); RegisterWaitingRequest request = new RegisterWaitingRequest(4); String publicCode = "ZiVXAD1vVr5b"; From d8132ac09e71e720d669a027c363bf8bceb7b85a Mon Sep 17 00:00:00 2001 From: Jihun Kim Date: Fri, 13 Feb 2026 18:50:44 +0900 Subject: [PATCH 7/7] =?UTF-8?q?refactor:=20=EC=9B=A8=EC=9D=B4=ED=8C=85=20?= =?UTF-8?q?=EC=A3=BC=EC=A0=90=20=EC=A0=95=EB=B3=B4=20=EC=A1=B0=ED=9A=8C=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../waiting/controller/WaitingController.java | 21 +++++ .../waiting/dto/GetMyWaitingInfoResponse.java | 23 +++++ .../waiting/service/WaitingService.java | 35 ++++++++ .../reservation/dto/GetMyWaitingBaseDto.java | 49 +++++++++++ .../ReservationCustomRepository.java | 12 +++ .../ReservationCustomRepositoryImpl.java | 87 +++++++++++++++++++ .../repository/ReservationRepository.java | 2 +- 7 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepository.java create mode 100644 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java index dbc2ed0..52c3bc6 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/controller/WaitingController.java @@ -1,5 +1,7 @@ package com.nowait.applicationuser.waiting.controller; +import java.util.List; + import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.DeleteMapping; @@ -12,6 +14,7 @@ import com.nowait.applicationuser.waiting.dto.CancelWaitingRequest; import com.nowait.applicationuser.waiting.dto.CancelWaitingResponse; +import com.nowait.applicationuser.waiting.dto.GetMyWaitingInfoResponse; import com.nowait.applicationuser.waiting.dto.GetWaitingSizeResponse; import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; @@ -34,6 +37,24 @@ public class WaitingController { /** * 대기열 리팩토링용 API */ + @GetMapping() + @Operation(summary = "대기열 리팩토링용 API", description = "전체 대기 목록 조회") + public ResponseEntity getMyWaitingInfo( + @AuthenticationPrincipal CustomOAuth2User customOAuth2User + ) { + List response = waitingService.getMyWaitingInfo( + customOAuth2User + ); + + return ResponseEntity + .ok() + .body( + ApiUtils.success( + response + ) + ); + } + @PostMapping("/{publicCode}") @Operation(summary = "대기열 리팩토링용 API", description = "대기열 리팩토링용 API") public ResponseEntity registerWaiting( diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java new file mode 100644 index 0000000..dd33aef --- /dev/null +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/dto/GetMyWaitingInfoResponse.java @@ -0,0 +1,23 @@ +package com.nowait.applicationuser.waiting.dto; + +import java.time.LocalDateTime; + +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class GetMyWaitingInfoResponse { + private String reservationId; + private Long storeId; + private String storeName; + private String departmentName; + private Integer rank; + private Integer teamsAhead; + private Integer partySize; + private String status; + private LocalDateTime registeredAt; + private String location; + private String profileImageUrl; + private String bannerImageUrl; +} diff --git a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java index 897c7cc..b7f7a3b 100644 --- a/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java +++ b/nowait-app-user-api/src/main/java/com/nowait/applicationuser/waiting/service/WaitingService.java @@ -2,6 +2,7 @@ import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; +import java.util.List; import java.util.Optional; import org.springframework.context.ApplicationEventPublisher; @@ -10,6 +11,7 @@ import com.nowait.applicationuser.waiting.dto.CancelWaitingRequest; import com.nowait.applicationuser.waiting.dto.CancelWaitingResponse; +import com.nowait.applicationuser.waiting.dto.GetMyWaitingInfoResponse; import com.nowait.applicationuser.waiting.dto.GetWaitingSizeResponse; import com.nowait.applicationuser.waiting.dto.RegisterWaitingRequest; import com.nowait.applicationuser.waiting.dto.RegisterWaitingResponse; @@ -21,6 +23,7 @@ import com.nowait.domaincorerdb.department.entity.Department; import com.nowait.domaincorerdb.department.exception.DepartmentNotFoundException; import com.nowait.domaincorerdb.department.repository.DepartmentRepository; +import com.nowait.domaincorerdb.reservation.dto.GetMyWaitingBaseDto; import com.nowait.domaincorerdb.reservation.entity.Reservation; import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException; import com.nowait.domaincorerdb.reservation.repository.ReservationRepository; @@ -161,6 +164,38 @@ public CancelWaitingResponse cancelWaiting(CustomOAuth2User oAuth2User, String p return response; } + // 웨이팅 목록 조회 + public List getMyWaitingInfo(CustomOAuth2User oAuth2User) { + User user = userRepository.findById(oAuth2User.getUserId()) + .orElseThrow(UserNotFoundException::new); + + Long userId = user.getId(); + + List waitingInfoList = reservationRepository.findMyWaitingInfo(userId); + + return waitingInfoList.stream() + .map(dto -> { + Long storeId = dto.getStoreId(); + Long rank = waitingRedisRepository.getWaitingCount(storeId); + + return GetMyWaitingInfoResponse.builder() + .reservationId(dto.getReservationId()) + .storeId(dto.getStoreId()) + .storeName(dto.getStoreName()) + .departmentName(dto.getDepartmentName()) + .rank(rank.intValue()) + .teamsAhead(rank.intValue() - 1) + .partySize(dto.getPartySize()) + .status(dto.getStatus().name()) + .registeredAt(dto.getRegisteredAt()) + .location(dto.getLocation()) + .profileImageUrl(dto.getProfileImageUrl()) + .bannerImageUrl(dto.getBannerImageUrl()) + .build(); + }) + .toList(); + } + // 현재 대기 인원 수 조회 public GetWaitingSizeResponse getWaitingCount(CustomOAuth2User oAuth2User, String publicCode) { diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java new file mode 100644 index 0000000..dc641db --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/dto/GetMyWaitingBaseDto.java @@ -0,0 +1,49 @@ +package com.nowait.domaincorerdb.reservation.dto; + +import java.time.LocalDateTime; +import java.util.List; + +import com.nowait.common.enums.ReservationStatus; +import com.querydsl.core.annotations.QueryProjection; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Getter; + +@Getter +public class GetMyWaitingBaseDto { + private final String reservationId; + private final Long storeId; + private final String storeName; + private final String departmentName; + private final Integer partySize; + private final ReservationStatus status; + private final LocalDateTime registeredAt; + private final String location; + private final String profileImageUrl; + private final String bannerImageUrl; + + @QueryProjection + public GetMyWaitingBaseDto( + String reservationId, + Long storeId, + String storeName, + String departmentName, + Integer partySize, + ReservationStatus status, + LocalDateTime registeredAt, + String location, + String profileImageUrl, + String bannerImageUrl + ) { + this.reservationId = reservationId; + this.storeId = storeId; + this.storeName = storeName; + this.departmentName = departmentName; + this.partySize = partySize; + this.status = status; + this.registeredAt = registeredAt; + this.location = location; + this.profileImageUrl = profileImageUrl == null ? "" : profileImageUrl; + this.bannerImageUrl = bannerImageUrl == null ? "" : bannerImageUrl; + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepository.java new file mode 100644 index 0000000..eb4014a --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepository.java @@ -0,0 +1,12 @@ +package com.nowait.domaincorerdb.reservation.repository; + +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.nowait.domaincorerdb.reservation.dto.GetMyWaitingBaseDto; + +@Repository +public interface ReservationCustomRepository { + List findMyWaitingInfo(Long userId); +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java new file mode 100644 index 0000000..2138887 --- /dev/null +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationCustomRepositoryImpl.java @@ -0,0 +1,87 @@ +package com.nowait.domaincorerdb.reservation.repository; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.util.List; + +import org.springframework.stereotype.Repository; + +import com.nowait.common.enums.ReservationStatus; +import com.nowait.domaincorerdb.department.entity.QDepartment; +import com.nowait.domaincorerdb.reservation.dto.GetMyWaitingBaseDto; +import com.nowait.domaincorerdb.reservation.dto.QGetMyWaitingBaseDto; +import com.nowait.domaincorerdb.reservation.entity.QReservation; +import com.nowait.domaincorerdb.store.entity.ImageType; +import com.nowait.domaincorerdb.store.entity.QStore; +import com.nowait.domaincorerdb.store.entity.QStoreImage; +import com.nowait.domaincorerdb.user.entity.QUser; +import com.querydsl.jpa.JPAExpressions; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@Repository +@RequiredArgsConstructor +public class ReservationCustomRepositoryImpl implements ReservationCustomRepository { + + private final JPAQueryFactory queryFactory; + + private final QReservation reservation = QReservation.reservation; + private final QStore store = QStore.store; + private final QDepartment department = QDepartment.department; + private final QUser user = QUser.user; + private final QStoreImage storeImage = QStoreImage.storeImage; + + + @Override + public List findMyWaitingInfo(Long userId) { + + LocalDateTime startOfDay = LocalDate.now().atStartOfDay(); + LocalDateTime endOfDay = LocalDate.now().atTime(LocalTime.MAX); + + QStoreImage subStoreImage = new QStoreImage("subStoreImage"); + + return queryFactory + .select(new QGetMyWaitingBaseDto( + reservation.reservationNumber, + store.storeId, + store.name, + department.name, + reservation.partySize, + reservation.status, + reservation.requestedAt, + store.location, + storeImage.imageUrl, + + JPAExpressions + .select(subStoreImage.imageUrl) + .from(subStoreImage) + .where( + subStoreImage.store.storeId.eq(store.storeId), + subStoreImage.imageType.eq(ImageType.BANNER) + ) + .limit(1) + )) + .from(reservation) + .join(reservation.store, store) + .join(reservation.user, user) + .leftJoin(department) + .on(store.departmentId.eq(department.id)) + .leftJoin(storeImage) + .on( + storeImage.store.storeId.eq(store.storeId), + storeImage.imageType.eq(ImageType.PROFILE) + ) + .where( + reservation.user.id.eq(userId), + reservation.status.in( + ReservationStatus.WAITING, + ReservationStatus.CALLING + ), + reservation.requestedAt.between(startOfDay, endOfDay), + store.deleted.isFalse() + ) + .fetch(); + } +} diff --git a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java index 79ed84d..15bff08 100644 --- a/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java +++ b/nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/repository/ReservationRepository.java @@ -13,7 +13,7 @@ import com.nowait.domaincorerdb.user.entity.User; @Repository -public interface ReservationRepository extends JpaRepository { +public interface ReservationRepository extends JpaRepository, ReservationCustomRepository { List findAllByStore_StoreIdOrderByRequestedAtAsc(Long storeId); boolean existsByUserAndStoreAndStatusIn(User user, Store store, List statuses);