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-95] 지원자 상세 조회 API 구현 #240

Merged
merged 7 commits into from
Feb 6, 2025
Merged

Conversation

Seooooo24
Copy link
Collaborator

@Seooooo24 Seooooo24 commented Feb 5, 2025

🚀 작업 내용

지원자 상세 조회 API 구현하였습니다.

🤔 고민했던 내용

FormAnswerListQuery의 위치를 고민했습니다. FormFieldAnswerListQuery와 같은 뎁스에 있으니 가독성이 좋지 않은 것 같기도 합니다....
FormFieldAnswerListQuery의 위치 또한 고민됩니다. FormFieldListQuery는 FormQuery에 있고, FormAnswerListQuery는 FormApplicationListQuery에 있어서 이들을 어떻게 위치시켜야 가독성 측면에서 좋을지 궁금합니다. 그냥 FormFieldAnswerListQuery를 외부로 빼버리는 건 어떨까요?

💬 리뷰 중점사항

현재 로컬 swagger에서 폼 관련 요청 전부 500 에러가 납니다.
test 코드를 실행하면 FacadeCentralFormApplicationServiceImpl에서 예외처리한 사용자 소유 이외의 폼지를 조회했을 때 나는 에러(권한 없음 에러)가 나는데, swagger로도 테스트 해봐야 무엇 때문에 오류가 나는지 알 수 있을 것 같습니다.

Summary by CodeRabbit

  • 새로운 기능
    • 특정 양식에 대한 신청 내역을 상세하게 조회할 수 있는 기능이 추가되었습니다.
    • 신청 정보와 함께 응답 구조가 개선되어, 제출일, 이름, 학번, 부서, 상태 및 각 양식 항목의 답변 내용을 확인할 수 있습니다.
    • 사용자 인증 정보를 반영하여, 사용자의 신청 내역을 안전하게 처리할 수 있도록 업데이트되었습니다.
    • API 문서 자동 생성 기능이 강화되어, 보다 명확한 안내를 제공합니다.

Copy link

coderabbitai bot commented Feb 5, 2025

"""

Walkthrough

이번 PR은 폼 어플리케이션의 세부 정보를 조회하는 기능을 추가합니다. CentralFormApplicationApi에 새로운 getFormApplication 메서드가 도입되고, 이를 구현하는 컨트롤러, 서비스, 리포지토리 계층에서 관련 메서드와 DTO, Query 객체가 추가되었습니다. 또한, 테스트 클래스가 기존 폼 중심 테스트에서 폼 어플리케이션 관련 테스트로 전환되어 해당 기능의 정상 동작을 검증합니다.

Changes

파일 변경 요약
src/main/java/ddingdong/.../api/CentralFormApplicationApi.java
src/main/java/ddingdong/.../controller/CentralFormApplicationController.java
새로운 getFormApplication 메서드 추가로 폼 어플리케이션 상세 정보를 조회하도록 API 및 컨트롤러 구현
src/main/java/ddingdong/.../controller/dto/response/FormApplicationResponse.java 새로운 FormApplicationResponse 레코드와 중첩 레코드 추가, API 응답 구조 정의 및 static 팩토리 메서드 도입
src/main/java/ddingdong/.../repository/FormAnswerRepository.java findAllByFormApplication 메서드 추가로 특정 폼 어플리케이션과 연관된 답변 목록 조회 기능 추가
src/main/java/ddingdong/.../service/FacadeCentralFormApplicationService.java
src/main/java/ddingdong/.../service/FacadeCentralFormApplicationServiceImpl.java
src/main/java/ddingdong/.../service/FormAnswerService.java
src/main/java/ddingdong/.../service/GeneralFormAnswerService.java
src/main/java/ddingdong/.../service/FormApplicationService.java
src/main/java/ddingdong/.../service/GeneralFormApplicationService.java
여러 서비스 인터페이스 및 구현 클래스에 getFormApplication, getAllByApplication, getById 메서드 추가 및 관련 의존성 주입/구현 변경
src/main/java/ddingdong/.../service/dto/query/FormApplicationQuery.java 새로운 FormApplicationQuery 레코드와 중첩 레코드 추가, 폼 어플리케이션 데이터 변환 및 조회용 DTO 제공
src/test/java/ddingdong/.../FacadeCentralFormApplicationServiceImplTest.java
src/test/java/ddingdong/.../FacadeCentralFormServiceImplTest.java
테스트 클래스 개편: 기존 폼 관련 테스트에서 폼 어플리케이션 조회 테스트로 전환하고, 관련 검증 로직 수정

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Controller as CentralFormApplicationController
    participant Service as FacadeCentralFormApplicationServiceImpl
    participant Repo as FormAnswerRepository
    participant DTO as FormApplicationResponse

    Client->>Controller: GET /forms/{formId}/applications/{applicationId}
    Controller->>Service: getFormApplication(formId, applicationId, principal)
    Service->>Repo: findAllByFormApplication(formApplication)
    Repo-->>Service: List<FormAnswer>
    Service-->>Controller: FormApplicationQuery
    Controller->>DTO: FormApplicationResponse.from(query)
    DTO-->>Client: FormApplicationResponse
Loading

Possibly related PRs

Suggested labels

✨기능, D-1

Suggested reviewers

  • wonjunYou
  • 5uhwann
  • KoSeonJe
    """
