Skip to content

[DDING-91] 지원하기 API 구현 #233

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

Merged
merged 33 commits into from
Feb 3, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
bb66260
feat : FormField, FormAnswer 엔티티 생성
Seooooo24 Feb 1, 2025
2fb9efc
feat : FormResponseRepository, FormAnswerRepository 생성
Seooooo24 Feb 1, 2025
392b085
feat : 지원하기 API에 필요한 서비스 생성
Seooooo24 Feb 1, 2025
237e9dd
feat : UserFormController 생성
Seooooo24 Feb 1, 2025
0a367c5
feat : 지원하기 API에 필요한 DTO 작성
Seooooo24 Feb 1, 2025
17bc0c2
feat : FormResponse 상태 enum 생성
Seooooo24 Feb 1, 2025
c9d2c6a
Merge branch 'develop' into feature/DDING-91
Seooooo24 Feb 1, 2025
a9e4a75
feat : 지원하기 스크립트 작성
Seooooo24 Feb 1, 2025
8297adc
feat : 지원하기 API 구현
Seooooo24 Feb 1, 2025
178793c
feat : dto의 formField 객체를 fieldId로 수정
Seooooo24 Feb 1, 2025
0736cb2
fix : FormAnswer 빌더에 누락된 연관관계 추가
Seooooo24 Feb 1, 2025
c995843
remove: 불필요한 import문 제거
Seooooo24 Feb 1, 2025
786d71e
rename: formapplication 패키지로 이동
Seooooo24 Feb 2, 2025
0b071f6
fix: 지원하기 API의 누락된 url 작성
Seooooo24 Feb 2, 2025
438292b
fix: UserFormController에서 createFormResponse 호출
Seooooo24 Feb 2, 2025
d0f9d6a
rename: FormResponse를 FormApplication으로 변경
Seooooo24 Feb 2, 2025
fbefe2b
rename: FormResponse를 FormApplication으로 변경
Seooooo24 Feb 2, 2025
cfecc45
rename: FormResponse를 FormApplication으로 변경
Seooooo24 Feb 2, 2025
331668e
fix: valueType의 타입을 FieldType으로 변경
Seooooo24 Feb 2, 2025
0f29123
fix: 엔터티 설정과 다른 ddl문 수정
Seooooo24 Feb 2, 2025
4aa5d56
fix: FormApplicationStatus 컬럼명 지정
Seooooo24 Feb 2, 2025
05b6525
fix: @Transactional 올바르게 사용
Seooooo24 Feb 2, 2025
7701b45
fix: dto에서 status 초기화
Seooooo24 Feb 2, 2025
37626bf
fix: FormAnswer의 value 타입 List<String>으로 변경
Seooooo24 Feb 3, 2025
8160061
fix: value 길이 1500자로 수정
Seooooo24 Feb 3, 2025
0fe9e37
fix: 서비스 내에서 예외 처리하도록 수정
Seooooo24 Feb 3, 2025
62b2f00
fix: FormAnswer의 valueType 속성 삭제
Seooooo24 Feb 3, 2025
97b93f0
style: 뎁스 수정
Seooooo24 Feb 3, 2025
0c61cdd
style: FacadeUserFormServiceImpl 내에 CreateFormAnswerCommand 임포트
Seooooo24 Feb 3, 2025
22a5063
style: 누락된 path variable 추가
Seooooo24 Feb 3, 2025
915c237
fix: SecurityConfig requestMatchers에 forms 추가
Seooooo24 Feb 3, 2025
91ddb0a
test: 지원하기 API 테스트 코드 작성
Seooooo24 Feb 3, 2025
e0f55d5
Merge branch 'develop' into feature/DDING-91
Seooooo24 Feb 3, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ddingdong.ddingdongBE.common.config;

import static org.springframework.http.HttpMethod.GET;
import static org.springframework.http.HttpMethod.POST;

