Skip to content

Commit 8b0ef75

Browse files
authored
feat: 지원자 학번 중복 불가 설정
* feat: 지원자 정보 갱신 (덮어쓰기) 메소드 구현, studentNo Unique 제약조건 추가 * feat: 지원자 생성 요청 DTO 수정 - 누락되었던 name 필드 추가 - 덮어쓰기 확인 여부 필드 추가 (기본값 false, nullable) * feat: 지원자 저장 로직 학번 검증 추가 - 이미 지원한 지원자가 다시 지원 시 덮어쓰기 하거나 저장하지 않는 방식으로 구현 - 에러코드 추가 * test: 변경된 저장 로직에 따라 테스트 케이스 구현 * docs: API 명세 실패 케이스 추가
1 parent 0a8bf83 commit 8b0ef75

File tree

7 files changed

+105
-5
lines changed

7 files changed

+105
-5
lines changed

src/main/java/dmu/dasom/api/domain/applicant/dto/ApplicantCreateRequestDto.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
package dmu.dasom.api.domain.applicant.dto;
22

3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import com.fasterxml.jackson.annotation.JsonSetter;
5+
import com.fasterxml.jackson.annotation.Nulls;
36
import dmu.dasom.api.domain.applicant.entity.Applicant;
47
import io.swagger.v3.oas.annotations.media.Schema;
58
import jakarta.validation.constraints.*;
@@ -9,6 +12,11 @@
912
@Schema(name = "ApplicantCreateRequestDto", description = "지원자 생성 요청 DTO")
1013
public class ApplicantCreateRequestDto {
1114

15+
@NotNull
16+
@Size(max = 16)
17+
@Schema(description = "이름", example = "홍길동")
18+
private String name;
19+
1220
@NotNull
1321
@Pattern(regexp = "^[0-9]{8}$")
1422
@Size(min = 8, max = 8)
@@ -39,13 +47,18 @@ public class ApplicantCreateRequestDto {
3947
private String reasonForApply;
4048

4149
@Size(max = 200)
42-
@Schema(description = "활동 희망사항", example = "동아리 활동 참여")
50+
@Schema(description = "활동 희망사항", example = "동아리 활동 참여", nullable = true)
4351
private String activityWish;
4452

4553
@NotNull
4654
@Schema(description = "개인정보 처리방침 동의 여부", example = "true")
4755
private Boolean isPrivacyPolicyAgreed;
4856

57+
@JsonProperty(defaultValue = "false")
58+
@JsonSetter(nulls = Nulls.SKIP)
59+
@Schema(description = "지원서 덮어쓰기 확인 여부", example = "false", defaultValue = "false", nullable = true)
60+
private Boolean isOverwriteConfirmed = false;
61+
4962
public Applicant toEntity() {
5063
return Applicant.builder()
5164
.studentNo(this.studentNo)

src/main/java/dmu/dasom/api/domain/applicant/entity/Applicant.java

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package dmu.dasom.api.domain.applicant.entity;
22

3+
import dmu.dasom.api.domain.applicant.dto.ApplicantCreateRequestDto;
34
import dmu.dasom.api.domain.applicant.dto.ApplicantDetailsResponseDto;
45
import dmu.dasom.api.domain.applicant.dto.ApplicantResponseDto;
56
import dmu.dasom.api.domain.applicant.enums.ApplicantStatus;
@@ -9,6 +10,7 @@
910
import lombok.Builder;
1011
import lombok.Getter;
1112
import lombok.NoArgsConstructor;
13+
import org.hibernate.annotations.DynamicUpdate;
1214
import org.springframework.data.annotation.CreatedDate;
1315
import org.springframework.data.annotation.LastModifiedDate;
1416
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
@@ -17,6 +19,7 @@
1719

1820
@AllArgsConstructor
1921
@Builder
22+
@DynamicUpdate
2023
@Entity
2124
@EntityListeners(AuditingEntityListener.class)
2225
@Getter
@@ -32,7 +35,7 @@ public class Applicant {
3235
@Size(max = 16)
3336
private String name;
3437

35-
@Column(name = "student_no", nullable = false, length = 8)
38+
@Column(name = "student_no", nullable = false, unique = true, length = 8)
3639
@Pattern(regexp = "^[0-9]{8}$")
3740
@Size(min = 8, max = 8)
3841
private String studentNo;
@@ -105,4 +108,14 @@ public ApplicantDetailsResponseDto toApplicantDetailsResponse() {
105108
.build();
106109
}
107110

111+
public void overwrite(final ApplicantCreateRequestDto request) {
112+
this.name = request.getName();
113+
this.contact = request.getContact();
114+
this.email = request.getEmail();
115+
this.grade = request.getGrade();
116+
this.reasonForApply = request.getReasonForApply();
117+
this.activityWish = request.getActivityWish();
118+
this.isPrivacyPolicyAgreed = request.getIsPrivacyPolicyAgreed();
119+
}
120+
108121
}

src/main/java/dmu/dasom/api/domain/applicant/repository/ApplicantRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@
66
import org.springframework.data.jpa.repository.JpaRepository;
77
import org.springframework.data.jpa.repository.Query;
88

9+
import java.util.Optional;
10+
911
public interface ApplicantRepository extends JpaRepository<Applicant, Long> {
1012

1113
@Query("SELECT a FROM Applicant a ORDER BY a.id DESC")
1214
Page<Applicant> findAllWithPageRequest(final Pageable pageable);
1315

16+
Optional<Applicant> findByStudentNo(final String studentNo);
17+
1418
}

src/main/java/dmu/dasom/api/domain/applicant/service/ApplicantServiceImpl.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,13 @@
1313
import org.springframework.data.domain.Page;
1414
import org.springframework.data.domain.PageRequest;
1515
import org.springframework.stereotype.Service;
16+
import org.springframework.transaction.annotation.Transactional;
17+
18+
import java.util.Optional;
1619

1720
@RequiredArgsConstructor
1821
@Service
22+
@Transactional
1923
public class ApplicantServiceImpl implements ApplicantService {
2024

2125
private final static int DEFAULT_PAGE_SIZE = 20;
@@ -25,6 +29,20 @@ public class ApplicantServiceImpl implements ApplicantService {
2529
// 지원자 저장
2630
@Override
2731
public void apply(final ApplicantCreateRequestDto request) {
32+
final Optional<Applicant> applicant = findByStudentNo(request.getStudentNo());
33+
34+
// 이미 지원한 학번이 존재할 경우
35+
if (applicant.isPresent()) {
36+
// 덮어쓰기 확인 여부가 false일 경우 예외 발생
37+
if (!request.getIsOverwriteConfirmed())
38+
throw new CustomException(ErrorCode.DUPLICATED_STUDENT_NO);
39+
40+
// 기존 지원자 정보 갱신 수행
41+
applicant.get().overwrite(request);
42+
return;
43+
}
44+
45+
// 새로운 지원자일 경우 저장
2846
applicantRepository.save(request.toEntity());
2947
}
3048

@@ -61,4 +79,9 @@ private Applicant findById(final Long id) {
6179
.orElseThrow(() -> new CustomException(ErrorCode.EMPTY_RESULT));
6280
}
6381

82+
// 학번으로 지원자 존재 여부 확인
83+
private Optional<Applicant> findByStudentNo(final String studentNo) {
84+
return applicantRepository.findByStudentNo(studentNo);
85+
}
86+
6487
}

src/main/java/dmu/dasom/api/domain/common/exception/ErrorCode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ public enum ErrorCode {
1818
INTERNAL_SERVER_ERROR(500, "C009", "서버에 문제가 발생하였습니다."),
1919
NOT_FOUND(404, "C010", "해당 리소스를 찾을 수 없습니다."),
2020
WRITE_FAIL(400, "C011", "데이터를 쓰는데 실패하였습니다."),
21-
EMPTY_RESULT(400, "C012", "조회 결과가 없습니다.")
21+
EMPTY_RESULT(400, "C012", "조회 결과가 없습니다."),
22+
DUPLICATED_STUDENT_NO(400, "C013", "이미 등록된 학번입니다."),
2223
;
2324

2425
private final int status;

src/main/java/dmu/dasom/api/domain/recruit/controller/RecruitController.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@
22

33
import dmu.dasom.api.domain.applicant.dto.ApplicantCreateRequestDto;
44
import dmu.dasom.api.domain.applicant.service.ApplicantService;
5+
import dmu.dasom.api.domain.common.exception.ErrorResponse;
56
import io.swagger.v3.oas.annotations.Operation;
7+
import io.swagger.v3.oas.annotations.media.Content;
8+
import io.swagger.v3.oas.annotations.media.ExampleObject;
9+
import io.swagger.v3.oas.annotations.media.Schema;
610
import io.swagger.v3.oas.annotations.responses.ApiResponse;
711
import io.swagger.v3.oas.annotations.responses.ApiResponses;
812
import jakarta.validation.Valid;
@@ -23,8 +27,15 @@ public class RecruitController {
2327
// 지원하기
2428
@Operation(summary = "부원 지원하기")
2529
@ApiResponses(value = {
26-
@ApiResponse(responseCode = "200", description = "지원 성공")
27-
})
30+
@ApiResponse(responseCode = "200", description = "지원 성공"),
31+
@ApiResponse(responseCode = "400", description = "실패 케이스",
32+
content = @Content(
33+
mediaType = "application/json",
34+
schema = @Schema(implementation = ErrorResponse.class),
35+
examples = {
36+
@ExampleObject(
37+
name = "학번 중복",
38+
value = "{ \"code\": \"C013\", \"message\": \"이미 등록된 학번입니다.\" }")}))})
2839
@PostMapping("/apply")
2940
public ResponseEntity<Void> apply(@Valid @RequestBody final ApplicantCreateRequestDto request) {
3041
applicantService.apply(request);

src/test/java/dmu/dasom/api/domain/applicant/ApplicantServiceTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import org.springframework.data.domain.PageRequest;
2020

2121
import java.util.Collections;
22+
import java.util.Optional;
2223

2324
import static org.junit.jupiter.api.Assertions.*;
2425
import static org.mockito.Mockito.*;
@@ -37,6 +38,8 @@ class ApplicantServiceTest {
3738
void apply_success() {
3839
// given
3940
ApplicantCreateRequestDto request = mock(ApplicantCreateRequestDto.class);
41+
when(request.getStudentNo()).thenReturn("20210000");
42+
when(applicantRepository.findByStudentNo("20210000")).thenReturn(Optional.empty());
4043

4144
// when
4245
applicantService.apply(request);
@@ -48,6 +51,38 @@ void apply_success() {
4851
@Test
4952
@DisplayName("지원자 저장 - 실패")
5053
void apply_fail() {
54+
// given
55+
ApplicantCreateRequestDto request = mock(ApplicantCreateRequestDto.class);
56+
when(request.getStudentNo()).thenReturn("20210000");
57+
when(applicantRepository.findByStudentNo("20210000")).thenReturn(Optional.of(mock(Applicant.class)));
58+
when(request.getIsOverwriteConfirmed()).thenReturn(false);
59+
60+
// when
61+
CustomException exception = assertThrows(CustomException.class, () -> {
62+
applicantService.apply(request);
63+
});
64+
65+
// then
66+
verify(applicantRepository).findByStudentNo("20210000");
67+
assertEquals(ErrorCode.DUPLICATED_STUDENT_NO, exception.getErrorCode());
68+
}
69+
70+
@Test
71+
@DisplayName("지원자 저장 - 덮어쓰기")
72+
void apply_overwrite() {
73+
// given
74+
ApplicantCreateRequestDto request = mock(ApplicantCreateRequestDto.class);
75+
when(request.getStudentNo()).thenReturn("20210000");
76+
Applicant applicant = mock(Applicant.class);
77+
when(applicantRepository.findByStudentNo("20210000")).thenReturn(Optional.of(applicant));
78+
when(request.getIsOverwriteConfirmed()).thenReturn(true);
79+
80+
// when
81+
applicantService.apply(request);
82+
83+
// then
84+
verify(applicantRepository).findByStudentNo("20210000");
85+
verify(applicant).overwrite(request);
5186
}
5287

5388
@Test

0 commit comments

Comments
 (0)