diff --git a/.github/workflows/CICD_DEVELOP.yml b/.github/workflows/CICD_DEVELOP.yml index 10526e43..5d0134b5 100644 --- a/.github/workflows/CICD_DEVELOP.yml +++ b/.github/workflows/CICD_DEVELOP.yml @@ -43,9 +43,9 @@ jobs: - name: Generate application.properties run: | echo commit.hash=${{ steps.slug.outputs.sha7 }} >> ./src/main/resources/application-prod.properties - echo ${{ secrets.DATASOURCE_DB_URL }} >> ./src/main/resources/application-prod.properties - echo ${{ secrets.DATASOURCE_DB_USERNAME }} >> ./src/main/resources/application-prod.properties - echo ${{ secrets.DATASOURCE_DB_PASSWORD }} >> ./src/main/resources/application-prod.properties + echo "spring.datasource.url=jdbc:mysql://${{ secrets.DATASOURCE_DB_URL }}:3306/kkijuk?useSSL=false&characterEncoding=UTF-8&serverTimezone=Asia/Seoul" >> ./src/main/resources/application-prod.properties + echo "spring.datasource.username=${{ secrets.DATASOURCE_DB_USERNAME }}" >> ./src/main/resources/application-prod.properties + echo "spring.datasource.password=${{ secrets.DATASOURCE_DB_PASSWORD }}" >> ./src/main/resources/application-prod.properties - name: Build with Gradle Wrapper run: ./gradlew build diff --git a/.gitignore b/.gitignore index 2bff8a41..e5df7fa2 100644 --- a/.gitignore +++ b/.gitignore @@ -35,5 +35,4 @@ out/ ### VS Code ### .vscode/ - .DS_Store \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 614ea307..e68109ba 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ FROM openjdk:17-alpine ARG JAR_FILE=build/libs/*.jar COPY ${JAR_FILE} app.jar -ENTRYPOINT ["java", "-jar", "/app.jar", "--spring.profiles.active=prod "] \ No newline at end of file +ENTRYPOINT ["java", "-jar", "-Dspring.profiles.active=prod", "/app.jar"] diff --git a/build.gradle b/build.gradle index 08740696..6ddcb097 100644 --- a/build.gradle +++ b/build.gradle @@ -25,7 +25,7 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' - compileOnly 'org.projectlombok:lombok' + compileOnly 'org.projectlombok:lombok' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' 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..eee72568 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/common/controller/ExceptionControllerAdvice.java @@ -0,0 +1,27 @@ +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.exception.ReviewRecruitNotMatchException; +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()); + } + + @ResponseStatus(HttpStatus.FORBIDDEN) + @ExceptionHandler(ReviewRecruitNotMatchException.class) + public ErrorResponse ReviewRecruitMatchException(ReviewRecruitNotMatchException 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/exception/ReviewRecruitNotMatchException.java b/src/main/java/umc/kkijuk/server/common/domian/exception/ReviewRecruitNotMatchException.java new file mode 100644 index 00000000..a5ccb222 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/common/domian/exception/ReviewRecruitNotMatchException.java @@ -0,0 +1,7 @@ +package umc.kkijuk.server.common.domian.exception; + +public class ReviewRecruitNotMatchException extends RuntimeException{ + public ReviewRecruitNotMatchException(long recruitId, long reviewId) { + super("Recruit "+ recruitId + "에서 Review " + reviewId + "를 찾을수 없습니다."); + } +} 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/introduce/common/BaseResponse.java b/src/main/java/umc/kkijuk/server/introduce/common/BaseResponse.java new file mode 100644 index 00000000..155a4765 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/common/BaseResponse.java @@ -0,0 +1,30 @@ +package umc.kkijuk.server.introduce.common; + +import lombok.Getter; + +@Getter +public class BaseResponse { + + private final int status; + private String message; + private T data; + + // Response constructor that includes status, message, and data + public BaseResponse(int status, String message, T data) { + this.status = status; + this.message = message; + this.data = data; + } + + // Response constructor that includes status and message only + public BaseResponse(int status, String message) { + this.status = status; + this.message = message; + } + + // Response constructor that includes status and data only + public BaseResponse(int status, T data) { + this.status = status; + this.data = data; + } +} diff --git a/src/main/java/umc/kkijuk/server/introduce/controller/IntroduceController.java b/src/main/java/umc/kkijuk/server/introduce/controller/IntroduceController.java new file mode 100644 index 00000000..0fe7a02c --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/controller/IntroduceController.java @@ -0,0 +1,4 @@ +package umc.kkijuk.server.introduce.controller; + +public class IntroduceController { +} diff --git a/src/main/java/umc/kkijuk/server/introduce/controller/MasterIntroduceController.java b/src/main/java/umc/kkijuk/server/introduce/controller/MasterIntroduceController.java new file mode 100644 index 00000000..b742a926 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/controller/MasterIntroduceController.java @@ -0,0 +1,79 @@ +package umc.kkijuk.server.introduce.controller; + +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import umc.kkijuk.server.introduce.common.BaseResponse; +import umc.kkijuk.server.introduce.domain.MasterIntroduce; +import umc.kkijuk.server.introduce.dto.MasterIntroduceReqDto; +import umc.kkijuk.server.introduce.dto.MasterIntroduceResDto; +import umc.kkijuk.server.introduce.error.BaseErrorResponse; +import umc.kkijuk.server.introduce.error.BaseException; +import umc.kkijuk.server.introduce.service.MasterIntroduceService; + +import java.util.List; + +@Tag(name = "master", description = "마스터 자기소개서 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/history/intro/master") +public class MasterIntroduceController { + private final MasterIntroduceService masterIntroduceService; + + @PostMapping + public ResponseEntity save(@RequestBody MasterIntroduceReqDto masterIntroduceReqDto){ + try { + MasterIntroduceResDto masterIntroduceResDto = masterIntroduceService.saveMasterIntro(masterIntroduceReqDto); + return ResponseEntity + .status(HttpStatus.OK) + .body(new BaseResponse<>(HttpStatus.OK.value(), "마스터 자기소개서 생성 완료", masterIntroduceResDto)); + } catch (BaseException e) { + return ResponseEntity + .status(e.getCode()) + .body(new BaseErrorResponse(e.getCode(), e.getMessage())); + } catch (Exception e) { + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new BaseErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Server error")); + } + } + + @GetMapping + public ResponseEntity get(){ + try { + List masterIntroduce = masterIntroduceService.getMasterIntro(); + return ResponseEntity + .status(HttpStatus.OK) + .body(new BaseResponse<>(HttpStatus.OK.value(), "마스터 자기소개서 조회 완료", masterIntroduce)); + } catch (BaseException e) { + return ResponseEntity + .status(e.getCode()) + .body(new BaseErrorResponse(e.getCode(), e.getMessage())); + } catch (Exception e) { + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new BaseErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Server error")); + } + } + + @PatchMapping + public ResponseEntity update(Long id, @RequestBody MasterIntroduceReqDto masterIntroduceReqDto){ + try { + MasterIntroduceResDto masterIntroduceResDto = masterIntroduceService.updateMasterIntro(id, masterIntroduceReqDto); + return ResponseEntity + .status(HttpStatus.OK) + .body(new BaseResponse<>(HttpStatus.OK.value(), "마스터 자기소개서 수정 완료", masterIntroduceResDto)); + } catch (BaseException e) { + return ResponseEntity + .status(e.getCode()) + .body(new BaseErrorResponse(e.getCode(), e.getMessage())); + } catch (Exception e) { + return ResponseEntity + .status(HttpStatus.INTERNAL_SERVER_ERROR) + .body(new BaseErrorResponse(HttpStatus.INTERNAL_SERVER_ERROR.value(), "Server error")); + } + } + +} diff --git a/src/main/java/umc/kkijuk/server/introduce/domain/Introduce.java b/src/main/java/umc/kkijuk/server/introduce/domain/Introduce.java new file mode 100644 index 00000000..38dde476 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/domain/Introduce.java @@ -0,0 +1,15 @@ +package umc.kkijuk.server.introduce.domain; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.Getter; + +@Entity +@Table(name="introduce") +@Getter +public class Introduce { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + +} diff --git a/src/main/java/umc/kkijuk/server/introduce/domain/IntroduceRepository.java b/src/main/java/umc/kkijuk/server/introduce/domain/IntroduceRepository.java new file mode 100644 index 00000000..d5b690b8 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/domain/IntroduceRepository.java @@ -0,0 +1,6 @@ +package umc.kkijuk.server.introduce.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface IntroduceRepository extends JpaRepository { +} diff --git a/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduce.java b/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduce.java new file mode 100644 index 00000000..0ddb65e5 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduce.java @@ -0,0 +1,57 @@ +package umc.kkijuk.server.introduce.domain; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; +import org.springframework.data.annotation.CreatedDate; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Date; + +@Entity +@Table(name = "master_introduce") +@Getter +@NoArgsConstructor +public class MasterIntroduce { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Size(max = 24) + private String oneLiner; + + private String subTitle; + private String content; + + @CreationTimestamp + private LocalDateTime created_at; + + @UpdateTimestamp + private LocalDateTime updated_at; + + @Builder + public MasterIntroduce(String oneLiner, String subTitle, String content) { + this.oneLiner = oneLiner; + this.subTitle = subTitle; + this.content = content; + } + + public String getUpdated_at() { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"); + return updated_at != null ? updated_at.format(formatter) : null; + } + + public void update(String oneLiner, String subTitle, String content) { + this.oneLiner = oneLiner; + this.subTitle = subTitle; + this.content = content; + } + +} diff --git a/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduceRepository.java b/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduceRepository.java new file mode 100644 index 00000000..c7001108 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/domain/MasterIntroduceRepository.java @@ -0,0 +1,6 @@ +package umc.kkijuk.server.introduce.domain; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface MasterIntroduceRepository extends JpaRepository { +} diff --git a/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceReqDto.java b/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceReqDto.java new file mode 100644 index 00000000..14433137 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceReqDto.java @@ -0,0 +1,14 @@ +package umc.kkijuk.server.introduce.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; + +@Builder +@AllArgsConstructor +@Getter +public class MasterIntroduceReqDto { + private String oneLiner; + private String subTitle; + private String content; +} diff --git a/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceResDto.java b/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceResDto.java new file mode 100644 index 00000000..3ef6139a --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/dto/MasterIntroduceResDto.java @@ -0,0 +1,24 @@ +package umc.kkijuk.server.introduce.dto; + +import lombok.*; +import umc.kkijuk.server.introduce.domain.MasterIntroduce; + +import java.time.LocalDateTime; + +@Getter +@Setter +public class MasterIntroduceResDto { + private String oneLiner; + private String subTitle; + private String content; + private String updatedAt; + + @Builder + public MasterIntroduceResDto(MasterIntroduce masterIntroduce) { + this.oneLiner = masterIntroduce.getOneLiner(); + this.subTitle = masterIntroduce.getSubTitle(); + this.content = masterIntroduce.getContent(); + this.updatedAt = masterIntroduce.getUpdated_at(); + } + +} diff --git a/src/main/java/umc/kkijuk/server/introduce/error/BaseErrorResponse.java b/src/main/java/umc/kkijuk/server/introduce/error/BaseErrorResponse.java new file mode 100644 index 00000000..a2e015c4 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/error/BaseErrorResponse.java @@ -0,0 +1,18 @@ +package umc.kkijuk.server.introduce.error; +import lombok.Getter; + +@Getter +public class BaseErrorResponse { + private final int status; + private final String message; + + public BaseErrorResponse(int status, String message) { + this.status = status; + this.message = message; + } + + public BaseErrorResponse(BaseException baseException) { + this.status = baseException.getCode(); + this.message = baseException.getMessage(); + } +} diff --git a/src/main/java/umc/kkijuk/server/introduce/error/BaseException.java b/src/main/java/umc/kkijuk/server/introduce/error/BaseException.java new file mode 100644 index 00000000..a39c5c37 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/error/BaseException.java @@ -0,0 +1,16 @@ +package umc.kkijuk.server.introduce.error; + +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BaseException extends RuntimeException{ + private final int code; + private final String message; + + public BaseException(int code, String message) { + this.code = code; + this.message = message; + } +} diff --git a/src/main/java/umc/kkijuk/server/introduce/error/GlobalExceptionHandler.java b/src/main/java/umc/kkijuk/server/introduce/error/GlobalExceptionHandler.java new file mode 100644 index 00000000..275b0914 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/error/GlobalExceptionHandler.java @@ -0,0 +1,21 @@ +package umc.kkijuk.server.introduce.error; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; // 여기에 추가 + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(Exception.class) + public ResponseEntity handleException(Exception e) { + BaseException exception = new BaseException(HttpStatus.INTERNAL_SERVER_ERROR.value(), e.getMessage()); + return ResponseEntity.status(exception.getCode()).body(new BaseErrorResponse(exception)); + } + + @ExceptionHandler(BaseException.class) + public ResponseEntity handleBaseException(BaseException e) { + return ResponseEntity.status(e.getCode()).body(new BaseErrorResponse(e)); + } +} diff --git a/src/main/java/umc/kkijuk/server/introduce/service/IntroduceService.java b/src/main/java/umc/kkijuk/server/introduce/service/IntroduceService.java new file mode 100644 index 00000000..bef31602 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/service/IntroduceService.java @@ -0,0 +1,4 @@ +package umc.kkijuk.server.introduce.service; + +public class IntroduceService { +} diff --git a/src/main/java/umc/kkijuk/server/introduce/service/MasterIntroduceService.java b/src/main/java/umc/kkijuk/server/introduce/service/MasterIntroduceService.java new file mode 100644 index 00000000..974b8987 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/introduce/service/MasterIntroduceService.java @@ -0,0 +1,54 @@ +package umc.kkijuk.server.introduce.service; + +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.stereotype.Service; +import umc.kkijuk.server.introduce.domain.MasterIntroduce; +import umc.kkijuk.server.introduce.domain.MasterIntroduceRepository; +import umc.kkijuk.server.introduce.dto.MasterIntroduceReqDto; +import umc.kkijuk.server.introduce.dto.MasterIntroduceResDto; +import umc.kkijuk.server.introduce.error.BaseException; + +import java.util.List; + +@RequiredArgsConstructor +@Service +public class MasterIntroduceService{ + private final MasterIntroduceRepository MasterIntroduceRepository; + private final MasterIntroduceRepository masterIntroduceRepository; + + @Transactional + public MasterIntroduceResDto saveMasterIntro(MasterIntroduceReqDto masterIntroduceReqDto) throws Exception{ + if( MasterIntroduceRepository.findAll().stream().count()>0 ){ + throw new BaseException(HttpStatus.CONFLICT.value(), "이미 마스터 자기소개가 존재합니다"); + } + + MasterIntroduce masterIntroduce=MasterIntroduce.builder() + .oneLiner(masterIntroduceReqDto.getOneLiner()) + .content(masterIntroduceReqDto.getContent()) + .subTitle(masterIntroduceReqDto.getSubTitle()) + .build(); + + MasterIntroduceRepository.save(masterIntroduce); + + return new MasterIntroduceResDto(masterIntroduce); + } + + @Transactional + public List getMasterIntro(){ + return MasterIntroduceRepository.findAll(); + } + + @Transactional + public MasterIntroduceResDto updateMasterIntro(Long id, MasterIntroduceReqDto masterIntroduceReqDto) throws Exception{ + MasterIntroduce masterIntroduce=masterIntroduceRepository.findById(id) + .orElseThrow(() -> new BaseException(HttpStatus.NOT_FOUND.value(), "아이디를 다시 확인해주세요")); + + masterIntroduce.update(masterIntroduceReqDto.getOneLiner(), + masterIntroduceReqDto.getSubTitle(), + masterIntroduceReqDto.getContent()); + + return new MasterIntroduceResDto(masterIntroduce); + } +} diff --git a/src/main/java/umc/kkijuk/server/member/controller/MemberController.java b/src/main/java/umc/kkijuk/server/member/controller/MemberController.java new file mode 100644 index 00000000..0463df36 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/member/controller/MemberController.java @@ -0,0 +1,13 @@ +package umc.kkijuk.server.member.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import umc.kkijuk.server.member.service.MemberService; + +@Controller +@RequiredArgsConstructor +public class MemberController { + + private final MemberService memberService; + +} diff --git a/src/main/java/umc/kkijuk/server/member/converter/StringListConverter.java b/src/main/java/umc/kkijuk/server/member/converter/StringListConverter.java new file mode 100644 index 00000000..21180c3e --- /dev/null +++ b/src/main/java/umc/kkijuk/server/member/converter/StringListConverter.java @@ -0,0 +1,33 @@ +package umc.kkijuk.server.member.converter; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.persistence.AttributeConverter; +import jakarta.persistence.Converter; + +import java.util.List; + +@Converter +public class StringListConverter implements AttributeConverter, String> { + + private final ObjectMapper mapper = new ObjectMapper(); + + + @Override + public String convertToDatabaseColumn(List entityData) { + try { + return mapper.writeValueAsString(entityData); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } + + @Override + public List convertToEntityAttribute(String dbData) { + try { + return mapper.readValue(dbData, List.class); + } catch (JsonProcessingException e) { + throw new RuntimeException(e); + } + } +} \ No newline at end of file diff --git a/src/main/java/umc/kkijuk/server/member/domain/Member.java b/src/main/java/umc/kkijuk/server/member/domain/Member.java new file mode 100644 index 00000000..2628beb1 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/member/domain/Member.java @@ -0,0 +1,58 @@ +package umc.kkijuk.server.member.domain; + +import jakarta.persistence.*; +import jakarta.validation.constraints.NotNull; +import lombok.*; +import umc.kkijuk.server.member.converter.StringListConverter; + +import java.time.LocalDate; +import java.util.List; +import java.util.ArrayList; + +@Entity +@Builder +@Getter @Setter +@NoArgsConstructor +@AllArgsConstructor +public class Member { + + @Id @GeneratedValue + @Column(name = "member_id") + private Long id; + + @NotNull + private String email; + + @NotNull + private String name; + + @NotNull + private String phoneNumber; + + @NotNull + private LocalDate birthDate; + + @NotNull + private String password; + + @Convert(converter = StringListConverter.class) + private List field = new ArrayList<>(); + + @NotNull + private Boolean marketingAgree; + + @NotNull + @Enumerated(EnumType.STRING) + private State userState; + + public Member(String email, String name, String phoneNumber, LocalDate birthDate, String password, Boolean marketingAgree, State userState) { + this.email = email; + this.name = name; + this.phoneNumber = phoneNumber; + this.birthDate = birthDate; + this.password = password; + this.marketingAgree = marketingAgree; + this.userState = userState; + } +} + diff --git a/src/main/java/umc/kkijuk/server/member/domain/State.java b/src/main/java/umc/kkijuk/server/member/domain/State.java new file mode 100644 index 00000000..d20c769e --- /dev/null +++ b/src/main/java/umc/kkijuk/server/member/domain/State.java @@ -0,0 +1,6 @@ +package umc.kkijuk.server.member.domain; + +public enum State { + ACTIVATE , + INACTIVATE +} diff --git a/src/main/java/umc/kkijuk/server/member/repository/MemberJpaRepository.java b/src/main/java/umc/kkijuk/server/member/repository/MemberJpaRepository.java new file mode 100644 index 00000000..25100fd3 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/member/repository/MemberJpaRepository.java @@ -0,0 +1,10 @@ +package umc.kkijuk.server.member.repository; + +import org.springframework.data.jpa.repository.JpaRepository; +import umc.kkijuk.server.member.domain.Member; + +import java.util.Optional; + +public interface MemberJpaRepository extends JpaRepository{ + Optional findById(Long id); +} diff --git a/src/main/java/umc/kkijuk/server/member/service/MemberService.java b/src/main/java/umc/kkijuk/server/member/service/MemberService.java new file mode 100644 index 00000000..5b6bd96e --- /dev/null +++ b/src/main/java/umc/kkijuk/server/member/service/MemberService.java @@ -0,0 +1,28 @@ +package umc.kkijuk.server.member.service; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import umc.kkijuk.server.member.domain.Member; +import umc.kkijuk.server.member.repository.MemberJpaRepository; +import umc.kkijuk.server.recruit.domain.Recruit; +import umc.kkijuk.server.recruit.infrastructure.RecruitEntity; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class MemberService { + private final MemberJpaRepository memberJpaRepository; + + public Member findOne(Long memberId) { + return memberJpaRepository.findById(memberId).get(); + } + + + //아직 인터페이스 구현 x, 추후에 구현 후 MemberService -> MemberServiceImpl로 수정 예정. + public Long join(Member member) { + memberJpaRepository.save(member); + return member.getId(); + } + +} 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 2d3d9287..41d2b7ce 100644 --- a/src/main/java/umc/kkijuk/server/recruit/controller/RecruitController.java +++ b/src/main/java/umc/kkijuk/server/recruit/controller/RecruitController.java @@ -1,16 +1,27 @@ package umc.kkijuk.server.recruit.controller; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import lombok.*; +import org.springframework.format.annotation.DateTimeFormat; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; 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.controller.response.RecruitListByEndDateResponse; +import umc.kkijuk.server.recruit.controller.response.RecruitListByEndTimeAfterResponse; +import umc.kkijuk.server.recruit.domain.*; +import umc.kkijuk.server.recruit.controller.response.RecruitIdResponse; +import umc.kkijuk.server.review.controller.port.ReviewService; +import umc.kkijuk.server.review.domain.Review; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; @Tag(name = "recruit", description = "모집 공고 API") @RestController @@ -18,14 +29,103 @@ @RequestMapping("/recruit") public class RecruitController { private final RecruitService recruitService; + private final ReviewService reviewService; + @Operation( + summary = "지원 공고 생성", + description = "주어진 정보를 바탕으로 지원 공고 데이터를 생성합니다.") @PostMapping - public ResponseEntity create(@RequestBody @Valid RecruitCreateDto recruitCreateDto) { + public ResponseEntity create(@RequestBody @Valid RecruitCreate recruitCreate) { LoginUser loginUser = LoginUser.get(); - Recruit recruit = recruitService.create(recruitCreateDto); + Recruit recruit = recruitService.create(recruitCreate); return ResponseEntity .status(HttpStatus.CREATED) - .body(RecruitResponse.from(recruit)); + .body(RecruitIdResponse.from(recruit)); + } + + @Operation( + summary = "지원 공고 수정", + description = "주어진 정보를 바탕으로 지원 공고 데이터를 수정합니다.") + @Parameter(name = "recruitId", description = "지원 공고 ID", example = "1") + @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()); + } + + @Operation( + summary = "지원 공고 상태 수정", + description = "다음 중 주어진 상태로 지원 공고의 상태를 수정합니다." + " [UNAPPLIED / PLANNED / APPLYING / REJECTED / ACCEPTED]") + @Parameter(name = "recruitId", description = "지원 공고 ID", example = "1") + @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()); + } + + @Operation( + summary = "지원 공고 삭제", + description = "지원 공고 ID에 해당 하는 공고를 삭제합니다") + @Parameter(name = "recruitId", description = "지원 공고 ID", example = "1") + @DeleteMapping("/{recruitId}") + public ResponseEntity delete(@PathVariable long recruitId) { + LoginUser loginUser = LoginUser.get(); + return ResponseEntity + .ok() + .body(recruitService.disable(recruitId).getId()); + } + + @Operation( + summary = "지원 공고 상세", + description = "지원 공고 ID에 해당하는 공고의 상세 정보를 요청합니다.") + @Parameter(name = "recruitId", description = "지원 공고 ID", example = "1") + @GetMapping("/{recruitId}") + public ResponseEntity getRecruitInfo(@PathVariable long recruitId) { + LoginUser loginUser = LoginUser.get(); + Recruit recruit = recruitService.getById(recruitId); + List reviews = reviewService.findAllByRecruitId(recruitId); + + return ResponseEntity + .ok() + .body(RecruitInfoResponse.from(recruit, reviews)); + } + + @Operation( + summary = "지원 공고 목록 (특정 날짜 이후)", + description = "주어진 날짜에 마감 종료되는 지원 공고들의 목록을 요청합니다.") + @GetMapping("/list/end") + public ResponseEntity findAllRecruitListByEndTime( + @Parameter(name = "date", description = "지원 공고 마감 날짜", example = "2024-07-20") + @RequestParam LocalDate date) { + List recruits = recruitService.findAllByEndTime(date); + return ResponseEntity + .ok() + .body(RecruitListByEndDateResponse.from(recruits)); + } + + @Operation( + summary = "지원 공고 목록 (특정 시간 이후)", + description = "주어진 시간 이후 마감 종료되는 지원 공고들의 목록을 요청합니다.") + @GetMapping("/list/after") + public ResponseEntity findAllRecruitListByEndTimeAfterNow( + @Parameter(name = "time", description = "이 시간 이후에 마감되는 공고를 요청", example = "2024-07-20 10:30") + @DateTimeFormat(pattern = "yyyy-MM-dd HH:mm") + @RequestParam LocalDateTime time) { + List recruits = recruitService.findAllByEndTimeAfter(time); + return ResponseEntity + .ok() + .body(RecruitListByEndTimeAfterResponse.from(recruits)); } + // 멤버 추가시 추가 +// @GetMapping("/list/valid") +// public ResponseEntity findValidRecruit() { +// +// } } 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..8526d14e 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 @@ -1,8 +1,26 @@ package umc.kkijuk.server.recruit.controller.port; import umc.kkijuk.server.recruit.domain.Recruit; -import umc.kkijuk.server.recruit.domain.RecruitCreateDto; +import umc.kkijuk.server.recruit.domain.RecruitCreate; +import umc.kkijuk.server.recruit.domain.RecruitStatusUpdate; +import umc.kkijuk.server.recruit.domain.RecruitUpdate; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; public interface RecruitService { - Recruit create(RecruitCreateDto recruitCreateDto); + Recruit create(RecruitCreate recruitCreate); + + Recruit update(Long recruitId, RecruitUpdate recruitUpdate); + + Recruit getById(long id); + + Recruit updateStatus(long recruitId, RecruitStatusUpdate recruitStatusUpdate); + + Recruit disable(long recruitId); + + List findAllByEndTime(LocalDate date); + + List findAllByEndTimeAfter(LocalDateTime endTime); } diff --git a/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitByEndDate.java b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitByEndDate.java new file mode 100644 index 00000000..b1c38e79 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitByEndDate.java @@ -0,0 +1,39 @@ +package umc.kkijuk.server.recruit.controller.response; + +import lombok.Builder; +import lombok.Getter; +import umc.kkijuk.server.recruit.domain.Recruit; + +import java.time.LocalDate; +import java.util.*; + +@Getter +@Builder +public class RecruitByEndDate { + private final LocalDate endDate; + private int count; + private List recruits; + + public static List from(List recruits) { + List outputs = new ArrayList<>(); + recruits.forEach(recruit -> { + Optional o = outputs.stream() + .filter(item -> recruit.getEndTime().toLocalDate().isEqual(item.getEndDate())) + .findFirst(); + o.ifPresent(recruitByEndDate -> { + recruitByEndDate.recruits.add(RecruitListResponse.from(recruit)); + recruitByEndDate.count++; + }); + if (o.isEmpty()) { + outputs.add(RecruitByEndDate.builder() + .endDate(recruit.getEndTime().toLocalDate()) + .count(1) + .recruits(new ArrayList<>(Arrays.asList(RecruitListResponse.from(recruit)))) + .build()); + } + }); + + outputs.sort(Comparator.comparing(RecruitByEndDate::getEndDate)); + return outputs; + } +} 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..ec225871 --- /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.review.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/RecruitListByEndDateResponse.java b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitListByEndDateResponse.java new file mode 100644 index 00000000..eedad787 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitListByEndDateResponse.java @@ -0,0 +1,21 @@ +package umc.kkijuk.server.recruit.controller.response; + +import lombok.Builder; +import lombok.Getter; +import umc.kkijuk.server.recruit.domain.Recruit; + +import java.util.List; + +@Getter +@Builder +public class RecruitListByEndDateResponse { + private final int count; + private final List recruits; + + public static RecruitListByEndDateResponse from(List recruits) { + return RecruitListByEndDateResponse.builder() + .count(recruits.size()) + .recruits(recruits.stream().map(RecruitListResponse::from).toList()) + .build(); + } +} diff --git a/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitListByEndTimeAfterResponse.java b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitListByEndTimeAfterResponse.java new file mode 100644 index 00000000..b26ac375 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitListByEndTimeAfterResponse.java @@ -0,0 +1,21 @@ +package umc.kkijuk.server.recruit.controller.response; + +import lombok.Builder; +import lombok.Getter; +import umc.kkijuk.server.recruit.domain.Recruit; + +import java.util.List; + +@Getter +@Builder +public class RecruitListByEndTimeAfterResponse { + private final int totalCount; + private final List outputs; + + public static RecruitListByEndTimeAfterResponse from(List recruits) { + return RecruitListByEndTimeAfterResponse.builder() + .totalCount(recruits.size()) + .outputs(RecruitByEndDate.from(recruits)) + .build(); + } +} diff --git a/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitListResponse.java b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitListResponse.java new file mode 100644 index 00000000..4af291df --- /dev/null +++ b/src/main/java/umc/kkijuk/server/recruit/controller/response/RecruitListResponse.java @@ -0,0 +1,26 @@ +package umc.kkijuk.server.recruit.controller.response; + +import lombok.Builder; +import lombok.Getter; +import umc.kkijuk.server.recruit.domain.Recruit; +import umc.kkijuk.server.recruit.domain.RecruitStatus; + +import java.util.List; + +@Getter +@Builder +public class RecruitListResponse { + private final Long recruitId; + private final String title; + private final RecruitStatus status; + private final List tag; + + public static RecruitListResponse from(Recruit recruit) { + return RecruitListResponse.builder() + .recruitId(recruit.getId()) + .title(recruit.getTitle()) + .status(recruit.getStatus()) + .tag(recruit.getTags()) + .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..6ccfc64a --- /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.review.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..3d2de6d6 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,16 +19,62 @@ public class Recruit { private final LocalDate applyDate; private final List tags; private final String link; + private final boolean active; + private final LocalDateTime disabledTime; - public static Recruit from(RecruitCreateDto recruitCreateDto) { + public static Recruit from(RecruitCreate recruitCreate) { return Recruit.builder() - .title(recruitCreateDto.getTitle()) - .status(recruitCreateDto.getStatus()) - .startTime(recruitCreateDto.getStartTime()) - .endTime(recruitCreateDto.getEndTime()) - .applyDate(recruitCreateDto.getApplyDate()) - .tags(recruitCreateDto.getTags()) - .link(recruitCreateDto.getLink()) + .title(recruitCreate.getTitle()) + .status(recruitCreate.getStatus()) + .startTime(recruitCreate.getStartTime()) + .endTime(recruitCreate.getEndTime()) + .applyDate(recruitCreate.getApplyDate()) + .tags(recruitCreate.getTags() != null ? recruitCreate.getTags() : new ArrayList<>()) + .link(recruitCreate.getLink()) + .active(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()) + .active(this.active) + .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()) + .active(this.active) + .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()) + .active(false) + .disabledTime(LocalDateTime.now()) .build(); } } diff --git a/src/main/java/umc/kkijuk/server/recruit/domain/RecruitCreateDto.java b/src/main/java/umc/kkijuk/server/recruit/domain/RecruitCreate.java similarity index 98% rename from src/main/java/umc/kkijuk/server/recruit/domain/RecruitCreateDto.java rename to src/main/java/umc/kkijuk/server/recruit/domain/RecruitCreate.java index b770f9ed..f09c45dd 100644 --- a/src/main/java/umc/kkijuk/server/recruit/domain/RecruitCreateDto.java +++ b/src/main/java/umc/kkijuk/server/recruit/domain/RecruitCreate.java @@ -14,7 +14,7 @@ @Builder @AllArgsConstructor @NoArgsConstructor(access = AccessLevel.PROTECTED) -public class RecruitCreateDto { +public class RecruitCreate { @NotBlank(message = "공고 제목은 필수 입력 항목입니다.") @Schema(description = "공고 제목", example = "[00] 공고제목", type = "string") private String title; 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/infrastructure/RecruitEntity.java b/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitEntity.java index cd741157..4e9965dc 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 active; + 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.active = recruit.isActive(); + recruitEntity.disabledTime = recruit.getDisabledTime(); return recruitEntity; } @@ -60,6 +64,8 @@ public Recruit toModel() { .applyDate(applyDate) .tags(tags) .link(link) + .active(active) + .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..eec30492 100644 --- a/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitJpaRepository.java +++ b/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitJpaRepository.java @@ -1,6 +1,19 @@ package umc.kkijuk.server.recruit.infrastructure; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; public interface RecruitJpaRepository extends JpaRepository { + Optional findByIdAndActive(long id, boolean active); + + @Query(value = "SELECT * FROM recruit as e WHERE DATE(e.end_time) = :endTime AND e.active = :isActive", nativeQuery = true) + List findAllByEndDateAndActive(@Param("endTime") LocalDate endTime, @Param("isActive") boolean isActive); + + List findAllByEndTimeAfterAndActive(LocalDateTime endTime, boolean active); } 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..4a47f6e9 100644 --- a/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitRepositoryImpl.java +++ b/src/main/java/umc/kkijuk/server/recruit/infrastructure/RecruitRepositoryImpl.java @@ -2,9 +2,13 @@ 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; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; @Repository @@ -21,4 +25,27 @@ 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 active) { + return recruitJpaRepository.findByIdAndActive(id, active).map(RecruitEntity::toModel); + } + + @Override + public List findAllByEndDateAndIsActive(LocalDate endTime, boolean active) { + return recruitJpaRepository.findAllByEndDateAndActive(endTime, active) + .stream().map(RecruitEntity::toModel).toList(); + } + + @Override + public List findAllByEndTimeAfterAndIsActive(LocalDateTime endTime, boolean active) { + return recruitJpaRepository.findAllByEndTimeAfterAndActive(endTime,active) + .stream().map(RecruitEntity::toModel).toList(); + } } 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..8b41832e 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,74 @@ 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.RecruitCreate; +import umc.kkijuk.server.recruit.domain.RecruitStatusUpdate; +import umc.kkijuk.server.recruit.domain.RecruitUpdate; import umc.kkijuk.server.recruit.service.port.RecruitRepository; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; + @Service +@Builder @RequiredArgsConstructor public class RecruitServiceImpl implements RecruitService { private final RecruitRepository recruitRepository; @Override - public Recruit create(RecruitCreateDto recruitCreateDto) { - Recruit recruit = Recruit.from(recruitCreateDto); + public Recruit getById(long id) { + return recruitRepository.findByIdAndIsActive(id, true) + .orElseThrow(() -> new ResourceNotFoundException("Recruit", id)); + } + + @Override + @Transactional + public Recruit create(RecruitCreate recruitCreate) { + Recruit recruit = Recruit.from(recruitCreate); + 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); } + + @Override + @Transactional + public List findAllByEndTime(LocalDate date) { + return recruitRepository.findAllByEndDateAndIsActive(date, true); + } + + @Override + @Transactional + public List findAllByEndTimeAfter(LocalDateTime endTime) { + return recruitRepository.findAllByEndTimeAfterAndIsActive(endTime, true); + } } 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..9955eca4 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 @@ -2,9 +2,18 @@ import umc.kkijuk.server.recruit.domain.Recruit; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; import java.util.Optional; public interface RecruitRepository { Optional findById(Long id); Recruit save(Recruit recruit); + Recruit getById(long id); + Optional findByIdAndIsActive(long id, boolean active); + + List findAllByEndDateAndIsActive(LocalDate endTime, boolean active); + + List findAllByEndTimeAfterAndIsActive(LocalDateTime endTime, boolean b); } diff --git a/src/main/java/umc/kkijuk/server/review/controller/ReviewController.java b/src/main/java/umc/kkijuk/server/review/controller/ReviewController.java new file mode 100644 index 00000000..7f82ccd4 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/review/controller/ReviewController.java @@ -0,0 +1,79 @@ +package umc.kkijuk.server.review.controller; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; +import umc.kkijuk.server.recruit.controller.port.RecruitService; +import umc.kkijuk.server.recruit.domain.Recruit; +import umc.kkijuk.server.review.controller.port.ReviewService; +import umc.kkijuk.server.review.controller.response.ReviewIdResponse; +import umc.kkijuk.server.review.domain.Review; +import umc.kkijuk.server.review.domain.ReviewCreate; +import umc.kkijuk.server.review.domain.ReviewUpdate; + +@Tag(name = "review", description = "모집 공고 후기 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/recruit/{recruitId}") +public class ReviewController { + private final ReviewService reviewService; + private final RecruitService recruitService; + + @Operation( + summary = "지원 공고 후기 추가", + description = "주어진 지원 공고에 후기를 생성합니다") + @Parameter(name = "recruitId", description = "지원 공고 ID", example = "1") + @PostMapping("/review") + public ResponseEntity create( + @PathVariable Long recruitId, + @RequestBody @Valid ReviewCreate reviewCreate + ) { + Recruit recruit = recruitService.getById(recruitId); + Review review = reviewService.create(recruit, reviewCreate); + + return ResponseEntity + .ok() + .body(ReviewIdResponse.from(review)); + } + + @Operation( + summary = "지원 공고 후기 수정", + description = "주어진 지원 공고 후기를 수정합니다") + @Parameter(name = "recruitId", description = "지원 공고 ID", example = "1") + @Parameter(name = "reviewId", description = "지원 공고 후기 ID", example = "1") + @PutMapping("/review/{reviewId}") + public ResponseEntity update( + @PathVariable Long recruitId, + @PathVariable Long reviewId, + @RequestBody @Valid ReviewUpdate reviewUpdate + ) { + Recruit recruit = recruitService.getById(recruitId); + Review review = reviewService.update(recruit, reviewId, reviewUpdate); + + return ResponseEntity + .ok() + .body(ReviewIdResponse.from(review)); + } + + @Operation( + summary = "지원 공고 후기 삭제", + description = "주어진 지원 공고 후기를 삭제합니다") + @Parameter(name = "recruitId", description = "지원 공고 ID", example = "1") + @Parameter(name = "reviewId", description = "지원 공고 후기 ID", example = "1") + @DeleteMapping("/review/{reviewId}") + public ResponseEntity delete( + @PathVariable Long recruitId, + @PathVariable Long reviewId + ) { + Recruit recruit = recruitService.getById(recruitId); + reviewService.delete(recruit, reviewId); + + return ResponseEntity + .noContent() + .build(); + } +} diff --git a/src/main/java/umc/kkijuk/server/review/controller/port/ReviewService.java b/src/main/java/umc/kkijuk/server/review/controller/port/ReviewService.java new file mode 100644 index 00000000..42e96b06 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/review/controller/port/ReviewService.java @@ -0,0 +1,20 @@ +package umc.kkijuk.server.review.controller.port; + +import umc.kkijuk.server.recruit.domain.Recruit; +import umc.kkijuk.server.review.domain.Review; +import umc.kkijuk.server.review.domain.ReviewCreate; +import umc.kkijuk.server.review.domain.ReviewUpdate; + +import java.util.List; + +public interface ReviewService { + Review create(Recruit recruit, ReviewCreate reviewCreate); + + Review update(Recruit recruit, Long reviewId, ReviewUpdate reviewUpdate); + + List findAllByRecruitId(Long recruitId); + + Review getById(Long reviewId); + + void delete(Recruit recruit, Long reviewId); +} diff --git a/src/main/java/umc/kkijuk/server/review/controller/response/ReviewIdResponse.java b/src/main/java/umc/kkijuk/server/review/controller/response/ReviewIdResponse.java new file mode 100644 index 00000000..17baed60 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/review/controller/response/ReviewIdResponse.java @@ -0,0 +1,17 @@ +package umc.kkijuk.server.review.controller.response; + +import lombok.Builder; +import lombok.Getter; +import umc.kkijuk.server.review.domain.Review; + +@Getter +@Builder +public class ReviewIdResponse { + private Long id; + + public static ReviewIdResponse from(Review review) { + return ReviewIdResponse.builder() + .id(review.getId()) + .build(); + } +} diff --git a/src/main/java/umc/kkijuk/server/review/domain/Review.java b/src/main/java/umc/kkijuk/server/review/domain/Review.java new file mode 100644 index 00000000..1ed880d7 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/review/domain/Review.java @@ -0,0 +1,36 @@ +package umc.kkijuk.server.review.domain; + +import lombok.Builder; +import lombok.Getter; +import umc.kkijuk.server.recruit.domain.Recruit; + +import java.time.LocalDate; + +@Getter +@Builder +public class Review { + private final Long id; + private final Long recruitId; + private final String title; + private final String content; + private final LocalDate date; + + public static Review from(Recruit recruit, ReviewCreate reviewCreate) { + return Review.builder() + .recruitId(recruit.getId()) + .title(reviewCreate.getTitle()) + .content(reviewCreate.getContent()) + .date(reviewCreate.getDate()) + .build(); + } + + public Review update(ReviewUpdate reviewUpdate) { + return Review.builder() + .id(this.id) + .recruitId(this.recruitId) + .title(reviewUpdate.getTitle()) + .content(reviewUpdate.getContent()) + .date(reviewUpdate.getDate()) + .build(); + } +} diff --git a/src/main/java/umc/kkijuk/server/review/domain/ReviewCreate.java b/src/main/java/umc/kkijuk/server/review/domain/ReviewCreate.java new file mode 100644 index 00000000..dd8baaf7 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/review/domain/ReviewCreate.java @@ -0,0 +1,28 @@ +package umc.kkijuk.server.review.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; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ReviewCreate { + + @NotBlank(message = "공고 후기 제목은 필수 입력 항목입니다.") + @Schema(description = "공고 후기 제목", example = "코딩 테스트", type = "string") + private String title; + + @Schema(description = "공고 후기 내용", example = "Dijkstra 알고리즘을 활용하는 문제", type = "string") + private String content; + + @NotNull(message = "날짜는 필수 입력 항목입니다.") + @Schema(description = "날짜", example = "2024-07-21", pattern = "yyyy-MM-dd") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul") + private LocalDate date; +} diff --git a/src/main/java/umc/kkijuk/server/review/domain/ReviewUpdate.java b/src/main/java/umc/kkijuk/server/review/domain/ReviewUpdate.java new file mode 100644 index 00000000..93251a78 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/review/domain/ReviewUpdate.java @@ -0,0 +1,27 @@ +package umc.kkijuk.server.review.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; + +@Getter +@Builder +@AllArgsConstructor +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ReviewUpdate { + @NotBlank(message = "공고 후기 제목은 필수 입력 항목입니다.") + @Schema(description = "변경될 공고 후기 제목", example = "변경될 제목", type = "string") + private String title; + + @Schema(description = "변경될 공고 후기 내용", example = "변경될 내용", type = "string") + private String content; + + @NotNull(message = "날짜는 필수 입력 항목입니다.") + @Schema(description = "날짜", example = "2024-07-23", pattern = "yyyy-MM-dd") + @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd", timezone = "Asia/Seoul") + private LocalDate date; +} diff --git a/src/main/java/umc/kkijuk/server/review/infrastructure/ReviewEntity.java b/src/main/java/umc/kkijuk/server/review/infrastructure/ReviewEntity.java new file mode 100644 index 00000000..e407256f --- /dev/null +++ b/src/main/java/umc/kkijuk/server/review/infrastructure/ReviewEntity.java @@ -0,0 +1,44 @@ +package umc.kkijuk.server.review.infrastructure; + +import jakarta.persistence.*; +import umc.kkijuk.server.review.domain.Review; + +import java.time.LocalDate; + +@Entity +@Table(name = "review") +public class ReviewEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long recruitId; + @Column(nullable = false) + private String title; + private String content; + @Column(nullable = false) + private LocalDate date; + + + public static ReviewEntity from(Review review) { + ReviewEntity reviewEntity = new ReviewEntity(); + reviewEntity.id = review.getId(); + reviewEntity.recruitId = review.getRecruitId(); + reviewEntity.title = review.getTitle(); + reviewEntity.content = review.getContent(); + reviewEntity.date = review.getDate(); + return reviewEntity; + } + + public Review toModel() { + return Review.builder() + .id(id) + .recruitId(recruitId) + .title(title) + .content(content) + .date(date) + .build(); + } +} diff --git a/src/main/java/umc/kkijuk/server/review/infrastructure/ReviewJpaRepository.java b/src/main/java/umc/kkijuk/server/review/infrastructure/ReviewJpaRepository.java new file mode 100644 index 00000000..4bff0327 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/review/infrastructure/ReviewJpaRepository.java @@ -0,0 +1,9 @@ +package umc.kkijuk.server.review.infrastructure; + +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface ReviewJpaRepository extends JpaRepository { + List findAllByRecruitId(Long id); +} diff --git a/src/main/java/umc/kkijuk/server/review/infrastructure/ReviewRepositoryImpl.java b/src/main/java/umc/kkijuk/server/review/infrastructure/ReviewRepositoryImpl.java new file mode 100644 index 00000000..931d13d2 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/review/infrastructure/ReviewRepositoryImpl.java @@ -0,0 +1,42 @@ +package umc.kkijuk.server.review.infrastructure; + +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; +import umc.kkijuk.server.common.domian.exception.ResourceNotFoundException; +import umc.kkijuk.server.review.domain.Review; +import umc.kkijuk.server.review.service.port.ReviewRepository; + +import java.util.List; +import java.util.Optional; + +@Repository +@RequiredArgsConstructor +public class ReviewRepositoryImpl implements ReviewRepository { + private final ReviewJpaRepository reviewJpaRepository; + + @Override + public Review save(Review review) { + return reviewJpaRepository.save(ReviewEntity.from(review)).toModel(); + } + + @Override + public Optional findById(Long id) { + return reviewJpaRepository.findById(id).map(ReviewEntity::toModel); + } + + @Override + public Review getById(Long id) { + return findById(id) + .orElseThrow(() -> new ResourceNotFoundException("review", id)); + } + + @Override + public void delete(Review review) { + reviewJpaRepository.delete(ReviewEntity.from(review)); + } + + @Override + public List findAllByRecruitId(Long id) { + return reviewJpaRepository.findAllByRecruitId(id).stream().map(ReviewEntity::toModel).toList(); + } +} diff --git a/src/main/java/umc/kkijuk/server/review/service/ReviewServiceImpl.java b/src/main/java/umc/kkijuk/server/review/service/ReviewServiceImpl.java new file mode 100644 index 00000000..ed09b999 --- /dev/null +++ b/src/main/java/umc/kkijuk/server/review/service/ReviewServiceImpl.java @@ -0,0 +1,60 @@ +package umc.kkijuk.server.review.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.ReviewRecruitNotMatchException; +import umc.kkijuk.server.recruit.domain.Recruit; +import umc.kkijuk.server.review.controller.port.ReviewService; +import umc.kkijuk.server.review.domain.Review; +import umc.kkijuk.server.review.domain.ReviewCreate; +import umc.kkijuk.server.review.domain.ReviewUpdate; +import umc.kkijuk.server.review.service.port.ReviewRepository; + +import java.util.List; + +@Service +@Builder +@RequiredArgsConstructor +public class ReviewServiceImpl implements ReviewService { + private final ReviewRepository reviewRepository; + + @Override + public List findAllByRecruitId(Long recruitId) { + return reviewRepository.findAllByRecruitId(recruitId); + } + + public Review getById(Long id) { + return reviewRepository.getById(id); + } + + @Override + @Transactional //멤버 + recruit 사이의 인가 예외처리 + public Review create(Recruit recruit, ReviewCreate reviewCreate) { + Review review = Review.from(recruit, reviewCreate); + return reviewRepository.save(review); + } + + @Override + @Transactional + public Review update(Recruit recruit, Long reviewId, ReviewUpdate reviewUpdate) { + Review review = getById(reviewId); + if (!review.getRecruitId().equals(recruit.getId())) { + throw new ReviewRecruitNotMatchException(recruit.getId(), reviewId); + } + + review = review.update(reviewUpdate); + return reviewRepository.save(review); + } + + @Override + public void delete(Recruit recruit, Long reviewId) { + Review review = getById(reviewId); + if (!review.getRecruitId().equals(recruit.getId())) { + throw new ReviewRecruitNotMatchException(recruit.getId(), reviewId); + } + + reviewRepository.delete(review); + } +} diff --git a/src/main/java/umc/kkijuk/server/review/service/port/ReviewRepository.java b/src/main/java/umc/kkijuk/server/review/service/port/ReviewRepository.java new file mode 100644 index 00000000..1e7ea06d --- /dev/null +++ b/src/main/java/umc/kkijuk/server/review/service/port/ReviewRepository.java @@ -0,0 +1,18 @@ +package umc.kkijuk.server.review.service.port; + +import umc.kkijuk.server.review.domain.Review; + +import java.util.List; +import java.util.Optional; + +public interface ReviewRepository { + Review save(Review review); + + Optional findById(Long id); + + Review getById(Long id); + + void delete(Review review); + + List findAllByRecruitId(Long id); +} diff --git a/src/main/resources/application-prod.properties b/src/main/resources/application-prod.properties index 57a5c698..bfdf7cf7 100644 --- a/src/main/resources/application-prod.properties +++ b/src/main/resources/application-prod.properties @@ -7,5 +7,5 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver # spring jpa spring.jpa.database=mysql spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=create -spring.jpa.properties.hibernate.format_sql=true +spring.jpa.hibernate.ddl-auto=update +spring.jpa.properties.hibernate.format_sql=true \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 8fa3a5c6..acf6314b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -10,7 +10,7 @@ spring.datasource.password=testPW # spring jpa spring.jpa.database=mysql spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto= create +spring.jpa.hibernate.ddl-auto=create spring.jpa.properties.hibernate.format_sql=true diff --git a/src/test/java/umc/kkijuk/server/introduce/controller/MasterIntroduceControllerTest.java b/src/test/java/umc/kkijuk/server/introduce/controller/MasterIntroduceControllerTest.java new file mode 100644 index 00000000..b8059ca0 --- /dev/null +++ b/src/test/java/umc/kkijuk/server/introduce/controller/MasterIntroduceControllerTest.java @@ -0,0 +1,98 @@ +package umc.kkijuk.server.introduce.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.transaction.Transactional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.web.server.LocalServerPort; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit.jupiter.SpringExtension; +import org.springframework.test.web.servlet.MockMvc; +import umc.kkijuk.server.introduce.domain.MasterIntroduce; +import umc.kkijuk.server.introduce.domain.MasterIntroduceRepository; +import umc.kkijuk.server.introduce.dto.MasterIntroduceReqDto; +import umc.kkijuk.server.introduce.service.MasterIntroduceService; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@ExtendWith(SpringExtension.class) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc +@Transactional +class MasterIntroduceControllerTest { + @LocalServerPort + private int port; + @Autowired + private MasterIntroduceRepository masterIntroduceRepository; + @Autowired + private MockMvc mockMvc; + @Autowired + private MasterIntroduceService masterIntroduceService; + @Autowired + private ObjectMapper objectMapper; + + @Test + @DisplayName("마스터 자기소개서 생성 테스트") + public void postMaster() throws Exception{ + final String oneLiner= "one-liner-test"; + final String subTitle= "sub-title-test"; + final String content= "content-test"; + + MasterIntroduceReqDto masterIntroduceReqDto= MasterIntroduceReqDto.builder() + .oneLiner(oneLiner) + .subTitle(subTitle) + .content(content) + .build(); + + + mockMvc.perform(post("/history/intro/master") + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(masterIntroduceReqDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.oneLiner").value("one-liner-test")) + .andExpect(jsonPath("$.data.subTitle").value("sub-title-test")) + .andExpect(jsonPath("$.data.content").value("content-test")); + } + + @Test + @DisplayName("마스터 자기소개서 수정 테스트") + public void updateMaster() throws Exception { + final String oneLiner = "one-liner-test"; + final String subTitle = "sub-title-test"; + final String content = "content-test"; + + MasterIntroduce masterIntroduce = masterIntroduceRepository.save(MasterIntroduce.builder() + .oneLiner(oneLiner) + .subTitle(subTitle) + .content(content) + .build()); + + Long id = masterIntroduce.getId(); + + String expectedOneLiner = "one-liner2"; + String expectedSubTitle = "sub-title2"; + String expectedContent = "content2"; + + MasterIntroduceReqDto masterIntroduceReqDto = MasterIntroduceReqDto.builder() + .oneLiner(expectedOneLiner) + .subTitle(expectedSubTitle) + .content(expectedContent) + .build(); + + mockMvc.perform(patch("/history/intro/master") + .param("id", id.toString()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(masterIntroduceReqDto))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.oneLiner").value(expectedOneLiner)) + .andExpect(jsonPath("$.data.subTitle").value(expectedSubTitle)) + .andExpect(jsonPath("$.data.content").value(expectedContent)); + } +} \ No newline at end of file diff --git a/src/test/java/umc/kkijuk/server/member/service/MemberServiceTest.java b/src/test/java/umc/kkijuk/server/member/service/MemberServiceTest.java new file mode 100644 index 00000000..879f0bb3 --- /dev/null +++ b/src/test/java/umc/kkijuk/server/member/service/MemberServiceTest.java @@ -0,0 +1,39 @@ +package umc.kkijuk.server.member.service; + + +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.junit4.SpringRunner; +import org.springframework.transaction.annotation.Transactional; +import umc.kkijuk.server.member.domain.Member; +import umc.kkijuk.server.member.domain.State; +import umc.kkijuk.server.member.repository.MemberJpaRepository; + +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; + + +@SpringBootTest +@Transactional +public class MemberServiceTest { + + @Autowired MemberService memberService; + @Autowired + MemberJpaRepository memberJpaRepository; + + @Test + public void 유저정보db저장및조회() throws Exception { + //given + Member member1 = new Member("asd@naver.com", "홍길동", "010-7444-1768", LocalDate.now(), "passwordTest", Boolean.TRUE, State.ACTIVATE); + //when + Long savedId = memberService.join(member1); + Optional member2 = memberJpaRepository.findById(savedId); + //then + assertEquals(member1, member2.get()); + } +} 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..9562d1b3 --- /dev/null +++ b/src/test/java/umc/kkijuk/server/recruit/mock/FakeRecruitRepository.java @@ -0,0 +1,75 @@ +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.time.LocalDate; +import java.time.LocalDateTime; +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()) + .active(recruit.isActive()) + .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 active) { + return data.stream() + .filter(item -> + item.getId().equals(id) && + item.isActive() == active).findAny(); + } + + @Override + public List findAllByEndDateAndIsActive(LocalDate endTime, boolean active) { + return data.stream() + .filter(item -> endTime.equals(item.getEndTime().toLocalDate()) && + item.isActive() == active).toList(); + } + + @Override + public List findAllByEndTimeAfterAndIsActive(LocalDateTime endTime, boolean active) { + return data.stream() + .filter(item -> item.getEndTime().isAfter(endTime) && + item.isActive() == active).toList(); + } +} 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..572a6f35 --- /dev/null +++ b/src/test/java/umc/kkijuk/server/recruit/service/RecruitServiceTest.java @@ -0,0 +1,291 @@ +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 java.util.List; + +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") + .active(true) + .build(); + + recruitRepository.save(recruit); + } + + @Test + void create_새로운_recruit_만들기() { + //given + RecruitCreate recruitCreate = RecruitCreate.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(recruitCreate); + + //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 create_새로운_recruit_만들기_nullable() { + //given + RecruitCreate recruitCreate = RecruitCreate.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(recruitCreate); + + //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 update_기존_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 update_수정시_없는_리소스로의_요청은_에러() { + //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 updateStatus_status만_수정시_없는_리소스로의_요청은_에러() { + //given + RecruitStatusUpdate recruitStatusUpdate = RecruitStatusUpdate.builder() + .status(RecruitStatus.ACCEPTED).build(); + + //when + //then + assertThatThrownBy( + () -> recruitService.updateStatus(2L, recruitStatusUpdate)).isInstanceOf(ResourceNotFoundException.class); + } + + @Test + void updateStatus_기존_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 disable_기존_recruit_비활성화() { + //given + //when + Recruit result = recruitService.disable(1L); + + //then + assertAll( + () -> assertThat(result.isActive()).isFalse(), + () -> assertThat(result.getDisabledTime()).isNotNull(), + () -> assertThat(result.getDisabledTime()).isBefore(LocalDateTime.now()) + ); + } + + @Test + void disable_없는_유저는_비활성화_할수_없다() { + //given + //when + //then + assertThatThrownBy( + () -> recruitService.disable(1234L)).isInstanceOf(ResourceNotFoundException.class); + } + + @Test + void disable_비활성화된_유저는_getById로_찾을수_없다() { + //given + recruitService.disable(1L); + + //when + //then + assertThatThrownBy( + () -> recruitService.getById(1L)).isInstanceOf(ResourceNotFoundException.class); + } + + @Test + void findAllByEndTime_마감시간이_date인_모든_active공고를_불러온다() { + //given + LocalDateTime dateTime = LocalDateTime.of(2024, 7, 30, 2, 30); + int times = 10; + for (int i = 0; i < times; i++){ + recruitService.create(RecruitCreate.builder() + .endTime(dateTime) + .build()); + } + + //when + List result = recruitService.findAllByEndTime(dateTime.toLocalDate()); + + //then + assertThat(result.size()).isEqualTo(times); + } + + @Test + void findAllByEndTime_마감시간이_date인_inactive공고는_제외한다() { + //given + LocalDateTime dateTime = LocalDateTime.of(2024, 7, 30, 2, 30); + int times = 10; + for (int i = 0; i < times; i++){ + Recruit recruit = recruitService.create(RecruitCreate.builder() + .endTime(dateTime) + .build()); + recruitService.disable(recruit.getId()); + } + + //when + List result = recruitService.findAllByEndTime(dateTime.toLocalDate()); + + //then + assertThat(result.size()).isZero(); + } + + @Test + void findAllByEndTime_active_inactive공고() { + //given + LocalDateTime dateTime = LocalDateTime.of(2024, 7, 30, 2, 30); + int times = 10; + for (int i = 0; i < times; i++){ + Recruit recruit = recruitService.create(RecruitCreate.builder() + .endTime(dateTime) + .build()); + if (i % 2 == 0) + recruitService.disable(recruit.getId()); + } + //when + List result = recruitService.findAllByEndTime(dateTime.toLocalDate()); + + //then + assertThat(result.size()).isEqualTo(times / 2); + } + + @Test + void findAllByEndTimeAfter_마감이_안지난_active공고를_불러온다() { + //given + //when + List result = recruitService.findAllByEndTimeAfter(LocalDateTime.of(2024, 1, 1, 1, 1)); + + //then + assertThat(result.size()).isOne(); + } + + @Test + void findAllByEndTimeAfter_inactive공고는_제외한다() { + //given + recruitService.disable(1L); + + //when + List result = recruitService.findAllByEndTimeAfter(LocalDateTime.of(2024, 1, 1, 1, 1)); + + //then + assertThat(result.size()).isZero(); + } +} \ No newline at end of file diff --git a/src/test/java/umc/kkijuk/server/review/mock/FakeReviewRepository.java b/src/test/java/umc/kkijuk/server/review/mock/FakeReviewRepository.java new file mode 100644 index 00000000..89e11bb9 --- /dev/null +++ b/src/test/java/umc/kkijuk/server/review/mock/FakeReviewRepository.java @@ -0,0 +1,60 @@ +package umc.kkijuk.server.review.mock; + +import umc.kkijuk.server.common.domian.exception.ResourceNotFoundException; +import umc.kkijuk.server.review.domain.Review; +import umc.kkijuk.server.review.service.port.ReviewRepository; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.Optional; +import java.util.concurrent.atomic.AtomicLong; + +public class FakeReviewRepository implements ReviewRepository { + private final AtomicLong authGeneratedId = new AtomicLong(0); + private final List data = new ArrayList<>(); + + @Override + public Review save(Review review) { + if (review.getId() == null || review.getId() == 0) { + Review newReview = Review.builder() + .id(authGeneratedId.incrementAndGet()) + .recruitId(review.getRecruitId()) + .title(review.getTitle()) + .content(review.getContent()) + .date(review.getDate()) + .build(); + data.add(newReview); + return newReview; + } else { + data.removeIf(item -> Objects.equals(item.getId(), review.getId())); + data.add(review); + return review; + } + } + + @Override + public Optional findById(Long id) { + return data.stream() + .filter(item -> item.getId().equals(id)) + .findAny(); + } + + @Override + public Review getById(Long id) { + return findById(id) + .orElseThrow(() -> new ResourceNotFoundException("review", id)); + } + + @Override + public void delete(Review review) { + data.remove(review); + } + + @Override + public List findAllByRecruitId(Long id) { + return data.stream() + .filter(review -> review.getRecruitId().equals(id)) + .toList(); + } +} diff --git a/src/test/java/umc/kkijuk/server/review/service/ReviewServiceTest.java b/src/test/java/umc/kkijuk/server/review/service/ReviewServiceTest.java new file mode 100644 index 00000000..972e1085 --- /dev/null +++ b/src/test/java/umc/kkijuk/server/review/service/ReviewServiceTest.java @@ -0,0 +1,202 @@ +package umc.kkijuk.server.review.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.common.domian.exception.ReviewRecruitNotMatchException; +import umc.kkijuk.server.recruit.domain.Recruit; +import umc.kkijuk.server.review.controller.port.ReviewService; +import umc.kkijuk.server.review.domain.Review; +import umc.kkijuk.server.review.domain.ReviewCreate; +import umc.kkijuk.server.review.domain.ReviewUpdate; +import umc.kkijuk.server.review.mock.FakeReviewRepository; +import umc.kkijuk.server.review.service.port.ReviewRepository; + +import java.time.LocalDate; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.*; + +class ReviewServiceTest { + + private ReviewService reviewService; + + @BeforeEach + void Init() { + ReviewRepository reviewRepository = new FakeReviewRepository(); + reviewService = ReviewServiceImpl.builder() + .reviewRepository(reviewRepository) + .build(); + + Review review = Review.builder() + .recruitId(3L) + .title("test-title") + .content("test-content") + .date(LocalDate.of(2024, 7, 21)) + .build(); + + reviewRepository.save(review); + } + + @Test + void crate_새로운_review_만들기() { + //given + ReviewCreate reviewCreate = ReviewCreate.builder() + .title("new-title") + .content("new-content") + .date(LocalDate.of(2024, 7, 21)) + .build(); + + Recruit recruit = Recruit.builder().id(3L).build(); + + //when + Review result = reviewService.create(recruit, reviewCreate); + + //then + assertAll( + () -> assertThat(result.getRecruitId()).isEqualTo(recruit.getId()), + () -> assertThat(result.getTitle()).isEqualTo(reviewCreate.getTitle()), + () -> assertThat(result.getContent()).isEqualTo(reviewCreate.getContent()), + () -> assertThat(result.getDate()).isEqualTo(LocalDate.of(2024, 7, 21)) + ); + } + + @Test + void create_새로운_review_만들기_nullable() { + //given + ReviewCreate reviewCreate = ReviewCreate.builder() + .title("new-title") + .date(LocalDate.of(2024, 7, 21)) + .build(); + + Recruit recruit = Recruit.builder().id(3L).build(); + + //when + Review result = reviewService.create(recruit, reviewCreate); + + //then + assertAll( + () -> assertThat(result.getRecruitId()).isEqualTo(recruit.getId()), + () -> assertThat(result.getTitle()).isEqualTo(reviewCreate.getTitle()), + () -> assertThat(result.getContent()).isNull(), + () -> assertThat(result.getDate()).isEqualTo(LocalDate.of(2024, 7, 21)) + ); + } + + @Test + void update_존재하던_review_수정() { + //given + Recruit recruit = Recruit.builder().id(3L).build(); + Long reviewId = 1L; + ReviewUpdate reviewUpdate = ReviewUpdate.builder() + .title("changed-title") + .content("changed-content") + .date(LocalDate.of(2024, 7, 30)) + .build(); + + //when + Review result = reviewService.update(recruit, reviewId, reviewUpdate); + + //then + assertAll( + () -> assertThat(result.getId()).isNotNull(), + () -> assertThat(result.getRecruitId()).isEqualTo(3L), + () -> assertThat(result.getTitle()).isEqualTo("changed-title"), + () -> assertThat(result.getContent()).isEqualTo("changed-content"), + () -> assertThat(result.getDate()).isEqualTo(LocalDate.of(2024, 7, 30)) + ); + } + + @Test + void update_존재하던_review_수정_content_이미존재했지만_null로변경() { + //given + Recruit recruit = Recruit.builder().id(3L).build(); + Long reviewId = 1L; + ReviewUpdate reviewUpdate = ReviewUpdate.builder() + .title("changed-title") + .date(LocalDate.of(2024, 7, 30)) + .build(); + + //when + Review result = reviewService.update(recruit, reviewId, reviewUpdate); + + //then + assertAll( + () -> assertThat(result.getId()).isNotNull(), + () -> assertThat(result.getRecruitId()).isEqualTo(3L), + () -> assertThat(result.getTitle()).isEqualTo("changed-title"), + () -> assertThat(result.getContent()).isNull(), + () -> assertThat(result.getDate()).isEqualTo(LocalDate.of(2024, 7, 30)) + ); + } + + @Test + void delete_존재하던_review_제거() { + //given + ReviewCreate reviewCreate = ReviewCreate.builder() + .title("new-title") + .content("new-content") + .date(LocalDate.of(2024, 7, 21)) + .build(); + + Recruit recruit = Recruit.builder().id(3L).build(); + Review review = reviewService.create(recruit, reviewCreate); + + //when + reviewService.delete(recruit, review.getId()); + + //then + assertThatThrownBy( + () -> reviewService.getById(review.getId())).isInstanceOf(ResourceNotFoundException.class); + } + + @Test + void delete_존재하지않은_review에대한_제거요청() { + //given + Recruit recruit = Recruit.builder().id(3L).build(); + + //when + //then + assertThatThrownBy( + () -> reviewService.delete(recruit, 3333L)).isInstanceOf(ResourceNotFoundException.class); + } + + @Test + void delete_review의_공고ID가_요청한_공고ID와_다른경우() { + //given + ReviewCreate reviewCreate = ReviewCreate.builder() + .title("new-title") + .content("new-content") + .date(LocalDate.of(2024, 7, 21)) + .build(); + Recruit recruit_1 = Recruit.builder().id(3L).build(); + Recruit recruit_2 = Recruit.builder().id(4L).build(); + Review review = reviewService.create(recruit_1, reviewCreate); + + //when + //then + assertThatThrownBy( + () -> reviewService.delete(recruit_2, review.getId())).isInstanceOf(ReviewRecruitNotMatchException.class); + } + + @Test + void findAllByRecruitId_공고의_모든_review찾기() { + //given + Recruit recruit = Recruit.builder().id(3333L).build(); + ReviewCreate reviewCreate = ReviewCreate.builder() + .title("new-title") + .content("new-content") + .date(LocalDate.of(2024, 7, 21)) + .build(); + for (int i = 0; i < 10; i++) + reviewService.create(recruit, reviewCreate); + + //when + List reviews = reviewService.findAllByRecruitId(recruit.getId()); + + //then + assertThat(reviews.size()).isEqualTo(10); + } +} \ No newline at end of file diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties index acf6314b..07a78ad5 100644 --- a/src/test/resources/application.properties +++ b/src/test/resources/application.properties @@ -10,7 +10,7 @@ spring.datasource.password=testPW # spring jpa spring.jpa.database=mysql spring.jpa.show-sql=true -spring.jpa.hibernate.ddl-auto=create +spring.jpa.hibernate.ddl-auto=update spring.jpa.properties.hibernate.format_sql=true