Skip to content

Test#83

Merged
kjh0718 merged 14 commits intomainfrom
test
Feb 3, 2026
Merged

Test#83
kjh0718 merged 14 commits intomainfrom
test

Conversation

@kjh0718
Copy link
Copy Markdown
Contributor

@kjh0718 kjh0718 commented Feb 2, 2026

✨ 변경 사항


✅ 테스트


  • 수동 테스트 완료
  • 테스트 코드 완료

Summary by CodeRabbit

  • New Features

    • 메뉴 이미지 업로드 기능 추가 및 별도 이미지 업로드 엔드포인트 제공
    • 관리자용 메뉴 생성/수정에서 multipart/form-data 지원
  • Chores

    • 메뉴 API 경로 구조 정비(일관된 /api/menus 등)
    • AWS S3 기반 저장소 통합 및 관련 설정 추가
    • 메뉴 삭제 시 연관 이미지 정리 로직 추가
  • Documentation

    • 공개 API 문서에 이미지 업로드 엔드포인트 설명 추가

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 2, 2026

Walkthrough

메뉴 기능에 AWS S3 이미지 업로드를 추가합니다. S3Service와 파일 유틸리티를 도입하고 MenuController/MenuService/MenuRequest를 멀티파트 파일 처리로 변경하며, S3 관련 설정과 에러 코드를 추가합니다.

Changes

Cohort / File(s) Summary
빌드/환경 설정
build.gradle, src/main/resources/application.yml
Spring Cloud AWS S3 스타터(BOM 포함) 의존성 추가 및 S3 버킷·자격 증명·리전 설정 추가
S3 통합 유틸/서비스
src/main/java/com/campustable/be/domain/s3/service/S3Service.java, src/main/java/com/campustable/be/domain/s3/util/FileUtil.java
새 S3Service 추가(업로드/삭제, 예외 매핑) 및 MultipartFile 검증용 FileUtil 유틸 추가
메뉴 서비스 변경
src/main/java/com/campustable/be/domain/menu/service/MenuService.java
S3Service 주입, createMenu/uploadMenuImage 메서드 추가·수정, 이미지 업로드/롤백/기존 이미지 삭제 로직 도입, deleteMenu에서 이미지 삭제 시도
메뉴 컨트롤러 및 문서
src/main/java/com/campustable/be/domain/menu/controller/MenuController.java, src/main/java/com/campustable/be/domain/menu/controller/MenuControllerDocs.java
컨트롤러 경로·엔드포인트 재배치, multipart/form-data 처리 추가, 메뉴 이미지 업로드 엔드포인트 신설 및 Swagger docs 업데이트
메뉴 DTO 변경
src/main/java/com/campustable/be/domain/menu/dto/MenuRequest.java
menuUrl 필드 제거, MultipartFile image 필드 추가, toEntity에서 menuUrl을 null로 설정
공통 유틸/에러 코드
src/main/java/com/campustable/be/global/common/CommonUtil.java, src/main/java/com/campustable/be/global/exception/ErrorCode.java
CommonUtil.nvl 추가 및 S3 관련(INVALID_FILE_REQUEST, S3_UPLOAD_, S3_DELETE_) 및 주문 관련 에러 코드 추가

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant MenuController
    participant MenuService
    participant S3Service
    participant S3
    participant Database

    Client->>MenuController: POST /api/admin/menus/{menu_id}/image (MultipartFile)
    MenuController->>MenuService: uploadMenuImage(menuId, image)
    MenuService->>S3Service: uploadFile(image, "menu/{cafeteria}")
    S3Service->>S3Service: validateAndExtractFilename(image)
    S3Service->>S3: putObject(s3Key, imageStream)
    S3-->>S3Service: stored S3 URL
    S3Service-->>MenuService: S3 URL
    MenuService->>Database: updateMenuUrl(menuId, S3 URL)
    opt 기존 이미지 존재
        MenuService->>S3Service: deleteFile(oldImageUrl)
        S3Service->>S3: deleteObject(oldS3Key)
        S3-->>S3Service: delete success
    end
    Database-->>MenuService: updated Menu
    MenuService-->>MenuController: MenuResponse
    MenuController-->>Client: 200 OK (MenuResponse)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Poem

