Skip to content

Conversation

@ehddms0320
Copy link

@ehddms0320 ehddms0320 commented May 30, 2025

기능: Swagger 문서화

  • springdoc-openapi 2.3.0 적용
  • /swagger-ui/index.html 정상 작동
  • 모집글 관련 API 자동 문서화 확인

Summary by CodeRabbit

  • 신규 기능

    • Spring Boot 기반의 리크루트 서비스 애플리케이션이 추가되었습니다.
    • 모집글 생성, 참여, 상태 변경, 삭제, 수정 등 모집 관련 REST API가 제공됩니다.
    • 사용자, 매장(스토어) 등록 및 조회 API가 추가되었습니다.
    • 모집글 상세 조회, 참여자 조회, 사용자가 생성·참여한 모집글 조회 기능이 포함됩니다.
    • Swagger 테스트 및 서버 정상 동작 확인용 엔드포인트가 추가되었습니다.
  • CI/CD 및 배포

    • 각 서비스(리크루트, 주문, 결제, 스토어, 사용자)에 대한 자동 빌드 및 배포 워크플로우가 도입되었습니다.
    • Docker 및 EC2 연동을 통한 자동 배포가 지원됩니다.
  • 예외 처리 및 응답 표준화

    • 표준 API 응답 포맷 및 글로벌 예외 처리 기능이 추가되었습니다.
    • 입력값 검증 오류 및 커스텀 예외에 대한 일관된 에러 응답이 제공됩니다.
  • 기타

    • 프로젝트 빌드 및 환경설정 파일(.gitignore, .gitattributes, build.gradle, application.properties 등)이 추가되었습니다.
    • 기본 테스트 코드가 포함되어 애플리케이션 구동 검증이 가능합니다.

@coderabbitai
Copy link

coderabbitai bot commented May 30, 2025

Walkthrough

이 변경은 recruitment-service 모듈의 전체 소스 코드, Gradle 빌드 및 설정 파일, 그리고 여러 서비스별 CI/CD GitHub Actions 워크플로우를 추가합니다. 주요 도메인 엔티티, 컨트롤러, 서비스, 예외 처리, API 응답 구조, 테스트, 그리고 배포 자동화 구성이 포함되어 있습니다.

Changes

파일/경로 요약 변경 요약
.github/workflows/ci-cd-*.yml recruitment-service, order-service, payment-service, store-service, user-service에 대한 GitHub Actions 기반 CI/CD 워크플로우 파일 추가. 각 워크플로우는 빌드, Docker 이미지 생성 및 EC2 배포 자동화 포함. recruitment-service 워크플로우의 EC2_SSH_USER 환경변수 값 변경.
recruitment-service/.gitignore, recruitment-service/.gitattributes Git 및 라인 엔딩 관리 파일 추가: IDE/빌드 산출물 무시, 파일별 줄바꿈 규칙 명시.
recruitment-service/build.gradle, settings.gradle, gradle/wrapper/gradle-wrapper.properties,
gradlew, gradlew.bat
Gradle 빌드 설정, 래퍼 스크립트 및 환경 구성 파일 추가.
recruitment-service/src/main/resources/application.properties Spring Boot 및 데이터베이스(MySQL) 연결, JPA, 외부 주문서버 URL 등 애플리케이션 설정 추가.
recruitment-service/src/main/java/com/example/recruitment/RecruitmentApplication.java Spring Boot 메인 클래스 추가.
recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java 서버 정상 작동 확인용 간단한 테스트 컨트롤러 추가.
recruitment-service/src/main/java/com/example/recruitment/common/ApiResponse.java,
FieldErrorDetail.java
API 응답 표준화 및 필드 에러 정보 구조체 추가.
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java,
StoreController.java,
SwaggerTestController.java,
UserController.java
리크루트, 스토어, 유저 관련 REST 컨트롤러 및 Swagger 테스트 엔드포인트 추가.
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentDetailDto.java,
RecruitmentRequestDto.java,
order/OrderRequestDto.java,
order/OrderResponseDto.java
리크루트 및 주문 관련 DTO 클래스 추가.
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java,
RecruitmentParticipant.java,
Store.java,
User.java
리크루트, 참가자, 스토어, 유저 JPA 엔티티 클래스 추가.
recruitment-service/src/main/java/com/example/recruitment/exception/CustomException.java,
ErrorCode.java,
GlobalExceptionHandler.java
커스텀 예외, 에러 코드, 전역 예외 핸들러 추가.
recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java,
RecruitmentRepository.java,
StoreRepository.java,
UserRepository.java
JPA 리포지토리 인터페이스 추가.
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java,
RecruitmentService.java
외부 주문 서비스 연동 및 리크루트 비즈니스 로직 서비스 클래스 추가.
recruitment-service/src/test/java/com/example/recruitment/RecruitmentApplicationTests.java Spring Boot 컨텍스트 로드 테스트 클래스 추가.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant RecruitmentController
    participant RecruitmentService
    participant UserRepository
    participant StoreRepository
    participant RecruitmentRepository
    participant OrderClient
    participant OrderServer

    Client->>RecruitmentController: POST /api/v1/recruitments
    RecruitmentController->>RecruitmentService: createRecruitment(dto)
    RecruitmentService->>UserRepository: findById(userId)
    RecruitmentService->>StoreRepository: findById(storeId)
    RecruitmentService->>RecruitmentRepository: save(new Recruitment)
    RecruitmentService->>OrderClient: createOrder(orderRequestDto)
    OrderClient->>OrderServer: POST /api/v1/orders
    OrderServer-->>OrderClient: 응답 (orderId)
    OrderClient-->>RecruitmentService: orderId
    RecruitmentService->>RecruitmentRepository: save(Recruitment with orderId)
    RecruitmentService-->>RecruitmentController: 완료
    RecruitmentController-->>Client: 응답
Loading

Possibly related PRs

  • #16: CI/CD 워크플로우에서 Docker Compose 명령어 변경 등 배포 자동화 관련 워크플로우 수정.
  • #39: recruitment-service CI/CD 워크플로우 파일에 환경변수 및 application.properties 생성 단계 추가.
  • #41: ApiResponse, ErrorCode, 전역 예외 핸들러 등 공통 API 응답 및 예외 처리 구조 추가(본 PR의 일부 코드 포함).

Suggested labels

enhancement

Poem

🐇
새싹처럼 솟아난 서비스 땅,
컨트롤러, 엔티티, CI/CD도 반짝!
에러는 예쁘게 포장하고,
주문도 외부로 척척 전송!
토끼는 기뻐 폴짝 뛰네,
새 프로젝트의 시작을 축하해!
🌱🚀

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

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: 30

🔭 Outside diff range comments (1)
recruitment-service/.github/workflows/ci-cd-payment-service.yml (1)

1-72: 🛠️ Refactor suggestion

워크플로우 템플릿화를 고려하여 중복을 줄이세요.

이 워크플로우는 앞의 order-service, user-service 워크플로우와 거의 동일한 구조와 문제점을 가지고 있습니다. 중복을 줄이기 위해 재사용 가능한 워크플로우 템플릿 사용을 고려해보세요.

GitHub Actions의 재사용 가능한 워크플로우를 사용하여 중복을 줄일 수 있습니다:

  1. .github/workflows/reusable-service-ci-cd.yml 생성
  2. 각 서비스별 워크플로우에서 해당 템플릿 호출
  3. 서비스명을 파라미터로 전달

이렇게 하면 유지보수성이 크게 향상됩니다.

♻️ Duplicate comments (3)
recruitment-service/.github/workflows/ci-cd-user-service.yml (2)

1-1: 워크플로우 이름을 더 구체적으로 수정하세요.

order-service 워크플로우와 동일한 문제입니다. user-service를 위한 워크플로우임을 명확히 해야 합니다.

-name: CI/CD for Spring Boot + MySQL with Docker Compose
+name: CI/CD for User Service - Spring Boot + MySQL with Docker Compose

62-71: 🛠️ Refactor suggestion

SSH Action 버전 업데이트 및 배포 스크립트 개선이 필요합니다.

order-service 워크플로우와 동일한 문제들이 있습니다.

recruitment-service/.github/workflows/ci-cd-payment-service.yml (1)

10-10: 주석 일관성 문제를 수정하세요.

다른 워크플로우들과 동일한 주석 문제가 있습니다.

-  SERVICE_NAME: payment-service   # 예: order-service
+  SERVICE_NAME: payment-service   # 현재 서비스명

Also applies to: 18-18

🧹 Nitpick comments (32)
recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java (2)

10-13: 에러 코드 번호 체계 검토 권장
NOT_FOUND(2007), FORBIDDEN(2006) 순서가 숫자 오름차순과 어긋납니다. 팀 내 에러 코드 가이드에 맞춰 연속성 있게 재정렬하거나 의미별 그룹으로 관리하면 유지보수에 도움이 됩니다.


15-17: 필드 설명용 Javadoc 추가 제안
code, status, message에 대한 간단한 Javadoc 주석을 넣으면 IDE에서 문서를 확인하기 더 편리해집니다.

recruitment-service/settings.gradle (1)

1-1: rootProject.name 명칭 일관성 제안
현재 'recruitment'로 설정되어 있는데, 서비스 디렉토리명(recruitment-service) 및 CI/CD Workflow의 SERVICE_NAME(recruitment-service)과 맞추는 것을 권장합니다.

-rootProject.name = 'recruitment'
+rootProject.name = 'recruitment-service'
recruitment-service/.gitattributes (1)

1-3: 추가 파일 유형 EOL 관리 추천
.sh 스크립트와 YAML 파일에도 EOL 규칙을 추가하면 더 일관성 있는 크로스 플랫폼 지원이 가능합니다.

*.sh text eol=lf
*.yml text eol=lf
recruitment-service/gradle/wrapper/gradle-wrapper.properties (1)

4-4: 네트워크 타임아웃 값 재검토 제안
networkTimeout=10000은 네트워크 상황에 따라 짧을 수 있습니다. 다운로드 실패 가능성을 줄이기 위해 기본값(60000) 또는 그 이상으로 조정하는 것을 고려해 보세요.

recruitment-service/src/test/java/com/example/recruitment/RecruitmentApplicationTests.java (1)

1-13: 스모크 테스트: 애플리케이션 컨텍스트 로딩 확인
@SpringBootTestcontextLoads() 메서드를 통해 애플리케이션 컨텍스트가 정상적으로 로드되는지 검증합니다. 기본적인 통합 테스트로 적절하지만, 향후 MockMVC나 단위 테스트를 추가하여 컨트롤러·서비스 레이어 기능을 검증하면 더욱 좋습니다.