import ddingdong.ddingdongBE.auth.service.JwtAuthService;
import ddingdong.ddingdongBE.common.filter.JwtAuthenticationFilter;
Expand Down Expand Up @@ -46,7 +47,13 @@ public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthService authSer
API_PREFIX + "/banners/**",
API_PREFIX + "/documents/**",
API_PREFIX + "/questions/**",
API_PREFIX + "/feeds/**")
API_PREFIX + "/feeds/**",
API_PREFIX + "/forms/**"
)
.permitAll()
.requestMatchers(POST,
API_PREFIX + "/forms/{formId}/applications"
)
.permitAll()
.requestMatchers(API_PREFIX + "/internal/**")
.permitAll()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,14 @@
import ddingdong.ddingdongBE.domain.form.entity.Form;
import ddingdong.ddingdongBE.domain.form.entity.FormField;
import java.util.List;
import java.util.Optional;

public interface FormFieldService {

void createAll(List<FormField> formFields);

FormField getById(Long id);

List<FormField> findAllByForm(Form form);

void deleteAll(List<FormField> originFormFields);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import ddingdong.ddingdongBE.domain.form.entity.FormField;
import ddingdong.ddingdongBE.domain.form.repository.FormFieldRepository;
import java.util.List;
import java.util.Optional;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -21,6 +23,12 @@ public void createAll(List<FormField> formFields) {
formFieldRepository.saveAll(formFields);
}


@Override
public FormField getById(Long id) {
return formFieldRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("해당 field를 id로 찾을 수 없습니다: " + id));

@Override
public List<FormField> findAllByForm(Form form) {
return formFieldRepository.findAllByForm(form);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ddingdong.ddingdongBE.domain.formapplicaion.api;

import ddingdong.ddingdongBE.domain.formapplicaion.controller.dto.request.CreateFormApplicationRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.*;

@Tag(name = "Form - User", description = "User Form API")
@RequestMapping("/server")
public interface UserFormApi {
Comment on lines +11 to +13
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 문서화와 경로에 대해 다음 사항들을 개선해 주시기 바랍니다:

  1. 오류 응답에 대한 문서화가 누락되었습니다
  2. "/server" 기본 경로가 RESTful 관행에 맞지 않습니다

다음과 같이 개선하는 것을 추천드립니다:

 @Tag(name = "Form - User", description = "User Form API")
-@RequestMapping("/server")
+@RequestMapping("/api/v1")
 public interface UserFormApi {

그리고 createFormResponse 메서드에 다음 문서화를 추가해 주세요:

 @Operation(summary = "지원하기 API")
 @ApiResponse(responseCode = "201", description = "지원하기 성공")
+@ApiResponse(responseCode = "400", description = "잘못된 요청")
+@ApiResponse(responseCode = "404", description = "폼을 찾을 수 없음")
+@ApiResponse(responseCode = "422", description = "폼 답변이 유효하지 않음")

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


@Operation(summary = "지원하기 API")
@ApiResponse(responseCode = "201", description = "지원하기 성공")
@ResponseStatus(HttpStatus.CREATED)
@PostMapping("/forms/{formId}/applications")
void createFormResponse(
@PathVariable Long formId,
@Valid @RequestBody CreateFormApplicationRequest request
);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ddingdong.ddingdongBE.domain.formapplicaion.controller;

import ddingdong.ddingdongBE.domain.formapplicaion.api.UserFormApi;
import ddingdong.ddingdongBE.domain.formapplicaion.controller.dto.request.CreateFormApplicationRequest;
import ddingdong.ddingdongBE.domain.formapplicaion.service.FacadeUserFormService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class UserFormController implements UserFormApi {

private final FacadeUserFormService facadeUserFormService;

@Override
public void createFormResponse(Long formId, CreateFormApplicationRequest createFormApplicationRequest) {
facadeUserFormService.createFormApplication(formId, createFormApplicationRequest.toCommand());
}
Comment on lines +15 to +18
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. formId에 대한 검증이 누락되었습니다

다음과 같이 개선하는 것을 추천드립니다:

-public void createFormResponse(Long formId, CreateFormApplicationRequest createFormApplicationRequest) {
+public ResponseEntity<ApiResponse> createFormResponse(
+    @PathVariable @Positive Long formId,
+    @Valid @RequestBody CreateFormApplicationRequest createFormApplicationRequest
+) {
     facadeUserFormService.createFormApplication(formId, createFormApplicationRequest.toCommand());
+    return ResponseEntity.status(HttpStatus.CREATED)
+        .body(ApiResponse.success("폼 신청이 성공적으로 생성되었습니다."));
 }
📝 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 createFormResponse(Long formId, CreateFormApplicationRequest createFormApplicationRequest) {
facadeUserFormService.createFormApplication(formId, createFormApplicationRequest.toCommand());
}
@Override
public ResponseEntity<ApiResponse> createFormResponse(
@PathVariable @Positive Long formId,
@Valid @RequestBody CreateFormApplicationRequest createFormApplicationRequest
) {
facadeUserFormService.createFormApplication(formId, createFormApplicationRequest.toCommand());
return ResponseEntity.status(HttpStatus.CREATED)
.body(ApiResponse.success("폼 신청이 성공적으로 생성되었습니다."));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package ddingdong.ddingdongBE.domain.formapplicaion.controller.dto.request;

import ddingdong.ddingdongBE.domain.formapplicaion.entity.FormApplicationStatus;
import ddingdong.ddingdongBE.domain.formapplicaion.service.dto.CreateFormApplicationCommand;
import ddingdong.ddingdongBE.domain.formapplicaion.service.dto.CreateFormApplicationCommand.CreateFormAnswerCommand;
import io.swagger.v3.oas.annotations.media.ArraySchema;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;

import java.util.List;

public record CreateFormApplicationRequest(

@NotNull(message = "지원자 이름은 필수 입력 사항입니다.")
@Schema(description = "지원자 이름", example = "김띵동")
String name,

@NotNull(message = "지원자 학번은 필수 입력 사항입니다.")
@Schema(description = "학번", example = "60200000")
String studentNumber,

@NotNull(message = "지원자 학과는 필수 입력 사항입니다.")
@Schema(description = "학과", example = "융합소프트웨어학부 응용소프트웨어전공")
String department,

@ArraySchema(schema = @Schema(implementation = CreateFormAnswerRequest.class))
List<CreateFormAnswerRequest> formAnswers
) {
Comment on lines +12 to +28
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

폼 ID가 누락되었습니다.

폼 신청을 생성하기 위해서는 어떤 폼에 대한 신청인지 식별할 수 있는 formId가 필요합니다.

 public record CreateFormApplicationRequest(
+        @NotNull(message = "폼 ID는 필수 입력 사항입니다.")
+        @Schema(description = "폼 ID", example = "1")
+        Long formId,
 
         @NotNull(message = "지원자 이름은 필수 입력 사항입니다.")
         @Schema(description = "지원자 이름", example = "김띵동")
         String name,
📝 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 record CreateFormApplicationRequest(
@NotNull(message = "지원자 이름은 필수 입력 사항입니다.")
@Schema(description = "지원자 이름", example = "김띵동")
String name,
@NotNull(message = "지원자 학번은 필수 입력 사항입니다.")
@Schema(description = "학번", example = "60200000")
String studentNumber,
@NotNull(message = "지원자 학과는 필수 입력 사항입니다.")
@Schema(description = "학과", example = "융합소프트웨어학부 응용소프트웨어전공")
String department,
@ArraySchema(schema = @Schema(implementation = CreateFormAnswerRequest.class))
List<CreateFormAnswerRequest> formAnswers
) {
public record CreateFormApplicationRequest(
@NotNull(message = "폼 ID는 필수 입력 사항입니다.")
@Schema(description = "폼 ID", example = "1")
Long formId,
@NotNull(message = "지원자 이름은 필수 입력 사항입니다.")
@Schema(description = "지원자 이름", example = "김띵동")
String name,
@NotNull(message = "지원자 학번은 필수 입력 사항입니다.")
@Schema(description = "학번", example = "60200000")
String studentNumber,
@NotNull(message = "지원자 학과는 필수 입력 사항입니다.")
@Schema(description = "학과", example = "융합소프트웨어학부 응용소프트웨어전공")
String department,
@ArraySchema(schema = @Schema(implementation = CreateFormAnswerRequest.class))
List<CreateFormAnswerRequest> formAnswers
) {

record CreateFormAnswerRequest(
@NotNull(message = "질문 id는 null이 될 수 없습니다.")
@Schema(description = "질문 id", example = "1")
Long fieldId,

@Schema(description = "답변 값")
List<String> value
) {
public CreateFormAnswerCommand toCommand() {
return CreateFormAnswerCommand.builder()
.fieldId(fieldId)
.value(value)
.build();
}
}

public CreateFormApplicationCommand toCommand() {
List<CreateFormAnswerCommand> createFormAnswerCommands = formAnswers.stream()
.map(CreateFormAnswerRequest::toCommand)
.toList();
return CreateFormApplicationCommand.builder()
.name(name)
.studentNumber(studentNumber)
.department(department)
.status(FormApplicationStatus.SUBMITTED)
.formAnswerCommands(createFormAnswerCommands)
.build();
}

}


Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package ddingdong.ddingdongBE.domain.formapplicaion.entity;

import ddingdong.ddingdongBE.common.BaseEntity;
import ddingdong.ddingdongBE.common.converter.StringListConverter;
import ddingdong.ddingdongBE.domain.form.entity.FieldType;
import ddingdong.ddingdongBE.domain.form.entity.FormField;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import java.util.List;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class FormAnswer extends BaseEntity {

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

@Column(nullable = false)
@Convert(converter = StringListConverter.class)
private List<String> value;

@ManyToOne(fetch = FetchType.LAZY)
private FormApplication formApplication;

@ManyToOne(fetch = FetchType.LAZY)
private FormField formField;
Comment on lines +27 to +31
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

연관 관계의 무결성 보장이 필요합니다.

FormApplication과 FormField에 대한 null 체크가 누락되어 있습니다. 엔티티 생성 시점에 이를 검증하는 것이 좋습니다.

다음과 같이 생성자에 검증 로직을 추가하는 것을 추천드립니다:

 @Builder
 private FormAnswer(List<String> value, FormApplication formApplication, FormField formField) {
+    validateRelations(formApplication, formField);
     this.value = value;
     this.formApplication = formApplication;
     this.formField = formField;
 }

+private void validateRelations(FormApplication formApplication, FormField formField) {
+    if (formApplication == null) {
+        throw new IllegalArgumentException("FormApplication은 null일 수 없습니다.");
+    }
+    if (formField == null) {
+        throw new IllegalArgumentException("FormField는 null일 수 없습니다.");
+    }
+}
📝 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
@ManyToOne(fetch = FetchType.LAZY)
private FormApplication formApplication;
@ManyToOne(fetch = FetchType.LAZY)
private FormField formField;
@Entity
@Getter
public class FormAnswer {
@ManyToOne(fetch = FetchType.LAZY)
private FormApplication formApplication;
@ManyToOne(fetch = FetchType.LAZY)
private FormField formField;
private List<String> value;
@Builder
private FormAnswer(List<String> value, FormApplication formApplication, FormField formField) {
validateRelations(formApplication, formField);
this.value = value;
this.formApplication = formApplication;
this.formField = formField;
}
private void validateRelations(FormApplication formApplication, FormField formField) {
if (formApplication == null) {
throw new IllegalArgumentException("FormApplication은 null일 수 없습니다.");
}
if (formField == null) {
throw new IllegalArgumentException("FormField는 null일 수 없습니다.");
}
}
// ... other methods and fields ...
}


@Builder
private FormAnswer(List<String> value, FormApplication formApplication, FormField formField) {
this.value = value;
this.formApplication = formApplication;
this.formField = formField;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package ddingdong.ddingdongBE.domain.formapplicaion.entity;

import ddingdong.ddingdongBE.common.BaseEntity;
import ddingdong.ddingdongBE.domain.form.entity.Form;
import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
public class FormApplication extends BaseEntity {

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

@Column(nullable = false)
private String name;

@Column(nullable = false)
private String studentNumber;

@Column(nullable = false)
private String department;
Comment on lines +20 to +27
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

개인정보 필드에 대한 보안 처리가 필요합니다.

name, studentNumber, department 필드에 개인정보가 포함되어 있으므로, 적절한 보안 처리가 필요합니다.

다음과 같은 보안 조치를 추가하는 것을 추천드립니다:

  1. 암호화 처리:
+@Convert(converter = EncryptionConverter.class)
 @Column(nullable = false)
 private String name;

+@Convert(converter = EncryptionConverter.class)
 @Column(nullable = false)
 private String studentNumber;

+@Convert(converter = EncryptionConverter.class)
 @Column(nullable = false)
 private String department;
  1. 로깅 시 마스킹 처리를 위한 toString 오버라이드:
@Override
public String toString() {
    return "FormApplication{" +
           "id=" + id +
           ", name='" + maskPersonalInfo(name) + '\'' +
           ", studentNumber='" + maskPersonalInfo(studentNumber) + '\'' +
           ", department='" + department + '\'' +
           ", status=" + status +
           '}';
}

private String maskPersonalInfo(String info) {
    if (info == null || info.length() <= 2) return "*".repeat(info.length());
    return info.substring(0, 1) + "*".repeat(info.length() - 2) + info.substring(info.length() - 1);
}


@Enumerated(EnumType.STRING)
@Column(nullable = false, name = "status")
private FormApplicationStatus status;
Comment on lines +29 to +31
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

상태 전이 로직이 필요합니다.

FormApplicationStatus에 대한 상태 관리 로직이 누락되어 있습니다.

다음과 같은 상태 관리 메서드를 추가하는 것을 추천드립니다:

public void approve() {
    validateStatusTransition(FormApplicationStatus.APPROVED);
    this.status = FormApplicationStatus.APPROVED;
}

public void reject() {
    validateStatusTransition(FormApplicationStatus.REJECTED);
    this.status = FormApplicationStatus.REJECTED;
}

private void validateStatusTransition(FormApplicationStatus newStatus) {
    if (this.status != FormApplicationStatus.PENDING) {
        throw new IllegalStateException("이미 처리된 지원서입니다.");
    }
}


@ManyToOne(fetch = FetchType.LAZY)
private Form form;

@Builder
private FormApplication(String name, String studentNumber, String department, FormApplicationStatus status, Form form) {
this.name = name;
this.studentNumber = studentNumber;
this.department = department;
this.status = status;
this.form = form;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package ddingdong.ddingdongBE.domain.formapplicaion.entity;

public enum FormApplicationStatus {
SUBMITTED,
FIRST_PASS,
FINAL_PASS,
FAILURE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ddingdong.ddingdongBE.domain.formapplicaion.repository;

import ddingdong.ddingdongBE.domain.formapplicaion.entity.FormAnswer;
import org.springframework.data.jpa.repository.JpaRepository;

public interface FormAnswerRepository extends JpaRepository<FormAnswer, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package ddingdong.ddingdongBE.domain.formapplicaion.repository;

import ddingdong.ddingdongBE.domain.formapplicaion.entity.FormApplication;
import org.springframework.data.jpa.repository.JpaRepository;

public interface FormApplicationRepository extends JpaRepository<FormApplication, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ddingdong.ddingdongBE.domain.formapplicaion.service;

import ddingdong.ddingdongBE.domain.formapplicaion.service.dto.CreateFormApplicationCommand;

public interface FacadeUserFormService {

void createFormApplication(Long formId, CreateFormApplicationCommand createFormApplicationCommand);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package ddingdong.ddingdongBE.domain.formapplicaion.service;

import ddingdong.ddingdongBE.domain.form.entity.Form;
import ddingdong.ddingdongBE.domain.formapplicaion.entity.FormAnswer;
import ddingdong.ddingdongBE.domain.form.entity.FormField;
import ddingdong.ddingdongBE.domain.formapplicaion.entity.FormApplication;
import ddingdong.ddingdongBE.domain.form.service.FormFieldService;
import ddingdong.ddingdongBE.domain.form.service.FormService;
import ddingdong.ddingdongBE.domain.formapplicaion.service.dto.CreateFormApplicationCommand;
import ddingdong.ddingdongBE.domain.formapplicaion.service.dto.CreateFormApplicationCommand.CreateFormAnswerCommand;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class FacadeUserFormServiceImpl implements FacadeUserFormService {

private final FormApplicationService formApplicationService;
private final FormAnswerService formAnswerService;
private final FormService formService;
private final FormFieldService formFieldService;

@Transactional
@Override
public void createFormApplication(Long formId, CreateFormApplicationCommand createFormApplicationCommand) {
Form form = formService.getById(formId);
FormApplication formApplication = createFormApplicationCommand.toEntity(form);
FormApplication savedFormApplication = formApplicationService.create(formApplication);

List<FormAnswer> formAnswers = toFormAnswers(savedFormApplication, createFormApplicationCommand.formAnswerCommands());
formAnswerService.createAll(formAnswers);
}
Comment on lines +29 to +36
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

비즈니스 검증 로직과 예외 처리가 필요합니다.

Facade 서비스에서 다음 사항들을 개선해 주시기 바랍니다:

  1. 폼 답변의 유효성 검증이 누락되었습니다
  2. 예외 처리가 누락되었습니다

다음과 같이 개선하는 것을 추천드립니다:

 @Transactional
 @Override
 public void createFormApplication(Long formId, CreateFormApplicationCommand createFormApplicationCommand) {
+    try {
         Form form = formService.getById(formId);
+        validateFormStatus(form);
+        validateFormAnswers(form, createFormApplicationCommand.formAnswerCommands());
         
         FormApplication formApplication = createFormApplicationCommand.toEntity(form);
         FormApplication savedFormApplication = formApplicationService.create(formApplication);

         List<FormAnswer> formAnswers = toFormAnswers(savedFormApplication, createFormApplicationCommand.formAnswerCommands());
         formAnswerService.createAll(formAnswers);
+    } catch (FormNotFoundException | FormFieldNotFoundException e) {
+        throw e;
+    } catch (Exception e) {
+        throw new FormApplicationException("폼 신청 생성 중 오류가 발생했습니다: " + e.getMessage());
+    }
 }

+private void validateFormStatus(Form form) {
+    if (!form.isAcceptingApplications()) {
+        throw new InvalidFormStatusException("현재 신청이 불가능한 폼입니다.");
+    }
+}

+private void validateFormAnswers(Form form, List<CreateFormAnswerCommand> answers) {
+    // 필수 답변 검증
+    Set<Long> requiredFieldIds = form.getRequiredFieldIds();
+    Set<Long> answeredFieldIds = answers.stream()
+        .map(CreateFormAnswerCommand::fieldId)
+        .collect(Collectors.toSet());
+    
+    if (!answeredFieldIds.containsAll(requiredFieldIds)) {
+        throw new InvalidFormAnswerException("필수 항목이 모두 답변되지 않았습니다.");
+    }
+}
📝 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 createFormApplication(Long formId, CreateFormApplicationCommand createFormApplicationCommand) {
Form form = formService.getById(formId);
FormApplication formApplication = createFormApplicationCommand.toEntity(form);
FormApplication savedFormApplication = formApplicationService.create(formApplication);
List<FormAnswer> formAnswers = toFormAnswers(savedFormApplication, createFormApplicationCommand.formAnswerCommands());
formAnswerService.createAll(formAnswers);
}
@Transactional
@Override
public void createFormApplication(Long formId, CreateFormApplicationCommand createFormApplicationCommand) {
try {
Form form = formService.getById(formId);
validateFormStatus(form);
validateFormAnswers(form, createFormApplicationCommand.formAnswerCommands());
FormApplication formApplication = createFormApplicationCommand.toEntity(form);
FormApplication savedFormApplication = formApplicationService.create(formApplication);
List<FormAnswer> formAnswers = toFormAnswers(savedFormApplication, createFormApplicationCommand.formAnswerCommands());
formAnswerService.createAll(formAnswers);
} catch (FormNotFoundException | FormFieldNotFoundException e) {
throw e;
} catch (Exception e) {
throw new FormApplicationException("폼 신청 생성 중 오류가 발생했습니다: " + e.getMessage());
}
}
private void validateFormStatus(Form form) {
if (!form.isAcceptingApplications()) {
throw new InvalidFormStatusException("현재 신청이 불가능한 폼입니다.");
}
}
private void validateFormAnswers(Form form, List<CreateFormAnswerCommand> answers) {
// 필수 답변 검증
Set<Long> requiredFieldIds = form.getRequiredFieldIds();
Set<Long> answeredFieldIds = answers.stream()
.map(CreateFormAnswerCommand::fieldId)
.collect(Collectors.toSet());
if (!answeredFieldIds.containsAll(requiredFieldIds)) {
throw new InvalidFormAnswerException("필수 항목이 모두 답변되지 않았습니다.");
}
}


private List<FormAnswer> toFormAnswers(FormApplication savedFormApplication, List<CreateFormAnswerCommand> createFormAnswerCommands) {
return createFormAnswerCommands.stream()
.map(formAnswerCommand -> {
FormField formField = formFieldService.getById(formAnswerCommand.fieldId());
return formAnswerCommand.toEntity(savedFormApplication, formField);
})
.toList();
}
Comment on lines +38 to +45
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

폼 필드 유효성 검증이 필요합니다.

toFormAnswers 메서드에서 폼 필드와 답변 값의 유효성을 검증해야 합니다.

다음과 같이 개선하는 것을 추천드립니다:

 private List<FormAnswer> toFormAnswers(FormApplication savedFormApplication, List<CreateFormAnswerCommand> createFormAnswerCommands) {
     return createFormAnswerCommands.stream()
             .map(formAnswerCommand -> {
                 FormField formField = formFieldService.getById(formAnswerCommand.fieldId());
+                validateAnswerValue(formField, formAnswerCommand.value());
                 return formAnswerCommand.toEntity(savedFormApplication, formField);
             })
             .toList();
 }

+private void validateAnswerValue(FormField formField, String value) {
+    if (formField.isRequired() && (value == null || value.trim().isEmpty())) {
+        throw new InvalidFormAnswerException("필수 항목에 답변이 누락되었습니다.");
+    }
+    
+    if (!formField.isValidValue(value)) {
+        throw new InvalidFormAnswerException("답변 형식이 올바르지 않습니다.");
+    }
+}
📝 Committable suggestion

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

Suggested change
private List<FormAnswer> toFormAnswers(FormApplication savedFormApplication, List<CreateFormAnswerCommand> createFormAnswerCommands) {
return createFormAnswerCommands.stream()
.map(formAnswerCommand -> {
FormField formField = formFieldService.getById(formAnswerCommand.fieldId());
return formAnswerCommand.toEntity(savedFormApplication, formField);
})
.toList();
}
private List<FormAnswer> toFormAnswers(FormApplication savedFormApplication, List<CreateFormAnswerCommand> createFormAnswerCommands) {
return createFormAnswerCommands.stream()
.map(formAnswerCommand -> {
FormField formField = formFieldService.getById(formAnswerCommand.fieldId());
validateAnswerValue(formField, formAnswerCommand.value());
return formAnswerCommand.toEntity(savedFormApplication, formField);
})
.toList();
}
private void validateAnswerValue(FormField formField, String value) {
if (formField.isRequired() && (value == null || value.trim().isEmpty())) {
throw new InvalidFormAnswerException("필수 항목에 답변이 누락되었습니다.");
}
if (!formField.isValidValue(value)) {
throw new InvalidFormAnswerException("답변 형식이 올바르지 않습니다.");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package ddingdong.ddingdongBE.domain.formapplicaion.service;

import ddingdong.ddingdongBE.domain.formapplicaion.entity.FormAnswer;
import java.util.List;

public interface FormAnswerService {

void createAll(List<FormAnswer> formAnswers);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package ddingdong.ddingdongBE.domain.formapplicaion.service;

import ddingdong.ddingdongBE.domain.formapplicaion.entity.FormApplication;

public interface FormApplicationService {

FormApplication create(FormApplication formApplication);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package ddingdong.ddingdongBE.domain.formapplicaion.service;

import ddingdong.ddingdongBE.domain.formapplicaion.entity.FormAnswer;
import ddingdong.ddingdongBE.domain.formapplicaion.repository.FormAnswerRepository;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class GeneralFormAnswerService implements FormAnswerService {

private final FormAnswerRepository formAnswerRepository;

@Transactional
@Override
public void createAll(List<FormAnswer> formAnswers) {
formAnswerRepository.saveAll(formAnswers);
}

}
Loading
Loading