🐰 구름 위에 당근처럼 반짝이는 이미지,
멀티파트 안에 품어 날려 보냈네.
S3에 안착한 한 점의 사진,
메뉴가 웃으며 빛나리라 🌸
토끼가 축하 노래를 흥얼거리네.

🚥 Pre-merge checks | ✅ 1 | ❌ 2
❌ Failed checks (2 warnings)
Check name Status Explanation Resolution
Title check ⚠️ Warning PR 제목 'Test'는 변경 사항과 무관한 제너릭 표현으로, 실제 변경 내용인 메뉴 이미지 파일 업로드 기능을 전혀 설명하지 않습니다. PR 제목을 'Add menu image upload feature with S3 integration' 또는 '메뉴 이미지 업로드 기능 추가'로 변경하여 실제 주요 변경 사항을 반영해야 합니다.
Docstring Coverage ⚠️ Warning Docstring coverage is 39.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ 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 test

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Copy Markdown

@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: 3

🤖 Fix all issues with AI agents
In `@build.gradle`:
- Around line 52-53: The project pulls
io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2 which brings Spring
Framework 6.2.7 with known CVEs; update your build to ensure Spring Framework is
at least 6.2.11 by either (A) switching to a fixed AWS starter/BOM that forces
newer Spring (e.g., import or update spring-cloud-aws-dependencies to a version
that resolves to Spring Framework 6.2.11+), or (B) explicitly override the
Spring Framework BOM/dependency to 6.2.11+ in your Gradle configuration so that
io.awspring.cloud:spring-cloud-aws-starter-s3 resolves against Spring Framework
>=6.2.11; ensure the chosen approach also addresses transitive CVE-2025-14763
from S3 encryption client.

In `@src/main/java/com/campustable/be/domain/menu/service/MenuService.java`:
- Around line 76-89: The code uses menuRepository.save(menu) inside
uploadMenuImage which may defer constraint violations until transaction commit
and prevent the S3 cleanup in the catch; replace menuRepository.save(menu) with
menuRepository.saveAndFlush(menu) so the persist is flushed immediately and any
constraint violations trigger the catch block allowing
s3Service.deleteFile(newUrl) cleanup to run; update the reference in the
try/catch around menuRepository.save(menu) (in uploadMenuImage) to call
saveAndFlush instead.

In `@src/main/java/com/campustable/be/domain/s3/service/S3Service.java`:
- Around line 54-76: The uploadFile method can let unchecked runtime exceptions
from s3Template.upload (e.g., network/credential issues) escape and break
ErrorCode mapping; add an additional catch for RuntimeException (after the
existing S3Exception and IOException catches) that logs the bucket, storedPath
and originalFilename along with e.getMessage(), and rethrow a CustomException
with the appropriate S3 error code (e.g.,
ErrorCode.S3_UPLOAD_AMAZON_CLIENT_ERROR or a new S3_UPLOAD_RUNTIME_ERROR) so all
runtime failures are mapped consistently for uploadFile / s3Template.upload.
🧹 Nitpick comments (4)
src/main/java/com/campustable/be/domain/s3/util/FileUtil.java (1)

9-10: isNullOrEmpty가 빈 파일명을 놓칠 수 있습니다.

파일명이 ""/공백인 경우도 empty로 간주하도록 보완하는 게 안전합니다.

♻️ 제안 변경
-  public boolean isNullOrEmpty(MultipartFile file){
-    return file == null || file.isEmpty() || file.getOriginalFilename() == null;
-  }
+  public boolean isNullOrEmpty(MultipartFile file){
+    if (file == null || file.isEmpty()) {
+      return true;
+    }
+    String originalFilename = file.getOriginalFilename();
+    return originalFilename == null || originalFilename.isBlank();
+  }
src/main/java/com/campustable/be/domain/s3/service/S3Service.java (1)

107-113: URL 키 추출 로직을 URI 기반으로 보강하는 것을 권장합니다.

도메인/쿼리 변화에 취약하므로 URI 파싱 기반으로 키를 추출하는 편이 안전합니다.