recruitment-service/src/main/java/com/example/recruitment/controller/SwaggerTestController.java (1)

7-14: 테스트 컨트롤러 구현이 적절함

Swagger 테스트용 컨트롤러가 올바르게 구현되었습니다. 기본적인 Spring MVC 패턴을 잘 따르고 있습니다.

선택적 개선사항으로 OpenAPI 문서화 어노테이션을 추가하는 것을 고려해보세요:

+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;

 @RestController
 @RequestMapping("/swagger-test")
+@Tag(name = "Swagger Test", description = "Swagger 연동 테스트 API")
 public class SwaggerTestController {
+    @Operation(summary = "Swagger 연동 테스트", description = "Swagger가 정상적으로 작동하는지 확인하는 테스트 엔드포인트")
     @GetMapping
     public String test() {
         return "Swagger works!";
     }
 }
recruitment-service/src/main/java/com/example/recruitment/common/FieldErrorDetail.java (1)

6-11: 데이터 클래스 구현이 적절함

Lombok을 활용한 깔끔한 데이터 클래스 구현입니다. 에러 처리를 위한 구조가 잘 설계되었습니다.

더 안전한 불변 객체로 만드는 것을 고려해보세요:

 @Getter
 @AllArgsConstructor
 public class FieldErrorDetail {
-    private String field;
-    private String reason;
+    private final String field;
+    private final String reason;
 }

또는 record 사용을 고려해보세요 (Java 14+):

public record FieldErrorDetail(String field, String reason) {
}
recruitment-service/src/main/java/com/example/recruitment/entity/MenuItem.java (1)

12-13: 금액 필드의 타입 개선 권장

price 필드가 int 타입으로 선언되어 있는데, 금액을 다루는 필드의 경우 정밀도와 소수점 처리를 위해 BigDecimal 타입 사용을 권장합니다. 또한 유효성 검증을 위한 어노테이션 추가도 고려해보세요.

+ import jakarta.validation.constraints.NotBlank;
+ import jakarta.validation.constraints.PositiveOrZero;
+ import java.math.BigDecimal;

- private String name;
- private int price;
+ @NotBlank(message = "메뉴 이름은 필수입니다")
+ private String name;
+ 
+ @PositiveOrZero(message = "가격은 0 이상이어야 합니다")
+ private BigDecimal price;
recruitment-service/src/main/java/com/example/recruitment/entity/Store.java (1)

6-18: JPA 엔티티 개선 제안

Store 엔티티에 다음 개선사항들을 고려해보세요:

  1. equals/hashCode 구현: JPA 엔티티에서는 연관관계 처리를 위해 필요합니다
  2. 유효성 검증: 필드들에 대한 검증 로직 추가
+ import jakarta.validation.constraints.NotBlank;
+ import java.util.Objects;

 @Entity
 @Getter @Setter
 @NoArgsConstructor
 @AllArgsConstructor
 public class Store {

     @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;

+    @NotBlank(message = "매장 이름은 필수입니다")
     private String name;
+    
+    @NotBlank(message = "카테고리는 필수입니다")
     private String category;
+    
+    @NotBlank(message = "위치는 필수입니다")
     private String location;
+    
+    @Override
+    public boolean equals(Object o) {
+        if (this == o) return true;
+        if (o == null || getClass() != o.getClass()) return false;
+        Store store = (Store) o;
+        return Objects.equals(id, store.id);
+    }
+    
+    @Override
+    public int hashCode() {
+        return Objects.hash(id);
+    }
 }
recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentRepository.java (1)

13-14: 불필요한 빈 줄 제거

파일 끝 부분에 불필요한 빈 줄이 있습니다. 코드 품질을 위해 제거해주세요.

     List<Recruitment> findByUserId(Long userId);

-    
-}
+}
recruitment-service/src/main/java/com/example/recruitment/controller/UserController.java (1)

20-22: 엔티티 직접 노출 대신 DTO 사용을 고려하세요.

컨트롤러에서 JPA 엔티티를 직접 반환하는 것은 보안 및 유지보수 측면에서 권장되지 않습니다. DTO(Data Transfer Object) 패턴 사용을 고려해보세요.

recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (1)

8-29: 엔티티 구조는 양호하지만 검증 및 감사 기능 추가를 권장합니다.

기본적인 JPA 엔티티 구조는 잘 설계되었습니다. 하지만 다음 개선사항들을 고려해보세요:

+import jakarta.validation.constraints.NotNull;
+import org.springframework.data.annotation.CreatedDate;
+import org.springframework.data.jpa.domain.support.AuditingEntityListener;

 @Entity
 @Getter @Setter
 @NoArgsConstructor
 @AllArgsConstructor
+@EntityListeners(AuditingEntityListener.class)
+@Table(indexes = {
+    @Index(name = "idx_user_recruitment", columnList = "user_id, recruitment_id", unique = true),
+    @Index(name = "idx_recruitment_joined_at", columnList = "recruitment_id, joined_at")
+})
 public class RecruitmentParticipant {

     @Id
     @GeneratedValue(strategy = GenerationType.IDENTITY)
     private Long id;

+    @NotNull
     @ManyToOne
     @JoinColumn(name = "user_id", nullable = false)
     private User user;

+    @NotNull
     @ManyToOne
     @JoinColumn(name = "recruitment_id", nullable = false)
     private Recruitment recruitment;

+    @CreatedDate
     @Column(name = "joined_at", nullable = false)
     private LocalDateTime joinedAt;
 }
recruitment-service/build.gradle (1)

26-38: 의존성 그룹화 및 순서 정리를 권장합니다.

가독성 향상을 위해 의존성을 기능별로 그룹화하고 정렬하는 것을 권장합니다:

 dependencies {
+	// Spring Boot Core
 	implementation 'org.springframework.boot:spring-boot-starter-web'
 	implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
 	implementation 'org.springframework.boot:spring-boot-starter-validation'
+	
+	// API Documentation
 	implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.2.0'
+	
+	// Database
+	runtimeOnly 'com.mysql:mysql-connector-j'
+	testImplementation 'com.h2database:h2'
+	
+	// Development Tools
 	compileOnly 'org.projectlombok:lombok'
+	annotationProcessor 'org.projectlombok:lombok'
 	developmentOnly 'org.springframework.boot:spring-boot-devtools'
-	runtimeOnly 'com.mysql:mysql-connector-j'
-	annotationProcessor 'org.projectlombok:lombok'
+	
+	// Testing
 	testImplementation 'org.springframework.boot:spring-boot-starter-test'
 	testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
-	testImplementation 'com.h2database:h2'
 }
recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java (2)

19-21: 불필요한 빈 줄을 제거해주세요.

코드 가독성을 위해 불필요한 빈 줄들을 정리해주세요.

     List<RecruitmentParticipant> findByRecruitmentId(Long recruitmentId);
     List<RecruitmentParticipant> findByUserId(Long userId);
-
-
-
 }

9-18: 유용한 추가 쿼리 메서드를 고려해보세요.

현재 쿼리 메서드들은 기본 요구사항을 잘 충족하지만, 다음과 같은 편의 메서드들을 추가하면 더 효율적인 API를 제공할 수 있습니다:

 public interface RecruitmentParticipantRepository extends JpaRepository<RecruitmentParticipant, Long> {
     // 특정 유저가 특정 모집글에 이미 참여했는지 확인
     Optional<RecruitmentParticipant> findByUserIdAndRecruitmentId(Long userId, Long recruitmentId);
+    
+    // 참여 여부만 확인하는 경우 더 효율적
+    boolean existsByUserIdAndRecruitmentId(Long userId, Long recruitmentId);

     // 특정 모집글에 참여한 인원 수 카운트
     long countByRecruitmentId(Long recruitmentId);

     List<RecruitmentParticipant> findByRecruitmentId(Long recruitmentId);
     List<RecruitmentParticipant> findByUserId(Long userId);
+    
+    // 최근 참여자 순으로 정렬
+    List<RecruitmentParticipant> findByRecruitmentIdOrderByJoinedAtDesc(Long recruitmentId);
 }
recruitment-service/src/main/java/com/example/recruitment/common/ApiResponse.java (1)

28-30: 유효성 검사 실패 응답 개선 제안

하드코딩된 한국어 메시지보다는 메시지 국제화(i18n)를 고려해보세요.

-return new ApiResponse<>(HttpStatus.BAD_REQUEST, false, 2001, "입력 값 유효성 검사 실패", null, errors);
+return new ApiResponse<>(HttpStatus.BAD_REQUEST, false, 2001, "Validation failed", null, errors);

또는 메시지를 매개변수로 받도록 오버로드 메서드를 추가하는 것을 고려해보세요.

recruitment-service/src/main/java/com/example/recruitment/entity/Order.java (1)

15-16: ID 생성 전략 명시 권장

@GeneratedValue에 전략을 명시하는 것이 좋습니다.

-@Id @GeneratedValue
+@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (2)

15-16: ID 생성 전략 명시 권장

다른 엔티티와의 일관성을 위해 생성 전략을 명시하는 것이 좋습니다.

-@Id @GeneratedValue
+@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

34-36: 카테고리 필드 개선 제안

카테고리도 Enum으로 관리하는 것을 고려해보세요. 또한 주석의 오타를 수정해야 합니다.

-//카테고리 추가가
+// 카테고리 추가
@Column(nullable = false)
private String category;

또는 Enum 사용:

-//카테고리 추가가
+// 카테고리
+@Enumerated(EnumType.STRING)
@Column(nullable = false)
-private String category;
+private RecruitmentCategory category;
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)

12-14: 생성자 추가 권장

DTO의 편의성을 위해 기본 생성자와 전체 필드 생성자를 추가하는 것이 좋습니다.

