From 0352f7b98e09d861f85391b8b03b3bb026c69635 Mon Sep 17 00:00:00 2001 From: yeon-22k Date: Tue, 14 Oct 2025 21:49:55 +0900 Subject: [PATCH 1/6] =?UTF-8?q?feat(menu):=20dto=20validation=20check=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sparta/tdd/domain/menu/controller/MenuController.java | 3 ++- .../java/com/sparta/tdd/domain/menu/dto/MenuRequestDto.java | 6 ++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/com/sparta/tdd/domain/menu/controller/MenuController.java b/src/main/java/com/sparta/tdd/domain/menu/controller/MenuController.java index 52573755..2a162b51 100644 --- a/src/main/java/com/sparta/tdd/domain/menu/controller/MenuController.java +++ b/src/main/java/com/sparta/tdd/domain/menu/controller/MenuController.java @@ -5,6 +5,7 @@ import com.sparta.tdd.domain.menu.dto.MenuResponseDto; import com.sparta.tdd.domain.menu.service.MenuService; import com.sparta.tdd.domain.user.enums.UserAuthority; +import jakarta.validation.Valid; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -49,7 +50,7 @@ public ResponseEntity getMenu(@PathVariable UUID storeId, @PreAuthorize("hasAnyRole('OWNER', 'MASTER')") @PostMapping("/{storeId}/menu") public ResponseEntity createMenu(@PathVariable UUID storeId, - @RequestBody MenuRequestDto menuRequestDto, + @Valid @RequestBody MenuRequestDto menuRequestDto, @AuthenticationPrincipal UserDetailsImpl userDetails) { Long userId = userDetails.getUserId(); return ResponseEntity.status(HttpStatus.CREATED) diff --git a/src/main/java/com/sparta/tdd/domain/menu/dto/MenuRequestDto.java b/src/main/java/com/sparta/tdd/domain/menu/dto/MenuRequestDto.java index 08d98b65..029ffec5 100644 --- a/src/main/java/com/sparta/tdd/domain/menu/dto/MenuRequestDto.java +++ b/src/main/java/com/sparta/tdd/domain/menu/dto/MenuRequestDto.java @@ -2,13 +2,15 @@ import com.sparta.tdd.domain.menu.entity.Menu; import com.sparta.tdd.domain.store.entity.Store; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import lombok.Builder; @Builder public record MenuRequestDto( - String name, + @NotNull @Size(max = 20) String name, String description, - Integer price, + @NotNull Integer price, String imageUrl ) { From 4673068c0ef70d1f8425efb67347cfbbc4a93895 Mon Sep 17 00:00:00 2001 From: yeon-22k Date: Tue, 14 Oct 2025 21:50:39 +0900 Subject: [PATCH 2/6] =?UTF-8?q?fix(menu):=20jpa=20find=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20menuId=20>=20id=20=EC=9E=AC=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/repository/MenuRepository.java | 2 +- .../tdd/domain/menu/service/MenuService.java | 2 +- .../domain/menu/service/MenuServiceTest.java | 20 +++++++++---------- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/main/java/com/sparta/tdd/domain/menu/repository/MenuRepository.java b/src/main/java/com/sparta/tdd/domain/menu/repository/MenuRepository.java index 340af464..3eba03e0 100644 --- a/src/main/java/com/sparta/tdd/domain/menu/repository/MenuRepository.java +++ b/src/main/java/com/sparta/tdd/domain/menu/repository/MenuRepository.java @@ -15,7 +15,7 @@ public interface MenuRepository extends JpaRepository, MenuRepositor List findAllByStoreId(UUID storeId); - Optional findByStoreIdAndMenuIdAndIsDeletedFalse(UUID storeId, UUID menuId); + Optional findByStoreIdAndIdAndIsDeletedFalse(UUID storeId, UUID menuId); List findAllByStoreIdAndIsHiddenFalseAndIsDeletedFalse(UUID storeId); diff --git a/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java b/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java index ee568eaa..39844cb2 100644 --- a/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java +++ b/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java @@ -88,7 +88,7 @@ public void deleteMenu(UUID storeId, UUID menuId, Long userId) { } private Menu findMenu(UUID storeId, UUID menuId) { - return menuRepository.findByStoreIdAndMenuIdAndIsDeletedFalse(storeId, menuId) + return menuRepository.findByStoreIdAndIdAndIsDeletedFalse(storeId, menuId) .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 메뉴입니다.")); } diff --git a/src/test/java/com/sparta/tdd/domain/menu/service/MenuServiceTest.java b/src/test/java/com/sparta/tdd/domain/menu/service/MenuServiceTest.java index 774f5551..32c7650d 100644 --- a/src/test/java/com/sparta/tdd/domain/menu/service/MenuServiceTest.java +++ b/src/test/java/com/sparta/tdd/domain/menu/service/MenuServiceTest.java @@ -166,7 +166,7 @@ class GetMenu { @DisplayName("Customer 메뉴 상세 테스트") void getMenusCustomerTest() { //given - when(menuRepository.findByStoreIdAndMenuIdAndIsDeletedFalse(store.getId(), + when(menuRepository.findByStoreIdAndIdAndIsDeletedFalse(store.getId(), menu2.getId())) .thenReturn(Optional.of(menu2)); @@ -174,7 +174,7 @@ void getMenusCustomerTest() { assertThrows(IllegalArgumentException.class, () -> menuService.getMenu(store.getId(), menu2.getId(), customer.getAuthority())); - verify(menuRepository, times(1)).findByStoreIdAndMenuIdAndIsDeletedFalse(store.getId(), + verify(menuRepository, times(1)).findByStoreIdAndIdAndIsDeletedFalse(store.getId(), menu2.getId()); } @@ -182,7 +182,7 @@ void getMenusCustomerTest() { @DisplayName("OWNER 메뉴 상세 테스트") void getMenusOwnerTest() { //given - when(menuRepository.findByStoreIdAndMenuIdAndIsDeletedFalse(store.getId(), + when(menuRepository.findByStoreIdAndIdAndIsDeletedFalse(store.getId(), menu2.getId())) .thenReturn(Optional.of(menu2)); @@ -193,7 +193,7 @@ void getMenusOwnerTest() { // then assertNotNull(testMenu); assertEquals(menu2.getId(), testMenu.menuId()); - verify(menuRepository, times(1)).findByStoreIdAndMenuIdAndIsDeletedFalse(store.getId(), + verify(menuRepository, times(1)).findByStoreIdAndIdAndIsDeletedFalse(store.getId(), menu2.getId()); } @@ -226,7 +226,7 @@ void createMenuSuccessTest() { @DisplayName("메뉴 수정 테스트") void updateMenuSuccessTest() { // given - when(menuRepository.findByStoreIdAndMenuIdAndIsDeletedFalse(store.getId(), menu1.getId())) + when(menuRepository.findByStoreIdAndIdAndIsDeletedFalse(store.getId(), menu1.getId())) .thenReturn(Optional.of(menu1)); when(userRepository.findById(2L)).thenReturn(Optional.of(owner)); when(storeRepository.findById(store.getId())).thenReturn(Optional.of(store)); @@ -235,7 +235,7 @@ void updateMenuSuccessTest() { menuService.updateMenu(store.getId(), menu1.getId(), dto3, 2L); // then - verify(menuRepository, times(1)).findByStoreIdAndMenuIdAndIsDeletedFalse(store.getId(), + verify(menuRepository, times(1)).findByStoreIdAndIdAndIsDeletedFalse(store.getId(), menu1.getId()); assertEquals(dto3.name(), menu1.getName()); assertEquals(dto3.description(), menu1.getDescription()); @@ -248,7 +248,7 @@ void updateMenuSuccessTest() { @DisplayName("메뉴 상태 수정 테스트") void updateMenuStatusSuccessTest() { // given - when(menuRepository.findByStoreIdAndMenuIdAndIsDeletedFalse(store.getId(), menu1.getId())) + when(menuRepository.findByStoreIdAndIdAndIsDeletedFalse(store.getId(), menu1.getId())) .thenReturn(Optional.of(menu1)); when(userRepository.findById(2L)).thenReturn(Optional.of(owner)); when(storeRepository.findById(store.getId())).thenReturn(Optional.of(store)); @@ -257,7 +257,7 @@ void updateMenuStatusSuccessTest() { menuService.updateMenuStatus(store.getId(), menu1.getId(), Boolean.TRUE, 2L); // then - verify(menuRepository, times(1)).findByStoreIdAndMenuIdAndIsDeletedFalse(store.getId(), + verify(menuRepository, times(1)).findByStoreIdAndIdAndIsDeletedFalse(store.getId(), menu1.getId()); assertTrue(menu1.isHidden()); } @@ -266,7 +266,7 @@ void updateMenuStatusSuccessTest() { @DisplayName("메뉴 삭제 테스트(soft delete)") void deleteMenuSuccessTest() { // given - when(menuRepository.findByStoreIdAndMenuIdAndIsDeletedFalse(store.getId(), menu1.getId())) + when(menuRepository.findByStoreIdAndIdAndIsDeletedFalse(store.getId(), menu1.getId())) .thenReturn(Optional.of(menu1)); when(userRepository.findById(2L)).thenReturn(Optional.of(owner)); when(storeRepository.findById(store.getId())).thenReturn(Optional.of(store)); @@ -275,7 +275,7 @@ void deleteMenuSuccessTest() { menuService.deleteMenu(store.getId(), menu1.getId(), owner.getId()); // then - verify(menuRepository, times(1)).findByStoreIdAndMenuIdAndIsDeletedFalse(store.getId(), + verify(menuRepository, times(1)).findByStoreIdAndIdAndIsDeletedFalse(store.getId(), menu1.getId()); assertNotNull(menu1.getDeletedAt()); assertEquals(owner.getId(), menu1.getDeletedBy()); From 38322a03547725b66d0fc380b44af9f97e5b9ad6 Mon Sep 17 00:00:00 2001 From: yeon-22k Date: Tue, 14 Oct 2025 22:14:46 +0900 Subject: [PATCH 3/6] =?UTF-8?q?feat(menu):=20=EC=97=90=EB=9F=AC=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=B6=94=EA=B0=80=20=EB=B0=8F=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../sparta/tdd/domain/menu/service/MenuService.java | 12 +++++++----- .../com/sparta/tdd/global/exception/ErrorCode.java | 2 ++ .../sparta/tdd/domain/menu/MenuIntegrationTest.java | 2 +- .../tdd/domain/menu/service/MenuServiceTest.java | 3 ++- 4 files changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java b/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java index 39844cb2..267a47aa 100644 --- a/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java +++ b/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java @@ -9,6 +9,8 @@ import com.sparta.tdd.domain.user.entity.User; import com.sparta.tdd.domain.user.enums.UserAuthority; import com.sparta.tdd.domain.user.repository.UserRepository; +import com.sparta.tdd.global.exception.BusinessException; +import com.sparta.tdd.global.exception.ErrorCode; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -40,7 +42,7 @@ public List getMenus(UUID storeId, UserAuthority authority) { public MenuResponseDto getMenu(UUID storeId, UUID menuId, UserAuthority authority) { Menu menu = findMenu(storeId, menuId); if (authority.isCustomerOrManager() && menu.isHidden()) { - throw new IllegalArgumentException("숨겨진 메뉴입니다."); + throw new BusinessException(ErrorCode.MENU_HIDDEN); } return MenuResponseDto.from(menu); } @@ -89,22 +91,22 @@ public void deleteMenu(UUID storeId, UUID menuId, Long userId) { private Menu findMenu(UUID storeId, UUID menuId) { return menuRepository.findByStoreIdAndIdAndIsDeletedFalse(storeId, menuId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 메뉴입니다.")); + .orElseThrow(() -> new BusinessException(ErrorCode.MENU_NOT_FOUND)); } private Store findStore(UUID storeId) { return storeRepository.findById(storeId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 가게입니다.")); + .orElseThrow(() -> new BusinessException(ErrorCode.STORE_NOT_FOUND)); } private User findUser(Long userId) { return userRepository.findById(userId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 사용자입니다.")); + .orElseThrow(() -> new BusinessException(ErrorCode.USER_NOT_FOUND)); } private void validateUserOnMenu(User user, Store store) { if (!store.isOwner(user)) { - throw new IllegalArgumentException("권한이 없는 사용자입니다."); + throw new BusinessException(ErrorCode.MENU_PERMISSION_DENIED); } } } diff --git a/src/main/java/com/sparta/tdd/global/exception/ErrorCode.java b/src/main/java/com/sparta/tdd/global/exception/ErrorCode.java index ddf4d29a..1ce2ac88 100644 --- a/src/main/java/com/sparta/tdd/global/exception/ErrorCode.java +++ b/src/main/java/com/sparta/tdd/global/exception/ErrorCode.java @@ -46,6 +46,8 @@ public enum ErrorCode { ORDER_NOT_FOUND(HttpStatus.NOT_FOUND, "존재하지 않는 주문입니다."), // MENU 도메인 관련 + MENU_HIDDEN(HttpStatus.FORBIDDEN, "숨겨진 메뉴입니다."), + MENU_PERMISSION_DENIED(HttpStatus.FORBIDDEN, "해당 메뉴에 대한 권한이 없습니다."), // REVIEW 도메인 관련 REVIEW_NOT_OWNED(HttpStatus.FORBIDDEN, "본인의 리뷰만 수정/삭제할 수 있습니다."), diff --git a/src/test/java/com/sparta/tdd/domain/menu/MenuIntegrationTest.java b/src/test/java/com/sparta/tdd/domain/menu/MenuIntegrationTest.java index e34356de..7671c65b 100644 --- a/src/test/java/com/sparta/tdd/domain/menu/MenuIntegrationTest.java +++ b/src/test/java/com/sparta/tdd/domain/menu/MenuIntegrationTest.java @@ -266,7 +266,7 @@ void ownerDeletesMenu() throws Exception { mockMvc.perform( get("/v1/store/{storeId}/menu/{menuId}", storeId, menuId) .with(csrf())) - .andExpectAll(status().isBadRequest()); + .andExpectAll(status().isNotFound()); } @Test diff --git a/src/test/java/com/sparta/tdd/domain/menu/service/MenuServiceTest.java b/src/test/java/com/sparta/tdd/domain/menu/service/MenuServiceTest.java index 32c7650d..00ca8cf7 100644 --- a/src/test/java/com/sparta/tdd/domain/menu/service/MenuServiceTest.java +++ b/src/test/java/com/sparta/tdd/domain/menu/service/MenuServiceTest.java @@ -20,6 +20,7 @@ import com.sparta.tdd.domain.user.entity.User; import com.sparta.tdd.domain.user.enums.UserAuthority; import com.sparta.tdd.domain.user.repository.UserRepository; +import com.sparta.tdd.global.exception.BusinessException; import java.lang.reflect.Field; import java.util.List; import java.util.Optional; @@ -171,7 +172,7 @@ void getMenusCustomerTest() { .thenReturn(Optional.of(menu2)); // when & then - assertThrows(IllegalArgumentException.class, + assertThrows(BusinessException.class, () -> menuService.getMenu(store.getId(), menu2.getId(), customer.getAuthority())); verify(menuRepository, times(1)).findByStoreIdAndIdAndIsDeletedFalse(store.getId(), From 2a6fec6e6ce80ef99b2ff4a24feeed9e3b1fd2b4 Mon Sep 17 00:00:00 2001 From: yeon-22k Date: Sat, 18 Oct 2025 03:29:42 +0900 Subject: [PATCH 4/6] =?UTF-8?q?docs(menu):=20swagger=20=EB=AC=B8=EC=84=9C?= =?UTF-8?q?=ED=99=94=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../menu/controller/MenuController.java | 25 +++++++++++++++++++ .../tdd/domain/menu/dto/MenuRequestDto.java | 4 +++ .../tdd/domain/menu/dto/MenuResponseDto.java | 8 ++++++ 3 files changed, 37 insertions(+) diff --git a/src/main/java/com/sparta/tdd/domain/menu/controller/MenuController.java b/src/main/java/com/sparta/tdd/domain/menu/controller/MenuController.java index 2539e635..b6d00aa5 100644 --- a/src/main/java/com/sparta/tdd/domain/menu/controller/MenuController.java +++ b/src/main/java/com/sparta/tdd/domain/menu/controller/MenuController.java @@ -5,6 +5,7 @@ import com.sparta.tdd.domain.menu.dto.MenuResponseDto; import com.sparta.tdd.domain.menu.service.MenuService; import com.sparta.tdd.domain.user.enums.UserAuthority; +import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import java.util.List; import java.util.UUID; @@ -30,6 +31,10 @@ public class MenuController { private final MenuService menuService; + @Operation( + summary = "메뉴 목록 조회", + description = "가게의 메뉴 목록을 조회합니다. OWERN나 MASTER가 아니면 숨겨진 메뉴를 볼 수 없습니다." + ) @GetMapping("/{storeId}/menu") public ResponseEntity> getMenus(@PathVariable UUID storeId, @AuthenticationPrincipal UserDetailsImpl userDetails) { @@ -38,6 +43,10 @@ public ResponseEntity> getMenus(@PathVariable UUID storeId .body(menuService.getMenus(storeId, authority)); } + @Operation( + summary = "메뉴 상세 조회", + description = "가게의 특정 메뉴를 조회합니다. OWNER나 MASTER가 아니면 숨겨진 메뉴를 볼 수 없습니다. " + ) @GetMapping("/{storeId}/menu/{menuId}") public ResponseEntity getMenu(@PathVariable UUID storeId, @PathVariable UUID menuId, @@ -47,6 +56,10 @@ public ResponseEntity getMenu(@PathVariable UUID storeId, .body(menuService.getMenu(storeId, menuId, authority)); } + @Operation( + summary = "메뉴 등록", + description = "가게의 메뉴를 등록합니다. OWNER나 MASTER만 가능하며, 해당 가게 점주가 아니라면 예외가 발생합니다. " + ) @PreAuthorize("hasAnyRole('OWNER', 'MASTER')") @PostMapping("/{storeId}/menu") public ResponseEntity createMenu(@PathVariable UUID storeId, @@ -57,6 +70,10 @@ public ResponseEntity createMenu(@PathVariable UUID storeId, .body(menuService.createMenu(storeId, menuRequestDto, userId)); } + @Operation( + summary = "메뉴 수정", + description = "가게의 메뉴를 수정합니다. OWNER나 MASTER만 가능하며, 해당 가게 점주가 아니라면 예외가 발생합니다. " + ) @PreAuthorize("hasAnyRole('OWNER', 'MASTER')") @PatchMapping("/{storeId}/menu/{menuId}") public ResponseEntity updateMenu(@PathVariable UUID storeId, @PathVariable UUID menuId, @@ -67,6 +84,10 @@ public ResponseEntity updateMenu(@PathVariable UUID storeId, @PathVariable return ResponseEntity.noContent().build(); } + @Operation( + summary = "메뉴 상태 변경", + description = "가게의 메뉴 상태를 변경합니다. 메뉴를 숨기거나, 공개할 수 있습니다. OWNER나 MASTER만 가능하며, 해당 가게 점주가 아니라면 예외가 발생합니다. " + ) @PreAuthorize("hasAnyRole('OWNER', 'MASTER')") @PatchMapping("/{storeId}/menu/{menuId}/status") public ResponseEntity updateMenuStatus(@PathVariable UUID storeId, @@ -78,6 +99,10 @@ public ResponseEntity updateMenuStatus(@PathVariable UUID storeId, return ResponseEntity.noContent().build(); } + @Operation( + summary = "메뉴 삭제", + description = "가게의 메뉴를 삭제합니다. OWNER나 MASTER만 가능하며, 해당 가게 점주가 아니라면 예외가 발생합니다. 메뉴는 soft delete 됩니다. " + ) @PreAuthorize("hasAnyRole('OWNER', 'MASTER')") @DeleteMapping("/{storeId}/menu/{menuId}") public ResponseEntity deleteMenu(@PathVariable UUID storeId, @PathVariable UUID menuId, diff --git a/src/main/java/com/sparta/tdd/domain/menu/dto/MenuRequestDto.java b/src/main/java/com/sparta/tdd/domain/menu/dto/MenuRequestDto.java index 029ffec5..14e0b3d5 100644 --- a/src/main/java/com/sparta/tdd/domain/menu/dto/MenuRequestDto.java +++ b/src/main/java/com/sparta/tdd/domain/menu/dto/MenuRequestDto.java @@ -2,14 +2,18 @@ import com.sparta.tdd.domain.menu.entity.Menu; import com.sparta.tdd.domain.store.entity.Store; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import lombok.Builder; +@Schema(description = "메뉴 등록 및 수정 요청 DTO") @Builder public record MenuRequestDto( + @Schema(description = "메뉴 설명", example = "바삭한 치킨과 신선한 야채가 들어간 버거") @NotNull @Size(max = 20) String name, String description, + @Schema(description = "가격", example = "10000") @NotNull Integer price, String imageUrl ) { diff --git a/src/main/java/com/sparta/tdd/domain/menu/dto/MenuResponseDto.java b/src/main/java/com/sparta/tdd/domain/menu/dto/MenuResponseDto.java index 114d3206..27c03dde 100644 --- a/src/main/java/com/sparta/tdd/domain/menu/dto/MenuResponseDto.java +++ b/src/main/java/com/sparta/tdd/domain/menu/dto/MenuResponseDto.java @@ -1,16 +1,24 @@ package com.sparta.tdd.domain.menu.dto; import com.sparta.tdd.domain.menu.entity.Menu; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.UUID; import lombok.Builder; +@Schema(description = "메뉴 응답 DTO") @Builder public record MenuResponseDto( + @Schema(description = "menu 아이디", example = "1") UUID menuId, + @Schema(description = "메뉴명", example = "햄버거") String name, + @Schema(description = "메뉴 설명", example = "바삭한 치킨과 신선한 야채가 들어간 버거") String description, + @Schema(description = "가격", example = "10000") Integer price, + @Schema(description = "메뉴 이미지 URL", example = "https://example.com/images/chicken-burger.jpg") String imageUrl, + @Schema(description = "숨김 여부", example = "Boolean.False") Boolean isHidden ) { From 03a2ca16b617cd2a11710260fcfc64d6bd67cc45 Mon Sep 17 00:00:00 2001 From: yeon-22k Date: Sat, 18 Oct 2025 13:38:04 +0900 Subject: [PATCH 5/6] =?UTF-8?q?docs(coupon):=20coupon/userCoupon=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20swagger=20=EC=9E=91=EC=97=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coupon/controller/CouponController.java | 27 +++++++++++++++++++ .../controller/UserCouponController.java | 11 +++++++- .../domain/coupon/dto/CouponRequestDto.java | 9 +++++++ .../domain/coupon/dto/CouponResponseDto.java | 11 ++++++++ .../coupon/dto/UserCouponResponseDto.java | 6 +++++ 5 files changed, 63 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/sparta/tdd/domain/coupon/controller/CouponController.java b/src/main/java/com/sparta/tdd/domain/coupon/controller/CouponController.java index 1e46880f..2fe48fd9 100644 --- a/src/main/java/com/sparta/tdd/domain/coupon/controller/CouponController.java +++ b/src/main/java/com/sparta/tdd/domain/coupon/controller/CouponController.java @@ -4,6 +4,7 @@ import com.sparta.tdd.domain.coupon.dto.CouponRequestDto; import com.sparta.tdd.domain.coupon.dto.CouponResponseDto; import com.sparta.tdd.domain.coupon.service.CouponService; +import io.swagger.v3.oas.annotations.Operation; import jakarta.validation.Valid; import java.util.List; import java.util.UUID; @@ -28,12 +29,22 @@ public class CouponController { private final CouponService couponService; + @Operation( + summary = "가게 쿠폰 목록 조회", + description = "해당 가게의 모든 쿠폰을 조회합니다." + ) @GetMapping("/list/{storeId}") public ResponseEntity> getStoreCoupons(@PathVariable UUID storeId) { return ResponseEntity.status(HttpStatus.OK) .body(couponService.getStoreCoupons(storeId)); } + @Operation( + summary = "STORE 쿠폰 등록", + description = + "해당 가게에만 적용할 수 있는 STORE 쿠폰을 발급합니다. OWNER, MASTER 권한만 접근 가능하며, 쿠폰 발급 시 최초 쿠폰 SCOPE는 STORE입니다." + + "TYPE 에서 FIXED(고정된 할인값), PERCENT(할인율)을 선택할 수 있으며 사용가능한 최소 금액과 만료기간을 필수로 입력해야 합니다." + ) @PreAuthorize("hasAnyRole('OWNER', 'MASTER')") @PostMapping("/{storeId}") public ResponseEntity createStoreCoupon(@PathVariable UUID storeId, @@ -43,6 +54,12 @@ public ResponseEntity createStoreCoupon(@PathVariable UUID st .body(couponService.createStoreCoupon(storeId, dto, userDetails.getUserId())); } + @Operation( + summary = "MASTER 쿠폰 등록", + description = + "가게와 무관하게 적용할 수 있는 MASTER 쿠폰을 발급합니다. MASTER 권한만 접근 가능하며, 쿠폰 발급 시 최초 쿠폰 SCOPE는 MASTER입니다." + + "TYPE 에서 FIXED(고정된 할인값), PERCENT(할인율)을 선택할 수 있으며 사용가능한 최소 금액과 만료기간을 필수로 입력해야 합니다." + ) @PreAuthorize("hasAnyRole('MASTER')") @PostMapping("/master") public ResponseEntity createMasterCoupon( @@ -51,6 +68,10 @@ public ResponseEntity createMasterCoupon( .body(couponService.createMasterCoupon(dto)); } + @Operation( + summary = "쿠폰 수정", + description = "쿠폰을 수정합니다. 해당 가게 점주(OWNER), MASTER 권한만 가능하며 이미 1회라도 사용자가 발급했다면 수정할 수 없습니다." + ) @PreAuthorize("hasAnyRole('OWNER', 'MASTER')") @PatchMapping("/{storeId}/{couponId}") public ResponseEntity updateCoupon(@PathVariable UUID storeId, @@ -61,6 +82,12 @@ public ResponseEntity updateCoupon(@PathVariable UUID storeId, return ResponseEntity.noContent().build(); } + @Operation( + summary = "쿠폰 삭제", + description = + "쿠폰을 삭제합니다(sort delete). 해당 가게 점주(OWNER), MASTER 권한만 가능하며 해당 쿠폰이 삭제되어도(추가 발급 불가)" + + " 이미 발급된 유저쿠폰은 적용할 수 있습니다." + ) @PreAuthorize("hasAnyRole('OWNER', 'MASTER')") @DeleteMapping("/{storeId}/{couponId}") public ResponseEntity deleteCoupon(@PathVariable UUID storeId, diff --git a/src/main/java/com/sparta/tdd/domain/coupon/controller/UserCouponController.java b/src/main/java/com/sparta/tdd/domain/coupon/controller/UserCouponController.java index 414df85f..a3cdfc9a 100644 --- a/src/main/java/com/sparta/tdd/domain/coupon/controller/UserCouponController.java +++ b/src/main/java/com/sparta/tdd/domain/coupon/controller/UserCouponController.java @@ -3,6 +3,7 @@ import com.sparta.tdd.domain.auth.UserDetailsImpl; import com.sparta.tdd.domain.coupon.dto.UserCouponResponseDto; import com.sparta.tdd.domain.coupon.service.UserCouponService; +import io.swagger.v3.oas.annotations.Operation; import java.util.List; import java.util.UUID; import lombok.RequiredArgsConstructor; @@ -24,7 +25,11 @@ public class UserCouponController { private final UserCouponService userCouponService; - + @Operation( + summary = "내 쿠폰 목록 조회", + description = "사용자의 쿠폰 목록을 조회합니다. CUSTOMER와 MASTER 권한만 가능하며, ACTIVE 상태 쿠폰부터 최신 발급 순 " + + "> USED/EXPIRED 상태 쿠폰 최신 발급 순으로 조회됩니다. 만료 후 7일이 지난 쿠폰은 조회되지 않습니다." + ) @GetMapping("/my/list") public ResponseEntity> getMyCoupons( @AuthenticationPrincipal UserDetailsImpl userDetails) { @@ -32,6 +37,10 @@ public ResponseEntity> getMyCoupons( .body(userCouponService.getMyCoupons(userDetails.getUserId())); } + @Operation( + summary = "유저쿠폰 발급", + description = "쿠폰을 발급하여 유저쿠폰을 생성합니다. CUSTOMER와 MASTER 권한만 가능하며, 발급된 유저쿠폰의 최초 CouponStatus는 ACTIVE 입니다." + ) @PostMapping("/{couponId}") public ResponseEntity createUserCoupon(@PathVariable UUID couponId, @AuthenticationPrincipal UserDetailsImpl userDetails) { diff --git a/src/main/java/com/sparta/tdd/domain/coupon/dto/CouponRequestDto.java b/src/main/java/com/sparta/tdd/domain/coupon/dto/CouponRequestDto.java index 975146ac..957d4dff 100644 --- a/src/main/java/com/sparta/tdd/domain/coupon/dto/CouponRequestDto.java +++ b/src/main/java/com/sparta/tdd/domain/coupon/dto/CouponRequestDto.java @@ -4,19 +4,28 @@ import com.sparta.tdd.domain.coupon.enums.Scope; import com.sparta.tdd.domain.coupon.enums.Type; import com.sparta.tdd.domain.store.entity.Store; +import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; import jakarta.validation.constraints.Size; import java.time.LocalDateTime; import lombok.Builder; +@Schema(description = "쿠폰 요청 DTO") @Builder public record CouponRequestDto( + @Schema(description = "쿠폰 이름", example = "2000원 할인 쿠폰") @NotNull @Size(max = 20) String name, + @Schema(description = "할인 종류", example = "FIXED") @NotNull Type type, + @Schema(description = "사용 범위", example = "STORE") @NotNull Scope scope, + @Schema(description = "할인값", example = "2000") @NotNull Integer discountValue, + @Schema(description = "최소 주문 금액", example = "15000") @NotNull Integer minOrderPrice, + @Schema(description = "발행 개수", example = "100") int quantity, + @Schema(description = "만료일자", example = "2025-09-29T12:00:00.000000000") @NotNull LocalDateTime expiredAt ) { diff --git a/src/main/java/com/sparta/tdd/domain/coupon/dto/CouponResponseDto.java b/src/main/java/com/sparta/tdd/domain/coupon/dto/CouponResponseDto.java index b0bdd794..402ab71d 100644 --- a/src/main/java/com/sparta/tdd/domain/coupon/dto/CouponResponseDto.java +++ b/src/main/java/com/sparta/tdd/domain/coupon/dto/CouponResponseDto.java @@ -3,20 +3,31 @@ import com.sparta.tdd.domain.coupon.entity.Coupon; import com.sparta.tdd.domain.coupon.enums.Scope; import com.sparta.tdd.domain.coupon.enums.Type; +import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDateTime; import java.util.UUID; import lombok.Builder; +@Schema(description = "쿠폰 응답 DTO") @Builder public record CouponResponseDto( + @Schema(description = "쿠폰 ID", example = "551e8400-e29b-41d4-a716-446655440001") UUID couponId, + @Schema(description = "쿠폰 이름", example = "2000원 할인 쿠폰") String name, + @Schema(description = "할인 종류", example = "FIXED") Type type, + @Schema(description = "사용 범위", example = "STORE") Scope scope, + @Schema(description = "할인값", example = "2000") Integer discountValue, + @Schema(description = "최소 주문 금액", example = "15000") Integer minOrderPrice, + @Schema(description = "발행 개수", example = "100") int quantity, + @Schema(description = "발급된 수량", example = "30") int issuedCount, + @Schema(description = "만료일자", example = "2025-09-29T12:00:00.000000000") LocalDateTime expiredAt ) { diff --git a/src/main/java/com/sparta/tdd/domain/coupon/dto/UserCouponResponseDto.java b/src/main/java/com/sparta/tdd/domain/coupon/dto/UserCouponResponseDto.java index 90ebf09c..aac84834 100644 --- a/src/main/java/com/sparta/tdd/domain/coupon/dto/UserCouponResponseDto.java +++ b/src/main/java/com/sparta/tdd/domain/coupon/dto/UserCouponResponseDto.java @@ -2,14 +2,20 @@ import com.sparta.tdd.domain.coupon.entity.UserCoupon; import com.sparta.tdd.domain.coupon.enums.CouponStatus; +import io.swagger.v3.oas.annotations.media.Schema; import java.util.UUID; import lombok.Builder; +@Schema(description = "유저쿠폰 응답 DTO") @Builder public record UserCouponResponseDto( + @Schema(description = "유저쿠폰 ID", example = "550e8400-e29b-41d4-a716-446655440000") UUID userCouponId, + @Schema(description = "유저 ID", example = "1") Long userId, + @Schema(description = "쿠폰 ID", example = "551e8400-e29b-41d4-a716-446655440001") UUID couponId, + @Schema(description = "유저 쿠폰 상태", example = "ACTIVE") CouponStatus couponStatus ) { From 19681bd25ad4afc6cd518b6585c34d50bb0121a8 Mon Sep 17 00:00:00 2001 From: yeon-22k Date: Sat, 18 Oct 2025 13:40:30 +0900 Subject: [PATCH 6/6] =?UTF-8?q?fix(menu):=20findMenu=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EC=88=98=EC=A0=95(=EB=8B=A4=EB=A5=B8=20?= =?UTF-8?q?=EB=B8=8C=EB=9E=9C=EC=B9=98=EC=97=90=EC=84=9C=20=EB=8F=99?= =?UTF-8?q?=EC=9D=BC=ED=95=98=EA=B2=8C=20=EC=88=98=EC=A0=95=ED=95=B4?= =?UTF-8?q?=EC=84=9C=20=EC=BB=A4=EB=B0=8B=ED=95=98=EC=97=AC=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=EB=90=A0=20=EC=88=98=20=EC=9E=88=EC=9D=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/sparta/tdd/domain/menu/service/MenuService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java b/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java index 35c664ff..680744b1 100644 --- a/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java +++ b/src/main/java/com/sparta/tdd/domain/menu/service/MenuService.java @@ -91,7 +91,7 @@ public void deleteMenu(UUID storeId, UUID menuId, Long userId) { private Menu findMenu(UUID storeId, UUID menuId) { return menuRepository.findByIdAndStoreIdAndIsDeletedFalse(menuId, storeId) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 메뉴입니다.")); + .orElseThrow(() -> new BusinessException(ErrorCode.MENU_NOT_FOUND)); } private Store findStore(UUID storeId) {