Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
5 changes: 3 additions & 2 deletions recruitment-service/build.gradle
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.5'
id 'io.spring.dependency-management' version '1.1.7'
id 'org.springframework.boot' version '3.1.5'
id 'io.spring.dependency-management' version '1.1.3'
}

group = 'com.example'
Expand All @@ -28,6 +28,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,13 @@

import com.example.recruitment.dto.RecruitmentDetailDto;
import com.example.recruitment.dto.RecruitmentRequestDto;
import com.example.recruitment.dto.order.OrderRequestDto;
import com.example.recruitment.entity.Recruitment;
import com.example.recruitment.entity.RecruitmentParticipant;
import com.example.recruitment.entity.Store;
import com.example.recruitment.entity.User;
import com.example.recruitment.repository.RecruitmentParticipantRepository;
import com.example.recruitment.repository.RecruitmentRepository;
import com.example.recruitment.repository.StoreRepository;
import com.example.recruitment.repository.UserRepository;

import com.example.recruitment.service.RecruitmentService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
Expand All @@ -21,29 +19,27 @@

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/recruitments")
@RequestMapping("/api/v1/recruitments")
public class RecruitmentController {

private final RecruitmentService recruitmentService;
private final RecruitmentRepository recruitmentRepository;
private final UserRepository userRepository;
private final StoreRepository storeRepository;
private final RecruitmentParticipantRepository participantRepository;

// 모집글 생성
//모집글 생성 (Order 서버에 주문 생성 포함)
@PostMapping
public Recruitment createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
User user = userRepository.findById(dto.getUserId()).orElseThrow();
Store store = storeRepository.findById(dto.getStoreId()).orElseThrow();

Recruitment recruitment = new Recruitment();
recruitment.setUser(user);
recruitment.setStore(store);
recruitment.setTitle(dto.getTitle());
recruitment.setDescription(dto.getDescription());
recruitment.setDeadlineTime(dto.getDeadlineTime());
recruitment.setStatus("RECRUITING");
public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
recruitmentService.createRecruitment(dto);
return ResponseEntity.ok("모집글 생성 완료");
}
Comment on lines +29 to +34
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

RESTful API 모범 사례를 따르도록 개선이 필요합니다.

생성된 리소스의 정보를 반환하고 적절한 HTTP 상태 코드를 사용하세요.

-    public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
-        recruitmentService.createRecruitment(dto);
-        return ResponseEntity.ok("모집글 생성 완료");
+    public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
+        Long recruitmentId = recruitmentService.createRecruitment(dto);
+        return ResponseEntity.created(URI.create("/api/v1/recruitments/" + recruitmentId))
+                .body(Map.of("id", recruitmentId, "message", "모집글이 생성되었습니다"));

서비스 메서드도 생성된 ID를 반환하도록 수정이 필요합니다.

📝 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
//모집글 생성 (Order 서버에 주문 생성 포함)
@PostMapping
public Recruitment createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
User user = userRepository.findById(dto.getUserId()).orElseThrow();
Store store = storeRepository.findById(dto.getStoreId()).orElseThrow();
Recruitment recruitment = new Recruitment();
recruitment.setUser(user);
recruitment.setStore(store);
recruitment.setTitle(dto.getTitle());
recruitment.setDescription(dto.getDescription());
recruitment.setDeadlineTime(dto.getDeadlineTime());
recruitment.setStatus("RECRUITING");
public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
recruitmentService.createRecruitment(dto);
return ResponseEntity.ok("모집글 생성 완료");
}
//모집글 생성 (Order 서버에 주문 생성 포함)
@PostMapping
public ResponseEntity<?> createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
Long recruitmentId = recruitmentService.createRecruitment(dto);
return ResponseEntity.created(URI.create("/api/v1/recruitments/" + recruitmentId))
.body(Map.of("id", recruitmentId, "message", "모집글이 생성되었습니다"));
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 29 to 34, the createRecruitment method currently returns a generic
success message with HTTP 200. Modify the recruitmentService.createRecruitment
method to return the ID of the newly created resource, then update the
controller to return this ID in the response body with HTTP status 201 Created,
following RESTful best practices.


