Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ public class MenuImageService {
private final MenuRepository menuRepository;
private final MenuImageRepository menuImageRepository;
private final S3Service s3Service;

@Transactional
public MenuImageUploadResponse save(Long menuId, MultipartFile file) {
if (file == null || file.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,6 @@ public class UserService {
@Transactional
public ManagerSignupResponseDto signup(ManagerSignupRequestDto managerSignupRequestDto) {
validateEmailDuplicated(managerSignupRequestDto);
validateNickNameDuplicated(managerSignupRequestDto.getNickname());
User user = managerSignupRequestDto.toEntity();
user.encodePassword(passwordEncoder);

Expand All @@ -56,12 +55,6 @@ private void validateEmailDuplicated(ManagerSignupRequestDto managerSignupReques
}
);
}
private void validateNickNameDuplicated(String nickName) {
userRepository.findByNickname(nickName).ifPresent(member -> {
throw new IllegalArgumentException();
}
);
}
@Transactional
public ResponseEntity<ManagerLoginResponseDto> login(ManagerLoginRequestDto managerLoginRequestDto) {
Authentication authentication = authenticationProvider.authenticate(
Expand Down
5 changes: 5 additions & 0 deletions nowait-app-user-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ dependencies {
implementation project(':nowait-infra') // 사용자 관련 도메인
implementation project(':nowait-domain:domain-user-rdb')
implementation project(':nowait-domain:domain-core-rdb')
implementation project(':nowait-domain:domain-redis')

// Spring Boot Starter
implementation 'org.springframework.boot:spring-boot-starter-web'
Expand All @@ -48,6 +49,10 @@ dependencies {
// SWAGGER
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.6.0'

// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'


// 기타 필요 라이브러리 (예: Lombok)
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import com.nowait.applicationuser.order.dto.OrderCreateRequestDto;
import com.nowait.applicationuser.order.dto.OrderCreateResponseDto;
import com.nowait.applicationuser.order.dto.OrderItemGroupByStatusResponseDto;
import com.nowait.applicationuser.order.dto.OrderItemListGetResponseDto;
import com.nowait.applicationuser.order.service.OrderService;
import com.nowait.common.api.ApiUtils;
Expand Down Expand Up @@ -58,7 +59,7 @@ public ResponseEntity<?> getOrderItems(
HttpSession session
) {
String sessionId = session.getId();
List<OrderItemListGetResponseDto> orderItems = orderService.getOrderItems(storeId, tableId, sessionId);
List<OrderItemGroupByStatusResponseDto> orderItems = orderService.getOrderItemsGroupByStatus(storeId, tableId, sessionId);
return ResponseEntity.
status(HttpStatus.OK)
.body(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package com.nowait.applicationuser.order.dto;

import java.util.List;

import com.nowait.domaincorerdb.order.entity.OrderStatus;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class OrderItemGroupByStatusResponseDto {
private OrderStatus status;
private List<OrderItemListGetResponseDto> items;
}

Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ public static OrderItemListGetResponseDto fromEntity(OrderItem orderItem,OrderSt
.menuName(orderItem.getMenu().getName())
.quantity(orderItem.getQuantity())
.price(orderItem.getMenu().getPrice())
.status(status)
.build();

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import com.nowait.applicationuser.order.dto.CartItemDto;
import com.nowait.applicationuser.order.dto.OrderCreateRequestDto;
import com.nowait.applicationuser.order.dto.OrderCreateResponseDto;
import com.nowait.applicationuser.order.dto.OrderItemGroupByStatusResponseDto;
import com.nowait.applicationuser.order.dto.OrderItemListGetResponseDto;
import com.nowait.domaincorerdb.menu.entity.Menu;
import com.nowait.domaincorerdb.menu.repository.MenuRepository;
Expand Down Expand Up @@ -93,18 +94,26 @@ public OrderCreateResponseDto createOrder(Long storeId, Long tableId,
}

@Transactional(readOnly = true)
public List<OrderItemListGetResponseDto> getOrderItems(Long storeId, Long tableId, String sessionId) {
// 1. UserOrder 목록 조회 (storeId, tableId, sessionId 기준)
public List<OrderItemGroupByStatusResponseDto> getOrderItemsGroupByStatus(Long storeId, Long tableId, String sessionId) {
List<UserOrder> userOrders = orderRepository.findByStore_StoreIdAndTableIdAndSessionId(storeId, tableId, sessionId);

// 2. OrderItem으로 변환
return userOrders.stream()
// flatMap으로 OrderItem + status로 펼친 뒤, status별로 groupBy
Map<OrderStatus, List<OrderItemListGetResponseDto>> grouped = userOrders.stream()
.flatMap(order -> order.getOrderItems().stream()
.map(orderItem -> OrderItemListGetResponseDto.fromEntity(orderItem, order.getStatus())))
.collect(Collectors.groupingBy(OrderItemListGetResponseDto::getStatus));
Comment on lines +101 to +104
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

OrderItemListGetResponseDto.fromEntity 메서드의 status 필드 설정을 확인해야 합니다.

관련 코드 스니펫에 따르면 fromEntity 메서드가 status 파라미터를 받지만 실제로는 status 필드를 설정하지 않는 것으로 보입니다. 이후 104번 줄에서 getStatus()를 호출하면 null이 반환되어 그룹화 로직이 올바르게 작동하지 않을 수 있습니다.

다음 스크립트를 실행하여 fromEntity 메서드 구현을 확인해 주세요:


🏁 Script executed:

#!/bin/bash
# Description: OrderItemListGetResponseDto.fromEntity 메서드의 status 필드 설정 확인

# fromEntity 메서드 구현 확인
ast-grep --pattern 'public static OrderItemListGetResponseDto fromEntity($_, $_) {
  $$$
}'

Length of output: 93


다음 스크립트로 DTO 클래스 위치와 fromEntity 메서드 구현을 다시 확인해 주세요.


🏁 Script executed:

#!/bin/bash
# 1. OrderItemListGetResponseDto 클래스 파일 위치 확인
echo "=== DTO 클래스 위치 ==="
fd OrderItemListGetResponseDto.java

# 2. 클래스 선언부 확인
echo "=== 클래스 선언부 스니펫 ==="
rg -n "class OrderItemListGetResponseDto" -A 5

# 3. fromEntity 메서드 구현 검색
echo "=== fromEntity 구현 ==="
rg -n "fromEntity" -A 20

Length of output: 102188


OrderItemListGetResponseDto.fromEntity에 status가 설정되지 않습니다

fromEntity 메서드가 전달받은 status 파라미터를 빌더에 반영하지 않아, getStatus()가 null을 반환해 그룹화 로직이 올바르게 동작하지 않습니다.

수정이 필요한 위치:

  • nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java
    fromEntity 메서드의 빌더 체인에 .status(status)를 추가해야 합니다.

예시 수정(diff):

 public static OrderItemListGetResponseDto fromEntity(OrderItem orderItem, OrderStatus status) {
     return OrderItemListGetResponseDto.builder()
         .orderId(orderItem.getUserOrder().getId())
         .menuName(orderItem.getMenu().getName())
         .quantity(orderItem.getQuantity())
         .price(orderItem.getMenu().getPrice())
+        .status(status)
         .build();
 }
🤖 Prompt for AI Agents
In
nowait-app-user-api/src/main/java/com/nowait/applicationuser/order/dto/OrderItemListGetResponseDto.java
inside the fromEntity method, the passed status parameter is not set on the
builder, causing getStatus() to return null and breaking the grouping logic. Fix
this by adding .status(status) to the builder chain in fromEntity so the status
field is properly initialized.


// status별로 responseDto로 변환
return grouped.entrySet().stream()
.map(entry -> OrderItemGroupByStatusResponseDto.builder()
.status(entry.getKey())
.items(entry.getValue())
.build())
.toList();
}



private static void parameterValidation(Long storeId, Long tableId, OrderCreateRequestDto orderCreateRequestDto) {
if (storeId == null || tableId == null || orderCreateRequestDto == null) {
throw new OrderParameterEmptyException();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto;
import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto;
import com.nowait.applicationuser.reservation.dto.WaitingResponseDto;
import com.nowait.applicationuser.reservation.service.ReservationService;
import com.nowait.common.api.ApiUtils;
import com.nowait.domainuserrdb.oauth.dto.CustomOAuth2User;
Expand Down Expand Up @@ -44,4 +45,22 @@ public ResponseEntity<?> create(
)
);
}

@PostMapping("/create/redis/{storeId}")
@Operation(summary = "대기열 등록", description = "특정 주점에 대한 대기열 등록")
@ApiResponse(responseCode = "201", description = "대기열 등록")
public ResponseEntity<?> createQueue(
@PathVariable Long storeId,
@AuthenticationPrincipal CustomOAuth2User customOAuth2User,
@RequestBody ReservationCreateRequestDto requestDto
) {
WaitingResponseDto response = reservationService.registerWaiting(storeId,customOAuth2User,requestDto);
return ResponseEntity
.status(HttpStatus.CREATED)
.body(
ApiUtils.success(
response
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.nowait.applicationuser.reservation.dto;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class WaitingResponseDto {
private final int rank;
private final boolean reserved; // true면 예약, false면 대기
private final int partySize;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.nowait.applicationuser.reservation.repository;


import java.util.Set;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Repository;
import com.nowait.domaincoreredis.common.util.RedisKeyUtils;
import lombok.RequiredArgsConstructor;

@Repository
@RequiredArgsConstructor
public class WaitingRedisRepository {
private final StringRedisTemplate redisTemplate;

// 중복 등록 방지: 이미 있으면 추가X
public boolean addToWaitingQueue(Long storeId, String userId, Integer partySize, long timestamp) {
String queueKey = RedisKeyUtils.buildWaitingKeyPrefix() + storeId;
String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId;

Boolean added = redisTemplate.opsForZSet().addIfAbsent(queueKey, userId, timestamp);
if (Boolean.TRUE.equals(added)) {
redisTemplate.opsForHash().put(partyKey, userId, partySize.toString());
}
return Boolean.TRUE.equals(added);
}

public Integer getPartySize(Long storeId, String userId) {
String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId;
Object value = redisTemplate.opsForHash().get(partyKey, userId);
return value == null ? null : Integer.valueOf(value.toString());
}

public Long getRank(Long storeId, String userId) {
String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId;
return redisTemplate.opsForZSet().rank(key, userId);
}

public Long getWaitingCount(Long storeId) {
String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId;
return redisTemplate.opsForZSet().zCard(key);
}

public boolean removeWaiting(Long storeId, String userId) {
String key = RedisKeyUtils.buildWaitingKeyPrefix() + storeId;
String partyKey = RedisKeyUtils.buildWaitingPartySizeKeyPrefix() + storeId;
redisTemplate.opsForZSet().remove(key, userId);
redisTemplate.opsForHash().delete(partyKey, userId);
return true;
}
}


Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

import com.nowait.applicationuser.reservation.dto.ReservationCreateRequestDto;
import com.nowait.applicationuser.reservation.dto.ReservationCreateResponseDto;
import com.nowait.applicationuser.reservation.dto.WaitingResponseDto;
import com.nowait.applicationuser.reservation.repository.WaitingRedisRepository;
import com.nowait.common.enums.ReservationStatus;
import com.nowait.domaincorerdb.reservation.entity.Reservation;
import com.nowait.domaincorerdb.reservation.exception.DuplicateReservationException;
Expand All @@ -30,6 +32,57 @@ public class ReservationService {
private final ReservationRepository reservationRepository;
private final StoreRepository storeRepository;
private final UserRepository userRepository;
private final WaitingRedisRepository waitingRedisRepository;

public WaitingResponseDto registerWaiting(
Long storeId,CustomOAuth2User customOAuth2User,ReservationCreateRequestDto requestDto
) {
// Store 유효성 검증 추가
Store store = storeRepository.findById(storeId)
.orElseThrow(StoreNotFoundException::new);
if (Boolean.FALSE.equals(store.getIsActive()))
throw new StoreWaitingDisabledException();

String userId = customOAuth2User.getUserId().toString();
long timestamp = System.currentTimeMillis();

// 예약 신청 유저 큐(queue)에 추가
boolean added = waitingRedisRepository.addToWaitingQueue(storeId, userId, requestDto.getPartySize(), timestamp);
if (!added) {
throw new IllegalArgumentException("Failed to add to waiting queue");
}
// 신규 등록/기존 등록 관계없이 내 순번, 전체 인원 반환
Long rank = waitingRedisRepository.getRank(storeId, userId);
return WaitingResponseDto.builder()
.rank(rank == null ? -1 : rank.intValue() + 1)
.partySize(requestDto.getPartySize() == null ? 0 : requestDto.getPartySize())
.build();
}

public WaitingResponseDto myWaitingInfo(Long storeId, String userId) {
// 입력 검증 추가
if (storeId == null || userId == null || userId.trim().isEmpty()) {
throw new IllegalArgumentException("Invalid storeId or userId");
}
Long rank = waitingRedisRepository.getRank(storeId, userId);
Integer partySize = waitingRedisRepository.getPartySize(storeId, userId);
return WaitingResponseDto.builder()
.rank(rank == null ? -1 : rank.intValue() + 1)
.partySize(partySize == null ? 0 : partySize)
.build();
}

public boolean cancelWaiting(Long storeId, String userId) {
if (storeId == null || userId == null || userId.trim().isEmpty()) {
throw new IllegalArgumentException("Invalid storeId or userId");
}
// 대기열에서 제거 및 결과 반환
boolean removed = waitingRedisRepository.removeWaiting(storeId, userId);
if (!removed) {
throw new IllegalArgumentException("Waiting not found");
}
return removed;
}

@Transactional
public ReservationCreateResponseDto create(Long storeId, CustomOAuth2User customOAuth2User,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ public class RedisKeyUtils {
private static final String KEY_FMT = "popular:%d:%s";
private static final DateTimeFormatter DTF = DateTimeFormatter.ofPattern("yyyyMMdd");

// Waiting keys
private static final String WAITING_KEY_PREFIX = "waiting:";
private static final String WAITING_PARTYSIZE_KEY_PREFIX = "waiting:party:";


private RedisKeyUtils() {
throw new UnsupportedOperationException("유틸리티 서비스는 인스턴스화 할 수 없습니다.");
Expand All @@ -33,4 +37,7 @@ public static String buildNextKey() {
public static String buildMenuKey() { return KEY_FMT; }

public static DateTimeFormatter buildMenuDateKey() { return DTF; }

public static String buildWaitingKeyPrefix() { return WAITING_KEY_PREFIX; }
public static String buildWaitingPartySizeKeyPrefix() { return WAITING_PARTYSIZE_KEY_PREFIX; }
}