✨ Finishing Touches
  • 📝 Generate Docstrings (Beta)

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

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

25-28: 트랜잭션 처리와 null 체크가 필요합니다.

다음 사항들을 고려해 주세요:

  1. 메서드에 @Transactional(readOnly = true) 어노테이션을 명시적으로 추가하는 것이 좋습니다.
  2. formApplication이 null인 경우에 대한 처리가 필요합니다.

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

    @Override
+   @Transactional(readOnly = true)
    public List<FormAnswer> getAllByApplication(FormApplication formApplication) {
+       if (formApplication == null) {
+           throw new IllegalArgumentException("FormApplication must not be null");
+       }
        return formAnswerRepository.findAllByFormApplication(formApplication);
    }
src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FormApplicationService.java (1)

11-12: 메서드 문서화 및 예외 처리 명세 필요

새로 추가된 getById 메서드에 대한 JavaDoc 문서화가 필요합니다. 특히 다음 사항들을 명시해주세요:

  • 메서드의 목적
  • 파라미터 설명
  • 반환값 설명
  • 발생 가능한 예외 상황
+    /**
+     * 지정된 ID로 폼 어플리케이션을 조회합니다.
+     *
+     * @param applicationId 조회할 폼 어플리케이션의 ID
+     * @return 조회된 폼 어플리케이션
+     * @throws EntityNotFoundException 해당 ID의 폼 어플리케이션이 존재하지 않는 경우
+     */
     FormApplication getById(Long applicationId);
src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/dto/query/FormApplicationQuery.java (1)

33-39: 정적 팩토리 메서드의 의도 명시
from(FormAnswer formAnswer)FormAnswer 엔티티를 레코드로 매핑하는 로직을 담당합니다. 추가적으로 formAnswer가 null이거나 value가 비어있는 경우의 예외 처리를 고려하면 안정성을 높일 수 있습니다.

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

27-32: 추가적인 예외 처리 제안
getFormApplication 메서드가 반환하는 FormApplicationResponse가 내부적으로 null 필드나 올바르지 않은 데이터에 의해 문제를 일으키지 않도록, 예외 케이스나 에러 응답에 대한 로직도 고려해 보시면 좋겠습니다.

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

55-66: 트랜잭션 범위와 성능 최적화를 검토해주세요.

  1. 현재 메서드가 여러 서비스를 호출하고 있어 성능에 영향을 미칠 수 있습니다.
  2. readOnly 트랜잭션 내에서 여러 조회 작업이 수행되므로, N+1 문제가 발생할 수 있습니다.

다음과 같은 개선을 고려해보세요:

  • 필요한 데이터를 한 번의 쿼리로 조회하도록 리포지토리 계층에서 최적화
  • 페치 조인을 사용하여 N+1 문제 해결
📜 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 8b40c1a.

