From b57ae66084414f6b2a8f9cc73b54ad21f5734451 Mon Sep 17 00:00:00 2001 From: JeongHyun Lee <86969518+hyunihs@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:15:07 +0900 Subject: [PATCH] chore: merge into main (#187) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * [feat] 지원 서류 삭제 기능 (#177) * [feat] 지원 서류 삭제 * [chore] update CODEOWNERS * [feat] 지원서 목록 보기 API required false인 parameter들에 대해 default value 설정 * [test] 지원서 목록 보기 API required false인 parameter들에 대한 테스트 코드 작성 * [feat] 지원서 목록 보기 API enum 타입인 parameter들에 대해 allowable values 설정 * [test] 지원서 목록 보기 API enum 타입인 parameter들에 대해 테스트 코드 작성 * [fix] 지원서 엑셀 파일 생성 시각 API 테스트 status code 수정 * [revert] 코드 수정 내역 되돌림 * [feat] 지원서 목록 보기 API required false인 parameter들에 대해 default value 설정 * [test] 지원서 목록 보기 API required false인 parameter들에 대한 테스트 코드 작성 * [feat] 지원서 목록 보기 API enum 타입인 parameter들에 대해 allowable values 설정 * [test] 지원서 목록 보기 API enum 타입인 parameter들에 대해 테스트 코드 작성 * [fix] 지원서 엑셀 파일 생성 시각 API 테스트 status code 수정 * [revert] 코드 수정 내역 되돌림 * [feat] 지원서 목록 보기 API required false인 parameter들에 대해 default value 설정 * [test] 지원서 목록 보기 API required false인 parameter들에 대한 테스트 코드 작성 * [feat] 지원서 목록 보기 API enum 타입인 parameter들에 대해 allowable values 설정 * [test] 지원서 목록 보기 API enum 타입인 parameter들에 대해 테스트 코드 작성 * [fix] 지원서 엑셀 파일 생성 시각 API 테스트 status code 수정 * [feat] 면접 참여 여부 확인용 DTO 생성 #180 * [feat] 최종 활동 여부 확인용 DTO 생성 #180 * [feat] 불가능 사유 unableReason 필드 및 해당 필드 업데이트 로직 추가 #180 * [feat] 최종 활동 여부 확인용 DTO 생성 #180 * [feat] 면접 참여 가능 여부 확인과 활동 가능 여부 확인 Service 로직 작성 #180 * [feat] 활동 가능 여부 확인 시 최종 합격 여부 검증하는 validator 작성 #180 * feat: 면접 참여 및 활동 가능 여부 확인용 Controller 작성 * feat: 면접 참여 및 활동 가능 여부 확인용 Controller 작성 #180 * [refact] 회고록 목록 보기 response dto 필드 수정 * [refact] 회고록 상세 보기/수정 response dto 필드 수정 * [refact] 리프레시 토큰 발급 로직 수정 (#186) * [refact] 리프레시토큰 발급 로직 수정 #185 * [refact] 상수 추가 --------- Co-authored-by: yoonsseo Co-authored-by: letskuku Co-authored-by: Yujeong Lee <90572599+letskuku@users.noreply.github.com> Co-authored-by: 이윤서 <90557277+yoonsseo@users.noreply.github.com> Co-authored-by: suhhyun <97878992+suhhyun524@users.noreply.github.com> --- .github/CODEOWNERS | 2 +- .../backend/domain/admin/AdminController.java | 13 +++-- .../domain/admin/service/AdminService.java | 12 +++-- .../application/ApplicationController.java | 52 ++++++++++++------- .../application/domain/Application.java | 11 +++- .../application/domain/ApplicationAnswer.java | 2 +- .../dto/response/GetFinalAvailability.java | 26 ++++++++++ .../response/GetInterviewAvailability.java | 25 +++++++++ .../exception/ApplicationErrorCode.java | 1 + .../NotDeletableDuringRecruitment.java | 12 +++++ .../service/ApplicationService.java | 43 +++++++++++++-- .../validator/ApplicationValidator.java | 4 ++ .../service/RecruitmentService.java | 3 ++ .../dto/response/GetRetrospectResponse.java | 4 ++ .../dto/response/GetRetrospectsElement.java | 8 ++- .../backend/global/config/RedisConfig.java | 3 -- .../global/config/jwt/TokenProvider.java | 8 +-- .../ApplicationControllerTest.java | 49 ++++++++++++++++- 18 files changed, 229 insertions(+), 49 deletions(-) create mode 100644 src/main/java/ceos/backend/domain/application/dto/response/GetFinalAvailability.java create mode 100644 src/main/java/ceos/backend/domain/application/dto/response/GetInterviewAvailability.java create mode 100644 src/main/java/ceos/backend/domain/application/exception/exceptions/NotDeletableDuringRecruitment.java diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index c647a9dd..f3445ca0 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1 +1 @@ -* @hyunihs @suhhyun524 @mushroom1324 @haen-su @mirageoasis \ No newline at end of file +* @hyunihs @mushroom1324 @letskuku @yoonsseo @itsme-shawn @YoungGyo-00 \ No newline at end of file diff --git a/src/main/java/ceos/backend/domain/admin/AdminController.java b/src/main/java/ceos/backend/domain/admin/AdminController.java index a982cf6e..439152f6 100644 --- a/src/main/java/ceos/backend/domain/admin/AdminController.java +++ b/src/main/java/ceos/backend/domain/admin/AdminController.java @@ -7,6 +7,7 @@ import ceos.backend.global.config.user.AdminDetails; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpServletRequest; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -21,6 +22,8 @@ public class AdminController { private final AdminService adminService; + private static final String MOBILE = "mobile"; + private static final String WEB = "web"; @Operation(summary = "닉네임 확인") @PostMapping("/username") @@ -38,9 +41,10 @@ public void signUp(@RequestBody @Valid SignUpRequest signUpRequest) { @Operation(summary = "로그인") @PostMapping("/signin") - public TokenResponse signIn(@RequestBody @Valid SignInRequest signInRequest) { + public TokenResponse signIn(HttpServletRequest request, @RequestBody @Valid SignInRequest signInRequest) { log.info("로그인"); - return adminService.signIn(signInRequest); + String device = request.getHeader("User-Agent").contains("mobile") ? MOBILE : WEB; + return adminService.signIn(device, signInRequest); } @Operation(summary = "아이디 찾기") @@ -68,9 +72,10 @@ public void resetPwd( @Operation(summary = "로그아웃") @PostMapping("/logout") - public void logout(@AuthenticationPrincipal AdminDetails adminUser) { + public void logout(HttpServletRequest request, @AuthenticationPrincipal AdminDetails adminUser) { log.info("로그아웃"); - adminService.logout(adminUser); + String device = request.getHeader("User-Agent").contains("mobile") ? MOBILE : WEB; + adminService.logout(device, adminUser); } @Operation(summary = "토큰 재발급") diff --git a/src/main/java/ceos/backend/domain/admin/service/AdminService.java b/src/main/java/ceos/backend/domain/admin/service/AdminService.java index 08c2c54e..7179b5ae 100644 --- a/src/main/java/ceos/backend/domain/admin/service/AdminService.java +++ b/src/main/java/ceos/backend/domain/admin/service/AdminService.java @@ -51,16 +51,18 @@ public void signUp(SignUpRequest signUpRequest) { } @Transactional - public TokenResponse signIn(SignInRequest signInRequest) { + public TokenResponse signIn(String device, SignInRequest signInRequest) { final Admin admin = adminHelper.findForSignIn(signInRequest); final Authentication authentication = adminHelper.adminAuthorizationInput(admin); adminHelper.checkRole(admin); + String redisKey = admin.getId().toString() + ":" + device; + // 토큰 발급 final String accessToken = tokenProvider.createAccessToken(admin.getId(), authentication); - final String refreshToken = tokenProvider.createRefreshToken(admin.getId(), authentication); + final String refreshToken = tokenProvider.createRefreshToken(admin.getId(), authentication, redisKey); return adminMapper.toTokenResponse(accessToken, refreshToken); } @@ -97,11 +99,13 @@ public void resetPwd(ResetPwdRequest resetPwdRequest, AdminDetails adminUser) { } @Transactional - public void logout(AdminDetails adminUser) { + public void logout(String device, AdminDetails adminUser) { final Admin admin = adminUser.getAdmin(); + String redisKey = admin.getId().toString() + ":" + device; + // 레디스 삭제 - tokenProvider.deleteRefreshToken(admin.getId()); + tokenProvider.deleteRefreshToken(redisKey); } @Transactional diff --git a/src/main/java/ceos/backend/domain/application/ApplicationController.java b/src/main/java/ceos/backend/domain/application/ApplicationController.java index f14f34db..cf328280 100644 --- a/src/main/java/ceos/backend/domain/application/ApplicationController.java +++ b/src/main/java/ceos/backend/domain/application/ApplicationController.java @@ -1,3 +1,4 @@ + package ceos.backend.domain.application; @@ -7,16 +8,13 @@ import ceos.backend.domain.application.dto.request.UpdateAttendanceRequest; import ceos.backend.domain.application.dto.request.UpdateInterviewTime; import ceos.backend.domain.application.dto.request.UpdatePassStatus; -import ceos.backend.domain.application.dto.response.GetApplication; -import ceos.backend.domain.application.dto.response.GetApplicationQuestion; -import ceos.backend.domain.application.dto.response.GetApplications; -import ceos.backend.domain.application.dto.response.GetCreationTime; -import ceos.backend.domain.application.dto.response.GetInterviewTime; -import ceos.backend.domain.application.dto.response.GetResultResponse; +import ceos.backend.domain.application.dto.response.*; import ceos.backend.domain.application.service.ApplicationExcelService; import ceos.backend.domain.application.service.ApplicationService; import ceos.backend.global.common.entity.Part; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import java.nio.file.Path; @@ -26,15 +24,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PatchMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @Slf4j @RestController @@ -48,10 +38,13 @@ public class ApplicationController { @Operation(summary = "지원자 목록 보기") @GetMapping public GetApplications getApplications( - @RequestParam(value = "part", required = false) Part part, - @RequestParam(value = "docPass", required = false) Pass docPass, - @RequestParam(value = "finalPass", required = false) Pass finalPass, - @RequestParam(value = "applicantName", required = false) String applicantName, + @Parameter(schema = @Schema(allowableValues = {"PRODUCT", "DESIGN", "FRONTEND", "BACKEND"})) + @RequestParam(value = "part", required = false, defaultValue = "") Part part, + @Parameter(schema = @Schema(allowableValues = {"PASS", "FAIL"})) + @RequestParam(value = "docPass", required = false, defaultValue = "") Pass docPass, + @Parameter(schema = @Schema(allowableValues = {"PASS", "FAIL"})) + @RequestParam(value = "finalPass", required = false, defaultValue = "") Pass finalPass, + @RequestParam(value = "applicantName", required = false, defaultValue = "") String applicantName, @RequestParam("pageNum") int pageNum, @RequestParam("limit") int limit) { log.info("지원자 목록 보기"); @@ -150,6 +143,13 @@ public void updateDocumentPassStatus( applicationService.updateDocumentPassStatus(applicationId, updatePassStatus); } + @Operation(summary = "면접 참여 가능 여부 확인", description = "resultDateDoc ~ resultDateFinal 전날") + @GetMapping(value = "/{applicationId}/interview/availability") + public GetInterviewAvailability getInterviewAvailability(@PathVariable("applicationId") Long applicationId) { + log.info("면접 참여 가능 여부 확인"); + return applicationService.getInterviewAvailability(applicationId); + } + @Operation(summary = "최종 합격 여부 변경", description = "resultDateDoc ~ ResultDateFinal 전날") @PatchMapping(value = "/{applicationId}/final") public void updateFinalPassStatus( @@ -159,6 +159,13 @@ public void updateFinalPassStatus( applicationService.updateFinalPassStatus(applicationId, updatePassStatus); } + @Operation(summary = "활동 가능 여부 확인", description = "resultDateDoc ~ resultDateFinal 전날") + @GetMapping(value = "/{applicationId}/final/availability") + public GetFinalAvailability getFinalPass(@PathVariable("applicationId") Long applicationId) { + log.info("활동 가능 여부 확인"); + return applicationService.getFinalAvailability(applicationId); + } + @Operation(summary = "지원서 엑셀 파일 생성") @GetMapping(value = "/file/create") public GetCreationTime createApplicationExcel() { @@ -191,4 +198,11 @@ public GetCreationTime getApplicationExcelCreationTime() { log.info("지원서 엑셀 파일 생성 시각 확인"); return applicationExcelService.getApplicationExcelCreationTime(); } + + @Operation(summary = "지원서 전체 삭제") + @DeleteMapping(value = "/delete") + public void deleteAllApplications() { + log.info("지원서 전체 삭제"); + applicationService.deleteAllApplications(); + } } diff --git a/src/main/java/ceos/backend/domain/application/domain/Application.java b/src/main/java/ceos/backend/domain/application/domain/Application.java index a08fc628..3f601655 100644 --- a/src/main/java/ceos/backend/domain/application/domain/Application.java +++ b/src/main/java/ceos/backend/domain/application/domain/Application.java @@ -8,6 +8,8 @@ import jakarta.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; + +import jakarta.validation.constraints.Size; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -36,17 +38,20 @@ public class Application extends BaseEntity { @ColumnDefault("false") private boolean interviewCheck; + @Size(max = 100) + private String unableReason; + @NotNull @Enumerated(EnumType.STRING) private Pass documentPass; @NotNull @ColumnDefault("false") - private boolean finalCheck; + private boolean finalCheck; // 활동 가능 여부 @NotNull @Enumerated(EnumType.STRING) - private Pass finalPass; + private Pass finalPass; // 최종 합격 결과 @OneToMany(mappedBy = "application", cascade = CascadeType.ALL) private List applicationAnswers = new ArrayList<>(); @@ -85,6 +90,8 @@ public void updateInterviewCheck(boolean check) { this.interviewCheck = check; } + public void updateUnableReason(String reason) { this.unableReason = reason; } + public void updateFinalCheck(boolean check) { this.finalCheck = check; } diff --git a/src/main/java/ceos/backend/domain/application/domain/ApplicationAnswer.java b/src/main/java/ceos/backend/domain/application/domain/ApplicationAnswer.java index 08b77be9..1a291cae 100644 --- a/src/main/java/ceos/backend/domain/application/domain/ApplicationAnswer.java +++ b/src/main/java/ceos/backend/domain/application/domain/ApplicationAnswer.java @@ -17,7 +17,7 @@ public class ApplicationAnswer extends BaseEntity { private Long id; // Question : Answer = 1:1 (단방향) - @OneToOne(cascade = CascadeType.ALL) + @OneToOne @JoinColumn(name = "application_question_id") private ApplicationQuestion applicationQuestion; diff --git a/src/main/java/ceos/backend/domain/application/dto/response/GetFinalAvailability.java b/src/main/java/ceos/backend/domain/application/dto/response/GetFinalAvailability.java new file mode 100644 index 00000000..aec81cd2 --- /dev/null +++ b/src/main/java/ceos/backend/domain/application/dto/response/GetFinalAvailability.java @@ -0,0 +1,26 @@ +package ceos.backend.domain.application.dto.response; + +import ceos.backend.domain.application.domain.Application; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class GetFinalAvailability { + private boolean finalAvailability; // 활동 가능 여부 + private String reason; // 활동 불가능 사유 + + @Builder + public GetFinalAvailability(boolean finalCheck, String reason) { + this.finalAvailability = finalCheck; + this.reason = reason; + } + + public static GetFinalAvailability of(Application application) { + return GetFinalAvailability.builder() + .finalAvailability(application.isFinalCheck()) + .reason(application.getUnableReason()) + .build(); + } + +} diff --git a/src/main/java/ceos/backend/domain/application/dto/response/GetInterviewAvailability.java b/src/main/java/ceos/backend/domain/application/dto/response/GetInterviewAvailability.java new file mode 100644 index 00000000..8ff16101 --- /dev/null +++ b/src/main/java/ceos/backend/domain/application/dto/response/GetInterviewAvailability.java @@ -0,0 +1,25 @@ +package ceos.backend.domain.application.dto.response; + +import ceos.backend.domain.application.domain.Application; +import lombok.Builder; +import lombok.Getter; + +@Getter +@Builder +public class GetInterviewAvailability { + private boolean interviewAvailability; // 참여 가능 여부 + private String reason; // 참여 불가능 사유 + + @Builder + public GetInterviewAvailability(boolean interviewCheck, String reason) { + this.interviewAvailability = interviewCheck; + this.reason = reason; + } + + public static GetInterviewAvailability of(Application application) { + return GetInterviewAvailability.builder() + .interviewAvailability(application.isInterviewCheck()) + .reason(application.getUnableReason()) + .build(); + } +} diff --git a/src/main/java/ceos/backend/domain/application/exception/ApplicationErrorCode.java b/src/main/java/ceos/backend/domain/application/exception/ApplicationErrorCode.java index 97295dad..910a9248 100644 --- a/src/main/java/ceos/backend/domain/application/exception/ApplicationErrorCode.java +++ b/src/main/java/ceos/backend/domain/application/exception/ApplicationErrorCode.java @@ -22,6 +22,7 @@ public enum ApplicationErrorCode implements BaseErrorCode { SAME_PASS_STATUS(BAD_REQUEST, "APPLICATION_400_7", "같은 상태로 변경할 수 없습니다."), NOT_SET_INTERVIEW_TIME(BAD_REQUEST, "APPLICATION_400_8", "면접 시간이 정해지지 않았습니다."), APPLICATION_STILL_EXIST(BAD_REQUEST, "APPLICATION_400_9", "기존 지원자 데이터가 남아있습니다."), + NOT_DELETABLE_DURING_RECRUITMENT(BAD_REQUEST, "APPLICATION_400_10", "최종 발표 전 지원자를 삭제할 수 없습니다."), APPLICANT_NOT_FOUND(BAD_REQUEST, "APPLICATION_404_3", "존재하지 않는 지원자입니다."), diff --git a/src/main/java/ceos/backend/domain/application/exception/exceptions/NotDeletableDuringRecruitment.java b/src/main/java/ceos/backend/domain/application/exception/exceptions/NotDeletableDuringRecruitment.java new file mode 100644 index 00000000..4a0e2e6e --- /dev/null +++ b/src/main/java/ceos/backend/domain/application/exception/exceptions/NotDeletableDuringRecruitment.java @@ -0,0 +1,12 @@ +package ceos.backend.domain.application.exception.exceptions; + +import ceos.backend.domain.application.exception.ApplicationErrorCode; +import ceos.backend.global.error.BaseErrorException; + +public class NotDeletableDuringRecruitment extends BaseErrorException { + public static final NotDeletableDuringRecruitment EXCEPTION = new NotDeletableDuringRecruitment(); + + private NotDeletableDuringRecruitment() { + super(ApplicationErrorCode.NOT_DELETABLE_DURING_RECRUITMENT); + } +} diff --git a/src/main/java/ceos/backend/domain/application/service/ApplicationService.java b/src/main/java/ceos/backend/domain/application/service/ApplicationService.java index 934bae3a..5d421d7e 100644 --- a/src/main/java/ceos/backend/domain/application/service/ApplicationService.java +++ b/src/main/java/ceos/backend/domain/application/service/ApplicationService.java @@ -13,11 +13,8 @@ import ceos.backend.domain.application.dto.request.UpdateAttendanceRequest; import ceos.backend.domain.application.dto.request.UpdateInterviewTime; import ceos.backend.domain.application.dto.request.UpdatePassStatus; -import ceos.backend.domain.application.dto.response.GetApplication; -import ceos.backend.domain.application.dto.response.GetApplicationQuestion; -import ceos.backend.domain.application.dto.response.GetApplications; -import ceos.backend.domain.application.dto.response.GetInterviewTime; -import ceos.backend.domain.application.dto.response.GetResultResponse; +import ceos.backend.domain.application.dto.response.*; +import ceos.backend.domain.application.exception.exceptions.NotDeletableDuringRecruitment; import ceos.backend.domain.application.helper.ApplicationHelper; import ceos.backend.domain.application.mapper.ApplicationMapper; import ceos.backend.domain.application.repository.ApplicationAnswerRepository; @@ -35,6 +32,8 @@ import ceos.backend.global.common.entity.Part; import ceos.backend.global.util.InterviewDateTimeConvertor; import ceos.backend.global.util.ParsedDurationConvertor; + +import java.time.LocalDateTime; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; @@ -165,6 +164,7 @@ public void updateInterviewAttendance( application.updateInterviewCheck(true); applicationRepository.save(application); } else { + application.updateUnableReason(request.getReason()); applicationHelper.sendSlackUnableReasonMessage(application, request, false); } } @@ -193,6 +193,7 @@ public void updateParticipationAvailability( application.updateFinalCheck(true); applicationRepository.save(application); } else { + application.updateUnableReason(request.getReason()); applicationHelper.sendSlackUnableReasonMessage(application, request, true); } } @@ -246,6 +247,16 @@ public void updateInterviewTime(Long applicationId, UpdateInterviewTime updateIn application.updateInterviewTime(duration); } + @Transactional(readOnly = true) + public GetInterviewAvailability getInterviewAvailability(Long applicationId) { + applicationValidator.validateExistingApplicant(applicationId); // 유저 검증 + final Application application = applicationHelper.getApplicationById(applicationId); + applicationValidator.validateDocumentPassStatus(application); // 서류 통과 검증 + + return GetInterviewAvailability.of(application); + } + + @Transactional public void updateDocumentPassStatus(Long applicationId, UpdatePassStatus updatePassStatus) { recruitmentValidator.validateBetweenStartDateDocAndResultDateDoc(); // 기간 검증 @@ -264,4 +275,26 @@ public void updateFinalPassStatus(Long applicationId, UpdatePassStatus updatePas application.updateFinalPass(updatePassStatus.getPass()); } + + @Transactional(readOnly = true) + public GetFinalAvailability getFinalAvailability(Long applicationId) { + applicationValidator.validateExistingApplicant(applicationId); // 유저 검증 + final Application application = applicationHelper.getApplicationById(applicationId); + applicationValidator.validateFinalPassStatus(application); // 최종 합격 검증 + + return GetFinalAvailability.of(application); + } + + + @Transactional + public void deleteAllApplications() { + Recruitment recruitment = recruitmentHelper.takeRecruitment(); + // 현재 시간이 resultDateFinal 이전이면 삭제 불가 + if(LocalDateTime.now().isBefore(recruitment.getResultDateFinal())) { + throw NotDeletableDuringRecruitment.EXCEPTION; + } + // application, applicationAnswer, applicationInterview 삭제 (cascade) + applicationRepository.deleteAll(); + } + } diff --git a/src/main/java/ceos/backend/domain/application/validator/ApplicationValidator.java b/src/main/java/ceos/backend/domain/application/validator/ApplicationValidator.java index 1590793f..efa6e678 100644 --- a/src/main/java/ceos/backend/domain/application/validator/ApplicationValidator.java +++ b/src/main/java/ceos/backend/domain/application/validator/ApplicationValidator.java @@ -70,6 +70,10 @@ public void validateDocumentPassStatus(Application application) { application.validateDocumentPass(); } + public void validateFinalPassStatus(Application application) { + application.validateFinalPass(); + } + public void validateInterviewTime(List interviews, String interviewTime) { if (interviews.stream() .noneMatch( diff --git a/src/main/java/ceos/backend/domain/recruitment/service/RecruitmentService.java b/src/main/java/ceos/backend/domain/recruitment/service/RecruitmentService.java index a50a9752..47172796 100644 --- a/src/main/java/ceos/backend/domain/recruitment/service/RecruitmentService.java +++ b/src/main/java/ceos/backend/domain/recruitment/service/RecruitmentService.java @@ -11,6 +11,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; + @Slf4j @Service @RequiredArgsConstructor @@ -34,6 +36,7 @@ public UserRecruitmentDTO getUserRecruitment() { @Transactional public void updateRecruitment(RecruitmentDTO recruitmentDTO) { Recruitment recruitment = recruitmentHelper.takeRecruitment(); + recruitment.validAmenablePeriod(LocalDateTime.now()); // 객체 업데이트 recruitment.updateRecruitment(recruitmentDTO); diff --git a/src/main/java/ceos/backend/domain/retrospect/dto/response/GetRetrospectResponse.java b/src/main/java/ceos/backend/domain/retrospect/dto/response/GetRetrospectResponse.java index 9fcb7ae8..bd65f392 100644 --- a/src/main/java/ceos/backend/domain/retrospect/dto/response/GetRetrospectResponse.java +++ b/src/main/java/ceos/backend/domain/retrospect/dto/response/GetRetrospectResponse.java @@ -10,6 +10,8 @@ public class GetRetrospectResponse { private String title; private String url; private String writer; + private Integer generation; + private String part; public static GetRetrospectResponse fromEntity(Retrospect retrospect) { GetRetrospectResponse getRetrospectResponse = new GetRetrospectResponse(); @@ -18,6 +20,8 @@ public static GetRetrospectResponse fromEntity(Retrospect retrospect) { getRetrospectResponse.setTitle(retrospect.getTitle()); getRetrospectResponse.setUrl(retrospect.getUrl()); getRetrospectResponse.setWriter(retrospect.getWriter()); + getRetrospectResponse.setGeneration(retrospect.getGeneration()); + getRetrospectResponse.setPart(retrospect.getPart().getPart()); return getRetrospectResponse; } diff --git a/src/main/java/ceos/backend/domain/retrospect/dto/response/GetRetrospectsElement.java b/src/main/java/ceos/backend/domain/retrospect/dto/response/GetRetrospectsElement.java index 356cf4b2..0255ccbd 100644 --- a/src/main/java/ceos/backend/domain/retrospect/dto/response/GetRetrospectsElement.java +++ b/src/main/java/ceos/backend/domain/retrospect/dto/response/GetRetrospectsElement.java @@ -10,16 +10,14 @@ public class GetRetrospectsElement { private Long id; private String title; - private String url; - private String writer; private Integer generation; + private String part; public static GetRetrospectsElement fromEntity(Retrospect retrospect) { return new GetRetrospectsElement( retrospect.getId(), retrospect.getTitle(), - retrospect.getUrl(), - retrospect.getWriter(), - retrospect.getGeneration()); + retrospect.getGeneration(), + retrospect.getPart().getPart()); } } diff --git a/src/main/java/ceos/backend/global/config/RedisConfig.java b/src/main/java/ceos/backend/global/config/RedisConfig.java index a1570ec5..a43ee2dc 100644 --- a/src/main/java/ceos/backend/global/config/RedisConfig.java +++ b/src/main/java/ceos/backend/global/config/RedisConfig.java @@ -25,10 +25,7 @@ public RedisConnectionFactory redisConnectionFactory() { @Bean public RedisTemplate redisTemplate() { - // redisTemplate를 받아와서 set, get, delete를 사용 RedisTemplate redisTemplate = new RedisTemplate<>(); - // setKeySerializer, setValueSerializer 설정 - // redis-cli을 통해 직접 데이터를 조회 시 알아볼 수 없는 형태로 출력되는 것을 방지 redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new StringRedisSerializer()); redisTemplate.setConnectionFactory(redisConnectionFactory()); diff --git a/src/main/java/ceos/backend/global/config/jwt/TokenProvider.java b/src/main/java/ceos/backend/global/config/jwt/TokenProvider.java index d9f7ca90..5d1268e3 100644 --- a/src/main/java/ceos/backend/global/config/jwt/TokenProvider.java +++ b/src/main/java/ceos/backend/global/config/jwt/TokenProvider.java @@ -82,7 +82,7 @@ public String createAccessToken(Long id, Authentication authentication) { .compact(); } - public String createRefreshToken(Long id, Authentication authentication) { + public String createRefreshToken(Long id, Authentication authentication, String redisKey) { String authorities = authentication.getAuthorities().stream() .map(GrantedAuthority::getAuthority) @@ -107,13 +107,13 @@ public String createRefreshToken(Long id, Authentication authentication) { redisTemplate .opsForValue() - .set(id.toString(), refreshToken, refreshExpirationTime, TimeUnit.SECONDS); + .set(redisKey, refreshToken, refreshExpirationTime, TimeUnit.SECONDS); return refreshToken; } - public void deleteRefreshToken(Long id) { - redisTemplate.delete(id.toString()); + public void deleteRefreshToken(String redisKey) { + redisTemplate.delete(redisKey); } public String getTokenUserId(String token) { diff --git a/src/test/java/ceos/backend/domain/application/ApplicationControllerTest.java b/src/test/java/ceos/backend/domain/application/ApplicationControllerTest.java index 31ca80f7..e135090c 100644 --- a/src/test/java/ceos/backend/domain/application/ApplicationControllerTest.java +++ b/src/test/java/ceos/backend/domain/application/ApplicationControllerTest.java @@ -6,6 +6,9 @@ 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.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.result.MockMvcResultMatchers; @@ -20,6 +23,50 @@ public class ApplicationControllerTest { @Test void getApplicationExcelCreationTime() throws Exception { mockMvc.perform(MockMvcRequestBuilders.get("/applications/file/creationtime")) - .andExpect(MockMvcResultMatchers.status().is(401)); + .andExpect(MockMvcResultMatchers.status().is(403)); + } + + @DisplayName("지원서 목록 보기 API - 필수 아닌 파라미터들 길이 0인 문자열로 처리") + @Test + void getApplicationsWithZeroStrings() throws Exception { + Authentication authentication = new TestingAuthenticationToken(null, null, "ROLE_ADMIN"); + + mockMvc.perform(MockMvcRequestBuilders + .get("/applications?part=&docPass=&finalPass=&applicantName=&pageNum=0&limit=7") + .with(SecurityMockMvcRequestPostProcessors.authentication(authentication))) + .andExpect(MockMvcResultMatchers.status().isOk()); + } + + @DisplayName("지원서 목록 보기 API - 필수 아닌 파라미터들 제외") + @Test + void getApplicationsWithoutRequiredFalse() throws Exception { + Authentication authentication = new TestingAuthenticationToken(null, null, "ROLE_ADMIN"); + + mockMvc.perform(MockMvcRequestBuilders + .get("/applications?pageNum=0&limit=7") + .with(SecurityMockMvcRequestPostProcessors.authentication(authentication))) + .andExpect(MockMvcResultMatchers.status().isOk()); + } + + @DisplayName("지원서 목록 보기 API - enum 타입 영어로 처리") + @Test + void successGetApplications() throws Exception { + Authentication authentication = new TestingAuthenticationToken(null, null, "ROLE_ADMIN"); + + mockMvc.perform(MockMvcRequestBuilders + .get("/applications?part=PRODUCT&docPass=PASS&finalPass=FAIL&applicantName=&pageNum=0&limit=7") + .with(SecurityMockMvcRequestPostProcessors.authentication(authentication))) + .andExpect(MockMvcResultMatchers.status().isOk()); + } + + @DisplayName("지원서 목록 보기 API - enum 타입 한글로 처리 시 예외 발생") + @Test + void failGetApplications() throws Exception { + Authentication authentication = new TestingAuthenticationToken(null, null, "ROLE_ADMIN"); + + mockMvc.perform(MockMvcRequestBuilders + .get("/applications?part=기획&docPass=합격&finalPass=불합격&applicantName=&pageNum=0&limit=7") + .with(SecurityMockMvcRequestPostProcessors.authentication(authentication))) + .andExpect(MockMvcResultMatchers.status().is(400)); } }