-
Notifications
You must be signed in to change notification settings - Fork 0
[FEAT]: 메뉴 관리 CRUD 구현 및 S3 수명주기 추가 #87
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
Merged
Merged
Changes from all commits
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
c1f230c
[FEAT]: 메뉴 도메인 DTO 추가
twodo0 a54580d
[FEAT]: 메뉴 API 상태코드 추가
twodo0 d629370
[FEAT]: 메뉴, 메뉴 이미지 등록, 삭제 로직 추가
twodo0 1d501f3
[FEAT]: 메뉴 수정 DTO 추가
twodo0 baf00a3
[FEAT]: 메뉴 수정 성공 응답코드 추가
twodo0 dc2906a
[FEAT]: 메뉴 수정 로직 추가
twodo0 228996b
[REFACTOR]: S3 예외처리 세분화
twodo0 d3a2389
[FEAT]: 메뉴 조회 DTO 및 성공 응답코드 추가
twodo0 b23b820
[FEAT]: 메뉴 조회 로직 추가
twodo0 8007772
[FEAT]: 메뉴 조회 시 N+1 방지를 위해 fetch join 쿼리 추가
twodo0 1f920dd
[FEAT]: 품절여부 변경 DTO 및 성공 응답코드 추가
twodo0 56c9214
[FEAT]: 메뉴 품절여부 변경 로직 추가
twodo0 5892557
[FEAT]: 메뉴 삭제 시 soft delete 설정
twodo0 c15f24d
[FEAT]: @Valid, @RequestParam, @PathVaraible 유효성 검사 실패 시 에러 메시지 보이도록 …
twodo0 336dd3c
[FEAT]: 이미지 선 업로드에 따른 S3 수명 주기 연동 로직 구현
twodo0 acccee2
[REFACTOR]: 이미지 Url 리턴하도록 수정
twodo0 61ec011
[FEAT]: 품절 여부 수정 요청이 기존과 동일하다면 바로 리턴하도록 조건 추가
twodo0 15e2981
[REFACTOR]: 이미지 삭제 API를 이미 등록된 이미지 삭제하는 것으로 역할 수정
twodo0 bfc0d40
[REFACTOR]: soft delete된 Menu는 안 가져오도록 쿼리 수정
twodo0 36a98f4
[REFACTOR]: @Where -> @SQLRestriction 으로 수정
twodo0 83cfaca
[FIX]: isSuccess(false)로 수정
twodo0 c20560f
[REFACTOR]: 예약금 정책 변경에 따라 minPrice 필드 삭제 및 로직 삭제
twodo0 8205906
[REFACTOR]: 남아있던 minPrice 사용하는 로직들 수정
twodo0 7cb98b0
[FEAT]: 사용하지 않는 removeMenu 메서드 삭제
twodo0 074c8a4
[REFACTOR]: 메뉴가 없는 가게도 조회될 수 있도록 쿼리 조건 수정
twodo0 d9cbf9c
[REFACTOR]: 트랜잭션 커밋 이후에 S3에 접근할 수 있도록 로직 수정
twodo0 df1accc
[FEAT]: moveObject()에서 이동 경로가 동일한 경우 예외처리 추가
twodo0 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
90 changes: 90 additions & 0 deletions
90
src/main/java/com/eatsfine/eatsfine/domain/menu/controller/MenuController.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| package com.eatsfine.eatsfine.domain.menu.controller; | ||
|
|
||
| import com.eatsfine.eatsfine.domain.menu.dto.MenuReqDto; | ||
| import com.eatsfine.eatsfine.domain.menu.dto.MenuResDto; | ||
| import com.eatsfine.eatsfine.domain.menu.service.MenuCommandService; | ||
| import com.eatsfine.eatsfine.domain.menu.service.MenuQueryService; | ||
| import com.eatsfine.eatsfine.domain.menu.status.MenuSuccessStatus; | ||
| import com.eatsfine.eatsfine.global.apiPayload.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.MediaType; | ||
| import org.springframework.web.bind.annotation.*; | ||
| import org.springframework.web.multipart.MultipartFile; | ||
|
|
||
| @Tag(name = "Menu", description = "가게 메뉴 관련 API") | ||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/api/v1") | ||
| public class MenuController { | ||
|
|
||
| private final MenuCommandService menuCommandService; | ||
| private final MenuQueryService menuQueryService; | ||
|
|
||
| @Operation(summary = "메뉴 이미지 선 업로드 API", description = "메뉴 등록 전에 이미지를 먼저 업로드하고 KEY를 반환합니다.") | ||
| @PostMapping(value = "/stores/{storeId}/menus/images", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) | ||
| public ApiResponse<MenuResDto.ImageUploadDto> uploadImage( | ||
| @PathVariable Long storeId, | ||
| @RequestPart("image") MultipartFile file | ||
| ){ | ||
| return ApiResponse.of(MenuSuccessStatus._MENU_IMAGE_UPLOAD_SUCCESS, menuCommandService.uploadImage(storeId, file)); | ||
| } | ||
|
|
||
| @Operation(summary = "메뉴 등록 API", description = "가게의 메뉴들을 등록합니다.") | ||
| @PostMapping("/stores/{storeId}/menus") | ||
| public ApiResponse<MenuResDto.MenuCreateDto> createMenus( | ||
| @PathVariable Long storeId, | ||
| @RequestBody @Valid MenuReqDto.MenuCreateDto dto | ||
| ) { | ||
| return ApiResponse.of(MenuSuccessStatus._MENU_CREATE_SUCCESS, menuCommandService.createMenus(storeId, dto)); | ||
| } | ||
|
|
||
| @Operation(summary = "메뉴 삭제 API", description = "가게의 메뉴들을 삭제합니다.") | ||
| @DeleteMapping("/stores/{storeId}/menus") | ||
| public ApiResponse<MenuResDto.MenuDeleteDto> deleteMenus( | ||
| @PathVariable Long storeId, | ||
| @RequestBody @Valid MenuReqDto.MenuDeleteDto dto | ||
| ) { | ||
| return ApiResponse.of(MenuSuccessStatus._MENU_DELETE_SUCCESS, menuCommandService.deleteMenus(storeId, dto)); | ||
| } | ||
|
|
||
| @Operation(summary = "메뉴 수정 API", description = "가게의 메뉴를 수정합니다.") | ||
| @PatchMapping("/stores/{storeId}/menus/{menuId}") | ||
| public ApiResponse<MenuResDto.MenuUpdateDto> updateMenu( | ||
| @PathVariable Long storeId, | ||
| @PathVariable Long menuId, | ||
| @RequestBody @Valid MenuReqDto.MenuUpdateDto dto | ||
| ) { | ||
| return ApiResponse.of(MenuSuccessStatus._MENU_UPDATE_SUCCESS, menuCommandService.updateMenu(storeId, menuId, dto)); | ||
| } | ||
|
|
||
| @Operation(summary = "품절 여부 변경 API", description = "메뉴의 품절 여부를 변경합니다.") | ||
| @PatchMapping("/stores/{storeId}/menus/{menuId}/sold-out") | ||
| public ApiResponse<MenuResDto.SoldOutUpdateDto> updateSoldOutStatus( | ||
| @PathVariable Long storeId, | ||
| @PathVariable Long menuId, | ||
| @RequestBody @Valid MenuReqDto.SoldOutUpdateDto dto | ||
| ){ | ||
| return ApiResponse.of(MenuSuccessStatus._SOLD_OUT_UPDATE_SUCCESS, menuCommandService.updateSoldOutStatus(storeId, menuId, dto.isSoldOut())); | ||
| } | ||
|
|
||
| @Operation(summary = "등록된 메뉴 이미지 삭제 API", description = "이미 등록된 메뉴의 이미지를 삭제합니다.") | ||
| @DeleteMapping("/stores/{storeId}/menus/{menuId}/image") | ||
| public ApiResponse<MenuResDto.ImageDeleteDto> deleteMenuImage( | ||
| @PathVariable Long storeId, | ||
| @PathVariable Long menuId | ||
| ) { | ||
| return ApiResponse.of(MenuSuccessStatus._MENU_IMAGE_DELETE_SUCCESS, menuCommandService.deleteMenuImage(storeId, menuId)); | ||
| } | ||
|
|
||
| @Operation(summary = "메뉴 조회 API", description = "가게의 메뉴들을 조회합니다.") | ||
| @GetMapping("/stores/{storeId}/menus") | ||
| public ApiResponse<MenuResDto.MenuListDto> getMenus( | ||
| @PathVariable Long storeId | ||
| ) { | ||
| return ApiResponse.of(MenuSuccessStatus._MENU_LIST_SUCCESS, menuQueryService.getMenus(storeId)); | ||
|
|
||
| } | ||
| } | ||
56 changes: 56 additions & 0 deletions
56
src/main/java/com/eatsfine/eatsfine/domain/menu/converter/MenuConverter.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,56 @@ | ||
| package com.eatsfine.eatsfine.domain.menu.converter; | ||
|
|
||
| import com.eatsfine.eatsfine.domain.menu.dto.MenuResDto; | ||
| import com.eatsfine.eatsfine.domain.menu.entity.Menu; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| public class MenuConverter { | ||
|
|
||
|
|
||
| public static MenuResDto.ImageUploadDto toImageUploadDto(String imageKey, String imageUrl){ | ||
| return MenuResDto.ImageUploadDto.builder() | ||
| .imageKey(imageKey) | ||
| .imageUrl(imageUrl) | ||
| .build(); | ||
| } | ||
|
|
||
| public static MenuResDto.ImageDeleteDto toImageDeleteDto(String imageKey) { | ||
| return MenuResDto.ImageDeleteDto.builder() | ||
| .deletedImageKey(imageKey) | ||
| .build(); | ||
| } | ||
|
|
||
|
|
||
| public static MenuResDto.MenuCreateDto toCreateDto(List<MenuResDto.MenuDto> menuDtos) { | ||
| return MenuResDto.MenuCreateDto.builder() | ||
| .menus(menuDtos) | ||
| .build(); | ||
| } | ||
|
|
||
| public static MenuResDto.MenuDeleteDto toDeleteDto(List<Long> menuIds){ | ||
| return MenuResDto.MenuDeleteDto.builder() | ||
| .deletedMenuIds(menuIds) | ||
| .build(); | ||
| } | ||
|
|
||
| public static MenuResDto.MenuUpdateDto toUpdateDto(Menu menu, String updatedImageUrl){ | ||
| return MenuResDto.MenuUpdateDto.builder() | ||
| .menuId(menu.getId()) | ||
| .name(menu.getName()) | ||
| .description(menu.getDescription()) | ||
| .price(menu.getPrice()) | ||
| .category(menu.getMenuCategory()) | ||
| .imageUrl(updatedImageUrl) | ||
| .build(); | ||
|
|
||
| } | ||
|
|
||
| public static MenuResDto.SoldOutUpdateDto toSoldOutUpdateDto(Menu menu){ | ||
| return MenuResDto.SoldOutUpdateDto.builder() | ||
| .menuId(menu.getId()) | ||
| .isSoldOut(menu.isSoldOut()) | ||
| .build(); | ||
| } | ||
|
|
||
| } |
59 changes: 59 additions & 0 deletions
59
src/main/java/com/eatsfine/eatsfine/domain/menu/dto/MenuReqDto.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,59 @@ | ||
| package com.eatsfine.eatsfine.domain.menu.dto; | ||
|
|
||
| import com.eatsfine.eatsfine.domain.menu.enums.MenuCategory; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.*; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.util.List; | ||
|
|
||
| public class MenuReqDto { | ||
|
|
||
| public record MenuCreateDto( | ||
| @Valid | ||
| @NotNull | ||
| @Size(min = 1, message = "최소 1개 이상의 메뉴를 등록해야 합니다.") | ||
| List<MenuDto> menus | ||
| ){} | ||
|
|
||
|
|
||
| public record MenuDto( | ||
| @NotBlank(message = "메뉴 이름은 필수입니다.") | ||
| String name, | ||
|
|
||
| @Size(max = 500, message = "설명은 500자 이내여야 합니다.") | ||
| String description, | ||
|
|
||
| @NotNull(message = "가격은 필수입니다.") | ||
| @Min(value = 0, message = "가격은 0원 이상이어야 합니다.") | ||
| BigDecimal price, | ||
|
|
||
| @NotNull(message = "카테고리는 필수입니다.") | ||
| MenuCategory category, | ||
|
|
||
| String imageKey // 이미지는 선택 사항이므로 검증 없음 (nullable) | ||
| ){} | ||
|
|
||
|
|
||
| public record MenuDeleteDto( | ||
| @NotNull | ||
| @Size(min = 1, message = "삭제할 메뉴를 최소 1개 이상 선택해주세요.") | ||
| List<Long> menuIds | ||
| ){} | ||
|
|
||
| public record MenuUpdateDto( | ||
| @Size(min = 1, message = "메뉴 이름은 1글자 이상이어야 합니다.") | ||
| String name, | ||
| @Size(max = 500, message = "설명은 500자 이내여야 합니다.") | ||
| String description, | ||
| @Min(value = 0, message = "가격은 0원 이상이어야 합니다.") | ||
| BigDecimal price, | ||
| MenuCategory category, | ||
| String imageKey | ||
| ){} | ||
|
|
||
| public record SoldOutUpdateDto( | ||
| @NotNull | ||
| Boolean isSoldOut | ||
| ){} | ||
| } |
75 changes: 75 additions & 0 deletions
75
src/main/java/com/eatsfine/eatsfine/domain/menu/dto/MenuResDto.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,75 @@ | ||
|
|
||
| package com.eatsfine.eatsfine.domain.menu.dto; | ||
|
|
||
| import com.eatsfine.eatsfine.domain.menu.enums.MenuCategory; | ||
| import lombok.Builder; | ||
|
|
||
| import java.math.BigDecimal; | ||
| import java.util.List; | ||
|
|
||
| public class MenuResDto { | ||
|
|
||
| @Builder | ||
| public record ImageUploadDto( | ||
| String imageKey, // 메뉴 등록/수정 시 서버에 다시 보낼 키 | ||
| String imageUrl // 프론트엔드에서 즉시 미리보기를 위한 전체 URL | ||
| ){} | ||
|
|
||
|
|
||
| @Builder | ||
| public record ImageDeleteDto( | ||
| String deletedImageKey | ||
| ){} | ||
|
|
||
| @Builder | ||
| public record MenuCreateDto( | ||
| List<MenuDto> menus | ||
| ){} | ||
|
|
||
| @Builder | ||
| public record MenuDto( | ||
| Long menuId, | ||
| String name, | ||
| String description, | ||
| BigDecimal price, | ||
| MenuCategory category, | ||
| String imageUrl | ||
| ){} | ||
|
|
||
| @Builder | ||
| public record MenuDeleteDto( | ||
| List<Long> deletedMenuIds | ||
| ){} | ||
|
|
||
| @Builder | ||
| public record MenuUpdateDto( | ||
| Long menuId, | ||
| String name, | ||
| String description, | ||
| BigDecimal price, | ||
| MenuCategory category, | ||
| String imageUrl | ||
| ){} | ||
|
|
||
| @Builder | ||
| public record SoldOutUpdateDto( | ||
| Long menuId, | ||
| boolean isSoldOut | ||
| ){} | ||
|
|
||
| @Builder | ||
| public record MenuListDto( | ||
| List<MenuDetailDto> menus | ||
| ){} | ||
|
|
||
| @Builder | ||
| public record MenuDetailDto( | ||
| Long menuId, | ||
| String name, | ||
| String description, | ||
| BigDecimal price, | ||
| MenuCategory category, | ||
| String imageUrl, | ||
| boolean isSoldOut | ||
| ){} | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
10 changes: 10 additions & 0 deletions
10
src/main/java/com/eatsfine/eatsfine/domain/menu/exception/MenuException.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.eatsfine.eatsfine.domain.menu.exception; | ||
|
|
||
| import com.eatsfine.eatsfine.global.apiPayload.code.BaseErrorCode; | ||
| import com.eatsfine.eatsfine.global.apiPayload.exception.GeneralException; | ||
|
|
||
| public class MenuException extends GeneralException { | ||
| public MenuException(BaseErrorCode code) { | ||
| super(code); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
API path mismatch for menu image deletion.
PR objectives and summary list
DELETE /stores/{storeId}/menus/images, but the controller exposes/stores/{storeId}/menus/{menuId}/image. Please align the route (or update the API contract/docs) to avoid client breakage.🤖 Prompt for AI Agents