Skip to content

[FEAT]: 메뉴 관리 CRUD 구현 및 S3 수명주기 추가#87

Merged
twodo0 merged 27 commits intodevelopfrom
feature/menu-crud
Jan 30, 2026
Merged

[FEAT]: 메뉴 관리 CRUD 구현 및 S3 수명주기 추가#87
twodo0 merged 27 commits intodevelopfrom
feature/menu-crud

Conversation

@twodo0
Copy link
Contributor

@twodo0 twodo0 commented Jan 29, 2026

💡 작업 개요

GET /api/v1/stores/{storeId}/menus (메뉴 조회 API)

POST /api/v1/stores/{storeId}/menus (메뉴 등록 API)

DELETE /api/v1/stores/{storeId}/menus (메뉴 삭제 API)

POST /api/v1/stores/{storeId}/menus/images (메뉴 이미지 선 업로드 API)

DELETE /api/v1/stores/{storeId}/menus/images (메뉴 이미지 삭제 API)

PATCH /api/v1/stores/{storeId}/menus/{menuId} (메뉴 수정 API)

PATCH /api/v1/stores/{storeId}/menus/{menuId}/sold-out (품절 여부 변경 API)

  • 이미지 처리 과정에서 발생할 수 있는 데이터 정합성 문제와 비동기적 UX를 위해 [선 이미지 업로드 -> 후 메뉴 등록] 프로세스를 적용했습니다.
  • 위 과정에서 발생할 수 있는 S3 고아 이미지 삭제를 위해 수명 주기 관리를 위한 로직 및 S3에서 수명주기를 설정했습니다.
  • 처음 업로드 시에는 temp/menus 디렉토리에 저장 후, 메뉴 등록이나 업데이트 완료 시 정상 디렉토리로 파일을 옮깁니다. 만약 업로드만 하고 브라우저 종료 시 고아 이미지가 남게 되는데, 수명주기 세팅으로 매일 1번씩 temp에 남아있는 이미지들을 삭제하도록 했습니다.

  • @Valid, @RequestParam, @PathVariable에서 예외가 발생했을 때, 설정한 메시지가 함께 보이도록 전역 핸들러(GeneralExceptionAdvice)에 메서드를 추가했습니다.
  • Menu를 Soft Delete 처리하여 추후 메뉴 삭제 시 참조 무결성 문제가 일어나지 않도록 했습니다.
  • Soft Delete를 위해 Menu 엔티티에 deletedAt 필드를 추가하여, git pull 후 빌드 에러가 날 수도 있습니다! 만약 DB 관련 에러가 난다면 ddl-auto: update 설정해주시거나, QClass 에러가 난다면 ./gradlew clean compileJava 해주시면 됩니다.

  • 예약금 정책이 주문금액 x 예약금 비율로 변경됨에 따라 Store의 minPrice 필드 및 기존 예약금 계산 로직을 삭제했습니다.

✅ 작업 내용

  • 기능 개발
  • 버그 수정
  • 리팩토링
  • 주석/포맷 정리
  • 기타 설정

🧪 테스트 내용

  • 로컬에서 정상 빌드 테스트 완료
  • 모든 API가 S3에 연관되어 있어 배포 서버에 올라간 후 ec2 상에서 테스트 예정

📝 기타 참고 사항

  • ⚠️ Menu 엔티티에 deletedAt필드를 추가하여, git pull 후 빌드 에러가 날 수도 있습니다! 만약 DB 관련 에러가 난다면 ddl-auto: update 설정해주시거나, QClass 에러가 난다면 ./gradlew clean compileJava 해주시면 됩니다.

Closes #84

Summary by CodeRabbit

  • New Features

    • Complete menu management APIs (create/update/delete/list), image upload/delete, sold-out toggles, and DTOs for menu operations.
  • Bug Fixes

    • Improved validation responses for request/body/params and clearer forbidden response for non-owners.
    • Better image error handling for invalid keys/directories.
  • Chores

    • Soft-delete support for menus; S3 move/migration and image cleanup enhancements.
  • Stability

    • Store detail responses no longer include deposit/minPrice.

✏️ Tip: You can customize this high-level summary in your review settings.

@twodo0 twodo0 self-assigned this Jan 29, 2026
@twodo0 twodo0 added the feature label Jan 29, 2026
@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

Adds full menu management (CRUD + sold-out), image pre-upload and temp→perm S3 moves, Menu soft-delete, controllers, DTOs, converters, services (command/query), repository query additions, S3Service validations/move, validation handlers, and new success/error status codes.

Changes

Cohort / File(s) Summary
Controller & Converter
src/main/java/com/eatsfine/eatsfine/domain/menu/controller/MenuController.java, src/main/java/com/eatsfine/eatsfine/domain/menu/converter/MenuConverter.java
New REST endpoints for menu/image operations and mapping utilities converting domain Menu(s) to response DTOs.
Menu DTOs
src/main/java/com/eatsfine/eatsfine/domain/menu/dto/MenuReqDto.java, src/main/java/com/eatsfine/eatsfine/domain/menu/dto/MenuResDto.java
Added request/response record DTOs for create/update/delete/list/image; validation and builders included.
Menu Domain & Repo
src/main/java/com/eatsfine/eatsfine/domain/menu/entity/Menu.java, src/main/java/com/eatsfine/eatsfine/domain/menu/repository/MenuRepository.java
Menu gains soft-delete fields/annotations and update* methods; repository adds findByImageKey.
Menu Services (interfaces & impls)
src/main/java/com/eatsfine/eatsfine/domain/menu/service/...
Added MenuCommandService/MenuQueryService and implementations handling upload, temp→perm moveObject, bulk create/delete, updates, sold-out toggles, ownership checks, and S3 interactions.
S3 Service & Image statuses
src/main/java/com/eatsfine/eatsfine/global/s3/S3Service.java, src/main/java/com/eatsfine/eatsfine/domain/image/status/ImageErrorStatus.java
S3Service: added moveObject, stricter input validation, extractExtension made public; ImageErrorStatus: new _INVALID_IMAGE_KEY and _INVALID_S3_DIRECTORY.
Menu statuses & exception
src/main/java/com/eatsfine/eatsfine/domain/menu/status/MenuSuccessStatus.java, src/main/java/com/eatsfine/eatsfine/domain/menu/status/MenuErrorStatus.java, src/main/java/com/eatsfine/eatsfine/domain/menu/exception/MenuException.java
New enums for menu success/error codes and a domain MenuException.
Store entity, DTOs & repo
src/main/java/com/eatsfine/eatsfine/domain/store/entity/Store.java, src/main/java/com/eatsfine/eatsfine/domain/store/repository/StoreRepository.java, src/main/java/com/eatsfine/eatsfine/domain/store/dto/...
Removed minPrice/deposit logic, removed orphanRemoval=true on menus, added findByIdWithMenus JPQL that fetches non-deleted menus, and removed depositAmount from responses/convertors.
Global exception handling
src/main/java/com/eatsfine/eatsfine/global/apiPayload/handler/GeneralExceptionAdvice.java
Added handlers for MethodArgumentNotValidException and ConstraintViolationException to return BAD_REQUEST with validation messages.
Store error status
src/main/java/com/eatsfine/eatsfine/domain/store/status/StoreErrorStatus.java
Added _STORE_NOT_OWNER error constant.
Image error status tweak
src/main/java/com/eatsfine/eatsfine/domain/image/status/ImageErrorStatus.java
Two new error constants added and builders adjusted (isSuccess flipped to false in reason builders).

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant Controller as MenuController
    participant CommandSvc as MenuCommandService
    participant StoreRepo as StoreRepository
    participant S3 as S3Service
    participant MenuRepo as MenuRepository

    Client->>Controller: POST /api/v1/stores/{id}/menus/images (file)
    Controller->>CommandSvc: uploadImage(storeId, file)
    CommandSvc->>StoreRepo: find/verify store
    StoreRepo-->>CommandSvc: store
    CommandSvc->>S3: upload to temp/
    S3-->>CommandSvc: tempKey + url
    CommandSvc-->>Controller: ImageUploadDto

    Client->>Controller: POST /api/v1/stores/{id}/menus (create DTO with tempKey)
    Controller->>CommandSvc: createMenus(storeId, dto)
    CommandSvc->>S3: moveObject(tempKey, permKey)
    S3-->>CommandSvc: copy & delete result
    CommandSvc->>MenuRepo: save(menu entities)
    MenuRepo-->>CommandSvc: persisted menus
    CommandSvc-->>Controller: MenuCreateDto
    Controller-->>Client: 201 Created
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • [FEAT]: 메뉴 엔티티 설계 #82: earlier PR that introduced the Menu entity and Store–Menu mapping; this PR extends that work with soft-delete, update methods, and menu/image flows.