@Getter
@Setter
+@NoArgsConstructor
+@AllArgsConstructor
public class RecruitmentRequestDto {
recruitment-service/.github/workflows/ci-cd-order-service.yml (3)

1-1: 워크플로우 이름을 더 구체적으로 수정하세요.

현재 이름이 일반적인 "Spring Boot + MySQL with Docker Compose"로 되어 있어 order-service를 위한 워크플로우임을 명확히 하지 못합니다.

다음과 같이 수정하는 것을 권장합니다:

-name: CI/CD for Spring Boot + MySQL with Docker Compose
+name: CI/CD for Order Service - Spring Boot + MySQL with Docker Compose

10-10: 환경변수 주석을 현재 서비스에 맞게 수정하세요.

주석에서 "예: order-service"라고 되어 있는데, 실제로는 order-service를 설정하고 있어 혼란을 줄 수 있습니다.

주석을 다음과 같이 수정하세요:

-  SERVICE_NAME: order-service   # 예: order-service
+  SERVICE_NAME: order-service   # 현재 서비스명
-  PROJECT_PATH: /home/ec2-user/delivery-system/order-service # 예: /home/ec2-user/delivery-system/order-service
+  PROJECT_PATH: /home/ec2-user/delivery-system/order-service # order-service 배포 경로

Also applies to: 18-18


67-71: 배포 스크립트에 오류 처리 및 헬스체크 추가를 고려하세요.

현재 배포 스크립트에는 오류 처리나 배포 후 헬스체크가 없어 배포 실패 시 감지하기 어렵습니다.

다음과 같이 개선할 수 있습니다:

         script: |
+          set -e  # 오류 발생 시 스크립트 중단
           cd ${{ env.PROJECT_PATH }}
           sudo docker compose pull
           sudo docker compose down
           sudo docker compose up -d
+          # 서비스 헬스체크 (선택사항)
+          sleep 30
+          sudo docker compose ps
recruitment-service/.github/workflows/ci-cd-user-service.yml (1)

10-10: 환경변수 주석의 일관성 문제를 수정하세요.

SERVICE_NAME은 user-service로 설정되어 있지만 주석에는 여전히 "예: order-service"라고 되어 있어 일관성이 없습니다.

-  SERVICE_NAME: user-service   # 예: order-service
+  SERVICE_NAME: user-service   # 현재 서비스명
-  PROJECT_PATH: /home/ec2-user/delivery-system/user-service # 예: /home/ec2-user/delivery-system/order-service
+  PROJECT_PATH: /home/ec2-user/delivery-system/user-service # user-service 배포 경로

Also applies to: 18-18

recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (1)

16-22: CustomException 처리에도 로깅을 고려하세요.

비즈니스 로직 예외도 모니터링을 위해 로깅하는 것이 좋습니다.

