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 @@ -32,6 +32,8 @@
import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException;
import com.nowait.domaincorerdb.order.exception.OrderViewUnauthorizedException;
import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException;
import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException;
import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException;
import com.nowait.domaincorerdb.token.exception.BusinessException;
import com.nowait.domaincorerdb.user.exception.UserNotFoundException;

Expand Down Expand Up @@ -156,6 +158,20 @@ public ErrorResponse reservationNotFoundException(ReservationNotFoundException e
return new ErrorResponse(e.getMessage(), NOTFOUND_RESERVATION.getCode());
}

@ResponseStatus(value = FORBIDDEN)
@ExceptionHandler(ReservationViewUnauthorizedException.class)
public ErrorResponse reservationViewUnauthorizedException(ReservationViewUnauthorizedException e) {
log.error("reservationViewUnauthorizedException", e);
return new ErrorResponse(e.getMessage(), RESERVATION_VIEW_UNAUTHORIZED.getCode());
}

@ResponseStatus(value = FORBIDDEN)
@ExceptionHandler(ReservationUpdateUnauthorizedException.class)
public ErrorResponse reservationUpdateUnauthorizedException(ReservationUpdateUnauthorizedException e) {
log.error("reservationUpdateUnauthorizedException", e);
return new ErrorResponse(e.getMessage(), RESERVATION_UPDATE_UNAUTHORIZED.getCode());
}

