From dee7638bffdb9951d7fc956dc1159f59eb27766f Mon Sep 17 00:00:00 2001 From: JeongHoon Lee Date: Fri, 19 Jul 2024 22:09:14 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20=EC=A7=80=EC=9B=90=20=EA=B3=B5=EA=B3=A0?= =?UTF-8?q?=20=EC=88=98=EC=A0=95=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ExceptionControllerAdvice.java | 19 +++++++ .../exception/ResourceNotFoundException.java | 7 +++ .../recruit/controller/RecruitController.java | 10 ++++ .../controller/port/RecruitService.java | 5 ++ .../kkijuk/server/recruit/domain/Recruit.java | 13 +++++ .../server/recruit/domain/RecruitUpdate.java | 45 ++++++++++++++++ .../recruit/service/RecruitServiceImpl.java | 21 ++++++++ .../recruit/mock/FakeRecruitRepository.java | 4 +- .../recruit/service/RecruitServiceTest.java | 54 ++++++++++++++++++- 9 files changed, 176 insertions(+), 2 deletions(-) create mode 100644 src/main/java/umc/kkijuk/server/common/controller/ExceptionControllerAdvice.java create mode 100644 src/main/java/umc/kkijuk/server/common/domian/exception/ResourceNotFoundException.java create mode 100644 src/main/java/umc/kkijuk/server/recruit/domain/RecruitUpdate.java diff --git a/src/main/java/umc/kkijuk/server/common/controller/ExceptionControllerAdvice.java b/src/main/java/umc/kkijuk/server/common/controller/ExceptionControllerAdvice.java new file mode 100644 index 00000000..bcd53ed5 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/common/controller/ExceptionControllerAdvice.java @@ -0,0 +1,19 @@ +package umc.kkijuk.server.common.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import umc.kkijuk.server.common.domian.exception.ResourceNotFoundException; + +@RestControllerAdvice +@RequiredArgsConstructor +public class ExceptionControllerAdvice { + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(ResourceNotFoundException.class) + public String resourceNotFoundException(ResourceNotFoundException exception) { + return exception.getMessage(); + } +} diff --git a/src/main/java/umc/kkijuk/server/common/domian/exception/ResourceNotFoundException.java b/src/main/java/umc/kkijuk/server/common/domian/exception/ResourceNotFoundException.java new file mode 100644 index 00000000..1020405e --- /dev/null +++ b/src/main/java/umc/kkijuk/server/common/domian/exception/ResourceNotFoundException.java @@ -0,0 +1,7 @@ +package umc.kkijuk.server.common.domian.exception; + +public class ResourceNotFoundException extends RuntimeException{ + public ResourceNotFoundException(String dataSource, long id) { + super(dataSource + "에서 ID " + id + "를 찾을 수 없습니다."); + } +} diff --git a/src/main/java/umc/kkijuk/server/recruit/controller/RecruitController.java b/src/main/java/umc/kkijuk/server/recruit/controller/RecruitController.java index 6b2f5179..29ee2c1e 100644 --- a/src/main/java/umc/kkijuk/server/recruit/controller/RecruitController.java +++ b/src/main/java/umc/kkijuk/server/recruit/controller/RecruitController.java @@ -8,6 +8,7 @@ import org.springframework.web.bind.annotation.*; import umc.kkijuk.server.common.LoginUser; import umc.kkijuk.server.recruit.controller.port.RecruitService; +import umc.kkijuk.server.recruit.domain.RecruitUpdate; import umc.kkijuk.server.recruit.controller.response.RecruitResponse; import umc.kkijuk.server.recruit.domain.Recruit; import umc.kkijuk.server.recruit.domain.RecruitCreateDto; @@ -27,4 +28,13 @@ public ResponseEntity create(@RequestBody @Valid RecruitCreateD .status(HttpStatus.CREATED) .body(RecruitResponse.from(recruit)); } + + @PutMapping("/{recruitId}") + public ResponseEntity modify(@RequestBody @Valid RecruitUpdate recruitUpdate, + @PathVariable Long recruitId) { + LoginUser loginUser = LoginUser.get(); + return ResponseEntity + .status(HttpStatus.OK) + .body(recruitService.update(recruitId, recruitUpdate).getId()); + } } diff --git a/src/main/java/umc/kkijuk/server/recruit/controller/port/RecruitService.java b/src/main/java/umc/kkijuk/server/recruit/controller/port/RecruitService.java index bd870f18..6f60c0b9 100644 --- a/src/main/java/umc/kkijuk/server/recruit/controller/port/RecruitService.java +++ b/src/main/java/umc/kkijuk/server/recruit/controller/port/RecruitService.java @@ -2,7 +2,12 @@ import umc.kkijuk.server.recruit.domain.Recruit; import umc.kkijuk.server.recruit.domain.RecruitCreateDto; +import umc.kkijuk.server.recruit.domain.RecruitUpdate; public interface RecruitService { Recruit create(RecruitCreateDto recruitCreateDto); + + Recruit update(Long recruitId, RecruitUpdate recruitUpdate); + + Recruit getById(long id); } diff --git a/src/main/java/umc/kkijuk/server/recruit/domain/Recruit.java b/src/main/java/umc/kkijuk/server/recruit/domain/Recruit.java index f38120d9..80759506 100644 --- a/src/main/java/umc/kkijuk/server/recruit/domain/Recruit.java +++ b/src/main/java/umc/kkijuk/server/recruit/domain/Recruit.java @@ -30,4 +30,17 @@ public static Recruit from(RecruitCreateDto recruitCreateDto) { .link(recruitCreateDto.getLink()) .build(); } + + public Recruit update(RecruitUpdate recruitUpdate) { + return Recruit.builder() + .id(id) + .title(recruitUpdate.getTitle()) + .status(recruitUpdate.getStatus()) + .startTime(recruitUpdate.getStartTime()) + .endTime(recruitUpdate.getEndTime()) + .applyDate(recruitUpdate.getApplyDate()) + .tags(recruitUpdate.getTags()) + .link(recruitUpdate.getLink()) + .build(); + } } diff --git a/src/main/java/umc/kkijuk/server/recruit/domain/RecruitUpdate.java b/src/main/java/umc/kkijuk/server/recruit/domain/RecruitUpdate.java new file mode 100644 index 00000000..3050197a --- /dev/null +++ b/src/main/java/umc/kkijuk/server/recruit/domain/RecruitUpdate.java @@ -0,0 +1,45 @@ +package umc.kkijuk.server.recruit.domain; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RecruitUpdate { + @NotBlank(message = "공고 제목은 필수 입력 항목입니다.") + @Schema(description = "수정될 공고 제목", example = "[00] 공고제목", type = "string") + private String title; + + @NotNull(message = "공고 모집 시작 날짜는 필수 입력 항목입니다.") + @Schema(description = "수정될 공고 모집 시작 날짜", example = "2022-09-18 10:11", pattern = "yyyy-MM-dd HH:mm", type = "string") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") + private LocalDateTime startTime; + + @NotNull(message = "공고 모집 마감 날짜는 필수 입력 항목입니다.") + @Schema(description = "수정될 공고 모집 마감 날짜", example = "2022-09-18 10:11", pattern = "yyyy-MM-dd HH:mm", type = "string") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm", timezone = "Asia/Seoul") + private LocalDateTime endTime; + + @NotNull(message = "유효하지 않은 지원 상태가 입력 되었습니다.") + @Schema(description = "수정될 공고 지원 상태", example = "PLANNED", type = "string") + private RecruitStatus status; + + @Schema(description = "수정될 공고 지원 날짜", pattern = "yyyy-MM-dd") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul") + private LocalDate applyDate; + + // @Schema(description = "태그", example = "[\"코딩 테스트\", \"인턴\", \"대외 활동\"]", type = "string") + private List tags; + + @Schema(description = "수정될 공고 링크", example = "https://www.naver.com", type = "string") + private String link; +} diff --git a/src/main/java/umc/kkijuk/server/recruit/service/RecruitServiceImpl.java b/src/main/java/umc/kkijuk/server/recruit/service/RecruitServiceImpl.java index bda29d89..35858164 100644 --- a/src/main/java/umc/kkijuk/server/recruit/service/RecruitServiceImpl.java +++ b/src/main/java/umc/kkijuk/server/recruit/service/RecruitServiceImpl.java @@ -3,11 +3,16 @@ import lombok.Builder; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import umc.kkijuk.server.common.domian.exception.ResourceNotFoundException; import umc.kkijuk.server.recruit.controller.port.RecruitService; import umc.kkijuk.server.recruit.domain.Recruit; import umc.kkijuk.server.recruit.domain.RecruitCreateDto; +import umc.kkijuk.server.recruit.domain.RecruitUpdate; import umc.kkijuk.server.recruit.service.port.RecruitRepository; +import java.util.Optional; + @Service @Builder @RequiredArgsConstructor @@ -16,8 +21,24 @@ public class RecruitServiceImpl implements RecruitService { private final RecruitRepository recruitRepository; @Override + public Recruit getById(long id) { + return recruitRepository.findById(id).orElseThrow(() -> new ResourceNotFoundException("Recruit", id)); + } + + @Override + @Transactional public Recruit create(RecruitCreateDto recruitCreateDto) { Recruit recruit = Recruit.from(recruitCreateDto); return recruitRepository.save(recruit); } + + @Override + @Transactional + public Recruit update(Long recruitId, RecruitUpdate recruitUpdate) { + Recruit recruit = getById(recruitId); + recruit = recruit.update(recruitUpdate); + return recruitRepository.save(recruit); + } + + } diff --git a/src/test/java/umc/kkijuk/server/recruit/mock/FakeRecruitRepository.java b/src/test/java/umc/kkijuk/server/recruit/mock/FakeRecruitRepository.java index e10af293..1cade240 100644 --- a/src/test/java/umc/kkijuk/server/recruit/mock/FakeRecruitRepository.java +++ b/src/test/java/umc/kkijuk/server/recruit/mock/FakeRecruitRepository.java @@ -21,7 +21,7 @@ public Optional findById(Long id) { @Override public Recruit save(Recruit recruit) { if (recruit.getId() == null || recruit.getId() == 0){ - return Recruit.builder() + Recruit newRecruit = Recruit.builder() .id(authGeneratedID.incrementAndGet()) .title(recruit.getTitle()) .status(recruit.getStatus()) @@ -31,6 +31,8 @@ public Recruit save(Recruit recruit) { .tags(recruit.getTags()) .link(recruit.getLink()) .build(); + data.add(newRecruit); + return newRecruit; } else { data.removeIf(item -> Objects.equals(item.getId(), recruit.getId())); data.add(recruit); diff --git a/src/test/java/umc/kkijuk/server/recruit/service/RecruitServiceTest.java b/src/test/java/umc/kkijuk/server/recruit/service/RecruitServiceTest.java index 9edb963f..69380826 100644 --- a/src/test/java/umc/kkijuk/server/recruit/service/RecruitServiceTest.java +++ b/src/test/java/umc/kkijuk/server/recruit/service/RecruitServiceTest.java @@ -1,11 +1,14 @@ package umc.kkijuk.server.recruit.service; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import umc.kkijuk.server.common.domian.exception.ResourceNotFoundException; import umc.kkijuk.server.recruit.controller.port.RecruitService; import umc.kkijuk.server.recruit.domain.Recruit; import umc.kkijuk.server.recruit.domain.RecruitCreateDto; import umc.kkijuk.server.recruit.domain.RecruitStatus; +import umc.kkijuk.server.recruit.domain.RecruitUpdate; import umc.kkijuk.server.recruit.mock.FakeRecruitRepository; import umc.kkijuk.server.recruit.service.port.RecruitRepository; @@ -15,7 +18,7 @@ import java.util.Arrays; import static org.assertj.core.api.Assertions.*; -import static org.junit.jupiter.api.Assertions.assertAll; +import static org.junit.jupiter.api.Assertions.*; class RecruitServiceTest { private RecruitService recruitService; @@ -92,4 +95,53 @@ void init() { () -> assertThat(result.getLink()).isNull() ); } + + @Test + void 기존_recruit_수정() { + //given + RecruitUpdate recruitUpdate = RecruitUpdate.builder() + .title("update-title") + .status(RecruitStatus.INVALID) + .startTime(LocalDateTime.of(2024, 9, 18, 1, 30)) + .endTime(LocalDateTime.of(2024, 10, 4, 3, 30)) + .applyDate(LocalDate.of(2024, 8, 19)) + .tags(new ArrayList<>(Arrays.asList("updatedTag1", "updatedTag2"))) + .link("https://www.update-title.com") + .build(); + + //when + Recruit updatedRecruit = recruitService.update(1L, recruitUpdate); + + //then + assertAll( + () -> assertThat(updatedRecruit.getId()).isEqualTo(1L), + () -> assertThat(updatedRecruit.getTitle()).isEqualTo("update-title"), + () -> assertThat(updatedRecruit.getStatus()).isEqualTo(RecruitStatus.INVALID), + () -> assertThat(updatedRecruit.getStartTime()).isEqualTo(LocalDateTime.of(2024, 9, 18, 1, 30)), + () -> assertThat(updatedRecruit.getEndTime().isEqual(LocalDateTime.of(2024, 10, 4, 3, 30))), + () -> assertThat(updatedRecruit.getApplyDate().isEqual(LocalDate.of(2024, 8, 19))), + () -> assertThat(updatedRecruit.getTags().size()).isEqualTo(2), + () -> assertEquals(updatedRecruit.getTags(), Arrays.asList("updatedTag1", "updatedTag2")), + () -> assertThat(updatedRecruit.getLink()).isEqualTo("https://www.update-title.com") + ); + } + + @Test + void 수정시_없는_리소스로의_요청은_에러() { + //given + RecruitUpdate recruitUpdate = RecruitUpdate.builder() + .title("update-title") + .status(RecruitStatus.INVALID) + .startTime(LocalDateTime.of(2024, 9, 18, 1, 30)) + .endTime(LocalDateTime.of(2024, 10, 4, 3, 30)) + .applyDate(LocalDate.of(2024, 8, 19)) + .tags(new ArrayList<>(Arrays.asList("updatedTag1", "updatedTag2"))) + .link("https://www.update-title.com") + .build(); + + //when + //then + Assertions.assertThatThrownBy( + () -> recruitService.update(2L, recruitUpdate)).isInstanceOf(ResourceNotFoundException.class); + } } \ No newline at end of file