Suggested reviewers

  • SungMinju
  • CokaNuri
  • zerochani
  • sonjunkyu

Poem

🐰
I nudged temp keys gently toward their home,
Copied, cleared, and watched the orphaned roam,
Menus tuck in with deletedAt kept neat,
Images find doors where DB and S3 meet,
A rabbit hops — the merge is tidy and sweet.

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title '[FEAT]: 메뉴 관리 CRUD 구현 및 S3 수명주기 추가' clearly and specifically summarizes the two main changes: menu management CRUD implementation and S3 lifecycle policy addition.
Description check ✅ Passed The PR description comprehensively covers the template sections with detailed work overview, completed task checkboxes, testing notes, and important caveats about potential build issues.
Linked Issues check ✅ Passed The code implements all core requirements from issue #84: menu CRUD APIs, pre-upload image workflow, temp-to-permanent file movement, S3 lifecycle configuration, soft-delete via deletedAt field, and bulk operations with proper S3 cleanup.
Out of Scope Changes check ✅ Passed Changes to Store entity (minPrice removal and deposit calculation) align with referenced policy change from issue #84. All modifications directly support menu management objectives or related infrastructure improvements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/menu-crud

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a 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 (1)
src/main/java/com/eatsfine/eatsfine/domain/image/status/ImageErrorStatus.java (1)

25-42: Bug: isSuccess should be false for error statuses.

Both getReason() and getReasonHttpStatus() set isSuccess(true), but this is an error status enum. Error responses should indicate failure. Compare with MenuErrorStatus and StoreErrorStatus which correctly use isSuccess(false).

🐛 Proposed fix
     `@Override`
     public ErrorReasonDto getReason() {
         return ErrorReasonDto.builder()
-                .isSuccess(true)
+                .isSuccess(false)
                 .message(message)
                 .code(code)
                 .build();
     }

     `@Override`
     public ErrorReasonDto getReasonHttpStatus() {
         return ErrorReasonDto.builder()
                 .httpStatus(httpStatus)
-                .isSuccess(true)
+                .isSuccess(false)
                 .code(code)
                 .message(message)
                 .build();
     }
🤖 Fix all issues with AI agents
In `@src/main/java/com/eatsfine/eatsfine/domain/menu/entity/Menu.java`:
- Around line 20-21: Replace the deprecated Hibernate annotation `@Where` on the
Menu entity with `@SQLRestriction`: locate the Menu class where `@SQLDelete`(sql =
"UPDATE menu SET deleted_at = NOW() WHERE id = ?") and `@Where`(clause =
"deleted_at IS NULL") are applied, remove `@Where` and add `@SQLRestriction`(clause
= "deleted_at IS NULL") so the soft-delete filter uses the Hibernate 6.3+
compatible annotation while keeping the existing `@SQLDelete` behavior and the
deleted_at column semantics.

In
`@src/main/java/com/eatsfine/eatsfine/domain/menu/service/MenuCommandServiceImpl.java`:
- Around line 197-212: The deleteImage method allows deletion of orphan images
because it only verifies ownership when a Menu is found; to fix, call the
existing store ownership check (e.g., findAndVerifyStore(storeId)) at the start
of deleteImage so the caller is authorized before any deletion, keep the later
verifyMenuBelongsToStore(menu, storeId) for linked images, and only after
successful store verification proceed to menuRepository.findByImageKey(...) and
s3Service.deleteByKey(imageKey); update any imports/usage so findAndVerifyStore
is used consistently in MenuCommandServiceImpl.deleteImage.

