Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DDING-93] 지원자 상태 수정 API 구현 #241

Merged
merged 17 commits into from
Feb 7, 2025
Merged

Conversation

Seooooo24
Copy link
Collaborator

@Seooooo24 Seooooo24 commented Feb 5, 2025

🚀 작업 내용

지원자 개인 상태 수정 API 구현하였습니다.

🤔 고민했던 내용

💬 리뷰 중점사항

Summary by CodeRabbit

  • 신규 기능

    • Form 애플리케이션 상세 조회 및 상태 업데이트 기능이 추가되었습니다.
    • 애플리케이션 상태 갱신을 위한 DTO와 커맨드 객체가 도입되었습니다.
    • 단일, 다중 애플리케이션 조회 메소드가 서비스에 추가되었습니다.
    • 상태 문자열을 확인하는 기능이 개선되었습니다.
    • FormFieldListQuery에 ID 필드가 추가되었습니다.
  • 리팩토링

    • createFormResponse 메소드가 createFormApplication으로 변경되어 일관성이 향상되었습니다.
    • 변수 명칭 및 서비스 의존성이 개선되었습니다.
  • 테스트

    • 테스트 케이스가 폼 애플리케이션 기능 중심으로 전환되었습니다.

Copy link

coderabbitai bot commented Feb 5, 2025

Walkthrough

이번 PR은 폼 어플리케이션 도메인 관련 변경사항을 포함합니다. 구체적으로, FormQueryFormFieldListQuery 레코드에 새 필드 id가 추가되고, 중앙 및 사용자 API 인터페이스와 컨트롤러에 getFormApplicationupdateFormApplicationStatus 메서드가 도입되었습니다. 또한, DTO와 커맨드 레코드가 새로 추가되고, FormApplication 엔티티 및 관련 서비스 계층에서 상태 업데이트 기능이 보강되었습니다. 이와 함께 테스트 클래스도 폼에서 폼 어플리케이션으로 초점이 변경되도록 리팩토링되었습니다.

Changes

파일 변경 내용
src/.../form/service/dto/query/FormQuery.java FormFieldListQuery 레코드에 새 필드 'Long id' 추가 및 from 메서드 업데이트
src/.../formapplication/api/CentralFormApplicationApi.java
src/.../formapplication/api/UserFormApplicationApi.java
중앙 API에 getFormApplication, updateFormApplicationStatus 메서드 추가
사용자 API에서 createFormResponsecreateFormApplication으로 변경
src/.../formapplication/controller/CentralFormApplicationController.java
src/.../formapplication/controller/UserFormApplicationController.java
컨트롤러에서 변수명 및 메서드 이름 변경: facadeCentralFormServicefacadeCentralFormApplicationService, createFormResponsecreateFormApplication
새로운 getFormApplication, updateFormApplicationStatus 메서드 추가
src/.../formapplication/controller/dto/request/UpdateFormApplicationStatusRequest.java
src/.../formapplication/service/dto/command/UpdateFormApplicationStatusCommand.java
폼 어플리케이션 상태 업데이트를 위한 새 DTO 및 커맨드 레코드 추가 (toCommand 메서드 포함)
src/.../formapplication/entity/FormApplication.java
src/.../formapplication/service/FacadeCentralFormApplicationService.java
src/.../formapplication/service/FacadeCentralFormApplicationServiceImpl.java
src/.../formapplication/service/FormApplicationService.java
src/.../formapplication/service/GeneralFormApplicationService.java
서비스와 저장소 계층에 신규 메서드 및 필드 추가
FormApplication 엔티티에 updateStatus 메서드 추가하여 상태 업데이트 지원
src/.../formapplication/entity/FormApplicationStatus.java FormApplicationStatus enum에 findStatus 메서드 추가 및 FINAL_FAIL 상수 수정
src/.../form/service/FacadeCentralFormApplicationServiceImplTest.java 테스트 클래스 명 및 메서드 이름 리팩토링: 폼 기능에서 폼 어플리케이션 기능으로 전환

Sequence Diagram(s)

sequenceDiagram
    participant U as 사용자
    participant C as CentralFormApplicationController
    participant F as FacadeCentralFormApplicationService
    participant R as Repository

    U->>C: getFormApplication(formId, applicationId, principal)
    C->>F: getFormApplication(formId, applicationId, user)
    F->>R: 폼 어플리케이션 데이터 조회
    R-->>F: FormApplicationQuery 반환
    F-->>C: FormApplicationQuery 반환
    C-->>U: FormApplicationResponse 전달
Loading
sequenceDiagram
    participant U as 사용자
    participant C as CentralFormApplicationController
    participant F as FacadeCentralFormApplicationService
    participant R as Repository

    U->>C: updateFormApplicationStatus(formId, principal, request)
    C->>F: updateStatus(command 생성 후 전달)
    F->>R: 해당 FormApplication 조회 및 상태 업데이트
    R-->>F: 업데이트된 데이터 저장 완료
    F-->>C: 처리 완료 (204 No Content)
    C-->>U: 204 No Content 응답
Loading

Suggested labels

D-1

Suggested reviewers

  • wonjunYou
  • 5uhwann
  • KoSeonJe

📜 Recent review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6b89e5e and 58226d7.

