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..fa66e867 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/common/controller/ExceptionControllerAdvice.java @@ -0,0 +1,20 @@ +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; +import umc.kkijuk.server.common.domian.response.ErrorResponse; + +@RestControllerAdvice +@RequiredArgsConstructor +public class ExceptionControllerAdvice { + + @ResponseStatus(HttpStatus.NOT_FOUND) + @ExceptionHandler(ResourceNotFoundException.class) + public ErrorResponse resourceNotFoundException(ResourceNotFoundException exception) { + return new ErrorResponse(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/common/domian/response/ErrorResponse.java b/src/main/java/umc/kkijuk/server/common/domian/response/ErrorResponse.java new file mode 100644 index 00000000..50273507 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/common/domian/response/ErrorResponse.java @@ -0,0 +1,12 @@ +package umc.kkijuk.server.common.domian.response; + +import lombok.Getter; + +@Getter +public class ErrorResponse { + private final String message; + + public ErrorResponse(String message) { + this.message = message; + } +} 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..e9b91a78 100644 --- a/src/main/java/umc/kkijuk/server/recruit/controller/RecruitController.java +++ b/src/main/java/umc/kkijuk/server/recruit/controller/RecruitController.java @@ -8,9 +8,11 @@ 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.controller.response.RecruitResponse; -import umc.kkijuk.server.recruit.domain.Recruit; -import umc.kkijuk.server.recruit.domain.RecruitCreateDto; +import umc.kkijuk.server.recruit.controller.response.RecruitInfoResponse; +import umc.kkijuk.server.recruit.domain.*; +import umc.kkijuk.server.recruit.controller.response.RecruitIdResponse; + +import java.util.ArrayList; @Tag(name = "recruit", description = "모집 공고 API") @RestController @@ -20,11 +22,49 @@ public class RecruitController { private final RecruitService recruitService; @PostMapping - public ResponseEntity create(@RequestBody @Valid RecruitCreateDto recruitCreateDto) { + public ResponseEntity create(@RequestBody @Valid RecruitCreateDto recruitCreateDto) { LoginUser loginUser = LoginUser.get(); Recruit recruit = recruitService.create(recruitCreateDto); return ResponseEntity .status(HttpStatus.CREATED) - .body(RecruitResponse.from(recruit)); + .body(RecruitIdResponse.from(recruit)); + } + + @PutMapping("/{recruitId}") + public ResponseEntity update(@RequestBody @Valid RecruitUpdate recruitUpdate, + @PathVariable long recruitId) { + LoginUser loginUser = LoginUser.get(); + return ResponseEntity + .status(HttpStatus.OK) + .body(recruitService.update(recruitId, recruitUpdate).getId()); + } + + @PatchMapping("/{recruitId}") + public ResponseEntity updateState(@RequestBody @Valid RecruitStatusUpdate recruitStatusUpdate, + @PathVariable long recruitId) { + LoginUser loginUser = LoginUser.get(); + return ResponseEntity + .ok() + .body(recruitService.updateStatus(recruitId, recruitStatusUpdate).getId()); + } + + @DeleteMapping("/{recruitId}") + public ResponseEntity delete(@PathVariable long recruitId) { + LoginUser loginUser = LoginUser.get(); + return ResponseEntity + .ok() + .body(recruitService.disable(recruitId).getId()); + } + + // review 만든 후 테스트 코드 작성 + @GetMapping("/{recruitId}") + public ResponseEntity getRecruitInfo(@PathVariable long recruitId) { + LoginUser loginUser = LoginUser.get(); + Recruit recruit = recruitService.getById(recruitId); +// List reviews = reviewService.getByRecruitId(recruitId); + + return ResponseEntity + .ok() + .body(RecruitInfoResponse.from(recruit, new ArrayList<>())); } } 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..4c7b08bd 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,19 @@ import umc.kkijuk.server.recruit.domain.Recruit; import umc.kkijuk.server.recruit.domain.RecruitCreateDto; +import umc.kkijuk.server.recruit.domain.RecruitStatusUpdate; +import umc.kkijuk.server.recruit.domain.RecruitUpdate; + +import javax.net.ssl.SSLSession; public interface RecruitService { Recruit create(RecruitCreateDto recruitCreateDto); + + Recruit update(Long recruitId, RecruitUpdate recruitUpdate); + + Recruit getById(long id); + + Recruit updateStatus(long recruitId, RecruitStatusUpdate recruitStatusUpdate); + + Recruit disable(long recruitId); } diff --git a/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitResponse.java b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitIdResponse.java similarity index 66% rename from src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitResponse.java rename to src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitIdResponse.java index 1f94ce06..2f23b4ca 100644 --- a/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitResponse.java +++ b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitIdResponse.java @@ -6,10 +6,10 @@ @Getter @Builder -public class RecruitResponse { +public class RecruitIdResponse { private final Long id; - public static RecruitResponse from(Recruit recruit) { - return RecruitResponse.builder() + public static RecruitIdResponse from(Recruit recruit) { + return RecruitIdResponse.builder() .id(recruit.getId()) .build(); } diff --git a/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitInfoResponse.java b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitInfoResponse.java new file mode 100644 index 00000000..15feaa7c --- /dev/null +++ b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitInfoResponse.java @@ -0,0 +1,47 @@ +package umc.kkijuk.server.recruit.controller.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import umc.kkijuk.server.recruit.domain.Recruit; +import umc.kkijuk.server.recruit.domain.Review; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + +@Getter +@Builder +public class RecruitInfoResponse { + private final String title; + + @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 final LocalDateTime startTime; + + @Schema(description = "공고 모집 마감 날짜", example = "2022-09-25 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 final LocalDateTime endTime; + + @Schema(description = "공고 지원 날짜", example = "2024-07-19") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul") + private final LocalDate applyDate; + private final List tags; + private final int reviewCount; + private final String link; + private final List reviews; + + public static RecruitInfoResponse from(Recruit recruit, List reviews) { + return RecruitInfoResponse.builder() + .title(recruit.getTitle()) + .startTime(recruit.getStartTime()) + .endTime(recruit.getEndTime()) + .applyDate(recruit.getApplyDate()) + .tags(recruit.getTags()) + .link(recruit.getLink()) + .reviewCount(reviews.size()) + .reviews(reviews.stream().map(ReviewResponse::from).toList()) + .build(); + } +} diff --git a/src/main/java/umc/kkijuk/server/recruit/controller/response/ReviewResponse.java b/src/main/java/umc/kkijuk/server/recruit/controller/response/ReviewResponse.java new file mode 100644 index 00000000..44434fd8 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/recruit/controller/response/ReviewResponse.java @@ -0,0 +1,30 @@ +package umc.kkijuk.server.recruit.controller.response; + +import com.fasterxml.jackson.annotation.JsonFormat; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Builder; +import lombok.Getter; +import umc.kkijuk.server.recruit.domain.Review; + +import java.time.LocalDate; + +@Getter +@Builder +public class ReviewResponse { + private final Long reviewId; + private final String title; + private final String content; + + @Schema(description = "날짜", example = "2024-07-19") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul") + private final LocalDate date; + + public static ReviewResponse from(Review review) { + return ReviewResponse.builder() + .reviewId(review.getId()) + .title(review.getTitle()) + .content(review.getContent()) + .date(review.getDate()) + .build(); + } +} 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..7235e600 100644 --- a/src/main/java/umc/kkijuk/server/recruit/domain/Recruit.java +++ b/src/main/java/umc/kkijuk/server/recruit/domain/Recruit.java @@ -5,6 +5,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.ArrayList; import java.util.List; @Builder @@ -18,6 +19,8 @@ public class Recruit { private final LocalDate applyDate; private final List tags; private final String link; + private final Boolean isActive; + private final LocalDateTime disabledTime; public static Recruit from(RecruitCreateDto recruitCreateDto) { return Recruit.builder() @@ -26,8 +29,52 @@ public static Recruit from(RecruitCreateDto recruitCreateDto) { .startTime(recruitCreateDto.getStartTime()) .endTime(recruitCreateDto.getEndTime()) .applyDate(recruitCreateDto.getApplyDate()) - .tags(recruitCreateDto.getTags()) + .tags(recruitCreateDto.getTags() != null ? recruitCreateDto.getTags() : new ArrayList<>()) .link(recruitCreateDto.getLink()) + .isActive(true) + .build(); + } + + public Recruit update(RecruitUpdate recruitUpdate) { + return Recruit.builder() + .id(this.id) + .title(recruitUpdate.getTitle()) + .status(recruitUpdate.getStatus()) + .startTime(recruitUpdate.getStartTime()) + .endTime(recruitUpdate.getEndTime()) + .applyDate(recruitUpdate.getApplyDate()) + .tags(recruitUpdate.getTags() != null ? recruitUpdate.getTags() : new ArrayList<>()) + .link(recruitUpdate.getLink()) + .isActive(this.isActive) + .build(); + } + + public Recruit updateStatus(RecruitStatusUpdate recruitStatusUpdate) { + return Recruit.builder() + .id(this.id) + .title(this.getTitle()) + .status(recruitStatusUpdate.getStatus()) + .startTime(this.getStartTime()) + .endTime(this.getEndTime()) + .applyDate(this.getApplyDate()) + .tags(this.getTags()) + .link(this.getLink()) + .isActive(this.isActive) + .build(); + } + + public Recruit disable() { + return Recruit.builder() + .id(this.id) + .title(this.getTitle()) + .status(this.getStatus()) + .startTime(this.getStartTime()) + .endTime(this.getEndTime()) + .applyDate(this.getApplyDate()) + .tags(this.getTags()) + .link(this.getLink()) + .isActive(false) + .disabledTime(LocalDateTime.now()) .build(); } } diff --git a/src/main/java/umc/kkijuk/server/recruit/domain/RecruitStatus.java b/src/main/java/umc/kkijuk/server/recruit/domain/RecruitStatus.java index 12834a48..1423e1b3 100644 --- a/src/main/java/umc/kkijuk/server/recruit/domain/RecruitStatus.java +++ b/src/main/java/umc/kkijuk/server/recruit/domain/RecruitStatus.java @@ -5,7 +5,7 @@ import java.util.stream.Stream; public enum RecruitStatus { - INVALID, UNAPPLIED, PLANNED, APPLYING, EJECTED, ACCEPTED; + INVALID, UNAPPLIED, PLANNED, APPLYING, REJECTED, ACCEPTED; @JsonCreator public static RecruitStatus parsing(String value) { diff --git a/src/main/java/umc/kkijuk/server/recruit/domain/RecruitStatusUpdate.java b/src/main/java/umc/kkijuk/server/recruit/domain/RecruitStatusUpdate.java new file mode 100644 index 00000000..7918465f --- /dev/null +++ b/src/main/java/umc/kkijuk/server/recruit/domain/RecruitStatusUpdate.java @@ -0,0 +1,15 @@ +package umc.kkijuk.server.recruit.domain; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; +import lombok.*; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class RecruitStatusUpdate { + @NotNull + @Schema(description = "변경될 공고 상태", example = "REJECTED", type = "string") + private RecruitStatus status; +} 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/domain/Review.java b/src/main/java/umc/kkijuk/server/recruit/domain/Review.java new file mode 100644 index 00000000..fcf6f690 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/recruit/domain/Review.java @@ -0,0 +1,15 @@ +package umc.kkijuk.server.recruit.domain; + +import lombok.Builder; +import lombok.Getter; + +import java.time.LocalDate; + +@Getter +@Builder +public class Review { + private final Long id; + private final String title; + private final String content; + private final LocalDate date; +} diff --git a/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitEntity.java b/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitEntity.java index cd741157..38776b01 100644 --- a/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitEntity.java +++ b/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitEntity.java @@ -36,6 +36,8 @@ public class RecruitEntity { private List tags; private String link; + private Boolean isActive; + private LocalDateTime disabledTime; public static RecruitEntity from(Recruit recruit) { RecruitEntity recruitEntity = new RecruitEntity(); @@ -47,6 +49,8 @@ public static RecruitEntity from(Recruit recruit) { recruitEntity.applyDate = recruit.getApplyDate(); recruitEntity.tags = recruit.getTags(); recruitEntity.link= recruit.getLink(); + recruitEntity.isActive = recruit.getIsActive(); + recruitEntity.disabledTime = recruit.getDisabledTime(); return recruitEntity; } @@ -60,6 +64,8 @@ public Recruit toModel() { .applyDate(applyDate) .tags(tags) .link(link) + .isActive(isActive) + .disabledTime(disabledTime) .build(); } } diff --git a/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitJpaRepository.java b/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitJpaRepository.java index 40892442..44829896 100644 --- a/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitJpaRepository.java +++ b/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitJpaRepository.java @@ -2,5 +2,8 @@ import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface RecruitJpaRepository extends JpaRepository { + Optional findByIdAndIsActive(long id, Boolean isActive); } diff --git a/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitRepositoryImpl.java b/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitRepositoryImpl.java index 658c4743..165c6b7c 100644 --- a/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitRepositoryImpl.java +++ b/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitRepositoryImpl.java @@ -2,6 +2,7 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; +import umc.kkijuk.server.common.domian.exception.ResourceNotFoundException; import umc.kkijuk.server.recruit.domain.Recruit; import umc.kkijuk.server.recruit.service.port.RecruitRepository; @@ -21,4 +22,15 @@ public Optional findById(Long id) { public Recruit save(Recruit recruit) { return recruitJpaRepository.save(RecruitEntity.from(recruit)).toModel(); } + + @Override + public Recruit getById(long id) { + return findByIdAndIsActive(id, true) + .orElseThrow(() -> new ResourceNotFoundException("recruit", id)); + } + + @Override + public Optional findByIdAndIsActive(long id, Boolean isActive) { + return recruitJpaRepository.findByIdAndIsActive(id, isActive).map(RecruitEntity::toModel); + } } 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 199a51bd..9f8a2726 100644 --- a/src/main/java/umc/kkijuk/server/recruit/service/RecruitServiceImpl.java +++ b/src/main/java/umc/kkijuk/server/recruit/service/RecruitServiceImpl.java @@ -1,21 +1,60 @@ package umc.kkijuk.server.recruit.service; +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.RecruitStatusUpdate; +import umc.kkijuk.server.recruit.domain.RecruitUpdate; import umc.kkijuk.server.recruit.service.port.RecruitRepository; +import java.util.Optional; + @Service +@Builder @RequiredArgsConstructor public class RecruitServiceImpl implements RecruitService { private final RecruitRepository recruitRepository; @Override + public Recruit getById(long id) { + return recruitRepository.findByIdAndIsActive(id, true) + .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); + } + + @Override + @Transactional + public Recruit updateStatus(long recruitId, RecruitStatusUpdate recruitStatusUpdate) { + Recruit recruit = getById(recruitId); + recruit = recruit.updateStatus(recruitStatusUpdate); + return recruitRepository.save(recruit); + } + + @Override + @Transactional + public Recruit disable(long recruitId) { + Recruit recruit = getById(recruitId); + recruit = recruit.disable(); + return recruitRepository.save(recruit); + } } diff --git a/src/main/java/umc/kkijuk/server/recruit/service/port/RecruitRepository.java b/src/main/java/umc/kkijuk/server/recruit/service/port/RecruitRepository.java index 367df7f1..6d18a41d 100644 --- a/src/main/java/umc/kkijuk/server/recruit/service/port/RecruitRepository.java +++ b/src/main/java/umc/kkijuk/server/recruit/service/port/RecruitRepository.java @@ -7,4 +7,6 @@ public interface RecruitRepository { Optional findById(Long id); Recruit save(Recruit recruit); + Recruit getById(long id); + Optional findByIdAndIsActive(long id, Boolean isActive); } diff --git a/src/test/java/umc/kkijuk/server/recruit/mock/FakeRecruitRepository.java b/src/test/java/umc/kkijuk/server/recruit/mock/FakeRecruitRepository.java new file mode 100644 index 00000000..b6024529 --- /dev/null +++ b/src/test/java/umc/kkijuk/server/recruit/mock/FakeRecruitRepository.java @@ -0,0 +1,60 @@ +package umc.kkijuk.server.recruit.mock; + +import umc.kkijuk.server.common.domian.exception.ResourceNotFoundException; +import umc.kkijuk.server.recruit.domain.Recruit; +import umc.kkijuk.server.recruit.service.port.RecruitRepository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; + +public class FakeRecruitRepository implements RecruitRepository { + private final AtomicLong authGeneratedID = new AtomicLong(0); + private final List data = new ArrayList<>(); + + @Override + public Optional findById(Long id) { + return data.stream().filter(item -> item.getId().equals(id)).findAny(); + } + + @Override + public Recruit save(Recruit recruit) { + if (recruit.getId() == null || recruit.getId() == 0){ + Recruit newRecruit = Recruit.builder() + .id(authGeneratedID.incrementAndGet()) + .title(recruit.getTitle()) + .status(recruit.getStatus()) + .startTime(recruit.getStartTime()) + .endTime(recruit.getEndTime()) + .applyDate(recruit.getApplyDate()) + .tags(recruit.getTags()) + .link(recruit.getLink()) + .isActive(recruit.getIsActive()) + .build(); + data.add(newRecruit); + return newRecruit; + } else { + data.removeIf(item -> Objects.equals(item.getId(), recruit.getId())); + data.add(recruit); + return recruit; + } + } + + @Override + public Recruit getById(long id) { + return findByIdAndIsActive(id, true) + .orElseThrow(() -> new ResourceNotFoundException("recruit", id) + ); + } + + @Override + public Optional findByIdAndIsActive(long id, Boolean isActive) { + return data.stream() + .filter(item -> + item.getId().equals(id) && + item.getIsActive() != null && + item.getIsActive()).findAny(); + } +} diff --git a/src/test/java/umc/kkijuk/server/recruit/service/RecruitServiceTest.java b/src/test/java/umc/kkijuk/server/recruit/service/RecruitServiceTest.java new file mode 100644 index 00000000..9830a3de --- /dev/null +++ b/src/test/java/umc/kkijuk/server/recruit/service/RecruitServiceTest.java @@ -0,0 +1,212 @@ +package umc.kkijuk.server.recruit.service; + +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.*; +import umc.kkijuk.server.recruit.mock.FakeRecruitRepository; +import umc.kkijuk.server.recruit.service.port.RecruitRepository; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.Arrays; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +class RecruitServiceTest { + private RecruitService recruitService; + + @BeforeEach + void init() { + RecruitRepository recruitRepository = new FakeRecruitRepository(); + this.recruitService = RecruitServiceImpl.builder() + .recruitRepository(recruitRepository) + .build(); + + Recruit recruit = Recruit.builder() + .title("test-title") + .status(RecruitStatus.PLANNED) + .startTime(LocalDateTime.of(2024, 7, 19, 2, 30)) + .endTime(LocalDateTime.of(2024, 7, 31, 17, 30)) + .applyDate(LocalDate.of(2024, 7, 19)) + .tags(new ArrayList<>(Arrays.asList("코딩 테스트", "인턴", "대외 활동"))) + .link("test-link") + .isActive(true) + .build(); + + recruitRepository.save(recruit); + } + + @Test + void 새로운_recruit_만들기() { + //given + RecruitCreateDto recruitCreateDto = RecruitCreateDto.builder() + .title("dto-title") + .status(RecruitStatus.PLANNED) + .startTime(LocalDateTime.of(2024, 7, 19, 2, 30)) + .endTime(LocalDateTime.of(2024, 7, 30, 2, 30)) + .applyDate(LocalDate.of(2024, 7, 25)) + .tags(new ArrayList<>(Arrays.asList("tag1", "tag2", "tag3"))) + .link("https://www.dto-title.com") + .build(); + //when + Recruit result = recruitService.create(recruitCreateDto); + + //then + assertAll( + () -> assertThat(result.getId()).isEqualTo(2L), + () -> assertThat(result.getTitle()).isEqualTo("dto-title"), + () -> assertThat(result.getStatus()).isEqualTo(RecruitStatus.PLANNED), + () -> assertThat(result.getStartTime()).isEqualTo(LocalDateTime.of(2024, 7, 19, 2, 30)), + () -> assertThat(result.getEndTime().isEqual(LocalDateTime.of(2024, 7, 30, 2, 30))), + () -> assertThat(result.getApplyDate().isEqual(LocalDate.of(2024, 7, 25))), + () -> assertThat(result.getTags().size()).isEqualTo(3), + () -> assertThat(result.getLink()).isEqualTo("https://www.dto-title.com") + ); + } + + @Test + void 새로운_recruit_만들기_nullable() { + //given + RecruitCreateDto recruitCreateDto = RecruitCreateDto.builder() + .title("dto-title") + .status(RecruitStatus.PLANNED) + .startTime(LocalDateTime.of(2024, 7, 19, 2, 30)) + .endTime(LocalDateTime.of(2024, 7, 30, 2, 30)) + .build(); + //when + Recruit result = recruitService.create(recruitCreateDto); + + //then + assertAll( + () -> assertThat(result.getId()).isEqualTo(2L), + () -> assertThat(result.getTitle()).isEqualTo("dto-title"), + () -> assertThat(result.getStatus()).isEqualTo(RecruitStatus.PLANNED), + () -> assertThat(result.getStartTime()).isEqualTo(LocalDateTime.of(2024, 7, 19, 2, 30)), + () -> assertThat(result.getEndTime().isEqual(LocalDateTime.of(2024, 7, 30, 2, 30))), + () -> assertThat(result.getApplyDate()).isNull(), + () -> assertThat(result.getTags().size()).isZero(), + () -> 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 + assertThatThrownBy( + () -> recruitService.update(2L, recruitUpdate)).isInstanceOf(ResourceNotFoundException.class); + } + + @Test + void status만_수정시_없는_리소스로의_요청은_에러() { + //given + RecruitStatusUpdate recruitStatusUpdate = RecruitStatusUpdate.builder() + .status(RecruitStatus.ACCEPTED).build(); + + //when + //then + assertThatThrownBy( + () -> recruitService.updateStatus(2L, recruitStatusUpdate)).isInstanceOf(ResourceNotFoundException.class); + } + + @Test + void 기존_recruit_status_수정() { + //given + RecruitStatusUpdate recruitStatusUpdate = RecruitStatusUpdate.builder() + .status(RecruitStatus.ACCEPTED).build(); + + //when + Recruit result = recruitService.updateStatus(1L, recruitStatusUpdate); + + assertAll( + () -> assertThat(result.getId()).isEqualTo(1L), + () -> assertThat(result.getTitle()).isEqualTo("test-title"), + () -> assertThat(result.getStatus()).isEqualTo(RecruitStatus.ACCEPTED), + () -> assertThat(result.getStartTime()).isEqualTo(LocalDateTime.of(2024, 7, 19, 2, 30)), + () -> assertThat(result.getEndTime().isEqual(LocalDateTime.of(2024, 7, 31, 17, 30))), + () -> assertThat(result.getApplyDate().isEqual(LocalDate.of(2024, 7, 19))), + () -> assertThat(result.getTags().size()).isEqualTo(3), + () -> assertEquals(result.getTags(), Arrays.asList("코딩 테스트", "인턴", "대외 활동")), + () -> assertThat(result.getLink()).isEqualTo("test-link") + ); + } + + @Test + void 기존_recruit_비활성화() { + //given + //when + Recruit result = recruitService.disable(1L); + + //then + assertAll( + () -> assertThat(result.getIsActive()).isFalse(), + () -> assertThat(result.getDisabledTime()).isNotNull(), + () -> assertThat(result.getDisabledTime()).isBefore(LocalDateTime.now()) + ); + } + + @Test + void 없는_유저는_비활성화_할수_없다() { + //given + //when + //then + assertThatThrownBy( + () -> recruitService.disable(1234L)).isInstanceOf(ResourceNotFoundException.class); + } + + @Test + void 비활성화된_유저는_getById로_찾을수_없다() { + //given + recruitService.disable(1L); + + //when + //then + assertThatThrownBy( + () -> recruitService.getById(1L)).isInstanceOf(ResourceNotFoundException.class); + } +} \ No newline at end of file