In
`@src/main/java/com/eatsfine/eatsfine/domain/menu/service/MenuQueryServiceImpl.java`:
- Around line 25-39: The stream currently maps all menus returned by
storeRepository.findByIdWithMenus into MenuResDto.MenuDetailDto, which can
include soft-deleted menus because the repository's left join fetch may bypass
the `@Where` filter; either update the repository query used by findByIdWithMenus
to explicitly exclude deleted menus (e.g., "select s from Store s left join
fetch s.menus m where s.id = :id and m.deletedAt IS NULL") or filter them out in
MenuQueryServiceImpl by changing the mapping stream to filter(menu ->
menu.getDeletedAt() == null) (or the equivalent isDeleted/isDeletedAt check)
before mapping to MenuResDto.MenuDetailDto so
s3Service.toUrl(menu.getImageKey()) and other fields are only called for
non-deleted menus.

In
`@src/main/java/com/eatsfine/eatsfine/domain/menu/status/MenuSuccessStatus.java`:
- Around line 19-23: The enum constants in MenuSuccessStatus
(_MENU_CREATE_SUCCESS, _MENU_DELETE_SUCCESS, _MENU_UPDATE_SUCCESS,
_SOLD_OUT_UPDATE_SUCCESS, _MENU_LIST_SUCCESS) use inconsistent code strings
(e.g., "MENU2002", "MENU2003", "MENU2004", "MENU2005")—remove the extra digit so
all codes follow the same three-character pattern (e.g., "MENU200", "MENU201",
"MENU202", "MENU203", "MENU204") and update each constant's code string
accordingly to restore consistent formatting across the enum.

In
`@src/main/java/com/eatsfine/eatsfine/domain/store/repository/StoreRepository.java`:
- Around line 13-17: The JPQL in findByIdWithMenus currently fetches s.menus
with a JOIN FETCH but doesn't filter out soft-deleted Menu rows, so modify the
query used by the findByIdWithMenus method on Store to alias the menus (e.g.,
"left join fetch s.menus m") and add an explicit predicate "m.deletedAt IS NULL"
alongside "s.id = :id" so only non-deleted Menu entities are returned when
loading Store.menus.
🧹 Nitpick comments (12)
src/main/java/com/eatsfine/eatsfine/global/apiPayload/handler/GeneralExceptionAdvice.java (2)

39-49: Consider defensive check and aggregating all validation errors.

Line 47 uses .get(0) which assumes the error list is non-empty. While this is expected behavior when MethodArgumentNotValidException is thrown, a defensive check would be safer. Additionally, returning only the first error message forces users to fix issues one-by-one instead of seeing all problems at once.

♻️ Optional: defensive check + aggregate all messages
     protected ResponseEntity<Object> handleMethodArgumentNotValid(
             MethodArgumentNotValidException e,
             HttpHeaders headers,
             HttpStatusCode status,
             WebRequest request
     ) {
-        String errorMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
+        String errorMessage = e.getBindingResult().getAllErrors().stream()
+                .map(error -> error.getDefaultMessage())
+                .filter(msg -> msg != null)
+                .findFirst()
+                .orElse("Validation failed");
         return handleExceptionInternalFalse(e, ErrorStatus._BAD_REQUEST, headers, status, request, errorMessage);
     }

Or to show all errors:

-        String errorMessage = e.getBindingResult().getAllErrors().get(0).getDefaultMessage();
+        String errorMessage = e.getBindingResult().getAllErrors().stream()
+                .map(error -> error.getDefaultMessage())
+                .filter(msg -> msg != null)
+                .collect(Collectors.joining(", "));

51-57: Same defensive coding suggestion for ConstraintViolationException.

Line 55 uses .iterator().next() which will throw NoSuchElementException if violations set is empty. While this shouldn't occur in practice, consider adding a guard.

♻️ Optional: defensive check
     `@ExceptionHandler`
     public ResponseEntity<Object> validation(ConstraintViolationException e, WebRequest request) {
-        // 첫 번째 에러 메시지만 추출
-        String errorMessage = e.getConstraintViolations().iterator().next().getMessage();
+        String errorMessage = e.getConstraintViolations().stream()
+                .map(v -> v.getMessage())
+                .findFirst()
+                .orElse("Constraint violation");
         return handleExceptionInternalFalse(e, ErrorStatus._BAD_REQUEST, HttpHeaders.EMPTY, ErrorStatus._BAD_REQUEST.getHttpStatus(), request, errorMessage);
     }
src/main/java/com/eatsfine/eatsfine/domain/image/status/ImageErrorStatus.java (1)

12-17: Inconsistent naming convention for enum constants.

The older constants (EMPTY_FILE, INVALID_FILE_TYPE, S3_UPLOAD_FAILED) lack the underscore prefix, while the new ones use it (_IMAGE_NOT_FOUND, _INVALID_IMAGE_KEY). Consider aligning all constants to use the same convention for consistency with other error status enums in the codebase.

src/main/java/com/eatsfine/eatsfine/domain/menu/entity/Menu.java (1)

52-52: Consider adding explicit @Column annotation for consistency.

Other fields in this entity explicitly define their column names. Adding @Column(name = "deleted_at") would ensure consistency and make the mapping explicit, matching the column name referenced in @SQLDelete.

♻️ Suggested change
-    private LocalDateTime deletedAt;
+    `@Column`(name = "deleted_at")
+    private LocalDateTime deletedAt;
src/main/java/com/eatsfine/eatsfine/domain/menu/converter/MenuConverter.java (1)

8-9: Consider making this a proper utility class.

Utility classes with only static methods should typically be final with a private constructor to prevent instantiation.

♻️ Proposed refactor
-public class MenuConverter {
+public final class MenuConverter {
+
+    private MenuConverter() {
+        // Utility class - prevent instantiation
+    }
src/main/java/com/eatsfine/eatsfine/global/s3/S3Service.java (1)

60-75: Consider wrapping S3 exceptions for consistency.

The copyObject call can throw S3Exception which will propagate as-is, while other S3 operations in this class wrap exceptions in ImageException. Also, if copyObject succeeds but deleteByKey fails, a duplicate object will remain (orphan in destination). This is acceptable since it's a safe failure mode (no data loss), but worth documenting.

♻️ Optional: wrap S3Exception for consistent error handling
     public void moveObject(String sourceKey, String destinationKey) {
         if (sourceKey == null || destinationKey == null || sourceKey.isBlank() || destinationKey.isBlank()) {
             throw new ImageException(ImageErrorStatus._INVALID_IMAGE_KEY);
         }

-        // 1. 객체 복사
-        s3Client.copyObject(CopyObjectRequest.builder()
-                .sourceBucket(bucket)
-                .sourceKey(sourceKey)
-                .destinationBucket(bucket)
-                .destinationKey(destinationKey)
-                .build());
+        try {
+            // 1. 객체 복사
+            s3Client.copyObject(CopyObjectRequest.builder()
+                    .sourceBucket(bucket)
+                    .sourceKey(sourceKey)
+                    .destinationBucket(bucket)
+                    .destinationKey(destinationKey)
+                    .build());
+        } catch (Exception e) {
+            throw new ImageException(ImageErrorStatus.S3_UPLOAD_FAILED); // or a new S3_COPY_FAILED status
+        }

         // 2. 원본(임시) 객체 삭제
         deleteByKey(sourceKey);
     }
src/main/java/com/eatsfine/eatsfine/domain/menu/dto/MenuReqDto.java (2)

44-53: @Size(min=1) allows whitespace-only names.

For MenuUpdateDto.name, using only @Size(min = 1) permits strings like " " (whitespace only). If an empty or whitespace-only name should be rejected when provided, consider adding @NotBlank or using a custom validator that only applies when the value is non-null.

♻️ Option: Use Pattern to reject blank strings when provided
     public record MenuUpdateDto(
-            `@Size`(min = 1, message = "메뉴 이름은 1글자 이상이어야 합니다.")
+            `@Size`(min = 1, message = "메뉴 이름은 1글자 이상이어야 합니다.")
+            `@Pattern`(regexp = "^(?!\\s*$).+", message = "메뉴 이름은 공백만으로 구성될 수 없습니다.")
             String name,

Note: With this approach, null is still allowed (for partial updates where name is not being changed), but non-null values must contain at least one non-whitespace character.


38-42: Consider validating list elements are not null.

List<Long> menuIds could contain null elements. If this is a concern, you can add @NotNull to the element type.

♻️ Proposed change
     public record MenuDeleteDto(
             `@NotNull`
             `@Size`(min = 1, message = "삭제할 메뉴를 최소 1개 이상 선택해주세요.")
-            List<Long> menuIds
+            List<@NotNull Long> menuIds
     ){}
src/main/java/com/eatsfine/eatsfine/domain/menu/service/MenuQueryServiceImpl.java (1)

4-4: Remove unused import.

StoreResDto is imported but never used in this file.

🧹 Proposed fix
-import com.eatsfine.eatsfine.domain.store.dto.StoreResDto;
src/main/java/com/eatsfine/eatsfine/domain/menu/controller/MenuController.java (1)

61-67: Consider using POST for bulk delete instead of DELETE with request body.

Using @RequestBody with HTTP DELETE is technically allowed but can cause issues:

  • Some HTTP clients, proxies, and API gateways may strip the body from DELETE requests
  • It's not idiomatic REST design for bulk operations

A common alternative is POST /stores/{storeId}/menus/delete or POST /stores/{storeId}/menus:batchDelete for bulk delete operations.

src/main/java/com/eatsfine/eatsfine/domain/menu/service/MenuCommandServiceImpl.java (2)

130-155: Redundant null check inside ifPresent.

Line 137 checks if (newImageKey != null && !newImageKey.isBlank()), but since this is inside ifPresent, newImageKey is guaranteed to be non-null. Only the blank check is necessary.

🧹 Proposed simplification
         Optional.ofNullable(dto.imageKey()).ifPresent(newImageKey -> {
             // 1. [Safety] 변경된 내용이 없으면 스킵 (프론트에서 기존 키를 그대로 보낸 경우)
             if (newImageKey.equals(menu.getImageKey())) {
                 return;
             }

             // 2. 새로운 이미지가 있다면 영구 경로로 이동 (Temp -> Perm)
-            if (newImageKey != null && !newImageKey.isBlank()) {
+            if (!newImageKey.isBlank()) {

114-116: Unused store variable.

The store variable is assigned on line 115 but never used in updateMenu. If store verification is the only purpose, consider calling findAndVerifyStore(storeId) without assigning the result, or suppress the warning if intentional.

🧹 Proposed fix
-        Store store = findAndVerifyStore(storeId);
+        findAndVerifyStore(storeId);

The same pattern applies to updateSoldOutStatus (line 164) and uploadImage (line 184).

Comment on lines +19 to +23
_MENU_CREATE_SUCCESS(HttpStatus.CREATED, "MENU202", "메뉴 생성 성공"),
_MENU_DELETE_SUCCESS(HttpStatus.OK, "MENU2002", "메뉴 삭제 성공"),
_MENU_UPDATE_SUCCESS(HttpStatus.OK, "MENU2003", "메뉴 수정 성공"),
_SOLD_OUT_UPDATE_SUCCESS(HttpStatus.OK, "MENU2005", "품절 여부 변경 성공"),
_MENU_LIST_SUCCESS(HttpStatus.OK, "MENU2004", "메뉴 조회 성공"),
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Inconsistent status code format.

The codes MENU2002, MENU2003, MENU2004, MENU2005 appear to have an extra digit compared to the earlier codes (MENU200, MENU201, MENU202). This looks like a typo and could cause confusion in API documentation or client-side error handling.

🔧 Proposed fix for consistency
-    _MENU_DELETE_SUCCESS(HttpStatus.OK, "MENU2002", "메뉴 삭제 성공"),
-    _MENU_UPDATE_SUCCESS(HttpStatus.OK, "MENU2003", "메뉴 수정 성공"),
-    _SOLD_OUT_UPDATE_SUCCESS(HttpStatus.OK, "MENU2005", "품절 여부 변경 성공"),
-    _MENU_LIST_SUCCESS(HttpStatus.OK, "MENU2004", "메뉴 조회 성공"),
+    _MENU_DELETE_SUCCESS(HttpStatus.OK, "MENU203", "메뉴 삭제 성공"),
+    _MENU_UPDATE_SUCCESS(HttpStatus.OK, "MENU204", "메뉴 수정 성공"),
+    _SOLD_OUT_UPDATE_SUCCESS(HttpStatus.OK, "MENU206", "품절 여부 변경 성공"),
+    _MENU_LIST_SUCCESS(HttpStatus.OK, "MENU205", "메뉴 조회 성공"),
📝 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.

Suggested change
_MENU_CREATE_SUCCESS(HttpStatus.CREATED, "MENU202", "메뉴 생성 성공"),
_MENU_DELETE_SUCCESS(HttpStatus.OK, "MENU2002", "메뉴 삭제 성공"),
_MENU_UPDATE_SUCCESS(HttpStatus.OK, "MENU2003", "메뉴 수정 성공"),
_SOLD_OUT_UPDATE_SUCCESS(HttpStatus.OK, "MENU2005", "품절 여부 변경 성공"),
_MENU_LIST_SUCCESS(HttpStatus.OK, "MENU2004", "메뉴 조회 성공"),
_MENU_CREATE_SUCCESS(HttpStatus.CREATED, "MENU202", "메뉴 생성 성공"),
_MENU_DELETE_SUCCESS(HttpStatus.OK, "MENU203", "메뉴 삭제 성공"),
_MENU_UPDATE_SUCCESS(HttpStatus.OK, "MENU204", "메뉴 수정 성공"),
_SOLD_OUT_UPDATE_SUCCESS(HttpStatus.OK, "MENU206", "품절 여부 변경 성공"),
_MENU_LIST_SUCCESS(HttpStatus.OK, "MENU205", "메뉴 조회 성공"),
🤖 Prompt for AI Agents
In
`@src/main/java/com/eatsfine/eatsfine/domain/menu/status/MenuSuccessStatus.java`
around lines 19 - 23, The enum constants in MenuSuccessStatus
(_MENU_CREATE_SUCCESS, _MENU_DELETE_SUCCESS, _MENU_UPDATE_SUCCESS,
_SOLD_OUT_UPDATE_SUCCESS, _MENU_LIST_SUCCESS) use inconsistent code strings
(e.g., "MENU2002", "MENU2003", "MENU2004", "MENU2005")—remove the extra digit so
all codes follow the same three-character pattern (e.g., "MENU200", "MENU201",
"MENU202", "MENU203", "MENU204") and update each constant's code string
accordingly to restore consistent formatting across the enum.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In
`@src/main/java/com/eatsfine/eatsfine/domain/menu/controller/MenuController.java`:
- Around line 73-79: The controller route for menu image deletion in
MenuController currently uses
`@DeleteMapping`("/stores/{storeId}/menus/{menuId}/image") in the deleteMenuImage
method, but the PR and API spec expect DELETE /stores/{storeId}/menus/images;
update the controller route to match the agreed contract (or update the API
spec) — either change the mapping string on deleteMenuImage to
"/stores/{storeId}/menus/images" and adjust method parameters (remove or retain
`@PathVariable` Long menuId as per the contract) and any service method signature
calls (menuCommandService.deleteMenuImage) accordingly, and then update related
API docs/tests to reflect the chosen path.

In
`@src/main/java/com/eatsfine/eatsfine/domain/menu/service/MenuCommandServiceImpl.java`:
- Around line 39-60: The code in MenuCommandServiceImpl allows any non-blank
imageKey to be moved via s3Service.moveObject, enabling arbitrary object
moves/deletes; before calling s3Service.moveObject and menu.updateImageKey,
validate that tempImageKey begins with a strict, expected temp prefix (for
example "temp/menus/" or whatever convention your upload flow uses), otherwise
skip the move or throw a validation exception; apply the same validation in the
other occurrence (lines ~130-154) so only keys with the approved temp path are
accepted and moved to the permanent key.

Comment on lines +73 to +79
@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));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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
In
`@src/main/java/com/eatsfine/eatsfine/domain/menu/controller/MenuController.java`
around lines 73 - 79, The controller route for menu image deletion in
MenuController currently uses
`@DeleteMapping`("/stores/{storeId}/menus/{menuId}/image") in the deleteMenuImage
method, but the PR and API spec expect DELETE /stores/{storeId}/menus/images;
update the controller route to match the agreed contract (or update the API
spec) — either change the mapping string on deleteMenuImage to
"/stores/{storeId}/menus/images" and adjust method parameters (remove or retain
`@PathVariable` Long menuId as per the contract) and any service method signature
calls (menuCommandService.deleteMenuImage) accordingly, and then update related
API docs/tests to reflect the chosen path.

Comment on lines 39 to 60
List<Menu> menus = dto.menus().stream()
.map(menuDto -> {
Menu menu = Menu.builder()
.name(menuDto.name())
.description(menuDto.description())
.price(menuDto.price())
.menuCategory(menuDto.category())
.build();

// 임시 이미지 키가 있는 경우, 영구 경로로 이동하고 키를 설정
String tempImageKey = menuDto.imageKey();
if (tempImageKey != null && !tempImageKey.isBlank()) {
// 1. 새로운 영구 키 생성
String extension = s3Service.extractExtension(tempImageKey);
String permanentImageKey = "stores/" + storeId + "/menus/" + UUID.randomUUID() + extension;

// 2. S3에서 객체 이동 (임시 -> 영구)
s3Service.moveObject(tempImageKey, permanentImageKey);

// 3. 엔티티에 영구 키 저장
menu.updateImageKey(permanentImageKey);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Block arbitrary S3 object move/delete by validating temp image keys.
Right now any non-blank imageKey can be moved/deleted. If a client supplies a permanent key (or another object key) it can delete unrelated objects. Enforce a strict temp-path prefix (e.g., temp/menus/) before moving.

🔒 Suggested fix (validate temp keys before move)
 String tempImageKey = menuDto.imageKey();
 if (tempImageKey != null && !tempImageKey.isBlank()) {
+    validateTempImageKey(tempImageKey);
     // 1. 새로운 영구 키 생성
     String extension = s3Service.extractExtension(tempImageKey);
     String permanentImageKey = "stores/" + storeId + "/menus/" + UUID.randomUUID() + extension;
@@
-        if (newImageKey != null && !newImageKey.isBlank()) {
+        if (!newImageKey.isBlank()) {
+            validateTempImageKey(newImageKey);
             String extension = s3Service.extractExtension(newImageKey);
             String permanentImageKey = "stores/" + storeId + "/menus/" + UUID.randomUUID() + extension;
             s3Service.moveObject(newImageKey, permanentImageKey);
@@
+    private void validateTempImageKey(String key) {
+        if (key == null || key.isBlank() || !key.startsWith("temp/menus/")) {
+            throw new ImageException(ImageErrorStatus._INVALID_IMAGE_KEY);
+        }
+    }

Also applies to: 130-154

🤖 Prompt for AI Agents
In
`@src/main/java/com/eatsfine/eatsfine/domain/menu/service/MenuCommandServiceImpl.java`
around lines 39 - 60, The code in MenuCommandServiceImpl allows any non-blank
imageKey to be moved via s3Service.moveObject, enabling arbitrary object
moves/deletes; before calling s3Service.moveObject and menu.updateImageKey,
validate that tempImageKey begins with a strict, expected temp prefix (for
example "temp/menus/" or whatever convention your upload flow uses), otherwise
skip the move or throw a validation exception; apply the same validation in the
other occurrence (lines ~130-154) so only keys with the approved temp path are
accepted and moved to the permanent key.

@twodo0 twodo0 marked this pull request as draft January 30, 2026 07:14
@twodo0 twodo0 force-pushed the feature/menu-crud branch from 307149f to 8205906 Compare January 30, 2026 12:54
@twodo0 twodo0 marked this pull request as ready for review January 30, 2026 12:57
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Fix all issues with AI agents
In
`@src/main/java/com/eatsfine/eatsfine/domain/menu/service/MenuCommandServiceImpl.java`:
- Around line 55-59: The S3 operations (s3Service.moveObject and
s3Service.deleteByKey) are executed immediately in createMenus, updateMenu, and
deleteMenus and must be deferred until the surrounding DB transaction
successfully commits; change each place where you currently call
s3Service.moveObject(...) or s3Service.deleteByKey(...) to instead register a
post-commit synchronization via
TransactionSynchronizationManager.registerSynchronization(...) that performs the
S3 move/delete after commit, while keeping the in-transaction updates such as
menu.updateImageKey(...), saveAll(...), updateMenu DB changes, and
deleteAll(...) intact so DB state is persisted before the actual S3 side-effects
run. Ensure the registered callback captures the needed keys (tempImageKey,
permanentImageKey) and handles exceptions/logging so failures in the post-commit
step do not affect the committed transaction.

In `@src/main/java/com/eatsfine/eatsfine/domain/store/entity/Store.java`:
- Around line 93-97: The removeMenu() logic nulls the non-nullable FK by calling
menu.assignStore(null) which will violate constraints because the `@OneToMany`
List<Menu> menus lacks orphanRemoval=true; either add orphanRemoval = true to
the menus association on the Store entity (i.e., the field annotated
`@OneToMany`(mappedBy = "store", cascade = CascadeType.ALL) named menus) to allow
removing/menu soft-delete via JPA, or remove the menu.assignStore(null) call
from the removeMenu()/removeMenus() methods and instead delete via the Menu
repository so the `@SQLDelete` soft-delete executes; update the
Store.removeMenu(s) implementations accordingly to match the pattern used by
businessHours and tableImages.

In
`@src/main/java/com/eatsfine/eatsfine/domain/store/repository/StoreRepository.java`:
- Around line 13-18: The JPQL in StoreRepository's `@Query` using "left join fetch
s.menus m" combined with "where m.deletedAt IS NULL" turns the LEFT JOIN into an
INNER JOIN and drops stores with no menus; update the predicate in the Query so
it preserves left-join semantics by allowing null joined rows — e.g. change the
WHERE to include either m.deletedAt IS NULL OR m.id IS NULL (or equivalent null
check) for s.menus/m.deletedAt, or alternatively remove the fetch join and load
menus separately or use a Hibernate WITH clause if you prefer vendor-specific
syntax.

In `@src/main/java/com/eatsfine/eatsfine/global/s3/S3Service.java`:
- Around line 60-75: The moveObject method currently allows sourceKey ==
destinationKey which causes a no-op copy followed by deleteByKey(sourceKey); add
an explicit guard at the start of moveObject to detect equals (use String.equals
or Objects.equals) and throw an ImageException (e.g.,
ImageErrorStatus._INVALID_IMAGE_KEY or a new specific status) when sourceKey
equals destinationKey; this check should be placed alongside the existing
null/blank validation before calling s3Client.copyObject and deleteByKey to
prevent accidental deletion.

@twodo0 twodo0 merged commit 635014d into develop Jan 30, 2026
2 checks passed
CokaNuri added a commit that referenced this pull request Jan 31, 2026
* Initial commit

* [CHORE]: CI 스크립트 작성

* [CHORE]: CI 배포 프로필 구성

* [FEAT]: HealthCheck API 구현 및 테스트 추가

* [FEAT]: Docker 빌드 환경 구성

* [FEAT]: CD 배포 파이프라인 구성

* [FIX]: 도커 이미지 풀링 및 컨테이너 실행 부분 수정

* [FIX]: 헬스체크 실패 방지를 위한 앱 기동 대기 추가

* [FIX]: invalid duration 문자열 추가

* [FEAT]: 의존성 추가

* [FIX]: 대문자를 소문자로 변경

* [FIX]: 경로 대소문자 에러 수정

* [FIX]: CI 파이프라인 수정

* [FIX]: CI 파이프라인 원래대로 다시 수정

* [FIX]: 스크립트 수정

* [FIX]: 도커파일 수정

* [FIX]: 도커파일 수정

* [chore] 빌드 및 패키지 설정 변경

* [chore] 빌드 및 패키지 설정 변경

* [chore] 빌드 및 패키지 설정 변경

* [chore] 빌드 및 패키지 설정 변경

* [FEAT]: 로컬 개발 환경 구성을 위한 Docker Compose 및 설정 추가

* [FEAT]: BaseEntity 추가

* [FEAT]: 예약 도메인 엔티티(Booking) 기본 설정

* [FIX]: Entity에 식별자 추가

* [FIX]: 불필요한 import문 제거

* [FEAT]: BusinessHours 요일 enum 정의

* [FEAT]: BusinessHours 엔티티 초기 세팅

* [FEAT]: Region 엔티티 설계

* [FEAT]: TableImage 엔티티 초기 세팅

* [REFACTOR]: BusinessHours isClosed 필드를 isHoliday로 변경

* [REFACTOR]: BusinessHours Store 연관관계 적용 및 BaseEntity 상속

* [FEAT]: 가게 승인 상태 enum 추가

* [FEAT]: 가게 카테고리 enum 추가

* [FEAT]: Store 엔티티 설계

* [REFACTOR]: TableImage Store 연관관계 적용 및 BaseEntity 상속

* [FEAT]: StoreRepository 세팅

* [FEAT]: TableImageRepository 세팅

* [FEAT]: BusinessHoursRepository 세팅

* [FEAT]: RegionRepository 세팅

* [CHORE]: yml 파일 수정

* [REFACTOR]: Store와 BusinessHours 양방향 연관관계 및 편의 메서드 추가

* [FIX]: StoreTable 연관관계 주석처리

* [REFACTOR]: Store와 TableImage 양방향 연관관계 및 편의 메서드 추가

* [REFACTOR]: Store-TableImage 양방향 연관관계 삭제 편의 메서드 추가 및 생명주기 관리 설정

* [REFACTOR]: ApiResponse 실패 응답을 BaseErrorCode 기반으로 통합

* [FEAT]: HTTPS 적용 및 NGINX Blue-Green 무중단 배포 전환 로직 수정

* [FEAT]: HTTPS 적용 및 NGINX Blue-Green 무중단 배포 전환 로직 수정

* [FEAT] 예약 가능 시간대 및 테이블 조회 DTO 개발

* [FEAT] 예약 가능 시간대,테이블 조회 컨트롤러 개발 및 스웨거 설정

* [FEAT] BookingTable 엔티티 개발

* [FIX] Booking 엔티티 수정 (BookingTable 연관관계 고려)

* [FEAT] BookingRepository- findReservedTableIds 메서드 구현

* [FIX] Store 엔티티 businessHours,tableImages 필드에 @Builder.Default 애노테이션 추가

* [FEAT] StoreTable 엔티티 기본 설정

* [FEAT] 예약 가능 시간 조회 시 '나눠 앉기' 동의 여부 로직 추가

* [FEAT] 테이블 유형 enum 추가

* [FEAT] TableLayout 엔티티 기본 설정 및 Store, StoreTable 연관관계 수정

* [FEAT] BusinessHours의 DayofWeek을 커스텀 enum이 아닌java.time 으로 변경

* [FIX] BusinessHours의 DayofWeek @Enumerated추가

* [FIX] rows 필드 db에서 오류 -> lows로 수정

* [FEAT] GET /api/v1/stores/{storeId}/bookings/available-times 구현

* [FEAT] GeneralException 구현

* [FIX] 로컬 개발 환경에서 https->http 사용

* [FEAT] User 엔티티 기본 설정

* [FEAT]: 식당 상세 조회 DTO 및 BusinessHours 변환 로직 추가

* [FEAT]: StoreSuccessStatus, StoreErrorStatus 추가

* [FEAT]: GeneralException(프로젝트 exception) 세팅

* [FEAT]: StoreException 세팅

* [FEAT]: 식당 상세 조회 API (Controller/Service) 구현

* [FEAT]: 전역 예외 핸들러(GeneralExceptionAdvice) 구현

* [REFACTOR]: BusinessHours 응답 DTO 및 컨버터 휴무 처리 단순화

* [FEAT] 예약 시간대 설정 후 테이블 조회 DTO 개발

* [FEAT] StoreTable에 Table의 위치 필드 추가

* [FEAT] 시간대 설정 후 예약 가능한 테이블 조회 서비스 구현

* [FEAT] 테이블 분리 허용 여부에 따라 테이블 조회 로직 수정

* [FEAT] GET /api/v1/stores/{storeId}/bookings/available-tables 구현

* [CHORE] Swagger 버전 변경 2.3.0 -> 2.8.1

* [FEAT] BookingException 및 ErrorStatus 추가

* [FEAT] 에러 핸들러 구현

* [FEAT] BookingController에 BookingException 포함

* 예전 테스트 코드 제거

* [REFACTOR] : BookingErrorStatus를 공통 ErrorStatus에서 Booking 도메인으로 분리

* [REFACTOR]: 전역 예외 핸들러 제거

* [REFACTOR]: BusinessHoursResDto.summary 응답 시간 포맷(HH:mm) 적용

* [REFACTOR]: BusinessHours 응답 DTO isClosed로 필드명 변경

* [FIX]: NGINX Blue-Green 자동 전환 배포 로직 수정

* [FIX]: NGINX Blue-Green 자동 전환 배포 로직 수정 (다시 복구)

* Update database password in application-local.yml

* [FEAT] : 예약 생성 DTO 개발

* [FIX]: NGINX Blue-Green 존재 여부 확인 후 stop 하도록 로직 수정

* [REFACTOR] : 예약 조회 DTO, 컨트롤러 구조 변경

* [FEAT] : BookingErrorStatus 추가

* [FEAT] : CreateBookingDTO에 isSplitAccepted 필드 추가

* [FEAT] : createdAt JPA 자동 설정 가능하게 애노테이션 추가

* [FEAT] : 비관적 락 적용해 테이블 조회하는 메서드 개발

* [FIX] : Post 방식 API ModelAttribute->RequestBody 변경

* [FIX] : boolingTables 필드에 @Builder.Default 애노테이션 추가

* [FEAT] : 예약 생성 비즈니스 로직 개발

* [FIX]: 배포 대상 판단 로직을 nginx upstream 기준으로 수정

* [FIX]: deploy.yml 로직 약간수정

* [FIX]: 기존으로 복구 및 로그 추가

* [FIX]: 기존으로 복구

* [FIX]: 배포 스크립트 Nginx 리로드 경로

* [FIX]: HTTPS 환경에서 깨지던 blue-green 배포 판단 로직 수정

* [FIX]: 로직 순서 수정

* [FIX]: 로직 순서 수정

* [FIX]: 코드 약간 수정

* [FIX]: 코드 약간 수정

* [FIX]: 코드 약간 수정

* [FIX]: 코드 약간 수정

* [FIX]: 코드 약간 수정

* [BUILD]: QueryDSL 설정 추가 (.gitignore, build.gradle)

* [CHORE] : DB_PASSWORD 환경변수로 관리

* [FEAT] : UserRepository 임시 사용

* [FEAT] : BookingErrorStatus 추가

* [FEAT] : 결제 요청 관련 DTO 개발

* [FEAT] : StoreErrorStatus 추가

* [REFACTOR] : Swagger 테스트용 @ParameterObject 애노테이션 추가

* [REFACTOR] : 영업시간 정보 없을 시 예외 처리 로직 추가

* [FEAT] : Booking 엔티티에 depositAmount 필드 추가 및 confirm 메서드 추가

* [FIX] : 예약 상태가 PENDING 혹은 CONFIRMED인 테이블을 조회하도록 수정

* [FEAT] : 결제 완료 처리 API 개발

* [FIX] : JpaAuditing 설정 변경

* [FEAT] Swagger 운영 도메인 등록

* [FEAT]: 결제 요청 API 구현 및 Booking 연동

* [FEAT]: 가게 등록 및 Region 예외 추가

* [REFACTOR]: Store 엔티티 초기 구조 보정

* [FEAT]: StoreConverter 추가

* [FEAT]: Store/Region 성공 상태 코드 추가

* [FEAT]: 가게 등록 및 영업시간 요청 처리

* [FEAT]: 영업시간 검증 및 예외처리

* [REFACTOR]: 영업시간 및 이미지 필드 수정

* [FEAT]: 가게 검색 기본 구조 추가

* [FEAT]: QueryDSL 기반 가게 검색 구현

* [FEAT]: 실시간 영업 여부 체크 로직 구현

* [FEAT]: 가게 검색에 키워드 검색, 지역 필터 기능 추가

* [FEAT]: 가게별 예약금 로직 추가

* [FEAT]: 가게 테이블(StoreTable) 도메인 엔티티 설계 (#51)

- StoreTable: Soft Delete 적용, 좌석 인원(min/max) 필드 세분화
- TableLayout: Soft Delete 적용, 배치도 활성/비활성 로직, Cascade 옵션 적용
- TableBlock: 예약 차단 시간 관리를 위한 엔티티 설계
- SlotStatus: 예약 시간대 상태 관리를 위한 Enum 및 DTO 구조 설계
- Common: DB 스키마 정합성을 위한 @column 제약조건 명시

* [FIX]: 간편결제만 놔두기

* [FEAT]: 토스페이먼츠 결제위젯 연동 및 승인 프로세스 구현

* [FEAT]: 독립적인 테스트 환경 구성을 위한 H2 DB 및 설정 추가해서 빌드 실패 해결

* [FIX]: username 삭제

* Feature/payment (#54)

* [FIX]: 간편결제만 놔두기

* [FEAT]: 토스페이먼츠 결제위젯 연동 및 승인 프로세스 구현

* [FEAT]: 독립적인 테스트 환경 구성을 위한 H2 DB 및 설정 추가해서 빌드 실패 해결

* [FIX]: username 삭제

* [FEAT]: 식당 기본 정보, 영업시간 수정, 브레이크타임 설정 API 개발  (#55)

* [FEAT]: 가게 기본 정보 수정 DTO 추가

* [FEAT]: StoreSuccessStatus에 가게 기본 정보 수정 성공 응답 추가

* [FEAT]: Store 엔티티에 가게 기본 정보 수정 메서드 추가

* [FEAT]: 가게 기본 정보 수정 로직 개발

* [FEAT]: StoreController에 가게 기본 정보 수정 API 추가

* [FEAT]: 영업시간 검증 메서드(생성용/수정용) 분리

* [FEAT]: 영업시간 수정 DTO 추가

* [FEAT]: 응답 컨버터 및 상태코드 추가

* [FEAT]: 영업 시간 수정 로직 개발

* [REFACTOR]: 영업시간 변경 응답 7일 모두 내려주도록 수정 및 필드명 변경

* [FEAT]: 브레이크타임 설정 DTO 및 응답코드 추가

* [FEAT]: 요청된 브레이크타임 유효성 검증 로직 개발

* [FEAT]: 브레이크타임 설정 로직 구현

* [FEAT]: 상세조회 응답에 브레이크타임 추가

* [FEAT]: 결제 승인 결과(성공/실패) 처리

* [REFACTOR]: Payment 예외 처리 리팩토링

* [FEAT]: 결제 취소(환불) API 구현

* [FEAT]: 결제 내역 조회 및 상세 조회 API 구현

* [FEAT]: 테이블 배치도 생성, 조회 API 구현 (#56)

* [REFACTOR]: TableLayout SQLDelete 쿼리문 수정 및 Builder.Default 추가

* [FEAT]: TableLayoutException 세팅

* [FEAT]: TableLayout 성공/실패 응답코드 추가

* [FEAT]: Controller에 배치도 생성 API 추가

* [FEAT]: 배치도 DTO, Converter 추가

* [FEAT]: 테이블 배치도 생성 로직 개발

* [REFACTOR]: StoreTable SQLDelete 쿼리문 수정

* [REFACTOR]: TableLayout - StoreTable 연관관계 Cascade.REMOVE 추가

* [FEAT]: 테이블 배치도 조회 API 추가

* [FEAT]: 테이블 배치도 조회 로직 개발

* [REFACTOR]: 테이블 배치도 조회 응답 DTO, Converter에 reviewCount 필드 추가

* [FEAT]: 가게 이미지, 테이블 이미지(S3) 업로드/조회/삭제 기능 구현 (#61)

* [REFACTOR]: url -> key로 변수명 변경 및 imageOrder 필드 추가

* [BUILD]: AWS S3 관련 의존성 추가

* [FEAT]: S3Config, S3Service 추가

* [FEAT]: 이미지 업로드 관련 예외 및 에러 상태코드 추가

* [FEAT]: 가게 메인 이미지 등록 응답 DTO 및 성공 상태코드 추가

* [FEAT]: 가게 메인 이미지 등록 로직 구현

* [FEAT]: 가게 매인 이미지 조회 응답 DTO 및 성공 상태코드 추가

* [FEAT]: 가게 메인 이미지 조회 로직 구현

* [FEAT]: 테이블 이미지 등록 응답 DTO 및 성공 상태코드 추가

* [FEAT]: 가게 테이블 이미지(TableImage) 등록 로직 구현

* [FEAT]: 가게 테이블 이미지(TableImage) 조회 로직 구현

* [REFACTOR]: 테이블 이미지 등록 및 조회 성공 코드 수정

* [FEAT]: 테이블 이미지 삭제 응답 DTO 및 성공 상태코드 추가

* [FEAT]: 가게 테이블 이미지(TableImage) 삭제 로직 구현

* [CHORE]: application.yml에 AWS S3 설정 추가

* [FIX]: StoreSuccessStatus 충돌 해결

* [FIX]: 에러 상태코드 중복 수정

* [CHORE]: application-test.yml에 aws s3 설정 추가

* [FIX]: S3 설정 위치 수정

* [REFACTOR]: AWS S3 설정값 환경변수 처리

* [FEAT]: 가게 테이블 생성 API 구현 (#62)

* [FEAT]: StoreTableException 세팅

* [FEAT]: 테이블 생성 성공/실패 응답코드 추가

* [FEAT]: StoreTableController 가게 테이블 생성 API 및 명세 추가

* [FEAT]: 테이블 생성 DTO, Converter 추가

* [FEAT]: 테이블 생성 검증 로직 추가

* [FEAT]: 테이블 생성 검증 서비스 로직 추가

* [FIX]: 무중단 배포 구조 스크립트에서 단일 컨테이너 배포 구조 스크립트로 변경

* [FIX]: EC2 SSH 배포 방식 수정 및 GitHub Actions 안정화

* [FIX]: GitHub Actions SSH 포트 임시 오픈/회수 로직 정리

* [FIX]: 배포 워크플로우 main으로 변경

* [CHORE]: 불필요 도커 이미지 및 컨테이너 정리

* [FEAT]: 테이블 예약 시간대 목록 조회, 관리 API 구현 (#69)

* [REFACTOR]: 테이블 슬롯 조회 생성/실패 응답 코드 추가 및 에러코드 통일

* [FEAT]: StoreTableController에 가게 슬롯 조회 API 및 명세 추가

* [FEAT]: TableBlockRepository 생성

* [FEAT]: BookingRepository에 JPQL 메소드 추가

* [FEAT]: 테이블 슬롯 조회 응답 DTO, Converter 추가

* [FEAT]: 테이블 슬롯 계산, 검증 로직 추가

* [FEAT]: 테이블 슬롯 조회 서비스 로직 추가

* [FEAT]: 테이블 슬롯 상태 변경 성공/실패 에러 핸들러 추가

* [FEAT]: TableBlockController에 테이블 슬롯 상태 변경 API 및 명세 추가

* [FEAT]: TableBlockRepository 특정 테이블 차단 내역 조회 메소드 추가

* [FEAT]: BookingRepository 테이블 예약 여부 조회 메소드 추가

* [FEAT]: 테이블 상태 변경 요청/응답 DTO, Converter 추가

* [FEAT]: 테이블 상태 변경시 검증 로직 추가

* [FEAT]: 테이블 상태 변경 서비스 로직 추가

* [FEAT]: 가게 등록 시 사업자번호 검증 추가 및 Region 엔티티 구조 변경 (#75)

* [REFACTOR]: 주소(Region) 저장 체계 변경

* [FEAT]: 사업자번호 검증 API 관련 의존성 및 설정파일 추가

* [FEAT]: 사업자번호 검증 로직 추가

* [FEAT]: 가게 등록 요청 시 사업자번호 검증부 추가

* [REFACTOR]: WebClient -> RestClient로 변경

* [FEAT]: Real, Mock으로 사업자번호 검증 분리

* [CHORE]: 사업자번호 검증 성공 로그 추가

* [FEAT]: application.yml에 사업자번호 검증 api key 설정부 추가

* [REFACTOR]: 사업자번호 검증 코드 패키지명 변경

* [FIX]: test의 application.yml에 더미 키값 추가

* [CHORE]: 멈춰있는 컨테이너 전부 삭제 명령어 제거

* [FEAT]: DTO 분리 및 필드명 변경

* [REFACTOR]: 결제 승인 로직 개선 및 DTO 적용

* [REFACTOR]: / 붙여줌

* [FEAT]: 메뉴 엔티티 설계 (#82)

* [FEAT]: 가게 테이블 상세 조회/수정/삭제 API 구현 (#77)

* [FEAT]: 테이블 상세 정보 조회 성공 응답 코드 추가

* [FEAT]: 테이블 상세 정보 조회 api 및 명세 추가

* [FEAT]: 테이블 상세 정보 조회 응답 DTO, Converter 추가

* [FEAT]: 테이블 상세 정보 조회 서비스 로직 추가

* [FEAT]: 테이블 정보 수정 성공/실패 응답 코드 추가

* [FEAT]: 테이블 정보 수정 api 및 명세 추가

* [FEAT]: StoreTable 엔티티 편의 메소드 추가

* [FEAT]: 테이블 정보 수정 요청/응답 DTO, Converter 추가

* [FEAT]: 테이블 정보 수정 서비스 로직 구현
- 테이블 번호 수정
- 테이블 좌석 수 변경
- 테이블 유형 변경

* [FEAT]: 테이블 삭제 성공/실패 응답 코드 추가

* [FEAT]: 테이블 삭제 api 및 명세 추가

* [FEAT]: 테이블 삭제 응답 DTO, Converter 추가

* [FEAT]: 테이블 삭제 서비스 로직 구현
- 삭제 시 현재 시간을 기준으로 예약 존재 여부 확인
- 삭제 시 soft delete

* 가게 개별 테이블 이미지 등록/삭제 API 구현 (#83)

* [FEAT]: 테이블 이미지 업로드 API 및 명세 추가

* [FEAT]: StoreTable 이미지 업로드 메소드 추가

* [FEAT]: 테이블 이미지 업로드 응답 DTO, Converter 추가

* [FEAT]: 테이블 이미지 업로드 서비스 로직 추가

* [REFACTOR]: 테이블 상세 조회 시 테이블 이미지 URL을 포함하도록 수정

* [FEAT]: 테이블 이미지 삭제 API 및 명세 추가

* [FEAT]: StoreTable 테이블 이미지 삭제 메소드 추가

* [FEAT]: 테이블 이미지 삭제 응답 DTO, Converter 추가

* [FEAT]: 테이블 이미지 삭제 서비스 로직 추가

* [FEAT] : 예약 취소 API 개발 / 마이페이지 예약 조회 API 개발  (#85)

* [FEAT] : 예약 취소 DTO 개발

* [FEAT] : 예약 취소 DTO 개발

* [FEAT] : 예약 취소 성공,실패 상태 추가

* [FIX] : 예약 상태 이름 변경

* [FEAT] : Booking 엔티티에 cancel 비지니스 메서드 추가

* [FEAT] : 예약 취소 API 구현

* [FEAT] : Booking 엔티티 취소 이유 필드와 취소 메서드 구현

* [FEAT] : BookingConverter 구현

* [FEAT] : 예약생성 응답 DTO에 paymentID, orderID 추가

* [CHORE] : 결제 요청 API 스웨거 설명 수정

* [REFACTOR] : 결제 승인 API에서 예약 상태도 변경하도록 코드 구조 변경

* [CHORE] : 결제 완료 처리 api 스웨거 설정 변경

* [REFACTOR] : 예약 생성 api에서 내부적으로 결제 요청 로직까지 진행하도록 코드 변경

* [FEAT] : 예약 엔티티에서 결제 완료된 결제키를 찾는 편의 메서드 개발

* [FEAT] : 예약 취소시 환불까지 연동되는 비즈니스로직 개발

* [FEAT] : 예약 조회 응답 DTO 개발

* [FEAT] : 예약 조회 Repository 개발

* [FEAT] : 예약 조회 Service 개발

* [FEAT] : 예약 조회 Controller 개발

* [CHORE] : application-local.yml 파일 설정 추가

* [FEAT] : BookingMenu 엔티티 개발

* [FEAT] : 예약 생성 요청 DTO 변경

* [FEAT] : 예약 생성 서비스 로직 변경(메뉴 고려)

* [FIX] : 예약금, 결제금액 타입 Integer->Decimal로 변경

* [FIX] : 예약 조회 기본 페이지 1로 변경

* [FIX] : 불필요한 예약 완료 처리 api 삭제

* [REFACTOR] : 존재하지 않는 테이블을 포함해 에약을 생성하는 예외 처리

* [CHORE] : PAYMENT 컨트롤러 SWAGGER 설정 복원

* [FEAT] : 예약 취소 로직 예외 처리 추가

* [FIX] : 결제 금액 타입 Integer -> BigDecimal로 변경

* [FEAT]: 메뉴 관리 CRUD 구현 및 S3 수명주기 추가 (#87)

* [FEAT]: 메뉴 도메인 DTO 추가

* [FEAT]: 메뉴 API 상태코드 추가

* [FEAT]: 메뉴, 메뉴 이미지 등록, 삭제 로직 추가

* [FEAT]: 메뉴 수정 DTO 추가

* [FEAT]: 메뉴 수정 성공 응답코드 추가

* [FEAT]: 메뉴 수정 로직 추가

* [REFACTOR]: S3 예외처리 세분화

* [FEAT]: 메뉴 조회 DTO 및 성공 응답코드 추가

* [FEAT]: 메뉴 조회 로직 추가

* [FEAT]: 메뉴 조회 시 N+1 방지를 위해 fetch join 쿼리 추가

* [FEAT]: 품절여부 변경 DTO 및 성공 응답코드 추가

* [FEAT]: 메뉴 품절여부 변경 로직 추가

* [FEAT]: 메뉴 삭제 시 soft delete 설정

* [FEAT]: @Valid, @RequestParam, @PathVaraible 유효성 검사 실패 시 에러 메시지 보이도록 메서드 추가

* [FEAT]: 이미지 선 업로드에 따른 S3 수명 주기 연동 로직 구현

* [REFACTOR]: 이미지 Url 리턴하도록 수정

* [FEAT]: 품절 여부 수정 요청이 기존과 동일하다면 바로 리턴하도록 조건 추가

* [REFACTOR]: 이미지 삭제 API를 이미 등록된 이미지 삭제하는 것으로 역할 수정

* [REFACTOR]: soft delete된 Menu는 안 가져오도록 쿼리 수정

* [REFACTOR]: @where -> @SQLRestriction 으로 수정

* [FIX]: isSuccess(false)로 수정

* [REFACTOR]: 예약금 정책 변경에 따라 minPrice 필드 삭제 및 로직 삭제

* [REFACTOR]: 남아있던 minPrice 사용하는 로직들 수정

* [FEAT]: 사용하지 않는 removeMenu 메서드 삭제

* [REFACTOR]: 메뉴가 없는 가게도 조회될 수 있도록 쿼리 조건 수정

* [REFACTOR]: 트랜잭션 커밋 이후에 S3에 접근할 수 있도록 로직 수정

* [FEAT]: moveObject()에서 이동 경로가 동일한 경우 예외처리 추가

* [FIX] : QClass 관련 빌드 에러 수정

---------

Co-authored-by: CHAN <150508884+zerochani@users.noreply.github.com>
Co-authored-by: SungMinju <minju2928@naver.com>
Co-authored-by: J_lisa&& <155425927+SungMinju@users.noreply.github.com>
Co-authored-by: twodo0 <dopal0426@gmail.com>
Co-authored-by: 손준규 <144649271+sonjunkyu@users.noreply.github.com>
@coderabbitai coderabbitai bot mentioned this pull request Feb 3, 2026
5 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT]: 메뉴 관리(CRUD) 및 이미지 처리 API 구현

3 participants