-
Notifications
You must be signed in to change notification settings - Fork 0
Refactor: 예외처리 보강 및 예약 번호를 통한 대기 상태 변경 로직 추가 #320
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
- 사용자 권한 검증 메서드 분리 - 예외처리 케이스 보강
|
Caution Review failedThe pull request is closed. Walkthrough이 PR은 전역 예외 매핑 대폭 확장, 다수 도메인별 커스텀 예외 추가, 관리자 서비스의 권한 검사 중앙화, 예약 상태 전이의 상태머신화 및 예약번호 기반 입장 처리 API/Redis 양방향 매핑 추가, ErrorMessage 식별자/메시지 보강을 포함합니다. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Admin as Admin(Manager/SuperAdmin)
participant RC as ReservationController
participant RS as ReservationService
participant RRedis as WaitingRedisRepository
participant RDB as ReservationRepository
Admin->>RC: PATCH /admin/{storeId}/{reservationNumber}\nbody: ReservationStatusRequest
RC->>RS: processEntryStatusByReservationNumber(storeId, reservationNumber, member, newStatus)
RS->>RS: getUser(member) & validate*Authorization
alt newStatus == CALLING
RS->>RRedis: getUserIdByReservationNumber(...)
RS->>RRedis: set calling state / update TTL
RS-->>RC: EntryStatusResponseDto(Calling)
else newStatus == CONFIRMED
RS->>RRedis: lookup reservationNumber→userId
opt Redis miss
RS->>RDB: find Reservation by reservationNumber
end
RS->>RRedis: cleanup mappings
RS->>RDB: save/update Reservation(Confirmed)
RS-->>RC: EntryStatusResponseDto(Confirmed)
else newStatus == CANCELLED
RS->>RRedis: lookup & cleanup mappings/queues
RS->>RDB: create Reservation(Cancelled)
RS-->>RC: EntryStatusResponseDto(Cancelled)
else
RS-->>RC: throw UnsupportedReservationStatusException
end
RC-->>Admin: 200 OK ApiUtils.success(...)
sequenceDiagram
autonumber
participant Admin as Admin
participant OS as OrderService(Admin)
participant Order as UserOrder(Entity)
Admin->>OS: updateOrderStatus(orderId, targetStatus)
OS->>OS: getUser & validateUpdateAuthorization
OS->>Order: updateStatus(targetStatus)
alt invalid transition
Order-->>OS: throw InvalidOrderStatusTransitionException
else cancel request on CANCELLED
Order-->>OS: throw OrderAlreadyCancelledException
else valid
Order-->>OS: status updated
end
OS-->>Admin: result or mapped error
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
📜 Recent review detailsConfiguration used: CodeRabbit UI Review profile: CHILL Plan: Pro 📒 Files selected for processing (3)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java (1)
41-43: 예약번호 기반 흐름을 도입했으므로 DB 유일성 보장 필요
reservationNumber가 식별/조회 키로 사용되는데 컬럼 제약에unique가 없습니다. 중복 발생 시 Redis 역매핑/서비스 로직과 데이터 정합성이 깨질 수 있습니다. DB 레벨 유니크 인덱스 추가를 권장합니다(널 허용은 그대로 유지 가능).애너테이션과 마이그레이션 예시는 아래와 같습니다.
- @Column(name = "reservation_number", nullable = true, length = 50) + @Column(name = "reservation_number", nullable = true, length = 50, unique = true) private String reservationNumber;
- 운영 DB에는 DDL 마이그레이션을 별도로 적용해 주세요(예: MySQL).
ALTER TABLE reservation ADD UNIQUE INDEX ux_reservation_reservation_number (reservation_number);nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java (1)
81-91: 메뉴-매장 교차 참조 검증 누락(데이터 무결성/권한 우회 위험)주문 생성 시 요청된 메뉴가 조회된 Store에 속하는지 검사하지 않아 타 매장 메뉴로 주문이 생성될 수 있습니다. 데이터 무결성과 정산/권한 모델을 깨뜨릴 수 있어 반드시 차단해야 합니다.
아래와 같이 교차 매장 메뉴를 차단해 주세요(도메인에 존재하는 예외 사용 가정: MenuCrossStoreConflictException).
@@ - .map(item -> { - Menu menu = Optional.ofNullable(menuMap.get(item.getMenuId())) - .orElseThrow(MenuNotFoundException::new); + .map(item -> { + Menu menu = Optional.ofNullable(menuMap.get(item.getMenuId())) + .orElseThrow(MenuNotFoundException::new); + if (!menu.getStore().equals(store)) { + throw new com.nowait.domaincorerdb.menu.exception.MenuCrossStoreConflictException(); + } return OrderItem.builder() .userOrder(savedOrder) .menu(menu) .quantity(item.getQuantity()) .build(); })
🧹 Nitpick comments (25)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java (1)
8-8: 미사용 import 제거 필요
@DeleteMapping어노테이션이 import되었지만 실제로는 사용되지 않고 있습니다. @DeleteMapping은 HTTP DELETE 요청을 처리하는 강력한 도구입니다만, 현재 컨트롤러에는 DELETE 엔드포인트가 없습니다.-import org.springframework.web.bind.annotation.DeleteMapping;nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java (1)
40-62: TTL 설정 누락 확인 필요.userMapKey에 대한 TTL 설정이 누락된 것으로 보입니다. 다른 키들과 동일한 생명주기를 가져야 하므로 TTL 설정을 추가해야 합니다.
다음과 같이 TTL을 추가하는 것을 권장합니다:
redisTemplate.expire(queueKey, ttl); redisTemplate.expire(partyKey, ttl); redisTemplate.expire(statusKey, ttl); redisTemplate.expire(seqKey, ttl); redisTemplate.expire(numberMapKey, ttl); + redisTemplate.expire(userMapKey, ttl);nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java (1)
5-9: 예외 정의 적절. 선택: 직렬화 UID 추가 권장경고 억제와 이력 안정성을 위해
serialVersionUID추가를 권장합니다.다음 패치를 고려해 주세요:
public class ReservationAlreadyCancelledException extends RuntimeException { + private static final long serialVersionUID = 1L; public ReservationAlreadyCancelledException() { super(ErrorMessage.RESERVATION_ALREADY_CANCELLED.getMessage()); } }nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java (1)
6-10: 예외 메시지 구성은 적절. 선택: 직렬화 UID 추가 권장장기적으로 경고 및 직렬화 안정성을 위해
serialVersionUID추가를 권장합니다.public class InvalidOrderStatusTransitionException extends RuntimeException { + private static final long serialVersionUID = 1L; public InvalidOrderStatusTransitionException(OrderStatus current, OrderStatus target) { super(ErrorMessage.INVALID_ORDER_STATUS_TRANSITION.format(current, target)); } }nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java (1)
66-71: 동일 상태로의 업데이트 시 예외 발생 — PATCH의 멱등성 확보 고려현재 동일 상태로 업데이트하면
InvalidOrderStatusTransitionException이 발생합니다. API 레벨에서 멱등적 PATCH를 원한다면 동일 상태 입력 시 조용히 반환하도록 처리하는 것을 권장합니다.public void updateStatus(OrderStatus newStatus) { + if (this.status == newStatus) { + return; // 멱등 처리 + } if (!isValidTransition(this.status, newStatus)) { throw new InvalidOrderStatusTransitionException(this.status, newStatus); } this.status = newStatus; }nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java (1)
6-10: 예외 정의 OK. 선택: 직렬화 UID 추가 권장다른 예외들과 일관성을 위해
serialVersionUID추가를 권장합니다.public class InvalidReservationStatusTransitionException extends RuntimeException { + private static final long serialVersionUID = 1L; public InvalidReservationStatusTransitionException(ReservationStatus current, ReservationStatus target) { super(ErrorMessage.INVALID_RESERVATION_STATUS_TRANSITION.format(current, target)); } }nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java (1)
99-116: 경로 패턴 충돌 가능성 및 일관성 개선 제안
/reservations/admin/{storeId}/{reservationNumber}는 2세그먼트 동적 매핑이라 향후 PATCH 메서드가 같은 패턴을 추가할 때 모호해질 수 있습니다. 기존 사용자ID 기반 업데이트 경로(/admin/update/{storeId}/{userId})와의 일관성도 떨어집니다. 다음과 같이 명시적 세그먼트를 추가하면 충돌을 예방하고 가독성이 좋아집니다.- @PatchMapping("/admin/{storeId}/{reservationNumber}") + @PatchMapping("/admin/update/{storeId}/number/{reservationNumber}") @Operation(summary = "예약팀 상태 업데이트 처리", description = "특정 예약에 대한 입장 완료 처리") @ApiResponse(responseCode = "200", description = "예약팀 상태 변경 : CALLING -> CONFIRMED") public ResponseEntity<?> updateEntryWithReservationNumber( @PathVariable Long storeId, @PathVariable String reservationNumber, @RequestBody ReservationStatusRequest request, @AuthenticationPrincipal MemberDetails memberDetails ) { EntryStatusResponseDto response = reservationService.processEntryStatusByReservationNumber(storeId, reservationNumber, memberDetails, request.getStatus()); return ResponseEntity .status(HttpStatus.OK) .body( ApiUtils.success( response )); }nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java (1)
65-79: 동일 상태 업데이트 처리의 일관성/멱등성 재검토
CONFIRMED/CANCELLED는 전용 예외를 던지지만,WAITING/CALLING의 동일 상태 입력은InvalidReservationStatusTransitionException을 발생시킵니다. PATCH 멱등성 및 도메인 일관성 측면에서 동일 상태일 때는 조용히 반환(또는 전용 Already 예외 통일)하는 방안을 고려해 주세요.public void markUpdated(LocalDateTime updatedAt, ReservationStatus newStatus) { - if (this.status == newStatus) { - switch (newStatus) { - case CONFIRMED -> throw new ReservationAlreadyConfirmedException(); - case CANCELLED -> throw new ReservationAlreadyCancelledException(); - default -> {} - } - } + if (this.status == newStatus) { + switch (newStatus) { + case CONFIRMED -> throw new ReservationAlreadyConfirmedException(); + case CANCELLED -> throw new ReservationAlreadyCancelledException(); + default -> { return; } // 멱등 처리 + } + } if (!isValidTransition(this.status, newStatus)) { throw new InvalidReservationStatusTransitionException(this.status, newStatus); } this.status = newStatus; this.updatedAt = updatedAt; }nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java (3)
140-147: 중복 방지 서명 해시를 SHA‑256으로 강화 권장MD5는 충돌 리스크가 있어(매우 낮지만 2초 윈도우에서 false positive 가능) SHA‑256 사용을 권장합니다. 표준 JDK로 대체 가능합니다.
- String raw = storeId + "-" + tableId + "-" + cartString; - return DigestUtils.md5DigestAsHex(raw.getBytes()); + String raw = storeId + "-" + tableId + "-" + cartString; + try { + java.security.MessageDigest md = java.security.MessageDigest.getInstance("SHA-256"); + byte[] digest = md.digest(raw.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + StringBuilder sb = new StringBuilder(digest.length * 2); + for (byte b : digest) sb.append(String.format("%02x", b)); + return sb.toString(); + } catch (java.security.NoSuchAlgorithmException e) { + throw new IllegalStateException("Hash algorithm not available", e); + }
149-156: 중복 검사 쿼리 인덱스 필요existsBySignatureAndCreatedAtAfter는 (signature, createdAt) 복합 인덱스가 없으면 테이블 스캔/시간대별 스캔이 발생할 수 있습니다. 운영 트래픽 대비 인덱스 생성 권장.
예시(적용 DB에 맞춰 조정):
- PostgreSQL:
CREATE INDEX CONCURRENTLY idx_orders_sig_createdat ON user_order(signature, created_at DESC);- MySQL:
CREATE INDEX idx_orders_sig_createdat ON user_order(signature, created_at);
103-105: 주문 목록 정렬 명시화 권장현재 반환 순서가 비결정적일 수 있습니다. 최신순 정렬을 명시하면 클라이언트 UX가 안정적입니다.
- return userOrders.stream() + return userOrders.stream() + .sorted(java.util.Comparator.comparing(UserOrder::getCreatedAt).reversed()) .map(order -> OrderResponseDto.builder()Also applies to: 107-119
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java (1)
93-115: 스프링 시큐리티로 권한 위임 고려메서드 내부 권한 체크 대신 @PreAuthorize로 위임하면 반복/누락 위험을 줄일 수 있습니다.
예:
@PreAuthorize("hasRole('SUPER_ADMIN') or (hasRole('MANAGER') and #memberDetails.storeId == principal.storeId)")nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java (2)
5-7: 불필요한 import 정리AuthenticationPrincipal, OrderView/UpdateUnauthorizedException import는 본 파일에서 사용되지 않습니다. 정리해 주세요.
-import org.springframework.security.core.annotation.AuthenticationPrincipal; @@ -import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; -import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException;Also applies to: 18-19
130-137: toggleActive의 storeId null 방어 로직 권장storeId가 null이면 레포지토리 구현에 따라 IAE가 던져질 수 있습니다. 상위와 동일하게 파라미터 검증을 추가하세요.
public Boolean toggleActive(Long storeId) { - Store store = storeRepository.findById(storeId) + if (storeId == null) throw new StoreParamEmptyException(); + Store store = storeRepository.findById(storeId) .orElseThrow(StoreNotFoundException::new);nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java (3)
75-100: 다중 키 삭제의 원자성 보장 필요(경쟁조건)userId↔reservationNumber, status/queue/party/calledAt 등 다수 키를 순차 삭제합니다. 동시 업데이트와 경합 시 양방향 불일치가 남을 수 있어 Redis 트랜잭션(MULTI/EXEC) 또는 Lua 스크립트로 원자화하는 것을 권장합니다.
- public void deleteWaiting(Long storeId, String userId) { - String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); - String userMapKey = RedisKeyUtils.buildReservationUserKey(storeId); - - Object reservationNumber = redisTemplate.opsForHash().get(numberMapKey, userId); - - // userId → reservationNumber 삭제 - redisTemplate.opsForHash().delete(numberMapKey, userId); - - // reservationNumber → userId 삭제 - if (reservationNumber != null) { - redisTemplate.opsForHash().delete(userMapKey, reservationNumber); - } - - String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; - redisTemplate.opsForHash().delete(statusKey, userId); - - String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; - redisTemplate.opsForZSet().remove(queueKey, userId); - - String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; - redisTemplate.opsForHash().delete(partyKey, userId); - - String calledAtKey = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; - redisTemplate.opsForHash().delete(calledAtKey, userId); - } + public void deleteWaiting(Long storeId, String userId) { + final String numberMapKey = RedisKeyUtils.buildReservationNumberKey(storeId); + final String userMapKey = RedisKeyUtils.buildReservationUserKey(storeId); + final String statusKey = RedisKeyUtils.buildWaitingStatusKeyPrefix() + storeId; + final String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; + final String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId; + final String calledAtKey = RedisKeyUtils.buildWaitingCalledAtKeyPrefix() + storeId; + + redisTemplate.execute((org.springframework.data.redis.core.SessionCallback<Object>) operations -> { + operations.watch(numberMapKey); + Object reservationNumber = operations.opsForHash().get(numberMapKey, userId); + operations.multi(); + operations.opsForHash().delete(numberMapKey, userId); + if (reservationNumber != null) { + operations.opsForHash().delete(userMapKey, reservationNumber); + } + operations.opsForHash().delete(statusKey, userId); + operations.opsForZSet().remove(queueKey, userId); + operations.opsForHash().delete(partyKey, userId); + operations.opsForHash().delete(calledAtKey, userId); + return operations.exec(); + }); + }
55-66: 상태 문자열 하드코딩 대신 상수/Enum 사용 제안"WAITING"/"CALLING" 매직스트링은 오타 리스크가 있습니다. 공용 상수/Enum으로 치환을 권장합니다.
- if (!"WAITING".equals(status) && !"CALLING".equals(status)) { + if (!WaitingStatus.WAITING.equals(status) && !WaitingStatus.CALLING.equals(status)) {또는 최소한 파일 상단에 상수 정의:
private static final String STATUS_WAITING = "WAITING"; private static final String STATUS_CALLING = "CALLING";
55-66: 메서드 명칭 일관성(nit): getReservationId → getReservationNumber반환값이 reservationNumber이므로 명칭 정합성을 맞추면 가독성이 좋아집니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java (4)
64-71: newStatus와 상태 전이 유효성 검증 보강 권장newStatus가 null이거나 금지 전이(예: CANCELLED→COOKED)일 때의 방어가 서비스 계층에 없습니다. 도메인에서 이미 막더라도 서비스 차원에서 빠른 실패가 유용합니다.
public OrderStatusUpdateResponseDto updateOrderStatus(Long orderId, OrderStatus newStatus, MemberDetails memberDetails) { + if (newStatus == null) throw new IllegalArgumentException("newStatus must not be null");
93-101: 취소 요청 파라미터 검증 추가 권장cancelOrderRequest 또는 reason이 null인 경우 이벤트에 null reason이 전달될 수 있습니다. 사전 검증을 추가해 주세요.
public OrderStatusUpdateResponseDto cancelOrder(Long orderId, CancelOrderRequest cancelOrderRequest, MemberDetails memberDetails) { - User user = getUser(memberDetails); + User user = getUser(memberDetails); + if (cancelOrderRequest == null || cancelOrderRequest.reason() == null || cancelOrderRequest.reason().isBlank()) { + throw new IllegalArgumentException("cancel reason is required"); + }
54-61: 주문 목록 정렬 명시화 권장findAllOrders의 반환 순서를 최신순으로 고정하면 클라이언트 처리 단순화에 도움이 됩니다.
- return orderRepository.findAllByStore_StoreIdAndCreatedAtBetween(storeId, startDateTime, endDateTime) - .stream() + return orderRepository.findAllByStore_StoreIdAndCreatedAtBetween(storeId, startDateTime, endDateTime) + .stream() + .sorted(java.util.Comparator.comparing(OrderResponseDto::createdAt).reversed()) .map(OrderResponseDto::fromEntity)필요 시 레포지토리 쿼리에 정렬을 직접 반영하는 방법이 더 효율적입니다.
124-133: 읽기 전용 API에 update 권한 검사를 사용getTop5StoresBySalesToday는 조회성 API인데 update 권한 검사를 적용했습니다. 의도라면 괜찮지만 보수적으로 view 권한으로 낮추는 편이 일관됩니다.
- validateUpdateAuthorization(user, storeId); + validateViewAuthorization(user, storeId);Also applies to: 142-147
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java (2)
30-31: 사용되지 않는 import 제거 필요
OrderUpdateUnauthorizedException과OrderViewUnauthorizedException이 import되어 있지만 이 클래스에서 사용되지 않습니다.-import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException; -import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException;
84-92: storeId 매개변수가 사용되지 않음 — 제거하거나 검증에 사용하세요nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java의 getMenuById(Long storeId, Long menuId, MemberDetails)에서 storeId는 null 체크만 하고 실제 로직에서는 사용되지 않습니다; 권한 검사는 menu.getStoreId()로 수행됩니다.
조치(둘 중 하나): 1) 컨트롤러·메서드 시그니처에서 storeId 제거 2) 전달된 storeId와 menu.getStoreId()를 비교해 권한 검증에 사용nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java (1)
109-109: 디버깅용 System.out.println 제거 필요프로덕션 코드에
System.out.println이 남아있습니다. 로깅 프레임워크를 사용해야 합니다.- System.out.println(waitingList); + log.debug("Waiting list for store {}: {}", storeId, waitingList);Also applies to: 114-114
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java (1)
315-315: 오타 수정 필요로그 메시지에 오타가 있습니다.
- log.error("reservation_viewUnauthorizedException", e); + log.error("reservationViewUnauthorizedException", e);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (32)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java(11 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java(9 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java(4 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java(1 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java(11 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java(5 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java(1 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java(5 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java(1 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java(6 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java(2 hunks)nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java(5 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java(2 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java(2 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java(0 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/ReservationDataInconsistencyException.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java(3 hunks)
💤 Files with no reviewable changes (1)
- nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java
🧰 Additional context used
🧬 Code graph analysis (9)
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java (1)
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
RedisKeyUtils(9-98)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java (2)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java (1)
StorePaymentDeleteUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java (2)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java (1)
InvalidOrderStatusTransitionException(6-10)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java (1)
OrderAlreadyCancelledException(5-9)
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java (1)
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
RedisKeyUtils(9-98)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java (3)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java (1)
InvalidReservationStatusTransitionException(6-10)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java (1)
ReservationAlreadyCancelledException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java (1)
ReservationAlreadyConfirmedException(5-9)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java (2)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderNotFoundException.java (1)
OrderNotFoundException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java (1)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java (7)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java (1)
MenuCrossStoreConflictException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDeleteUnauthorizedException.java (1)
MenuDeleteUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java (1)
MenuDuplicateIdException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java (1)
MenuInvalidSortOrderException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuUpdateUnauthorizedException.java (1)
MenuUpdateUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuViewUnauthorizedException.java (1)
MenuViewUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java (15)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java (1)
MenuAlreadyDeletedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java (1)
MenuCrossStoreConflictException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java (1)
MenuDuplicateIdException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java (1)
MenuInvalidSortOrderException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java (1)
MenuToggleUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java (1)
OrderAlreadyCancelledException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java (1)
InvalidReservationParameterException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationNumberIssueFailException.java (1)
ReservationNumberIssueFailException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java (1)
UnsupportedReservationStatusException(6-10)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UserWaitingLimitExceededException.java (1)
UserWaitingLimitExceededException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreWaitingDisabledException.java (1)
StoreWaitingDisabledException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentAlreadyExistsException.java (1)
StorePaymentAlreadyExistsException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorizedException.java (1)
StorePaymentCreationUnauthorizedException(5-10)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/exception/MenuCounterUpdateException.java (1)
MenuCounterUpdateException(5-7)
🔇 Additional comments (31)
nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java (1)
175-175: 에러 코드 상수명 변경 확인
NOT_FOUND_USER.getCode()로 변경되었습니다. 이는 에러 메시지 표준화 작업의 일환으로 보입니다.nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
7-7: 에러 메시지 상수 표준화 완료
NOT_FOUND_USER로 변경되어 GlobalExceptionHandler와 일관성이 확보되었습니다.nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
79-81: 예약번호-사용자 매핑을 위한 키 생성 메서드 추가
buildReservationUserKey메서드가 추가되어 예약번호를 통한 사용자 ID 역방향 매핑을 지원합니다. 기존 패턴과 일관성 있게 구현되어 있습니다.nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java (1)
1-9: 메뉴 중복 ID 예외 클래스 추가메뉴 도메인의 예외 처리를 강화하는 새로운 예외 클래스입니다. 표준화된 에러 메시지 패턴을 따르고 있습니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java (1)
1-9: 메뉴 정렬 순서 검증 예외 클래스 추가메뉴의 정렬 순서 관련 비즈니스 로직 검증을 위한 예외 클래스입니다. 표준화된 패턴을 준수하고 있습니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java (1)
1-9: 매개변수화된 에러 메시지를 활용한 예외 클래스
ErrorMessage.format(details)메서드를 사용하여 동적 메시지를 생성합니다. 이는 더 구체적인 오류 정보를 제공할 수 있어 좋은 접근 방식입니다.nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java (1)
1-9: 주문 취소 상태 검증 예외 클래스 추가주문 도메인의 상태 전이 검증을 강화하는 예외 클래스입니다. 비즈니스 로직의 무결성 보장에 도움이 됩니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java (1)
5-9: 예외 클래스 구조가 일관성 있게 잘 설계되어 있습니다.표준적인 RuntimeException 확장 패턴을 따르고 있으며, ErrorMessage enum을 활용한 메시지 관리 방식이 적절합니다.
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/ReservationDataInconsistencyException.java (1)
5-9: 매개변수를 활용한 동적 메시지 구성이 적절합니다.Redis와 DB 간의 데이터 불일치 상황을 구체적으로 설명할 수 있도록 details 매개변수를 받아 format() 메서드를 활용하는 구조가 좋습니다. 디버깅 시 유용할 것으로 판단됩니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java (1)
5-9: 인가 관련 예외 처리가 명확하게 구현되었습니다.메뉴 토글 권한 부족 시나리오에 대한 전용 예외 클래스로, 보안 관련 예외 처리가 체계적으로 이루어지고 있습니다.
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java (2)
40-40: 역방향 조회를 위한 매핑 키 추가가 적절합니다.예약번호로 사용자 ID를 조회할 수 있도록 userMapKey를 추가한 것이 좋습니다. RedisKeyUtils를 통한 일관된 키 관리 방식을 유지하고 있습니다.
51-51: 양방향 매핑 구조 완성.기존 numberMapKey(userId → reservationId)에 더해 userMapKey(reservationId → userId) 매핑을 추가하여 양방향 조회가 가능하도록 구현되었습니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java (1)
5-9: 크로스 스토어 충돌 예외 처리가 명확합니다.서로 다른 매장의 메뉴가 혼재된 상황을 감지하고 처리하기 위한 전용 예외 클래스가 적절하게 구현되었습니다. 데이터 무결성 보장에 도움이 될 것입니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java (2)
5-9: 매장 결제 삭제 권한 예외 처리가 적절합니다.결제 정보 삭제에 대한 권한 부족 시나리오를 처리하는 전용 예외 클래스입니다. 보안 측면에서 중요한 예외 처리가 체계적으로 구현되었습니다.
1-1: 원 코멘트가 부정확합니다 — Java 패키지명은 소문자여야 합니다.프로젝트의 package 선언은 com.nowait.domaincorerdb.storepayment.*(소문자)를 사용하고 있으나 실제 디렉터리 경로 nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment(대문자 P)가 존재해 패키지 선언과 불일치합니다. 조치: 디렉터리명을 소문자 storepayment로 변경해 package 선언과 일치시켜야 합니다.
Likely an incorrect or invalid review comment.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java (1)
5-9: 예약 상태 전이 검증을 위한 예외 처리가 잘 구현되었습니다.이미 확정된 예약에 대한 중복 처리를 방지하는 예외 클래스로, 예약 상태 관리의 무결성을 보장하는 데 도움이 됩니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java (2)
6-9: 매개변수를 활용한 상태별 예외 메시지가 유용합니다.지원되지 않는 예약 상태에 대해 구체적인 상태값을 포함한 에러 메시지를 생성하는 구조가 디버깅과 로깅에 도움이 될 것입니다.
3-3: ReservationStatus 정의 위치 확인 — 모듈 경계 검토 필요nowait-common/src/main/java/com/nowait/common/enums/ReservationStatus.java에 정의되어 있습니다. 이 enum이 도메인 전용이라면 domain 모듈로 이동(의존성 영향 검토)하고, 공용으로 쓰이는 경우 현재 위치 유지하세요.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java (1)
81-90: 전이 테이블 명확하고 읽기 쉬움허용 전이 정의가 명확합니다. 도메인 검증 의도가 잘 드러납니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java (1)
99-116: 동일 prefix 하 PATCH 매핑 충돌 여부 점검 요청 — 스크립트 재실행 필요원본 스크립트 실행 결과 "No files were searched"로 실패했습니다. 리포지토리 전역에서 /reservations/admin 하위의 @PatchMapping 충돌을 확인하려면 아래 개선된 스크립트를 로컬에서 실행한 출력 결과를 코멘트로 붙여넣을 것.
#!/bin/bash set -euo pipefail echo "=== Search: classes annotated with @RequestMapping(\"/reservations\") and nearby @PatchMapping ===" # 우선 git-tracked 소스 파일에서 검색 (ignore 문제 회피) git ls-files '*.java' '*.kt' '*.groovy' '*.scala' '*.ts' 2>/dev/null | \ xargs -r rg -nP -C2 '@RequestMapping\(\s*"/reservations"\s*\)' || true echo echo "=== Per-file: show @PatchMapping lines in files that declare the above mapping ===" git ls-files '*.java' '*.kt' '*.groovy' '*.scala' '*.ts' 2>/dev/null | \ while read -r f; do if rg -nP '@RequestMapping\(\s*"/reservations"\s*\)' -q "$f"; then echo "== $f ==" rg -nP '@PatchMapping\(\s*"(.*?)"\s*\)' -C2 "$f" || true fi done echo echo "=== Global search (no ignore): any @PatchMapping with /reservations/admin prefix ===" rg --hidden --no-ignore --no-ignore-vcs -nP '@PatchMapping\(\s*"(?:/reservations/admin/[^"]*)"\s*\)' -C2 --glob '!**/build/**' --glob '!**/out/**' --glob '!**/node_modules/**' || truenowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java (1)
20-26: 도메인 예외 전환 좋습니다일반적 예외를 도메인 예외로 치환해 명확성이 좋아졌습니다. 파라미터/존재성 검증도 일관되어 응답 매핑에 유리합니다.
Also applies to: 32-33, 56-57, 84-85, 122-137
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java (1)
89-115: 권한 검사 헬퍼 추출 잘 하셨습니다검증 로직이 응집되어 가독성과 재사용성이 좋아졌습니다. 동일 패턴 확산에 동의합니다.
해당 validateDeleteAuthorization가 실제 삭제 API 경로에서도 호출되는지 확인 부탁드립니다(현재 파일에서는 미사용).
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java (1)
132-134: NotFound 예외 전환 LGTMtoggleActive에서 IllegalArgumentException 대신 StoreNotFoundException 사용은 일관된 오류 모델에 부합합니다.
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java (1)
68-73: 예약번호→사용자 역매핑 조회 추가 좋습니다역방향 조회 유틸이 명확해졌습니다. 이후 write 경로에서도 동일 키를 반드시 함께 갱신해 주세요.
역매핑 생성/갱신이 어디서 수행되는지(생성/호출/상태변경 시점) 확인 부탁드립니다. 일관성 위해 단일 트랜잭션/스크립트 기반 갱신을 권장합니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java (1)
47-53: 스토어 존재성 체크 위치 OKstore 존재 검증 후 권한 검사 순서가 적절합니다. 조회 기준일 계산도 KST 기준으로 명확합니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java (1)
192-197: 좋은 리팩토링: 권한 검증 로직 중앙화권한 검증 로직을 별도의 private 메서드로 분리하여 코드 중복을 제거하고 유지보수성을 향상시켰습니다. MANAGER 역할 체크가 추가되어 권한 체계가 확장되었네요.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java (1)
528-544: 헬퍼 메서드들의 일관된 구현사용자 인증과 권한 검증을 위한 헬퍼 메서드들이 잘 구현되었습니다. 이는 MenuService와 일관된 패턴을 유지합니다.
nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java (2)
111-113: 유용한 format 메서드 추가파라미터화된 에러 메시지를 지원하는
format()메서드가 추가되어 동적 에러 메시지 생성이 가능해졌습니다. 특히 상태 전이 오류 메시지에 유용하게 사용될 것 같습니다.
26-26: 예약 관련 에러 메시지 확장예약 상태 전이 및 파라미터 검증을 위한 새로운 에러 메시지들이 추가되었습니다. 특히
INVALID_RESERVATION_STATUS_TRANSITION과INVALID_RESERVATION_PARAMETER는 format 메서드를 활용하여 동적 메시지를 생성할 수 있어 유용합니다.Also applies to: 36-40
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java (2)
569-577: 범용 예외 핸들러 추가예상치 못한 예외를 처리하는 범용 핸들러가 추가되었습니다. 이는 예외 처리 안정성을 향상시킵니다.
166-577: 체계적인 예외 처리 구조도메인별로 예외 처리를 그룹화하고 주석으로 구분한 것이 좋습니다. 예외 처리가 매우 체계적으로 구성되어 있네요.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java
Show resolved
Hide resolved
...in-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java
Outdated
Show resolved
Hide resolved
...in-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java
Show resolved
Hide resolved
...-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java
Show resolved
Hide resolved
.../src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java
Show resolved
Hide resolved
...it-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 5
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (3)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java (1)
83-101: 경로 파라미터와 리소스 소유 일치성 검증 누락
storeId경로값과 조회된menu.getStoreId()의 일치 여부를 확인하지 않아 교차 매장 접근 가능성이 있습니다.User user = getUser(memberDetails); Menu menu = getMenu(menuId); + if (!java.util.Objects.equals(storeId, menu.getStoreId())) { + throw new MenuCrossStoreConflictException(); + } validateViewAuthorization(user, menu.getStoreId());nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java (2)
287-292: 빌더 패턴에서 status 필드가 누락되었습니다새 Reservation 생성 시 초기 상태를 설정하지 않아서
markUpdated호출 시 NullPointerException이 발생할 수 있습니다.Reservation r = Reservation.builder() .reservationNumber(reservationNumber) .store(storeRepository.getReferenceById(storeId)) .user(userRepository.getReferenceById(Long.valueOf(userId))) .partySize(partySize) .requestedAt(requestedAt) .updatedAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))) + .status(ReservationStatus.valueOf(currStatus)) .build();
339-348: CANCELLED 예약 생성 시에도 status 필드가 누락되었습니다Line 291과 동일한 문제입니다. 초기 상태 설정이 필요합니다.
Reservation r = Reservation.builder() .reservationNumber(reservationNumber) .store(storeRepository.getReferenceById(storeId)) .user(userRepository.getReferenceById(Long.valueOf(userId))) .partySize(partySize) .requestedAt(requestedAt) .updatedAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))) + .status(ReservationStatus.valueOf(currStatus)) .build();
🧹 Nitpick comments (29)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java (1)
8-8: 미사용 import 정리 필요
DeleteMappingimport가 추가되었지만 실제로 사용되지 않고 있습니다. 향후 삭제 기능 구현을 위한 준비 작업으로 보이나 현재는 불필요한 import입니다.불필요한 import를 제거하거나, 삭제 엔드포인트 구현이 계획되어 있다면 해당 기능을 추가하세요.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java (2)
5-9: serialVersionUID와 cause 생성자 추가 제안직렬화 경고 억제 및 예외 체이닝 유지에 도움됩니다.
public class MenuCrossStoreConflictException extends RuntimeException { + private static final long serialVersionUID = 1L; public MenuCrossStoreConflictException() { super(ErrorMessage.MENU_CROSS_STORE_CONFLICT.getMessage()); } + public MenuCrossStoreConflictException(Throwable cause) { + super(ErrorMessage.MENU_CROSS_STORE_CONFLICT.getMessage(), cause); + } }
5-9: 반복되는 패턴은 공통 베이스 예외로 흡수 권장여러 도메인 예외에서 동일한 보일러플레이트가 반복됩니다. ErrorMessage를 보존하는 NowaitDomainException(추상) 등을 도입하면 중복 제거와 핸들러 일관성이 좋아집니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java (1)
5-9: serialVersionUID와 cause 생성자 추가 제안동일한 사유로 보완을 권장합니다.
public class MenuToggleUnauthorizedException extends RuntimeException { + private static final long serialVersionUID = 1L; public MenuToggleUnauthorizedException() { super(ErrorMessage.MENU_TOGGLE_UNAUTHORIZED.getMessage()); } + public MenuToggleUnauthorizedException(Throwable cause) { + super(ErrorMessage.MENU_TOGGLE_UNAUTHORIZED.getMessage(), cause); + } }nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/ReservationDataInconsistencyException.java (1)
5-9: 세부 메시지 유지 + 체이닝을 위한 생성자 보강운영 트러블슈팅 시 원인 예외를 함께 전달할 수 있게 합니다.
public class ReservationDataInconsistencyException extends RuntimeException { + private static final long serialVersionUID = 1L; public ReservationDataInconsistencyException(String details) { super(ErrorMessage.RESERVATION_DATA_INCONSISTENCY.format(details)); } + public ReservationDataInconsistencyException(String details, Throwable cause) { + super(ErrorMessage.RESERVATION_DATA_INCONSISTENCY.format(details), cause); + } }nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java (1)
5-9: serialVersionUID와 cause 생성자 추가 제안일관된 예외 체이닝/직렬화 지원을 위해 권장합니다.
public class MenuAlreadyDeletedException extends RuntimeException { + private static final long serialVersionUID = 1L; public MenuAlreadyDeletedException() { super(ErrorMessage.MENU_ALREADY_DELETED.getMessage()); } + public MenuAlreadyDeletedException(Throwable cause) { + super(ErrorMessage.MENU_ALREADY_DELETED.getMessage(), cause); + } }nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java (1)
5-9: 세부 파라미터 예외의 체이닝 생성자 및 serialVersionUID 추가파라미터 파싱/검증 단계의 원인 예외 보존에 유용합니다.
public class InvalidReservationParameterException extends RuntimeException { + private static final long serialVersionUID = 1L; public InvalidReservationParameterException(String details) { super(ErrorMessage.INVALID_RESERVATION_PARAMETER.format(details)); } + public InvalidReservationParameterException(String details, Throwable cause) { + super(ErrorMessage.INVALID_RESERVATION_PARAMETER.format(details), cause); + } }nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java (1)
5-9: serialVersionUID와 cause 생성자 추가 제안상태 전이 검증 과정의 하위 예외를 유지할 수 있게 합니다.
public class ReservationAlreadyConfirmedException extends RuntimeException { + private static final long serialVersionUID = 1L; public ReservationAlreadyConfirmedException() { super(ErrorMessage.RESERVATION_ALREADY_CONFIRMED.getMessage()); } + public ReservationAlreadyConfirmedException(Throwable cause) { + super(ErrorMessage.RESERVATION_ALREADY_CONFIRMED.getMessage(), cause); + } }nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java (1)
5-9: serialVersionUID와 cause 생성자 추가 + 포매팅 자잘한 정리
extends RuntimeException{앞뒤 공백 및 표준 생성자 보강을 권장합니다.-public class StorePaymentDeleteUnauthorizedException extends RuntimeException{ +public class StorePaymentDeleteUnauthorizedException extends RuntimeException { + private static final long serialVersionUID = 1L; public StorePaymentDeleteUnauthorizedException() { super(ErrorMessage.STORE_PAYMENT_DELETE_UNAUTHORIZED.getMessage()); } + public StorePaymentDeleteUnauthorizedException(Throwable cause) { + super(ErrorMessage.STORE_PAYMENT_DELETE_UNAUTHORIZED.getMessage(), cause); + } }nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java (1)
5-9: serialVersionUID와 cause 생성자 추가 제안상태 머신 전이 검사에서 하위 원인 보존을 위해 권장합니다.
public class ReservationAlreadyCancelledException extends RuntimeException { + private static final long serialVersionUID = 1L; public ReservationAlreadyCancelledException() { super(ErrorMessage.RESERVATION_ALREADY_CANCELLED.getMessage()); } + public ReservationAlreadyCancelledException(Throwable cause) { + super(ErrorMessage.RESERVATION_ALREADY_CANCELLED.getMessage(), cause); + } }nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java (1)
80-86: 상태 전이 규칙에서 COOKED 상태의 전이가 제한적입니다현재
COOKED상태에서COOKING으로의 전이를 허용하고 있는데, 이는 일반적인 주문 플로우에서 비논리적입니다. 조리 완료된 주문이 다시 조리 중 상태로 돌아가는 것은 비즈니스 로직상 타당하지 않을 수 있습니다.private boolean isValidTransition(OrderStatus current, OrderStatus target) { return switch (current) { - case WAITING_FOR_PAYMENT, COOKED -> target == OrderStatus.COOKING || target == OrderStatus.CANCELLED; + case WAITING_FOR_PAYMENT -> target == OrderStatus.COOKING || target == OrderStatus.CANCELLED; case COOKING -> target == OrderStatus.COOKED || target == OrderStatus.CANCELLED; + case COOKED -> target == OrderStatus.CANCELLED; case CANCELLED -> false; }; }nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java (1)
99-115: 예약번호 기반 엔드포인트 추가가 유용합니다예약번호를 통한 상태 업데이트 엔드포인트 추가로 사용자 경험이 개선될 것으로 보입니다. 다만, 몇 가지 개선사항을 제안합니다.
- 경로 변수 검증:
reservationNumber에 대한 형식 검증이 필요할 수 있습니다.- OpenAPI 설명 업데이트: Line 100의 summary가 기존 엔드포인트(Line 82)와 동일합니다. 예약번호 사용을 명시하면 좋겠습니다.
- @Operation(summary = "예약팀 상태 업데이트 처리", description = "특정 예약에 대한 입장 완료 처리") + @Operation(summary = "예약번호로 예약팀 상태 업데이트 처리", description = "예약번호를 사용하여 특정 예약에 대한 입장 완료 처리")nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java (2)
41-47: memberDetails null/빈 ID 가드 추가 필요
getUser(memberDetails)호출 전/내부에 널 가드가 없어 NPE 가능성이 있습니다.getStorePaymentByStoreId와 동일하게 일관되게 검사하세요.다음 수정 예:
private User getUser(MemberDetails memberDetails) { - return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + if (memberDetails == null || memberDetails.getId() == null) { + throw new StorePaymentParamEmptyException(); + } + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); }또한 public 메서드 진입부에서도 가드를 권장합니다.
public StorePaymentCreateResponse createStorePayment(StorePaymentCreateRequest request, MemberDetails memberDetails) { if (request == null) throw new StorePaymentParamEmptyException(); + if (memberDetails == null) throw new StorePaymentParamEmptyException();public StorePaymentReadDto updateStorePayment(StorePaymentUpdateRequest request, MemberDetails memberDetails) { if (request == null) throw new StorePaymentParamEmptyException(); + if (memberDetails == null) throw new StorePaymentParamEmptyException();Also applies to: 72-75, 89-91
111-115: 미사용 유틸 메서드
validateDeleteAuthorization는 현재 호출되지 않습니다. 사용 계획이 없다면 제거하거나, 삭제 플로우에 연결하세요.nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java (3)
139-151: NPE 방지: equals 대신 Objects.equals 사용
storeId.equals(user.getStoreId())패턴은 어느 한쪽이 null이면 NPE 위험. 아래처럼 교체를 권장합니다.- || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())) + || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(storeId, user.getStoreId()))동일 패턴을 update 검증에도 적용하세요.
110-128: 삭제 권한 예외 타입 불일치삭제에서
validateUpdateAuthorization를 사용해StoreUpdateUnauthorizedException를 던집니다. 도메인 시맨틱에 맞게 삭제 전용 예외를 사용하세요.- validateUpdateAuthorization(user, storeId); + validateDeleteAuthorization(user, storeId);+ private void validateDeleteAuthorization(User user, Long storeId) { + if (!(Role.SUPER_ADMIN.equals(user.getRole()) + || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(storeId, user.getStoreId())))) { + throw new StoreDeleteUnauthorizedException(); + } + }
153-155: memberDetails null/빈 ID 가드 필요
getUser에서 널 가드가 없어 NPE 위험이 있습니다.private User getUser(MemberDetails memberDetails) { - return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + if (memberDetails == null || memberDetails.getId() == null) { + throw new StoreParamEmptyException(); + } + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); }nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java (4)
47-53: 입력 가드: storeId null 체크 누락
findAllOrders에storeIdnull 가드가 없습니다. 다른 서비스들과 일관되게 파라미터 예외를 던지세요.예:
if (storeId == null) throw new OrderParamEmptyException();(프로젝트 내 존재하는 파라미터 예외 타입으로 대체)
135-147: NPE 방지 및 일관화: Objects.equals 사용권한 검증에서
equals대신Objects.equals사용을 권장합니다.- || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())) + || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(storeId, user.getStoreId()))동일 패턴을 update 검증에도 적용하세요.
124-133: 읽기 전용 API의 권한 검증 레벨
getTop5StoresBySalesToday는 조회 성격인데 업데이트 권한 검증을 사용합니다. 조회 검증으로 낮추는 것이 적절합니다.- validateUpdateAuthorization(user, storeId); + validateViewAuthorization(user, storeId);
149-151: memberDetails null/빈 ID 가드 필요
getUser에 널 가드 추가를 권장합니다.private User getUser(MemberDetails memberDetails) { - return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + if (memberDetails == null || memberDetails.getId() == null) { + throw new OrderParamEmptyException(); // 프로젝트 컨벤션에 맞게 조정 + } + return userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); }nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java (6)
47-59: 생성 입력 가드 및 권한 예외 타입 정교화
requestnull 가드가 없습니다.- 생성 시
validateViewAuthorization대신 생성/수정 레벨 검증이 더 적절합니다. 필요 시MenuCreationUnauthorizedException활용 권장.@Transactional public MenuCreateResponse createMenu(MenuCreateRequest request, MemberDetails memberDetails) { + if (request == null) throw new MenuParamEmptyException(); User user = getUser(memberDetails); - validateViewAuthorization(user, request.getStoreId()); + validateUpdateAuthorization(user, request.getStoreId());
62-81: storeId null 가드 누락다른 서비스와 동일하게
storeIdnull이면 파라미터 예외를 던지세요.public MenuReadResponse getAllMenusByStoreId(Long storeId, MemberDetails memberDetails) { + if (storeId == null) throw new MenuParamEmptyException(); User user = getUser(memberDetails);
103-125: 업데이트 입력 가드 누락
request에 대한 null 체크가 없습니다.public MenuReadDto updateMenu(Long menuId, MenuUpdateRequest request, MemberDetails memberDetails) { + if (request == null) throw new MenuParamEmptyException(); User user = getUser(memberDetails);
127-134: 패턴 일관화: getUser 헬퍼 사용여기만 직접
userRepository를 호출합니다.getUser헬퍼로 통일하세요.- User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new); + User user = getUser(memberDetails);
191-211: NPE 방지: equals → Objects.equals권한 검증 전반에
Objects.equals사용 권장.- || (Role.MANAGER.equals(user.getRole()) && storeId.equals(user.getStoreId())) + || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(storeId, user.getStoreId()))동일 패턴을 update/delete 검증에도 적용하세요.
169-179: 응답 메시지 언어 일관성영문/국문 혼용 문자열입니다. 한 언어로 통일하세요.
- return "Menu with ID " + menuId + " 삭제되었습니다."; + return "메뉴 ID " + menuId + "이(가) 삭제되었습니다.";nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java (1)
296-303: 예외 처리 로직이 일관성이 없습니다동일한
removeActiveMember호출에 대해 한 곳에서는 예외를 무시하고 다른 곳에서는 그대로 전파합니다. 일관된 처리가 필요합니다.모든
removeActiveMember호출에 대해 일관된 예외 처리 정책을 적용하세요. Redis 데이터가 이미 삭제된 경우를 허용한다면 모든 호출에서 try-catch를 사용하거나, 아니면 모두 예외를 전파해야 합니다.nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java (1)
315-315: 오타가 있습니다로그 메시지에 언더스코어가 잘못 위치해 있습니다.
-log.error("reservation_viewUnauthorizedException", e); +log.error("reservationViewUnauthorizedException", e);
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (32)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java(11 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java(9 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java(4 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java(1 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java(11 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java(5 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/controller/StorePaymentController.java(1 hunks)nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java(5 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java(1 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java(6 hunks)nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java(2 hunks)nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java(5 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java(2 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java(2 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java(0 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java(1 hunks)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/ReservationDataInconsistencyException.java(1 hunks)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java(3 hunks)
💤 Files with no reviewable changes (1)
- nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/store/exception/StoreKeywordEmptyException.java
🧰 Additional context used
🧬 Code graph analysis (10)
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java (1)
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
RedisKeyUtils(9-98)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/order/service/OrderService.java (2)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderNotFoundException.java (1)
OrderNotFoundException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java (1)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java (2)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java (1)
InvalidOrderStatusTransitionException(6-10)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java (1)
OrderAlreadyCancelledException(5-9)
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java (1)
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
RedisKeyUtils(9-98)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java (2)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java (1)
StorePaymentDeleteUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java (7)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java (1)
MenuCrossStoreConflictException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDeleteUnauthorizedException.java (1)
MenuDeleteUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java (1)
MenuDuplicateIdException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java (1)
MenuInvalidSortOrderException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuUpdateUnauthorizedException.java (1)
MenuUpdateUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuViewUnauthorizedException.java (1)
MenuViewUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java (8)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationParameterException.java (1)
InvalidReservationParameterException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java (1)
InvalidReservationStatusTransitionException(6-10)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/exception/ReservationDataInconsistencyException.java (1)
ReservationDataInconsistencyException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationUpdateUnauthorizedException.java (1)
ReservationUpdateUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationViewUnauthorizedException.java (1)
ReservationViewUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java (1)
UnsupportedReservationStatusException(6-10)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
RedisKeyUtils(9-98)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java (3)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java (1)
InvalidReservationStatusTransitionException(6-10)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyCancelledException.java (1)
ReservationAlreadyCancelledException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationAlreadyConfirmedException.java (1)
ReservationAlreadyConfirmedException(5-9)
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java (9)
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuAlreadyDeletedException.java (1)
MenuAlreadyDeletedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuCrossStoreConflictException.java (1)
MenuCrossStoreConflictException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java (1)
MenuDuplicateIdException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java (1)
MenuInvalidSortOrderException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuToggleUnauthorizedException.java (1)
MenuToggleUnauthorizedException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java (1)
OrderAlreadyCancelledException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/ReservationNumberIssueFailException.java (1)
ReservationNumberIssueFailException(5-9)nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
UserNotFoundException(5-9)nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/rank/exception/MenuCounterUpdateException.java (1)
MenuCounterUpdateException(5-7)
🔇 Additional comments (32)
nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/service/OrderService.java (7)
20-20: 도메인별 예외로의 전환이 적절합니다
MenuNotFoundException과 같은 도메인 특화 예외를 사용하는 것이 일반적인 예외보다 더 명확하고 구체적인 오류 처리를 가능하게 합니다.
25-25: 도메인 특화 예외 임포트가 예외 처리 일관성을 향상시킵니다
DepositorNameTooLongException과StoreNotFoundException임포트가 추가되어 기존의 범용 예외 대신 의미있는 도메인별 예외 처리를 가능하게 합니다.Also applies to: 32-32
56-56: Store 조회 시 도메인 특화 예외 적용이 올바릅니다기존의 범용적인 예외 대신
StoreNotFoundException을 사용하여 더 명확한 에러 메시지와 처리가 가능해졌습니다.
84-84: 메뉴 조회 실패 시 도메인 특화 예외 사용이 적절합니다
MenuNotFoundException을 통해 메뉴를 찾을 수 없는 상황을 명확히 표현하고 있습니다.
96-96: 응답 생성 로직의 가독성 개선메서드 호출이 한 줄로 정리되어 가독성이 향상되었습니다.
103-104: 매개변수 정렬의 가독성 개선긴 매개변수 목록을 여러 줄로 나누어 가독성이 향상되었습니다.
122-138: 유효성 검사 로직의 도메인 특화 예외 전환이 우수합니다각 검증 시나리오에 맞는 구체적인 예외들을 사용하고 있습니다:
OrderParameterEmptyException: 필수 매개변수 누락OrderItemsEmptyException: 주문 항목 누락DepositorNameTooLongException: 예금자명 길이 초과이러한 변경으로 클라이언트가 각 오류 상황을 더 정확히 파악할 수 있습니다.
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/common/util/RedisKeyUtils.java (1)
79-81: LGTM!Redis에서 양방향 매핑을 구현하는 표준 방식인 두 개의 해시맵 사용에 따라 새로운 키 빌더가 추가되었습니다. 예약번호↔사용자 ID 매핑을 위해 필요한 구조적 확장입니다.
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java (1)
40-41: 예약 데이터 일관성 확보를 위한 양방향 매핑 구현기존 사용자 ID → 예약번호 매핑에 추가하여 예약번호 → 사용자 ID 역방향 매핑을 구현했습니다. 이는 예약번호 기반 조회 기능을 지원하기 위한 필수 변경입니다.
nowait-domain/domain-redis/src/main/java/com/nowait/domaincoreredis/reservation/repository/WaitingRedisRepository.java (4)
55-55: 주석 개선으로 코드 가독성 향상주석이 구체적으로 변경되어 매핑 방향이 명확해졌습니다.
68-73: 예약번호 기반 사용자 조회 기능 구현새로운 역방향 조회 메서드가 추가되어 예약번호를 통한 사용자 ID 조회가 가능해졌습니다. 로직이 간단하고 명확합니다.
75-87: 양방향 매핑 정리로 데이터 일관성 보장삭제 시 양방향 매핑을 모두 정리하여 데이터 일관성을 유지합니다. HDEL 명령어가 여러 인수를 처리하므로 성능상 문제가 없습니다.
98-99: 변수명 사용으로 코드 가독성 개선하드코딩된 키 생성 대신 변수를 사용하여 코드가 더 명확해졌습니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/exception/UserNotFoundException.java (1)
7-7: 에러 메시지 상수명 일관성 확보
NOTFOUND_USER에서NOT_FOUND_USER로 변경하여 네이밍 일관성이 향상되었습니다.nowait-app-user-api/src/main/java/com/nowait/applicationuser/exception/GlobalExceptionHandler.java (1)
175-175: 에러 코드 상수명 통일
NOTFOUND_USER에서NOT_FOUND_USER로 변경하여 도메인 예외와 일치하게 되었습니다.nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuDuplicateIdException.java (1)
5-8: 표준 패턴을 따른 도메인 예외 구현메뉴 도메인의 중복 ID 처리를 위한 예외 클래스가 잘 구현되었습니다. 기존 패턴과 일치하며 중앙화된 에러 메시지를 사용합니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/OrderAlreadyCancelledException.java (1)
5-8: 주문 상태 전이 예외 처리 구현이미 취소된 주문에 대한 중복 취소 시도를 방지하는 예외 클래스입니다. 상태 머신 패턴에 따른 적절한 구현입니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/UnsupportedReservationStatusException.java (1)
1-11: 파일 구조는 적절합니다예외 클래스가 간단명료하게 구현되었고, 중앙화된 에러 메시지 시스템을 잘 활용하고 있습니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/menu/exception/MenuInvalidSortOrderException.java (1)
1-10: 예외 처리 구현이 적절합니다중앙화된 ErrorMessage를 활용한 일관된 예외 처리 방식이 좋습니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/entity/UserOrder.java (1)
66-71: 상태 전이 검증 로직 추가가 훌륭합니다명시적인 상태 전이 검증을 통해 도메인 무결성이 강화되었습니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/controller/ReservationController.java (1)
81-97: userId 기반 엔드포인트와 예약번호 기반 엔드포인트 공존현재 userId 기반 엔드포인트(Line 81-97)와 예약번호 기반 엔드포인트(Line 99-115)가 공존합니다. 두 엔드포인트의 용도와 사용 시나리오를 명확히 구분하는 것이 중요합니다.
두 엔드포인트의 사용 시나리오와 향후 계획을 확인해주세요:
- userId 기반 엔드포인트는 언제 사용되나요?
- 예약번호 기반 엔드포인트는 언제 사용되나요?
- 향후 하나로 통합할 계획이 있나요?
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/exception/InvalidReservationStatusTransitionException.java (1)
1-11: 예외 클래스 구현이 깔끔합니다포맷팅 기능을 활용한 동적 에러 메시지 생성이 잘 구현되었습니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/order/exception/InvalidOrderStatusTransitionException.java (1)
6-10: 도메인 예외 정의 적절메시지 포맷과 시그니처 모두 일관적입니다. 전역 핸들러 매핑만 확인해 주세요.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/menu/service/MenuService.java (3)
139-156: 정합성 검사 적절(LGTM)중복 ID/정렬값/교차 매장 검증 로직이 명확합니다.
213-220: 엔티티 조회 헬퍼 적절(LGTM)삭제 플래그 고려한 단일 책임 메서드로 적절합니다.
181-189: 검증 결과: 인증 적용됨 — toggleSoldOut는 인증 필요SecurityConfig(filterChain)에서 .anyRequest().authenticated()로 설정되어 있고 '/admin/menus/**'가 permitAll 목록에 없어 /admin/menus/toggle-soldout/{menuId}는 인증 없이 호출되지 않습니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/store/service/StoreServiceImpl.java (1)
130-137: 인증/인가 적용 검토: toggleActive 엔드포인트 보안 확인
StoreController.toggleActive(@PatchMapping("/toggle-active/{storeId}"))는MemberDetails나@AuthenticationPrincipal없이 외부에 노출됩니다. 인증/인가 적용 여부 확인 후, 필요 시@PreAuthorize등 보안 어노테이션 또는 인증 파라미터를 추가하세요.nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/reservation/entity/Reservation.java (2)
65-79: 상태 전이 로직이 잘 구현되었습니다!state-machine 패턴을 활용한 예약 상태 관리가 명확하고 체계적입니다. 중복 상태 체크와 유효하지 않은 전이에 대한 예외 처리가 적절합니다.
81-91: 상태 전이 규칙이 명확하게 정의되었습니다비즈니스 로직에 맞는 상태 전이 규칙이 switch 표현식으로 깔끔하게 구현되었습니다. CANCELLED 상태에서는 어떤 전이도 허용하지 않는 점이 적절합니다.
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java (1)
542-544: 권한 검증 헬퍼 메서드들이 잘 구현되었습니다중복된 권한 검증 로직을 private 메서드로 추출하여 재사용성과 유지보수성이 향상되었습니다.
nowait-common/src/main/java/com/nowait/common/exception/ErrorMessage.java (1)
111-113: 동적 메시지 포맷팅을 위한 유용한 메서드입니다
format메서드 추가로 파라미터화된 에러 메시지를 깔끔하게 처리할 수 있게 되었습니다.nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/exception/GlobalExceptionHandler.java (1)
568-577: 전역 예외 처리기가 적절히 추가되었습니다예상하지 못한 예외를 처리하는 fallback 핸들러가 추가되어 시스템의 안정성이 향상되었습니다.
...in-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java
Show resolved
Hide resolved
| @Transactional | ||
| public EntryStatusResponseDto processEntryStatusByReservationNumber(Long storeId, String reservationNumber, | ||
| MemberDetails member, ReservationStatus newStatus) { | ||
|
|
||
| User user = getUser(member); | ||
| validateUpdateAuthorization(user, storeId); | ||
|
|
||
| if (reservationNumber == null || reservationNumber.isBlank()) { | ||
| throw new InvalidReservationParameterException("reservationNumber 값이 비어있습니다."); | ||
| } | ||
|
|
||
| // Redis에서 userId 역추적 | ||
| String userId = waitingRedisRepository.getUserIdByReservationNumber(storeId, reservationNumber); | ||
| if (userId == null) { | ||
| throw new ReservationDataInconsistencyException( | ||
| String.format("storeId=%d, reservationNumber=%s 에 해당하는 userId를 찾을 수 없습니다.", storeId, reservationNumber) | ||
| ); | ||
| } | ||
|
|
||
| String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId; | ||
|
|
||
| String currStatus = waitingRedisRepository.getWaitingStatus(storeId, userId); | ||
| Double score = redisTemplate.opsForZSet().score(queueKey, userId); | ||
| Integer partySize = waitingRedisRepository.getWaitingPartySize(storeId, userId); | ||
|
|
||
|
|
||
| if (partySize == null || partySize <= 0) { | ||
| throw new InvalidReservationParameterException("partySize가 유효하지 않습니다. (storeId=" + storeId + ", reservationNumber=" + reservationNumber + ")"); | ||
| } | ||
|
|
||
| if (currStatus == null) { | ||
| throw new ReservationDataInconsistencyException( | ||
| String.format("storeId=%d, reservationNumber=%s 의 상태값을 찾을 수 없습니다.", storeId, reservationNumber) | ||
| ); | ||
| } | ||
|
|
||
| LocalDateTime requestedAt = score != null | ||
| ? Instant.ofEpochMilli(score.longValue()).atZone(ZoneId.of("Asia/Seoul")).toLocalDateTime() | ||
| : LocalDateTime.now(); | ||
| LocalDateTime now = LocalDateTime.now(ZoneId.of("Asia/Seoul")); | ||
|
|
||
| switch (newStatus) { | ||
| case CALLING: | ||
| if (!ReservationStatus.WAITING.name().equals(currStatus)) { | ||
| throw new InvalidReservationStatusTransitionException( | ||
| ReservationStatus.valueOf(currStatus), ReservationStatus.CALLING | ||
| ); | ||
| } | ||
| waitingRedisRepository.setWaitingStatus(storeId, userId, ReservationStatus.CALLING.name()); | ||
| waitingRedisRepository.setWaitingCalledAt(storeId, userId, | ||
| now.atZone(ZoneId.of("Asia/Seoul")).toInstant().toEpochMilli()); | ||
|
|
||
| return EntryStatusResponseDto.builder() | ||
| .reservationNumber(reservationNumber) | ||
| .userId(userId) | ||
| .partySize(partySize) | ||
| .userName(userRepository.getReferenceById(Long.valueOf(userId)).getNickname()) | ||
| .createdAt(requestedAt) | ||
| .status("CALLING") | ||
| .updatedAt(now) | ||
| .message("호출되었습니다.") | ||
| .build(); | ||
|
|
||
| case CONFIRMED: | ||
| // 1) 기존 대기 중이거나 호출 중일 때: Redis → DB 최초 저장 | ||
| if (ReservationStatus.WAITING.name().equals(currStatus) || ReservationStatus.CALLING.name() | ||
| .equals(currStatus)) { | ||
|
|
||
| if (reservationNumber != null) { | ||
| waitingPermitLuaRepository.removeActiveMember( | ||
| userId, String.valueOf(storeId), reservationNumber | ||
| ); | ||
| } | ||
|
|
||
| // 새 Reservation 생성 & 저장 | ||
| Reservation r = Reservation.builder() | ||
| .reservationNumber(reservationNumber) | ||
| .store(storeRepository.getReferenceById(storeId)) | ||
| .user(userRepository.getReferenceById(Long.valueOf(userId))) | ||
| .partySize(partySize) | ||
| .requestedAt(requestedAt) | ||
| .updatedAt(LocalDateTime.now(ZoneId.of("Asia/Seoul"))) | ||
| .status(ReservationStatus.valueOf(currStatus)) | ||
| .build(); | ||
|
|
||
| // 호출 시각 반영 | ||
| r.markUpdated(LocalDateTime.now(), ReservationStatus.CONFIRMED); | ||
| Reservation saved = reservationRepository.save(r); | ||
| // Redis 전부 삭제 | ||
| waitingRedisRepository.deleteWaiting(storeId, userId); | ||
| return EntryStatusResponseDto.fromEntity(saved); | ||
| } else { | ||
| if (reservationNumber != null) { | ||
| try { | ||
| waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), | ||
| reservationNumber); | ||
| } catch (Exception ignore) { | ||
| } | ||
| } | ||
|
|
||
| // 2) 이미 취소(CANCELLED)된 경우: DB 레코드 찾아 바로 CONFIRMED 로 전환 | ||
| LocalDateTime start = LocalDate.now().atStartOfDay(); | ||
| LocalDateTime end = LocalDate.now().atTime(LocalTime.MAX); | ||
| Reservation existing = reservationRepository | ||
| .findFirstByStore_StoreIdAndUserIdAndStatusInAndRequestedAtBetweenOrderByRequestedAtDesc( | ||
| storeId, | ||
| Long.valueOf(userId), | ||
| List.of(ReservationStatus.CANCELLED), | ||
| start, | ||
| end | ||
| ).orElseThrow(() -> new ReservationDataInconsistencyException( | ||
| String.format("취소된 예약이 DB에 존재하지 않습니다. (storeId=%d, userId=%s)", storeId, userId) | ||
| )); | ||
|
|
||
| existing.markUpdated(LocalDateTime.now(ZoneId.of("Asia/Seoul")), ReservationStatus.CONFIRMED); | ||
| Reservation saved = reservationRepository.save(existing); | ||
| return EntryStatusResponseDto.fromEntity(saved); | ||
| } | ||
|
|
||
| case CANCELLED: | ||
| if (!(ReservationStatus.WAITING.name().equals(currStatus) | ||
| || ReservationStatus.CALLING.name().equals(currStatus))) { | ||
| throw new InvalidReservationStatusTransitionException( | ||
| ReservationStatus.valueOf(currStatus), ReservationStatus.CANCELLED | ||
| ); | ||
| } | ||
|
|
||
| waitingPermitLuaRepository.removeActiveMember(userId, String.valueOf(storeId), reservationNumber); | ||
|
|
||
| Reservation r = Reservation.builder() | ||
| .reservationNumber(reservationNumber) | ||
| .store(storeRepository.getReferenceById(storeId)) | ||
| .user(userRepository.getReferenceById(Long.valueOf(userId))) | ||
| .partySize(partySize) | ||
| .requestedAt(requestedAt) | ||
| .updatedAt(now) | ||
| .status(ReservationStatus.valueOf(currStatus)) | ||
| .build(); | ||
|
|
||
| r.markUpdated(now, ReservationStatus.CANCELLED); | ||
| Reservation saved = reservationRepository.save(r); | ||
| waitingRedisRepository.deleteWaiting(storeId, userId); | ||
|
|
||
| return EntryStatusResponseDto.fromEntity(saved); | ||
|
|
||
| default: | ||
| throw new UnsupportedReservationStatusException(newStatus); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
코드 중복이 심각합니다
processEntryStatusByReservationNumber 메서드가 processEntryStatus와 거의 동일한 로직을 반복합니다. 두 메서드의 차이점은 userId를 직접 받느냐, reservationNumber로 조회하느냐 뿐입니다.
공통 로직을 private 메서드로 추출하여 코드 중복을 제거하세요:
+private EntryStatusResponseDto processEntryStatusInternal(Long storeId, String userId,
+ String reservationNumber, ReservationStatus newStatus) {
+ // 현재 processEntryStatus의 핵심 로직
+ String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId;
+ // ... 나머지 로직
+}
+
public EntryStatusResponseDto processEntryStatus(Long storeId, String userId,
MemberDetails member, ReservationStatus newStatus) {
User user = getUser(member);
validateUpdateAuthorization(user, storeId);
if (userId == null || userId.isBlank()) {
throw new InvalidReservationParameterException("userId 값이 비어있습니다.");
}
+ String reservationNumber = waitingRedisRepository.getReservationId(storeId, userId);
+ return processEntryStatusInternal(storeId, userId, reservationNumber, newStatus);
- // 기존 로직 제거
}
public EntryStatusResponseDto processEntryStatusByReservationNumber(Long storeId,
String reservationNumber, MemberDetails member, ReservationStatus newStatus) {
User user = getUser(member);
validateUpdateAuthorization(user, storeId);
if (reservationNumber == null || reservationNumber.isBlank()) {
throw new InvalidReservationParameterException("reservationNumber 값이 비어있습니다.");
}
String userId = waitingRedisRepository.getUserIdByReservationNumber(storeId, reservationNumber);
if (userId == null) {
throw new ReservationDataInconsistencyException(
String.format("storeId=%d, reservationNumber=%s 에 해당하는 userId를 찾을 수 없습니다.",
storeId, reservationNumber)
);
}
+ return processEntryStatusInternal(storeId, userId, reservationNumber, newStatus);
- // 기존 로직 제거
}Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java
lines 360-508: the method processEntryStatusByReservationNumber duplicates
almost all logic from processEntryStatus; extract the shared workflow into a
single private helper that takes storeId, userId (nullable), reservationNumber
(nullable) and the desired newStatus, and returns EntryStatusResponseDto. In the
helper perform validation, Redis lookups, partySize/status checks, timestamp
derivation and the switch handling for CALLING/CONFIRMED/CANCELLED, and only
keep the differences (resolving userId from reservationNumber or using the
provided userId, and any small pre/post steps like removeActiveMember) in the
two public methods which should now call the helper; preserve existing
authorization checks in the public entry points and propagate errors unchanged.
| private void validateViewAuthorization(User user, Long storeId) { | ||
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { | ||
| throw new StorePaymentViewUnauthorizedException(); | ||
| } | ||
| } | ||
|
|
||
| private void validateCreationAuthorization(User user, Long storeId) { | ||
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { | ||
| throw new StorePaymentCreationUnauthorizedException(); | ||
| } | ||
| } | ||
|
|
||
| private void validateUpdateAuthorization(User user, Long storeId) { | ||
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { | ||
| throw new StorePaymentUpdateUnauthorizedException(); | ||
| } | ||
| } | ||
|
|
||
| private void validateDeleteAuthorization(User user, Long storeId) { | ||
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { | ||
| throw new StorePaymentDeleteUnauthorizedException(); | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
권한 체크 로직에 역할 제약 누락(권한 상승 위험)
현재 (Role.SUPER_ADMIN) || user.getStoreId().equals(storeId) 조건이라 MANAGER 제약이 없고, storeId만 같으면 어떤 역할이든 통과합니다. 다른 서비스(Store/Order/Menu)와 불일치하며 보안상 취약합니다. 또한 user.getStoreId()가 null이면 NPE 위험이 있습니다.
다음과 같이 수정 제안합니다(Objects.equals 사용):
- if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) {
+ if (!(Role.SUPER_ADMIN.equals(user.getRole())
+ || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(user.getStoreId(), storeId)))) {
throw new StorePaymentViewUnauthorizedException();
}같은 패턴을 Creation/Update/Delete 검증에도 동일 적용:
- if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) {
+ if (!(Role.SUPER_ADMIN.equals(user.getRole())
+ || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(user.getStoreId(), storeId)))) {
throw new StorePaymentCreationUnauthorizedException();
}- if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) {
+ if (!(Role.SUPER_ADMIN.equals(user.getRole())
+ || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(user.getStoreId(), storeId)))) {
throw new StorePaymentUpdateUnauthorizedException();
}- if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) {
+ if (!(Role.SUPER_ADMIN.equals(user.getRole())
+ || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(user.getStoreId(), storeId)))) {
throw new StorePaymentDeleteUnauthorizedException();
}📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| private void validateViewAuthorization(User user, Long storeId) { | |
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { | |
| throw new StorePaymentViewUnauthorizedException(); | |
| } | |
| } | |
| private void validateCreationAuthorization(User user, Long storeId) { | |
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { | |
| throw new StorePaymentCreationUnauthorizedException(); | |
| } | |
| } | |
| private void validateUpdateAuthorization(User user, Long storeId) { | |
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { | |
| throw new StorePaymentUpdateUnauthorizedException(); | |
| } | |
| } | |
| private void validateDeleteAuthorization(User user, Long storeId) { | |
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) || user.getStoreId().equals(storeId))) { | |
| throw new StorePaymentDeleteUnauthorizedException(); | |
| } | |
| } | |
| private void validateViewAuthorization(User user, Long storeId) { | |
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) | |
| || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(user.getStoreId(), storeId)))) { | |
| throw new StorePaymentViewUnauthorizedException(); | |
| } | |
| } | |
| private void validateCreationAuthorization(User user, Long storeId) { | |
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) | |
| || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(user.getStoreId(), storeId)))) { | |
| throw new StorePaymentCreationUnauthorizedException(); | |
| } | |
| } | |
| private void validateUpdateAuthorization(User user, Long storeId) { | |
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) | |
| || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(user.getStoreId(), storeId)))) { | |
| throw new StorePaymentUpdateUnauthorizedException(); | |
| } | |
| } | |
| private void validateDeleteAuthorization(User user, Long storeId) { | |
| if (!(Role.SUPER_ADMIN.equals(user.getRole()) | |
| || (Role.MANAGER.equals(user.getRole()) && java.util.Objects.equals(user.getStoreId(), storeId)))) { | |
| throw new StorePaymentDeleteUnauthorizedException(); | |
| } | |
| } |
🤖 Prompt for AI Agents
In
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/storePayment/service/StorePaymentServiceImpl.java
around lines 93 to 115, the authorization checks allow any role with a matching
storeId and risk a NullPointerException; update each validate*Authorization
method to require either Role.SUPER_ADMIN or Role.MANAGER in combination with a
matching storeId, and use Objects.equals(user.getStoreId(), storeId) to safely
compare IDs (apply the same change to validateViewAuthorization,
validateCreationAuthorization, validateUpdateAuthorization, and
validateDeleteAuthorization).
| // 5) Hash에 저장 | ||
| redisTemplate.opsForHash().put(numberMapKey, userId, reservationId); | ||
|
|
||
| redisTemplate.opsForHash().put(userMapKey, reservationId, userId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TTL 설정 누락으로 인한 메모리 누수 위험
userMapKey에 대한 TTL 설정이 누락되어 있습니다. 다른 매핑 키들과 동일한 만료 시간을 적용해야 메모리 누수를 방지할 수 있습니다.
다음과 같이 수정하세요:
redisTemplate.expire(seqKey, ttl);
redisTemplate.expire(numberMapKey, ttl);
+ redisTemplate.expire(userMapKey, ttl);📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| redisTemplate.opsForHash().put(userMapKey, reservationId, userId); | |
| redisTemplate.opsForHash().put(userMapKey, reservationId, userId); | |
| redisTemplate.expire(userMapKey, ttl); |
🤖 Prompt for AI Agents
nowait-app-user-api/src/main/java/com/nowait/applicationuser/reservation/repository/WaitingUserRedisRepository.java
around line 51: 현재 redis에 userMapKey로 해시를 저장만 하고 TTL을 설정하지 않아 메모리 누수가 발생할 수
있습니다; userMapKey에 다른 매핑 키들과 동일한 만료 시간을 적용하도록 수정하세요 — 즉, userMapKey에 값을 넣은 직후(또는
트랜잭션 내) 동일한 TTL 값(프로젝트에 정의된 reservation TTL 상수 또는 기존 키에서 사용하는 TTL)을 사용해
redisTemplate.expire(userMapKey, ttl, TimeUnit.SECONDS) 또는 동등한 API로 만료 시간을 설정하도록
추가하세요.
| @@ -0,0 +1,9 @@ | |||
| package com.nowait.domaincorerdb.storepayment.exception; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
패키지-디렉터리 대소문자 불일치 가능성(Linux/CI에서 빌드 실패 위험)
파일 경로는 .../storePayment/exception/... 인데, 패키지는 ...storepayment.exception(소문자)입니다. 케이스 센시티브 환경에서 소스 경로와 패키지 디렉터리가 불일치하면 컴파일/IDE 인덱싱 문제가 발생합니다. 디렉터리명을 storepayment로 정규화하거나, 패키지를 경로와 일치시키세요(권장: 모두 소문자 디렉터리).
다음 스크립트로 불일치 파일을 점검해 주세요.
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# 1) 대문자 포함 디렉터리 탐지
fd -t d -g 'nowait-domain/**/src/main/java/**' | rg -n '[A-Z]' || true
# 2) 해당 클래스 실제 경로와 선언된 패키지 확인
echo '--- Declared package ---'
rg -n '^package\s+com\.nowait\.domaincorerdb\.storepayment\.exception;' nowait-domain -C0 || true
echo '--- Files under storePayment dir ---'
fd -g 'nowait-domain/**/src/main/java/**/storePayment/**/*.java'Length of output: 1410
긴급: 패키지명 ↔ 디렉터리 대소문자 불일치 — 즉시 수정 필요
다음 파일들은 package 선언이 com.nowait.domaincorerdb.storepayment.exception(소문자)인데 실제 경로는 .../storePayment/exception/...(대문자 P)로 불일치하여 Linux/CI에서 컴파일 실패합니다.
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentAlreadyExistsException.java
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentParamEmptyException.java
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentUpdateUnauthorizedException.java
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentViewUnauthorizedException.java
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentCreationUnauthorizedException.java
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentNotFoundException.java
수정: 디렉터리명을 모두 소문자(storepayment)로 변경하거나 package 선언을 디렉터리와 일치하도록 수정(권장: 디렉터리 소문자화).
🤖 Prompt for AI Agents
In
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/storePayment/exception/StorePaymentDeleteUnauthorizedException.java
around line 1, the package declaration uses
com.nowait.domaincorerdb.storepayment.exception but the filesystem path contains
storePayment (capital P), causing case-sensitive compilation failures; fix this
by renaming the on-disk directory from storePayment to storepayment (preferred)
so it matches the package declarations across all listed exception files, or
alternatively update each file's package declaration to match the existing
directory name—ensure all seven files and their directories are consistent and
update any references/imports accordingly.
작업 요약
Issue Link
#319
문제점 및 어려움
해결 방안
Reference
Summary by CodeRabbit