     @ExceptionHandler(CustomException.class)
     public ResponseEntity<ApiResponse<Object>> handleCustomException(CustomException e) {
         ErrorCode ec = e.getErrorCode();
+        logger.warn("비즈니스 예외 발생: 코드={}, 메시지={}", ec.getCode(), ec.getMessage());
         return ResponseEntity
                 .status(ec.getStatus())
                 .body(ApiResponse.fail(ec.getCode(), ec.getMessage(), ec.getStatus()));
     }
recruitment-service/.github/workflows/ci-cd-store-service.yml (1)

10-10: 주석 언어 일관성 개선 필요

영문 워크플로우 파일에 한글 주석이 혼재되어 있습니다. 협업과 유지보수를 위해 영문으로 통일하는 것이 좋습니다.

-  SERVICE_NAME: store-service   # 예: order-service
+  SERVICE_NAME: store-service   # e.g., order-service
-  PROJECT_PATH: /home/ec2-user/delivery-system/store-service # 예: /home/ec2-user/delivery-system/order-service
+  PROJECT_PATH: /home/ec2-user/delivery-system/store-service # e.g., /home/ec2-user/delivery-system/order-service

Also applies to: 18-18

recruitment-service/.github/workflows/ci-cd-recruitment-service.yml (3)

35-35: YAML 포맷팅 오류 수정 필요

정적 분석 도구가 감지한 trailing spaces를 제거해야 합니다.

-        distribution: 'temurin'
-        
+        distribution: 'temurin'
+
-        echo "${{ env.APPLICATION }}" > "${{ env.SERVICE_NAME }}/src/main/resources/application.properties"
-        
+        echo "${{ env.APPLICATION }}" > "${{ env.SERVICE_NAME }}/src/main/resources/application.properties"
+

Also applies to: 40-40

🧰 Tools
🪛 YAMLlint (1.37.1)

[error] 35-35: trailing spaces

(trailing-spaces)


37-37: YAML 콜론 앞 공백 수정 필요

YAML 표준에 따라 콜론 앞의 불필요한 공백을 제거해야 합니다.

-      run : |
+      run: |
🧰 Tools
🪛 YAMLlint (1.37.1)

[warning] 37-37: too many spaces before colon

(colons)


10-10: 주석 언어 일관성 개선 필요

다른 워크플로우 파일과 일관성을 위해 영문 주석으로 통일하는 것이 좋습니다.

-  SERVICE_NAME: recruitment-service   # 예: order-service
+  SERVICE_NAME: recruitment-service   # e.g., order-service
-  PROJECT_PATH: /home/kmj02dev/delivery-system/recruitment-service # 예: /home/ec2-user/delivery-system/order-service
+  PROJECT_PATH: /home/kmj02dev/delivery-system/recruitment-service # e.g., /home/ec2-user/delivery-system/order-service

Also applies to: 18-18

recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (2)

62-85: 메서드 명명 규칙 및 Swagger 문서화

메서드 이름 getRecruitmentDetail은 일반적인 REST 컨벤션에 따라 getRecruitment 또는 getById가 더 적절합니다.

+    @Operation(summary = "모집글 상세 조회", description = "모집글의 상세 정보와 참여자 목록을 조회합니다.")
     @GetMapping("/{recruitmentId}")
-public ResponseEntity<?> getRecruitmentDetail(@PathVariable Long recruitmentId) {
+    public ResponseEntity<RecruitmentDetailDto> getRecruitment(@PathVariable Long recruitmentId) {

87-90: 들여쓰기 일관성 문제

메서드 선언의 들여쓰기가 일관되지 않습니다.

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

-// 특정 유저가 참여한 모집글 조회
+    // 특정 유저가 참여한 모집글 조회
     @GetMapping("/user/{userId}/joined-recruitments")
-public List<Recruitment> getRecruitmentsJoinedByUser(@PathVariable Long userId) {
+    public List<Recruitment> getRecruitmentsJoinedByUser(@PathVariable Long userId) {

Also applies to: 93-99

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6303a6e and d20dc23.

⛔ Files ignored due to path filters (1)
  • recruitment-service/gradle/wrapper/gradle-wrapper.jar is excluded by !**/*.jar
📒 Files selected for processing (39)
  • .github/workflows/ci-cd-recruitment-service.yml (1 hunks)
  • recruitment-service/.gitattributes (1 hunks)
  • recruitment-service/.github/workflows/ci-cd-order-service.yml (1 hunks)
  • recruitment-service/.github/workflows/ci-cd-payment-service.yml (1 hunks)
  • recruitment-service/.github/workflows/ci-cd-recruitment-service.yml (1 hunks)
  • recruitment-service/.github/workflows/ci-cd-store-service.yml (1 hunks)
  • recruitment-service/.github/workflows/ci-cd-user-service.yml (1 hunks)
  • recruitment-service/.gitignore (1 hunks)
  • recruitment-service/build.gradle (1 hunks)
  • recruitment-service/gradle/wrapper/gradle-wrapper.properties (1 hunks)
  • recruitment-service/gradlew (1 hunks)
  • recruitment-service/gradlew.bat (1 hunks)
  • recruitment-service/settings.gradle (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/RecruitmentApplication.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/common/ApiResponse.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/common/FieldErrorDetail.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/controller/StoreController.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/controller/SwaggerTestController.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/controller/UserController.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentDetailDto.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/entity/MenuItem.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/entity/Order.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/entity/Store.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/entity/User.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/exception/CustomException.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/repository/OrderRepository.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentRepository.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/repository/StoreRepository.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/repository/UserRepository.java (1 hunks)
  • recruitment-service/src/main/resources/application.properties (1 hunks)
  • recruitment-service/src/test/java/com/example/recruitment/RecruitmentApplicationTests.java (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (8)
recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java (1)
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (1)
  • RestController (22-212)
recruitment-service/src/main/java/com/example/recruitment/controller/UserController.java (2)
recruitment-service/src/main/java/com/example/recruitment/controller/StoreController.java (1)
  • RestController (12-28)
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (1)
  • RestController (22-212)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentDetailDto.java (1)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
  • Getter (12-33)
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (2)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1)
  • Entity (9-37)
recruitment-service/src/main/java/com/example/recruitment/entity/User.java (1)
  • Entity (6-17)
recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java (3)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
  • Getter (12-33)
recruitment-service/src/main/java/com/example/recruitment/common/FieldErrorDetail.java (1)
  • Getter (6-11)
recruitment-service/src/main/java/com/example/recruitment/exception/CustomException.java (1)
  • Getter (5-17)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (4)
recruitment-service/src/main/java/com/example/recruitment/entity/User.java (1)
  • Entity (6-17)
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (1)
  • Entity (8-29)
recruitment-service/src/main/java/com/example/recruitment/entity/Store.java (1)
  • Entity (6-18)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
  • Getter (12-33)
recruitment-service/src/main/java/com/example/recruitment/entity/Order.java (2)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1)
  • Entity (9-37)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
  • Getter (12-33)
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (3)
recruitment-service/src/main/java/com/example/recruitment/controller/StoreController.java (1)
  • RestController (12-28)
recruitment-service/src/main/java/com/example/recruitment/controller/UserController.java (1)
  • RestController (12-28)
recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java (1)
  • RestController (6-13)
🪛 YAMLlint (1.37.1)
recruitment-service/.github/workflows/ci-cd-recruitment-service.yml

[error] 35-35: trailing spaces

(trailing-spaces)


[warning] 37-37: too many spaces before colon

(colons)


[error] 40-40: trailing spaces

(trailing-spaces)

🔇 Additional comments (21)
recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java (2)

1-6: 패키지 및 import 구성이 적절합니다.
org.springframework.http.HttpStatus를 사용해 HTTP 상태를 명시한 점이 좋습니다.


7-9: Lombok 어노테이션 사용으로 코드가 간결해졌습니다.
@Getter, @AllArgsConstructor를 활용해 getter 생성 및 생성자 정의를 자동화한 부분이 적절합니다.

recruitment-service/.gitattributes (1)

1-3: 라인 엔딩 설정 적절합니다.
스크립트와 배치 파일, 바이너리 파일에 대한 EOL 강제화 규칙이 잘 구성되어 있습니다.

recruitment-service/gradle/wrapper/gradle-wrapper.properties (1)

1-7: Gradle Wrapper 설정이 표준에 맞습니다.
버전(8.13), 바이너리 분배, 네트워크 타임아웃 등이 적절하게 지정되어 있어 일관된 빌드를 보장합니다.

recruitment-service/src/main/java/com/example/recruitment/repository/UserRepository.java (1)

1-7: 표준 JPA 리포지토리 설정
UserRepositoryJpaRepository<User, Long>를 올바르게 확장하여 기본 CRUD 및 페이징 기능을 제공합니다. Spring Boot의 컴포넌트 스캔(com.example.recruitment 패키지 기준)이 이 인터페이스를 자동 감지하므로 별도의 애노테이션이 필요 없습니다.

recruitment-service/src/main/java/com/example/recruitment/repository/StoreRepository.java (1)

1-7: Store 엔티티용 JPA 리포지토리
StoreRepositoryJpaRepository<Store, Long>를 확장해 생성, 조회, 수정, 삭제 및 페이징 API를 제공하며, 애노테이션 없이도 Spring Data JPA가 자동 구성합니다.

recruitment-service/src/main/java/com/example/recruitment/repository/OrderRepository.java (1)

1-7: Order 엔티티용 JPA 리포지토리
OrderRepositoryJpaRepository<Order, Long>를 상속하여 주문 데이터를 위한 표준 CRUD 및 페이징 메서드를 제공합니다. 패키지 구조 및 스캔 범위가 올바르게 설정되어 있습니다.

recruitment-service/src/main/java/com/example/recruitment/RecruitmentApplication.java (1)

1-13: Spring Boot 진입점 설정
@SpringBootApplication이 적용된 RecruitmentApplication 클래스가 올바르게 구성되어 있습니다. SpringApplication.run()을 통해 애플리케이션이 정상적으로 부팅되며, 패키지 스캔 경로도 com.example.recruitment로 적절히 설정되어 있습니다.

recruitment-service/src/main/resources/application.properties (1)

7-9:

Details

❓ Verification inconclusive

JPA 설정 검토 권장

개발 환경에서는 적절하지만, 프로덕션 환경에서는 다음 사항을 고려해야 합니다:

  • ddl-auto=update는 프로덕션에서 데이터 손실 위험이 있습니다
  • show-sql=true는 성능에 영향을 줄 수 있습니다

환경별 프로파일 설정을 확인하기 위한 스크립트:


🏁 Script executed:

#!/bin/bash
# Description: 환경별 application 설정 파일 확인
# Test: application-{profile}.properties 파일 존재 여부 확인

fd "application-.*\.properties" --type f

Length of output: 42


프로덕션 환경 JPA 설정 검토 및 프로파일 분리 확인 필요

application.properties( Lines 7–9 )에 아래와 같이 설정되어 있습니다:

  • spring.jpa.hibernate.ddl-auto=update → 프로덕션에서 데이터 손실 위험
  • spring.jpa.show-sql=true → 성능 저하 우려

현재 application-*.properties 파일이 확인되지 않아 별도 프로덕션 프로파일 설정 유무를 알 수 없습니다.
프로덕션용 설정을 분리하고 다음을 적용해 주세요:

  • spring.jpa.hibernate.ddl-autonone 또는 validate로 변경
  • spring.jpa.show-sqlfalse로 설정

• 프로파일별 설정 파일 존재 여부 및 CI/CD 환경변수 관리 방식 검증
• 프로덕션 배포 전 위 변경사항이 반영되었는지 확인

recruitment-service/src/main/java/com/example/recruitment/exception/CustomException.java (1)

5-17:

Details

✅ Verification successful

커스텀 예외 클래스 구현이 적절함

표준적인 커스텀 예외 패턴을 잘 따르고 있으며, ErrorCode enum을 활용한 구조화된 에러 처리가 좋습니다.

ErrorCode enum의 존재를 확인하고, 선택적으로 예외 체이닝 지원을 고려해보세요:

예외 체이닝 지원을 위한 선택적 개선:

 public class CustomException extends RuntimeException {
     private final ErrorCode errorCode;

     public CustomException(ErrorCode errorCode) {
         this.errorCode = errorCode;
     }

+    public CustomException(ErrorCode errorCode, Throwable cause) {
+        super(cause);
+        this.errorCode = errorCode;
+    }

     @Override
     public String getMessage() {
         return errorCode.getMessage();
     }
 }

🏁 Script executed:

#!/bin/bash
# Description: ErrorCode enum 클래스 존재 여부 확인
# Test: ErrorCode.java 파일이 존재하는지 확인

fd "ErrorCode.java" --type f

Length of output: 111


CustomException 클래스: ErrorCode enum 확인 및 예외 체이닝 지원 제안

  • recruitment-service/src/main/java/com/example/recruitment/exception/ErrorCode.java 파일이 존재함을 확인했습니다.
  • CustomException 구현은 표준 패턴을 잘 따르고 있으며, ErrorCode enum을 활용한 구조화된 예외 처리가 적절합니다.
  • 예외 체이닝을 지원하려면 선택적으로 아래 생성자를 추가할 수 있습니다.

제안되는 변경사항 (선택 사항):

 public class CustomException extends RuntimeException {
     private final ErrorCode errorCode;

     public CustomException(ErrorCode errorCode) {
         this.errorCode = errorCode;
     }

+    public CustomException(ErrorCode errorCode, Throwable cause) {
+        super(cause);
+        this.errorCode = errorCode;
+    }

     @Override
     public String getMessage() {
         return errorCode.getMessage();
     }
 }
recruitment-service/src/main/java/com/example/recruitment/entity/MenuItem.java (1)

6-10: 엔티티 구조 승인

@embeddable 어노테이션과 Lombok 어노테이션들이 적절히 사용되었습니다. 전반적인 엔티티 구조가 깔끔하고 용도에 맞게 설계되었습니다.

recruitment-service/.gitignore (1)

1-37: 표준적인 .gitignore 설정 승인

Spring Boot 프로젝트에 적합한 .gitignore 파일입니다. 빌드 아티팩트, IDE 관련 파일들이 적절히 제외되어 있고, 주요 IDE들(STS, IntelliJ IDEA, NetBeans, VS Code)에 대한 설정이 포함되어 있습니다.

recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentRepository.java (1)

9-11: 리포지토리 인터페이스 승인

Spring Data JPA 규칙에 맞는 적절한 쿼리 메서드들이 정의되어 있습니다. findByStatusfindByUserId 메서드는 모집글 조회 기능에 필요한 핵심 기능들을 제공합니다.

recruitment-service/gradlew.bat (1)

1-95: 표준 Gradle wrapper 스크립트입니다.

이 파일은 Gradle에서 제공하는 표준 Windows 배치 스크립트로, 크로스 플랫폼 빌드 일관성을 보장합니다. 코드 구조와 로직이 표준 Gradle 배포본과 일치하며, 적절한 라이선스 헤더도 포함되어 있습니다.

recruitment-service/src/main/java/com/example/recruitment/common/ApiResponse.java (2)

8-15: API 응답 구조가 잘 설계되었습니다.

제네릭 record 클래스를 사용하여 일관된 API 응답 형식을 제공하는 좋은 접근 방식입니다. @JsonIgnore를 통해 HTTP 상태코드를 JSON 응답에서 제외하는 것도 적절합니다.


16-22: 성공 응답 팩토리 메서드가 잘 구현되었습니다.

okcreated 메서드가 각각 HTTP 200과 201 상태코드에 대응하며, 고유한 코드(1000, 1001)를 사용하는 것이 좋습니다.

recruitment-service/src/main/java/com/example/recruitment/entity/Order.java (1)

24-26:

Details

✅ Verification successful

MenuItem 엔티티 정의 확인 필요

@ElementCollection을 사용한 MenuItem이 임베디드 타입인지 확인이 필요합니다. 만약 MenuItem이 별도의 엔티티라면 @OneToMany 관계를 고려해야 합니다.

다음 스크립트로 MenuItem 클래스 정의를 확인해주세요:


🏁 Script executed:

#!/bin/bash
# MenuItem 클래스가 @Embeddable인지 @Entity인지 확인
ast-grep --pattern 'class MenuItem {
  $$$
}'

Length of output: 47


다음 스크립트로 MenuItem 클래스 파일 위치와 어노테이션 여부를 확인해주세요:


🏁 Script executed:

#!/bin/bash
# 1. MenuItem 클래스가 정의된 파일 경로 확인
rg -l 'class\s\+MenuItem' -R src/main/java

# 2. 클래스 선언 전후 3줄씩 출력하여 @Entity 또는 @Embeddable 어노테이션 확인
rg -n -C3 'class\s\+MenuItem' -R src/main/java

Length of output: 903


다음 스크립트로 MenuItem.java 파일 위치와 클래스 선언부 전후에 어노테이션이 있는지 재확인 부탁드립니다:


🏁 Script executed:

#!/bin/bash
# 1. MenuItem.java 파일 경로 확인
find src/main/java -type f -name '*MenuItem.java'

# 2. 클래스 선언부 전후 3줄씩 출력하여 @Entity 또는 @Embeddable 어노테이션 확인
grep -R -n -C3 'class MenuItem' src/main/java

Length of output: 195


다음 스크립트로 MenuItem.java 파일 위치와 클래스 선언부 전후의 어노테이션 여부를 한번에 확인해주세요:

#!/bin/bash
# 1. MenuItem.java 파일 위치 찾기
find . -type f -name 'MenuItem.java'

# 2. 해당 파일에서 @Entity, @Embeddable 어노테이션과 class 선언부 함께 출력
FILE=$(find . -type f -name 'MenuItem.java')
grep -n -C3 '@Entity\|@Embeddable\|class MenuItem' "$FILE"

다음 스크립트로 recruitment-service 모듈 내에서 MenuItem.java 위치와 어노테이션 여부를 확인해주세요:

#!/bin/bash
# 1. recruitment-service 모듈 내 MenuItem.java 파일 찾기
FILE=$(find recruitment-service -type f -name 'MenuItem.java')
echo "Found: $FILE"

# 2. 클래스 선언부 및 @Entity, @Embeddable 어노테이션 전후 3줄씩 출력
grep -n -C3 '@Entity\|@Embeddable\|class MenuItem' "$FILE"

아래 스크립트로 전체 코드베이스에서 MenuItem 정의와 @Entity/@Embeddable 어노테이션 적용 여부를 다시 한번 확인해주세요:


🏁 Script executed:

#!/bin/bash
# 1. 전체 코드베이스에서 MenuItem 클래스 선언 탐색
grep -R -n 'class MenuItem' .

# 2. 전체 코드베이스에서 @Entity 어노테이션 탐색
grep -R -n '@Entity' .

# 3. 전체 코드베이스에서 @Embeddable 어노테이션 탐색
grep -R -n '@Embeddable' .

Length of output: 746


검증 완료: MenuItem@Embeddable로 정의되어 @ElementCollection 사용이 적합합니다

  • recruitment-service/src/main/java/com/example/recruitment/entity/MenuItem.java 파일의 6행에 @Embeddable 어노테이션이 적용되어 있습니다.
  • 따라서 별도의 @Entity가 아니므로 @OneToMany로 변경할 필요가 없습니다.
recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (1)

13-14: 글로벌 예외 핸들러 구현이 잘 되어 있습니다.

@RestControllerAdvice를 사용한 중앙집중식 예외 처리 구조가 잘 설계되어 있고, 다양한 예외 타입에 대한 적절한 처리가 구현되어 있습니다.

recruitment-service/.github/workflows/ci-cd-store-service.yml (1)

1-72: 워크플로우 구성이 적절합니다.

CI/CD 파이프라인이 Spring Boot 애플리케이션의 빌드, Docker 이미지 생성 및 EC2 배포를 위해 잘 구성되어 있습니다.

recruitment-service/gradlew (1)

1-252: 표준 Gradle Wrapper 스크립트

이는 Gradle에서 제공하는 표준 wrapper 스크립트입니다. 프로젝트의 빌드 환경을 일관되게 유지하는 데 필요한 파일입니다.

recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (1)

22-25: ⚠️ Potential issue

Swagger 문서화 어노테이션 누락

PR 목적이 "Swagger 설정 및 모집글 API 문서화 완료"인데, 컨트롤러에 Swagger/OpenAPI 관련 어노테이션이 없습니다. API 문서화를 위해 @Operation, @ApiResponse 등의 어노테이션을 추가해야 합니다.

예시:

@Tag(name = "모집글 API", description = "모집글 관련 CRUD 및 참여 기능")
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/recruitments")
public class RecruitmentController {

Likely an incorrect or invalid review comment.

Comment on lines +1 to +4
package com.example.recruitment;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

패키지 구조 일관성 개선 필요

다른 컨트롤러들이 com.example.recruitment.controller 패키지에 위치하는 반면, 이 컨트롤러는 루트 패키지에 있어 일관성이 떨어집니다.

-package com.example.recruitment;
+package com.example.recruitment.controller;
📝 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
package com.example.recruitment;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
package com.example.recruitment.controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java
at the top of the file, the package declaration places this controller in the
root package com.example.recruitment, which is inconsistent with other
controllers located in com.example.recruitment.controller. To fix this, change
the package declaration to com.example.recruitment.controller and move the file
into the corresponding directory structure to maintain package consistency.

Comment on lines +9 to +12
@GetMapping("/")
public String hello() {
return "서버 정상 작동 중!";
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

엔드포인트 경로 및 Swagger 문서화 개선 필요

현재 루트 경로 "/"를 사용하고 있어 다른 엔드포인트와 충돌할 수 있습니다. 또한 PR 목표가 Swagger 문서화인데 OpenAPI 어노테이션이 없습니다.

+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;

+@Tag(name = "Health Check", description = "서버 상태 확인 API")
+@RestController
+public class HealthCheckController {

-    @GetMapping("/")
+    @GetMapping("/health")
+    @Operation(summary = "서버 상태 확인", description = "서버가 정상적으로 작동 중인지 확인합니다.")
     public String hello() {
         return "서버 정상 작동 중!";
     }

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

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java
around lines 9 to 12, the endpoint uses the root path "/" which may conflict
with other endpoints, and it lacks OpenAPI annotations for Swagger
documentation. Change the endpoint path to a more specific one to avoid
conflicts, and add appropriate OpenAPI annotations such as @Operation and
@ApiResponse to document the endpoint for Swagger.

Comment on lines +6 to +7
@RestController
public class RecruitmentControllerTest {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

클래스명 변경 필요

RecruitmentControllerTest라는 이름은 테스트 클래스로 오해될 수 있습니다. 이는 실제 프로덕션 컨트롤러이므로 더 명확한 이름을 사용해야 합니다.

-public class RecruitmentControllerTest {
+public class HealthCheckController {

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

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/RecruitmentControllerTest.java
around lines 6 to 7, the class name RecruitmentControllerTest is misleading as
it suggests a test class. Rename the class to RecruitmentController to clearly
indicate it is a production controller and avoid confusion.

Comment on lines +13 to 18
EC2_SSH_USER: kmj02dev
PRIVATE_KEY: ${{ secrets.EC2_SSH_PRIVATE_KEY }}
TIMEZONE: Asia/Seoul
DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
DOCKER_TOKEN: ${{ secrets.DOCKERHUB_TOKEN }}
PROJECT_PATH: /home/ec2-user/delivery-system/recruitment-service # 예: /home/ec2-user/delivery-system/order-service
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

EC2_SSH_USER와 PROJECT_PATH 간 불일치
EC2_SSH_USERkmj02dev로 변경했으나, PROJECT_PATH/home/ec2-user/...가 남아 있습니다. SSH 사용자와 디렉토리 경로가 다르면 배포 실패로 이어지니, 아래처럼 수정하세요.

-  EC2_SSH_USER: kmj02dev
+  EC2_SSH_USER: kmj02dev

-  PROJECT_PATH: /home/ec2-user/delivery-system/recruitment-service
+  PROJECT_PATH: /home/${{ env.EC2_SSH_USER }}/delivery-system/recruitment-service
🤖 Prompt for AI Agents
In .github/workflows/ci-cd-recruitment-service.yml around lines 13 to 18, the
EC2_SSH_USER is set to kmj02dev but the PROJECT_PATH still uses /home/ec2-user,
causing a mismatch. Update the PROJECT_PATH to use
/home/kmj02dev/delivery-system/recruitment-service to match the SSH user and
avoid deployment failures.

Comment on lines +2 to +4
spring.datasource.url=jdbc:mysql://localhost:3306/recruitment_db?serverTimezone=Asia/Seoul
spring.datasource.username=root
spring.datasource.password=040320
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

보안 취약점: 데이터베이스 자격 증명이 평문으로 노출됨

데이터베이스 연결 정보가 하드코딩되어 있어 보안상 매우 위험합니다. 특히 패스워드가 평문으로 저장되어 있습니다.

다음과 같이 환경 변수나 외부 설정을 사용하도록 수정하세요:

-spring.datasource.url=jdbc:mysql://localhost:3306/recruitment_db?serverTimezone=Asia/Seoul
-spring.datasource.username=root
-spring.datasource.password=040320
+spring.datasource.url=${DB_URL:jdbc:mysql://localhost:3306/recruitment_db?serverTimezone=Asia/Seoul}
+spring.datasource.username=${DB_USERNAME:root}
+spring.datasource.password=${DB_PASSWORD}

또는 application-{profile}.properties 파일을 사용하여 환경별로 설정을 분리하세요.

📝 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
spring.datasource.url=jdbc:mysql://localhost:3306/recruitment_db?serverTimezone=Asia/Seoul
spring.datasource.username=root
spring.datasource.password=040320
spring.datasource.url=${DB_URL:jdbc:mysql://localhost:3306/recruitment_db?serverTimezone=Asia/Seoul}
spring.datasource.username=${DB_USERNAME:root}
spring.datasource.password=${DB_PASSWORD}
🤖 Prompt for AI Agents
In recruitment-service/src/main/resources/application.properties lines 2 to 4,
the database credentials are hardcoded in plain text, posing a security risk.
Remove the plain text password and username from this file and instead configure
them to be loaded from environment variables or external configuration sources.
Alternatively, use profile-specific properties files like
application-{profile}.properties to separate sensitive settings per environment,
ensuring credentials are not exposed in the codebase.

Comment on lines 32 to 212
// 모집글 생성
@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");

return recruitmentRepository.save(recruitment);
}

// 모집글 전체 조회
@GetMapping
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();
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();

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

if (alreadyJoined) {
return ResponseEntity.badRequest().body("이미 참여한 모집입니다.");
}

RecruitmentParticipant participant = new RecruitmentParticipant();
participant.setUser(user);
participant.setRecruitment(recruitment);
participant.setJoinedAt(LocalDateTime.now());

participantRepository.save(participant);
return ResponseEntity.ok("모집글 참여 완료");
}

// 모집 상태 업데이트 (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())) {
if (participantCount >= 3) {
recruitment.setStatus("CONFIRMED");
} else {
recruitment.setStatus("FAILED");
}
recruitmentRepository.save(recruitment);
return ResponseEntity.ok("상태가 " + recruitment.getStatus() + "로 변경되었습니다.");
} else {
return ResponseEntity.ok("아직 마감 시간이 지나지 않았습니다.");
}
}

// 주문 수락 상태로 변경 (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("작성자만 수정할 수 있습니다.");
}

// 수정할 항목들 업데이트
recruitment.setTitle(dto.getTitle());
recruitment.setDescription(dto.getDescription());
recruitment.setDeadlineTime(dto.getDeadlineTime());

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

recruitmentRepository.save(recruitment);
return ResponseEntity.ok("모집글이 수정되었습니다.");
}

}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

트랜잭션 관리 누락

컨트롤러의 여러 메서드에서 다중 데이터베이스 작업을 수행하지만 @Transactional 어노테이션이 없습니다. 데이터 일관성을 위해 서비스 레이어로 비즈니스 로직을 분리하고 트랜잭션을 적용하는 것이 좋습니다.

서비스 레이어 도입을 권장합니다:

@Service
@Transactional
public class RecruitmentService {
    // 비즈니스 로직을 서비스 레이어로 이동
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
from lines 32 to 212, the controller methods perform multiple database
operations without transaction management, risking data inconsistency. Refactor
by creating a RecruitmentService class annotated with @Service and
@Transactional, move all business logic from the controller into this service
layer, and update the controller methods to call the service methods. This
ensures proper transaction management and separation of concerns.

Comment on lines 105 to 125
@PostMapping("/{recruitmentId}/join")
public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId, @RequestParam Long userId) {
User user = userRepository.findById(userId).orElseThrow();
Recruitment recruitment = recruitmentRepository.findById(recruitmentId).orElseThrow();

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

if (alreadyJoined) {
return ResponseEntity.badRequest().body("이미 참여한 모집입니다.");
}

RecruitmentParticipant participant = new RecruitmentParticipant();
participant.setUser(user);
participant.setRecruitment(recruitment);
participant.setJoinedAt(LocalDateTime.now());

participantRepository.save(participant);
return ResponseEntity.ok("모집글 참여 완료");
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

보안 취약점: 사용자 인증 부재

현재 userId를 요청 파라미터로 받고 있어 누구나 다른 사용자로 위장하여 모집글에 참여할 수 있습니다. Spring Security를 통한 인증된 사용자 정보를 사용해야 합니다.

+    @Operation(summary = "모집글 참여", description = "인증된 사용자가 모집글에 참여합니다.")
     @PostMapping("/{recruitmentId}/join")
-    public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId, @RequestParam Long userId) {
+    public ResponseEntity<?> joinRecruitment(@PathVariable Long recruitmentId, 
+                                           @AuthenticationPrincipal UserDetails userDetails) {
+        Long userId = getCurrentUserId(userDetails);
         User user = userRepository.findById(userId).orElseThrow();

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 105 to 125, the joinRecruitment method accepts userId as a request
parameter, which allows impersonation and is a security risk. Remove the userId
parameter and instead obtain the authenticated user's identity from Spring
Security's SecurityContext. Use this authenticated user information to fetch the
User entity and proceed with the participation logic, ensuring only the
logged-in user can join recruitments on their own behalf.

Comment on lines +50 to +54
@GetMapping
public List<Recruitment> getAll() {
return recruitmentRepository.findAll();
}

Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

페이징 처리 추가 권장

전체 모집글을 조회할 때 데이터가 많아질 경우 성능 문제가 발생할 수 있습니다. 페이징 처리를 추가하는 것을 권장합니다.

+    @Operation(summary = "모집글 목록 조회", description = "모집글 목록을 페이징하여 조회합니다.")
     @GetMapping
-    public List<Recruitment> getAll() {
-        return recruitmentRepository.findAll();
+    public Page<Recruitment> getAll(@PageableDefault(size = 20) Pageable pageable) {
+        return recruitmentRepository.findAll(pageable);
     }
📝 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
@GetMapping
public List<Recruitment> getAll() {
return recruitmentRepository.findAll();
}
@Operation(summary = "모집글 목록 조회", description = "모집글 목록을 페이징하여 조회합니다.")
@GetMapping
public Page<Recruitment> getAll(@PageableDefault(size = 20) Pageable pageable) {
return recruitmentRepository.findAll(pageable);
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 50 to 54, the getAll method returns all recruitment entries without
pagination, which can cause performance issues with large data sets. Modify the
method to accept pagination parameters (such as page number and size) and use
the repository's paging capabilities to return a paged list of recruitment
entries instead of all at once.

Comment on lines 33 to 47
@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");

return recruitmentRepository.save(recruitment);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

예외 처리 및 Swagger 문서화 개선 필요

  1. orElseThrow()가 구체적인 예외 메시지 없이 사용되고 있습니다.
  2. Swagger 문서화를 위한 어노테이션이 누락되었습니다.
+    @Operation(summary = "모집글 생성", description = "새로운 모집글을 생성합니다.")
+    @ApiResponse(responseCode = "200", description = "모집글 생성 성공")
+    @ApiResponse(responseCode = "404", description = "사용자 또는 가게를 찾을 수 없음")
     @PostMapping
     public Recruitment createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
-        User user = userRepository.findById(dto.getUserId()).orElseThrow();
-        Store store = storeRepository.findById(dto.getStoreId()).orElseThrow();
+        User user = userRepository.findById(dto.getUserId())
+                .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
+        Store store = storeRepository.findById(dto.getStoreId())
+                .orElseThrow(() -> new CustomException(ErrorCode.STORE_NOT_FOUND));
📝 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
@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");
return recruitmentRepository.save(recruitment);
}
@Operation(summary = "모집글 생성", description = "새로운 모집글을 생성합니다.")
@ApiResponse(responseCode = "200", description = "모집글 생성 성공")
@ApiResponse(responseCode = "404", description = "사용자 또는 가게를 찾을 수 없음")
@PostMapping
public Recruitment createRecruitment(@Valid @RequestBody RecruitmentRequestDto dto) {
User user = userRepository.findById(dto.getUserId())
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
Store store = storeRepository.findById(dto.getStoreId())
.orElseThrow(() -> new CustomException(ErrorCode.STORE_NOT_FOUND));
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");
return recruitmentRepository.save(recruitment);
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 33 to 47, the calls to orElseThrow() lack specific exception
messages, which reduces clarity when an entity is not found. Replace
orElseThrow() with versions that throw exceptions including descriptive messages
for missing User or Store. Additionally, add Swagger annotations such as
@ApiOperation and @ApiResponses to document the createRecruitment endpoint for
better API documentation.

Comment on lines 128 to 146
@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())) {
if (participantCount >= 3) {
recruitment.setStatus("CONFIRMED");
} else {
recruitment.setStatus("FAILED");
}
recruitmentRepository.save(recruitment);
return ResponseEntity.ok("상태가 " + recruitment.getStatus() + "로 변경되었습니다.");
} else {
return ResponseEntity.ok("아직 마감 시간이 지나지 않았습니다.");
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

매직 넘버 사용 및 비즈니스 로직 외부화 필요

최소 참여자 수(3명)가 하드코딩되어 있습니다. 설정 가능한 값으로 변경하거나 상수로 정의하는 것이 좋습니다.

+    private static final int MINIMUM_PARTICIPANTS = 3;
+
     @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())) {
-            if (participantCount >= 3) {
+            if (participantCount >= MINIMUM_PARTICIPANTS) {
                 recruitment.setStatus("CONFIRMED");
             } else {
📝 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
@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())) {
if (participantCount >= 3) {
recruitment.setStatus("CONFIRMED");
} else {
recruitment.setStatus("FAILED");
}
recruitmentRepository.save(recruitment);
return ResponseEntity.ok("상태가 " + recruitment.getStatus() + "로 변경되었습니다.");
} else {
return ResponseEntity.ok("아직 마감 시간이 지나지 않았습니다.");
}
}
// add this near the top of the class
private static final int MINIMUM_PARTICIPANTS = 3;
@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())) {
if (participantCount >= MINIMUM_PARTICIPANTS) {
recruitment.setStatus("CONFIRMED");
} else {
recruitment.setStatus("FAILED");
}
recruitmentRepository.save(recruitment);
return ResponseEntity.ok("상태가 " + recruitment.getStatus() + "로 변경되었습니다.");
} else {
return ResponseEntity.ok("아직 마감 시간이 지나지 않았습니다.");
}
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
around lines 128 to 146, the minimum participant count of 3 is hardcoded as a
magic number. Define this value as a constant at the class level or load it from
a configuration file to make it configurable and improve code readability.
Replace the hardcoded '3' with the defined constant or configuration property in
the participant count comparison.

@ehddms0320 ehddms0320 temporarily deployed to recruitment-service May 30, 2025 14:20 — with GitHub Actions Inactive
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: 18

♻️ Duplicate comments (11)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (8)

19-23: 외래키 제약조건 추가 필요

데이터 무결성을 위해 @JoinColumn 설정을 추가해야 합니다.

-@ManyToOne
+@ManyToOne
+@JoinColumn(name = "user_id", nullable = false)
 private User user;

-@ManyToOne
+@ManyToOne  
+@JoinColumn(name = "store_id", nullable = false)
 private Store store;

28-29: 필수 필드에 제약조건 추가 필요

제목과 설명은 필수 필드이므로 제약조건을 추가해야 합니다.

+@Column(nullable = false, length = 100)
+@NotBlank(message = "제목은 필수입니다.")
 private String title;

+@Column(nullable = false, length = 1000)
+@NotBlank(message = "설명은 필수입니다.")
 private String description;

31-31: 상태 필드를 Enum으로 변경 권장

문자열 대신 Enum을 사용하여 타입 안전성을 확보하는 것이 좋습니다.

-private String status;  // RECRUITING, CONFIRMED 등
+@Enumerated(EnumType.STRING)
+@Column(nullable = false)
+private RecruitmentStatus status;  // RECRUITING, CONFIRMED 등

33-33: 마감시간 제약조건 추가 필요

마감시간은 현재 시간보다 미래여야 하므로 유효성 검사를 추가해야 합니다.

+@Column(nullable = false)
+@Future(message = "마감시간은 현재 시간보다 미래여야 합니다.")
 private LocalDateTime deadlineTime;

19-23: 외래키 제약조건 추가 필요

이전 리뷰에서 지적된 사항이 아직 적용되지 않았습니다. 데이터 무결성을 위해 @JoinColumn 설정을 추가해야 합니다.


28-29: 필수 필드에 제약조건 추가 필요

이전 리뷰에서 권장된 제목과 설명 필드의 validation 어노테이션이 아직 적용되지 않았습니다.


31-31: 상태 필드를 Enum으로 변경 권장

이전 리뷰에서 제안된 상태 필드의 Enum 변경이 아직 적용되지 않았습니다. 타입 안전성을 위해 적용을 권장합니다.


33-33: 마감시간 제약조건 추가 필요

이전 리뷰에서 제안된 마감시간 유효성 검사가 아직 적용되지 않았습니다.

recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (3)

17-21: ID 필드 유효성 검사 강화 필요

이전 리뷰에서 제안된 @Positive 어노테이션이 아직 적용되지 않았습니다. ID 필드들이 양수여야 하므로 추가 검사가 필요합니다.

@NotNull(message = "userId는 필수입니다.")
+@Positive(message = "userId는 양수여야 합니다.")
private Long userId;

@NotNull(message = "storeId는 필수입니다.")
+@Positive(message = "storeId는 양수여야 합니다.")
private Long storeId;

26-27: 설명 필드 유효성 검사 개선 필요

이전 리뷰에서 제안된 @NotBlank 어노테이션이 아직 적용되지 않았습니다. 빈 문자열이나 공백만 있는 경우를 방지하기 위해 필요합니다.

+@NotBlank(message = "설명은 비어 있을 수 없습니다.")
@Size(min = 10, message = "설명은 최소 10자 이상이어야 합니다.")
private String description;

29-30: 마감시간 유효성 검사 추가 필요

이전 리뷰에서 제안된 @Future 어노테이션이 아직 적용되지 않았습니다. 마감시간이 현재 시간보다 미래인지 확인하는 검사가 필요합니다.

@NotNull(message = "마감 시간은 필수입니다.")
+@Future(message = "마감시간은 현재 시간보다 미래여야 합니다.")
private LocalDateTime deadlineTime;
🧹 Nitpick comments (6)
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java (1)

22-26: 외부 서비스 호출 안정성 개선 필요

외부 서비스 호출에 재시도 메커니즘이나 서킷 브레이커 패턴을 적용하는 것을 고려해보세요.

Spring Retry나 Resilience4j를 사용한 개선 방안:

@Retryable(value = {Exception.class}, maxAttempts = 3, backoff = @Backoff(delay = 1000))
public Long createOrder(OrderRequestDto requestDto) {
    // 기존 로직
}
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (2)

35-35: 오타 수정 필요

주석에 오타가 있습니다.

-//카테고리 추가가
+// 카테고리 필드

45-47: addOrderId 메서드에 null 체크 추가

null 값이 추가되는 것을 방지하기 위한 검증이 필요합니다.

 public void addOrderId(Long orderId) {
+    if (orderId == null) {
+        throw new IllegalArgumentException("orderId는 null일 수 없습니다.");
+    }
     this.orderIds.add(orderId);
 }
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (1)

86-90: 빌더 패턴 사용으로 가독성 향상

RecruitmentParticipant 엔티티 생성에도 빌더 패턴을 사용하면 코드 가독성이 향상됩니다.

-        // 참여자 등록 + 주문 ID 저장
-        RecruitmentParticipant participant = new RecruitmentParticipant();
-        participant.setRecruitment(recruitment);
-        participant.setUser(user);
-        participant.setOrderId(orderId);
-        participantRepository.save(participant);
+        // 참여자 등록 + 주문 ID 저장
+        RecruitmentParticipant participant = RecruitmentParticipant.builder()
+                .recruitment(recruitment)
+                .user(user)
+                .orderId(orderId)
+                .build();
+        participantRepository.save(participant);
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (1)

27-28: 자동 타임스탬프 설정 추가 권장

joinedAt 필드가 엔티티 생성 시 자동으로 설정되도록 @PrePersist 어노테이션을 추가하는 것이 좋습니다.

+@PrePersist
+protected void onCreate() {
+    if (joinedAt == null) {
+        joinedAt = LocalDateTime.now();
+    }
+}
+
 @Column(name = "joined_at", nullable = false)
 private LocalDateTime joinedAt;
recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java (1)

15-15: 메서드 매개변수 타입 일관성 개선 필요

같은 목적의 메서드들이 서로 다른 매개변수 타입을 사용하고 있습니다. 일관성을 위해 하나의 방식으로 통일하는 것이 좋습니다.

 // 옵션 1: ID 기반으로 통일
-boolean existsByRecruitmentAndUser(Recruitment recruitment, User user);
+boolean existsByRecruitmentIdAndUserId(Long recruitmentId, Long userId);

 // 또는 옵션 2: 엔티티 기반으로 통일
-Optional<RecruitmentParticipant> findByUserIdAndRecruitmentId(Long userId, Long recruitmentId);
+Optional<RecruitmentParticipant> findByUserAndRecruitment(User user, Recruitment recruitment);

ID 기반 접근을 권장합니다. 엔티티 객체가 이미 로드되지 않은 경우 불필요한 조회를 방지할 수 있습니다.

Also applies to: 25-25

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d20dc23 and 5235264.

📒 Files selected for processing (11)
  • recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderRequestDto.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderResponseDto.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java (1 hunks)
  • recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (1 hunks)
  • recruitment-service/src/main/resources/application.properties (1 hunks)
✅ Files skipped from review due to trivial changes (2)
  • recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderResponseDto.java
  • recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderRequestDto.java
🚧 Files skipped from review as they are similar to previous changes (3)
  • recruitment-service/src/main/resources/application.properties
  • recruitment-service/src/main/java/com/example/recruitment/exception/GlobalExceptionHandler.java
  • recruitment-service/src/main/java/com/example/recruitment/controller/RecruitmentController.java
🧰 Additional context used
🧬 Code Graph Analysis (4)
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java (1)
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (1)
  • Service (17-92)
recruitment-service/src/main/java/com/example/recruitment/service/RecruitmentService.java (1)
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java (1)
  • Service (11-42)
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java (4)
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (1)
  • Entity (8-32)
recruitment-service/src/main/java/com/example/recruitment/entity/Store.java (1)
  • Entity (6-18)
recruitment-service/src/main/java/com/example/recruitment/entity/User.java (1)
  • Entity (6-17)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
  • Getter (13-46)
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java (1)
recruitment-service/src/main/java/com/example/recruitment/dto/order/OrderRequestDto.java (3)
  • Getter (8-32)
  • Getter (17-24)
  • Getter (26-31)
🔇 Additional comments (3)
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java (2)

19-25: 관계 매핑 검증 완료

사용자와 모집글에 대한 @manytoone 관계가 올바르게 설정되었습니다. @joincolumn에 nullable = false 제약조건도 적절히 추가되어 데이터 무결성이 보장됩니다.


19-25: 관계 설정이 잘 구현되었습니다

UserRecruitment 엔티티와의 관계에서 @JoinColumnnullable = false 설정이 적절히 구현되어 데이터 무결성을 보장합니다.

recruitment-service/src/main/java/com/example/recruitment/repository/RecruitmentParticipantRepository.java (1)

12-26: 메서드 명명 규칙이 잘 준수되었습니다

Spring Data JPA의 메서드 명명 규칙을 잘 따르고 있으며, 필요한 쿼리 메서드들이 적절히 정의되어 있습니다.

Comment on lines +8 to +12
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class RecruitmentParticipant {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

테이블명 명시 및 인덱스 추가 권장

엔티티 클래스에 명시적인 테이블명과 복합 인덱스를 추가하는 것이 좋습니다.

 @Entity
+@Table(name = "recruitment_participant", 
+       indexes = {
+           @Index(name = "idx_user_recruitment", columnList = "user_id, recruitment_id"),
+           @Index(name = "idx_recruitment", columnList = "recruitment_id"),
+           @Index(name = "idx_user", columnList = "user_id")
+       })
 @Getter @Setter
📝 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
@Entity
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class RecruitmentParticipant {
@Entity
@Table(
name = "recruitment_participant",
indexes = {
@Index(name = "idx_user_recruitment", columnList = "user_id, recruitment_id"),
@Index(name = "idx_recruitment", columnList = "recruitment_id"),
@Index(name = "idx_user", columnList = "user_id")
}
)
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class RecruitmentParticipant {
// … existing fields and mappings …
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java
around lines 8 to 12, the entity class lacks an explicit table name and
composite index definitions. Add the @Table annotation above the class
declaration specifying the table name and define any necessary composite indexes
using the indexes attribute to improve database clarity and performance.

Comment on lines +27 to +28
@Column(name = "joined_at", nullable = false)
private LocalDateTime joinedAt;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

자동 타임스탬프 설정 추가 필요

참여 시점을 자동으로 설정하도록 개선해야 합니다.

+@CreationTimestamp
 @Column(name = "joined_at", nullable = false)
 private LocalDateTime joinedAt;

또는 엔티티에 @PrePersist 메서드를 추가:

+@PrePersist
+protected void onCreate() {
+    if (joinedAt == null) {
+        joinedAt = LocalDateTime.now();
+    }
+}
📝 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
@Column(name = "joined_at", nullable = false)
private LocalDateTime joinedAt;
// Add this import for Hibernate’s annotation
// import org.hibernate.annotations.CreationTimestamp;
// And for JPA lifecycle callbacks:
// import javax.persistence.PrePersist;
@CreationTimestamp
@Column(name = "joined_at", nullable = false)
private LocalDateTime joinedAt;
@PrePersist
protected void onCreate() {
if (joinedAt == null) {
joinedAt = LocalDateTime.now();
}
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/entity/RecruitmentParticipant.java
at lines 27-28, the joinedAt field lacks automatic timestamp setting. To fix
this, implement automatic setting of joinedAt when the entity is persisted by
adding a method annotated with @PrePersist that assigns LocalDateTime.now() to
joinedAt before insertion.

@Service
public class OrderClient {

private final RestTemplate restTemplate = new RestTemplate();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

RestTemplate을 의존성 주입으로 변경 권장

RestTemplate을 직접 인스턴스화하는 대신 의존성 주입을 사용하는 것이 좋습니다.

-private final RestTemplate restTemplate = new RestTemplate();
+private final RestTemplate restTemplate;
+
+public OrderClient(RestTemplate restTemplate) {
+    this.restTemplate = restTemplate;
+}

그리고 Configuration 클래스에서 RestTemplate 빈을 등록:

@Configuration
public class RestConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
at line 14, the RestTemplate is instantiated directly which is not recommended.
Refactor the code to remove the direct instantiation and instead inject
RestTemplate as a dependency via constructor or field injection. Additionally,
create a configuration class (e.g., RestConfig) to define a RestTemplate bean
annotated with @Bean so that Spring can manage its lifecycle and inject it where
needed.

🛠️ Refactor suggestion

RestTemplate 주입 방식으로 변경 권장

RestTemplate을 직접 생성하는 대신 Spring Bean으로 주입받아 사용하는 것이 좋습니다. 이렇게 하면 설정(타임아웃, 커넥션 풀 등)을 중앙에서 관리할 수 있습니다.

-private final RestTemplate restTemplate = new RestTemplate();
+private final RestTemplate restTemplate;
+
+public OrderClient(RestTemplate restTemplate) {
+    this.restTemplate = restTemplate;
+}

별도의 Configuration 클래스에서 RestTemplate Bean을 정의해야 합니다:

@Configuration
public class RestTemplateConfig {
    
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
at line 14, the RestTemplate is instantiated directly which prevents centralized
configuration management. Refactor the code to remove the direct instantiation
and instead inject RestTemplate as a Spring Bean. Create a separate
configuration class annotated with @Configuration that defines a RestTemplate
@Bean, then use @Autowired or constructor injection in OrderClient to receive
the RestTemplate instance.

Comment on lines +37 to +41
throw new RuntimeException("응답에 orderId 없음");
}

throw new RuntimeException("주문 생성 실패: " + response.getStatusCode());
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

구체적인 예외 처리 및 로깅 추가 필요

현재 RuntimeException은 너무 일반적이며, 로깅이 부족합니다.

+private static final Logger logger = LoggerFactory.getLogger(OrderClient.class);

+logger.error("주문 서버 응답에 orderId가 없습니다. Response: {}", responseBody);
 throw new RuntimeException("응답에 orderId 없음");

+logger.error("주문 생성 실패. Status: {}, Response: {}", response.getStatusCode(), response.getBody());
 throw new RuntimeException("주문 생성 실패: " + response.getStatusCode());

더 구체적인 커스텀 예외 사용을 권장합니다.

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

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
around lines 37 to 41, replace the generic RuntimeException with a more specific
custom exception class that clearly indicates the error type. Additionally, add
proper logging before throwing the exception to record the error details,
including the response status code or missing orderId information, to improve
error traceability and debugging.

Comment on lines +25 to +35
ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);

if (response.getStatusCode().is2xxSuccessful()) {
Map<String, Object> responseBody = response.getBody();

if (responseBody != null && responseBody.containsKey("data")) {
Map<String, Object> data = (Map<String, Object>) responseBody.get("data");

if (data != null && data.containsKey("orderId")) {
return Long.valueOf(data.get("orderId").toString());
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

타입 안전성 및 응답 파싱 개선 필요

현재 Map 타입 캐스팅은 타입 안전하지 않고 런타임 오류 위험이 있습니다.

-ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);
+ResponseEntity<OrderResponseDto> response = restTemplate.postForEntity(fullUrl, requestDto, OrderResponseDto.class);

-if (response.getStatusCode().is2xxSuccessful()) {
-    Map<String, Object> responseBody = response.getBody();
-    if (responseBody != null && responseBody.containsKey("data")) {
-        Map<String, Object> data = (Map<String, Object>) responseBody.get("data");
-        if (data != null && data.containsKey("orderId")) {
-            return Long.valueOf(data.get("orderId").toString());
-        }
-    }
+if (response.getStatusCode().is2xxSuccessful()) {
+    OrderResponseDto responseBody = response.getBody();
+    if (responseBody != null && responseBody.getData() != null) {
+        return responseBody.getData().getOrderId();
+    }

OrderResponseDto 클래스 생성이 필요합니다.

📝 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
ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);
if (response.getStatusCode().is2xxSuccessful()) {
Map<String, Object> responseBody = response.getBody();
if (responseBody != null && responseBody.containsKey("data")) {
Map<String, Object> data = (Map<String, Object>) responseBody.get("data");
if (data != null && data.containsKey("orderId")) {
return Long.valueOf(data.get("orderId").toString());
}
ResponseEntity<OrderResponseDto> response =
restTemplate.postForEntity(fullUrl, requestDto, OrderResponseDto.class);
if (response.getStatusCode().is2xxSuccessful()) {
OrderResponseDto responseBody = response.getBody();
if (responseBody != null && responseBody.getData() != null) {
return responseBody.getData().getOrderId();
}
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
around lines 25 to 35, the current use of raw Map and unchecked casting for
parsing the response is not type-safe and risks runtime errors. To fix this,
create an OrderResponseDto class that models the expected response structure
with proper fields, then update the restTemplate call to deserialize directly
into this DTO instead of Map. Adjust the code to access orderId through the
DTO's getter methods, ensuring type safety and clearer response parsing.

private String category;

// 주문용 메뉴 정보
private List<OrderRequestDto.MenuDto> menus;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

메뉴 리스트 유효성 검사 추가

메뉴 리스트에 대한 유효성 검사가 없습니다. 비어있지 않은지, 각 메뉴 항목이 유효한지 확인이 필요합니다.

// 주문용 메뉴 정보
+@NotEmpty(message = "메뉴는 최소 1개 이상 선택해야 합니다.")
+@Valid
private List<OrderRequestDto.MenuDto> menus;
📝 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
private List<OrderRequestDto.MenuDto> menus;
// 주문용 메뉴 정보
@NotEmpty(message = "메뉴는 최소 1개 이상 선택해야 합니다.")
@Valid
private List<OrderRequestDto.MenuDto> menus;
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java
at line 36, the menus list lacks validation. Add validation annotations or logic
to ensure the menus list is not empty and that each menu item is valid, such as
using @NotEmpty on the list and @Valid on the menu items to enforce nested
validation.

Comment on lines +39 to +45
public OrderRequestDto toOrderRequestDto() {
OrderRequestDto dto = new OrderRequestDto();
dto.setUserId(this.userId);
dto.setStoreId(this.storeId);
dto.setMenus(this.menus);
return dto;
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

null 안전성 개선 및 방어적 프로그래밍

toOrderRequestDto 메서드에서 null 체크가 없어 NPE가 발생할 수 있습니다.

//OrderRequestDto 변환 메서드
public OrderRequestDto toOrderRequestDto() {
+    if (this.userId == null || this.storeId == null) {
+        throw new IllegalStateException("userId와 storeId는 필수값입니다.");
+    }
    OrderRequestDto dto = new OrderRequestDto();
    dto.setUserId(this.userId);
    dto.setStoreId(this.storeId);
    dto.setMenus(this.menus);
    return dto;
}
📝 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 OrderRequestDto toOrderRequestDto() {
OrderRequestDto dto = new OrderRequestDto();
dto.setUserId(this.userId);
dto.setStoreId(this.storeId);
dto.setMenus(this.menus);
return dto;
}
public OrderRequestDto toOrderRequestDto() {
if (this.userId == null || this.storeId == null) {
throw new IllegalStateException("userId와 storeId는 필수값입니다.");
}
OrderRequestDto dto = new OrderRequestDto();
dto.setUserId(this.userId);
dto.setStoreId(this.storeId);
dto.setMenus(this.menus);
return dto;
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/dto/RecruitmentRequestDto.java
around lines 39 to 45, the toOrderRequestDto method lacks null checks which can
cause NullPointerException. Add null checks for fields like userId, storeId, and
menus before setting them on the OrderRequestDto object, and handle null cases
appropriately to ensure null safety and defensive programming.

Comment on lines +30 to +36
if (responseBody != null && responseBody.containsKey("data")) {
Map<String, Object> data = (Map<String, Object>) responseBody.get("data");

if (data != null && data.containsKey("orderId")) {
return Long.valueOf(data.get("orderId").toString());
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

응답 파싱 안전성 강화 필요

중첩된 Map 구조를 파싱할 때 타입 안전성과 null 체크가 부족합니다.

 if (responseBody != null && responseBody.containsKey("data")) {
-    Map<String, Object> data = (Map<String, Object>) responseBody.get("data");
+    Object dataObj = responseBody.get("data");
+    if (dataObj instanceof Map) {
+        Map<String, Object> data = (Map<String, Object>) dataObj;
-        if (data != null && data.containsKey("orderId")) {
-            return Long.valueOf(data.get("orderId").toString());
+        if (data.containsKey("orderId")) {
+            Object orderIdObj = data.get("orderId");
+            if (orderIdObj != null) {
+                try {
+                    return Long.valueOf(orderIdObj.toString());
+                } catch (NumberFormatException e) {
+                    throw new RuntimeException("orderId 형식이 올바르지 않습니다: " + orderIdObj);
+                }
+            }
         }
+    }
 }
📝 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 (responseBody != null && responseBody.containsKey("data")) {
Map<String, Object> data = (Map<String, Object>) responseBody.get("data");
if (data != null && data.containsKey("orderId")) {
return Long.valueOf(data.get("orderId").toString());
}
}
if (responseBody != null && responseBody.containsKey("data")) {
Object dataObj = responseBody.get("data");
if (dataObj instanceof Map) {
@SuppressWarnings("unchecked")
Map<String, Object> data = (Map<String, Object>) dataObj;
if (data.containsKey("orderId")) {
Object orderIdObj = data.get("orderId");
if (orderIdObj != null) {
try {
return Long.valueOf(orderIdObj.toString());
} catch (NumberFormatException e) {
throw new RuntimeException("orderId 형식이 올바르지 않습니다: " + orderIdObj, e);
}
}
}
}
}
🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
around lines 30 to 36, the current code lacks sufficient type safety and null
checks when parsing the nested Map structure from the responseBody. To fix this,
add explicit type checks before casting, verify that the "data" object is indeed
a Map, and ensure all accessed keys and values are non-null before usage to
prevent ClassCastException and NullPointerException.

public Long createOrder(OrderRequestDto requestDto) {
String fullUrl = serverUrl + path;

ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

예외 처리 및 로깅 개선 필요

외부 서비스 호출 시 네트워크 예외나 타임아웃에 대한 처리가 없습니다. 또한 디버깅을 위한 로깅도 필요합니다.

+@Slf4j
 @Service
 public class OrderClient {
 
+    public Long createOrder(OrderRequestDto requestDto) {
+        String fullUrl = serverUrl + path;
+        log.info("주문 생성 요청: URL={}, 요청데이터={}", fullUrl, requestDto);
+        
+        try {
-            ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);
+            ResponseEntity<Map> response = restTemplate.postForEntity(fullUrl, requestDto, Map.class);
+            log.info("주문 생성 응답: 상태코드={}, 응답데이터={}", response.getStatusCode(), response.getBody());
+        } catch (Exception e) {
+            log.error("주문 생성 중 오류 발생: URL={}, 오류={}", fullUrl, e.getMessage(), e);
+            throw new RuntimeException("주문 서비스 호출 실패", e);
+        }

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

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/service/OrderClient.java
at line 25, the call to restTemplate.postForEntity lacks exception handling for
network errors and timeouts, and there is no logging for debugging. Wrap the
call in a try-catch block to handle exceptions like RestClientException, log the
exception details using a logger, and ensure the method handles or propagates
the error appropriately to improve robustness and traceability.

Comment on lines +39 to +43
@Column(name = "order_id")
private Long orderId;

@ElementCollection
private List<Long> orderIds = new ArrayList<>();
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

orderId와 orderIds 필드의 목적 명확화 필요

단일 orderId 필드와 컬렉션 orderIds 필드가 동시에 존재하여 혼란을 야기할 수 있습니다. 각각의 사용 목적을 명확히 하거나 하나로 통합하는 것을 고려해보세요.

+// 옵션 1: 단일 orderId만 사용
-@Column(name = "order_id")
-private Long orderId;
-
-@ElementCollection
-private List<Long> orderIds = new ArrayList<>();

+@Column(name = "order_id")
+private Long orderId;

+// 옵션 2: 컬렉션만 사용 (여러 주문 지원)
-@Column(name = "order_id")
-private Long orderId;
-
+@ElementCollection
+@CollectionTable(name = "recruitment_order_ids")
+@Column(name = "order_id")
+private List<Long> orderIds = new ArrayList<>();

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

🤖 Prompt for AI Agents
In
recruitment-service/src/main/java/com/example/recruitment/entity/Recruitment.java
around lines 39 to 43, there are two fields, orderId and orderIds, which may
cause confusion due to overlapping purposes. Clarify the intended use of each
field by adding comments or renaming them for clarity, or consolidate them into
a single field if they represent the same concept to avoid redundancy and
improve code maintainability.

@kmj02dev kmj02dev closed this May 30, 2025
@ehddms0320 ehddms0320 had a problem deploying to recruitment-service May 30, 2025 17:00 — with GitHub Actions Failure
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.

3 participants