return recruitmentRepository.save(recruitment);
//모집글 참여 (Order 서버에 주문 생성 포함)
@PostMapping("/{recruitmentId}/join")
public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId,
@RequestParam Long userId,
@RequestBody OrderRequestDto orderRequestDto) {
recruitmentService.joinRecruitment(recruitmentId, userId, orderRequestDto);
return ResponseEntity.ok("모집글 참여 완료");
}
Comment on lines +36 to 43
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

API 설계 개선이 필요합니다.

userId를 쿼리 파라미터로 받는 것보다 인증 정보에서 추출하는 것이 보안상 더 안전합니다.

-    @PostMapping("/{recruitmentId}/join")
-    public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId,
-                                             @RequestParam Long userId,
-                                             @RequestBody OrderRequestDto orderRequestDto) {
-        recruitmentService.joinRecruitment(recruitmentId, userId, orderRequestDto);
+    @PostMapping("/{recruitmentId}/join")
+    public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId,
+                                             @RequestBody OrderRequestDto orderRequestDto,
+                                             @AuthenticationPrincipal UserDetails userDetails) {
+        Long userId = getUserIdFromAuth(userDetails);
+        recruitmentService.joinRecruitment(recruitmentId, userId, orderRequestDto);

향후 Spring Security를 통한 인증 구현을 고려하세요.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 36 to 43, the userId is currently received as a request parameter,
which is less secure. Modify the method to extract the userId from the
authentication principal or security context instead of accepting it as a query
parameter. This involves integrating Spring Security or a similar authentication
mechanism to obtain the authenticated user's ID securely within the controller
method.


// 모집글 전체 조회
Expand All @@ -52,84 +48,56 @@ public List<Recruitment> getAll() {
return recruitmentRepository.findAll();
}

// 상태별 조회 (ex. /api/recruitments?status=RECRUITING)
// 상태별 조회
@GetMapping(params = "status")
public List<Recruitment> getByStatus(@RequestParam String status) {
return recruitmentRepository.findByStatus(status);
}

// 모집글 상세 조회
@GetMapping("/{recruitmentId}")
public ResponseEntity<?> getRecruitmentDetail(@PathVariable Long recruitmentId) {
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();

// 참여자 목록 조회
List<RecruitmentParticipant> participantEntities =
participantRepository.findByRecruitmentId(recruitmentId);

List<User> participants = participantEntities.stream()
.map(RecruitmentParticipant::getUser)
.toList();

// DTO 구성
RecruitmentDetailDto dto = new RecruitmentDetailDto();
dto.setId(recruitment.getId());
dto.setTitle(recruitment.getTitle());
dto.setDescription(recruitment.getDescription());
dto.setStatus(recruitment.getStatus());
dto.setDeadlineTime(recruitment.getDeadlineTime());
dto.setUser(recruitment.getUser());
dto.setStore(recruitment.getStore());
dto.setParticipants(participants);

return ResponseEntity.ok(dto);
}

@GetMapping("/user/{userId}/created-recruitments")
public List<Recruitment> getRecruitmentsCreatedByUser(@PathVariable Long userId) {
return recruitmentRepository.findByUserId(userId);
}

// 특정 유저가 참여한 모집글 조회
@GetMapping("/user/{userId}/joined-recruitments")
public List<Recruitment> getRecruitmentsJoinedByUser(@PathVariable Long userId) {
List<RecruitmentParticipant> participantList = participantRepository.findByUserId(userId);
return participantList.stream()
.map(RecruitmentParticipant::getRecruitment)
.toList();
}




// 모집글 참여
@PostMapping("/{recruitmentId}/join")
public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId, @RequestParam Long userId) {
User user = userRepository.findById(userId).orElseThrow();
public ResponseEntity<?> getRecruitmentDetail(@PathVariable Long recruitmentId) {
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();

boolean alreadyJoined = participantRepository
.findByUserIdAndRecruitmentId(userId, recruitmentId)
.isPresent();

if (alreadyJoined) {
return ResponseEntity.badRequest().body("이미 참여한 모집입니다.");
}
List<RecruitmentParticipant> participants =
participantRepository.findByRecruitmentId(recruitmentId);
List<User> participantUsers = participants.stream()
.map(RecruitmentParticipant::getUser)
.toList();

RecruitmentDetailDto dto = new RecruitmentDetailDto();
dto.setId(recruitment.getId());
dto.setTitle(recruitment.getTitle());
dto.setDescription(recruitment.getDescription());
dto.setStatus(recruitment.getStatus());
dto.setDeadlineTime(recruitment.getDeadlineTime());
dto.setUser(recruitment.getUser());
dto.setStore(recruitment.getStore());
dto.setParticipants(participantUsers);

return ResponseEntity.ok(dto);
}
Comment on lines +57 to +79
Copy link

Choose a reason for hiding this comment

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

💡 Verification agent

🧩 Analysis chain

DTO 생성 방식 개선 및 성능 최적화가 필요합니다.

-        RecruitmentDetailDto dto = new RecruitmentDetailDto();
-        dto.setId(recruitment.getId());
-        dto.setTitle(recruitment.getTitle());
-        dto.setDescription(recruitment.getDescription());
-        dto.setStatus(recruitment.getStatus());
-        dto.setDeadlineTime(recruitment.getDeadlineTime());
-        dto.setUser(recruitment.getUser());
-        dto.setStore(recruitment.getStore());
-        dto.setParticipants(participantUsers);
+        RecruitmentDetailDto dto = RecruitmentDetailDto.builder()
+                .id(recruitment.getId())
+                .title(recruitment.getTitle())
+                .description(recruitment.getDescription())
+                .status(recruitment.getStatus())
+                .deadlineTime(recruitment.getDeadlineTime())
+                .user(recruitment.getUser())
+                .store(recruitment.getStore())
+                .participants(participantUsers)
+                .build();

N+1 쿼리 문제 확인:


🏁 Script executed:

#!/bin/bash
# RecruitmentParticipantRepository에서 fetch join 사용 여부 확인
rg -A 5 "findByRecruitmentId" --glob "*.java"

Length of output: 1776


🏁 Script executed:

#!/bin/bash
# RecruitmentDetailDto 클래스 정의 및 Lombok @Builder 어노테이션 확인
rg -A 5 "class RecruitmentDetailDto" --glob "*.java"
rg "@Builder" --glob "*RecruitmentDetailDto.java"

Length of output: 814


Builder 패턴 적용 및 N+1 쿼리 최적화 필요

RecruitmentDetailDto에 Lombok @builder가 적용되어 있지 않아 제안한 builder 생성 코드가 동작하지 않으며, findByRecruitmentId 호출 시 RecruitmentParticipant.user가 LAZY 로딩일 경우 participants 수만큼 추가 쿼리가 발생할 수 있습니다.

• RecruitmentDetailDto 클래스(recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentDetailDto.java)
– Lombok @builder, @AllArgsConstructor, @NoArgsConstructor 어노테이션 추가 또는 직접 Builder 구현
예시:

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class RecruitmentDetailDto { … }

• RecruitmentParticipantRepository 인터페이스(recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java)
– fetch join을 사용해 N+1 문제 방지

@Query("SELECT p FROM RecruitmentParticipant p JOIN FETCH p.user WHERE p.recruitment.id = :recruitmentId")
List<RecruitmentParticipant> findByRecruitmentIdFetchUser(@Param("recruitmentId") Long recruitmentId);

또는 Spring Data JPA EntityGraph 사용

위 사항을 반영해 DTO 생성과 데이터 조회 방식을 개선해주세요.

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
lines 57-79, refactor the DTO creation to use the Lombok @Builder pattern by
first adding @Builder, @NoArgsConstructor, and @AllArgsConstructor annotations
to the RecruitmentDetailDto class. Also, modify the participantRepository query
to use a fetch join to eagerly load the User entities and avoid N+1 queries by
defining a new method like findByRecruitmentIdFetchUser with a JPQL query using
JOIN FETCH. Then update the controller to call this new repository method and
build the RecruitmentDetailDto using the builder for cleaner and more efficient
code.


RecruitmentParticipant participant = new RecruitmentParticipant();
participant.setUser(user);
participant.setRecruitment(recruitment);
participant.setJoinedAt(LocalDateTime.now());
// 유저가 만든 모집글
@GetMapping("/user/{userId}/created-recruitments")
public List<Recruitment> getRecruitmentsCreatedByUser(@PathVariable Long userId) {
return recruitmentRepository.findByUserId(userId);
}

participantRepository.save(participant);
return ResponseEntity.ok("모집글 참여 완료");
// 유저가 참여한 모집글
@GetMapping("/user/{userId}/joined-recruitments")
public List<Recruitment> getRecruitmentsJoinedByUser(@PathVariable Long userId) {
List<RecruitmentParticipant> participantList = participantRepository.findByUserId(userId);
return participantList.stream()
.map(RecruitmentParticipant::getRecruitment)
.toList();
}

// 모집 상태 업데이트 (CONFIRMED 또는 FAILED)
// 모집 상태 업데이트
@PatchMapping("/{recruitmentId}/status")
public ResponseEntity<?> updateRecruitmentStatus(@PathVariable Long recruitmentId) {
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();
LocalDateTime now = LocalDateTime.now();

long participantCount = participantRepository.countByRecruitmentId(recruitmentId);

if (now.isAfter(recruitment.getDeadlineTime())) {
Expand All @@ -145,68 +113,56 @@ public ResponseEntity<?> updateRecruitmentStatus(@PathVariable Long recruitmentI
}
}

// 주문 수락 상태로 변경 (ACCEPTED)
// 주문 수락 상태 변경
@PatchMapping("/{recruitmentId}/accept")
public ResponseEntity<?> acceptRecruitment(@PathVariable Long recruitmentId) {
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();

if (!"CONFIRMED".equals(recruitment.getStatus())) {
return ResponseEntity.badRequest().body("주문 수락은 CONFIRMED 상태에서만 가능합니다.");
}

recruitment.setStatus("ACCEPTED");
recruitmentRepository.save(recruitment);

return ResponseEntity.ok("상태가 ACCEPTED로 변경되었습니다.");
}

// 배달 완료 상태로 변경 (DELIVERED)
// 배달 완료 상태 변경
@PatchMapping("/{recruitmentId}/deliver")
public ResponseEntity<?> completeDelivery(@PathVariable Long recruitmentId) {
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();

if (!"ACCEPTED".equals(recruitment.getStatus())) {
return ResponseEntity.badRequest().body("배달 완료는 ACCEPTED 상태에서만 가능합니다.");
}

recruitment.setStatus("DELIVERED");
recruitmentRepository.save(recruitment);

return ResponseEntity.ok("상태가 DELIVERED로 변경되었습니다.");
}
// 모집글 삭제
@DeleteMapping("/{recruitmentId}")
public ResponseEntity<?> deleteRecruitment(@PathVariable Long recruitmentId) {
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();

recruitmentRepository.delete(recruitment);
return ResponseEntity.ok("모집글이 삭제되었습니다.");
}

// 모집글 수정
@PutMapping("/{recruitmentId}")
public ResponseEntity<?> updateRecruitment(@PathVariable Long recruitmentId,
@Valid @RequestBody RecruitmentRequestDto dto) {
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();

// (선택) 본인 작성한 글인지 확인
if (!recruitment.getUser().getId().equals(dto.getUserId())) {
return ResponseEntity.status(403).body("작성자만 수정할 수 있습니다.");
// 모집글 삭제
@DeleteMapping("/{recruitmentId}")
public ResponseEntity<?> deleteRecruitment(@PathVariable Long recruitmentId) {
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();
recruitmentRepository.delete(recruitment);
return ResponseEntity.ok("모집글이 삭제되었습니다.");
}

// 수정할 항목들 업데이트
recruitment.setTitle(dto.getTitle());
recruitment.setDescription(dto.getDescription());
recruitment.setDeadlineTime(dto.getDeadlineTime());
// 모집글 수정
@PutMapping("/{recruitmentId}")
public ResponseEntity<?> updateRecruitment(@PathVariable Long recruitmentId,
@Valid @RequestBody RecruitmentRequestDto dto) {
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();
if (!recruitment.getUser().getId().equals(dto.getUserId())) {
return ResponseEntity.status(403).body("작성자만 수정할 수 있습니다.");
}

// (선택) 가게 변경도 허용
if (dto.getStoreId() != null) {
Store store = storeRepository.findById(dto.getStoreId()).orElseThrow();
recruitment.setStore(store);
}
recruitment.setTitle(dto.getTitle());
recruitment.setDescription(dto.getDescription());
recruitment.setDeadlineTime(dto.getDeadlineTime());

recruitmentRepository.save(recruitment);
return ResponseEntity.ok("모집글이 수정되었습니다.");
}
if (dto.getStoreId() != null) {
recruitment.setStore(recruitment.getStore());
}
Comment on lines +161 to +163
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

치명적인 버그: Store 업데이트가 작동하지 않습니다.

현재 코드는 자기 자신을 설정하고 있어 아무런 변경이 일어나지 않습니다.

         if (dto.getStoreId() != null) {
-            recruitment.setStore(recruitment.getStore());
+            Store newStore = storeRepository.findById(dto.getStoreId())
+                    .orElseThrow(() -> new StoreNotFoundException("가게를 찾을 수 없습니다. ID: " + dto.getStoreId()));
+            recruitment.setStore(newStore);
         }

이를 위해 컨트롤러에 StoreRepository 의존성을 다시 추가하거나, 서비스 레이어에서 처리하도록 리팩토링하세요.

📝 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
if (dto.getStoreId() != null) {
recruitment.setStore(recruitment.getStore());
}
if (dto.getStoreId() != null) {
Store newStore = storeRepository.findById(dto.getStoreId())
.orElseThrow(() -> new StoreNotFoundException("가게를 찾을 수 없습니다. ID: " + dto.getStoreId()));
recruitment.setStore(newStore);
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 161 to 163, the code incorrectly sets
recruitment.setStore(recruitment.getStore()), which does not update the store.
To fix this, inject StoreRepository into the controller or delegate the store
update logic to the service layer, then fetch the Store entity by
dto.getStoreId() and set it on recruitment properly.


recruitmentRepository.save(recruitment);
return ResponseEntity.ok("모집글이 수정되었습니다.");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.recruitment.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/swagger-test")
public class SwaggerTestController {
@GetMapping
public String test() {
return "Swagger works!";
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,14 @@
package com.example.recruitment.dto;

import com.example.recruitment.dto.order.OrderRequestDto;
import lombok.Getter;
import lombok.Setter;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;

import java.time.LocalDateTime;

import lombok.Getter;
import lombok.Setter;
import java.util.List;

@Getter
@Setter
Expand All @@ -27,25 +28,19 @@ public class RecruitmentRequestDto {

@NotNull(message = "마감 시간은 필수입니다.")
private LocalDateTime deadlineTime;
}



//원래 dto 코드
/*
* package com.example.recruitment.dto;
@NotBlank(message = "카테고리는 비어 있을 수 없습니다.")
private String category;

import java.time.LocalDateTime;
// 주문용 메뉴 정보
private List<OrderRequestDto.MenuDto> menus;

import lombok.Getter;
import lombok.Setter;

@Getter @Setter
public class RecruitmentRequestDto {
private Long userId;
private Long storeId;
private String title;
private String description;
private LocalDateTime deadlineTime;
//OrderRequestDto 변환 메서드
public OrderRequestDto toOrderRequestDto() {
OrderRequestDto dto = new OrderRequestDto();
dto.setUserId(this.userId);
dto.setStoreId(this.storeId);
dto.setMenus(this.menus);
return dto;
}
}
*/
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.example.recruitment.dto.order;

import java.util.List;

import lombok.Getter;
import lombok.Setter;

@Getter
@Setter

public class OrderRequestDto {
private Long groupId;
private Long userId;
private Long storeId;
private List<MenuDto> menus;

@Getter @Setter
public static class MenuDto {
private Long menuId;
private String menuName;
private int basePrice;
private int count;
private List<OptionDto> options;
}

@Getter @Setter
public static class OptionDto {
private Long optionId;
private String optionName;
private int price;
}
}
Loading
Loading