@ResponseStatus(value = FORBIDDEN)
@ExceptionHandler(MenuCreationUnauthorizedException.class)
public ErrorResponse menuCreationUnauthorizedException(MenuCreationUnauthorizedException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PathVariable;
Expand All @@ -14,6 +15,7 @@
import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto;
import com.nowait.applicationadmin.reservation.service.ReservationService;
import com.nowait.common.api.ApiUtils;
import com.nowait.domaincorerdb.user.entity.MemberDetails;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
Expand All @@ -31,8 +33,9 @@ public class ReservationController {
@GetMapping("/admin/{storeId}")
@Operation(summary = "주점별 예약리스트 조회", description = "특정 주점에 대한 예약리스트 조회")
@ApiResponse(responseCode = "200", description = "예약리스트 조회")
public ResponseEntity<?> getReservationListByStoreId(@PathVariable Long storeId) {
ReservationStatusSummaryDto response = reservationService.getReservationListByStoreId(storeId);
public ResponseEntity<?> getReservationListByStoreId(@PathVariable Long storeId,
@AuthenticationPrincipal MemberDetails memberDetails) {
ReservationStatusSummaryDto response = reservationService.getReservationListByStoreId(storeId,memberDetails);
return ResponseEntity
.status(HttpStatus.OK)
.body(
Expand All @@ -45,9 +48,10 @@ public ResponseEntity<?> getReservationListByStoreId(@PathVariable Long storeId)
@PatchMapping("/admin/updates/{reservationId}")
@Operation(summary = "예약팀 상태 변경", description = "특정 예약에 대한 상태 변경(예약중->호출중,호출중->입장완료,취소)")
@ApiResponse(responseCode = "200", description = "예약팀 상태 변경")
public ResponseEntity<?> updateReservationStatus(@PathVariable Long reservationId,@RequestBody
ReservationStatusUpdateRequestDto requestDto) {
CallGetResponseDto response = reservationService.updateReservationStatus(reservationId,requestDto);
public ResponseEntity<?> updateReservationStatus(@PathVariable Long reservationId,
@RequestBody ReservationStatusUpdateRequestDto requestDto,
@AuthenticationPrincipal MemberDetails memberDetails) {
CallGetResponseDto response = reservationService.updateReservationStatus(reservationId,requestDto,memberDetails);
return ResponseEntity
.status(HttpStatus.OK)
.body(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,17 @@
import com.nowait.applicationadmin.reservation.dto.ReservationStatusSummaryDto;
import com.nowait.applicationadmin.reservation.dto.ReservationStatusUpdateRequestDto;
import com.nowait.common.enums.ReservationStatus;
import com.nowait.common.enums.Role;
import com.nowait.domaincorerdb.order.exception.OrderUpdateUnauthorizedException;
import com.nowait.domaincorerdb.reservation.entity.Reservation;
import com.nowait.domaincorerdb.reservation.exception.ReservationNotFoundException;
import com.nowait.domaincorerdb.reservation.exception.ReservationUpdateUnauthorizedException;
import com.nowait.domaincorerdb.reservation.exception.ReservationViewUnauthorizedException;
import com.nowait.domaincorerdb.reservation.repository.ReservationRepository;
import com.nowait.domaincorerdb.user.entity.MemberDetails;
import com.nowait.domaincorerdb.user.entity.User;
import com.nowait.domaincorerdb.user.exception.UserNotFoundException;
import com.nowait.domaincorerdb.user.repository.UserRepository;

import lombok.RequiredArgsConstructor;

Expand All @@ -22,9 +30,14 @@
public class ReservationService {

private final ReservationRepository reservationRepository;
private final UserRepository userRepository;

@Transactional(readOnly = true)
public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId) {
public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, MemberDetails memberDetails) {
User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new);
if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) {
throw new ReservationViewUnauthorizedException();
}
Comment on lines +36 to +40
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

null 안전성 검증 추가 필요

권한 검증 로직이 올바르게 구현되었으나, user.getStoreId()가 null인 경우 NullPointerException이 발생할 수 있습니다.

 public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId, MemberDetails memberDetails) {
     User user =  userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new);
-    if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(storeId)) {
+    if (!Role.SUPER_ADMIN.equals(user.getRole()) && (user.getStoreId() == null || !user.getStoreId().equals(storeId))) {
         throw new ReservationViewUnauthorizedException();
     }
🤖 Prompt for AI Agents
In
nowait-app-admin-api/src/main/java/com/nowait/applicationadmin/reservation/service/ReservationService.java
around lines 36 to 40, the authorization check uses user.getStoreId() without
null safety, which can cause a NullPointerException if getStoreId() returns
null. To fix this, add a null check for user.getStoreId() before calling equals,
or use a null-safe comparison method to ensure no NullPointerException occurs
during the role and store ID validation.

List<Reservation> reservations = reservationRepository.findAllByStore_StoreIdOrderByRequestedAtAsc(storeId);

// 상태별 카운트 집계
Expand All @@ -50,9 +63,13 @@ public ReservationStatusSummaryDto getReservationListByStoreId(Long storeId) {
.build();
}
@Transactional
public CallGetResponseDto updateReservationStatus(Long reservationId, ReservationStatusUpdateRequestDto requestDto) {
Reservation reservation = reservationRepository.findById(reservationId)
.orElseThrow(ReservationNotFoundException::new);
public CallGetResponseDto updateReservationStatus(Long reservationId, ReservationStatusUpdateRequestDto requestDto,
MemberDetails memberDetails) {
User user = userRepository.findById(memberDetails.getId()).orElseThrow(UserNotFoundException::new);
Reservation reservation = reservationRepository.findById(reservationId).orElseThrow(ReservationNotFoundException::new);
if (!Role.SUPER_ADMIN.equals(user.getRole()) && !user.getStoreId().equals(reservation.getStore().getStoreId())) {
throw new ReservationUpdateUnauthorizedException();
}
reservation.updateStatus(requestDto.getStatus());
return CallGetResponseDto.fromEntity(reservation);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public enum ErrorMessage {

//reservation
NOTFOUND_RESERVATION("저장된 예약 정보가 없습니다.", "reservation001"),
RESERVATION_VIEW_UNAUTHORIZED("예약 보기 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "reservation002"),
RESERVATION_UPDATE_UNAUTHORIZED("예약 수정 권한이 없습니다.(슈퍼계정 or 주점 관리자만 가능)", "reservation003"),

// bookmark
DUPLICATE_BOOKMARK("이미 북마크한 주점입니다.", "bookmark001"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.nowait.domaincorerdb.reservation.exception;

import com.nowait.common.exception.ErrorMessage;

public class ReservationUpdateUnauthorizedException extends RuntimeException {
public ReservationUpdateUnauthorizedException() {
super(ErrorMessage.RESERVATION_UPDATE_UNAUTHORIZED.getMessage());
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package com.nowait.domaincorerdb.reservation.exception;

import com.nowait.common.exception.ErrorMessage;

public class ReservationViewUnauthorizedException extends RuntimeException {
public ReservationViewUnauthorizedException() {
super(ErrorMessage.RESERVATION_VIEW_UNAUTHORIZED.getMessage());
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ public class User {
@Enumerated(EnumType.STRING)
private Role role;

@Column(nullable = false)
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

기존 데이터와 데이터베이스 마이그레이션에 대한 검증이 필요합니다.

storeId 필드에 nullable = false 제약조건을 추가하는 것은 권한 검증 로직에 적합하지만, 다음 사항들을 확인해야 합니다:

  1. 기존 데이터베이스에 storeId가 null인 사용자 레코드가 존재하는지 확인
  2. SUPER_ADMIN 역할의 사용자들도 특정 store에 연결되어야 하는지 비즈니스 로직 검토
  3. 데이터베이스 마이그레이션 스크립트에서 기존 null 값들을 적절히 처리하는 로직 포함

다음 스크립트를 실행하여 기존 데이터의 상태를 확인하고 마이그레이션 전략을 수립하세요:


🏁 Script executed:

#!/bin/bash
# Description: Check for existing null storeId values and SUPER_ADMIN users in the codebase

# Check for database migration files that handle this constraint
fd -e sql -e xml | xargs grep -l "storeId" | head -5

# Check for SUPER_ADMIN role usage to understand business logic
rg "SUPER_ADMIN" --type java -A 3 -B 3

# Check for User entity creation patterns to ensure storeId is always set
ast-grep --pattern 'User.builder()$$$build()'

Length of output: 13134


데이터베이스 마이그레이션 및 사용자 생성 로직 검증 필요

이번 변경으로 User.storeId@Column(nullable = false) 제약이 추가되었으나, 다음 항목을 반드시 보강해야 합니다:

  • SQL/XML 마이그레이션 파일에서 storeId 제약을 처리하는 스크립트가 없습니다.
    → 기존 null 값에 대한 업데이트 혹은 기본값 부여 로직을 포함한 마이그레이션 스크립트를 추가하세요.
  • SUPER_ADMIN 사용자도 storeId 없이 생성될 가능성을 검토해야 합니다.
    → 비즈니스 정책에 따라 SUPER_ADMIN에게 기본 storeId를 할당할지 결정하고, 관련 로직을 반영하세요.
  • User 객체 생성 흐름(회원가입 API, 관리자 생성 서비스 등)에서 storeId가 항상 설정되는지 확인하세요.
    → 빌더 패턴이나 생성자 호출 시 누락되는 부분이 없는지 코드 전반을 점검하고, 필요하면 유효성 검사를 강화하세요.
🤖 Prompt for AI Agents
In
nowait-domain/domain-core-rdb/src/main/java/com/nowait/domaincorerdb/user/entity/User.java
at line 47, the addition of @Column(nullable = false) on User.storeId requires
updating the database migration scripts to handle existing null values by
providing default values or update logic. Review and update the user creation
logic, especially for SUPER_ADMIN users, to assign a default storeId if business
rules require it. Also, audit all User object creation points, including signup
and admin creation flows, to ensure storeId is always set, adding validation or
builder pattern checks as needed.

private Long storeId;

@Builder
Expand Down