♻️ 제안 변경
   private String extractKeyFromUrl(String fileUrl) {
-    if (fileUrl.contains(".com/")) {
-      String key = fileUrl.substring(fileUrl.lastIndexOf(".com/") + 5);
-      log.info("추출된 S3 Key: [{}]", key);
-      return key;
-    }
-    return fileUrl;
+    try {
+      var uri = java.net.URI.create(fileUrl);
+      String path = uri.getPath();
+      if (path == null || path.isBlank()) {
+        return fileUrl;
+      }
+      String key = path.startsWith("/") ? path.substring(1) : path;
+      if (key.startsWith(bucket + "/")) {
+        key = key.substring(bucket.length() + 1);
+      }
+      log.info("추출된 S3 Key: [{}]", key);
+      return key;
+    } catch (IllegalArgumentException e) {
+      return fileUrl;
+    }
   }
src/main/java/com/campustable/be/domain/menu/service/MenuService.java (1)

91-97: oldUrl과 newUrl이 동일할 때 삭제를 건너뛰어 주세요.
uploadFile이 동일 키를 재사용하는 경우 새 파일까지 삭제될 수 있습니다. 동일 여부 확인 후 삭제하도록 가드가 필요합니다.

🛡️ 제안 수정
-    if (oldUrl != null && !oldUrl.isBlank()) {
+    if (oldUrl != null && !oldUrl.isBlank() && !oldUrl.equals(newUrl)) {
src/main/java/com/campustable/be/domain/menu/controller/MenuControllerDocs.java (1)

72-109: 멀티파트 요청 스키마가 Swagger에 제대로 노출되는지 확인해주세요.
@Parameter만으로는 image가 file(binary)로 표시되지 않을 수 있으니 @RequestBody + @Content(mediaType="multipart/form-data")@Schema(type="string", format="binary") 적용 여부를 점검해주세요.

📌 예시 (요지)
`@io.swagger.v3.oas.annotations.parameters.RequestBody`(
  content = `@Content`(
    mediaType = "multipart/form-data",
    schema = `@Schema`(
      type = "object"
      // image: type="string", format="binary" 등
    )
  )
)

build.gradle Outdated
Comment on lines +52 to +53
// S3
implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2'
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

spring-cloud-aws-starter-s3 3.4.2 Spring Boot 3.5.7 compatibility requirements

💡 Result:

For io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2 (Spring Cloud AWS 3.4.x) the expected compatibility stack is:

  • Spring Boot: 3.5.x (so Spring Boot 3.5.7 is in-family and supported) [1][2]
  • Spring Cloud release train: 2025.0.x (Northfields) [1][3]
  • Spring Framework: 6.2.x [2]
  • AWS SDK: AWS SDK for Java v2 (not v1) [2]
  • Java: 17+ (baseline for Spring Boot 3 / Spring Framework 6) [4]

Use the matching BOM to keep versions aligned:

  • io.awspring.cloud:spring-cloud-aws-dependencies:3.4.2 (then add spring-cloud-aws-starter-s3 without a version). [2]

Sources: Spring Cloud release train ↔ Boot matrix [1], Spring Cloud AWS compatibility table [2], Spring Boot 3.5.7 release confirmation [3], Java 17 baseline for Spring 6 / Boot 3 [4].


🌐 Web query:

spring-cloud-aws-starter-s3 3.4.2 security vulnerabilities CVE

💡 Result:

For io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2 there are no widely reported “direct” CVEs in the starter itself, but scanners commonly flag transitive dependency CVEs pulled in via Spring Cloud AWS / Spring Framework / AWS libraries:

  • CVE-2025-14763 (Amazon S3 Encryption Client for Java) – “missing cryptographic key commitment”; mitigate by upgrading the Amazon S3 Encryption Client to 4.0.0+ (or ensuring your dependency/BOM resolves to a fixed version). This CVE is commonly reported as coming from Spring Cloud AWS 3.4.2 dependency graph. [1][2]

  • CVE-2025-41242 (Spring Framework path traversal on non-compliant Servlet containers) – affects Spring Framework 6.2.0–6.2.9; fixed in 6.2.10. If your resolved Spring Framework version is in that range, you’re affected. [3]

  • CVE-2025-41249 (Spring Framework annotation detection / authorization decisions) – affects Spring Framework 6.2.0–6.2.10; fixed in 6.2.11. If your resolved Spring Framework version is in that range, you’re affected. [4]

A Spring Cloud AWS maintainer issue notes scanners flagging Spring Cloud AWS (including spring-cloud-aws-starter-s3) pulling Spring Framework 6.2.7, which would fall into the affected ranges above for CVE-2025-41242 and CVE-2025-41249. [5]

What to do

  1. Check your resolved dependency tree (mvn dependency:tree / Gradle dependencies) for:
    • amazon-s3-encryption-client-java (or similarly named artifact) and bump to 4.0.0+ if present. [2]
    • org.springframework:spring-* versions; upgrade Spring Framework to 6.2.11+ to cover both CVE-2025-41242 (via 6.2.10) and CVE-2025-41249 (via 6.2.11). [3][4]

Sources:
[1] MvnRepository (Spring Cloud AWS 3.4.2) vulnerability listing (CVE-2025-14763) (mvnrepository.com)
[2] NVD record for CVE-2025-14763 (nvd.nist.gov)
[3] Spring advisory for CVE-2025-41242 (spring.io)
[4] Spring advisory for CVE-2025-41249 (spring.io)
[5] awspring/spring-cloud-aws issue discussing Spring 6.2.7 and CVE-2025-41242/41249 exposure (github.com)

Citations:


🌐 Web query:

io.awspring.cloud spring-cloud-aws-starter-s3 3.4.2 release notes

💡 Result:

io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2 is part of the Spring Cloud AWS 3.4.2 release (released Dec 1, 2025). [2]

Release notes (3.4.2)

The 3.4.2 release contains a single documented change (no S3-specific items are listed for this patch release):

  • Core: StsWebIdentityTokenFileCredentialsProvider is no longer auto-configured unless the required properties are provided (PR #1528). [1]

References

  • Release entry / “What’s Changed” + reference docs for 3.4.2: [1]
  • Release date (3.4.2): [2]

Spring Framework 보안 패치 필요

Spring Boot 3.5.7과의 호환성은 정상이나, spring-cloud-aws-starter-s3 3.4.2에서 가져오는 Spring Framework 6.2.7에 다음 보안 취약점이 있습니다:

또한 전이 의존성에서 CVE-2025-14763 (Amazon S3 Encryption Client)이 보고될 수 있으므로, spring-cloud-aws-dependencies:3.4.2 BOM을 사용하거나 Spring Framework를 명시적으로 6.2.11+로 업그레이드하세요.

🤖 Prompt for AI Agents
In `@build.gradle` around lines 52 - 53, The project pulls
io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2 which brings Spring
Framework 6.2.7 with known CVEs; update your build to ensure Spring Framework is
at least 6.2.11 by either (A) switching to a fixed AWS starter/BOM that forces
newer Spring (e.g., import or update spring-cloud-aws-dependencies to a version
that resolves to Spring Framework 6.2.11+), or (B) explicitly override the
Spring Framework BOM/dependency to 6.2.11+ in your Gradle configuration so that
io.awspring.cloud:spring-cloud-aws-starter-s3 resolves against Spring Framework
>=6.2.11; ensure the chosen approach also addresses transitive CVE-2025-14763
from S3 encryption client.

Comment on lines +54 to +76
public String uploadFile(MultipartFile file, String dirName) {

String originalFilename = validateAndExtractFilename(file);

String storedPath = dirName + "/" + UUID.randomUUID() + "_" + originalFilename;
log.debug("생성된 파일명: {}", storedPath);

try (InputStream inputStream = file.getInputStream()) {

S3Resource resource = s3Template.upload(bucket, storedPath, inputStream);

String s3Url = resource.getURL().toString();
log.info("S3 업로드 성공: {}", s3Url);

return s3Url;

} catch (S3Exception e) {
log.error("AmazonServiceException - S3 파일 업로드 실패. 버킷: {}, 파일명: {}, 에러: {}", bucket, storedPath, e.getMessage());
throw new CustomException(ErrorCode.S3_UPLOAD_AMAZON_CLIENT_ERROR);
} catch (IOException e) {
log.error("IOException - 파일 스트림 처리 중 에러 발생. 원본 파일명: {}, 파일명: {} 에러: {}", originalFilename, storedPath, e.getMessage());
throw new CustomException(ErrorCode.S3_UPLOAD_ERROR);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

업로드 경로에서 런타임 예외가 그대로 전파됩니다.

네트워크/자격증명 등 런타임 예외가 누락되어 에러코드 매핑이 깨질 수 있습니다.

🐛 제안 변경
     } catch (S3Exception e) {
       log.error("AmazonServiceException - S3 파일 업로드 실패. 버킷: {}, 파일명: {}, 에러: {}", bucket, storedPath, e.getMessage());
       throw new CustomException(ErrorCode.S3_UPLOAD_AMAZON_CLIENT_ERROR);
     } catch (IOException e) {
       log.error("IOException - 파일 스트림 처리 중 에러 발생. 원본 파일명: {}, 파일명: {} 에러: {}", originalFilename, storedPath, e.getMessage());
       throw new CustomException(ErrorCode.S3_UPLOAD_ERROR);
+    } catch (RuntimeException e) {
+      log.error("RuntimeException - S3 파일 업로드 실패. 버킷: {}, 파일명: {}, 에러: {}", bucket, storedPath, e.getMessage());
+      throw new CustomException(ErrorCode.S3_UPLOAD_AMAZON_CLIENT_ERROR);
     }
📝 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
public String uploadFile(MultipartFile file, String dirName) {
String originalFilename = validateAndExtractFilename(file);
String storedPath = dirName + "/" + UUID.randomUUID() + "_" + originalFilename;
log.debug("생성된 파일명: {}", storedPath);
try (InputStream inputStream = file.getInputStream()) {
S3Resource resource = s3Template.upload(bucket, storedPath, inputStream);
String s3Url = resource.getURL().toString();
log.info("S3 업로드 성공: {}", s3Url);
return s3Url;
} catch (S3Exception e) {
log.error("AmazonServiceException - S3 파일 업로드 실패. 버킷: {}, 파일명: {}, 에러: {}", bucket, storedPath, e.getMessage());
throw new CustomException(ErrorCode.S3_UPLOAD_AMAZON_CLIENT_ERROR);
} catch (IOException e) {
log.error("IOException - 파일 스트림 처리 중 에러 발생. 원본 파일명: {}, 파일명: {} 에러: {}", originalFilename, storedPath, e.getMessage());
throw new CustomException(ErrorCode.S3_UPLOAD_ERROR);
}
public String uploadFile(MultipartFile file, String dirName) {
String originalFilename = validateAndExtractFilename(file);
String storedPath = dirName + "/" + UUID.randomUUID() + "_" + originalFilename;
log.debug("생성된 파일명: {}", storedPath);
try (InputStream inputStream = file.getInputStream()) {
S3Resource resource = s3Template.upload(bucket, storedPath, inputStream);
String s3Url = resource.getURL().toString();
log.info("S3 업로드 성공: {}", s3Url);
return s3Url;
} catch (S3Exception e) {
log.error("AmazonServiceException - S3 파일 업로드 실패. 버킷: {}, 파일명: {}, 에러: {}", bucket, storedPath, e.getMessage());
throw new CustomException(ErrorCode.S3_UPLOAD_AMAZON_CLIENT_ERROR);
} catch (IOException e) {
log.error("IOException - 파일 스트림 처리 중 에러 발생. 원본 파일명: {}, 파일명: {} 에러: {}", originalFilename, storedPath, e.getMessage());
throw new CustomException(ErrorCode.S3_UPLOAD_ERROR);
} catch (RuntimeException e) {
log.error("RuntimeException - S3 파일 업로드 실패. 버킷: {}, 파일명: {}, 에러: {}", bucket, storedPath, e.getMessage());
throw new CustomException(ErrorCode.S3_UPLOAD_AMAZON_CLIENT_ERROR);
}
🤖 Prompt for AI Agents
In `@src/main/java/com/campustable/be/domain/s3/service/S3Service.java` around
lines 54 - 76, The uploadFile method can let unchecked runtime exceptions from
s3Template.upload (e.g., network/credential issues) escape and break ErrorCode
mapping; add an additional catch for RuntimeException (after the existing
S3Exception and IOException catches) that logs the bucket, storedPath and
originalFilename along with e.getMessage(), and rethrow a CustomException with
the appropriate S3 error code (e.g., ErrorCode.S3_UPLOAD_AMAZON_CLIENT_ERROR or
a new S3_UPLOAD_RUNTIME_ERROR) so all runtime failures are mapped consistently
for uploadFile / s3Template.upload.

Copy link
Copy Markdown

@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: 1

🤖 Fix all issues with AI agents
In `@src/main/java/com/campustable/be/domain/menu/service/MenuService.java`:
- Around line 185-192: The current flow deletes the S3 file via
s3Service.deleteFile(menu.get().getMenuUrl()) before removing the DB row with
menuRepository.deleteById(menuId), which can leave the DB pointing at a
now-missing S3 object if deleteById fails; change the sequence to (1) capture
the menu URL into a local variable (e.g., String menuUrl =
menu.get().getMenuUrl()) so you have it after the DB operation, (2) call
menuRepository.deleteById(menuId) first, and only if that succeeds attempt
s3Service.deleteFile(menuUrl) inside a try/catch, logging failures with
log.warn("deleteMenu: 이미지 삭제 실패. menuId={}, url={}", menuId, menuUrl, e) so S3
errors don’t block DB deletion.

Comment on lines +185 to +192
if (menu.get().getMenuUrl() != null && !menu.get().getMenuUrl().isBlank()) {
try {
s3Service.deleteFile(menu.get().getMenuUrl());
} catch (Exception e) {
log.warn("deleteMenu: 이미지 삭제 실패. menuId={}, url={}", menuId, menu.get().getMenuUrl(), e);
}
}
menuRepository.deleteById(menuId);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

S3 삭제와 DB 삭제 순서 조정 필요

현재 S3 파일을 삭제한 후 DB 레코드를 삭제합니다. deleteById()가 실패할 경우 (예: 외래키 제약 위반), S3 파일은 이미 삭제되었지만 메뉴 레코드는 유효하지 않은 URL을 가진 채로 남게 됩니다.

DB 삭제를 먼저 수행하고, 성공 시 S3 파일을 삭제하는 것이 안전합니다.

수정 제안
  `@Transactional`
  public void deleteMenu(Long menuId) {

    Optional<Menu> menu = menuRepository.findById(menuId);
    if (menu.isEmpty()) {
      log.error("menuId not found {}", menuId);
      throw new CustomException(ErrorCode.MENU_NOT_FOUND);
    }
+   String menuUrl = menu.get().getMenuUrl();
+   menuRepository.deleteById(menuId);
+   
+   if (menuUrl != null && !menuUrl.isBlank()) {
-   if (menu.get().getMenuUrl() != null && !menu.get().getMenuUrl().isBlank()) {
      try {
-       s3Service.deleteFile(menu.get().getMenuUrl());
+       s3Service.deleteFile(menuUrl);
      } catch (Exception e) {
-       log.warn("deleteMenu: 이미지 삭제 실패. menuId={}, url={}", menuId, menu.get().getMenuUrl(), e);
+       log.warn("deleteMenu: 이미지 삭제 실패. menuId={}, url={}", menuId, menuUrl, e);
      }
    }
-   menuRepository.deleteById(menuId);
  }
🤖 Prompt for AI Agents
In `@src/main/java/com/campustable/be/domain/menu/service/MenuService.java` around
lines 185 - 192, The current flow deletes the S3 file via
s3Service.deleteFile(menu.get().getMenuUrl()) before removing the DB row with
menuRepository.deleteById(menuId), which can leave the DB pointing at a
now-missing S3 object if deleteById fails; change the sequence to (1) capture
the menu URL into a local variable (e.g., String menuUrl =
menu.get().getMenuUrl()) so you have it after the DB operation, (2) call
menuRepository.deleteById(menuId) first, and only if that succeeds attempt
s3Service.deleteFile(menuUrl) inside a try/catch, logging failures with
log.warn("deleteMenu: 이미지 삭제 실패. menuId={}, url={}", menuId, menuUrl, e) so S3
errors don’t block DB deletion.

@kjh0718 kjh0718 merged commit 593e4e7 into main Feb 3, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant