diff --git a/src/main/java/org/mjulikelion/baker/config/CacheConfig.java b/src/main/java/org/mjulikelion/baker/config/CacheConfig.java new file mode 100644 index 0000000..5bdd3c7 --- /dev/null +++ b/src/main/java/org/mjulikelion/baker/config/CacheConfig.java @@ -0,0 +1,9 @@ +package org.mjulikelion.baker.config; + +import org.springframework.cache.annotation.EnableCaching; +import org.springframework.context.annotation.Configuration; + +@Configuration +@EnableCaching +public class CacheConfig { +} diff --git a/src/main/java/org/mjulikelion/baker/constant/RegexPatterns.java b/src/main/java/org/mjulikelion/baker/constant/RegexPatterns.java new file mode 100644 index 0000000..ff1112b --- /dev/null +++ b/src/main/java/org/mjulikelion/baker/constant/RegexPatterns.java @@ -0,0 +1,5 @@ +package org.mjulikelion.baker.constant; + +public class RegexPatterns { + public static final String APPLICATION_STUDENT_ID_PATTERN = "^60\\d{6}$";//60으로 시작하고 그 뒤로 숫자 6개인 문자열 +} diff --git a/src/main/java/org/mjulikelion/baker/controller/ExceptionController.java b/src/main/java/org/mjulikelion/baker/controller/ExceptionController.java index ddab148..ac835b0 100644 --- a/src/main/java/org/mjulikelion/baker/controller/ExceptionController.java +++ b/src/main/java/org/mjulikelion/baker/controller/ExceptionController.java @@ -7,6 +7,8 @@ import org.mjulikelion.baker.dto.response.ResponseDto; import org.mjulikelion.baker.errorcode.ErrorCode; import org.mjulikelion.baker.errorcode.ValidationErrorCode; +import org.mjulikelion.baker.exception.ApplicationIntroduceNotFoundException; +import org.mjulikelion.baker.exception.ApplicationNotFoundException; import org.mjulikelion.baker.exception.AuthenticationException; import org.mjulikelion.baker.exception.InvalidDataException; import org.mjulikelion.baker.exception.JwtException; @@ -144,4 +146,24 @@ public ResponseEntity> handleAuthenticationException( ResponseDto responseDto = getFromCustomException(authenticationException); return new ResponseEntity<>(responseDto, HttpStatus.UNAUTHORIZED); } + + //ApplicationNotFoundException 예외를 처리하는 핸들러(해당 지원서가 존재하지 않는 경우) + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(ApplicationNotFoundException.class) + public ResponseEntity> handleApplicationNotFoundException( + ApplicationNotFoundException applicationNotFoundException) { + log.error("ApplicationNotFoundException: {}", applicationNotFoundException.getMessage()); + ResponseDto responseDto = getFromCustomException(applicationNotFoundException); + return new ResponseEntity<>(responseDto, HttpStatus.NOT_FOUND); + } + + //ApplicationIntroduceNotFoundException 예외를 처리하는 핸들러(해당 지원서에 자기소개가 존재하지 않는 경우) + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(ApplicationIntroduceNotFoundException.class) + public ResponseEntity> handleApplicationIntroduceNotFoundException( + ApplicationIntroduceNotFoundException applicationIntroduceNotFoundException) { + log.error("ApplicationIntroduceNotFoundException: {}", applicationIntroduceNotFoundException.getMessage()); + ResponseDto responseDto = getFromCustomException(applicationIntroduceNotFoundException); + return new ResponseEntity<>(responseDto, HttpStatus.NOT_FOUND); + } } diff --git a/src/main/java/org/mjulikelion/baker/controller/IntroduceController.java b/src/main/java/org/mjulikelion/baker/controller/IntroduceController.java new file mode 100644 index 0000000..3ca15d5 --- /dev/null +++ b/src/main/java/org/mjulikelion/baker/controller/IntroduceController.java @@ -0,0 +1,31 @@ +package org.mjulikelion.baker.controller; + +import static org.mjulikelion.baker.constant.RegexPatterns.APPLICATION_STUDENT_ID_PATTERN; + +import jakarta.validation.constraints.Pattern; +import lombok.AllArgsConstructor; +import org.mjulikelion.baker.dto.response.ResponseDto; +import org.mjulikelion.baker.dto.response.introduce.IntroduceGetResponseData; +import org.mjulikelion.baker.service.introduce.IntroduceQueryService; +import org.springframework.cache.annotation.Cacheable; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@AllArgsConstructor +@RequestMapping("introduce") +public class IntroduceController { + private final IntroduceQueryService introduceQueryService; + + @GetMapping() + @Cacheable(value = "applicationByStudentId", key = "#studentId") + public ResponseEntity> getStudentIntroduce( + @RequestParam(value = "studentId") + @Pattern(regexp = APPLICATION_STUDENT_ID_PATTERN, message = "학번이 형식에 맞지 않습니다.") String studentId + ) { + return this.introduceQueryService.getStudentIntroduce(studentId); + } +} diff --git a/src/main/java/org/mjulikelion/baker/dto/response/introduce/IntroduceGetResponseData.java b/src/main/java/org/mjulikelion/baker/dto/response/introduce/IntroduceGetResponseData.java new file mode 100644 index 0000000..6598ff7 --- /dev/null +++ b/src/main/java/org/mjulikelion/baker/dto/response/introduce/IntroduceGetResponseData.java @@ -0,0 +1,15 @@ +package org.mjulikelion.baker.dto.response.introduce; + +import com.fasterxml.jackson.annotation.JsonProperty; +import java.util.List; +import lombok.Builder; +import org.mjulikelion.baker.model.Part; +import org.mjulikelion.baker.vo.IntroduceDetailVO; + +@Builder +public class IntroduceGetResponseData { + @JsonProperty + private final Part part; + @JsonProperty + private final List introduceDetailVOList; +} diff --git a/src/main/java/org/mjulikelion/baker/errorcode/ErrorCode.java b/src/main/java/org/mjulikelion/baker/errorcode/ErrorCode.java index 58ed260..f908d75 100644 --- a/src/main/java/org/mjulikelion/baker/errorcode/ErrorCode.java +++ b/src/main/java/org/mjulikelion/baker/errorcode/ErrorCode.java @@ -11,6 +11,8 @@ public enum ErrorCode { //잘못된 경로, 메소드 오류들 METHOD_NOT_ALLOWED_ERROR("4050", "허용되지 않은 메소드 입니다."),//허용되지 않은 메소드 NO_RESOURCE_ERROR("4040", "해당 리소스를 찾을 수 없습니다."),//리소스를 찾을 수 없음(잘못된 URI) + APPLICATION_NOT_FOUND_ERROR("4041", "해당 지원서가 존재하지 않습니다."),//지원서가 존재하지 않음 + APPLICATION_INTRODUCE_NOT_FOUND_ERROR("4042", "해당 지원서에 자기소개가 존재하지 않습니다."),//자기소개가 존재하지 않음 //잘못된 데이터 오류들 INVALID_REQUEST_FORMAT_ERROR("4000", "유효하지 않은 Body 형식입니다."),//요청 형식이 잘못됨 INVALID_PART_ERROR("4001", "유효하지 않은 파트입니다."),//유효하지 않은 파트 diff --git a/src/main/java/org/mjulikelion/baker/exception/ApplicationIntroduceNotFoundException.java b/src/main/java/org/mjulikelion/baker/exception/ApplicationIntroduceNotFoundException.java new file mode 100644 index 0000000..127250a --- /dev/null +++ b/src/main/java/org/mjulikelion/baker/exception/ApplicationIntroduceNotFoundException.java @@ -0,0 +1,9 @@ +package org.mjulikelion.baker.exception; + +import org.mjulikelion.baker.errorcode.ErrorCode; + +public class ApplicationIntroduceNotFoundException extends CustomException { + public ApplicationIntroduceNotFoundException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/org/mjulikelion/baker/exception/ApplicationNotFoundException.java b/src/main/java/org/mjulikelion/baker/exception/ApplicationNotFoundException.java new file mode 100644 index 0000000..262971d --- /dev/null +++ b/src/main/java/org/mjulikelion/baker/exception/ApplicationNotFoundException.java @@ -0,0 +1,9 @@ +package org.mjulikelion.baker.exception; + +import org.mjulikelion.baker.errorcode.ErrorCode; + +public class ApplicationNotFoundException extends CustomException { + public ApplicationNotFoundException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/org/mjulikelion/baker/model/Application.java b/src/main/java/org/mjulikelion/baker/model/Application.java index 8be6b9e..2d4aa6b 100644 --- a/src/main/java/org/mjulikelion/baker/model/Application.java +++ b/src/main/java/org/mjulikelion/baker/model/Application.java @@ -65,7 +65,7 @@ public class Application { @CreatedDate private Date createdAt; - @OneToMany(mappedBy = "application", orphanRemoval = true, fetch = FetchType.LAZY) + @OneToMany(mappedBy = "application", orphanRemoval = true, fetch = FetchType.EAGER) private List introduces; @OneToMany(mappedBy = "application", orphanRemoval = true, fetch = FetchType.LAZY) diff --git a/src/main/java/org/mjulikelion/baker/model/ApplicationIntroduce.java b/src/main/java/org/mjulikelion/baker/model/ApplicationIntroduce.java index 0b82e08..6e5fa29 100644 --- a/src/main/java/org/mjulikelion/baker/model/ApplicationIntroduce.java +++ b/src/main/java/org/mjulikelion/baker/model/ApplicationIntroduce.java @@ -26,7 +26,7 @@ public class ApplicationIntroduce { @GeneratedValue(strategy = GenerationType.AUTO, generator = "uuid2") private UUID id; - @ManyToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.EAGER) + @ManyToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY) @JoinColumn(name = "application_id") private Application application; diff --git a/src/main/java/org/mjulikelion/baker/repository/ApplicationIntroduceRepository.java b/src/main/java/org/mjulikelion/baker/repository/ApplicationIntroduceRepository.java index c4fe741..0fbddb4 100644 --- a/src/main/java/org/mjulikelion/baker/repository/ApplicationIntroduceRepository.java +++ b/src/main/java/org/mjulikelion/baker/repository/ApplicationIntroduceRepository.java @@ -1,8 +1,10 @@ package org.mjulikelion.baker.repository; +import java.util.List; import java.util.UUID; import org.mjulikelion.baker.model.ApplicationIntroduce; import org.springframework.data.jpa.repository.JpaRepository; public interface ApplicationIntroduceRepository extends JpaRepository { + List findByApplicationId(UUID applicationId); } diff --git a/src/main/java/org/mjulikelion/baker/repository/ApplicationRepository.java b/src/main/java/org/mjulikelion/baker/repository/ApplicationRepository.java index 3d30fb8..225af38 100644 --- a/src/main/java/org/mjulikelion/baker/repository/ApplicationRepository.java +++ b/src/main/java/org/mjulikelion/baker/repository/ApplicationRepository.java @@ -1,5 +1,6 @@ package org.mjulikelion.baker.repository; +import java.util.Optional; import java.util.UUID; import org.mjulikelion.baker.model.Application; import org.mjulikelion.baker.model.Part; @@ -10,5 +11,7 @@ public interface ApplicationRepository extends JpaRepository { Long countAllByPart(Part part); + Optional findByStudentId(String studentId); + Page findAllByPart(Part part, Pageable pageable); } diff --git a/src/main/java/org/mjulikelion/baker/service/introduce/IntroduceQueryService.java b/src/main/java/org/mjulikelion/baker/service/introduce/IntroduceQueryService.java new file mode 100644 index 0000000..3e83144 --- /dev/null +++ b/src/main/java/org/mjulikelion/baker/service/introduce/IntroduceQueryService.java @@ -0,0 +1,9 @@ +package org.mjulikelion.baker.service.introduce; + +import org.mjulikelion.baker.dto.response.ResponseDto; +import org.mjulikelion.baker.dto.response.introduce.IntroduceGetResponseData; +import org.springframework.http.ResponseEntity; + +public interface IntroduceQueryService { + ResponseEntity> getStudentIntroduce(String studentId); +} diff --git a/src/main/java/org/mjulikelion/baker/service/introduce/IntroduceQueryServiceImpl.java b/src/main/java/org/mjulikelion/baker/service/introduce/IntroduceQueryServiceImpl.java new file mode 100644 index 0000000..ec0825b --- /dev/null +++ b/src/main/java/org/mjulikelion/baker/service/introduce/IntroduceQueryServiceImpl.java @@ -0,0 +1,82 @@ +package org.mjulikelion.baker.service.introduce; + +import static org.mjulikelion.baker.errorcode.ErrorCode.APPLICATION_INTRODUCE_NOT_FOUND_ERROR; +import static org.mjulikelion.baker.errorcode.ErrorCode.APPLICATION_NOT_FOUND_ERROR; + +import java.util.Comparator; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; +import org.mjulikelion.baker.dto.response.ResponseDto; +import org.mjulikelion.baker.dto.response.introduce.IntroduceGetResponseData; +import org.mjulikelion.baker.exception.ApplicationIntroduceNotFoundException; +import org.mjulikelion.baker.exception.ApplicationNotFoundException; +import org.mjulikelion.baker.model.Application; +import org.mjulikelion.baker.model.ApplicationIntroduce; +import org.mjulikelion.baker.model.Introduce; +import org.mjulikelion.baker.repository.ApplicationIntroduceRepository; +import org.mjulikelion.baker.repository.ApplicationRepository; +import org.mjulikelion.baker.vo.IntroduceDetailVO; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@AllArgsConstructor +public class IntroduceQueryServiceImpl implements IntroduceQueryService { + private final ApplicationRepository applicationRepository; + private final ApplicationIntroduceRepository applicationIntroduceRepository; + + @Override + @Transactional(readOnly = true) + public ResponseEntity> getStudentIntroduce(String studentId) { + Application application = this.findApplicationByStudentId(studentId); + List applicationIntroduceList = this.findApplicationIntroduces(application.getId()); + List introduceDetailVOList = this.buildIntroduceDetailVOList(applicationIntroduceList); + + IntroduceGetResponseData introduceGetResponseData = IntroduceGetResponseData.builder() + .introduceDetailVOList(introduceDetailVOList) + .part(application.getPart()) + .build(); + + return ResponseEntity.ok(ResponseDto.res(HttpStatus.OK, "OK", introduceGetResponseData)); + } + + private Application findApplicationByStudentId(String studentId) { + return this.applicationRepository.findByStudentId(studentId) + .orElseThrow(() -> new ApplicationNotFoundException(APPLICATION_NOT_FOUND_ERROR)); + } + + private List findApplicationIntroduces(UUID applicationId) { + List applicationIntroduceList = this.applicationIntroduceRepository.findByApplicationId( + applicationId); + if (applicationIntroduceList.isEmpty()) { + throw new ApplicationIntroduceNotFoundException(APPLICATION_INTRODUCE_NOT_FOUND_ERROR); + } + return applicationIntroduceList; + } + + private List buildIntroduceDetailVOList(List applicationIntroduceList) { + Map introduceMap = applicationIntroduceList.stream() + .sorted(Comparator.comparingInt(a -> a.getIntroduce().getSequence())) + .collect(Collectors.toMap( + ApplicationIntroduce::getIntroduce, + ApplicationIntroduce::getContent, + (a, b) -> b, + LinkedHashMap::new + )); + + return introduceMap.keySet().stream() + .map(introduce -> IntroduceDetailVO.builder() + .sequence(introduce.getSequence()) + .title(introduce.getTitle()) + .content(introduceMap.get(introduce)) + .build()) + .collect(Collectors.toList()); + } +} + diff --git a/src/main/java/org/mjulikelion/baker/vo/IntroduceDetailVO.java b/src/main/java/org/mjulikelion/baker/vo/IntroduceDetailVO.java new file mode 100644 index 0000000..7640860 --- /dev/null +++ b/src/main/java/org/mjulikelion/baker/vo/IntroduceDetailVO.java @@ -0,0 +1,14 @@ +package org.mjulikelion.baker.vo; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Builder; + +@Builder +public class IntroduceDetailVO { + @JsonProperty + private final int sequence; + @JsonProperty + private final String title; + @JsonProperty + private final String content; +}