📒 Files selected for processing (12)
  • src/main/java/ddingdong/ddingdongBE/domain/form/service/dto/query/FormQuery.java (2 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/api/CentralFormApplicationApi.java (2 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/CentralFormApplicationController.java (2 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/dto/response/FormApplicationResponse.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 (1 hunks)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/dto/query/FormApplicationQuery.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze
🔇 Additional comments (8)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FormAnswerService.java (1)

12-12: 메서드 시그니처가 명확하고 적절합니다!

FormApplication에 대한 모든 FormAnswer를 조회하는 메서드의 이름과 반환 타입이 명확하게 정의되어 있습니다.

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

10-10: Spring Data JPA 명명 규칙을 잘 따르고 있습니다!

findAllByFormApplication 메서드는 Spring Data JPA의 메서드 명명 규칙을 정확히 따르고 있으며, 서비스 계층의 요구사항과 잘 매칭됩니다.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/dto/query/FormApplicationQuery.java (1)

19-27: 레코드 필드 구성의 명확성 확인
레코드 정의와 빌더 패턴을 함께 사용한 것은 가독성과 유지보수에 유리합니다. 다만 FormApplicationQuery의 각 필드가 누락 없이 정확히 초기화되는지 확인하여, 예상치 못한 누락으로 인한 버그가 발생하지 않도록 주의해 주세요.

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

11-11: 권한 검증 로직 주의
getFormApplication(Long formId, Long applicationId, User user) 메서드는 사용자의 접근 권한 검증이 중요한 부분이므로, 실제 구현에서 사용자 정보와 신청서 간 연관 관계를 꼼꼼히 확인하고 예외 상황을 처리해야 합니다.

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

23-23: LGTM! FormFieldListQuery에 식별자 필드가 잘 추가되었습니다.

폼 필드를 고유하게 식별할 수 있는 id 필드가 적절하게 추가되었고, from 메서드에서도 올바르게 매핑되고 있습니다.

Also applies to: 33-33

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

35-45: API 명세가 잘 정의되었습니다.

보안 요구사항, 응답 상태, 경로 변수 등이 적절하게 정의되어 있습니다. OpenAPI 어노테이션을 통한 문서화도 잘 되어있습니다.

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

32-33: 의존성 주입이 적절히 이루어졌습니다.

FormAnswerService와 FormFieldService가 final로 선언되어 불변성이 보장됩니다.

src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/dto/response/FormApplicationResponse.java (1)

17-47: API 문서화가 잘 되어있습니다.

각 필드에 대한 설명, 예시, 허용 값 등이 OpenAPI 어노테이션을 통해 상세하게 문서화되어 있습니다.

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.

⚠️ Potential issue

예외 처리 개선 필요

현재 구현에서 발견된 문제점들:

  1. null을 반환하는 대신 명시적인 예외 처리가 필요합니다
  2. applicationId 유효성 검증이 없습니다
  3. 오류 로깅이 누락되었습니다

다음과 같이 개선을 제안합니다:

     @Override
     public FormApplication getById(Long applicationId) {
-        return formApplicationRepository.findById(applicationId).orElse(null);
+        if (applicationId == null || applicationId <= 0) {
+            throw new IllegalArgumentException("유효하지 않은 applicationId입니다: " + applicationId);
+        }
+        return formApplicationRepository.findById(applicationId)
+            .orElseThrow(() -> new EntityNotFoundException("ID가 " + applicationId + "인 폼 어플리케이션을 찾을 수 없습니다"));
     }

추가로 로깅을 위해 다음 의존성을 추가하는 것을 추천드립니다:

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

그리고 클래스 상단에 로거를 선언:

private static final Logger log = LoggerFactory.getLogger(GeneralFormApplicationService.class);

Comment on lines 64 to 94
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()));
List<FormFieldAnswerListQuery> formFieldAnswerListQueries = formFieldListQueries.stream()
.map(fieldQuery -> {
FormAnswerListQuery answerQuery = answerMap.get(fieldQuery.id());
if (answerQuery == null) {
answerQuery = FormAnswerListQuery.builder()
.fieldId(fieldQuery.id())
.value(null)
.build();
}
return FormFieldAnswerListQuery.from(fieldQuery, answerQuery);
})
.collect(Collectors.toList());
return FormApplicationQuery.builder()
.createdAt(formApplication.getCreatedAt())
.name(formApplication.getName())
.studentNumber(formApplication.getStudentNumber())
.department(formApplication.getDepartment())
.status(formApplication.getStatus())
.formFieldAnswers(formFieldAnswerListQueries)
.build();
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

결측 답변에 대한 처리 로직 보강 제안
of 메서드에서 필드에 대응하는 답변이 없을 때 valuenull로 설정하고 있습니다. 이 로직이 의도한 로직인지, 또는 빈 리스트(Collections.emptyList()) 등으로 처리하는 편이 더 안전한지 고려해 보시면 좋겠습니다. null 값이 뷰나 다른 계층에서 처리될 때 예외가 발생할 수 있으니, 처리 방식을 명확히 정의해 주세요.

Comment on lines 49 to 59
public static FormFieldAnswerListResponse from(FormFieldAnswerListQuery formFieldAnswerListQuery) {
return FormFieldAnswerListResponse.builder()
.question(formFieldAnswerListQuery.question())
.type(formFieldAnswerListQuery.type())
.options(formFieldAnswerListQuery.options())
.required(formFieldAnswerListQuery.required())
.order(formFieldAnswerListQuery.order())
.section(formFieldAnswerListQuery.section())
.value(formFieldAnswerListQuery.value())
.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

fieldId 매핑이 누락되었습니다.

FormFieldAnswerListResponse.from 메서드에서 fieldId 필드에 대한 매핑이 누락되었습니다.

다음과 같이 수정해주세요:

 public static FormFieldAnswerListResponse from(FormFieldAnswerListQuery formFieldAnswerListQuery) {
     return FormFieldAnswerListResponse.builder()
+            .fieldId(formFieldAnswerListQuery.id())
             .question(formFieldAnswerListQuery.question())
             .type(formFieldAnswerListQuery.type())
             .options(formFieldAnswerListQuery.options())
             .required(formFieldAnswerListQuery.required())
             .order(formFieldAnswerListQuery.order())
             .section(formFieldAnswerListQuery.section())
             .value(formFieldAnswerListQuery.value())
             .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 static FormFieldAnswerListResponse from(FormFieldAnswerListQuery formFieldAnswerListQuery) {
return FormFieldAnswerListResponse.builder()
.question(formFieldAnswerListQuery.question())
.type(formFieldAnswerListQuery.type())
.options(formFieldAnswerListQuery.options())
.required(formFieldAnswerListQuery.required())
.order(formFieldAnswerListQuery.order())
.section(formFieldAnswerListQuery.section())
.value(formFieldAnswerListQuery.value())
.build();
}
public static FormFieldAnswerListResponse from(FormFieldAnswerListQuery formFieldAnswerListQuery) {
return FormFieldAnswerListResponse.builder()
.fieldId(formFieldAnswerListQuery.id())
.question(formFieldAnswerListQuery.question())
.type(formFieldAnswerListQuery.type())
.options(formFieldAnswerListQuery.options())
.required(formFieldAnswerListQuery.required())
.order(formFieldAnswerListQuery.order())
.section(formFieldAnswerListQuery.section())
.value(formFieldAnswerListQuery.value())
.build();
}

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 (3)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/dto/response/FormApplicationResponse.java (3)

16-29: 필드 유효성 검증 어노테이션 추가 권장

응답 DTO에 유효성 검증 어노테이션을 추가하면 API 문서화와 런타임 검증이 향상됩니다.

예를 들어:

  • name, studentNumber, department@NotBlank
  • status@NotNull
  • formFieldAnswers@NotEmpty
 public record FormApplicationResponse (
         @Schema(description = "제출일시", example = "2025-01-01T00:00")
+        @NotNull
         LocalDateTime submittedAt,
         @Schema(description = "지원자 이름", example = "김띵동")
+        @NotBlank
         String name,
         @Schema(description = "지원자 학번", example = "60201111")
+        @NotBlank
         String studentNumber,
         @Schema(description = "지원자 학과", example = "융합소프트웨어학부")
+        @NotBlank
         String department,
         @Schema(description = "status", example = "SUBMITTED")
+        @NotNull
         FormApplicationStatus status,
         @ArraySchema(schema = @Schema(implementation = FormFieldAnswerListResponse.class))
+        @NotEmpty
         List<FormFieldAnswerListResponse> formFieldAnswers
 )

31-48: 중첩 레코드에 유효성 검증 어노테이션 추가 권장

FormFieldAnswerListResponse 레코드의 필드들도 유효성 검증이 필요합니다.

 record FormFieldAnswerListResponse (
         @Schema(description = "폼지 질문 ID", example = "1")
+        @NotNull
         Long fieldId,
         @Schema(description = "폼지 질문", example = "성별이 무엇입니까??")
+        @NotBlank
         String question,
         @Schema(description = "폼지 질문 유형", example = "RADIO", allowableValues = {"CHECK_BOX", "RADIO", "TEXT", "LONG_TEXT", "FILE"})
+        @NotNull
         FieldType type,
         @Schema(description = "폼지 지문", example = "[\"여성\", \"남성\"]")
         List<String> options,
         @Schema(description = "필수 여부", example = "true")
+        @NotNull
         Boolean required,
         @Schema(description = "질문 순서", example = "1")
+        @NotNull
         Integer order,
         @Schema(description = "섹션", example = "공통")
+        @NotBlank
         String section,
         @Schema(description = "질문 답변 값", example = "[\"지문1\"]")
         List<String> value
 )

62-75: 팩토리 메서드에 방어적 프로그래밍 적용 권장

from 메서드에서 null 체크를 추가하여 더 안전한 객체 생성이 가능합니다.

 public static FormApplicationResponse from(FormApplicationQuery formApplicationQuery) {
+        if (formApplicationQuery == null) {
+            throw new IllegalArgumentException("FormApplicationQuery must not be null");
+        }
+        
+        if (formApplicationQuery.formFieldAnswers() == null) {
+            throw new IllegalArgumentException("FormFieldAnswers must not be null");
+        }
+
         List<FormFieldAnswerListResponse> responses = formApplicationQuery.formFieldAnswers().stream()
                 .map(FormFieldAnswerListResponse::from)
                 .toList();
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 8b40c1a and 5ff484b.

📒 Files selected for processing (1)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/controller/dto/response/FormApplicationResponse.java (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze

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

🧹 Nitpick comments (6)
src/test/java/ddingdong/ddingdongBE/domain/form/service/FacadeCentralFormServiceImplTest.java (1)

53-82: 테스트 데이터 설정에서 불필요한 null 설정이 있습니다.

Club 엔티티 생성 시 score와 clubMembers를 명시적으로 null로 설정하는 것은 불필요해 보입니다. FixtureMonkey가 자동으로 처리할 수 있습니다.

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

        Club club = fixtureMonkey.giveMeBuilder(Club.class)
                .set("id", 1L)
                .set("user", savedUser)
-               .set("score", null)
-               .set("clubMembers", null)
                .set("deletedAt", null)
                .sample();
src/test/java/ddingdong/ddingdongBE/domain/form/service/FacadeCentralFormApplicationServiceImplTest.java (5)

23-24: Assertion 라이브러리를 혼용 사용 시 주의가 필요합니다.
AssertJJUnit 기본 assertion을 동시에 사용하면 유지보수 시 혼란이 생길 수 있습니다. 특정 상황에서 필요한 경우가 아니라면, 가급적 한 가지 라이브러리로 통일하는 방안을 고려해 주세요.


30-30: 필드 주입 대신 생성자 주입 방식을 권장드립니다.
Spring에서는 생성자 주입이 테스트 안정성, 코드 가독성, 의존성 불변성을 높이는 데 유리합니다. 아래 예시처럼 수정할 수 있습니다.

-    @Autowired
-    private UserRepository userRepository;
-    @Autowired
-    private FormRepository formRepository;
-    @Autowired
-    private FormApplicationService formApplicationService;
-    @Autowired
-    private FacadeCentralFormApplicationService facadeCentralFormApplicationService;

+    private final UserRepository userRepository;
+    private final FormRepository formRepository;
+    private final FormApplicationService formApplicationService;
+    private final FacadeCentralFormApplicationService facadeCentralFormApplicationService;

+    @Autowired
+    public FacadeCentralFormApplicationServiceImplTest(
+            UserRepository userRepository,
+            FormRepository formRepository,
+            FormApplicationService formApplicationService,
+            FacadeCentralFormApplicationService facadeCentralFormApplicationService
+    ) {
+        this.userRepository = userRepository;
+        this.formRepository = formRepository;
+        this.formApplicationService = formApplicationService;
+        this.facadeCentralFormApplicationService = facadeCentralFormApplicationService;
+    }

Also applies to: 36-36, 39-39, 41-42


48-48: 테스트 메서드명과 목적이 일치하지만, 검증 범위를 확장해 보세요.
현재 name만 확인하고 있으므로, 응답 DTO에서 중요하게 다루는 다른 필드(status, department 등)에 대한 검증이 추가된다면 테스트 신뢰도가 더 높아질 수 있습니다.


79-79: 하드코딩된 동아리 식별자 사용에 대한 고려가 필요합니다.
1L은 테스트 흐름상 가독성을 해치지는 않으나, 실제 도메인 로직에서 다른 식별자를 활용할 수 있도록 Fixture에서 동적으로 추출해 쓰면 유연성이 높아집니다.


81-81: 검증 범위 확장 제안
formApplicationQuery에 포함된 studentNumber, department, 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 5ff484b and 9fb14da.

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

1-52: 테스트 클래스 설정이 적절합니다.

의존성 주입과 테스트 데이터 생성을 위한 설정이 잘 되어있습니다.

src/test/java/ddingdong/ddingdongBE/domain/form/service/FacadeCentralFormApplicationServiceImplTest.java (6)

10-14: 새로운 폼 지원 도메인 관련 import 추가가 적절합니다.
테스트 클래스 내에서 폼 지원 엔티티 및 서비스 로직을 검증하기 위해 필요한 의존성을 명확히 드러내므로, 현재 import 구성이 논리적으로 타당합니다.


26-27: SpringBootTest와 TestContainerSupport 적용으로 통합 테스트 환경이 잘 구성되었습니다.
실제 환경과 유사하게 여러 계층을 포괄하는 테스트를 진행하는 방식은 안정적인 품질 보증에 도움을 줍니다.


44-44: FixtureMonkey를 이용한 객체 생성 상수 선언은 깔끔합니다.
테스트에서 일관된 Fixture를 사용하기에 적절한 접근입니다.


46-46: 테스트 시나리오 주석이 명확합니다.
테스트가 수행하는 기능—the "동아리가 지원자 응답을 상세조회 한다"—를 잘 설명하고 있어 가독성이 좋습니다.


50-50: FixtureMonkey를 이용한 Given 단계 구성이 명확합니다.
User, Club, Form 엔티티를 성공적으로 연결해 저장하며 시나리오를 구축하고 있어, 데이터 흐름이 한눈에 들어옵니다.

Also applies to: 55-55, 56-56, 63-63, 64-64, 67-67, 68-68, 69-69


70-77: 학번, 이름 등 민감 정보가 존재하므로 운영 로그 출력 시 주의가 필요합니다.
테스트 환경에서는 문제없으나, 운영 환경에서 로깅하거나 예외 메시지로 노출될 경우 잠재적 개인정보 침해 우려가 있을 수 있습니다.

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 테스트에 예외 케이스가 누락되었습니다.

존재하지 않는 ID로 조회하는 경우나 권한이 없는 사용자가 조회하는 경우 등의 예외 케이스에 대한 테스트가 필요합니다.

다음과 같은 테스트 케이스 추가를 제안드립니다:

@Test
@DisplayName("존재하지 않는 폼지 조회 시 예외가 발생한다")
void getForm_NotFound() {
    assertThrows(FormNotFoundException.class, () -> {
        facadeCentralFormService.getForm(999L);
    });
}

@Test
@DisplayName("권한이 없는 사용자는 폼지를 조회할 수 없다")
void getForm_NoAuthority() {
    // given
    User unauthorizedUser = fixtureMonkey.giveMeBuilder(User.class)
            .set("role", Role.STUDENT)
            .sample();
    
    // when & then
    assertThrows(NonHaveAuthority.class, () -> {
        facadeCentralFormService.getForm(1L, unauthorizedUser);
    });
}

Comment on lines +246 to +250
Form form = fixtureMonkey.giveMeBuilder(Form.class)
.set("id", 1L)
.set("title", "제목1")
.set("club", null)
.sample();
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Form 엔티티의 club이 null로 설정된 것이 적절하지 않습니다.

getForm 테스트에서 Form 엔티티의 club을 null로 설정하는 것은 실제 사용 사례와 맞지 않습니다. Form은 항상 Club과 연관관계를 가져야 합니다.

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

        Form form = fixtureMonkey.giveMeBuilder(Form.class)
                .set("id", 1L)
                .set("title", "제목1")
-               .set("club", null)
+               .set("club", club)
                .sample();
📝 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
Form form = fixtureMonkey.giveMeBuilder(Form.class)
.set("id", 1L)
.set("title", "제목1")
.set("club", null)
.sample();
Form form = fixtureMonkey.giveMeBuilder(Form.class)
.set("id", 1L)
.set("title", "제목1")
.set("club", club)
.sample();

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 테스트에서 폼 필드의 다른 속성들(예: 필수 여부, 필드 타입 등)에 대한 검증이 누락되어 있습니다.

추가적인 검증을 포함하는 것을 추천드립니다:

        assertThat(formFields).isNotEmpty();
        assertThat(formFields.get(0).getQuestion()).isEqualTo("수정된 질문");
+       assertThat(formFields.get(0).isRequired()).isNotNull();
+       assertThat(formFields.get(0).getFieldType()).isNotNull();
📝 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("수정된 질문");
}
@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("수정된 질문");
assertThat(formFields.get(0).isRequired()).isNotNull();
assertThat(formFields.get(0).getFieldType()).isNotNull();
}

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

고생하셨습니다.
다른 api에서 사용한 dto를 사용하기전 봐야할 것은
가장 첫 번째로 해당 dto를 변경하지 않고 사용할 수 있는가이고,
이것도 위험하기때문에 두 번째로 정말 공통된 dto냐 생각해봐야합니다.

예를 들어 paging처리하는 dto는 정말 공통되기 때문에 feed쪽에서 제가 재사용한 적이 있습니다.
이처럼 다른 api에서 사용한 dto 재사용은 고민많이 하고 사용해야합니다.
하지만, 이번 재사용은 적절하지 않은 것 같아요! 변경하면서까지 사용해야 하기 때문입니다.

그리고 로직이 복잡하게 된 것은 FormField랑 FormAnswer랑 따로 생성하고 받았기 때문인데, FormAnswer내부 formField를 사용하면 로직이 많이 단순해 질것입니다.

@@ -20,6 +20,7 @@ public record FormQuery(

@Builder
public record FormFieldListQuery(
Long id,
Copy link
Collaborator

Choose a reason for hiding this comment

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

p3) 이건 왜 추가된 것인가요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

fieldId를 FormFieldAnswerListQuery에서 사용해야 해서 추가했었습니다. 현재는 삭제하고 선배님 말씀 참고해서 삭제했습니다!

String section,
List<String> value
) {
public static FormFieldAnswerListQuery from(FormFieldListQuery formFieldListQuery, FormAnswerListQuery formAnswerListQuery) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

p3)
서로다른 api에서 dto를 재사용하는 것은 거의 드뭅니다! 거의 공통된 도메인을 목적으로 사용하는 것이 아니라면 따로 만드는 것이 좋아요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

넵 명심하겠습니다! 그런데 여쭤보고 싶은 점이 있습니다. feed에 있는 내용 거의 그대로인 PagingQuery와 PagingResponse 같은 경우 이후에 같은 용도로 사용될 일이 많을 것 같은데, 일반화해서 common 패키지에 빼놓는 것은 어떻게 생각하시나요? 당장 우선순위가 높은 일은 아니지만 궁금해서 선배님 의견을 여쭤보고 싶습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

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

그래도 해당 도메인의 페이지 처리에 요구사항이 변경될 수 있다고 하는데, 그렇게 사용하는 건 위험이 너무 크다고 생각합니다.
하나 바꾸면 전체 바꿔야 하니까!.
사실 위 같은 이유때문에 common패키지란 것이 정말 사용을 많이 고민하고 사용해야 합니다

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

그렇군요!! 설명해주셔서 감사합니다!

}
FormApplication formApplication = formApplicationService.getById(applicationId);
List<FormField> formFields = formFieldService.findAllByForm(form);
List<FormAnswer> formAnswers = formAnswerService.getAllByApplication(formApplication);
Copy link
Collaborator

Choose a reason for hiding this comment

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

p1) form으로 fields를 찾는 것보다 formAnswer내부의 field를 사용하면 dto내 복잡한 로직을 줄일 수 있을 것 같아요

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

아하...!!! 제가 엔터티가 객체랑 매핑되어 있는 걸 잊었네요... 테이블에 있는 것처럼 id만 있다고 생각 했더니 이렇게 됐습니다. 알려주셔서 감사합니다! dto 재사용이 조심스러워야 한다는 점도 처음 알았습니다. 감사합니다. 수정 완료했습니다!

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

🔭 Outside diff range comments (1)
src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.java (1)

30-42:

🧩 Analysis chain

접근 제어 로직이 누락되었습니다!

getMyFormApplicationPage 메서드에서 사용자의 권한 검증 로직이 제거되었습니다. PR 목표에서 언급된 권한 관련 오류와 연관이 있을 수 있습니다.

다음 사항을 확인해주세요:

  1. 폼에 대한 접근 권한 검증
  2. 사용자의 클럽 소속 여부 확인

권한 검증 로직이 다른 곳으로 이동되었는지 확인하기 위해 다음 스크립트를 실행해주세요:


🏁 Script executed:

#!/bin/bash
# Description: Check if access control logic exists elsewhere

# Search for access control related code
rg -A 5 "AccessDeniedException|hasPermission|isOwner|getClub" --type java

Length of output: 23878


접근 제어 로직 누락 확인됨

src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/FacadeCentralFormApplicationServiceImpl.javagetMyFormApplicationPage 메서드에서는 사용자 권한 검증 관련 로직이 보이지 않습니다.
실행한 검색 스크립트 결과, 다른 곳에서는 접근 제어와 관련된 코드(예: NonHaveAuthority 체크)가 보이지만, 폼 애플리케이션 관련 로직에서는 해당 검증을 확인할 수 없습니다.

검토 사항:

  • 폼 애플리케이션 조회시 사용자에게 적절한 접근 권한 검증이 이루어지고 있는지 확인할 것
  • 만약 검증 로직이 다른 레이어(예, 컨트롤러 또는 서비스 내부의 별도 메서드)로 이동되었다면 그 위치와 연계가 올바른지 재검토 필요
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 9fb14da and 70b7e7f.

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

5-6: 의존성 추가가 적절합니다!

새로운 기능 구현을 위한 FormAnswer 관련 의존성 추가가 잘 되어있습니다.

Also applies to: 27-27

Comment on lines +44 to 49
@Override
public FormApplicationQuery getFormApplication(Long formId, Long applicationId, User user) {
FormApplication formApplication = formApplicationService.getById(applicationId);
List<FormAnswer> formAnswers = formAnswerService.getAllByApplication(formApplication);
return FormApplicationQuery.of(formApplication, formAnswers);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

필수 유효성 검사가 누락되었습니다!

getFormApplication 메서드에 다음과 같은 중요한 검증 로직이 누락되었습니다:

  1. 사용자의 접근 권한 검증
  2. formApplication null 체크
  3. formIdapplication의 연관성 검증

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

 @Override
 public FormApplicationQuery getFormApplication(Long formId, Long applicationId, User user) {
+    Form form = formService.getById(formId);
+    if (!form.getClub().equals(user.getClub())) {
+        throw new AccessDeniedException("해당 폼에 대한 접근 권한이 없습니다.");
+    }
     FormApplication formApplication = formApplicationService.getById(applicationId);
+    if (formApplication == null) {
+        throw new EntityNotFoundException("지원서를 찾을 수 없습니다.");
+    }
+    if (!formApplication.getForm().getId().equals(formId)) {
+        throw new InvalidArgumentException("지원서가 해당 폼에 속하지 않습니다.");
+    }
     List<FormAnswer> formAnswers = formAnswerService.getAllByApplication(formApplication);
     return FormApplicationQuery.of(formApplication, formAnswers);
 }
📝 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 FormApplicationQuery getFormApplication(Long formId, Long applicationId, User user) {
FormApplication formApplication = formApplicationService.getById(applicationId);
List<FormAnswer> formAnswers = formAnswerService.getAllByApplication(formApplication);
return FormApplicationQuery.of(formApplication, formAnswers);
}
@Override
public FormApplicationQuery getFormApplication(Long formId, Long applicationId, User user) {
Form form = formService.getById(formId);
if (!form.getClub().equals(user.getClub())) {
throw new AccessDeniedException("해당 폼에 대한 접근 권한이 없습니다.");
}
FormApplication formApplication = formApplicationService.getById(applicationId);
if (formApplication == null) {
throw new EntityNotFoundException("지원서를 찾을 수 없습니다.");
}
if (!formApplication.getForm().getId().equals(formId)) {
throw new InvalidArgumentException("지원서가 해당 폼에 속하지 않습니다.");
}
List<FormAnswer> formAnswers = formAnswerService.getAllByApplication(formApplication);
return FormApplicationQuery.of(formApplication, formAnswers);
}

@github-actions github-actions bot added D-2 and removed D-3 labels Feb 6, 2025
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.

확인했습니다! 코멘트 확인 부탁드립니다.
추가로 코드 스타일이 적용이 안된거 같은데 체크 한번 부탁드립니다!


@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)
formId, 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.

추후 해당 폼이 로그인한 사용자 소유의 폼인지 유효성 검사를 해야 할 것 같아서 받아왔는데 일단 삭제해두는 게 맞을까요?

@@ -31,6 +31,11 @@ public Slice<FormApplication> getFormApplicationPageByFormId(Long formId, int si
return buildSlice(formApplicationPages, size);
}

@Override
public FormApplication getById(Long applicationId) {
return formApplicationRepository.findById(applicationId).orElse(null);
Copy link
Collaborator

Choose a reason for hiding this comment

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

p1)
getById는 현재 동작상 반드시 FormApplication 객체 반환을 보장해야 하는 거 같은데 조회된 값이 없을 시 null을 return 하는 거 보단 ResourceNotFoundException(커스텀 exception 정의되어 있음)을 던지는게 좋을거 같습니다!

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

@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/GeneralFormApplicationService.java (1)

35-39: 입력 유효성 검증과 로깅 추가 필요

현재 구현은 이전 리뷰의 제안사항을 잘 반영했습니다만, 다음 개선사항을 고려해주세요:

  1. applicationId의 유효성 검증 추가
  2. 예외 발생 시 로깅 추가

다음과 같이 개선하는 것을 제안드립니다:

    @Override
    public FormApplication getById(Long applicationId) {
+        if (applicationId == null || applicationId <= 0) {
+            log.error("유효하지 않은 applicationId: {}", applicationId);
+            throw new IllegalArgumentException("유효하지 않은 applicationId입니다: " + applicationId);
+        }
+        log.debug("FormApplication 조회 시도: id={}", applicationId);
         return formApplicationRepository.findById(applicationId)
-            .orElseThrow(() -> new ResourceNotFound("주어진 id로 해당 지원자를 찾을 수 없습니다.:"+applicationId));
+            .orElseThrow(() -> {
+                log.error("FormApplication을 찾을 수 없음: id={}", applicationId);
+                return new ResourceNotFound("주어진 id로 해당 지원자를 찾을 수 없습니다.: " + applicationId);
+            });
    }

로깅을 위해 클래스 상단에 다음을 추가해주세요:

private static final Logger log = LoggerFactory.getLogger(GeneralFormApplicationService.class);
📜 Review details

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

📥 Commits

Reviewing files that changed from the base of the PR and between 70b7e7f and 12d1248.

📒 Files selected for processing (1)
  • src/main/java/ddingdong/ddingdongBE/domain/formapplication/service/GeneralFormApplicationService.java (2 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/GeneralFormApplicationService.java (2)

3-3: 적절한 예외 클래스 import 추가!

ResourceNotFound 예외를 사용하여 리소스를 찾을 수 없는 상황을 명확하게 처리하고 있습니다.


16-19: 트랜잭션 관리와 아키텍처 통합이 잘 되어있습니다!

클래스 레벨의 @Transactional(readOnly = true)와 잘 통합되어 있으며, 기존 서비스 구조와 일관성있게 구현되었습니다.

Also applies to: 35-39

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