Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
11 commits
Select commit Hold shift + click to select a range
6e140e0
메뉴_이미지_파일_업로드_기능_추가 : feat : {S3Service - upload 기능 구현} https://githu…
kjh0718 Jan 25, 2026
a340bb4
메뉴_이미지_파일_업로드_기능_추가 : feat : {S3 upload 기능 test 삭제} https://github.co…
kjh0718 Jan 25, 2026
2a231f8
메뉴_이미지_파일_업로드_기능_추가 : feat : {menu image 파일 업로드 기능 구현} https://github…
kjh0718 Jan 25, 2026
8430fef
메뉴_이미지_파일_업로드_기능_추가 : feat : {메뉴 이미지 해당 식당별 경로 생성} https://github.com…
kjh0718 Jan 26, 2026
6ebcd32
메뉴_이미지_파일_업로드_기능_추가 : feat : {메뉴 생성시 이미지 추가 기능 + docs 수정} https://git…
kjh0718 Jan 26, 2026
57dfdbc
메뉴_이미지_파일_업로드_기능_추가 : feat : {s3 deletefile 기능 구현} https://github.com…
kjh0718 Jan 27, 2026
06ad745
메뉴_이미지_파일_업로드_기능_추가 : feat : {menu 관리자 전용 API 경로 분리} https://github.c…
kjh0718 Jan 27, 2026
c2f800f
메뉴_이미지_파일_업로드_기능_추가 : feat : {spring-cloud-aws-starter-s3 버전 3.1.0 ->…
kjh0718 Feb 1, 2026
3f38909
메뉴_이미지_파일_업로드_기능_추가 : feat : {crateMenu:이미지포함 생성시 이미지 url까지 반환,기존 이미지…
kjh0718 Feb 1, 2026
15d0e12
메뉴_이미지_파일_업로드_기능_추가 : feat : {메뉴 저장 실패시 이미지 삭제} https://github.com/Ca…
kjh0718 Feb 1, 2026
202ca11
메뉴_이미지_파일_업로드_기능_추가 : feat : {로그 수정} https://github.com/CampusTable/c…
kjh0718 Feb 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ dependencies {

// JavaNetCookieJar
implementation 'com.squareup.okhttp3:okhttp-urlconnection:4.9.3'

// S3
implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2'
Comment on lines +51 to +53
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

🧩 Analysis chain

🌐 Web query:

Spring Cloud AWS 3.4.2 Spring Boot 3.5.7 compatibility

💡 Result:

Yes—Spring Cloud AWS 3.4.2 is compatible with Spring Boot 3.5.7, because Spring Cloud AWS 3.4.x targets Spring Boot 3.5.x (and Spring Cloud 2025.0.x). [1][2]

If you’re on Boot 3.5.7, you should align your BOMs accordingly (i.e., Spring Cloud 2025.0.x + Spring Cloud AWS 3.4.2) to avoid dependency mismatches. [1][2]

Sources:
[1] Spring Cloud AWS GitHub README – compatibility table (3.4.x → Boot 3.5.x)
[2] Spring Cloud project page – release train mapping (2025.0.x → Boot 3.5.x)


🌐 Web query:

io.awspring.cloud spring-cloud-aws-starter-s3 latest version 2024 2025

💡 Result:

For io.awspring.cloud:spring-cloud-aws-starter-s3:

  • Latest released in 2025: 3.4.2 (published Nov 30, 2025) [1]
  • Latest released in 2024: 3.2.1 (published Oct 28, 2024) [1]

As of Feb 1, 2026, Maven Central also shows a newer pre-release: 4.0.0-RC1 (published Jan 14, 2026). The latest stable remains 3.4.2. [1]

Maven coords

<dependency>
  <groupId>io.awspring.cloud</groupId>
  <artifactId>spring-cloud-aws-starter-s3</artifactId>
  <version>3.4.2</version>
</dependency>

🌐 Web query:

spring-cloud-aws-starter-s3 3.4.2 security vulnerabilities release notes

💡 Result:

spring-cloud-aws-starter-s3 3.4.2 release notes (security-related)

Official 3.4.2 release notes (awspring)

Spring Cloud AWS 3.4.2 (released Dec 1, 2025) lists only one change, and it is in Core (STS autoconfiguration):

  • StsWebIdentityTokenFileCredentialsProvider is not configured unless the required properties are provided. [1]

No other fixes (including S3-specific or security fixes) are mentioned in the 3.4.2 release notes. [1]

Known vulnerability reported for 3.4.2 (transitive dependency)

MvnRepository flags CVE-2025-14763 as a vulnerability coming from dependencies for Spring Cloud AWS 3.4.2 / its BOM. [2][3]

Per NVD, CVE-2025-14763 affects the Amazon S3 Encryption Client for Java (missing cryptographic key commitment) and AWS recommends upgrading the S3 Encryption Client to 4.0.0+. [4]

Implication: if your dependency tree (directly or transitively) pulls in a vulnerable amazon-s3-encryption-client-java version, you may need to override that dependency (or upgrade to a Spring Cloud AWS release line that pulls a fixed version). [2][4]

Sources: [1] GitHub Releases (awspring/spring-cloud-aws) • [2] MvnRepository spring-cloud-aws 3.4.2 • [3] MvnRepository spring-cloud-aws-dependencies 3.4.2 • [4] NVD CVE-2025-14763


Spring Cloud AWS S3 스타터 3.4.2 버전 확인

Spring Boot 3.5.7과의 호환성 및 최신 안정 버전 여부가 확인되었습니다.

  • 호환성: Spring Cloud AWS 3.4.x는 Spring Boot 3.5.x와 호환됨 (Spring Cloud 2025.0.x BOM과 함께 사용)
  • 최신 안정 버전: 3.4.2는 2026년 2월 현재 최신 정식 릴리스 (4.0.0-RC1은 프리릴리스 상태)

다만 CVE-2025-14763 (Amazon S3 Encryption Client의 암호화 키 커밋 누락)이 전이 의존성으로 플래그되었습니다. S3 암호화 기능을 사용하는 경우, amazon-s3-encryption-client-java 의존성을 4.0.0 이상으로 오버라이드하거나 업스트림 패치를 기다려야 합니다.

🤖 Prompt for AI Agents
In `@build.gradle` around lines 51 - 53, The S3 starter implementation line
(implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.4.2') pulls a
transitive amazon-s3-encryption-client version vulnerable to CVE-2025-14763; if
your project uses S3 encryption, explicitly override the transitive dependency
to a patched release (amazon-s3-encryption-client version >= 4.0.0) in your
dependency management (or add it as a direct
implementation/dependencyConstraint), or wait for the upstream patch—ensure the
override references the amazon-s3-encryption-client-java artifact and keeps
compatibility with the Spring Cloud AWS 3.4.2 starter.

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import org.springframework.web.multipart.MultipartFile;

@RestController
@RequestMapping("/api/menu")
@RequestMapping("/api")
@RequiredArgsConstructor
public class MenuController implements MenuControllerDocs {

Expand All @@ -23,7 +25,7 @@ public class MenuController implements MenuControllerDocs {


@Override
@GetMapping
@GetMapping("/menus")
@LogMonitoringInvocation
public ResponseEntity<List<MenuResponse>> getAllMenus(){

Expand All @@ -35,7 +37,7 @@ public ResponseEntity<List<MenuResponse>> getAllMenus(){

@Override
@LogMonitoringInvocation
@GetMapping("/category/{category_id}")
@GetMapping("/category/{category_id}/menus")
public ResponseEntity<List<MenuResponse>> getAllMenusByCategoryId(
@PathVariable(name = "category_id") Long categoryId){

Expand All @@ -47,31 +49,46 @@ public ResponseEntity<List<MenuResponse>> getAllMenusByCategoryId(

@Override
@LogMonitoringInvocation
@GetMapping("/{menuId}")
@GetMapping("/menus/{menuId}")
public ResponseEntity<MenuResponse> getMenuById(@PathVariable Long menuId){
return ResponseEntity.ok(menuService.getMenuById(menuId));
}

@Override
@LogMonitoringInvocation
@GetMapping("/cafeteria/{cafeteria-id}")
@GetMapping("/menus/cafeteria/{cafeteria-id}")
public ResponseEntity<List<MenuResponse>> getAllMenusByCafeteriaId(
@PathVariable(name = "cafeteria-id") Long cafeteriaId
) {
return ResponseEntity.ok(menuService.getAllMenusByCafeteriaId(cafeteriaId));
}
Comment on lines 57 to 64
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

🧩 Analysis chain

🌐 Web query:

Spring MVC path variable hyphen naming rules allowed characters

💡 Result:

In Spring MVC path patterns, there are two different “naming rules” to keep straight:

1) The path variable name (inside {...}) — hyphen not allowed

Spring’s {var} syntax is based on URI Template variable naming, where a variable name is:

  • ALPHA / DIGIT / "_" / pct-encoded, with optional dot-separated parts (.) — no - (hyphen). [2]

So this is invalid as a variable name:

  • /orders/{order-id}order-id contains - (not allowed by the varname grammar) [2]

Use something like:

  • /orders/{orderId}
  • /orders/{order_id}

2) The path variable value (what gets matched from the URL) — hyphen allowed

A captured path segment can contain any URI “unreserved” characters, which explicitly includes the hyphen (-). [1]

So this is fine:

  • /orders/abc-123 matched by /orders/{id}

Notes (Spring-specific syntax)

  • {var} captures exactly one path segment. [3]
  • {var:regex} lets you constrain the value with a regex. [3]
  • {*var} captures the rest of the path across / separators (multiple segments). [3]

Sources: RFC 3986 (hyphen is unreserved) [1], RFC 6570 (variable-name grammar) [2], Spring PathPattern syntax [3].


🌐 Web query:

Spring @PathVariable hyphen example {variable-name}

💡 Result:

In Spring MVC/Spring Boot you can use a hyphenated URI template variable name like {variable-name}, but you must bind it explicitly because variable-name is not a valid Java parameter name.

`@RestController`
class DemoController {

  `@GetMapping`("/items/{variable-name}")
  public String get(`@PathVariable`("variable-name") String variableName) {
    return variableName;
  }
}

Spring matches the URI template variable by the name you provide in @PathVariable("..."), which can differ from the Java method parameter name. [1][2]


🏁 Script executed:

# Search for any existing code patterns in the repository
rg "@GetMapping.*{.*-.*}" -A 3 -B 1
rg "@PathVariable.*-" -A 1 -B 1

Repository: CampusTable/campus-table-be

Length of output: 572


🌐 Web query:

Spring @GetMapping @PathVariable hyphen work runtime error

💡 Result:

A hyphen in the path variable value (e.g., /users/u-fred) is valid and Spring MVC should bind it normally—no special config needed. If you’re getting null / “Missing URI template variable …”, it’s almost always a name mismatch between the {…} in @GetMapping and what Spring thinks your method parameter is called. Spring binds by the URI-template variable name (or by the Java parameter name if you don’t specify one). [1][2][3]

Use one of these fixes:

// Prefer: use a normal Java identifier in the template
`@GetMapping`("/users/{userId}")
public User get(`@PathVariable` String userId) { ... }
// If your template variable name itself contains a hyphen, you MUST specify it explicitly
`@GetMapping`("/users/{user-id}")
public User get(`@PathVariable`("user-id") String userId) { ... }

If you were relying on implicit parameter-name binding, ensure your code is compiled with Java’s -parameters flag, otherwise Spring may not “see” the real parameter name and binding can fail unless you specify @PathVariable("..."). [2]

Sources: Spring @PathVariable docs (name/value attributes) [1]; common cause is URI-template-variable name mismatch [2]; dashes in the value are supported [3].

[1] https://docs.spring.io/spring-framework/docs/6.0.17/javadoc-api/org/springframework/web/bind/annotation/PathVariable.html
[2] https://stackoverflow.com/questions/20713522/missing-uri-template-variable-studentid-for-method-parameter-type-java-lang-i
[3] https://stackoverflow.com/questions/4833438/how-do-you-configure-spring-mvc-pathvariables-to-accept-strings-containing-dash


🌐 Web query:

Spring PathPattern variable naming RFC 6570 hyphen support

💡 Result:

In RFC 6570, a variable name (varname) is defined as varchar *( ["."] varchar ) where varchar = ALPHA / DIGIT / "_" / pct-encoded — i.e., a hyphen (-) is not allowed in the variable name grammar. [1]

Spring’s PathPattern / URI template syntax for path variables is {varName} (and {varName:regex}, {*varName}), and it follows the same “variable name” concept, so a template like /{user-id} will not be treated as a valid variable name (use something like /{userId} or /{user_id} instead). [2][3]

Sources:
[1] RFC 6570 (URI Template) variable name ABNF. (datatracker.ietf.org)
[2] Spring Framework reference docs on URI template variables ({varName}, {varName:regex}, {*varName}). (docs.spring.io)
[3] Spring PathPattern Javadoc examples of variable capture syntax. (docs.enterprise.spring.io)

Citations:


🏁 Script executed:

# Search for other path variable examples in the codebase to see the pattern
rg "@PathVariable" -B 2 -A 0 | head -30

Repository: CampusTable/campus-table-be

Length of output: 3070


🏁 Script executed:

# Check if there are any other examples with hyphenated paths
rg "@GetMapping.*{.*-.*}" -A 2 src/

Repository: CampusTable/campus-table-be

Length of output: 187


🏁 Script executed:

# Search for GetMapping patterns with hyphens (fixing regex)
rg '@GetMapping.*\{' -A 1 | grep -E '{.*-.*}'

Repository: CampusTable/campus-table-be

Length of output: 197


🏁 Script executed:

# Check all GetMapping annotations to see naming patterns
rg '@GetMapping' -A 0 | head -20

Repository: CampusTable/campus-table-be

Length of output: 1787


경로 변수명 형식을 코드베이스 규칙과 통일해주세요.

{cafeteria-id}는 RFC 6570 URI 템플릿 표준에서 변수명에 하이픈을 허용하지 않습니다. 현재 코드는 명시적 @PathVariable(name = "cafeteria-id")로 인해 런타임에 작동하지만, 코드베이스의 다른 모든 엔드포인트({userId}, {menuId}, {cafeteriaId} 등)가 camelCase를 사용하고 있으므로 일관성을 위해 cafeteriaId로 변경해주세요.

수정 제안
-    `@GetMapping`("/menus/cafeteria/{cafeteria-id}")
+    `@GetMapping`("/menus/cafeteria/{cafeteriaId}")
     public ResponseEntity<List<MenuResponse>> getAllMenusByCafeteriaId(
-      `@PathVariable`(name = "cafeteria-id") Long cafeteriaId
+      `@PathVariable` Long cafeteriaId
     ) {
📝 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
@Override
@LogMonitoringInvocation
@GetMapping("/cafeteria/{cafeteria-id}")
@GetMapping("/menus/cafeteria/{cafeteria-id}")
public ResponseEntity<List<MenuResponse>> getAllMenusByCafeteriaId(
@PathVariable(name = "cafeteria-id") Long cafeteriaId
) {
return ResponseEntity.ok(menuService.getAllMenusByCafeteriaId(cafeteriaId));
}
`@Override`
`@LogMonitoringInvocation`
`@GetMapping`("/menus/cafeteria/{cafeteriaId}")
public ResponseEntity<List<MenuResponse>> getAllMenusByCafeteriaId(
`@PathVariable` Long cafeteriaId
) {
return ResponseEntity.ok(menuService.getAllMenusByCafeteriaId(cafeteriaId));
}
🤖 Prompt for AI Agents
In `@src/main/java/com/campustable/be/domain/menu/controller/MenuController.java`
around lines 57 - 64, The path variable in
MenuController.getAllMenusByCafeteriaId uses "{cafeteria-id}" which is
nonstandard and inconsistent; change the `@GetMapping` path to use "{cafeteriaId}"
and update the `@PathVariable` annotation to match (remove the explicit name or
use "cafeteriaId") so the method signature and mapping use camelCase
consistently with other endpoints and RFC 6570-friendly variable names.


@Override
@PostMapping
@PostMapping(value = "/admin/menus", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@LogMonitoringInvocation
public ResponseEntity<MenuResponse> createMenu(@Valid @RequestBody MenuRequest createRequest){
MenuResponse createMenu = menuService.createMenu(createRequest);
public ResponseEntity<MenuResponse> createMenu(
@Valid @ModelAttribute MenuRequest request
){
MenuResponse createMenu = menuService.createMenu(request, request.getImage());

return ResponseEntity.status(HttpStatus.CREATED).body(createMenu);
}

@Override
@PatchMapping("/{menu_id}")
@PostMapping(value = "/admin/menus/{menu_id}/image" ,consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@LogMonitoringInvocation
public ResponseEntity<MenuResponse> uploadMenuImage(
@PathVariable(name = "menu_id") Long menuId,
@RequestParam("image") MultipartFile image){

MenuResponse response = menuService.uploadMenuImage(menuId, image);

return ResponseEntity.ok(response);

}

@Override
@PatchMapping("/admin/menus/{menu_id}")
@LogMonitoringInvocation
public ResponseEntity<MenuResponse> updateMenu(
@PathVariable(name = "menu_id") Long menuId,
Expand All @@ -84,7 +101,7 @@ public ResponseEntity<MenuResponse> updateMenu(

@Override
@LogMonitoringInvocation
@DeleteMapping("/{menu_id}")
@DeleteMapping("/admin/menus/{menu_id}")
public ResponseEntity<Void> deleteMenu(
@PathVariable(name = "menu_id") Long menuId) {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import org.springframework.http.ResponseEntity;

import java.util.List;
import org.springframework.web.multipart.MultipartFile;

/**
* 메뉴 관리 시스템의 API 명세를 정의하는 인터페이스입니다.
Expand All @@ -24,16 +25,13 @@ public interface MenuControllerDocs {

/**
* 시스템에 등록된 모든 메뉴 목록을 조회합니다.
* * @return 메뉴 정보 리스트를 담은 ResponseEntity
*/
@Operation(summary = "메뉴 전체 조회", description = "모든 메뉴 목록을 조회합니다.")
@ApiResponse(responseCode = "200", description = "조회 성공")
ResponseEntity<List<MenuResponse>> getAllMenus();

/**
* 고유 식별자를 통해 단일 메뉴의 상세 정보를 조회합니다.
* * @param menuId 조회하고자 하는 메뉴의 ID
* @return 해당 메뉴의 상세 정보를 담은 ResponseEntity
*/
@Operation(summary = "단일 메뉴 상세 조회", description = "특정 ID에 해당하는 메뉴의 상세 정보를 조회합니다.")
@ApiResponses({
Expand All @@ -47,8 +45,6 @@ ResponseEntity<MenuResponse> getMenuById(

/**
* 특정 카테고리에 속한 모든 메뉴를 조회합니다.
* * @param categoryId 카테고리 고유 식별자
* @return 해당 카테고리의 메뉴 리스트를 담은 ResponseEntity
*/
@Operation(summary = "카테고리별 메뉴 조회", description = "특정 카테고리 ID에 해당하는 메뉴 목록을 조회합니다.")
@ApiResponses({
Expand All @@ -62,8 +58,6 @@ ResponseEntity<List<MenuResponse>> getAllMenusByCategoryId(

/**
* 특정 식당에서 제공하는 모든 메뉴를 조회합니다.
* * @param cafeteriaId 식당 고유 식별자
* @return 해당 식당의 메뉴 리스트를 담은 ResponseEntity
*/
@Operation(summary = "식당별 메뉴 조회", description = "식당 ID에 해당하는 메뉴 목록을 조회합니다.")
@ApiResponses({
Expand All @@ -77,7 +71,7 @@ ResponseEntity<List<MenuResponse>> getAllMenusByCafeteriaId(

/**
* 새로운 메뉴를 시스템에 등록합니다. (관리자 권한 필요)
* * @param menuRequest 생성할 메뉴의 상세 정보 DTO
* * @param request 생성할 메뉴의 상세 정보 DTO + 이미지 파일
* @return 생성된 메뉴 정보를 담은 ResponseEntity
*/
@Operation(summary = "신규 메뉴 생성 (관리자 전용)", description = "새로운 메뉴를 등록합니다.")
Expand All @@ -88,15 +82,36 @@ ResponseEntity<List<MenuResponse>> getAllMenusByCafeteriaId(
@ApiResponse(responseCode = "409", description = "이미 존재하는 메뉴입니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
ResponseEntity<MenuResponse> createMenu(MenuRequest menuRequest);
ResponseEntity<MenuResponse> createMenu(
@Parameter(description = "메뉴 정보 및 이미지 파일") MenuRequest request
);

/**
* 메뉴에 이미지를 업로드 합니다. (관리자 권한 필요)
* * @param menuId 이미지를 등록할 메뉴의 ID
* @param image 업로드할 이미지 파일
* @return 이미지 업로드가 완료된 메뉴 정보를 담은 ResponseEntity
*/
@Operation(
summary = "메뉴 이미지 개별 업로드/수정 (관리자 전용)",
description = "메뉴의 사진을 추가하거나 수정합니다."
)
@ApiResponses({
@ApiResponse(responseCode = "200", description = "이미지 업로드 및 경로 업데이트 성공"),
@ApiResponse(responseCode = "404", description = "해당 ID의 메뉴를 찾을 수 없습니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "400", description = "유효하지 않은 파일 요청입니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
})
ResponseEntity<MenuResponse> uploadMenuImage(
@Parameter(description = "대상 메뉴 ID", example = "1") Long menuId,
@Parameter(description = "업로드할 이미지 파일") MultipartFile image
);

/**
* 기존 메뉴 정보를 수정합니다. (관리자 권한 필요)
* * @param menuId 수정할 메뉴의 ID
* @param menuUpdateRequest 수정할 내용이 담긴 DTO
* @return 수정 완료된 메뉴 정보를 담은 ResponseEntity
*/
@Operation(summary = "메뉴 정보 수정 (관리자 전용)", description = "특정 ID의 메뉴 정보를 수정합니다.")
@Operation(summary = "메뉴 정보 수정 (관리자 전용)", description = "특정 ID의 메뉴 정보를 수정합니다. (이미지 제외)")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "메뉴 수정 성공"),
@ApiResponse(responseCode = "400", description = "입력값 오류",
Expand All @@ -106,13 +121,11 @@ ResponseEntity<List<MenuResponse>> getAllMenusByCafeteriaId(
})
ResponseEntity<MenuResponse> updateMenu(
@Parameter(description = "수정할 메뉴 ID", example = "1") Long menuId,
MenuUpdateRequest menuUpdateRequest
@Parameter(description = "수정할 메뉴 정보") MenuUpdateRequest menuUpdateRequest
);

/**
* 특정 메뉴를 시스템에서 삭제합니다. (관리자 권한 필요)
* * @param menuId 삭제할 메뉴의 ID
* @return 삭제 성공 시 빈 바디를 담은 ResponseEntity (204 No Content)
*/
@Operation(summary = "메뉴 삭제 (관리자 전용)", description = "특정 ID의 메뉴를 삭제합니다.")
@ApiResponses({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@

import java.math.BigDecimal;
import lombok.Setter;
import org.springframework.web.multipart.MultipartFile;

@Getter
@Setter
Expand All @@ -26,21 +27,21 @@ public class MenuRequest {
@Min(value = 0, message = "가격은 0원 이상이어야 합니다.")
private Integer price;

@NotBlank(message = "이미지를 위한url은 필수입니다.")
private String menuUrl;

@NotNull(message = "판매 가능 여부는 필수입니다.")
private Boolean available;

private Integer stockQuantity;

private MultipartFile image;


public Menu toEntity(Category category) {
return Menu.builder()
.category(category)
.menuName(this.getMenuName())
.price(this.getPrice())
.menuUrl(this.getMenuUrl())
.menuUrl(null)
.available(this.getAvailable())
.stockQuantity(this.getStockQuantity())
.build();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.campustable.be.domain.menu.dto.MenuUpdateRequest;
import com.campustable.be.domain.menu.entity.Menu;
import com.campustable.be.domain.menu.repository.MenuRepository;
import com.campustable.be.domain.s3.service.S3Service;
import com.campustable.be.global.exception.CustomException;
import com.campustable.be.global.exception.ErrorCode;
import java.util.ArrayList;
Expand All @@ -19,6 +20,7 @@
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Optional;
import org.springframework.web.multipart.MultipartFile;


@Slf4j
Expand All @@ -29,37 +31,79 @@ public class MenuService {
private final MenuRepository menuRepository;
private final CategoryRepository categoryRepository;
private final CafeteriaService cafeteriaService;
private final S3Service s3Service;


@Transactional
public MenuResponse createMenu(MenuRequest request) {
public MenuResponse createMenu(MenuRequest request, MultipartFile image) {

Category category = categoryRepository.findById(request.getCategoryId())
.orElseThrow(() -> {
log.warn("createMenu: 유효하지 않은 category id");
return new CustomException(ErrorCode.CATEGORY_NOT_FOUND);
});

Optional<Menu> existingMenu = menuRepository.findByCategoryAndMenuName(
category,
request.getMenuName()
);
menuRepository.findByCategoryAndMenuName(category, request.getMenuName())
.ifPresent(menu -> {
log.error("createMenu: 이미 해당 카테고리에 menu가 존재합니다. 생성이 아닌 수정을 통해 진행해주세요.");
throw new CustomException(ErrorCode.MENU_ALREADY_EXISTS);
});

Menu menu = request.toEntity(category);
Menu savedMenu = menuRepository.save(menu);

if (existingMenu.isPresent()) {
log.error("createMenu: 이미 해당 카테고리에 menu가 존재합니다. 생성이 아닌 수정을 통해 진행해주세요.");
throw new CustomException(ErrorCode.MENU_ALREADY_EXISTS);
if (image != null && !image.isEmpty()) {
return uploadMenuImage(savedMenu.getId(), image);
}

Menu menu = request.toEntity(category);
return MenuResponse.from(menuRepository.save(menu));
return MenuResponse.from(savedMenu);

}

@Transactional
public MenuResponse uploadMenuImage(Long menuId, MultipartFile image) {
Menu menu = menuRepository.findById(menuId)
.orElseThrow(() -> new CustomException(ErrorCode.MENU_NOT_FOUND));

if (image == null || image.isEmpty()) {
throw new CustomException(ErrorCode.INVALID_FILE_REQUEST);
}

String oldUrl = menu.getMenuUrl();
String cafeteriaName = menu.getCategory().getCafeteria().getName();
String dirName = "menu/" + cafeteriaName;

String newUrl = s3Service.uploadFile(image, dirName);
menu.setMenuUrl(newUrl);
Menu savedMenu;
try {
savedMenu = menuRepository.save(menu);
} catch (Exception e) {
try {
s3Service.deleteFile(newUrl);
} catch (Exception ex) {
log.warn("uploadMenuImage: 신규 이미지 정리 실패. newUrl={}", newUrl, ex);
}
log.error("uploadMenuImage: 메뉴 저장 실패. menuId={}", menuId, e);
throw e;
}

if (oldUrl != null && !oldUrl.isBlank()) {
try {
s3Service.deleteFile(oldUrl);
} catch (Exception e) {
log.warn("uploadMenuImage: 기존 이미지 삭제 실패. oldUrl={}", oldUrl, e);
}
}

return MenuResponse.from(savedMenu);
}

@Transactional(readOnly = true)
public MenuResponse getMenuById(Long menuId) {

Menu menu = menuRepository.findById(menuId)
.orElseThrow(()->{
.orElseThrow(() -> {
log.error("getMenuById : 유효하지않은 menuId");
return new CustomException(ErrorCode.MENU_NOT_FOUND);
});
Expand Down Expand Up @@ -137,9 +181,15 @@ public void deleteMenu(Long menuId) {
if (menu.isEmpty()) {
log.error("menuId not found {}", menuId);
throw new CustomException(ErrorCode.MENU_NOT_FOUND);
} else {
menuRepository.delete(menu.get());
}
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);
}

}
Expand Down
Loading