📒 Files selected for processing (3)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/entity/FormApplication.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (2 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/dto/command/UpdateFormApplicationStatusCommand.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/entity/FormApplication.java
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/dto/command/UpdateFormApplicationStatusCommand.java
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze

Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ 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.
    • Generate unit testing code for this file.
    • 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 generate unit testing code for this file.
    • @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 generate unit testing code.
    • @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.

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. (Beta)
  • @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.

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

🧹 Nitpick comments (8)
src/test/java/ddingdong/ddingdongBE/domain/form/service/FacadeCentralFormApplicationServiceImplTest.java (2)

48-83: 응답 상세 조회 테스트 로직 개선 제안
현재는 지원자의 이름만 검증하고 있습니다. 응답 내용을 좀 더 철저히 확인하기 위해 응답 필드 전체(학번, 학과 등)를 추가적으로 검증해 주시면 좋을 것 같습니다.


86-130: 상태 업데이트 테스트에 DB 재확인 절차 권장
상태 업데이트 후 메모리 상의 엔티티만 확인하고 있습니다. DB에 정상 반영되었는지 확인하기 위해, 별도의 find 로직을 통해 DB에서 다시 조회하여 상태를 검증하면 테스트 신뢰도를 높일 수 있습니다.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/repository/FormAnswerRepository.java (1)

4-10: JPA 쿼리 메서드 추가 검토
findAllByFormApplication() 메서드는 직관적인 네이밍을 통해 목적이 명확해 보입니다. 다만, 연관관계가 복잡해질 수 있으므로 페치 전략이나 추가 최적화가 필요한지 확인을 고려해 보세요.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/api/CentralFormApplicationApi.java (2)

37-47: API 오류 응답 문서화 추가 필요

지원자 상세 조회 API에 대한 오류 응답 케이스가 문서화되어 있지 않습니다.

다음과 같이 오류 응답을 추가하는 것을 제안합니다:

@Operation(summary = "지원자 상세 조회 API")
@ApiResponse(responseCode = "200", description = "지원자 상세 조회 성공",
        content = @Content(schema = @Schema(implementation = FormApplicationResponse.class)))
+@ApiResponse(responseCode = "404", description = "지원자를 찾을 수 없음")
+@ApiResponse(responseCode = "403", description = "접근 권한 없음")
@ResponseStatus(HttpStatus.OK)

49-59: API 오류 응답 문서화 및 요청 유효성 검증 추가 필요

상태 수정 API에 대한 오류 응답 케이스와 요청 본문 유효성 검증이 필요합니다.

다음과 같이 오류 응답을 추가하는 것을 제안합니다:

@Operation(summary = "지원자 개인 상태 수정 API")
@ApiResponse(responseCode = "204", description = "지원자 상태 수정 성공")
+@ApiResponse(responseCode = "400", description = "잘못된 상태 값")
+@ApiResponse(responseCode = "404", description = "지원자를 찾을 수 없음")
+@ApiResponse(responseCode = "403", description = "접근 권한 없음")
@ResponseStatus(HttpStatus.NO_CONTENT)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/dto/response/FormApplicationResponse.java (1)

16-29: 응답 필드에 대한 유효성 검증 어노테이션 추가 필요

응답 DTO의 필드들에 대한 기본적인 유효성 검증이 누락되어 있습니다.

다음과 같이 유효성 검증 어노테이션을 추가하는 것을 제안합니다:

@Builder
public record FormApplicationResponse (
        @Schema(description = "제출일시", example = "2025-01-01T00:00")
+       @NotNull(message = "제출일시는 필수입니다")
        LocalDateTime submittedAt,
        @Schema(description = "지원자 이름", example = "김띵동")
+       @NotBlank(message = "이름은 필수입니다")
        String name,
        @Schema(description = "지원자 학번", example = "60201111")
+       @Pattern(regexp = "^\\d{8}$", message = "학번은 8자리 숫자여야 합니다")
        String studentNumber,
        // ... 나머지 필드들에 대해서도 동일하게 적용
src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/dto/query/FormApplicationQuery.java (1)

64-93: 성능 최적화를 위한 리팩토링 제안

of 메서드에서 여러 번의 스트림 변환과 컬렉션 생성이 발생하고 있습니다.

다음과 같이 최적화하는 것을 제안합니다:

public static FormApplicationQuery of(FormApplication formApplication, List<FormField> formFields, List<FormAnswer> formAnswers) {
-   List<FormFieldListQuery> formFieldListQueries = formFields.stream()
-           .map(FormFieldListQuery::from)
-           .toList();
-   List<FormAnswerListQuery> formAnswerListQueries = formAnswers.stream()
-           .map(FormAnswerListQuery::from)
-           .toList();
-   Map<Long, FormAnswerListQuery> answerMap = formAnswerListQueries.stream()
-           .collect(Collectors.toMap(FormAnswerListQuery::fieldId, Function.identity()));
+   Map<Long, FormAnswerListQuery> answerMap = formAnswers.stream()
+           .map(FormAnswerListQuery::from)
+           .collect(Collectors.toMap(FormAnswerListQuery::fieldId, Function.identity()));

    List<FormFieldAnswerListQuery> formFieldAnswerListQueries = formFields.stream()
            .map(field -> {
+               FormFieldListQuery fieldQuery = FormFieldListQuery.from(field);
                FormAnswerListQuery answerQuery = answerMap.get(field.getId());
                if (answerQuery == null) {
                    answerQuery = FormAnswerListQuery.builder()
-                           .fieldId(fieldQuery.id())
+                           .fieldId(field.getId())
                            .value(null)
                            .build();
                }
-               return FormFieldAnswerListQuery.from(fieldQuery, answerQuery);
+               return FormFieldAnswerListQuery.from(fieldQuery, answerQuery);
            })
            .collect(Collectors.toList());
src/test/java/ddingdong/ddingdongBE/domain/form/service/FacadeCentralFormServiceImplTest.java (1)

53-82: createForm 테스트 케이스의 검증을 강화할 필요가 있습니다.

테스트의 기본 구조는 좋으나, 다음과 같은 개선이 필요합니다:

  1. 생성된 Form의 구체적인 필드 값 검증
  2. 생성된 FormField의 개수와 내용 검증
    @Test
    void createForm() {
        // given
        User user = fixtureMonkey.giveMeBuilder(User.class)
                .set("id", 1L)
                .set("Role", Role.CLUB)
                .set("deletedAt", null)
                .sample();
        User savedUser = userRepository.save(user);
        Club club = fixtureMonkey.giveMeBuilder(Club.class)
                .set("id", 1L)
                .set("user", savedUser)
                .set("score", null)
                .set("clubMembers", null)
                .set("deletedAt", null)
                .sample();
        clubRepository.save(club);
        CreateFormCommand createFormCommand = fixtureMonkey.giveMeBuilder(CreateFormCommand.class)
                .set("user", savedUser)
                .sample();
        // when
        facadeCentralFormService.createForm(createFormCommand);
        // then
        List<Form> forms = formRepository.findAll();
        List<FormField> formFields = formFieldRepository.findAll();

        assertThat(forms).isNotEmpty();
+       Form createdForm = forms.get(0);
+       assertThat(createdForm.getTitle()).isEqualTo(createFormCommand.title());
+       assertThat(createdForm.getDescription()).isEqualTo(createFormCommand.description());
+       assertThat(createdForm.getClub()).isEqualTo(club);

        assertThat(formFields).isNotEmpty();
+       assertThat(formFields).hasSameSizeAs(createFormCommand.formFieldCommands());
+       assertThat(formFields.get(0).getQuestion())
+           .isEqualTo(createFormCommand.formFieldCommands().get(0).question());
    }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 185c0bb and 6079928.

📒 Files selected for processing (19)
  • src/main/java/ddingdong/ddingdongBE/domain/form/service/dto/query/FormQuery.java (2 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/api/CentralFormApplicationApi.java (3 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/api/UserFormApplicationApi.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/CentralFormApplicationController.java (2 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/UserFormApplicationController.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/dto/request/UpdateFormApplicationStatusRequest.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/dto/response/FormApplicationResponse.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/entity/FormApplication.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/repository/FormAnswerRepository.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationService.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (3 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FormAnswerService.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FormApplicationService.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/GeneralFormAnswerService.java (2 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/GeneralFormApplicationService.java (2 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/dto/command/UpdateFormApplicationStatusCommand.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/dto/query/FormApplicationQuery.java (1 hunks)
  • src/test/java/ddingdong/ddingdongBE/domain/form/service/FacadeCentralFormApplicationServiceImplTest.java (1 hunks)
  • src/test/java/ddingdong/ddingdongBE/domain/form/service/FacadeCentralFormServiceImplTest.java (1 hunks)
🔇 Additional comments (16)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/UserFormApplicationController.java (1)

15-18: 메서드 이름 변경이 적절하게 구현되었습니다.

createFormResponse에서 createFormApplication으로의 메서드 이름 변경이 API 인터페이스와 일관성 있게 구현되었으며, 기능의 목적을 더 명확하게 표현합니다.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/api/UserFormApplicationApi.java (2)

15-18: API 문서화가 명확하게 작성되었습니다.

Swagger 어노테이션을 통한 API 문서화가 한글로 잘 작성되어 있으며, 응답 상태 코드와 설명이 적절합니다.


19-22: 메서드 시그니처가 RESTful 규칙을 잘 따르고 있습니다.

  • 엔드포인트 구조가 RESTful 규칙을 잘 따르고 있습니다
  • @Valid 어노테이션을 통한 요청 검증이 적절히 구현되어 있습니다
  • 메서드 이름이 기능을 더 명확하게 표현하도록 변경되었습니다
src/test/java/ddingdong/ddingdongBE/domain/form/service/FacadeCentralFormApplicationServiceImplTest.java (1)

11-16: 새로운 엔티티 및 DTO import 추가 확인
해당 import들이 신규 도메인 로직(FormApplication)과 잘 연동되어 보입니다. 현재로서는 충돌 없이 정상적으로 동작할 것으로 판단됩니다.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FormAnswerService.java (1)

4-13: 특정 지원서에 대한 답변 목록 조회 기능 추가
새로 추가된 getAllByApplication() 메서드는 지원서와 연관된 답변을 한 번에 가져올 수 있어 유용합니다. 파라미터와 반환타입이 명확해 보이며 설계가 적절합니다.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FormApplicationService.java (1)

13-15: 인터페이스 메서드 추가가 적절합니다!

getByIdupdateStatus 메서드가 단일 책임 원칙을 잘 따르고 있으며, 메서드 시그니처가 명확합니다.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationService.java (1)

12-14: 퍼사드 패턴이 잘 적용되었습니다!

사용자 권한 확인을 포함한 메서드 설계가 적절하며, 퍼사드 패턴의 의도를 잘 반영하고 있습니다.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/dto/request/UpdateFormApplicationStatusRequest.java (2)

9-13: 검증 로직이 잘 구현되었습니다!

  • @NotNull 검증과 한글 에러 메시지가 적절합니다
  • Swagger 문서화를 위한 @Schema 어노테이션이 잘 적용되었습니다

14-21: Command 변환 로직이 깔끔합니다!

toCommand 메서드가 모든 필요한 필드를 포함하여 명확하게 구현되었습니다.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/GeneralFormAnswerService.java (1)

25-28: 구현이 깔끔하고 명확합니다!

트랜잭션 처리가 적절하게 되어있고, 메서드 구현이 간단명료합니다.

src/main/java/ddingdong/ddingdongBE/domain/form/service/dto/query/FormQuery.java (1)

23-23: ID 필드 추가가 적절합니다!

폼 필드의 식별을 위한 ID 필드 추가는 적절한 변경사항입니다.

Also applies to: 33-33

src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/CentralFormApplicationController.java (1)

19-19: 변수명 변경이 적절합니다!

facadeCentralFormService에서 facadeCentralFormApplicationService로의 변경은 더 명확한 의미를 전달합니다.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (3)

33-34: 의존성 주입이 적절히 구성되었습니다.

새로 추가된 FormAnswerService와 FormFieldService의 의존성 주입이 잘 구성되어 있습니다.


56-67: getFormApplication 메소드의 보안 검증이 잘 구현되어 있습니다.

사용자의 권한 검증이 적절히 구현되어 있으며, 필요한 데이터를 효율적으로 조회하고 있습니다.

하지만 다음 사항들을 고려해보시기 바랍니다:

  1. formApplication이 해당 form에 속하는지 검증이 필요합니다.
  2. null 체크가 누락되어 있습니다.
    @Override
    public FormApplicationQuery getFormApplication(Long formId, Long applicationId, User user) {
        Club club = clubService.getByUserId(user.getId());
        Form form = formService.getById(formId);
        if (!form.getClub().equals(club)) {
            throw new AccessDeniedException("권한이 없습니다.");
        }
        FormApplication formApplication = formApplicationService.getById(applicationId);
+       if (!formApplication.getForm().equals(form)) {
+           throw new AccessDeniedException("해당 폼의 지원서가 아닙니다.");
+       }
        List<FormField> formFields = formFieldService.findAllByForm(form);
        List<FormAnswer> formAnswers = formAnswerService.getAllByApplication(formApplication);
        return FormApplicationQuery.of(formApplication, formFields, formAnswers);
    }

69-79: updateStatus 메소드의 트랜잭션 처리가 적절합니다.

@transactional 어노테이션이 올바르게 적용되어 있으며, 권한 검증도 잘 구현되어 있습니다.

다만 다음 개선사항을 고려해보시기 바랍니다:

  1. formApplication이 해당 form에 속하는지 검증이 필요합니다.
  2. status 값의 유효성 검증이 필요할 수 있습니다.
    @Transactional
    @Override
    public void updateStatus(UpdateFormApplicationStatusCommand command) {
        Club club = clubService.getByUserId(command.user().getId());
        Form form = formService.getById(command.formId());
        if (!form.getClub().equals(club)) {
            throw new AccessDeniedException("권한이 없습니다.");
        }
        FormApplication formApplication = formApplicationService.getById(command.applicationId());
+       if (!formApplication.getForm().equals(form)) {
+           throw new AccessDeniedException("해당 폼의 지원서가 아닙니다.");
+       }
        formApplicationService.updateStatus(command.applicationId(), command.status());
    }
src/test/java/ddingdong/ddingdongBE/domain/form/service/FacadeCentralFormServiceImplTest.java (1)

51-51: FixtureMonkey 설정이 적절합니다.

테스트 데이터 생성을 위한 FixtureMonkey 설정이 잘 되어 있습니다.

Comment on lines 16 to 21
public FormApplication toEntity(Form form) {
return FormApplication.builder()
.form(form)
.status(status)
.build();
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

toEntity 메서드의 구현이 불완전해 보입니다.

toEntity 메서드가 formstatus만 설정하고 있습니다. 다음 사항들을 고려해주세요:

  • 다른 필수 필드들이 누락되었을 수 있습니다
  • 기존 엔티티의 다른 필드들이 손실될 수 있습니다

다음과 같이 수정하는 것을 고려해보세요:

public FormApplication toEntity(Form form) {
    return FormApplication.builder()
            .form(form)
            .status(status)
+           // 다른 필수 필드들 추가
            .build();
}

또는 상태 업데이트만을 위한 별도의 메서드를 만드는 것을 고려해보세요.

📝 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 FormApplication toEntity(Form form) {
return FormApplication.builder()
.form(form)
.status(status)
.build();
}
public FormApplication toEntity(Form form) {
return FormApplication.builder()
.form(form)
.status(status)
// 다른 필수 필드들 추가
.build();
}

Comment on lines 45 to 47
public void update(FormApplicationStatus status) {
this.status = status;
}
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 체크와 상태 전이 규칙 검증을 추가하는 것이 좋습니다.

다음과 같이 개선해보세요:

 public void update(FormApplicationStatus status) {
+    if (status == null) {
+        throw new IllegalArgumentException("상태값은 null이 될 수 없습니다.");
+    }
+    validateStatusTransition(status);
     this.status = status;
 }

+private void validateStatusTransition(FormApplicationStatus newStatus) {
+    // 현재 상태에서 새로운 상태로의 전이가 가능한지 검증
+    if (!this.status.canTransitionTo(newStatus)) {
+        throw new IllegalStateException(
+            String.format("현재 상태 %s에서 %s로 변경할 수 없습니다.", this.status, newStatus)
+        );
+    }
+}
📝 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 void update(FormApplicationStatus status) {
this.status = status;
}
public void update(FormApplicationStatus status) {
if (status == null) {
throw new IllegalArgumentException("상태값은 null이 될 수 없습니다.");
}
validateStatusTransition(status);
this.status = status;
}
private void validateStatusTransition(FormApplicationStatus newStatus) {
// 현재 상태에서 새로운 상태로의 전이가 가능한지 검증
if (!this.status.canTransitionTo(newStatus)) {
throw new IllegalStateException(
String.format("현재 상태 %s에서 %s로 변경할 수 없습니다.", this.status, newStatus)
);
}
}

Comment on lines 28 to 33
@Override
public FormApplicationResponse getFormApplication(Long formId, Long applicationId, PrincipalDetails principalDetails) {
User user = principalDetails.getUser();
FormApplicationQuery query = facadeCentralFormApplicationService.getFormApplication(formId, applicationId, user);
return FormApplicationResponse.from(query);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

요청 파라미터 검증이 필요합니다.

엔드포인트에 @Valid 또는 @validated 어노테이션을 추가하여 입력값 검증을 구현하는 것이 좋습니다.

다음과 같이 개선해보세요:

 @Override
-public FormApplicationResponse getFormApplication(Long formId, Long applicationId, PrincipalDetails principalDetails) {
+public FormApplicationResponse getFormApplication(
+    @Positive(message = "폼 ID는 양수여야 합니다") @PathVariable Long formId,
+    @Positive(message = "지원서 ID는 양수여야 합니다") @PathVariable Long applicationId,
+    @Valid PrincipalDetails principalDetails) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public FormApplicationResponse getFormApplication(Long formId, Long applicationId, PrincipalDetails principalDetails) {
User user = principalDetails.getUser();
FormApplicationQuery query = facadeCentralFormApplicationService.getFormApplication(formId, applicationId, user);
return FormApplicationResponse.from(query);
}
@Override
public FormApplicationResponse getFormApplication(
@Positive(message = "폼 ID는 양수여야 합니다") @PathVariable Long formId,
@Positive(message = "지원서 ID는 양수여야 합니다") @PathVariable Long applicationId,
@Valid PrincipalDetails principalDetails) {
User user = principalDetails.getUser();
FormApplicationQuery query = facadeCentralFormApplicationService.getFormApplication(formId, applicationId, user);
return FormApplicationResponse.from(query);
}

Comment on lines 35 to 39
@Override
public void updateFormApplicationStatus(Long formId, Long applicationId, PrincipalDetails principalDetails, UpdateFormApplicationStatusRequest request) {
User user = principalDetails.getUser();
facadeCentralFormApplicationService.updateStatus(request.toCommand(formId, applicationId, user));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

상태 업데이트 요청에 대한 검증이 필요합니다.

상태 변경 요청에 대한 유효성 검증을 추가하고, 적절한 예외 처리를 구현해야 합니다.

다음과 같이 개선해보세요:

 @Override
-public void updateFormApplicationStatus(Long formId, Long applicationId, PrincipalDetails principalDetails, UpdateFormApplicationStatusRequest request) {
+public void updateFormApplicationStatus(
+    @Positive(message = "폼 ID는 양수여야 합니다") @PathVariable Long formId,
+    @Positive(message = "지원서 ID는 양수여야 합니다") @PathVariable Long applicationId,
+    @Valid PrincipalDetails principalDetails,
+    @Valid @RequestBody UpdateFormApplicationStatusRequest request) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public void updateFormApplicationStatus(Long formId, Long applicationId, PrincipalDetails principalDetails, UpdateFormApplicationStatusRequest request) {
User user = principalDetails.getUser();
facadeCentralFormApplicationService.updateStatus(request.toCommand(formId, applicationId, user));
}
@Override
public void updateFormApplicationStatus(
@Positive(message = "폼 ID는 양수여야 합니다") @PathVariable Long formId,
@Positive(message = "지원서 ID는 양수여야 합니다") @PathVariable Long applicationId,
@Valid PrincipalDetails principalDetails,
@Valid @RequestBody UpdateFormApplicationStatusRequest request) {
User user = principalDetails.getUser();
facadeCentralFormApplicationService.updateStatus(request.toCommand(formId, applicationId, user));
}

Comment on lines 40 to 44
@Override
public void updateStatus(Long applicationId, FormApplicationStatus status) {
FormApplication formApplication = formApplicationRepository.findById(applicationId).orElse(null);
formApplication.update(status);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

NullPointerException 방지를 위한 null 체크 추가 필요

updateStatus 메서드에서 formApplication이 null일 경우 NullPointerException이 발생할 수 있습니다.

다음과 같이 수정하는 것을 제안합니다:

@Override
public void updateStatus(Long applicationId, FormApplicationStatus status) {
    FormApplication formApplication = formApplicationRepository.findById(applicationId).orElse(null);
+   if (formApplication == null) {
+       throw new IllegalArgumentException("지원서를 찾을 수 없습니다: " + applicationId);
+   }
    formApplication.update(status);
}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public void updateStatus(Long applicationId, FormApplicationStatus status) {
FormApplication formApplication = formApplicationRepository.findById(applicationId).orElse(null);
formApplication.update(status);
}
@Override
public void updateStatus(Long applicationId, FormApplicationStatus status) {
FormApplication formApplication = formApplicationRepository.findById(applicationId).orElse(null);
if (formApplication == null) {
throw new IllegalArgumentException("지원서를 찾을 수 없습니다: " + applicationId);
}
formApplication.update(status);
}

Comment on lines 242 to 262
@DisplayName("동아리는 폼지를 상세조회 할 수 있다.")
@Test
void getForm() {
// given
Form form = fixtureMonkey.giveMeBuilder(Form.class)
.set("id", 1L)
.set("title", "제목1")
.set("club", null)
.sample();
Form form2 = fixtureMonkey.giveMeBuilder(Form.class)
.set("id", 2L)
.set("title", "제목2")
.set("club", null)
.sample();
formService.create(form);
formService.create(form2);
// when
FormQuery formQuery = facadeCentralFormService.getForm(1L);
// then
assertThat(formQuery.title()).isEqualTo("제목1");
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

getForm 테스트에서 예외 케이스 처리가 누락되었습니다.

존재하지 않는 Form ID를 조회하는 경우에 대한 테스트가 필요합니다.

    @Test
    void getForm() {
        // ... existing test code ...
+
+       @Test
+       @DisplayName("존재하지 않는 폼 조회시 예외가 발생한다")
+       void getForm_NotFound() {
+           // when & then
+           assertThrows(EntityNotFoundException.class,
+               () -> facadeCentralFormService.getForm(999L));
+       }
    }

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

Comment on lines 84 to 127
@DisplayName("폼지와 폼지 질문을 수정할 수 있다.")
@Test
void updateForm() {
// given
User user = fixtureMonkey.giveMeBuilder(User.class)
.set("id", 1L)
.set("Role", Role.CLUB)
.set("deletedAt", null)
.sample();
User savedUser = userRepository.save(user);
Club club = fixtureMonkey.giveMeBuilder(Club.class)
.set("id", 1L)
.set("user", savedUser)
.set("score", null)
.set("clubMembers", null)
.set("deletedAt", null)
.sample();
clubRepository.save(club);
Form form = fixtureMonkey.giveMeBuilder(Form.class)
.set("club", club)
.sample();
Form savedForm = formService.create(form);
UpdateFormCommand updateFormCommand = fixtureMonkey.giveMeBuilder(UpdateFormCommand.class)
.set("title", "수정된 제목")
.set("description", "수정된 설명")
.set("formId", savedForm.getId())
.set("formFieldCommands", List.of(
fixtureMonkey.giveMeBuilder(UpdateFormFieldCommand.class)
.set("question", "수정된 질문")
.sample())
)
.sample();
// when
facadeCentralFormService.updateForm(updateFormCommand);
// then
Form found = formRepository.findById(savedForm.getId()).orElse(null);
List<FormField> formFields = formFieldRepository.findAllByForm(found);
assertThat(found).isNotNull();
assertThat(found.getTitle()).isEqualTo("수정된 제목");
assertThat(found.getDescription()).isEqualTo("수정된 설명");
assertThat(formFields).isNotEmpty();
assertThat(formFields.get(0).getQuestion()).isEqualTo("수정된 질문");

}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

updateForm 테스트의 중복된 설정 코드를 개선해야 합니다.

테스트 설정 코드가 createForm 테스트와 중복됩니다. @beforeeach를 사용하여 공통 설정을 분리하는 것이 좋습니다.

+   private User savedUser;
+   private Club club;
+
+   @BeforeEach
+   void setUp() {
+       User user = fixtureMonkey.giveMeBuilder(User.class)
+               .set("id", 1L)
+               .set("Role", Role.CLUB)
+               .set("deletedAt", null)
+               .sample();
+       savedUser = userRepository.save(user);
+       Club clubEntity = fixtureMonkey.giveMeBuilder(Club.class)
+               .set("id", 1L)
+               .set("user", savedUser)
+               .set("score", null)
+               .set("clubMembers", null)
+               .set("deletedAt", null)
+               .sample();
+       club = clubRepository.save(clubEntity);
+   }

    @Test
    void updateForm() {
-       // given
-       User user = fixtureMonkey.giveMeBuilder(User.class)...
-       User savedUser = userRepository.save(user);
-       Club club = fixtureMonkey.giveMeBuilder(Club.class)...
-       clubRepository.save(club);
        Form form = fixtureMonkey.giveMeBuilder(Form.class)
                .set("club", club)
                .sample();
        // ... rest of the test
    }
📝 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
@DisplayName("폼지와 폼지 질문을 수정할 수 있다.")
@Test
void updateForm() {
// given
User user = fixtureMonkey.giveMeBuilder(User.class)
.set("id", 1L)
.set("Role", Role.CLUB)
.set("deletedAt", null)
.sample();
User savedUser = userRepository.save(user);
Club club = fixtureMonkey.giveMeBuilder(Club.class)
.set("id", 1L)
.set("user", savedUser)
.set("score", null)
.set("clubMembers", null)
.set("deletedAt", null)
.sample();
clubRepository.save(club);
Form form = fixtureMonkey.giveMeBuilder(Form.class)
.set("club", club)
.sample();
Form savedForm = formService.create(form);
UpdateFormCommand updateFormCommand = fixtureMonkey.giveMeBuilder(UpdateFormCommand.class)
.set("title", "수정된 제목")
.set("description", "수정된 설명")
.set("formId", savedForm.getId())
.set("formFieldCommands", List.of(
fixtureMonkey.giveMeBuilder(UpdateFormFieldCommand.class)
.set("question", "수정된 질문")
.sample())
)
.sample();
// when
facadeCentralFormService.updateForm(updateFormCommand);
// then
Form found = formRepository.findById(savedForm.getId()).orElse(null);
List<FormField> formFields = formFieldRepository.findAllByForm(found);
assertThat(found).isNotNull();
assertThat(found.getTitle()).isEqualTo("수정된 제목");
assertThat(found.getDescription()).isEqualTo("수정된 설명");
assertThat(formFields).isNotEmpty();
assertThat(formFields.get(0).getQuestion()).isEqualTo("수정된 질문");
}
public class FacadeCentralFormServiceImplTest {
// ... (other class members and imports)
private User savedUser;
private Club club;
@BeforeEach
void setUp() {
User user = fixtureMonkey.giveMeBuilder(User.class)
.set("id", 1L)
.set("Role", Role.CLUB)
.set("deletedAt", null)
.sample();
savedUser = userRepository.save(user);
Club clubEntity = fixtureMonkey.giveMeBuilder(Club.class)
.set("id", 1L)
.set("user", savedUser)
.set("score", null)
.set("clubMembers", null)
.set("deletedAt", null)
.sample();
club = clubRepository.save(clubEntity);
}
@DisplayName("폼지와 폼지 질문을 수정할 수 있다.")
@Test
void updateForm() {
// given
Form form = fixtureMonkey.giveMeBuilder(Form.class)
.set("club", club)
.sample();
Form savedForm = formService.create(form);
UpdateFormCommand updateFormCommand = fixtureMonkey.giveMeBuilder(UpdateFormCommand.class)
.set("title", "수정된 제목")
.set("description", "수정된 설명")
.set("formId", savedForm.getId())
.set("formFieldCommands", List.of(
fixtureMonkey.giveMeBuilder(UpdateFormFieldCommand.class)
.set("question", "수정된 질문")
.sample())
)
.sample();
// when
facadeCentralFormService.updateForm(updateFormCommand);
// then
Form found = formRepository.findById(savedForm.getId()).orElse(null);
List<FormField> formFields = formFieldRepository.findAllByForm(found);
assertThat(found).isNotNull();
assertThat(found.getTitle()).isEqualTo("수정된 제목");
assertThat(found.getDescription()).isEqualTo("수정된 설명");
assertThat(formFields).isNotEmpty();
assertThat(formFields.get(0).getQuestion()).isEqualTo("수정된 질문");
}
// ... (other tests and methods)
}

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

🧹 Nitpick comments (1)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (1)

56-60: 상태 변경에 대한 로깅과 유효성 검증이 필요합니다.

상태 변경은 중요한 도메인 이벤트이므로, 다음 개선사항을 고려해주세요:

  1. 상태 변경 전 유효성 검증
  2. 상태 변경 이력 로깅
  3. 상태 변경 이벤트 발행 고려

예시 코드:

 @Transactional
 @Override
 public void updateStatus(UpdateFormApplicationStatusCommand command) {
+    log.info("지원서 상태 변경 시도: applicationId={}, newStatus={}", 
+        command.applicationId(), command.status());
+    
+    FormApplication application = formApplicationService.getById(command.applicationId());
+    Status oldStatus = application.getStatus();
+    
     formApplicationService.updateStatus(command.applicationId(), command.status());
+    
+    log.info("지원서 상태 변경 완료: applicationId={}, oldStatus={}, newStatus={}", 
+        command.applicationId(), oldStatus, command.status());
 }
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6079928 and 3808c28.

📒 Files selected for processing (1)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze
🔇 Additional comments (2)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (2)

3-7: 의존성 추가가 적절합니다!

새로운 기능 구현을 위해 필요한 의존성들이 잘 추가되었습니다.

Also applies to: 29-30


47-54: 유효성 검증 로직 추가가 필요합니다.

다음 사항들에 대한 검증이 필요해 보입니다:

  1. 조회된 엔티티들의 null 체크
  2. formApplication이 주어진 form에 속하는지 검증

예시 코드:

 @Override
 public FormApplicationQuery getFormApplication(Long formId, Long applicationId, User user) {
     Form form = formService.getById(formId);
     FormApplication formApplication = formApplicationService.getById(applicationId);
+    if (!formApplication.getForm().getId().equals(formId)) {
+        throw new IllegalArgumentException("해당 폼에 속하지 않는 지원서입니다.");
+    }
     List<FormField> formFields = formFieldService.findAllByForm(form);
     List<FormAnswer> formAnswers = formAnswerService.getAllByApplication(formApplication);
     return FormApplicationQuery.of(formApplication, formFields, formAnswers);
 }

Copy link

sonarqubecloud bot commented Feb 5, 2025

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@github-actions github-actions bot added the D-3 label Feb 5, 2025
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: 6

🧹 Nitpick comments (1)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FormApplicationService.java (1)

14-16: 메서드 문서화 추가 필요

인터페이스에 새로 추가된 메서드들의 동작과 예외 상황을 명확히 하기 위해 JavaDoc 문서화가 필요합니다.

다음과 같이 수정하는 것을 제안합니다:

+    /**
+     * 지원서 ID로 단일 지원서를 조회합니다.
+     *
+     * @param applicationId 조회할 지원서 ID
+     * @return 조회된 지원서, 없을 경우 null
+     */
     FormApplication getById(Long applicationId);

+    /**
+     * 여러 지원서 ID로 복수의 지원서를 조회합니다.
+     *
+     * @param applicationIds 조회할 지원서 ID 목록
+     * @return 조회된 지원서 목록
+     */
     List<FormApplication> getAllById(List<Long> applicationIds);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3808c28 and d6e28ae.

📒 Files selected for processing (7)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/api/CentralFormApplicationApi.java (3 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/CentralFormApplicationController.java (2 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/dto/request/UpdateFormApplicationStatusRequest.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (3 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FormApplicationService.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/GeneralFormApplicationService.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/dto/command/UpdateFormApplicationStatusCommand.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze
🔇 Additional comments (4)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/dto/command/UpdateFormApplicationStatusCommand.java (1)

18-23: ⚠️ Potential issue

toEntity 메서드의 구현이 불완전합니다.

현재 구현은 form과 status만 설정하고 있어 다른 필수 필드들이 누락될 수 있습니다. 상태 업데이트만을 위한 별도의 메서드를 만드는 것이 좋습니다.

다음과 같이 수정하는 것을 제안합니다:

-public FormApplication toEntity(Form form) {
-    return FormApplication.builder()
-            .form(form)
-            .status(status)
-            .build();
-}
+public FormApplication updateStatus(FormApplication application) {
+    return application.toBuilder()
+            .status(status)
+            .build();
+}

Likely invalid or redundant comment.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/CentralFormApplicationController.java (1)

29-33: ⚠️ Potential issue

경로 변수에 대한 유효성 검증이 필요합니다.

formId와 applicationId에 대한 유효성 검증이 누락되어 있습니다.

다음과 같이 수정하는 것을 제안합니다:

@Override
public FormApplicationResponse getFormApplication(
-    Long formId,
-    Long applicationId,
+    @Positive(message = "폼 ID는 양수여야 합니다") @PathVariable Long formId,
+    @Positive(message = "지원서 ID는 양수여야 합니다") @PathVariable Long applicationId,
    @Valid PrincipalDetails principalDetails) {

Likely invalid or redundant comment.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (2)

3-8: 의존성 주입이 잘 구성되어 있습니다!

새로운 의존성들이 단일 책임 원칙(SRP)을 잘 따르고 있으며, 생성자 주입을 통해 적절하게 관리되고 있습니다.

Also applies to: 30-32


49-56: 폼과 지원서 간의 유효성 검증이 필요합니다.

다음과 같은 개선사항을 제안드립니다:

  1. 폼 애플리케이션이 해당 폼에 속하는지 검증이 필요합니다.
  2. user 파라미터가 현재 사용되지 않고 있습니다. 권한 검증에 활용하거나 불필요하다면 제거해주세요.

아래는 개선된 구현을 제안드립니다:

 public FormApplicationQuery getFormApplication(Long formId, Long applicationId, User user) {
     Form form = formService.getById(formId);
     FormApplication formApplication = formApplicationService.getById(applicationId);
+    if (!formApplication.getForm().getId().equals(formId)) {
+        throw new IllegalArgumentException("해당 폼에 속하지 않는 지원서입니다.");
+    }
     List<FormField> formFields = formFieldService.findAllByForm(form);
     List<FormAnswer> formAnswers = formAnswerService.getAllByApplication(formApplication);
     return FormApplicationQuery.of(formApplication, formFields, formAnswers);
 }

Comment on lines 39 to 42
@Override
public List<FormApplication> getAllById(List<Long> applicationIds) {
return formApplicationRepository.findAllById(applicationIds);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

입력 검증 및 예외 처리 추가 필요

applicationIds에 대한 유효성 검사와 결과가 없는 경우에 대한 처리가 필요합니다.

다음과 같이 수정하는 것을 제안합니다:

    @Override
    public List<FormApplication> getAllById(List<Long> applicationIds) {
+        if (applicationIds == null || applicationIds.isEmpty()) {
+            return new ArrayList<>();
+        }
+        
+        // 중복 제거 및 null 값 필터링
+        applicationIds = applicationIds.stream()
+            .filter(id -> id != null)
+            .distinct()
+            .collect(Collectors.toList());
+            
         return formApplicationRepository.findAllById(applicationIds);
    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public List<FormApplication> getAllById(List<Long> applicationIds) {
return formApplicationRepository.findAllById(applicationIds);
}
@Override
public List<FormApplication> getAllById(List<Long> applicationIds) {
if (applicationIds == null || applicationIds.isEmpty()) {
return new ArrayList<>();
}
// 중복 제거 및 null 값 필터링
applicationIds = applicationIds.stream()
.filter(id -> id != null)
.distinct()
.collect(Collectors.toList());
return formApplicationRepository.findAllById(applicationIds);
}

Comment on lines 34 to 37
@Override
public FormApplication getById(Long applicationId) {
return formApplicationRepository.findById(applicationId).orElse(null);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Optional을 사용한 null 처리 개선 필요

null을 직접 반환하는 대신 Optional을 사용하거나 예외를 throw하는 것이 더 안전한 방식입니다.

다음과 같이 수정하는 것을 제안합니다:

    @Override
    public FormApplication getById(Long applicationId) {
-        return formApplicationRepository.findById(applicationId).orElse(null);
+        return formApplicationRepository.findById(applicationId)
+            .orElseThrow(() -> new IllegalArgumentException("지원서를 찾을 수 없습니다: " + applicationId));
    }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@Override
public FormApplication getById(Long applicationId) {
return formApplicationRepository.findById(applicationId).orElse(null);
}
@Override
public FormApplication getById(Long applicationId) {
- return formApplicationRepository.findById(applicationId).orElse(null);
+ return formApplicationRepository.findById(applicationId)
+ .orElseThrow(() -> new IllegalArgumentException("지원서를 찾을 수 없습니다: " + applicationId));
}

Comment on lines 13 to 14
@Schema(description = "수정할 지원자 상태", example = "FIRST_PASS")
List<Long> applicationIds,
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

applicationIds의 스키마 설명이 잘못되었습니다.

applicationIds 필드의 @Schema 설명이 status 필드와 동일하게 설정되어 있습니다.

다음과 같이 수정하는 것을 제안합니다:

-@Schema(description = "수정할 지원자 상태", example = "FIRST_PASS")
+@Schema(description = "수정할 지원자 ID 목록", example = "[1, 2, 3]")
List<Long> applicationIds,
📝 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
@Schema(description = "수정할 지원자 상태", example = "FIRST_PASS")
List<Long> applicationIds,
@Schema(description = "수정할 지원자 ID 목록", example = "[1, 2, 3]")
List<Long> applicationIds,

Comment on lines 37 to 47
@Operation(summary = "지원자 상세 조회 API")
@ApiResponse(responseCode = "200", description = "지원자 상세 조회 성공",
content = @Content(schema = @Schema(implementation = FormApplicationResponse.class)))
@ResponseStatus(HttpStatus.OK)
@SecurityRequirement(name = "AccessToken")
@GetMapping("/my/forms/{formId}/applications/{applicationId}")
FormApplicationResponse getFormApplication(
@PathVariable("formId") Long formId,
@PathVariable("applicationId") Long applicationId,
@AuthenticationPrincipal PrincipalDetails principalDetails
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

API 문서에 오류 응답에 대한 설명이 필요합니다.

현재 성공 케이스만 문서화되어 있습니다. 주요 오류 시나리오(권한 없음, 리소스 없음 등)에 대한 응답도 문서화가 필요합니다.

다음과 같이 추가하는 것을 제안합니다:

@ApiResponse(responseCode = "200", description = "지원자 상세 조회 성공",
        content = @Content(schema = @Schema(implementation = FormApplicationResponse.class)))
+@ApiResponse(responseCode = "403", description = "권한 없음")
+@ApiResponse(responseCode = "404", description = "지원서를 찾을 수 없음")
📝 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
@Operation(summary = "지원자 상세 조회 API")
@ApiResponse(responseCode = "200", description = "지원자 상세 조회 성공",
content = @Content(schema = @Schema(implementation = FormApplicationResponse.class)))
@ResponseStatus(HttpStatus.OK)
@SecurityRequirement(name = "AccessToken")
@GetMapping("/my/forms/{formId}/applications/{applicationId}")
FormApplicationResponse getFormApplication(
@PathVariable("formId") Long formId,
@PathVariable("applicationId") Long applicationId,
@AuthenticationPrincipal PrincipalDetails principalDetails
);
@Operation(summary = "지원자 상세 조회 API")
@ApiResponse(responseCode = "200", description = "지원자 상세 조회 성공",
content = @Content(schema = @Schema(implementation = FormApplicationResponse.class)))
@ApiResponse(responseCode = "403", description = "권한 없음")
@ApiResponse(responseCode = "404", description = "지원서를 찾을 수 없음")
@ResponseStatus(HttpStatus.OK)
@SecurityRequirement(name = "AccessToken")
@GetMapping("/my/forms/{formId}/applications/{applicationId}")
FormApplicationResponse getFormApplication(
@PathVariable("formId") Long formId,
@PathVariable("applicationId") Long applicationId,
@AuthenticationPrincipal PrincipalDetails principalDetails
);

Comment on lines +49 to +58
@Operation(summary = "지원자 상태 수정 API")
@ApiResponse(responseCode = "204", description = "지원자 상태 수정 성공")
@ResponseStatus(HttpStatus.NO_CONTENT)
@SecurityRequirement(name = "AccessToken")
@PatchMapping("/my/forms/{formId}/applications")
void updateFormApplicationStatus(
@PathVariable("formId") Long formId,
@AuthenticationPrincipal PrincipalDetails principalDetails,
@Valid @RequestBody UpdateFormApplicationStatusRequest request
);
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

상태 업데이트 API에도 오류 응답 문서화가 필요합니다.

상태 업데이트 API도 주요 오류 시나리오에 대한 응답 문서화가 필요합니다.

다음과 같이 추가하는 것을 제안합니다:

@ApiResponse(responseCode = "204", description = "지원자 상태 수정 성공")
+@ApiResponse(responseCode = "400", description = "잘못된 요청")
+@ApiResponse(responseCode = "403", description = "권한 없음")
+@ApiResponse(responseCode = "404", description = "지원서를 찾을 수 없음")
📝 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
@Operation(summary = "지원자 상태 수정 API")
@ApiResponse(responseCode = "204", description = "지원자 상태 수정 성공")
@ResponseStatus(HttpStatus.NO_CONTENT)
@SecurityRequirement(name = "AccessToken")
@PatchMapping("/my/forms/{formId}/applications")
void updateFormApplicationStatus(
@PathVariable("formId") Long formId,
@AuthenticationPrincipal PrincipalDetails principalDetails,
@Valid @RequestBody UpdateFormApplicationStatusRequest request
);
@Operation(summary = "지원자 상태 수정 API")
@ApiResponse(responseCode = "204", description = "지원자 상태 수정 성공")
@ApiResponse(responseCode = "400", description = "잘못된 요청")
@ApiResponse(responseCode = "403", description = "권한 없음")
@ApiResponse(responseCode = "404", description = "지원서를 찾을 수 없음")
@ResponseStatus(HttpStatus.NO_CONTENT)
@SecurityRequirement(name = "AccessToken")
@PatchMapping("/my/forms/{formId}/applications")
void updateFormApplicationStatus(
@PathVariable("formId") Long formId,
@AuthenticationPrincipal PrincipalDetails principalDetails,
@Valid @RequestBody UpdateFormApplicationStatusRequest request
);

Comment on lines 58 to 64
@Transactional
@Override
public void updateStatus(UpdateFormApplicationStatusCommand command) {
List<FormApplication> formApplications = formApplicationService.getAllById(command.applicationIds());
formApplications.forEach(formApplication -> formApplication.update(command.status()));
formApplicationRepository.saveAll(formApplications);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

상태 업데이트 시 유효성 검증과 예외 처리가 필요합니다.

다음과 같은 개선사항을 제안드립니다:

  1. 상태값의 유효성 검증
  2. 존재하지 않는 지원서에 대한 예외 처리
  3. 벌크 업데이트 실패 시의 예외 처리

아래는 개선된 구현을 제안드립니다:

 @Transactional
 @Override
 public void updateStatus(UpdateFormApplicationStatusCommand command) {
+    if (command.status() == null) {
+        throw new IllegalArgumentException("상태값은 null일 수 없습니다.");
+    }
     List<FormApplication> formApplications = formApplicationService.getAllById(command.applicationIds());
+    if (formApplications.size() != command.applicationIds().size()) {
+        throw new IllegalArgumentException("존재하지 않는 지원서가 포함되어 있습니다.");
+    }
     formApplications.forEach(formApplication -> formApplication.update(command.status()));
-    formApplicationRepository.saveAll(formApplications);
+    try {
+        formApplicationRepository.saveAll(formApplications);
+    } catch (Exception e) {
+        throw new RuntimeException("상태 업데이트 중 오류가 발생했습니다.", e);
+    }
 }
📝 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
@Transactional
@Override
public void updateStatus(UpdateFormApplicationStatusCommand command) {
List<FormApplication> formApplications = formApplicationService.getAllById(command.applicationIds());
formApplications.forEach(formApplication -> formApplication.update(command.status()));
formApplicationRepository.saveAll(formApplications);
}
@Transactional
@Override
public void updateStatus(UpdateFormApplicationStatusCommand command) {
if (command.status() == null) {
throw new IllegalArgumentException("상태값은 null일 수 없습니다.");
}
List<FormApplication> formApplications = formApplicationService.getAllById(command.applicationIds());
if (formApplications.size() != command.applicationIds().size()) {
throw new IllegalArgumentException("존재하지 않는 지원서가 포함되어 있습니다.");
}
formApplications.forEach(formApplication -> formApplication.update(command.status()));
try {
formApplicationRepository.saveAll(formApplications);
} catch (Exception e) {
throw new RuntimeException("상태 업데이트 중 오류가 발생했습니다.", e);
}
}

@Seooooo24 Seooooo24 changed the title [DDING-93] 지원자 개인 상태 수정 API 구현 [DDING-93] 지원자 상태 수정 API 구현 Feb 6, 2025
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: 1

🧹 Nitpick comments (2)
src/test/java/ddingdong/ddingdongBE/domain/form/service/FacadeCentralFormApplicationServiceImplTest.java (2)

1-1: 패키지 구조 업데이트 필요

현재 테스트 클래스가 form.service 패키지에 위치해 있지만, 클래스의 주요 기능이 formapplication 도메인을 다루고 있습니다. 패키지 구조를 formapplication.service로 이동하는 것이 더 적절할 것 같습니다.

-package ddingdong.ddingdongBE.domain.form.service;
+package ddingdong.ddingdongBE.domain.formapplication.service;

49-85: 테스트 검증 범위 확장 필요

현재 테스트는 지원자의 이름만 검증하고 있습니다. 응답의 완전성을 보장하기 위해 다음 필드들도 검증이 필요합니다:

  • studentNumber
  • department
  • status
         // then
         assertThat(formApplicationQuery.name()).isEqualTo("지원자1");
+        assertThat(formApplicationQuery.studentNumber()).isEqualTo("60201115");
+        assertThat(formApplicationQuery.department()).isEqualTo("융합소프트웨어학부");
+        assertThat(formApplicationQuery.status()).isEqualTo(FormApplicationStatus.SUBMITTED);
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6e28ae and b3d768f.

📒 Files selected for processing (1)
  • src/test/java/ddingdong/ddingdongBE/domain/form/service/FacadeCentralFormApplicationServiceImplTest.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze

Comment on lines 87 to 143
.set("deletedAt", null)
.sample();
clubRepository.save(club);
User user2 = fixtureMonkey.giveMeBuilder(User.class)
.set("id", 2L)
.set("Role", Role.CLUB)
.set("deletedAt", null)
.sample();
User savedUser2 = userRepository.save(user2);
Club club2 = fixtureMonkey.giveMeBuilder(Club.class)
.set("id", 2L)
.set("user", savedUser2)
.set("score", null)
.set("clubMembers", null)
.set("deletedAt", null)
.sample();
clubRepository.save(club2);

Form form = fixtureMonkey.giveMeBuilder(Form.class)
.set("club", club)
.sample();
Form savedForm = formService.create(form);
// when //then
assertThrows(NonHaveAuthority.class, () -> {
facadeCentralFormService.deleteForm(savedForm.getId(), user2);
});
}

@DisplayName("동아리는 자신의 폼지를 전부 조회할 수 있다.")
@Test
void getAllMyForm() {
// given
User user = fixtureMonkey.giveMeBuilder(User.class)
Club savedClub = clubRepository.saveAndFlush(club);
Form form = fixture.giveMeBuilder(Form.class)
.set("id", 1L)
.set("Role", Role.CLUB)
.set("deletedAt", null)
.sample();
User savedUser = userRepository.save(user);
Club club = fixtureMonkey.giveMeBuilder(Club.class)
.set("id", 1L)
.set("user", savedUser)
.set("score", null)
.set("clubMembers", null)
.set("deletedAt", null)
.sample();
clubRepository.save(club);
Form form = fixtureMonkey.giveMeBuilder(Form.class)
.set("title", "제목1")
.set("club", club)
.sample();
Form form2 = fixtureMonkey.giveMeBuilder(Form.class)
.set("title", "제목2")
.set("club", club)
.set("club", savedClub)
.sample();
Form savedForm = formRepository.saveAndFlush(form);
FormApplication formApplication1 = FormApplication.builder()
.name("지원자1")
.studentNumber("60201115")
.department("융합소프트웨어학부")
.status(FormApplicationStatus.SUBMITTED)
.form(savedForm)
.build();
FormApplication formApplication2 = FormApplication.builder()
.name("지원자2")
.studentNumber("602011156")
.department("디지털콘텐츠디자인학과")
.status(FormApplicationStatus.SUBMITTED)
.form(savedForm)
.build();
FormApplication savedApplication1 = formApplicationService.create(formApplication1);
FormApplication savedApplication2 = formApplicationService.create(formApplication2);
List<Long> applicationIds = new ArrayList<>();
applicationIds.add(savedApplication1.getId());
applicationIds.add(savedApplication2.getId());
UpdateFormApplicationStatusCommand command = fixture.giveMeBuilder(UpdateFormApplicationStatusCommand.class)
.set("formId", savedForm.getId())
.set("applicationIds", applicationIds)
.set("status", FormApplicationStatus.FIRST_PASS)
.set("user", savedUser)
.sample();
formService.create(form);
formService.create(form2);

// when
List<FormListQuery> queries = facadeCentralFormService.getAllMyForm(savedUser);
facadeCentralFormApplicationService.updateStatus(command);
// then
assertThat(queries).isNotEmpty();
assertThat(queries.size()).isEqualTo(2);
assertThat(queries.get(0).title()).isEqualTo("제목1");
assertThat(queries.get(1).title()).isEqualTo("제목2");

assertThat(formApplication1.getName()).isEqualTo("지원자1");
assertThat(formApplication1.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
assertThat(formApplication2.getName()).isEqualTo("지원자2");
assertThat(formApplication2.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

테스트 케이스 보완 필요

현재 테스트는 메모리상의 객체만 검증하고 있어, 실제 데이터베이스에 변경사항이 저장되었는지 확인할 수 없습니다. 또한 다음과 같은 엣지 케이스에 대한 테스트가 필요합니다:

  1. 빈 applicationIds 리스트 처리
  2. 존재하지 않는 applicationId 처리
  3. 권한이 없는 사용자의 요청 처리

데이터베이스 저장 검증을 위해 다음과 같이 수정하는 것을 제안합니다:

         // when
         facadeCentralFormApplicationService.updateStatus(command);
 
         // then
-        assertThat(formApplication1.getName()).isEqualTo("지원자1");
-        assertThat(formApplication1.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
-        assertThat(formApplication2.getName()).isEqualTo("지원자2");
-        assertThat(formApplication2.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
+        FormApplication updatedApplication1 = formApplicationService.findById(savedApplication1.getId());
+        FormApplication updatedApplication2 = formApplicationService.findById(savedApplication2.getId());
+        
+        assertThat(updatedApplication1.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
+        assertThat(updatedApplication2.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
📝 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
@DisplayName("동아리는 지원자의 상태를 수정할 수 있다.")
@Test
void validateEqualsClub() {
void updateFormApplicationStatus() {
// given
User user = fixtureMonkey.giveMeBuilder(User.class)
User user = fixture.giveMeBuilder(User.class)
.set("id", 1L)
.set("Role", Role.CLUB)
.set("deletedAt", null)
.sample();
User savedUser = userRepository.save(user);
Club club = fixtureMonkey.giveMeBuilder(Club.class)
User savedUser = userRepository.saveAndFlush(user);
Club club = fixture.giveMeBuilder(Club.class)
.set("id", 1L)
.set("user", savedUser)
.set("score", null)
.set("clubMembers", null)
.set("deletedAt", null)
.sample();
clubRepository.save(club);
User user2 = fixtureMonkey.giveMeBuilder(User.class)
.set("id", 2L)
.set("Role", Role.CLUB)
.set("deletedAt", null)
.sample();
User savedUser2 = userRepository.save(user2);
Club club2 = fixtureMonkey.giveMeBuilder(Club.class)
.set("id", 2L)
.set("user", savedUser2)
.set("score", null)
.set("clubMembers", null)
.set("deletedAt", null)
.sample();
clubRepository.save(club2);
Form form = fixtureMonkey.giveMeBuilder(Form.class)
.set("club", club)
.sample();
Form savedForm = formService.create(form);
// when //then
assertThrows(NonHaveAuthority.class, () -> {
facadeCentralFormService.deleteForm(savedForm.getId(), user2);
});
}
@DisplayName("동아리는 자신의 폼지를 전부 조회할 수 있다.")
@Test
void getAllMyForm() {
// given
User user = fixtureMonkey.giveMeBuilder(User.class)
Club savedClub = clubRepository.saveAndFlush(club);
Form form = fixture.giveMeBuilder(Form.class)
.set("id", 1L)
.set("Role", Role.CLUB)
.set("deletedAt", null)
.sample();
User savedUser = userRepository.save(user);
Club club = fixtureMonkey.giveMeBuilder(Club.class)
.set("id", 1L)
.set("user", savedUser)
.set("score", null)
.set("clubMembers", null)
.set("deletedAt", null)
.sample();
clubRepository.save(club);
Form form = fixtureMonkey.giveMeBuilder(Form.class)
.set("title", "제목1")
.set("club", club)
.sample();
Form form2 = fixtureMonkey.giveMeBuilder(Form.class)
.set("title", "제목2")
.set("club", club)
.set("club", savedClub)
.sample();
Form savedForm = formRepository.saveAndFlush(form);
FormApplication formApplication1 = FormApplication.builder()
.name("지원자1")
.studentNumber("60201115")
.department("융합소프트웨어학부")
.status(FormApplicationStatus.SUBMITTED)
.form(savedForm)
.build();
FormApplication formApplication2 = FormApplication.builder()
.name("지원자2")
.studentNumber("602011156")
.department("디지털콘텐츠디자인학과")
.status(FormApplicationStatus.SUBMITTED)
.form(savedForm)
.build();
FormApplication savedApplication1 = formApplicationService.create(formApplication1);
FormApplication savedApplication2 = formApplicationService.create(formApplication2);
List<Long> applicationIds = new ArrayList<>();
applicationIds.add(savedApplication1.getId());
applicationIds.add(savedApplication2.getId());
UpdateFormApplicationStatusCommand command = fixture.giveMeBuilder(UpdateFormApplicationStatusCommand.class)
.set("formId", savedForm.getId())
.set("applicationIds", applicationIds)
.set("status", FormApplicationStatus.FIRST_PASS)
.set("user", savedUser)
.sample();
formService.create(form);
formService.create(form2);
// when
List<FormListQuery> queries = facadeCentralFormService.getAllMyForm(savedUser);
facadeCentralFormApplicationService.updateStatus(command);
// then
assertThat(queries).isNotEmpty();
assertThat(queries.size()).isEqualTo(2);
assertThat(queries.get(0).title()).isEqualTo("제목1");
assertThat(queries.get(1).title()).isEqualTo("제목2");
assertThat(formApplication1.getName()).isEqualTo("지원자1");
assertThat(formApplication1.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
assertThat(formApplication2.getName()).isEqualTo("지원자2");
assertThat(formApplication2.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
}
@DisplayName("동아리는 지원자의 상태를 수정할 수 있다.")
@Test
void updateFormApplicationStatus() {
// given
User user = fixture.giveMeBuilder(User.class)
.set("id", 1L)
.set("Role", Role.CLUB)
.set("deletedAt", null)
.sample();
User savedUser = userRepository.saveAndFlush(user);
Club club = fixture.giveMeBuilder(Club.class)
.set("id", 1L)
.set("user", savedUser)
.set("score", null)
.set("clubMembers", null)
.set("deletedAt", null)
.sample();
Club savedClub = clubRepository.saveAndFlush(club);
Form form = fixture.giveMeBuilder(Form.class)
.set("id", 1L)
.set("title", "제목1")
.set("club", savedClub)
.sample();
Form savedForm = formRepository.saveAndFlush(form);
FormApplication formApplication1 = FormApplication.builder()
.name("지원자1")
.studentNumber("60201115")
.department("융합소프트웨어학부")
.status(FormApplicationStatus.SUBMITTED)
.form(savedForm)
.build();
FormApplication formApplication2 = FormApplication.builder()
.name("지원자2")
.studentNumber("602011156")
.department("디지털콘텐츠디자인학과")
.status(FormApplicationStatus.SUBMITTED)
.form(savedForm)
.build();
FormApplication savedApplication1 = formApplicationService.create(formApplication1);
FormApplication savedApplication2 = formApplicationService.create(formApplication2);
List<Long> applicationIds = new ArrayList<>();
applicationIds.add(savedApplication1.getId());
applicationIds.add(savedApplication2.getId());
UpdateFormApplicationStatusCommand command = fixture.giveMeBuilder(UpdateFormApplicationStatusCommand.class)
.set("formId", savedForm.getId())
.set("applicationIds", applicationIds)
.set("status", FormApplicationStatus.FIRST_PASS)
.set("user", savedUser)
.sample();
// when
facadeCentralFormApplicationService.updateStatus(command);
// then
- assertThat(formApplication1.getName()).isEqualTo("지원자1");
- assertThat(formApplication1.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
- assertThat(formApplication2.getName()).isEqualTo("지원자2");
- assertThat(formApplication2.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
+ FormApplication updatedApplication1 = formApplicationService.findById(savedApplication1.getId());
+ FormApplication updatedApplication2 = formApplicationService.findById(savedApplication2.getId());
+
+ assertThat(updatedApplication1.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
+ assertThat(updatedApplication2.getStatus()).isEqualTo(FormApplicationStatus.FIRST_PASS);
}

@github-actions github-actions bot added D-2 and removed D-3 labels Feb 6, 2025
Copy link
Collaborator

@KoSeonJe KoSeonJe left a comment

Choose a reason for hiding this comment

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

고생하셨습니다 코멘트 확인해주세요

Comment on lines 16 to 19
@NotNull(message = "지원자 상태는 필수 입력 사항입니다.")
@Schema(description = "수정할 지원자 상태", example = "FIRST_PASS")
FormApplicationStatus status
) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

p3)
이것도 전에 말했던 것과 같이 String값으로 받아온뒤,
변환시키는 과정에서 해당 값이 유효한지 체크해주면 좋을 것 같아요
현재 제 API도 이렇게 되어있는데 오늘 수정해서 올리겠습니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

참고해서 수정하였습니다! 감사합니다.

public void updateStatus(UpdateFormApplicationStatusCommand command) {
List<FormApplication> formApplications = formApplicationService.getAllById(command.applicationIds());
formApplications.forEach(formApplication -> formApplication.update(command.status()));
formApplicationRepository.saveAll(formApplications);
Copy link
Collaborator

Choose a reason for hiding this comment

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

p2)
변경감지에 대해 공부해보시면 좋을 것 같아요!
update 시 save하지 않아도 괜찮아요!
https://velog.io/@hyojhand/JPA-Dirty-Checking%EB%B3%80%EA%B2%BD%EA%B0%90%EC%A7%80-DynamicUpdate

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

카우 세션에서 배웠던 내용인데 잊어버렸네요...ㅎㅎ 자료까지 주셔서 감사합니다. 수정 완료했습니다!

Copy link
Collaborator

@5uhwann 5uhwann left a comment

Choose a reason for hiding this comment

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

확인했습니다! 코멘트 확인 부탁드립니다.
해당 pr도 마찬가지로 코드 스타일 확인 부탁드립니다!

Comment on lines 45 to 47
public void update(FormApplicationStatus status) {
this.status = status;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

p3)
해당 메서드가 어떤 FormApplication의 무엇을 업데이트하는 메서드인지 명확하게 표현하는것이 좋을거 같습니다!

Suggested change
public void update(FormApplicationStatus status) {
this.status = status;
}
public void updateFormApplicationStatus (FormApplicationStatus status) {
this.status = status;
}

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

updateStatus()로 변경했습니다! 감사합니다!

}

@Override
public FormApplicationQuery getFormApplication(Long formId, Long applicationId, User user) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

p3)
해당 메서드에서 user 파라미터는 사용하지 않는데 불필요하다면 제거해도 좋을거 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

이것 또한 추후에 해당 폼이 로그인한 유저의 소유인지 유효성 검증을 위해서 필요하다고 생각했는데, 현재 필요가 없다면 삭제하는 것이 맞을까요?

Comment on lines 18 to 23
public FormApplication toEntity(Form form) {
return FormApplication.builder()
.form(form)
.status(status)
.build();
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

p3)
FacadeCentralFormApplicationServiceImpl의 updateStatus() 메서드에서 UpdateFormApplicationStatusCommandapplicationIds, status 두개의 필드만 사용하고 있습니다!
불필요한 필드와 toEntity 메서드는 제거해도 좋을거 같습니다!

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

toEntity 메서드 삭제 완료했습니다! 감사합니다.

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

♻️ Duplicate comments (1)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (1)

54-60: ⚠️ Potential issue

상태 업데이트 로직 개선이 필요합니다.

  1. 상태값 검증이 누락되어 있습니다.
  2. 존재하지 않는 지원서에 대한 예외 처리가 필요합니다.
  3. JPA의 변경 감지(dirty checking) 기능으로 인해 saveAll 호출이 불필요합니다.

다음과 같이 개선해주세요:

 @Transactional
 @Override
 public void updateStatus(UpdateFormApplicationStatusCommand command) {
+    if (command.status() == null) {
+        throw new IllegalArgumentException("상태값은 null일 수 없습니다.");
+    }
     List<FormApplication> formApplications = formApplicationService.getAllById(command.applicationIds());
+    if (formApplications.size() != command.applicationIds().size()) {
+        throw new IllegalArgumentException("존재하지 않는 지원서가 포함되어 있습니다.");
+    }
     formApplications.forEach(formApplication -> formApplication.update(command.status()));
-    formApplicationRepository.saveAll(formApplications);
 }
🧹 Nitpick comments (2)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/entity/FormApplicationStatus.java (1)

13-18: 메서드 문서화 및 입력값 검증 추가 제안

메서드 구현이 잘 되어 있지만, 다음 개선사항들을 고려해보시면 좋을 것 같습니다:

  1. 공개 메서드에 대한 JavaDoc 문서화 추가
  2. null 또는 빈 문자열 입력에 대한 검증 로직 추가
  3. 대소문자 구분 없는 비교 검토

다음과 같이 개선해보시는 건 어떨까요?:

+    /**
+     * 문자열을 FormApplicationStatus 열거형으로 변환합니다.
+     *
+     * @param status 변환할 상태 문자열
+     * @return 찾은 FormApplicationStatus
+     * @throws InvalidatedEnumValue 상태를 찾을 수 없거나 입력이 유효하지 않은 경우
+     */
     public static FormApplicationStatus findStatus(String status) {
+        if (status == null || status.trim().isEmpty()) {
+            throw new InvalidatedEnumValue("상태 값이 비어있습니다.");
+        }
+        
         return Arrays.stream(values())
-            .filter(findStatus -> findStatus.name().equals(status))
+            .filter(findStatus -> findStatus.name().equalsIgnoreCase(status.trim()))
             .findFirst()
             .orElseThrow(() -> new InvalidatedEnumValue("FormApplicationStatus (status=" + status + ")를 찾을 수 없습니다."));
     }
src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (1)

29-29: 사용하지 않는 의존성을 제거해주세요.

FormFieldService가 현재 클래스의 어떤 메서드에서도 사용되지 않고 있습니다. 불필요한 의존성은 제거하는 것이 좋습니다.

-    private final FormFieldService formFieldService;
📜 Review details

Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c0c009e and 6b89e5e.

📒 Files selected for processing (5)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/api/CentralFormApplicationApi.java (3 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/dto/request/UpdateFormApplicationStatusRequest.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/entity/FormApplicationStatus.java (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (3 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/GeneralFormApplicationService.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/GeneralFormApplicationService.java
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/dto/request/UpdateFormApplicationStatusRequest.java
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/api/CentralFormApplicationApi.java
🔇 Additional comments (1)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/entity/FormApplicationStatus.java (1)

3-5: 코드가 깔끔하고 잘 구성되어 있습니다!

열거형 상수들이 명확하게 정의되어 있고, 필요한 import문들이 잘 구성되어 있습니다.

Also applies to: 6-11

@Seooooo24 Seooooo24 merged commit c899d11 into develop Feb 7, 2025
1 of 2 checks passed
@Seooooo24 Seooooo24 deleted the feature/DDING-93 branch February 7, 2025 08:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants