diff --git a/gradlew b/gradlew old mode 100644 new mode 100755 diff --git a/src/main/java/com/example/DoroServer/domain/educationApplication/api/EducationApplicationApi.java b/src/main/java/com/example/DoroServer/domain/educationApplication/api/EducationApplicationApi.java index 06c828e2..a9f2b303 100644 --- a/src/main/java/com/example/DoroServer/domain/educationApplication/api/EducationApplicationApi.java +++ b/src/main/java/com/example/DoroServer/domain/educationApplication/api/EducationApplicationApi.java @@ -2,10 +2,12 @@ import java.util.List; +import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -38,35 +40,46 @@ public class EducationApplicationApi { private final EducationApplicationService applicationService; private final ClassGroupService classGroupService; + @ModelAttribute("phoneNumber") + public String getPhoneNumber(HttpServletRequest request) { + // SessionFilter에서 넣어준 phoneNumber를 가져온다 + return (String) request.getAttribute("phoneNumber"); + } + /* Education Application */ @ApiOperation(value = "교육 신청 작성", notes = "교육을 신청합니다.") @PostMapping - public SuccessResponse create(@RequestBody @Valid EducationApplicationReq applicationReq) { - EducationApplicationRes savedApplication = applicationService.save(applicationReq); + public SuccessResponse create(@ModelAttribute("phoneNumber") String phoneNumber, + @RequestBody @Valid EducationApplicationReq applicationReq) { + EducationApplicationRes savedApplication = applicationService.save(applicationReq, phoneNumber); return SuccessResponse.successResponse(savedApplication); } @ApiOperation(value = "휴대폰 번호로 작성한 교육 신청 조회", notes = "신청한 교육을 핸드폰 번호를 통해 조회합니다.") @GetMapping - public SuccessResponse read(@RequestBody @Valid RetrieveApplicationReq retrieveApplicationReq) { + public SuccessResponse read(@ModelAttribute("phoneNumber") String phoneNumber, + @RequestBody @Valid RetrieveApplicationReq retrieveApplicationReq) { List applications = applicationService - .findByPhoneNumber(retrieveApplicationReq); + .findByPhoneNumber(retrieveApplicationReq, phoneNumber); return SuccessResponse.successResponse(applications); } @ApiOperation(value = "신청한 교육 수정", notes = "신청한 교육을 수정합니다.") @PutMapping("/{applicationId}") - public SuccessResponse update(@PathVariable Long applicationId, + public SuccessResponse update(@ModelAttribute("phoneNumber") String phoneNumber, + @PathVariable Long applicationId, @RequestBody @Valid EducationApplicationReq applicationReq) { - EducationApplicationRes updatedApplication = applicationService.update(applicationId, applicationReq); + EducationApplicationRes updatedApplication = applicationService.update(applicationId, applicationReq, + phoneNumber); return SuccessResponse.successResponse(updatedApplication); } @ApiOperation(value = "신청한 교육 삭제", notes = "신청한 교육을 삭제합니다. 학급정보도 같이 삭제됩니다.") @DeleteMapping("/{applicationId}") - public SuccessResponse delete(@PathVariable Long applicationId) { - applicationService.delete(applicationId); + public SuccessResponse delete(@ModelAttribute("phoneNumber") String phoneNumber, + @PathVariable Long applicationId) { + applicationService.delete(applicationId, phoneNumber); return SuccessResponse.successResponse("신청이 삭제되었습니다."); } @@ -74,25 +87,29 @@ public SuccessResponse delete(@PathVariable Long applicationId) { @ApiOperation(value = "신청한 교육에 학급정보 추가", notes = "우선 교육을 신청해야 합니다.") @PostMapping("/{applicationId}/classGroup") - public SuccessResponse addClassGroup(@PathVariable Long applicationId, + public SuccessResponse addClassGroup(@ModelAttribute("phoneNumber") String phoneNumber, + @PathVariable Long applicationId, @RequestBody @Valid ClassGroupReq classGroupReq) { - ClassGroupRes savedClassGroup = classGroupService.addClassGroupToApplication(applicationId, classGroupReq); + ClassGroupRes savedClassGroup = classGroupService.addClassGroupToApplication(applicationId, classGroupReq, + phoneNumber); return SuccessResponse.successResponse(savedClassGroup); } @ApiOperation(value = "학급정보 업데이트", notes = "우선 교육을 신청해야 합니다.") @PutMapping("/{applicationId}/classGroup/{classGroupId}") - public SuccessResponse updateClassGroup(@PathVariable Long applicationId, @PathVariable Long classGroupId, + public SuccessResponse updateClassGroup(@ModelAttribute("phoneNumber") String phoneNumber, + @PathVariable Long applicationId, @PathVariable Long classGroupId, @RequestBody @Valid ClassGroupReq classGroupReq) { ClassGroupRes updatedClassGroup = classGroupService.updateClassGroup(applicationId, classGroupId, - classGroupReq); + classGroupReq, phoneNumber); return SuccessResponse.successResponse(updatedClassGroup); } @ApiOperation(value = "학급정보 삭제", notes = "학급정보가 삭제됩니다.") @DeleteMapping("/{applicationId}/classGroup/{classGroupId}") - public SuccessResponse deleteClassGroup(@PathVariable Long applicationId, @PathVariable Long classGroupId) { - classGroupService.deleteClassGroup(applicationId, classGroupId); + public SuccessResponse deleteClassGroup(@ModelAttribute("phoneNumber") String phoneNumber, + @PathVariable Long applicationId, @PathVariable Long classGroupId) { + classGroupService.deleteClassGroup(applicationId, classGroupId, phoneNumber); return SuccessResponse.successResponse("학급정보가 삭제되었습니다."); } } \ No newline at end of file diff --git a/src/main/java/com/example/DoroServer/domain/educationApplication/api/SessionFilter.java b/src/main/java/com/example/DoroServer/domain/educationApplication/api/SessionFilter.java new file mode 100644 index 00000000..b450c94a --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/educationApplication/api/SessionFilter.java @@ -0,0 +1,50 @@ +package com.example.DoroServer.domain.educationApplication.api; + +import javax.servlet.FilterChain; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.filter.GenericFilterBean; + +import com.example.DoroServer.global.exception.Code; +import com.example.DoroServer.global.exception.SessionIdException; +import com.example.DoroServer.global.jwt.RedisService; + +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@RequiredArgsConstructor +public class SessionFilter extends GenericFilterBean { + // 해당 필터는 "/education-application"에 대한 요청에 대해서만 적용 + + private final RedisService redisService; + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + + HttpServletRequest httpRequest = (HttpServletRequest) request; + String requestURI = httpRequest.getRequestURI(); + + if (requestURI.startsWith("/education-application")) { + String sessionId = httpRequest.getHeader("Session-Id"); + if (sessionId == null) { + log.debug("유효한 Session-Id가 없습니다, uri: {}", requestURI); + throw new SessionIdException(Code.SESSION_ID_NOT_FOUND); + } + String phoneNumber = redisService.getValues(sessionId); + if (phoneNumber == null) { + log.debug("유효한 Session-Id가 아닙니다, uri: {}", requestURI); + throw new SessionIdException(Code.SESSION_ID_NOT_VALID); + } + httpRequest.setAttribute("phoneNumber", phoneNumber); + + } + + chain.doFilter(request, response); + } +} diff --git a/src/main/java/com/example/DoroServer/domain/educationApplication/api/SessionFilterHandler.java b/src/main/java/com/example/DoroServer/domain/educationApplication/api/SessionFilterHandler.java new file mode 100644 index 00000000..8e68d78c --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/educationApplication/api/SessionFilterHandler.java @@ -0,0 +1,46 @@ +package com.example.DoroServer.domain.educationApplication.api; + +import javax.servlet.FilterChain; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletResponse; +import javax.servlet.ServletException; + +import java.io.IOException; + +import org.springframework.web.filter.GenericFilterBean; + +import com.example.DoroServer.global.common.AuthErrorResponse; +import com.example.DoroServer.global.exception.Code; +import com.example.DoroServer.global.exception.SessionIdException; + +public class SessionFilterHandler extends GenericFilterBean { + + @Override + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { + try { + chain.doFilter(request, response); + } catch (SessionIdException e) { + HttpServletResponse httpResponse = (HttpServletResponse) response; + httpResponse.setContentType("application/json"); + httpResponse.setCharacterEncoding("utf-8"); + Code code = e.getMessage().equals("SESSION_ID_NOT_FOUND") ? Code.SESSION_ID_NOT_FOUND + : Code.SESSION_ID_NOT_VALID; + httpResponse.setStatus(code.getHttpStatus().value()); + AuthErrorResponse authErrorResponse = buildAuthErrorResponse(code); + httpResponse.getWriter().write(authErrorResponse.toString()); + + return; + } + } + + // 리팩터링 필요 - static 메서드가 아닌 별개의 클래스에서 메서드 생성 + public static AuthErrorResponse buildAuthErrorResponse(Code errorCode) { + return AuthErrorResponse.builder() + .errorCode(errorCode) + .message(errorCode.getMessage()) + .cause(SessionFilter.class.getName()) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/DoroServer/domain/educationApplication/dto/EducationApplicationReq.java b/src/main/java/com/example/DoroServer/domain/educationApplication/dto/EducationApplicationReq.java index 26af9194..83a56cd7 100644 --- a/src/main/java/com/example/DoroServer/domain/educationApplication/dto/EducationApplicationReq.java +++ b/src/main/java/com/example/DoroServer/domain/educationApplication/dto/EducationApplicationReq.java @@ -3,6 +3,7 @@ import javax.validation.constraints.Email; import javax.validation.constraints.Min; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; import lombok.AllArgsConstructor; import lombok.Getter; @@ -27,6 +28,7 @@ public class EducationApplicationReq { private String position; // 신청자 직위 @NotBlank + @Pattern(regexp = "^01([016789])([0-9]{3,4})([0-9]{4})$", message = "올바른 휴대폰 번호 형식이 아닙니다.") private String phoneNumber; // 신청자 전화번호 @Email diff --git a/src/main/java/com/example/DoroServer/domain/educationApplication/dto/RetrieveApplicationReq.java b/src/main/java/com/example/DoroServer/domain/educationApplication/dto/RetrieveApplicationReq.java index d1976350..b07539e5 100644 --- a/src/main/java/com/example/DoroServer/domain/educationApplication/dto/RetrieveApplicationReq.java +++ b/src/main/java/com/example/DoroServer/domain/educationApplication/dto/RetrieveApplicationReq.java @@ -1,6 +1,7 @@ package com.example.DoroServer.domain.educationApplication.dto; import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Pattern; import lombok.AllArgsConstructor; import lombok.Getter; @@ -15,5 +16,6 @@ @AllArgsConstructor public class RetrieveApplicationReq { @NotBlank + @Pattern(regexp = "^01([016789])([0-9]{3,4})([0-9]{4})$", message = "올바른 휴대폰 번호 형식이 아닙니다.") private String phoneNumber; // 신청자 전화번호 } diff --git a/src/main/java/com/example/DoroServer/domain/educationApplication/repository/EducationApplicationRepository.java b/src/main/java/com/example/DoroServer/domain/educationApplication/repository/EducationApplicationRepository.java index 2fae4bca..f290ae27 100644 --- a/src/main/java/com/example/DoroServer/domain/educationApplication/repository/EducationApplicationRepository.java +++ b/src/main/java/com/example/DoroServer/domain/educationApplication/repository/EducationApplicationRepository.java @@ -1,7 +1,7 @@ package com.example.DoroServer.domain.educationApplication.repository; import java.util.List; - +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import com.example.DoroServer.domain.educationApplication.entity.EducationApplication; @@ -10,4 +10,6 @@ public interface EducationApplicationRepository extends JpaRepository findByPhoneNumber(String phoneNumber); + Optional findByIdAndPhoneNumber(Long id, String phoneNumber); + } diff --git a/src/main/java/com/example/DoroServer/domain/educationApplication/service/EducationApplicationService.java b/src/main/java/com/example/DoroServer/domain/educationApplication/service/EducationApplicationService.java index 0ed102f3..798047ba 100644 --- a/src/main/java/com/example/DoroServer/domain/educationApplication/service/EducationApplicationService.java +++ b/src/main/java/com/example/DoroServer/domain/educationApplication/service/EducationApplicationService.java @@ -25,7 +25,9 @@ public class EducationApplicationService { /* Education Application */ // create - public EducationApplicationRes save(EducationApplicationReq applicationReq) { + public EducationApplicationRes save(EducationApplicationReq applicationReq, String phoneNumber) { + throwExceptionIfNotEqual(applicationReq.getPhoneNumber(), phoneNumber); + EducationApplication educationApplication = mapper.toEntity(applicationReq); EducationApplication savedEducationApplication = repository.save(educationApplication); @@ -33,17 +35,21 @@ public EducationApplicationRes save(EducationApplicationReq applicationReq) { } // read - public List findByPhoneNumber(RetrieveApplicationReq retrieveApplicationReq) { - List educationApplications = repository - .findByPhoneNumber(retrieveApplicationReq.getPhoneNumber()); + public List findByPhoneNumber(RetrieveApplicationReq retrieveApplicationReq, + String phoneNumber) { + throwExceptionIfNotEqual(retrieveApplicationReq.getPhoneNumber(), phoneNumber); + + List educationApplications = repository.findByPhoneNumber(phoneNumber); return mapper.toDTO(educationApplications); } // update - public EducationApplicationRes update(Long id, EducationApplicationReq applicationReq) { - EducationApplication educationApplication = repository.findById(id) - .orElseThrow(() -> new BaseException(Code.EDUCATION_APPLICATION_NOT_FOUND)); + public EducationApplicationRes update(Long id, EducationApplicationReq applicationReq, String phoneNumber) { + throwExceptionIfNotEqual(applicationReq.getPhoneNumber(), phoneNumber); + + EducationApplication educationApplication = repository.findByIdAndPhoneNumber(id, phoneNumber).orElseThrow( + () -> new BaseException(Code.EDUCATION_APPLICATION_NOT_FOUND)); mapper.toEntity(applicationReq, educationApplication); EducationApplication updatedEducationApplication = repository.save(educationApplication); @@ -52,8 +58,18 @@ public EducationApplicationRes update(Long id, EducationApplicationReq applicati } // delete - public void delete(Long id) { - repository.deleteById(id); + public void delete(Long id, String phoneNumber) { + EducationApplication educationApplication = repository.findByIdAndPhoneNumber(id, phoneNumber).orElseThrow( + () -> new BaseException(Code.EDUCATION_APPLICATION_NOT_FOUND)); + + repository.delete(educationApplication); + } + + public static void throwExceptionIfNotEqual(String phoneNumber1, String phoneNumber2) { + // 두 번호가 같지 않으면 예외 발생 + if (!phoneNumber2.equals(phoneNumber1)) { + throw new BaseException(Code.EDUCATION_APPLICATION_DIFFERENT_PHONE); + } } } diff --git a/src/main/java/com/example/DoroServer/domain/educationApplicationClassGroup/service/ClassGroupService.java b/src/main/java/com/example/DoroServer/domain/educationApplicationClassGroup/service/ClassGroupService.java index 7d749d6b..caaca117 100644 --- a/src/main/java/com/example/DoroServer/domain/educationApplicationClassGroup/service/ClassGroupService.java +++ b/src/main/java/com/example/DoroServer/domain/educationApplicationClassGroup/service/ClassGroupService.java @@ -25,8 +25,10 @@ public class ClassGroupService { /* Class Group */ // create - public ClassGroupRes addClassGroupToApplication(Long applicationId, ClassGroupReq classGroupReq) { - EducationApplication educationApplication = educationApplicationRepository.findById(applicationId) + public ClassGroupRes addClassGroupToApplication(Long applicationId, ClassGroupReq classGroupReq, + String phoneNumber) { + EducationApplication educationApplication = educationApplicationRepository + .findByIdAndPhoneNumber(applicationId, phoneNumber) .orElseThrow(() -> new BaseException(Code.EDUCATION_APPLICATION_NOT_FOUND)); ClassGroup classGroup = mapper.toEntity(classGroupReq); @@ -37,21 +39,33 @@ public ClassGroupRes addClassGroupToApplication(Long applicationId, ClassGroupRe } // update - public ClassGroupRes updateClassGroup(Long id, Long classGroupId, ClassGroupReq classGroupReq) { - ClassGroup target = classGroupRepository.findById(classGroupId) + public ClassGroupRes updateClassGroup(Long applicationId, Long classGroupId, ClassGroupReq classGroupReq, + String phoneNumber) { + EducationApplication educationApplication = educationApplicationRepository + .findByIdAndPhoneNumber(applicationId, phoneNumber) + .orElseThrow(() -> new BaseException(Code.EDUCATION_APPLICATION_NOT_FOUND)); + + ClassGroup classGroup = educationApplication.getClassGroups().stream() + .filter(group -> group.getId().equals(classGroupId)) + .findFirst() .orElseThrow(() -> new BaseException(Code.EDUCATION_CLASS_GROUP_NOT_FOUND)); - mapper.toEntity(classGroupReq, target); - ClassGroup updatedClassGroup = classGroupRepository.save(target); + mapper.toEntity(classGroupReq, classGroup); + ClassGroup updatedClassGroup = classGroupRepository.save(classGroup); return mapper.toDTO(updatedClassGroup); } // delete - public void deleteClassGroup(Long id, Long classGroupId) { - ClassGroup classGroup = classGroupRepository.findById(classGroupId) + public void deleteClassGroup(Long applicationId, Long classGroupId, String phoneNumber) { + EducationApplication educationApplication = educationApplicationRepository + .findByIdAndPhoneNumber(applicationId, phoneNumber) + .orElseThrow(() -> new BaseException(Code.EDUCATION_APPLICATION_NOT_FOUND)); + + educationApplication.getClassGroups().stream() + .filter(group -> group.getId().equals(classGroupId)) + .findFirst() .orElseThrow(() -> new BaseException(Code.EDUCATION_CLASS_GROUP_NOT_FOUND)); - classGroup.getEducationApplication().getId().equals(id); classGroupRepository.deleteById(classGroupId); } diff --git a/src/main/java/com/example/DoroServer/domain/lecture/dto/CreateLectureReq.java b/src/main/java/com/example/DoroServer/domain/lecture/dto/CreateLectureReq.java index e612db9b..ac05ffd4 100644 --- a/src/main/java/com/example/DoroServer/domain/lecture/dto/CreateLectureReq.java +++ b/src/main/java/com/example/DoroServer/domain/lecture/dto/CreateLectureReq.java @@ -3,7 +3,6 @@ import com.example.DoroServer.domain.lecture.entity.Lecture; import com.example.DoroServer.domain.lecture.entity.LectureDate; import com.example.DoroServer.domain.lecture.entity.LectureStatus; -import com.example.DoroServer.domain.lectureContent.dto.LectureContentDto; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.ArrayList; @@ -50,7 +49,7 @@ public class CreateLectureReq { @NotBlank private String staff; // 강의 스태프 수 @NotBlank - private String mainPayment; //강사 급여 + private String mainPayment; // 강사 급여 @NotBlank private String subPayment; @NotBlank @@ -61,6 +60,8 @@ public class CreateLectureReq { private String time; // 시간 @NotBlank private String remark; + + @Builder.Default @NotNull private List lectureDates = new ArrayList<>(); // 강의 날짜 @NotNull diff --git a/src/main/java/com/example/DoroServer/domain/lecture/entity/Lecture.java b/src/main/java/com/example/DoroServer/domain/lecture/entity/Lecture.java index d08e26be..9352ae95 100644 --- a/src/main/java/com/example/DoroServer/domain/lecture/entity/Lecture.java +++ b/src/main/java/com/example/DoroServer/domain/lecture/entity/Lecture.java @@ -50,19 +50,20 @@ public class Lecture extends BaseEntity { private String staff; // 강의 스태프 수 - private String mainPayment; //강사 급여 + private String mainPayment; // 강사 급여 private String subPayment; private String staffPayment; - private String transportCost;//교통비 + private String transportCost;// 교통비 private String time; // 시간 private String remark; // 강의 기타 사항 - @ElementCollection() + @Builder.Default + @ElementCollection @CollectionTable(name = "lecture_date", joinColumns = @JoinColumn(name = "lecture_id")) private List lectureDates = new ArrayList<>(); // 강의 날짜 @@ -72,7 +73,7 @@ public class Lecture extends BaseEntity { @Embedded private LectureDate lectureDate; // 강의 날짜 관련 [enrollStateDate, enrollEndDate] - //== 연관관계 매핑 ==// + // == 연관관계 매핑 ==// // Lecture와 LectureContent는 다대일(Many-to-One) 관계 @ManyToOne(fetch = LAZY) @@ -88,8 +89,8 @@ public void setLectureContent(LectureContent lectureContent) { this.lectureContent = lectureContent; } - public void changeLectureStatus(LectureStatus lectureStatus){ - this.status=lectureStatus; + public void changeLectureStatus(LectureStatus lectureStatus) { + this.status = lectureStatus; } } diff --git a/src/main/java/com/example/DoroServer/domain/lectureContent/api/LectureContentApi.java b/src/main/java/com/example/DoroServer/domain/lectureContent/api/LectureContentApi.java index 618d8e76..1965900c 100644 --- a/src/main/java/com/example/DoroServer/domain/lectureContent/api/LectureContentApi.java +++ b/src/main/java/com/example/DoroServer/domain/lectureContent/api/LectureContentApi.java @@ -1,50 +1,93 @@ package com.example.DoroServer.domain.lectureContent.api; - -import com.example.DoroServer.domain.lectureContent.dto.LectureContentDto; +import com.example.DoroServer.domain.lectureContent.dto.CreateLectureContentReq; +import com.example.DoroServer.domain.lectureContent.dto.LectureContentRes; import com.example.DoroServer.domain.lectureContent.dto.UpdateLectureContentReq; import com.example.DoroServer.domain.lectureContent.service.LectureContentService; +import com.example.DoroServer.domain.lectureContentImage.dto.LectureContentImageReq; +import com.example.DoroServer.domain.lectureContentImage.dto.LectureContentImageRes; +import com.example.DoroServer.domain.lectureContentImage.service.LectureContentImageService; import com.example.DoroServer.global.common.SuccessResponse; import java.util.List; import io.swagger.annotations.Api; +import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; import org.springframework.security.access.annotation.Secured; import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ModelAttribute; 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.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import javax.validation.Valid; -@Api(tags = "강의 자료") + +@Api(tags = "강의 컨텐츠") @RestController @RequestMapping("/lecture-contents") @RequiredArgsConstructor @Validated public class LectureContentApi { private final LectureContentService lectureContentService; - @Secured("ROLE_ADMIN") - @PostMapping() - public SuccessResponse createLectureContent(@RequestBody @Valid LectureContentDto lectureContentDto){ - Long lectureId = lectureContentService.createLecture(lectureContentDto); - return SuccessResponse.successResponse(lectureId); - } - @GetMapping() - public SuccessResponse findAllLectureContents(){ - List allLectureContents = lectureContentService.findAllLectureContents(); + private final LectureContentImageService lectureContentImageService; + + @ApiOperation(value = "모든 강의 컨텐츠 조회", notes = "인증된 사용자만 조회 가능합니다.") + @GetMapping + public SuccessResponse findAllLectureContents() { + List allLectureContents = lectureContentService.findAllLectureContents(); return SuccessResponse.successResponse(allLectureContents); } + + @ApiOperation(value = "강의 컨텐츠 추가", notes = "관리자만 생성 가능합니다. 이미지는 1개라도 MultipartFile[]로 보내야 합니다.") + @Secured("ROLE_ADMIN") + @PostMapping + public SuccessResponse createLectureContent(@ModelAttribute @Valid CreateLectureContentReq lectureContentReq) { + LectureContentRes createdLectureContent = lectureContentService.createLectureContent(lectureContentReq); + return SuccessResponse.successResponse(createdLectureContent); + } + + @ApiOperation(value = "강의 컨텐츠 수정", notes = "관리자만 수정 가능합니다. 해당 엔드포인트에선 이미지를 제외한 나머지 필드들만 수정 가능합니다.") @Secured("ROLE_ADMIN") @PatchMapping("/{id}") - public SuccessResponse updateLectureContent( + public SuccessResponse updateLectureContent( @PathVariable("id") Long id, - @RequestBody UpdateLectureContentReq updateLectureContentReq){ - Long updateLectureContentId = lectureContentService.updateLectureContent(id, updateLectureContentReq); - return SuccessResponse.successResponse(updateLectureContentId + "is updated"); + @ModelAttribute UpdateLectureContentReq updateLectureContentReq) { + LectureContentRes updatedLectureContent = lectureContentService.updateLectureContent(id, + updateLectureContentReq); + return SuccessResponse.successResponse(updatedLectureContent); + } + + @ApiOperation(value = "강의 컨텐츠 삭제", notes = "관리자만 삭제 가능합니다.") + @Secured("ROLE_ADMIN") + @DeleteMapping("/{id}") + public SuccessResponse deleteLectureContent(@PathVariable("id") Long id) { + lectureContentService.deleteLectureContent(id); + return SuccessResponse.successResponse("강의 자료 삭제 성공"); + } + + /* 강의 컨텐츠의 이미지 추가 및 삭제 API */ + + @ApiOperation(value = "강의 컨텐츠 이미지 추가", notes = "관리자만 추가 가능합니다. 이미지는 1개라도 MultipartFile[]로 보내야 합니다.") + @Secured("ROLE_ADMIN") + @PostMapping("/{id}/images") + public SuccessResponse addLectureContentImages(@PathVariable("id") Long id, + @ModelAttribute @Valid LectureContentImageReq lectureContentImageReq) { + List addedLectureContentImages = lectureContentImageService.addLectureContentImages(id, + lectureContentImageReq); + return SuccessResponse.successResponse(addedLectureContentImages); + } + + @ApiOperation(value = "강의 컨텐츠 이미지 삭제", notes = "관리자만 삭제 가능합니다. 강의 컨텐츠와 강의 컨텐츠의 이미지 id를 통해 삭제합니다.") + @Secured("ROLE_ADMIN") + @DeleteMapping("/{id}/images/{imageId}") + public SuccessResponse deleteLectureContentImage(@PathVariable("id") Long id, + @PathVariable("imageId") Long imageId) { + lectureContentImageService.deleteLectureContentImage(id, imageId); + return SuccessResponse.successResponse("강의 컨텐츠 이미지 삭제 성공"); } } diff --git a/src/main/java/com/example/DoroServer/domain/lectureContent/dto/CreateLectureContentReq.java b/src/main/java/com/example/DoroServer/domain/lectureContent/dto/CreateLectureContentReq.java new file mode 100644 index 00000000..2723d082 --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/lectureContent/dto/CreateLectureContentReq.java @@ -0,0 +1,42 @@ +package com.example.DoroServer.domain.lectureContent.dto; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import org.springframework.web.multipart.MultipartFile; + +import com.example.DoroServer.global.s3.MultipartFileUtils; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@NoArgsConstructor +@Builder +@AllArgsConstructor +@Setter +public class CreateLectureContentReq { + + @NotBlank + private String kit; // 강의 사용 키트 + + @NotBlank + private String detail; // 강의 세부 구성 + + @NotNull + private String requirement; // 강의 자격 요건 + + @NotNull + private String content; // 강의 컨텐츠 + + @NotNull + private MultipartFile[] files; // 강의 컨텐츠 이미지 파일들 + + public void validateFiles() { + MultipartFileUtils.validateMultipartFiles(this.files); + } + +} diff --git a/src/main/java/com/example/DoroServer/domain/lectureContent/dto/LectureContentDto.java b/src/main/java/com/example/DoroServer/domain/lectureContent/dto/LectureContentDto.java index 71a4ceb3..2fa0b267 100644 --- a/src/main/java/com/example/DoroServer/domain/lectureContent/dto/LectureContentDto.java +++ b/src/main/java/com/example/DoroServer/domain/lectureContent/dto/LectureContentDto.java @@ -1,12 +1,12 @@ package com.example.DoroServer.domain.lectureContent.dto; -import com.example.DoroServer.domain.lecture.entity.Lecture; -import com.example.DoroServer.domain.lectureContent.entity.LectureContent; +import java.util.List; import javax.validation.constraints.NotBlank; -import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; +import com.example.DoroServer.domain.lectureContentImage.dto.LectureContentImageDto; + import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; @@ -28,16 +28,11 @@ public class LectureContentDto { @NotBlank private String detail; // 강의 세부 구성 - @NotNull private String requirement; // 강의 자격 요건 @NotNull - private String content; - - - - - + private String content; // 강의 내용 + private List images; // 강의 자료 이미지 } diff --git a/src/main/java/com/example/DoroServer/domain/lectureContent/dto/LectureContentMapper.java b/src/main/java/com/example/DoroServer/domain/lectureContent/dto/LectureContentMapper.java index 22e990b8..26240d23 100644 --- a/src/main/java/com/example/DoroServer/domain/lectureContent/dto/LectureContentMapper.java +++ b/src/main/java/com/example/DoroServer/domain/lectureContent/dto/LectureContentMapper.java @@ -1,11 +1,19 @@ package com.example.DoroServer.domain.lectureContent.dto; import com.example.DoroServer.domain.lectureContent.entity.LectureContent; + +import java.util.List; + import org.mapstruct.Mapper; @Mapper(componentModel = "spring") public interface LectureContentMapper { - //LectureContent -> LectureContentDto - LectureContentDto toLectureContentDto(LectureContent lectureContent); LectureContent toLectureContent(LectureContentDto lectureContentDto); + + LectureContentDto toLectureContentDto(LectureContent lectureContent); + + LectureContentRes toLectureContentRes(LectureContent lectureContent); + + List toLectureContentResList(List lectureContentList); + } diff --git a/src/main/java/com/example/DoroServer/domain/lectureContent/dto/LectureContentRes.java b/src/main/java/com/example/DoroServer/domain/lectureContent/dto/LectureContentRes.java new file mode 100644 index 00000000..2d77e01c --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/lectureContent/dto/LectureContentRes.java @@ -0,0 +1,22 @@ +package com.example.DoroServer.domain.lectureContent.dto; + +import java.util.List; + +import com.example.DoroServer.domain.lectureContentImage.dto.LectureContentImageRes; + +import lombok.Builder; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@Builder +@AllArgsConstructor +public class LectureContentRes { + + private Long id; // PK + private String kit; // 강의 사용 키트 + private String detail; // 강의 세부 구성 + private String requirement; // 강의 자격 요건 + private String content; // 강의 컨텐츠 + private List images; // 강의 컨텐츠 이미지 파일들 +} diff --git a/src/main/java/com/example/DoroServer/domain/lectureContent/dto/UpdateLectureContentReq.java b/src/main/java/com/example/DoroServer/domain/lectureContent/dto/UpdateLectureContentReq.java index b0b59a82..a1c94e5a 100644 --- a/src/main/java/com/example/DoroServer/domain/lectureContent/dto/UpdateLectureContentReq.java +++ b/src/main/java/com/example/DoroServer/domain/lectureContent/dto/UpdateLectureContentReq.java @@ -1,15 +1,16 @@ package com.example.DoroServer.domain.lectureContent.dto; - import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.Setter; @Getter -@Builder @NoArgsConstructor +@Builder @AllArgsConstructor +@Setter public class UpdateLectureContentReq { private String kit; // 강의 사용 키트 @@ -17,5 +18,6 @@ public class UpdateLectureContentReq { private String remark; // 강의 기타 사항 - private String content; + private String content; // 강의 컨텐츠 + } diff --git a/src/main/java/com/example/DoroServer/domain/lectureContent/entity/LectureContent.java b/src/main/java/com/example/DoroServer/domain/lectureContent/entity/LectureContent.java index becd08ae..d1ff8017 100644 --- a/src/main/java/com/example/DoroServer/domain/lectureContent/entity/LectureContent.java +++ b/src/main/java/com/example/DoroServer/domain/lectureContent/entity/LectureContent.java @@ -3,11 +3,17 @@ import javax.persistence.GenerationType; import lombok.*; +import java.util.ArrayList; +import java.util.List; + +import javax.persistence.CascadeType; import javax.persistence.Column; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; -import javax.validation.constraints.NotBlank; +import javax.persistence.OneToMany; + +import com.example.DoroServer.domain.lectureContentImage.entity.LectureContentImage; @Entity @Getter @@ -27,5 +33,13 @@ public class LectureContent { private String requirement; // 강의 자격 요건 - private String content; + private String content; // 강의 컨텐츠 + + // == 연관관계 매핑 ==// + + // LectureContent과 LectureContentImage는 일대다(One-to-Many) 관계 + @Builder.Default + @OneToMany(cascade = CascadeType.ALL) + private List images = new ArrayList<>(); + } diff --git a/src/main/java/com/example/DoroServer/domain/lectureContent/service/ContentImageS3Service.java b/src/main/java/com/example/DoroServer/domain/lectureContent/service/ContentImageS3Service.java new file mode 100644 index 00000000..1d076282 --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/lectureContent/service/ContentImageS3Service.java @@ -0,0 +1,12 @@ +package com.example.DoroServer.domain.lectureContent.service; + +import org.springframework.web.multipart.MultipartFile; +import java.util.List; + +import com.example.DoroServer.domain.lectureContentImage.entity.LectureContentImage; + +public interface ContentImageS3Service { + List uploadS3Images(MultipartFile[] files); + + void deleteS3Image(LectureContentImage lectureContentImage); +} diff --git a/src/main/java/com/example/DoroServer/domain/lectureContent/service/ContentImageS3ServiceImpl.java b/src/main/java/com/example/DoroServer/domain/lectureContent/service/ContentImageS3ServiceImpl.java new file mode 100644 index 00000000..aaaf313d --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/lectureContent/service/ContentImageS3ServiceImpl.java @@ -0,0 +1,41 @@ +package com.example.DoroServer.domain.lectureContent.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +import com.example.DoroServer.domain.lectureContentImage.entity.LectureContentImage; +import com.example.DoroServer.global.s3.AwsS3ServiceImpl; + +import java.util.Arrays; +import java.util.stream.Collectors; +import lombok.AllArgsConstructor; + +@AllArgsConstructor +@Service +public class ContentImageS3ServiceImpl implements ContentImageS3Service { + private final AwsS3ServiceImpl awsS3Service; + + private static final int fileNameLength = 40; // UUID(36)+확장자(4) + + String getFileNameFrom(String url) { + return url.substring(url.length() - fileNameLength); + } + + @Override + public List uploadS3Images(MultipartFile[] files) { + // S3 BUCKET/lecture-content에 파일 업로드 후, 업로드된 파일들의 URL 반환 + return Arrays.stream(files) + .map(file -> awsS3Service.upload(file, "lecture-content")) + .collect(Collectors.toList()); + } + + @Override + public void deleteS3Image(LectureContentImage lectureContentImage) { + String uploadedUrl = lectureContentImage.getUrl(); + String fileName = getFileNameFrom(uploadedUrl); + awsS3Service.deleteImage2(fileName); + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/DoroServer/domain/lectureContent/service/LectureContentService.java b/src/main/java/com/example/DoroServer/domain/lectureContent/service/LectureContentService.java index f18c88c2..3dd8fa7e 100644 --- a/src/main/java/com/example/DoroServer/domain/lectureContent/service/LectureContentService.java +++ b/src/main/java/com/example/DoroServer/domain/lectureContent/service/LectureContentService.java @@ -1,44 +1,79 @@ package com.example.DoroServer.domain.lectureContent.service; -import com.example.DoroServer.domain.lecture.repository.LectureRepository; -import com.example.DoroServer.domain.lectureContent.dto.LectureContentDto; import com.example.DoroServer.domain.lectureContent.dto.LectureContentMapper; +import com.example.DoroServer.domain.lectureContent.dto.CreateLectureContentReq; +import com.example.DoroServer.domain.lectureContent.dto.LectureContentRes; import com.example.DoroServer.domain.lectureContent.dto.UpdateLectureContentReq; import com.example.DoroServer.domain.lectureContent.entity.LectureContent; import com.example.DoroServer.domain.lectureContent.repository.LectureContentRepository; +import com.example.DoroServer.domain.lectureContentImage.entity.LectureContentImage; import com.example.DoroServer.global.exception.BaseException; import com.example.DoroServer.global.exception.Code; -import java.util.List; + import java.util.stream.Collectors; -import lombok.RequiredArgsConstructor; +import java.util.List; import org.modelmapper.ModelMapper; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import lombok.RequiredArgsConstructor; @Service @RequiredArgsConstructor @Transactional public class LectureContentService { + private final LectureContentRepository lectureContentRepository; private final LectureContentMapper lectureContentMapper; private final ModelMapper modelMapper; - public Long createLecture(LectureContentDto lectureContentDto){ - LectureContent lectureContent = lectureContentMapper.toLectureContent(lectureContentDto); - LectureContent savedLectureContent = lectureContentRepository.save(lectureContent); - return savedLectureContent.getId(); - } - public List findAllLectureContents(){ + private final ContentImageS3ServiceImpl contentImageS3Service; + + /* 서비스 코드 */ + + public List findAllLectureContents() { List lectureContentList = lectureContentRepository.findAll(); - List lectureContentResList = lectureContentList.stream() - .map(lectureContent -> lectureContentMapper.toLectureContentDto(lectureContent)) + + return lectureContentMapper.toLectureContentResList(lectureContentList); + } + + public LectureContentRes createLectureContent(CreateLectureContentReq lectureContentReq) { + lectureContentReq.validateFiles(); + LectureContent lectureContent = modelMapper.map(lectureContentReq, LectureContent.class); + + // lectureContentReq의 files를 S3에 업로드 후, 업로드된 파일들의 URL을 LectureContentImage로 변환 + List uploadedUrls = contentImageS3Service.uploadS3Images(lectureContentReq.getFiles()); + List lectureContentImages = uploadedUrls.stream() + .map(LectureContentImage::new) .collect(Collectors.toList()); - return lectureContentResList; + + lectureContent.getImages().addAll(lectureContentImages); + LectureContent savedLectureContent = lectureContentRepository.save(lectureContent); + + return lectureContentMapper.toLectureContentRes(savedLectureContent); + } + + public LectureContentRes updateLectureContent(Long id, UpdateLectureContentReq updateLectureContentReq) { + LectureContent lectureContent = findLectureContentById(id); + + modelMapper.map(updateLectureContentReq, lectureContent); + + LectureContent updatedLectureContent = lectureContentRepository.save(lectureContent); + + return lectureContentMapper.toLectureContentRes(updatedLectureContent); } - public Long updateLectureContent(Long id, UpdateLectureContentReq updateLectureContentReq){ - LectureContent lectureContent = lectureContentRepository.findById(id) + public void deleteLectureContent(Long id) { + LectureContent lectureContent = findLectureContentById(id); + + lectureContent.getImages().forEach(contentImageS3Service::deleteS3Image); + + lectureContentRepository.delete(lectureContent); + } + + /* 서비스 코드에서 사용되는 메서드 */ + + LectureContent findLectureContentById(Long id) { + return lectureContentRepository.findById(id) .orElseThrow(() -> new BaseException(Code.LECTURE_CONTENT_NOT_FOUND)); - modelMapper.map(updateLectureContentReq,lectureContent); - return lectureContent.getId(); } + } diff --git a/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageDto.java b/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageDto.java new file mode 100644 index 00000000..c2bbad87 --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageDto.java @@ -0,0 +1,17 @@ +package com.example.DoroServer.domain.lectureContentImage.dto; + +import javax.validation.constraints.NotNull; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@NoArgsConstructor +@Setter +public class LectureContentImageDto { + private Long id; + + @NotNull + private String url; +} diff --git a/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageMapper.java b/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageMapper.java new file mode 100644 index 00000000..8c9fbff5 --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageMapper.java @@ -0,0 +1,15 @@ +package com.example.DoroServer.domain.lectureContentImage.dto; + +import java.util.List; + +import org.mapstruct.Mapper; + +import com.example.DoroServer.domain.lectureContentImage.entity.LectureContentImage; + +@Mapper(componentModel = "spring") +public interface LectureContentImageMapper { + + LectureContentImageRes toLectureContentImageRes(LectureContentImage lectureContentImage); + + List toLectureContentImageResList(List lectureContentImageList); +} diff --git a/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageReq.java b/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageReq.java new file mode 100644 index 00000000..4ca25b2f --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageReq.java @@ -0,0 +1,27 @@ +package com.example.DoroServer.domain.lectureContentImage.dto; + +import javax.validation.constraints.NotNull; + +import org.springframework.web.multipart.MultipartFile; + +import com.example.DoroServer.global.s3.MultipartFileUtils; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@NoArgsConstructor +@Builder +@AllArgsConstructor +@Setter +public class LectureContentImageReq { + @NotNull + private MultipartFile[] files; // 강의 컨텐츠 이미지 파일들 -> 하나만 보내면 무시됨 + + public void validateFiles() { + MultipartFileUtils.validateMultipartFiles(this.files); + } +} diff --git a/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageRes.java b/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageRes.java new file mode 100644 index 00000000..429a79e4 --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/lectureContentImage/dto/LectureContentImageRes.java @@ -0,0 +1,17 @@ +package com.example.DoroServer.domain.lectureContentImage.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@NoArgsConstructor +@Setter +@AllArgsConstructor +@Builder +public class LectureContentImageRes { + private Long id; // PK + private String url; // uploaded image url +} diff --git a/src/main/java/com/example/DoroServer/domain/lectureContentImage/entity/LectureContentImage.java b/src/main/java/com/example/DoroServer/domain/lectureContentImage/entity/LectureContentImage.java new file mode 100644 index 00000000..d0a07923 --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/lectureContentImage/entity/LectureContentImage.java @@ -0,0 +1,29 @@ +package com.example.DoroServer.domain.lectureContentImage.entity; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class LectureContentImage { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String url; + + public LectureContentImage(String url) { + this.url = url; + } + +} diff --git a/src/main/java/com/example/DoroServer/domain/lectureContentImage/repository/LectureContentImageRepository.java b/src/main/java/com/example/DoroServer/domain/lectureContentImage/repository/LectureContentImageRepository.java new file mode 100644 index 00000000..30a15fd5 --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/lectureContentImage/repository/LectureContentImageRepository.java @@ -0,0 +1,8 @@ +package com.example.DoroServer.domain.lectureContentImage.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.DoroServer.domain.lectureContentImage.entity.LectureContentImage; + +public interface LectureContentImageRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/example/DoroServer/domain/lectureContentImage/service/LectureContentImageService.java b/src/main/java/com/example/DoroServer/domain/lectureContentImage/service/LectureContentImageService.java new file mode 100644 index 00000000..6518f365 --- /dev/null +++ b/src/main/java/com/example/DoroServer/domain/lectureContentImage/service/LectureContentImageService.java @@ -0,0 +1,73 @@ +package com.example.DoroServer.domain.lectureContentImage.service; + +import java.util.List; +import java.util.stream.Collectors; + +import javax.transaction.Transactional; + +import org.springframework.stereotype.Service; + +import com.example.DoroServer.domain.lectureContent.entity.LectureContent; +import com.example.DoroServer.domain.lectureContent.repository.LectureContentRepository; +import com.example.DoroServer.domain.lectureContent.service.ContentImageS3ServiceImpl; +import com.example.DoroServer.domain.lectureContentImage.dto.LectureContentImageMapper; +import com.example.DoroServer.domain.lectureContentImage.dto.LectureContentImageReq; +import com.example.DoroServer.domain.lectureContentImage.dto.LectureContentImageRes; +import com.example.DoroServer.domain.lectureContentImage.entity.LectureContentImage; +import com.example.DoroServer.domain.lectureContentImage.repository.LectureContentImageRepository; +import com.example.DoroServer.global.exception.BaseException; +import com.example.DoroServer.global.exception.Code; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +@Transactional +public class LectureContentImageService { + private final LectureContentImageRepository lectureContentImageRepository; + private final LectureContentRepository lectureContentRepository; + private final LectureContentImageMapper lectureContentImageMapper; + private final ContentImageS3ServiceImpl contentImageS3Service; + + public List addLectureContentImages(Long id, + LectureContentImageReq lectureContentImageReq) { + lectureContentImageReq.validateFiles(); + LectureContent lectureContent = findLectureContentById(id); + + // lectureContentReq의 files를 S3에 업로드 후, 업로드된 파일들의 URL을 LectureContentImage로 변환 + List uploadedUrls = contentImageS3Service.uploadS3Images(lectureContentImageReq.getFiles()); + List lectureContentImages = uploadedUrls.stream() + .map(LectureContentImage::new) + .collect(Collectors.toList()); + + lectureContentImages = lectureContentImageRepository.saveAll(lectureContentImages); + lectureContent.getImages().addAll(lectureContentImages); + + return lectureContentImageMapper.toLectureContentImageResList(lectureContentImages); + } + + public void deleteLectureContentImage(Long contentId, Long imageId) { + LectureContent lectureContent = findLectureContentById(contentId); + LectureContentImage lectureContentImage = getLectureContentImage(imageId, lectureContent); + + contentImageS3Service.deleteS3Image(lectureContentImage); + lectureContent.getImages().remove(lectureContentImage); + lectureContentImageRepository.delete(lectureContentImage); + } + + /* 서비스 코드에서 사용되는 메서드 */ + + private LectureContentImage getLectureContentImage(Long imageId, LectureContent lectureContent) { + LectureContentImage lectureContentImage = lectureContent.getImages().stream() + .filter(image -> image.getId().equals(imageId)) + .findFirst() + .orElseThrow(() -> new BaseException(Code.LECTURE_CONTENT_IMAGE_NOT_FOUND)); + return lectureContentImage; + } + + LectureContent findLectureContentById(Long id) { + return lectureContentRepository.findById(id) + .orElseThrow(() -> new BaseException(Code.LECTURE_CONTENT_NOT_FOUND)); + } + +} diff --git a/src/main/java/com/example/DoroServer/domain/user/entity/User.java b/src/main/java/com/example/DoroServer/domain/user/entity/User.java index 565c7be1..3540f0ae 100644 --- a/src/main/java/com/example/DoroServer/domain/user/entity/User.java +++ b/src/main/java/com/example/DoroServer/domain/user/entity/User.java @@ -4,18 +4,11 @@ import com.example.DoroServer.domain.token.entity.Token; import java.time.LocalDate; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; + import org.hibernate.annotations.ColumnDefault; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; -import javax.persistence.Column; -import javax.persistence.Entity; -import javax.persistence.GeneratedValue; -import javax.persistence.Id; import java.util.ArrayList; import java.util.Collection; import com.example.DoroServer.domain.chat.entity.Chat; @@ -71,6 +64,7 @@ public class User extends BaseEntity implements UserDetails { private String profileImg; // 사용자 이미 + @Builder.Default @OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true) private List tokens = new ArrayList<>(); // 사용자 보유 FCM 토큰 @@ -128,7 +122,7 @@ public void updateGeneration(int generation) { this.generation = generation; } - public void updateBirth(LocalDate birth){ + public void updateBirth(LocalDate birth) { this.birth = birth; } @@ -136,7 +130,7 @@ public void updatePhone(String phone) { this.phone = phone; } - public void toInactive(){ + public void toInactive() { this.isActive = false; } diff --git a/src/main/java/com/example/DoroServer/global/config/SecurityConfig.java b/src/main/java/com/example/DoroServer/global/config/SecurityConfig.java index 500560bd..cfdf63a7 100644 --- a/src/main/java/com/example/DoroServer/global/config/SecurityConfig.java +++ b/src/main/java/com/example/DoroServer/global/config/SecurityConfig.java @@ -1,5 +1,7 @@ package com.example.DoroServer.global.config; +import com.example.DoroServer.domain.educationApplication.api.SessionFilter; +import com.example.DoroServer.domain.educationApplication.api.SessionFilterHandler; import com.example.DoroServer.global.jwt.*; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; @@ -50,7 +52,6 @@ public WebSecurityCustomizer webSecurityCustomizer() { "/check/phone", "/find/account", "/change/password", - "/education-application/**", "/", "/swagger-ui.html", "/v3/api-docs/**", @@ -82,10 +83,13 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .and() .authorizeRequests() + .antMatchers("/education-application/**").permitAll() // 훚대폰 인증 세션 필터 적용 .anyRequest().authenticated() .and() .addFilter(corsConfig.corsFilter()) + .addFilterBefore(new SessionFilter(redisService), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(new SessionFilterHandler(), SessionFilter.class) .addFilterBefore(new JwtAuthenticationFilter(jwtTokenProvider, redisService), UsernamePasswordAuthenticationFilter.class) .addFilterBefore(new JwtAuthenticationExceptionHandler(), JwtAuthenticationFilter.class); diff --git a/src/main/java/com/example/DoroServer/global/exception/Code.java b/src/main/java/com/example/DoroServer/global/exception/Code.java index b6a25122..245413e0 100644 --- a/src/main/java/com/example/DoroServer/global/exception/Code.java +++ b/src/main/java/com/example/DoroServer/global/exception/Code.java @@ -11,83 +11,89 @@ @RequiredArgsConstructor public enum Code { - SUCCESS(OK, "SUCCESS", "OK"), - // Common - BAD_REQUEST(HttpStatus.BAD_REQUEST, "AUTH001", "잘못된 요청입니다."), - // 인증 관련 오류 AUTH001,AUTH002... - FORBIDDEN(HttpStatus.FORBIDDEN, "AUTH001", "접근 권한이 없습니다."), - UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "AUTH002", "인증정보가 유효하지 않습니다."), - JWT_BAD_REQUEST(HttpStatus.UNAUTHORIZED, "AUTH003", "잘못된 JWT 서명입니다."), - JWT_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "AUTH004", "토큰이 만료되었습니다."), - JWT_UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH006", "지원하지 않는 JWT 토큰입니다."), - JWT_TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AUTH007", "유효한 JWT 토큰이 없습니다."), - UNAUTHORIZED_PHONE_NUMBER(HttpStatus.UNAUTHORIZED, "AUTH008", "인증되지 않은 전화번호입니다."), - EXIST_ACCOUNT(HttpStatus.CONFLICT, "AUTH009", "이미 존재하는 아이디입니다."), - PASSWORD_DID_NOT_MATCH(HttpStatus.BAD_REQUEST, "AUTH010", "비밀번호가 일치하지 않습니다."), - DORO_ADMIN_AUTH_FAILED(HttpStatus.UNAUTHORIZED, "AUTH011", "관리자 인증번호가 일치하지 않습니다."), - DORO_USER_AUTH_FAILED(HttpStatus.UNAUTHORIZED, "AUTH012", "도로 인증번호가 일치하지 않습니다."), - ACCOUNT_NOT_FOUND(HttpStatus.NOT_FOUND, "AUTH013", "가입된 아이디가 없습니다."), - EXIST_PHONE(HttpStatus.CONFLICT, "AUTH014", "이미 존재하는 휴대폰 번호입니다."), - REFRESH_TOKEN_DID_NOT_MATCH(HttpStatus.BAD_REQUEST, "AUTH015", "RefreshToken 정보가 일치하지 않습니다."), - - WITHDRAWAL_FAILED(HttpStatus.BAD_REQUEST, "AUTH016", "회원 탈퇴에 실패했습니다."), - - // USER 관련 오류 U001,U002... - USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER001", "사용자가 존재하지 않습니다"), - - // 강의 관련 오류 L001,L002.. - LECTURE_NOT_FOUND(HttpStatus.BAD_REQUEST, "LEC001", "강의가 존재하지 않습니다."), - LECTURE_CONTENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "LEC002", "강의 컨텐츠가 존재하지 않습니다."), - - // 신청 강사 관련 오류 - TUTOR_NOT_FOUND(HttpStatus.BAD_REQUEST, "TUTOR001", "신청하지 않은 강사입니다."), - ALREADY_EXIST(HttpStatus.BAD_REQUEST, "TUTOR002", "이미 신청하셨습니다"), - USER_LECTURE_NOT_FOUND(HttpStatus.BAD_REQUEST, "TUTOR003", "신청내역이 존재하지 않습니다"), - - // 알림톡 관련 오류 M001, M002... - MESSAGE_SEND_FAILED(HttpStatus.BAD_REQUEST, "M001", "메시지 전송이 실패했습니다. 올바른 번호인지 확인하세요."), - VERIFICATION_DID_NOT_MATCH(HttpStatus.BAD_REQUEST, "M002", "인증 번호가 일치하지 않습니다."), - - // Notification 관련 오류 - NOTIFICATION_PUSH_FAIL(HttpStatus.BAD_REQUEST, "NOTI001", "FCM 알림 푸쉬에 실패했습니다."), - NOTIFICATION_NOT_FOUND(HttpStatus.BAD_REQUEST, "NOTI002", "알림을 찾을 수 없습니다."), - - // 공지 관련 오류 - ANNOUNCEMENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "ANNO001", "공지를 찾을 수 없습니다."), - - // S3 관련 오류 S001, S002... - RESIZE_FAILED(HttpStatus.BAD_REQUEST, "S001", "파일 리사이징을 실패했습니다."), - EMPTY_FILE(HttpStatus.NO_CONTENT, "S002", "업로드 파일이 없습니다."), - UPLOAD_FAILED(HttpStatus.BAD_REQUEST, "S003", "업로드 중 오류가 발생했습니다."), - FILE_DELETE_FAILED(HttpStatus.BAD_REQUEST, "S004", "파일 삭제 중 오류가 발생했습니다."), - - // UserNotification 관련 오류 - USER_NOTIFICATION_NOT_FOUND(HttpStatus.BAD_REQUEST, "USERNOTI001", "UserNotification을 찾을 수 없습니다."), - - // fcm 관련 오류 - FCM_UNSPECIFIED_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "FCM001", - "현재 알림을 보낼 수 없습니다. 다시 한번 시도하시고 안되면 관리자에게 문의 바랍니다."), - FCM_CERTIFICATION_IS_NOT_VALID(HttpStatus.SERVICE_UNAVAILABLE, "FCM002", - "FCM 계정을 확인해주세요 apns 혹은 web 인증서에 문제가 있습니다."), - FCM_INTERNAL_SERVER_ERROR(HttpStatus.SERVICE_UNAVAILABLE, "FCM003", - "현재 FCM 서버에 문제가 있습니다, 다시 한번 시도해보시고 계속 문제가 있으면 FCM 측에 문의를 해주세요"), - FCM_INVALID_ARGUMENT(HttpStatus.INTERNAL_SERVER_ERROR, "FCM004", - "어플리케이션 이용이 오래되어 토큰이 삭제되었습니다, 로그아웃 혹은 앱을 지웠다가 다시 접속해주세요."), - FCM_UNREGISTERED(HttpStatus.INTERNAL_SERVER_ERROR, "FCM005", - "어플리케이션 이용이 오래되어 토큰이 삭제되었습니다, 로그아웃 혹은 앱을 지웠다가 다시 접속해주세요"), - - // 강의(교육) 신청 관련 오류 - EDUCATION_APPLICATION_NOT_FOUND(HttpStatus.BAD_REQUEST, "EDU001", "교육 신청서를 찾을 수 없습니다."), - EDUCATION_CLASS_GROUP_NOT_FOUND(HttpStatus.BAD_REQUEST, "EDU002", "교육 학급을 찾을 수 없습니다."), - - METHOD_ARGUMENT_NOT_VALID(HttpStatus.BAD_REQUEST, "C-0004", "요청 인자가 유효하지 않음"), - METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "C-0002", "허용되지 않은 Request Method 호출"), - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "C-0003", "내부 서버 오류"), - INVALID_TYPE_VALUE(HttpStatus.BAD_REQUEST, "C005", "유효하지 않은 값 타입"), - JSON_SYNTAX_ERROR(HttpStatus.BAD_REQUEST, "C-006", "json 형식이 잘못되었습니다."), - NOT_SUPPORTED_BODY_TYPE(HttpStatus.BAD_REQUEST, "C-007", "지원하지 않는 형식의 Request Body 형식입니다. json 형식으로 보내주세요"); - - private final HttpStatus httpStatus; - private final String code; - private final String message; + SUCCESS(OK, "SUCCESS", "OK"), + // Common + BAD_REQUEST(HttpStatus.BAD_REQUEST, "AUTH001", "잘못된 요청입니다."), + // 인증 관련 오류 AUTH001,AUTH002... + FORBIDDEN(HttpStatus.FORBIDDEN, "AUTH001", "접근 권한이 없습니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "AUTH002", "인증정보가 유효하지 않습니다."), + JWT_BAD_REQUEST(HttpStatus.UNAUTHORIZED, "AUTH003", "잘못된 JWT 서명입니다."), + JWT_TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "AUTH004", "토큰이 만료되었습니다."), + JWT_UNSUPPORTED_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH006", "지원하지 않는 JWT 토큰입니다."), + JWT_TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AUTH007", "유효한 JWT 토큰이 없습니다."), + UNAUTHORIZED_PHONE_NUMBER(HttpStatus.UNAUTHORIZED, "AUTH008", "인증되지 않은 전화번호입니다."), + EXIST_ACCOUNT(HttpStatus.CONFLICT, "AUTH009", "이미 존재하는 아이디입니다."), + PASSWORD_DID_NOT_MATCH(HttpStatus.BAD_REQUEST, "AUTH010", "비밀번호가 일치하지 않습니다."), + DORO_ADMIN_AUTH_FAILED(HttpStatus.UNAUTHORIZED, "AUTH011", "관리자 인증번호가 일치하지 않습니다."), + DORO_USER_AUTH_FAILED(HttpStatus.UNAUTHORIZED, "AUTH012", "도로 인증번호가 일치하지 않습니다."), + ACCOUNT_NOT_FOUND(HttpStatus.NOT_FOUND, "AUTH013", "가입된 아이디가 없습니다."), + EXIST_PHONE(HttpStatus.CONFLICT, "AUTH014", "이미 존재하는 휴대폰 번호입니다."), + REFRESH_TOKEN_DID_NOT_MATCH(HttpStatus.BAD_REQUEST, "AUTH015", "RefreshToken 정보가 일치하지 않습니다."), + SESSION_ID_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AUTH016", "SessionId 정보가 없습니다."), + SESSION_ID_NOT_VALID(HttpStatus.UNAUTHORIZED, "AUTH017", "SessionId 정보가 유효하지 않습니다."), + + WITHDRAWAL_FAILED(HttpStatus.BAD_REQUEST, "AUTH016", "회원 탈퇴에 실패했습니다."), + + // USER 관련 오류 U001,U002... + USER_NOT_FOUND(HttpStatus.BAD_REQUEST, "USER001", "사용자가 존재하지 않습니다"), + + // 강의 관련 오류 L001,L002.. + LECTURE_NOT_FOUND(HttpStatus.BAD_REQUEST, "LEC001", "강의가 존재하지 않습니다."), + LECTURE_CONTENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "LEC002", "강의 컨텐츠가 존재하지 않습니다."), + LECTURE_CONTENT_IMAGE_SIZE_OVER(HttpStatus.BAD_REQUEST, "LEC003", "이미지 사이즈가 10MB를 초과했습니다."), + LECTURE_CONTENT_INVAILD_IMAGE_COUNT(HttpStatus.BAD_REQUEST, "LEC004", "이미지는 1장 이상 100장 이하로 업로드해주세요."), + LECTURE_CONTENT_IMAGE_NOT_FOUND(HttpStatus.BAD_REQUEST, "LEC005", "이미지가 존재하지 않습니다."), + + // 신청 강사 관련 오류 + TUTOR_NOT_FOUND(HttpStatus.BAD_REQUEST, "TUTOR001", "신청하지 않은 강사입니다."), + ALREADY_EXIST(HttpStatus.BAD_REQUEST, "TUTOR002", "이미 신청하셨습니다"), + USER_LECTURE_NOT_FOUND(HttpStatus.BAD_REQUEST, "TUTOR003", "신청내역이 존재하지 않습니다"), + + // 알림톡 관련 오류 M001, M002... + MESSAGE_SEND_FAILED(HttpStatus.BAD_REQUEST, "M001", "메시지 전송이 실패했습니다. 올바른 번호인지 확인하세요."), + VERIFICATION_DID_NOT_MATCH(HttpStatus.BAD_REQUEST, "M002", "인증 번호가 일치하지 않습니다."), + + // Notification 관련 오류 + NOTIFICATION_PUSH_FAIL(HttpStatus.BAD_REQUEST, "NOTI001", "FCM 알림 푸쉬에 실패했습니다."), + NOTIFICATION_NOT_FOUND(HttpStatus.BAD_REQUEST, "NOTI002", "알림을 찾을 수 없습니다."), + + // 공지 관련 오류 + ANNOUNCEMENT_NOT_FOUND(HttpStatus.BAD_REQUEST, "ANNO001", "공지를 찾을 수 없습니다."), + + // S3 관련 오류 S001, S002... + RESIZE_FAILED(HttpStatus.BAD_REQUEST, "S001", "파일 리사이징을 실패했습니다."), + EMPTY_FILE(HttpStatus.NO_CONTENT, "S002", "업로드 파일이 없습니다."), + UPLOAD_FAILED(HttpStatus.BAD_REQUEST, "S003", "업로드 중 오류가 발생했습니다."), + FILE_DELETE_FAILED(HttpStatus.BAD_REQUEST, "S004", "파일 삭제 중 오류가 발생했습니다."), + + // UserNotification 관련 오류 + USER_NOTIFICATION_NOT_FOUND(HttpStatus.BAD_REQUEST, "USERNOTI001", "UserNotification을 찾을 수 없습니다."), + + // fcm 관련 오류 + FCM_UNSPECIFIED_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "FCM001", + "현재 알림을 보낼 수 없습니다. 다시 한번 시도하시고 안되면 관리자에게 문의 바랍니다."), + FCM_CERTIFICATION_IS_NOT_VALID(HttpStatus.SERVICE_UNAVAILABLE, "FCM002", + "FCM 계정을 확인해주세요 apns 혹은 web 인증서에 문제가 있습니다."), + FCM_INTERNAL_SERVER_ERROR(HttpStatus.SERVICE_UNAVAILABLE, "FCM003", + "현재 FCM 서버에 문제가 있습니다, 다시 한번 시도해보시고 계속 문제가 있으면 FCM 측에 문의를 해주세요"), + FCM_INVALID_ARGUMENT(HttpStatus.INTERNAL_SERVER_ERROR, "FCM004", + "어플리케이션 이용이 오래되어 토큰이 삭제되었습니다, 로그아웃 혹은 앱을 지웠다가 다시 접속해주세요."), + FCM_UNREGISTERED(HttpStatus.INTERNAL_SERVER_ERROR, "FCM005", + "어플리케이션 이용이 오래되어 토큰이 삭제되었습니다, 로그아웃 혹은 앱을 지웠다가 다시 접속해주세요"), + + // 강의(교육) 신청 관련 오류 + EDUCATION_APPLICATION_NOT_FOUND(HttpStatus.BAD_REQUEST, "EDU001", "교육 신청서를 찾을 수 없습니다."), + EDUCATION_CLASS_GROUP_NOT_FOUND(HttpStatus.BAD_REQUEST, "EDU002", "교육 학급을 찾을 수 없습니다."), + EDUCATION_APPLICATION_DIFFERENT_PHONE(HttpStatus.UNAUTHORIZED, "EDU003", "휴대폰 번호가 동일하지 않습니다."), + + METHOD_ARGUMENT_NOT_VALID(HttpStatus.BAD_REQUEST, "C-0004", "요청 인자가 유효하지 않음"), + METHOD_NOT_ALLOWED(HttpStatus.METHOD_NOT_ALLOWED, "C-0002", "허용되지 않은 Request Method 호출"), + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "C-0003", "내부 서버 오류"), + INVALID_TYPE_VALUE(HttpStatus.BAD_REQUEST, "C005", "유효하지 않은 값 타입"), + JSON_SYNTAX_ERROR(HttpStatus.BAD_REQUEST, "C-006", "json 형식이 잘못되었습니다."), + NOT_SUPPORTED_BODY_TYPE(HttpStatus.BAD_REQUEST, "C-007", "지원하지 않는 형식의 Request Body 형식입니다. json 형식으로 보내주세요"); + + private final HttpStatus httpStatus; + private final String code; + private final String message; } diff --git a/src/main/java/com/example/DoroServer/global/exception/SessionIdException.java b/src/main/java/com/example/DoroServer/global/exception/SessionIdException.java new file mode 100644 index 00000000..a6bddb3b --- /dev/null +++ b/src/main/java/com/example/DoroServer/global/exception/SessionIdException.java @@ -0,0 +1,8 @@ +package com.example.DoroServer.global.exception; + +public class SessionIdException extends RuntimeException { + + public SessionIdException(Code code) { + super(code.name()); + } +} diff --git a/src/main/java/com/example/DoroServer/global/jwt/JwtAuthenticationFilter.java b/src/main/java/com/example/DoroServer/global/jwt/JwtAuthenticationFilter.java index d11006a9..c2dba415 100644 --- a/src/main/java/com/example/DoroServer/global/jwt/JwtAuthenticationFilter.java +++ b/src/main/java/com/example/DoroServer/global/jwt/JwtAuthenticationFilter.java @@ -3,6 +3,8 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; +import org.springframework.core.Ordered; +import org.springframework.core.annotation.Order; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.util.StringUtils; @@ -16,6 +18,7 @@ import java.io.IOException; @Slf4j +@Order(Ordered.HIGHEST_PRECEDENCE) @RequiredArgsConstructor public class JwtAuthenticationFilter extends GenericFilterBean { @@ -23,13 +26,14 @@ public class JwtAuthenticationFilter extends GenericFilterBean { private final RedisService redisService; @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) + throws IOException, ServletException { HttpServletRequest httpServletRequest = (HttpServletRequest) request; String jwtToken = jwtTokenProvider.resolveToken(httpServletRequest); String requestURI = httpServletRequest.getRequestURI(); if (StringUtils.hasText(jwtToken) && jwtTokenProvider.validateToken(jwtToken)) { - if(redisService.getValues(jwtToken) == null) { + if (redisService.getValues(jwtToken) == null) { Authentication authentication = jwtTokenProvider.getAuthentication(jwtToken); SecurityContextHolder.getContext().setAuthentication(authentication); log.debug("Security Context에 '{}' 인증 정보를 저장했습니다, uri: {}", authentication.getName(), requestURI); @@ -38,7 +42,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha log.debug("유효한 JWT 토큰이 없습니다, uri: {}", requestURI); } - chain.doFilter(request, response); } } diff --git a/src/main/java/com/example/DoroServer/global/message/MessageController.java b/src/main/java/com/example/DoroServer/global/message/MessageController.java index b423eed9..1c0d91bb 100644 --- a/src/main/java/com/example/DoroServer/global/message/MessageController.java +++ b/src/main/java/com/example/DoroServer/global/message/MessageController.java @@ -3,11 +3,16 @@ import com.example.DoroServer.global.common.SuccessResponse; import com.example.DoroServer.global.message.dto.SendAuthNumReq; import com.example.DoroServer.global.message.dto.VerifyAuthNumReq; +import com.example.DoroServer.global.message.dto.VerifyAuthNumRes; + import io.swagger.annotations.Api; import io.swagger.v3.oas.annotations.Operation; + import javax.validation.Valid; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; + +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; @@ -21,16 +26,24 @@ public class MessageController { @Operation(summary = "002_01", description = "인증번호 전송") @PostMapping("/message/send") - public SuccessResponse sendAuthNum(@RequestBody @Valid SendAuthNumReq sendAuthNumReq){ + public SuccessResponse sendAuthNum(@RequestBody @Valid SendAuthNumReq sendAuthNumReq) { messageService.sendAuthNum(sendAuthNumReq); return SuccessResponse.successResponse("인증번호가 전송되었습니다."); } - @Operation(summary = "002_01", description = "인증번호 확인") @PostMapping("/message/verify") - public SuccessResponse verifyAuthNum(@RequestBody @Valid VerifyAuthNumReq verifyAuthNumReq){ + public SuccessResponse verifyAuthNum(@RequestBody @Valid VerifyAuthNumReq verifyAuthNumReq) { messageService.verifyAuthNum(verifyAuthNumReq); return SuccessResponse.successResponse("인증 성공"); } + + @Operation(summary = "인증번호를 확인하고 세션이 생성됩니다", description = "인증번호 확인") + @PostMapping("/message/verify2") + public ResponseEntity verifyAuthNum2(@RequestBody @Valid VerifyAuthNumReq verifyAuthNumReq) { + VerifyAuthNumRes result = messageService.verifyAuthNum2(verifyAuthNumReq); + return ResponseEntity.ok() + .headers(result.getSessionId()) + .body("인증 성공"); + } } diff --git a/src/main/java/com/example/DoroServer/global/message/MessageService.java b/src/main/java/com/example/DoroServer/global/message/MessageService.java index 3e912957..c415c3ea 100644 --- a/src/main/java/com/example/DoroServer/global/message/MessageService.java +++ b/src/main/java/com/example/DoroServer/global/message/MessageService.java @@ -2,9 +2,13 @@ import com.example.DoroServer.global.message.dto.SendAuthNumReq; import com.example.DoroServer.global.message.dto.VerifyAuthNumReq; +import com.example.DoroServer.global.message.dto.VerifyAuthNumRes; public interface MessageService { void sendAuthNum(SendAuthNumReq sendAuthNumReq); + void verifyAuthNum(VerifyAuthNumReq verifyAuthNumReq); + + VerifyAuthNumRes verifyAuthNum2(VerifyAuthNumReq verifyAuthNumReq); } diff --git a/src/main/java/com/example/DoroServer/global/message/MessageServiceImpl.java b/src/main/java/com/example/DoroServer/global/message/MessageServiceImpl.java index 9da4e0a7..1d65c5ec 100644 --- a/src/main/java/com/example/DoroServer/global/message/MessageServiceImpl.java +++ b/src/main/java/com/example/DoroServer/global/message/MessageServiceImpl.java @@ -6,7 +6,10 @@ import com.example.DoroServer.global.jwt.RedisService; import com.example.DoroServer.global.message.dto.SendAuthNumReq; import com.example.DoroServer.global.message.dto.VerifyAuthNumReq; +import com.example.DoroServer.global.message.dto.VerifyAuthNumRes; + import java.time.Duration; +import java.util.UUID; import java.util.HashMap; import java.util.Random; import lombok.extern.slf4j.Slf4j; @@ -16,12 +19,13 @@ import net.nurigo.sdk.message.request.SingleMessageSendingRequest; import net.nurigo.sdk.message.service.DefaultMessageService; import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service @Slf4j -public class MessageServiceImpl implements MessageService{ +public class MessageServiceImpl implements MessageService { private final DefaultMessageService defaultMessageService; private final RedisService redisService; @@ -30,31 +34,32 @@ public class MessageServiceImpl implements MessageService{ private final String templateId; public MessageServiceImpl(@Value("${solapi.api-key}") String apiKey, - @Value("${solapi.api-secret}") String apiSecret, - @Value("${solapi.from-number}") String fromNumber, - @Value("${solapi.domain}") String domain, - @Value("${solapi.pfid}") String pfid, - @Value("${solapi.templateId}") String templateId, - RedisService redisService) { + @Value("${solapi.api-secret}") String apiSecret, + @Value("${solapi.from-number}") String fromNumber, + @Value("${solapi.domain}") String domain, + @Value("${solapi.pfid}") String pfid, + @Value("${solapi.templateId}") String templateId, + RedisService redisService) { this.defaultMessageService = NurigoApp.INSTANCE.initialize(apiKey, apiSecret, domain); this.fromNumber = fromNumber; this.pfId = pfid; this.templateId = templateId; this.redisService = redisService; } + @Transactional @Override public void sendAuthNum(SendAuthNumReq sendAuthNumReq) { - try{ + try { String authNum = numberGen(6); Message message = makeKakaoMessage(sendAuthNumReq, authNum); redisService.setValues(sendAuthNumReq.getMessageType() + sendAuthNumReq.getPhone(), - authNum, Duration.ofSeconds(300)); + authNum, Duration.ofSeconds(300)); log.info("Redis 저장 성공"); defaultMessageService.sendOne(new SingleMessageSendingRequest(message)); log.info("메시지 전송 성공"); - }catch (Exception e){ + } catch (Exception e) { throw new MessageException(Code.MESSAGE_SEND_FAILED); } } @@ -78,13 +83,30 @@ private Message makeKakaoMessage(SendAuthNumReq sendAuthNumReq, String authNum) @Override public void verifyAuthNum(VerifyAuthNumReq verifyAuthNumReq) { String redisAuthNum = redisService.getValues(verifyAuthNumReq.getMessageType() + - verifyAuthNumReq.getPhone()); - if(redisAuthNum == null || !redisAuthNum.equals(verifyAuthNumReq.getAuthNum())){ + verifyAuthNumReq.getPhone()); + if (redisAuthNum == null || !redisAuthNum.equals(verifyAuthNumReq.getAuthNum())) { throw new BaseException(Code.VERIFICATION_DID_NOT_MATCH); } redisService.deleteValues(verifyAuthNumReq.getPhone()); redisService.setValues(verifyAuthNumReq.getMessageType() + - verifyAuthNumReq.getPhone(), "Verified", Duration.ofMinutes(30)); + verifyAuthNumReq.getPhone(), "Verified", Duration.ofMinutes(30)); + } + + @Override + public VerifyAuthNumRes verifyAuthNum2(VerifyAuthNumReq verifyAuthNumReq) { + String redisAuthNum = redisService.getValues(verifyAuthNumReq.getMessageType() + + verifyAuthNumReq.getPhone()); + if (redisAuthNum == null || !redisAuthNum.equals(verifyAuthNumReq.getAuthNum())) { + throw new BaseException(Code.VERIFICATION_DID_NOT_MATCH); + } + redisService.deleteValues(verifyAuthNumReq.getPhone()); + + // Redis에 5분간 유효한 세션(세션 ID: 전화번호) 생성 + String sessionId = generateSessionId(); + redisService.setValues(sessionId, verifyAuthNumReq.getPhone(), Duration.ofMinutes(5)); + HttpHeaders httpHeaders = new HttpHeaders(); + httpHeaders.add("Session-Id", sessionId); + return VerifyAuthNumRes.builder().sessionId(httpHeaders).build(); } /** @@ -94,10 +116,14 @@ private String numberGen(int len) { Random rand = new Random(); StringBuilder numStr = new StringBuilder(); - for(int i = 0; i < len; i++) { + for (int i = 0; i < len; i++) { String ran = Integer.toString(rand.nextInt(10)); numStr.append(ran); } return numStr.toString(); } + + public static String generateSessionId() { + return UUID.randomUUID().toString(); + } } diff --git a/src/main/java/com/example/DoroServer/global/message/dto/SendAuthNumReq.java b/src/main/java/com/example/DoroServer/global/message/dto/SendAuthNumReq.java index cc06fbc6..8311169a 100644 --- a/src/main/java/com/example/DoroServer/global/message/dto/SendAuthNumReq.java +++ b/src/main/java/com/example/DoroServer/global/message/dto/SendAuthNumReq.java @@ -14,7 +14,7 @@ public class SendAuthNumReq { @NotNull private MessageType messageType; - public enum MessageType{ - JOIN, ACCOUNT, PASSWORD, UPDATE + public enum MessageType { + JOIN, ACCOUNT, PASSWORD, UPDATE, TEMP // TEMP는 교육 신청시 사용 } } diff --git a/src/main/java/com/example/DoroServer/global/message/dto/VerifyAuthNumReq.java b/src/main/java/com/example/DoroServer/global/message/dto/VerifyAuthNumReq.java index dfe9cb3f..ee41e1b6 100644 --- a/src/main/java/com/example/DoroServer/global/message/dto/VerifyAuthNumReq.java +++ b/src/main/java/com/example/DoroServer/global/message/dto/VerifyAuthNumReq.java @@ -1,12 +1,20 @@ package com.example.DoroServer.global.message.dto; import com.example.DoroServer.global.message.dto.SendAuthNumReq.MessageType; + import javax.validation.constraints.NotBlank; import javax.validation.constraints.NotNull; import javax.validation.constraints.Pattern; + +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Getter; +import lombok.NoArgsConstructor; @Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor public class VerifyAuthNumReq { @NotBlank @Pattern(regexp = "^01([016789])([0-9]{3,4})([0-9]{4})$", message = "올바른 휴대폰 번호 형식이 아닙니다.") diff --git a/src/main/java/com/example/DoroServer/global/message/dto/VerifyAuthNumRes.java b/src/main/java/com/example/DoroServer/global/message/dto/VerifyAuthNumRes.java new file mode 100644 index 00000000..f98fda0b --- /dev/null +++ b/src/main/java/com/example/DoroServer/global/message/dto/VerifyAuthNumRes.java @@ -0,0 +1,16 @@ +package com.example.DoroServer.global.message.dto; + +import org.springframework.http.HttpHeaders; + +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.AllArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class VerifyAuthNumRes { + HttpHeaders sessionId; +} \ No newline at end of file diff --git a/src/main/java/com/example/DoroServer/global/s3/AwsS3ServiceImpl.java b/src/main/java/com/example/DoroServer/global/s3/AwsS3ServiceImpl.java index 7431bfba..794e2359 100644 --- a/src/main/java/com/example/DoroServer/global/s3/AwsS3ServiceImpl.java +++ b/src/main/java/com/example/DoroServer/global/s3/AwsS3ServiceImpl.java @@ -1,6 +1,5 @@ package com.example.DoroServer.global.s3; -import com.amazonaws.services.s3.AmazonS3; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.CannedAccessControlList; import com.amazonaws.services.s3.model.ObjectMetadata; @@ -19,7 +18,7 @@ @Service @RequiredArgsConstructor -public class AwsS3ServiceImpl implements AwsS3Service{ +public class AwsS3ServiceImpl implements AwsS3Service { @Value("${cloud.aws.s3.bucket}") private String bucket; @@ -30,8 +29,8 @@ public class AwsS3ServiceImpl implements AwsS3Service{ private final AmazonS3Client amazonS3Client; @Override - public String upload(MultipartFile multipartFile, String dirName){ - String fileName = dirName+"/"+ UUID.randomUUID() + ".jpg"; + public String upload(MultipartFile multipartFile, String dirName) { + String fileName = dirName + "/" + UUID.randomUUID() + ".jpg"; if (multipartFile.isEmpty()) { throw new BaseException(Code.BAD_REQUEST); } @@ -45,7 +44,7 @@ public String upload(MultipartFile multipartFile, String dirName){ ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes); amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, byteArrayInputStream, objectMetadata) - .withCannedAcl(CannedAccessControlList.PublicRead)); + .withCannedAcl(CannedAccessControlList.PublicRead)); } catch (IOException e) { throw new BaseException(Code.UPLOAD_FAILED); } @@ -55,12 +54,24 @@ public String upload(MultipartFile multipartFile, String dirName){ @Override public void deleteImage(String fileName) { - int index=fileName.indexOf(url); - String fileRoute=fileName.substring(index + url.length()+1); + int index = fileName.indexOf(url); + String fileRoute = fileName.substring(index + url.length() + 1); try { boolean isObjectExist = amazonS3Client.doesObjectExist(bucket, fileRoute); if (isObjectExist) { - amazonS3Client.deleteObject(bucket,fileRoute); + amazonS3Client.deleteObject(bucket, fileRoute); + } + } catch (Exception e) { + throw new BaseException(Code.BAD_REQUEST); + } + } + + public void deleteImage2(String fileName) { + String objectName = "lecture-content/" + fileName; // BUCKET_FOLDER/UUID.jpg + try { + boolean isObjectExist = amazonS3Client.doesObjectExist(bucket, objectName); + if (isObjectExist) { + amazonS3Client.deleteObject(bucket, objectName); } } catch (Exception e) { throw new BaseException(Code.BAD_REQUEST); diff --git a/src/main/java/com/example/DoroServer/global/s3/MultipartFileUtils.java b/src/main/java/com/example/DoroServer/global/s3/MultipartFileUtils.java new file mode 100644 index 00000000..44d8dab1 --- /dev/null +++ b/src/main/java/com/example/DoroServer/global/s3/MultipartFileUtils.java @@ -0,0 +1,22 @@ +package com.example.DoroServer.global.s3; + +import org.springframework.web.multipart.MultipartFile; + +import com.example.DoroServer.global.exception.BaseException; +import com.example.DoroServer.global.exception.Code; + +public class MultipartFileUtils { + public static void validateMultipartFiles(MultipartFile[] files) { + if (files == null || files.length == 0) { + throw new BaseException(Code.JSON_SYNTAX_ERROR); + } + if (files.length > 100 || files.length < 1) { + throw new BaseException(Code.LECTURE_CONTENT_INVAILD_IMAGE_COUNT); + } + for (MultipartFile file : files) { + if (file.getSize() > 10485760) { // 10MB + throw new BaseException(Code.LECTURE_CONTENT_IMAGE_SIZE_OVER); + } + } + } +} diff --git a/src/test/java/com/example/DoroServer/domain/educationApplication/EducationApplicationTestSetup.java b/src/test/java/com/example/DoroServer/domain/educationApplication/EducationApplicationTestSetup.java index 42e55b39..473b2819 100644 --- a/src/test/java/com/example/DoroServer/domain/educationApplication/EducationApplicationTestSetup.java +++ b/src/test/java/com/example/DoroServer/domain/educationApplication/EducationApplicationTestSetup.java @@ -12,7 +12,7 @@ public class EducationApplicationTestSetup { public static Long ID = 1L; public static String NAME = "홍길동"; public static String UPDATED_NAME = "고길동"; - public static String PHONE_NUMBER = "010-1234-5678"; + public static String PHONE_NUMBER = "01012345678"; public static String INSTITUTION_NAME = "냉장고등학교"; public static String POSITION = "교장선생님"; public static String UPDATED_POSITION = "교감선생님"; diff --git a/src/test/java/com/example/DoroServer/domain/educationApplication/api/EducationApplicationApiTest.java b/src/test/java/com/example/DoroServer/domain/educationApplication/api/EducationApplicationApiTest.java index 8ebc148c..b6cb9211 100644 --- a/src/test/java/com/example/DoroServer/domain/educationApplication/api/EducationApplicationApiTest.java +++ b/src/test/java/com/example/DoroServer/domain/educationApplication/api/EducationApplicationApiTest.java @@ -1,5 +1,6 @@ package com.example.DoroServer.domain.educationApplication.api; +import static com.example.DoroServer.domain.educationApplication.EducationApplicationTestSetup.getEducationApplication; import static com.example.DoroServer.domain.educationApplication.EducationApplicationTestSetup.getEducationApplicationReq; import static com.example.DoroServer.domain.educationApplication.EducationApplicationTestSetup.getEducationApplicationRes; import static com.example.DoroServer.domain.educationApplication.EducationApplicationTestSetup.getRetrieveApplicationReq; @@ -10,6 +11,8 @@ import static com.example.DoroServer.domain.educationApplicationClassGroup.ClassGroupTestSetup.getUpdateClassGroupReq; import static com.example.DoroServer.domain.educationApplicationClassGroup.ClassGroupTestSetup.getUpdateClassGroupRes; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.BDDMockito.given; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; @@ -23,9 +26,8 @@ import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; -import org.springframework.data.jpa.mapping.JpaMetamodelMappingContext; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; @@ -36,178 +38,208 @@ import com.example.DoroServer.domain.educationApplicationClassGroup.dto.ClassGroupReq; import com.example.DoroServer.domain.educationApplicationClassGroup.dto.ClassGroupRes; import com.example.DoroServer.domain.educationApplicationClassGroup.service.ClassGroupService; +import com.example.DoroServer.global.jwt.RedisService; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -@WebMvcTest(EducationApplicationApi.class) -@AutoConfigureMockMvc(addFilters = false) // SecurityConfig를 적용하지 않음 +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@AutoConfigureMockMvc public class EducationApplicationApiTest { - @Autowired - private MockMvc mockMvc; - - @MockBean - private EducationApplicationService educationApplicationService; - - @MockBean - private ClassGroupService classGroupService; - - @MockBean - private JpaMetamodelMappingContext jpaMetamodelMappingContext; // JPA auditing을 위한 Bean - - private static ObjectMapper mapper; - - @BeforeAll - static void setUp() { - mapper = new ObjectMapper(); - mapper.registerModule(new JavaTimeModule()); - } - - @Test - @DisplayName("MockMvc를 통한 교육 신청 작성 테스트") - public void createEducationApplicationTest() throws Exception { - // given - EducationApplicationReq applicationReq = getEducationApplicationReq(); - EducationApplicationRes educationApplicationRes = getEducationApplicationRes(); - - given(educationApplicationService.save(any(EducationApplicationReq.class))) - .willReturn(educationApplicationRes); - - // when & then - mockMvc.perform(post("/education-application") - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(applicationReq))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.id").value(educationApplicationRes.getId())) - .andExpect(jsonPath("$.data.name").value(educationApplicationRes.getName())) - .andExpect(jsonPath("$.data.institutionName") - .value(educationApplicationRes.getInstitutionName())) - .andExpect(jsonPath("$.data.position").value(educationApplicationRes.getPosition())) - .andExpect(jsonPath("$.data.phoneNumber") - .value(educationApplicationRes.getPhoneNumber())) - .andExpect(jsonPath("$.data.email").value(educationApplicationRes.getEmail())) - .andExpect(jsonPath("$.data.numberOfStudents") - .value(educationApplicationRes.getNumberOfStudents())) - .andExpect(jsonPath("$.data.studentRank") - .value(educationApplicationRes.getStudentRank())) - .andExpect(jsonPath("$.data.budget").value(educationApplicationRes.getBudget())) - .andExpect(jsonPath("$.data.overallRemark") - .value(educationApplicationRes.getOverallRemark())); - } - - @Test - @DisplayName("MockMvc를 통한 신청한 교육 불러오기 테스트") - public void retrieveApplicationTest() throws Exception { - // given - RetrieveApplicationReq retrieveApplicationReq = getRetrieveApplicationReq(); - EducationApplicationRes educationApplicationRes = getEducationApplicationRes(); - - given(educationApplicationService.findByPhoneNumber(any(RetrieveApplicationReq.class))) - .willReturn(List.of(educationApplicationRes)); - - // when & then - mockMvc.perform(get("/education-application") - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(retrieveApplicationReq))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data[0].id").value(educationApplicationRes.getId())) - .andExpect(jsonPath("$.data[0].name").value(educationApplicationRes.getName())) - .andExpect(jsonPath("$.data[0].institutionName") - .value(educationApplicationRes.getInstitutionName())) - .andExpect(jsonPath("$.data[0].position").value(educationApplicationRes.getPosition())) - .andExpect(jsonPath("$.data[0].phoneNumber") - .value(educationApplicationRes.getPhoneNumber())) - .andExpect(jsonPath("$.data[0].email").value(educationApplicationRes.getEmail())) - .andExpect(jsonPath("$.data[0].numberOfStudents") - .value(educationApplicationRes.getNumberOfStudents())) - .andExpect(jsonPath("$.data[0].studentRank") - .value(educationApplicationRes.getStudentRank())) - .andExpect(jsonPath("$.data[0].budget").value(educationApplicationRes.getBudget())) - .andExpect(jsonPath("$.data[0].overallRemark") - .value(educationApplicationRes.getOverallRemark())); - - } - - @Test - @DisplayName("MockMvc를 통한 신청한 교육 수정 테스트") - void updateEducationApplicationTest() throws Exception { - // given - EducationApplicationReq applicationReq = getUpdateEducationApplicationReq(); - EducationApplicationRes educationApplicationRes = getUpdateEducationApplicationRes(); - - given(educationApplicationService.update(any(Long.class), any(EducationApplicationReq.class))) - .willReturn(educationApplicationRes); - - // when & then - mockMvc.perform(put("/education-application/1") - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(applicationReq))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.id").value(educationApplicationRes.getId())) - .andExpect(jsonPath("$.data.name").value(educationApplicationRes.getName())) - .andExpect(jsonPath("$.data.institutionName") - .value(educationApplicationRes.getInstitutionName())) - .andExpect(jsonPath("$.data.position").value(educationApplicationRes.getPosition())) - .andExpect(jsonPath("$.data.phoneNumber") - .value(educationApplicationRes.getPhoneNumber())) - .andExpect(jsonPath("$.data.email").value(educationApplicationRes.getEmail())) - .andExpect(jsonPath("$.data.numberOfStudents") - .value(educationApplicationRes.getNumberOfStudents())) - .andExpect(jsonPath("$.data.studentRank") - .value(educationApplicationRes.getStudentRank())) - .andExpect(jsonPath("$.data.budget").value(educationApplicationRes.getBudget())) - .andExpect(jsonPath("$.data.overallRemark") - .value(educationApplicationRes.getOverallRemark())); - } - - @Test - @DisplayName("MockMvc를 통한 신청한 교육에 학급정보 추가 테스트") - void addClassGroupToApplicationTest() throws Exception { - // given - ClassGroupReq classGroupReq = getClassGroupReq(); - ClassGroupRes classGroupRes = getClassGroupRes(); - - given(classGroupService.addClassGroupToApplication(any(Long.class), any(ClassGroupReq.class))) - .willReturn(classGroupRes); - - // when & then - mockMvc.perform(post("/education-application/1/classGroup") - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(classGroupReq))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.id").value(classGroupRes.getId())) - .andExpect(jsonPath("$.data.className").value(classGroupRes.getClassName())) - .andExpect(jsonPath("$.data.educationConcept") - .value(classGroupRes.getEducationConcept())) - .andExpect(jsonPath("$.data.numberOfStudents") - .value(classGroupRes.getNumberOfStudents())) - .andExpect(jsonPath("$.data.remark").value(classGroupRes.getRemark())) - .andExpect(jsonPath("$.data.unfixed").value(classGroupRes.isUnfixed())); - } - - @Test - @DisplayName("MockMvc를 통한 신청한 교육에 학급정보 업데이트 테스트") - void updateClassGroupTest() throws Exception { - // given - ClassGroupReq classGroupReq = getUpdateClassGroupReq(); - ClassGroupRes classGroupRes = getUpdateClassGroupRes(); - - given(classGroupService.updateClassGroup(any(Long.class), any(Long.class), any(ClassGroupReq.class))) - .willReturn(classGroupRes); - - // when & then - mockMvc.perform(put("/education-application/1/classGroup/1") - .contentType(MediaType.APPLICATION_JSON) - .content(mapper.writeValueAsString(classGroupReq))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.data.id").value(classGroupRes.getId())) - .andExpect(jsonPath("$.data.className").value(classGroupRes.getClassName())) - .andExpect(jsonPath("$.data.educationConcept") - .value(classGroupRes.getEducationConcept())) - .andExpect(jsonPath("$.data.numberOfStudents") - .value(classGroupRes.getNumberOfStudents())) - .andExpect(jsonPath("$.data.remark").value(classGroupRes.getRemark())) - .andExpect(jsonPath("$.data.unfixed").value(classGroupRes.isUnfixed())); - } + @Autowired + private MockMvc mockMvc; + + @MockBean + private RedisService redisService; + + @MockBean + private EducationApplicationService educationApplicationService; + + @MockBean + private ClassGroupService classGroupService; + + private static ObjectMapper mapper; + + @BeforeAll + static void setUp() { + mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + } + + @Test + @DisplayName("MockMvc를 통한 교육 신청 작성 테스트") + public void createEducationApplicationTest() throws Exception { + // given + EducationApplicationReq applicationReq = getEducationApplicationReq(); + EducationApplicationRes educationApplicationRes = getEducationApplicationRes(); + String phoneNumber = applicationReq.getPhoneNumber(); + + given(redisService.getValues("some-session-id")).willReturn(phoneNumber); + given(educationApplicationService.save(any(EducationApplicationReq.class), anyString())) + .willReturn(educationApplicationRes); + + // when & then + mockMvc.perform(post("/education-application") + .contentType(MediaType.APPLICATION_JSON) + .header("Session-Id", "some-session-id") + .content(mapper.writeValueAsString(applicationReq))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(educationApplicationRes.getId())) + .andExpect(jsonPath("$.data.name").value(educationApplicationRes.getName())) + .andExpect(jsonPath("$.data.institutionName") + .value(educationApplicationRes.getInstitutionName())) + .andExpect(jsonPath("$.data.position").value(educationApplicationRes.getPosition())) + .andExpect(jsonPath("$.data.phoneNumber") + .value(educationApplicationRes.getPhoneNumber())) + .andExpect(jsonPath("$.data.email").value(educationApplicationRes.getEmail())) + .andExpect(jsonPath("$.data.numberOfStudents") + .value(educationApplicationRes.getNumberOfStudents())) + .andExpect(jsonPath("$.data.studentRank") + .value(educationApplicationRes.getStudentRank())) + .andExpect(jsonPath("$.data.budget").value(educationApplicationRes.getBudget())) + .andExpect(jsonPath("$.data.overallRemark") + .value(educationApplicationRes.getOverallRemark())); + } + + @Test + @DisplayName("MockMvc를 통한 신청한 교육 불러오기 테스트") + public void retrieveApplicationTest() throws Exception { + // given + RetrieveApplicationReq retrieveApplicationReq = getRetrieveApplicationReq(); + EducationApplicationRes educationApplicationRes = getEducationApplicationRes(); + String phoneNumber = retrieveApplicationReq.getPhoneNumber(); + + given(redisService.getValues("some-session-id")).willReturn(phoneNumber); + given(educationApplicationService.findByPhoneNumber(any(RetrieveApplicationReq.class), anyString())) + .willReturn(List.of(educationApplicationRes)); + + // when & then + mockMvc.perform(get("/education-application") + .contentType(MediaType.APPLICATION_JSON) + .header("Session-Id", "some-session-id") + .content(mapper.writeValueAsString(retrieveApplicationReq))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data[0].id").value(educationApplicationRes.getId())) + .andExpect(jsonPath("$.data[0].name").value(educationApplicationRes.getName())) + .andExpect(jsonPath("$.data[0].institutionName") + .value(educationApplicationRes.getInstitutionName())) + .andExpect(jsonPath("$.data[0].position").value(educationApplicationRes.getPosition())) + .andExpect(jsonPath("$.data[0].phoneNumber") + .value(educationApplicationRes.getPhoneNumber())) + .andExpect(jsonPath("$.data[0].email").value(educationApplicationRes.getEmail())) + .andExpect(jsonPath("$.data[0].numberOfStudents") + .value(educationApplicationRes.getNumberOfStudents())) + .andExpect(jsonPath("$.data[0].studentRank") + .value(educationApplicationRes.getStudentRank())) + .andExpect(jsonPath("$.data[0].budget").value(educationApplicationRes.getBudget())) + .andExpect(jsonPath("$.data[0].overallRemark") + .value(educationApplicationRes.getOverallRemark())); + + } + + @Test + @DisplayName("MockMvc를 통한 신청한 교육 수정 테스트") + void updateEducationApplicationTest() throws Exception { + // given + EducationApplicationReq applicationReq = getUpdateEducationApplicationReq(); + EducationApplicationRes educationApplicationRes = getUpdateEducationApplicationRes(); + String phoneNumber = applicationReq.getPhoneNumber(); + + given(redisService.getValues("some-session-id")).willReturn(phoneNumber); + given(educationApplicationService.update(anyLong(), any(EducationApplicationReq.class), anyString())) + .willReturn(educationApplicationRes); + + // when & then + mockMvc.perform(put("/education-application/1") + .contentType(MediaType.APPLICATION_JSON) + .header("Session-Id", "some-session-id") + .content(mapper.writeValueAsString(applicationReq))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(educationApplicationRes.getId())) + .andExpect(jsonPath("$.data.name").value(educationApplicationRes.getName())) + .andExpect(jsonPath("$.data.institutionName") + .value(educationApplicationRes.getInstitutionName())) + .andExpect(jsonPath("$.data.position").value(educationApplicationRes.getPosition())) + .andExpect(jsonPath("$.data.phoneNumber") + .value(educationApplicationRes.getPhoneNumber())) + .andExpect(jsonPath("$.data.email").value(educationApplicationRes.getEmail())) + .andExpect(jsonPath("$.data.numberOfStudents") + .value(educationApplicationRes.getNumberOfStudents())) + .andExpect(jsonPath("$.data.studentRank") + .value(educationApplicationRes.getStudentRank())) + .andExpect(jsonPath("$.data.budget").value(educationApplicationRes.getBudget())) + .andExpect(jsonPath("$.data.overallRemark") + .value(educationApplicationRes.getOverallRemark())); + } + + @Test + @DisplayName("MockMvc를 통한 신청한 교육에 학급정보 추가 테스트") + void addClassGroupToApplicationTest() throws Exception { + // given + ClassGroupReq classGroupReq = getClassGroupReq(); + ClassGroupRes classGroupRes = getClassGroupRes(); + String phoneNumber = getEducationApplication().getPhoneNumber(); + + given(redisService.getValues("some-session-id")).willReturn(phoneNumber); + given(classGroupService.addClassGroupToApplication(any(Long.class), any(ClassGroupReq.class), anyString())) + .willReturn(classGroupRes); + + // when & then + mockMvc.perform(post("/education-application/1/classGroup") + .contentType(MediaType.APPLICATION_JSON) + .header("Session-Id", "some-session-id") + .content(mapper.writeValueAsString(classGroupReq))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(classGroupRes.getId())) + .andExpect(jsonPath("$.data.className").value(classGroupRes.getClassName())) + .andExpect(jsonPath("$.data.educationConcept") + .value(classGroupRes.getEducationConcept())) + .andExpect(jsonPath("$.data.numberOfStudents") + .value(classGroupRes.getNumberOfStudents())) + .andExpect(jsonPath("$.data.remark").value(classGroupRes.getRemark())) + .andExpect(jsonPath("$.data.unfixed").value(classGroupRes.isUnfixed())); + } + + @Test + @DisplayName("MockMvc를 통한 신청한 교육에 학급정보 업데이트 테스트") + void updateClassGroupTest() throws Exception { + // given + ClassGroupReq classGroupReq = getUpdateClassGroupReq(); + ClassGroupRes classGroupRes = getUpdateClassGroupRes(); + String phoneNumber = getEducationApplication().getPhoneNumber(); + + given(redisService.getValues("some-session-id")).willReturn(phoneNumber); + given(classGroupService.updateClassGroup(any(Long.class), any(Long.class), any(ClassGroupReq.class), + anyString())) + .willReturn(classGroupRes); + + // when & then + mockMvc.perform(put("/education-application/1/classGroup/1") + .contentType(MediaType.APPLICATION_JSON) + .header("Session-Id", "some-session-id") + .content(mapper.writeValueAsString(classGroupReq))) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.data.id").value(classGroupRes.getId())) + .andExpect(jsonPath("$.data.className").value(classGroupRes.getClassName())) + .andExpect(jsonPath("$.data.educationConcept") + .value(classGroupRes.getEducationConcept())) + .andExpect(jsonPath("$.data.numberOfStudents") + .value(classGroupRes.getNumberOfStudents())) + .andExpect(jsonPath("$.data.remark").value(classGroupRes.getRemark())) + .andExpect(jsonPath("$.data.unfixed").value(classGroupRes.isUnfixed())); + } + + @Test + @DisplayName("MockMvc를 통한 교육 신청서 작성 세션헤더 없이 접근 테스트") + public void createEducationApplicationWithoutAuthenticationTest() throws Exception { + // given + EducationApplicationReq applicationReq = getEducationApplicationReq(); + + // when & then + mockMvc.perform(post("/education-application") + .contentType(MediaType.APPLICATION_JSON) + .content(mapper.writeValueAsString(applicationReq))) + .andExpect(status().isUnauthorized()); + } } diff --git a/src/test/java/com/example/DoroServer/domain/educationApplication/api/SessionFilterTest.java b/src/test/java/com/example/DoroServer/domain/educationApplication/api/SessionFilterTest.java new file mode 100644 index 00000000..2b0e7cb3 --- /dev/null +++ b/src/test/java/com/example/DoroServer/domain/educationApplication/api/SessionFilterTest.java @@ -0,0 +1,86 @@ +package com.example.DoroServer.domain.educationApplication.api; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockFilterChain; +import org.springframework.mock.web.MockHttpServletRequest; +import org.springframework.mock.web.MockHttpServletResponse; + +import com.example.DoroServer.global.exception.SessionIdException; +import com.example.DoroServer.global.jwt.RedisService; + +@ExtendWith(MockitoExtension.class) +public class SessionFilterTest { + + @Mock + private RedisService redisService; + + @InjectMocks + private SessionFilter sessionFilter; + + @Test + @DisplayName("doFilter 메서드 테스트 - 세션 ID가 유효한 경우") + public void testDoFilter_ValidSessionId_Success() throws Exception { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/education-application"); + request.addHeader("Session-Id", "valid-session-id"); + String phoneNumber = "01012345678"; // valid-session-id: 01012345678 + MockHttpServletResponse response = new MockHttpServletResponse(); + + given(redisService.getValues("valid-session-id")).willReturn(phoneNumber); + + // when + MockFilterChain filterChain = new MockFilterChain(); + sessionFilter.doFilter(request, response, filterChain); + + // then + verify(redisService, times(1)).getValues("valid-session-id"); + assertEquals(phoneNumber, request.getAttribute("phoneNumber")); + } + + @Test + @DisplayName("doFilter 메서드 테스트 - 세션 ID가 없이 요청한 경우") + public void testDoFilter_InvalidSessionId_ExceptionThrown() throws Exception { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/education-application"); + + MockHttpServletResponse response = new MockHttpServletResponse(); + + // when + MockFilterChain filterChain = new MockFilterChain(); + + // then + assertThrows(SessionIdException.class, () -> sessionFilter.doFilter(request, response, filterChain)); + } + + @Test + @DisplayName("doFilter 메서드 테스트 - 세션 ID가 유효하지 않은 경우") + public void testDoFilter_InvalidSessionId_ExceptionThrown2() throws Exception { + // given + MockHttpServletRequest request = new MockHttpServletRequest(); + request.setRequestURI("/education-application"); + request.addHeader("Session-Id", "invalid-session-id"); + + MockHttpServletResponse response = new MockHttpServletResponse(); + + given(redisService.getValues("invalid-session-id")).willReturn(null); + + // when + MockFilterChain filterChain = new MockFilterChain(); + + // then + assertThrows(SessionIdException.class, () -> sessionFilter.doFilter(request, response, filterChain)); + } +} diff --git a/src/test/java/com/example/DoroServer/domain/educationApplication/service/EducationApplicationServiceTest.java b/src/test/java/com/example/DoroServer/domain/educationApplication/service/EducationApplicationServiceTest.java index c836af45..6b43dc28 100644 --- a/src/test/java/com/example/DoroServer/domain/educationApplication/service/EducationApplicationServiceTest.java +++ b/src/test/java/com/example/DoroServer/domain/educationApplication/service/EducationApplicationServiceTest.java @@ -1,6 +1,8 @@ package com.example.DoroServer.domain.educationApplication.service; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -19,6 +21,7 @@ import com.example.DoroServer.domain.educationApplication.dto.RetrieveApplicationReq; import com.example.DoroServer.domain.educationApplication.entity.EducationApplication; import com.example.DoroServer.domain.educationApplication.repository.EducationApplicationRepository; +import com.example.DoroServer.global.exception.BaseException; import static com.example.DoroServer.domain.educationApplication.EducationApplicationTestSetup.getEducationApplication; import static com.example.DoroServer.domain.educationApplication.EducationApplicationTestSetup.getEducationApplicationReq; @@ -49,13 +52,14 @@ void createEducationApplicationTest() { EducationApplicationReq applicationReq = getEducationApplicationReq(); EducationApplication educationApplication = getEducationApplication(); EducationApplicationRes applicationRes = getEducationApplicationRes(); + String phoneNumber = applicationReq.getPhoneNumber(); given(mapper.toEntity(applicationReq)).willReturn(educationApplication); given(educationApplicationRepository.save(educationApplication)).willReturn(educationApplication); given(mapper.toDTO(educationApplication)).willReturn(applicationRes); // when - EducationApplicationRes result = educationApplicationService.save(applicationReq); + EducationApplicationRes result = educationApplicationService.save(applicationReq, phoneNumber); // then verify(mapper, times(1)).toEntity(applicationReq); @@ -64,6 +68,17 @@ void createEducationApplicationTest() { assertThat(result.getId()).isEqualTo(ID); } + @DisplayName("다른번호로 교육 신청 생성 테스트 - 예외 발생") + @Test + void createEducationApplicationWithDifferentPhoneNumberTest() { + // given + EducationApplicationReq applicationReq = getEducationApplicationReq(); + String phoneNumber = "01087654321"; + + // when & then + assertThrows(BaseException.class, () -> educationApplicationService.save(applicationReq, phoneNumber)); + } + @DisplayName("교육 신청 조회 테스트") @Test void retrieveEducationApplicationTest() { @@ -72,17 +87,21 @@ void retrieveEducationApplicationTest() { EducationApplication educationApplication = getEducationApplication(); EducationApplicationRes applicationRes = getEducationApplicationRes(); List applicationResList = List.of(applicationRes); + String phoneNumber = retrieveApplicationReq.getPhoneNumber(); + given(educationApplicationRepository.findByPhoneNumber(educationApplication.getPhoneNumber())) .willReturn(List.of(educationApplication)); given(mapper.toDTO(List.of(educationApplication))).willReturn(applicationResList); // when - List result = educationApplicationService - .findByPhoneNumber(retrieveApplicationReq); + List result = educationApplicationService.findByPhoneNumber( + retrieveApplicationReq, + phoneNumber); // then verify(mapper, times(1)).toDTO(List.of(educationApplication)); - verify(educationApplicationRepository, times(1)).findByPhoneNumber(educationApplication.getPhoneNumber()); + verify(educationApplicationRepository, times(1)) + .findByPhoneNumber(educationApplication.getPhoneNumber()); verify(mapper, times(1)).toDTO(List.of(educationApplication)); assertThat(result.get(0).getId()).isEqualTo(ID); @@ -95,8 +114,9 @@ void updateEducationApplicationTest() { EducationApplicationReq applicationReq = getUpdateEducationApplicationReq(); EducationApplication educationApplication = getEducationApplication(); EducationApplicationRes applicationRes = getUpdateEducationApplicationRes(); + String phoneNumber = applicationReq.getPhoneNumber(); - given(educationApplicationRepository.findById(educationApplication.getId())) + given(educationApplicationRepository.findByIdAndPhoneNumber(educationApplication.getId(), phoneNumber)) .willReturn(java.util.Optional.of(educationApplication)); given(mapper.toEntity(applicationReq, educationApplication)).willReturn(educationApplication); given(educationApplicationRepository.save(educationApplication)).willReturn(educationApplication); @@ -104,10 +124,11 @@ void updateEducationApplicationTest() { // when EducationApplicationRes result = educationApplicationService.update(educationApplication.getId(), - applicationReq); + applicationReq, phoneNumber); // then - verify(educationApplicationRepository, times(1)).findById(educationApplication.getId()); + verify(educationApplicationRepository, times(1)).findByIdAndPhoneNumber(educationApplication.getId(), + phoneNumber); verify(mapper, times(1)).toEntity(applicationReq, educationApplication); verify(educationApplicationRepository, times(1)).save(educationApplication); verify(mapper, times(1)).toDTO(educationApplication); diff --git a/src/test/java/com/example/DoroServer/domain/educationApplicationClassGroup/service/ClassGroupServiceTest.java b/src/test/java/com/example/DoroServer/domain/educationApplicationClassGroup/service/ClassGroupServiceTest.java index c331f771..54d870bb 100644 --- a/src/test/java/com/example/DoroServer/domain/educationApplicationClassGroup/service/ClassGroupServiceTest.java +++ b/src/test/java/com/example/DoroServer/domain/educationApplicationClassGroup/service/ClassGroupServiceTest.java @@ -3,9 +3,11 @@ import static com.example.DoroServer.domain.educationApplicationClassGroup.ClassGroupTestSetup.getClassGroup; import static com.example.DoroServer.domain.educationApplicationClassGroup.ClassGroupTestSetup.getClassGroupReq; import static com.example.DoroServer.domain.educationApplicationClassGroup.ClassGroupTestSetup.getClassGroupRes; +import static com.example.DoroServer.domain.educationApplicationClassGroup.ClassGroupTestSetup.getUpdateClassGroup; import static com.example.DoroServer.domain.educationApplicationClassGroup.ClassGroupTestSetup.getUpdateClassGroupReq; import static com.example.DoroServer.domain.educationApplicationClassGroup.ClassGroupTestSetup.getUpdateClassGroupRes; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -28,75 +30,120 @@ import com.example.DoroServer.domain.educationApplicationClassGroup.dto.ClassGroupRes; import com.example.DoroServer.domain.educationApplicationClassGroup.entity.ClassGroup; import com.example.DoroServer.domain.educationApplicationClassGroup.repository.ClassGroupRepository; +import com.example.DoroServer.global.exception.BaseException; @ExtendWith(MockitoExtension.class) public class ClassGroupServiceTest { - @InjectMocks - private ClassGroupService classGroupService; - - @Mock - private EducationApplicationRepository educationApplicationRepository; - - @Mock - private ClassGroupRepository classGroupRepository; - - @Mock - private ClassGroupMapper mapper; - - @Test - @DisplayName("교육 신청서에 학급 정보 추가 테스트") - void testAddClassGroup() { - // given - EducationApplication educationApplication = getEducationApplication(); - ClassGroupReq classGroupReq = getClassGroupReq(); - ClassGroup classGroup = getClassGroup(); - ClassGroupRes classGroupRes = getClassGroupRes(); - - given(educationApplicationRepository.findById(educationApplication.getId())) - .willReturn(Optional.of(educationApplication)); - given(mapper.toEntity(classGroupReq)).willReturn(classGroup); - given(classGroupRepository.save(classGroup)).willReturn(classGroup); - given(mapper.toDTO(classGroup)).willReturn(classGroupRes); - - // when - ClassGroupRes savedClassGroup = classGroupService.addClassGroupToApplication( - educationApplication.getId(), - classGroupReq); - - // then - verify(educationApplicationRepository, times(1)).findById(educationApplication.getId()); - verify(mapper, times(1)).toEntity(classGroupReq); - verify(classGroupRepository, times(1)).save(classGroup); - verify(mapper, times(1)).toDTO(classGroup); - assertThat(savedClassGroup.getId()).isEqualTo(1L); - } - - @Test - @DisplayName("교육 신청서에 학급 정보 수정 테스트") - void testUpdateClassGroup() { - // given - EducationApplication educationApplication = getEducationApplication(); - ClassGroupReq classGroupReq = getUpdateClassGroupReq(); - ClassGroup classGroup = getClassGroup(); - ClassGroupRes classGroupRes = getUpdateClassGroupRes(); - - given(classGroupRepository.findById(classGroup.getId())).willReturn(Optional.of(classGroup)); - given(mapper.toEntity(classGroupReq, classGroup)).willReturn(classGroup); - given(classGroupRepository.save(classGroup)).willReturn(classGroup); - given(mapper.toDTO(classGroup)).willReturn(classGroupRes); - - // when - ClassGroupRes updatedClassGroup = classGroupService.updateClassGroup(educationApplication.getId(), - classGroup.getId(), classGroupReq); - - // then - verify(classGroupRepository, times(1)).findById(classGroup.getId()); - verify(mapper, times(1)).toEntity(classGroupReq, classGroup); - verify(classGroupRepository, times(1)).save(classGroup); - verify(mapper, times(1)).toDTO(classGroup); - - assertThat(updatedClassGroup.getNumberOfStudents()).isEqualTo(classGroupRes.getNumberOfStudents()); - } + @InjectMocks + private ClassGroupService classGroupService; + + @Mock + private EducationApplicationRepository educationApplicationRepository; + + @Mock + private ClassGroupRepository classGroupRepository; + + @Mock + private ClassGroupMapper mapper; + + @Test + @DisplayName("교육 신청서에 학급 정보 추가 테스트") + void addClassGroupToApplicationTest() { + // given + EducationApplication educationApplication = getEducationApplication(); + ClassGroupReq classGroupReq = getClassGroupReq(); + ClassGroup classGroup = getClassGroup(); + ClassGroup savedClassGroup = getClassGroup(); + ClassGroupRes classGroupRes = getClassGroupRes(); + Long applicationId = educationApplication.getId(); + String phoneNumber = educationApplication.getPhoneNumber(); + + given(educationApplicationRepository.findByIdAndPhoneNumber(applicationId, phoneNumber)) + .willReturn(Optional.of(educationApplication)); + given(mapper.toEntity(classGroupReq)).willReturn(classGroup); + given(classGroupRepository.save(classGroup)).willReturn(savedClassGroup); + given(mapper.toDTO(savedClassGroup)).willReturn(classGroupRes); + + // when + ClassGroupRes result = classGroupService.addClassGroupToApplication(applicationId, classGroupReq, phoneNumber); + + // then + assertThat(result).isEqualTo(classGroupRes); + verify(educationApplicationRepository, times(1)).findByIdAndPhoneNumber(applicationId, phoneNumber); + verify(mapper, times(1)).toEntity(classGroupReq); + verify(classGroupRepository, times(1)).save(classGroup); + verify(mapper, times(1)).toDTO(savedClassGroup); + } + + @Test + @DisplayName("교육 신청서에 학급 정보 추가 테스트") + void updateClassGroupToApplicationTest() { + // given + EducationApplication educationApplication = getEducationApplication(); + ClassGroupReq updateClassGroupReq = getUpdateClassGroupReq(); + ClassGroup classGroup = getClassGroup(); + ClassGroup updateClassGroup = getUpdateClassGroup(); + ClassGroupRes classGroupRes = getUpdateClassGroupRes(); + Long applicationId = educationApplication.getId(); + Long classGroupId = classGroup.getId(); + String phoneNumber = educationApplication.getPhoneNumber(); + + educationApplication.getClassGroups().add(classGroup); + given(educationApplicationRepository.findByIdAndPhoneNumber(applicationId, phoneNumber)) + .willReturn(Optional.of(educationApplication)); + given(mapper.toEntity(updateClassGroupReq, classGroup)).willReturn(updateClassGroup); + given(classGroupRepository.save(classGroup)).willReturn(classGroup); + given(mapper.toDTO(classGroup)).willReturn(classGroupRes); + + // when + ClassGroupRes result = classGroupService.updateClassGroup(applicationId, classGroupId, updateClassGroupReq, + phoneNumber); + + // then + assertThat(result).isEqualTo(classGroupRes); + verify(educationApplicationRepository, times(1)).findByIdAndPhoneNumber(applicationId, phoneNumber); + verify(mapper, times(1)).toEntity(updateClassGroupReq, classGroup); + verify(classGroupRepository, times(1)).save(classGroup); + verify(mapper, times(1)).toDTO(classGroup); + } + + @Test + @DisplayName("교육 신청서에 학급 정보 업데이트 실패 테스트 - 교육 신청서가 존재하지 않음") + void updateClassGroupErrorTest1() { + // given + EducationApplication educationApplication = getEducationApplication(); + ClassGroupReq updateClassGroupReq = getUpdateClassGroupReq(); + ClassGroup classGroup = getClassGroup(); + Long applicationId = educationApplication.getId(); + Long classGroupId = classGroup.getId(); + String phoneNumber = educationApplication.getPhoneNumber(); + + given(educationApplicationRepository.findByIdAndPhoneNumber(applicationId, phoneNumber)) + .willReturn(Optional.of(educationApplication)); + + // when & then + assertThrows(BaseException.class, () -> classGroupService.updateClassGroup(applicationId, classGroupId, + updateClassGroupReq, phoneNumber)); + verify(educationApplicationRepository, times(1)).findByIdAndPhoneNumber(applicationId, phoneNumber); + + } + + @Test + @DisplayName("교육 신청서에 학급 정보 업데이트 실패 테스트 - 핸드폰 번호가 일치하지 않음") + void updateClassGroupErrorTest2() { + // given + EducationApplication educationApplication = getEducationApplication(); + ClassGroupReq updateClassGroupReq = getUpdateClassGroupReq(); + ClassGroup classGroup = getClassGroup(); + Long applicationId = educationApplication.getId(); + Long classGroupId = classGroup.getId(); + String phoneNumber = "01011112222"; // 01012345678 + + // when & then + assertThrows(BaseException.class, () -> classGroupService.updateClassGroup(applicationId, classGroupId, + updateClassGroupReq, phoneNumber)); + + } } diff --git a/src/test/java/com/example/DoroServer/domain/lecture/repository/LectureRepositoryTest.java b/src/test/java/com/example/DoroServer/domain/lecture/repository/LectureRepositoryTest.java index 59f008c3..8451a578 100644 --- a/src/test/java/com/example/DoroServer/domain/lecture/repository/LectureRepositoryTest.java +++ b/src/test/java/com/example/DoroServer/domain/lecture/repository/LectureRepositoryTest.java @@ -1,7 +1,10 @@ package com.example.DoroServer.domain.lecture.repository; +import java.util.Arrays; +import java.util.Collections; import com.example.DoroServer.domain.lecture.entity.Lecture; import com.example.DoroServer.domain.lectureContent.entity.LectureContent; +import com.example.DoroServer.domain.lectureContent.repository.LectureContentRepository; import com.example.DoroServer.global.config.QueryDslConfig; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.BeforeEach; @@ -26,27 +29,39 @@ @DataJpaTest @AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE) -@Import({QueryDslConfig.class}) +@Import({ QueryDslConfig.class }) class LectureRepositoryTest { @Autowired LectureRepository lectureRepository; + @Autowired + LectureContentRepository lectureContentRepository; + @PersistenceContext EntityManager em; + LectureContent lectureContent; + + @BeforeEach + void setUp() { + lectureContent = LectureContent.builder().kit("테스트 키트").detail("세부사항").requirement("고졸").content("컨텐츠").build(); + lectureContentRepository.save(lectureContent); + } @DisplayName("findLectureById_Success_Test") @Test void findLectureByIdTest() { // given - LectureContent lectureContent = LectureContent.builder().id(1L).build(); Lecture lecture = Lecture.builder() + .mainTitle("강의 제목") .lectureContent(lectureContent) .build(); Lecture saved = lectureRepository.save(lecture); + // when Optional optionalLecture = lectureRepository.findLectureById(saved.getId()); + // then assertThat(em.getEntityManagerFactory().getPersistenceUnitUtil() .isLoaded(optionalLecture.get().getLectureContent())).isTrue(); @@ -56,39 +71,23 @@ void findLectureByIdTest() { @Test void findLecturesByFinishedDateTest() { // given - LectureContent lectureContent = LectureContent.builder().id(1L).build(); - - ArrayList> localDatesList = new ArrayList<>(); - - ArrayList localDates = new ArrayList<>(); - localDates.add(LocalDate.of(2023,10,10)); - localDates.add(LocalDate.of(2023,10,11)); - - - ArrayList localDates2 = new ArrayList<>(); - localDates2.add(LocalDate.of(2023,10,10)); - localDates2.add(LocalDate.of(2023,10,9)); - - ArrayList localDates3 = new ArrayList<>(); - localDates3.add(LocalDate.of(2023,10,10)); - - - localDatesList.add(localDates); - localDatesList.add(localDates2); - localDatesList.add(localDates3); - - for(ArrayList inputList : localDatesList){ - Lecture lecture1 = Lecture.builder() + List> localDatesList = Arrays.asList( + Arrays.asList(LocalDate.of(2023, 10, 10), LocalDate.of(2023, 10, 11)), + Arrays.asList(LocalDate.of(2023, 10, 10), LocalDate.of(2023, 10, 9)), + Collections.singletonList(LocalDate.of(2023, 10, 10))); + for (List inputList : localDatesList) { + Lecture lecture = Lecture.builder() .lectureDates(inputList) .lectureContent(lectureContent) .build(); - Lecture saved = lectureRepository.save(lecture1); + lectureRepository.save(lecture); } - Integer finishedLecturesCount=2; + Integer finishedLecturesCount = 2; + LocalDate finishedDate = LocalDate.of(2023, 10, 10); - LocalDate finishedDate=LocalDate.of(2023, 10, 10); // when List lecturesByFinishedDate = lectureRepository.findLecturesByFinishedDate(finishedDate); + // then assertThat(lecturesByFinishedDate.size()).isEqualTo(finishedLecturesCount); } diff --git a/src/test/java/com/example/DoroServer/domain/lectureContent/service/ContentImageS3ServiceImplTest.java b/src/test/java/com/example/DoroServer/domain/lectureContent/service/ContentImageS3ServiceImplTest.java new file mode 100644 index 00000000..8ef57875 --- /dev/null +++ b/src/test/java/com/example/DoroServer/domain/lectureContent/service/ContentImageS3ServiceImplTest.java @@ -0,0 +1,105 @@ +package com.example.DoroServer.domain.lectureContent.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import com.example.DoroServer.domain.lectureContent.dto.CreateLectureContentReq; +import com.example.DoroServer.domain.lectureContentImage.entity.LectureContentImage; +import com.example.DoroServer.global.exception.BaseException; +import com.example.DoroServer.global.s3.AwsS3ServiceImpl; + +@ExtendWith(MockitoExtension.class) +public class ContentImageS3ServiceImplTest { + @InjectMocks + private ContentImageS3ServiceImpl contentImageS3Service; + + @Mock + private AwsS3ServiceImpl awsS3Service; + + private String FILE_URL = "https://rodu-s3/lecture-content/052d05f0-e3aa-4088-9b6d-ed3bc42485b2.jpg"; + + private CreateLectureContentReq setUpLectureContentReq0() { + return CreateLectureContentReq.builder() + .kit("kit") + .detail("detail") + .requirement("requirement") + .content("content") + .build(); + } + + private CreateLectureContentReq setUpLectureContentReq1() { + MockMultipartFile mockFile = new MockMultipartFile("file", "Hello, World!".getBytes()); + return CreateLectureContentReq.builder() + .kit("kit") + .detail("detail") + .requirement("requirement") + .content("content") + .files(new MultipartFile[] { mockFile }) + .build(); + } + + private LectureContentImage setUpLectureContentImage() { + return LectureContentImage.builder() + .url(FILE_URL).build(); + } + + @Test + @DisplayName("validate 테스트 - 이미지 파일이 없는 경우 예외발생") + void validateTest0() { + // given + CreateLectureContentReq lectureContentReq = setUpLectureContentReq0(); + + // when & then + assertThrows(BaseException.class, () -> lectureContentReq.validateFiles()); + + } + + @Test + @DisplayName("uploadS3Images 테스트") + void uploadS3ImagesTest() { + // given + CreateLectureContentReq lectureContentReq = setUpLectureContentReq1(); + + given(awsS3Service.upload(any(MultipartFile.class), eq("lecture-content"))) + .willReturn(FILE_URL); + + // when + List urls = contentImageS3Service.uploadS3Images(lectureContentReq.getFiles()); + + // then + verify(awsS3Service, times(1)).upload(any(MultipartFile.class), eq("lecture-content")); + assertThat(urls.get(0)).isEqualTo(FILE_URL); + + } + + @Test + @DisplayName("deleteS3Image 테스트") + void deleteS3ImageTest() { + // given + LectureContentImage lectureContentImage = setUpLectureContentImage(); + + // when + contentImageS3Service.deleteS3Image(lectureContentImage); + + // then + verify(awsS3Service, times(1)).deleteImage2(anyString()); + + } + +} diff --git a/src/test/java/com/example/DoroServer/domain/lectureContent/service/LectureContentServiceTest.java b/src/test/java/com/example/DoroServer/domain/lectureContent/service/LectureContentServiceTest.java index f5810c26..eef158b7 100644 --- a/src/test/java/com/example/DoroServer/domain/lectureContent/service/LectureContentServiceTest.java +++ b/src/test/java/com/example/DoroServer/domain/lectureContent/service/LectureContentServiceTest.java @@ -1,12 +1,13 @@ package com.example.DoroServer.domain.lectureContent.service; - -import com.example.DoroServer.domain.lectureContent.dto.LectureContentDto; +import com.example.DoroServer.domain.lectureContent.dto.CreateLectureContentReq; import com.example.DoroServer.domain.lectureContent.dto.LectureContentMapper; +import com.example.DoroServer.domain.lectureContent.dto.LectureContentRes; import com.example.DoroServer.domain.lectureContent.dto.UpdateLectureContentReq; import com.example.DoroServer.domain.lectureContent.entity.LectureContent; import com.example.DoroServer.domain.lectureContent.repository.LectureContentRepository; import com.example.DoroServer.global.exception.BaseException; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -16,9 +17,8 @@ import org.mockito.Spy; import org.mockito.junit.jupiter.MockitoExtension; import org.modelmapper.ModelMapper; - - -import javax.annotation.meta.When; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; import java.lang.reflect.Field; import java.util.ArrayList; @@ -28,13 +28,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.*; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.*; @ExtendWith(MockitoExtension.class) class LectureContentServiceTest { - @InjectMocks private LectureContentService lectureContentService; @@ -42,13 +42,17 @@ class LectureContentServiceTest { private LectureContentRepository lectureContentRepository; @Mock - private ModelMapper modelMapper; + private ContentImageS3ServiceImpl contentImageS3Service; + + @Spy + private ModelMapper modelMapper = new ModelMapper(); @Spy private LectureContentMapper lectureContentMapper = Mappers.getMapper(LectureContentMapper.class); - private LectureContentDto setUpLectureContentDto(){ - return LectureContentDto.builder() + private LectureContent setUpLectureContent() { + return LectureContent.builder() + .id(1L) .kit("kit") .detail("detail") .requirement("requirement") @@ -56,43 +60,77 @@ private LectureContentDto setUpLectureContentDto(){ .build(); } - private LectureContent setUpLectureContent(){ - return LectureContent.builder() - .id(1L) + private CreateLectureContentReq setUpLectureContentReq() { + MockMultipartFile mockFile = new MockMultipartFile("file", "Hello, World!".getBytes()); + return CreateLectureContentReq.builder() .kit("kit") .detail("detail") .requirement("requirement") .content("content") + .files(new MultipartFile[] { mockFile }) .build(); } - private UpdateLectureContentReq setUpUpdateLectureContentReq(){ + + private UpdateLectureContentReq setUpUpdateLectureContentReq() { return UpdateLectureContentReq.builder() .kit("updated") .content("updated") .build(); } - private List setUpLectureContentList(int listSize){ - List allContents= new ArrayList(); - for(int i=0;i()) + .build(); + } + + private LectureContentRes setUpUpdatedLectureContentRes() { + return LectureContentRes.builder() + .id(1L) + .kit("updated") + .detail("detail") + .requirement("requirement") + .content("updated") + .images(new ArrayList<>()) + .build(); + } + + private List setUpLectureContentList(int listSize) { + List allContents = new ArrayList(); + for (int i = 0; i < listSize; i++) { allContents.add(setUpLectureContent()); } return allContents; } + private List setUpLectureContentResList(int listSize) { + List allContents = new ArrayList(); + for (int i = 0; i < listSize; i++) { + allContents.add(setUpLectureContentRes()); + } + return allContents; + } @DisplayName("강의자료 생성 Mapper 테스트") @Test void toLectureContentMapperTest() throws IllegalAccessException { - //given - LectureContentDto lectureContentDto = setUpLectureContentDto(); - //when - LectureContent lectureContent = lectureContentMapper.toLectureContent(lectureContentDto); + // given + CreateLectureContentReq createLectureContentReq = setUpLectureContentReq(); + LectureContent lectureContent = setUpLectureContent(); + + // when + LectureContent lectureContentMapped = modelMapper.map(createLectureContentReq, LectureContent.class); - //then - for(Field field :lectureContent.getClass().getDeclaredFields() ){ + // then + for (Field field : lectureContentMapped.getClass().getDeclaredFields()) { field.setAccessible(true); Object value = field.get(lectureContent); - if(field.getName().equals("id")){ + if (field.getName().equals("id")) { continue; } assertThat(value).isNotNull(); @@ -103,56 +141,74 @@ void toLectureContentMapperTest() throws IllegalAccessException { @DisplayName("강의 자료 생성 테스트") @Test void createLectureContentTest() { - // given - LectureContentDto lectureContentDto = setUpLectureContentDto(); + CreateLectureContentReq createLectureContentReq = setUpLectureContentReq(); LectureContent lectureContent = setUpLectureContent(); + LectureContentRes lectureContentRes = setUpLectureContentRes(); + + given(modelMapper.map(createLectureContentReq, LectureContent.class)).willReturn(lectureContent); + given(contentImageS3Service.uploadS3Images(any(MultipartFile[].class))).willReturn(new ArrayList()); + given(lectureContentRepository.save(any(LectureContent.class))).willReturn(setUpLectureContent()); + given(lectureContentMapper.toLectureContentRes(any(LectureContent.class))).willReturn(lectureContentRes); - given(lectureContentMapper.toLectureContent(any(LectureContentDto.class))).willReturn(lectureContent); - given(lectureContentRepository.save(any(LectureContent.class))).willReturn(lectureContent); // when - Long lectureID = lectureContentService.createLecture(lectureContentDto); + LectureContentRes savedLectureContent = lectureContentService.createLectureContent(createLectureContentReq); + // then - assertThat(lectureID).isEqualTo(lectureContent.getId()); - verify(lectureContentMapper,times(1)).toLectureContent(lectureContentDto); - verify(lectureContentRepository,times(1)).save(lectureContent); + verify(modelMapper, times(1)).map(createLectureContentReq, LectureContent.class); + verify(contentImageS3Service, times(1)).uploadS3Images(any(MultipartFile[].class)); + verify(lectureContentRepository, times(1)).save(any(LectureContent.class)); + verify(lectureContentMapper, times(1)).toLectureContentRes(any(LectureContent.class)); + assertThat(savedLectureContent).isNotNull(); } @DisplayName("강의 자료 조회 Mapper 테스트") @Test - void toLectureContentDtoMapperTest() throws IllegalAccessException { + void toLectureContentResListMapperTest() throws IllegalAccessException { // given - LectureContent lectureContent = setUpLectureContent(); + int contentCount = 5; + List lectureContentList = setUpLectureContentList(contentCount); + List lectureContentResList = setUpLectureContentResList(contentCount); // when - LectureContentDto lectureContentDto = lectureContentMapper.toLectureContentDto(lectureContent); + List mappedlectureContentResList = lectureContentMapper + .toLectureContentResList(lectureContentList); + // then - for(Field field :lectureContentDto.getClass().getDeclaredFields() ){ - field.setAccessible(true); - Object value = field.get(lectureContentDto); - assertThat(value).isNotNull(); + for (int i = 0; i < contentCount; i++) { + LectureContentRes mappedRes = mappedlectureContentResList.get(i); + LectureContentRes originalRes = lectureContentResList.get(i); + + for (Field field : mappedRes.getClass().getDeclaredFields()) { + field.setAccessible(true); + Object mappedValue = field.get(mappedRes); + Object originalValue = field.get(originalRes); + + assertThat(mappedValue).isEqualTo(originalValue); + } } } - @DisplayName("모든 강의 자료 조회 테스트") @Test - void findAllLectureContents() { + @DisplayName("강의 자료 조회 테스트") + void findAllLectureContentsTest() { // given - int contentCount=5; - List lectureContents = setUpLectureContentList(contentCount); - given(lectureContentRepository.findAll()).willReturn(lectureContents); - given(lectureContentMapper.toLectureContentDto(any(LectureContent.class))).willReturn(setUpLectureContentDto()); + int contentCount = 5; + List lectureContentList = setUpLectureContentList(contentCount); + List lectureContentResList = setUpLectureContentResList(contentCount); + + given(lectureContentRepository.findAll()).willReturn(lectureContentList); + given(lectureContentMapper.toLectureContentResList(anyList())).willReturn(lectureContentResList); // when - List allLectureContents = lectureContentService.findAllLectureContents(); + List result = lectureContentService.findAllLectureContents(); // then - verify(lectureContentRepository,times(1)).findAll(); - verify(lectureContentMapper,times(contentCount)).toLectureContentDto(any(LectureContent.class)); - assertThat(allLectureContents.size()).isEqualTo(contentCount); - + assertEquals(contentCount, result.size()); + verify(lectureContentRepository, times(1)).findAll(); + verify(lectureContentMapper, times(1)).toLectureContentResList(anyList()); } @DisplayName("강의 자료 업데이트 예외 테스트") @@ -161,13 +217,12 @@ void updateLectureContentExceptionTest() { // given LectureContent lectureContent = setUpLectureContent(); UpdateLectureContentReq updateLectureContentReq = setUpUpdateLectureContentReq(); - given(lectureContentRepository.findById(any(Long.class))).willReturn(Optional.empty()); - // when + given(lectureContentRepository.findById(any(Long.class))).willReturn(Optional.empty()); - // then - assertThrows(BaseException.class,()->{ - lectureContentService.updateLectureContent(lectureContent.getId(),updateLectureContentReq); + // when & then + assertThrows(BaseException.class, () -> { + lectureContentService.updateLectureContent(lectureContent.getId(), updateLectureContentReq); }); } @@ -177,14 +232,23 @@ void updateLectureContentTest() { // given LectureContent lectureContent = setUpLectureContent(); UpdateLectureContentReq updateLectureContentReq = setUpUpdateLectureContentReq(); + LectureContentRes updatedLectureContentRes = setUpUpdatedLectureContentRes(); + given(lectureContentRepository.findById(any(Long.class))).willReturn(Optional.of(lectureContent)); - doNothing().when(modelMapper).map(any(UpdateLectureContentReq.class),any(LectureContent.class)); + given(lectureContentRepository.save(any(LectureContent.class))).willReturn(lectureContent); + given(lectureContentMapper.toLectureContentRes(any(LectureContent.class))) + .willReturn(updatedLectureContentRes); // when - lectureContentService.updateLectureContent(lectureContent.getId(),updateLectureContentReq); + LectureContentRes updatedLectureContent = lectureContentService + .updateLectureContent(lectureContent.getId(), updateLectureContentReq); + // then - verify(lectureContentRepository,times(1)).findById(any(Long.class)); - verify(modelMapper,times(1)).map(any(UpdateLectureContentReq.class),any(LectureContent.class));; + verify(lectureContentRepository, times(1)).findById(any(Long.class)); + verify(lectureContentRepository, times(1)).save(any(LectureContent.class)); + verify(lectureContentMapper, times(1)).toLectureContentRes(any(LectureContent.class)); + assertThat(updatedLectureContent).isNotNull(); + assertThat(updatedLectureContent.getKit()).isEqualTo(updateLectureContentReq.getKit()); } diff --git a/src/test/java/com/example/DoroServer/domain/lectureContentImage/service/LectureContentImageServiceTest.java b/src/test/java/com/example/DoroServer/domain/lectureContentImage/service/LectureContentImageServiceTest.java new file mode 100644 index 00000000..3c39232c --- /dev/null +++ b/src/test/java/com/example/DoroServer/domain/lectureContentImage/service/LectureContentImageServiceTest.java @@ -0,0 +1,167 @@ +package com.example.DoroServer.domain.lectureContentImage.service; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyList; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.willDoNothing; +import static org.mockito.Mockito.doNothing; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mapstruct.factory.Mappers; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.Spy; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.web.multipart.MultipartFile; + +import com.example.DoroServer.domain.lectureContent.dto.LectureContentRes; +import com.example.DoroServer.domain.lectureContent.entity.LectureContent; +import com.example.DoroServer.domain.lectureContent.repository.LectureContentRepository; +import com.example.DoroServer.domain.lectureContent.service.ContentImageS3ServiceImpl; +import com.example.DoroServer.domain.lectureContentImage.dto.LectureContentImageMapper; +import com.example.DoroServer.domain.lectureContentImage.dto.LectureContentImageReq; +import com.example.DoroServer.domain.lectureContentImage.dto.LectureContentImageRes; +import com.example.DoroServer.domain.lectureContentImage.entity.LectureContentImage; +import com.example.DoroServer.domain.lectureContentImage.repository.LectureContentImageRepository; +import com.example.DoroServer.global.exception.BaseException; + +import java.util.List; + +@ExtendWith(MockitoExtension.class) +public class LectureContentImageServiceTest { + + @InjectMocks + private LectureContentImageService lectureContentImageService; + + @Mock + private LectureContentImageRepository lectureContentImageRepository; + + @Mock + private LectureContentRepository lectureContentRepository; + + @Mock + private ContentImageS3ServiceImpl contentImageS3Service; + + @Spy + private LectureContentImageMapper lectureContentImageMapper = Mappers.getMapper(LectureContentImageMapper.class); + + LectureContentImageReq setUplectureContentImageReq() { + MockMultipartFile mockFile = new MockMultipartFile("file", "Hello, World!".getBytes()); + return LectureContentImageReq.builder() + .files(new MultipartFile[] { mockFile }) + .build(); + } + + String UPLOADED_URL = "url"; + + LectureContent setUplectureContent() { + return LectureContent.builder() + .id(1L) + .kit("kit") + .detail("detail") + .requirement("requirement") + .content("content") + .build(); + } + + LectureContentImage setUplectureContentImage() { + return LectureContentImage.builder() + .id(1L) + .url(UPLOADED_URL) + .build(); + } + + LectureContentImageRes setUplectureContentImageRes() { + return LectureContentImageRes.builder() + .id(1L) + .url(UPLOADED_URL) + .build(); + } + + LectureContentRes setUplectureContentRes() { + return LectureContentRes.builder() + .id(1L) + .kit("kit") + .detail("detail") + .requirement("requirement") + .content("content") + .images(List.of(setUplectureContentImageRes())) + .build(); + } + + @Test + @DisplayName("강의 컨텐츠 이미지 추가 테스트") + void addLectureContentImages() { + // given + LectureContentImageReq lectureContentImageReq = setUplectureContentImageReq(); + Long id = 1L; + LectureContent lectureContent = setUplectureContent(); + List lectureContentImageResList = List.of(setUplectureContentImageRes()); + + given(lectureContentRepository.findById(id)).willReturn(Optional.of(lectureContent)); + given(contentImageS3Service.uploadS3Images(lectureContentImageReq.getFiles())) + .willReturn(List.of(UPLOADED_URL)); + given(lectureContentImageRepository.saveAll(anyList())) + .willReturn(List.of(setUplectureContentImage())); + given(lectureContentImageMapper.toLectureContentImageResList(lectureContent.getImages())) + .willReturn(lectureContentImageResList); + + // when + List result = lectureContentImageService.addLectureContentImages(id, + lectureContentImageReq); + + // then + assertThat(result).isEqualTo(lectureContentImageResList); + verify(lectureContentRepository, times(1)).findById(id); + verify(contentImageS3Service, times(1)).uploadS3Images(lectureContentImageReq.getFiles()); + verify(lectureContentImageRepository, times(1)).saveAll(anyList()); + verify(lectureContentImageMapper, times(1)).toLectureContentImageResList(lectureContent.getImages()); + + } + + @Test + @DisplayName("강의 컨텐츠 이미지 삭제 테스트") + void deleteLectureContentImage() { + // given + Long id = 1L; + Long imageId = 1L; + LectureContent lectureContent = setUplectureContent(); + LectureContentImage lectureContentImage = setUplectureContentImage(); + lectureContent.getImages().add(lectureContentImage); + + given(lectureContentRepository.findById(id)).willReturn(Optional.of(lectureContent)); + doNothing().when(contentImageS3Service).deleteS3Image(lectureContentImage); + willDoNothing().given(lectureContentImageRepository).delete(lectureContentImage); + + // when + lectureContentImageService.deleteLectureContentImage(id, imageId); + + // then + verify(lectureContentRepository, times(1)).findById(id); + verify(contentImageS3Service, times(1)).deleteS3Image(lectureContentImage); + verify(lectureContentImageRepository, times(1)).delete(lectureContentImage); + } + + @Test + @DisplayName("유효하지 않은 강의 컨텐츠 이미지 삭제 예외 발생 테스트") + void deleteLectureContentImageWithInvalidId() { + // given + Long id = -1L; + Long imageId = 1L; + + given(lectureContentRepository.findById(id)).willReturn(Optional.empty()); + + // when & then + assertThatThrownBy(() -> lectureContentImageService.deleteLectureContentImage(id, imageId)) + .isInstanceOf(BaseException.class); + } + +} diff --git a/src/test/java/com/example/DoroServer/domain/userLecture/service/UserLectureServiceTest.java b/src/test/java/com/example/DoroServer/domain/userLecture/service/UserLectureServiceTest.java index 2b88587e..c17ecb04 100644 --- a/src/test/java/com/example/DoroServer/domain/userLecture/service/UserLectureServiceTest.java +++ b/src/test/java/com/example/DoroServer/domain/userLecture/service/UserLectureServiceTest.java @@ -4,6 +4,8 @@ import com.example.DoroServer.domain.lecture.entity.LectureDate; import com.example.DoroServer.domain.lecture.entity.LectureStatus; import com.example.DoroServer.domain.lecture.repository.LectureRepository; +import com.example.DoroServer.domain.notification.dto.NotificationContentReq; +import com.example.DoroServer.domain.notification.service.NotificationServiceRefact; import com.example.DoroServer.domain.user.entity.Degree; import com.example.DoroServer.domain.user.entity.Gender; import com.example.DoroServer.domain.user.entity.StudentStatus; @@ -23,7 +25,6 @@ import org.mockito.*; import org.mockito.junit.jupiter.MockitoExtension; - import java.lang.reflect.Field; import java.time.LocalDate; import java.util.ArrayList; @@ -36,7 +37,6 @@ import static org.mockito.BDDMockito.given; import static org.mockito.Mockito.*; - @ExtendWith(MockitoExtension.class) class UserLectureServiceTest { @InjectMocks @@ -51,11 +51,13 @@ class UserLectureServiceTest { @Mock private LectureRepository lectureRepository; + @Mock + private NotificationServiceRefact notificationService; + @Spy private UserLectureMapper userLectureMapper = Mappers.getMapper(UserLectureMapper.class); - - private User setUpUser(Long userId){ + private User setUpUser(Long userId) { return User.builder() .id(userId) .name("name") @@ -73,7 +75,7 @@ private User setUpUser(Long userId){ .build(); } - private Lecture setUpLecture(Long lectureId){ + private Lecture setUpLecture(Long lectureId) { ArrayList dates = new ArrayList<>(); LocalDate now = LocalDate.now(); dates.add(now); @@ -103,11 +105,11 @@ private Lecture setUpLecture(Long lectureId){ .build(); } - private UserLecture setUpUserLecture(Long userLectureId){ - Long userId=3L; + private UserLecture setUpUserLecture(Long userLectureId) { + Long userId = 3L; User user = setUpUser(userId); - Long lectureId=2L; + Long lectureId = 2L; Lecture lecture = setUpLecture(lectureId); return UserLecture.builder() @@ -119,21 +121,21 @@ private UserLecture setUpUserLecture(Long userLectureId){ .build(); } - private List setUpUserLectureList(int length){ + private List setUpUserLectureList(int length) { ArrayList userLectureArrayList = new ArrayList<>(); - for(int i=0;i userLectures = setUpUserLectureList(userLectureCount); given(userLectureRepository.findAllTutors(any(Long.class))).willReturn(userLectures); FindAllTutorsRes findAllTutorsRes = setUpFindAllTutorsRes(); - given(userLectureMapper.toFindAllTutorsRes(any(UserLecture.class),any(User.class))).willReturn(findAllTutorsRes); + given(userLectureMapper.toFindAllTutorsRes(any(UserLecture.class), any(User.class))) + .willReturn(findAllTutorsRes); // when - Long lectureId=3L; + Long lectureId = 3L; List allTutors = userLectureService.findAllTutors(lectureId); // then - verify(userLectureRepository,times(1)).findAllTutors(any(Long.class)); + verify(userLectureRepository, times(1)).findAllTutors(any(Long.class)); assertThat(allTutors.size()).isEqualTo(userLectureCount); } @@ -269,11 +276,12 @@ void findAllTutorsTest() { @Test void findMyLecturesMapperTest() throws IllegalAccessException { // given - Long userLectureId=4L; + Long userLectureId = 4L; UserLecture userLecture = setUpUserLecture(userLectureId); // when - FindMyLecturesRes findMyLecturesRes = userLectureMapper.toFindMyLecturesRes(userLecture.getLecture(), userLecture); + FindMyLecturesRes findMyLecturesRes = userLectureMapper.toFindMyLecturesRes(userLecture.getLecture(), + userLecture); // then for (Field field : findMyLecturesRes.getClass().getDeclaredFields()) { field.setAccessible(true); @@ -292,14 +300,15 @@ void findMyLectureTest() { given(userLectureRepository.findMyLectures(any(Long.class))).willReturn(userLectures); FindMyLecturesRes findMyLecturesRes = setUpFindMyLectures(); - given(userLectureMapper.toFindMyLecturesRes(any(Lecture.class),any(UserLecture.class))).willReturn(findMyLecturesRes); + given(userLectureMapper.toFindMyLecturesRes(any(Lecture.class), any(UserLecture.class))) + .willReturn(findMyLecturesRes); // when - Long userId=4L; + Long userId = 4L; List myLectures = userLectureService.findMyLectures(userId); // then - verify(userLectureRepository,times(1)).findMyLectures(any(Long.class)); + verify(userLectureRepository, times(1)).findMyLectures(any(Long.class)); assertThat(myLectures.size()).isEqualTo(userLectureCount); } @@ -308,8 +317,8 @@ void findMyLectureTest() { @Test void selectTutorNoTutorExceptionTest() { // given - given(userLectureRepository.findUserLecture(any(Long.class),any(Long.class),any(TutorRole.class))).willReturn(Optional.empty()); - + given(userLectureRepository.findUserLecture(any(Long.class), any(Long.class), any(TutorRole.class))) + .willReturn(Optional.empty()); Long lectureId = 1L; SelectTutorReq selectTutorReq = SelectTutorReq.builder() @@ -329,9 +338,10 @@ void selectTutorNoTutorExceptionTest() { @Test void selectTutorNoUserExceptionTest() { // given - Long userLectureId=4L; + Long userLectureId = 4L; UserLecture userLecture = setUpUserLecture(userLectureId); - given(userLectureRepository.findUserLecture(any(Long.class),any(Long.class),any(TutorRole.class))).willReturn(Optional.of(userLecture)); + given(userLectureRepository.findUserLecture(any(Long.class), any(Long.class), any(TutorRole.class))) + .willReturn(Optional.of(userLecture)); given(userRepository.findById(any(Long.class))).willReturn(Optional.empty()); Long lectureId = 1L; @@ -352,9 +362,10 @@ void selectTutorNoUserExceptionTest() { @Test void selectTutorNoLectureExceptionTest() { // given - Long userLectureId=4L; + Long userLectureId = 4L; UserLecture userLecture = setUpUserLecture(userLectureId); - given(userLectureRepository.findUserLecture(any(Long.class),any(Long.class),any(TutorRole.class))).willReturn(Optional.of(userLecture)); + given(userLectureRepository.findUserLecture(any(Long.class), any(Long.class), any(TutorRole.class))) + .willReturn(Optional.of(userLecture)); given(userRepository.findById(any(Long.class))).willReturn(Optional.of(userLecture.getUser())); given(lectureRepository.findById(any(Long.class))).willReturn(Optional.empty()); @@ -378,18 +389,22 @@ void selectTutorNoLectureExceptionTest() { @Test void selectTutorTest() { // given - Long userLectureId=4L; + Long userLectureId = 4L; UserLecture userLecture = setUpUserLecture(userLectureId); - given(userLectureRepository.findUserLecture(any(Long.class),any(Long.class),any(TutorRole.class))).willReturn(Optional.of(userLecture)); + given(userLectureRepository.findUserLecture(any(Long.class), any(Long.class), any(TutorRole.class))) + .willReturn(Optional.of(userLecture)); - Long userId=3L; + Long userId = 3L; User user = setUpUser(userId); given(userRepository.findById(any(Long.class))).willReturn(Optional.of(user)); - Long lectureId=4L; + Long lectureId = 4L; Lecture lecture = setUpLecture(lectureId); given(lectureRepository.findById(any(Long.class))).willReturn(Optional.of(lecture)); + given(notificationService.sendNotificationToOne(any(Long.class), any(Long.class), + any(NotificationContentReq.class))) + .willReturn(userId); SelectTutorReq selectTutorReq = SelectTutorReq.builder() .userId(userId) @@ -397,21 +412,22 @@ void selectTutorTest() { .build(); // when - userLectureService.selectTutor(lectureId,selectTutorReq); + userLectureService.selectTutor(lectureId, selectTutorReq); + // then assertThat(userLecture.getTutorStatus()).isEqualTo(TutorStatus.ASSIGNED); } -// @DisplayName("강의 신청 취소 테스트") -// @Test -// void deleteLectureTest() { -// // given -// doNothing().when(userLectureRepository).deleteById(any(Long.class)); -// // when -// Long userLectureId=1L; -// userLectureService.deleteUserLecture(userLectureId,); -// // then -// verify(userLectureRepository,times(1)).deleteById(any(Long.class)); -// -// } + // @DisplayName("강의 신청 취소 테스트") + // @Test + // void deleteLectureTest() { + // // given + // doNothing().when(userLectureRepository).deleteById(any(Long.class)); + // // when + // Long userLectureId=1L; + // userLectureService.deleteUserLecture(userLectureId,); + // // then + // verify(userLectureRepository,times(1)).deleteById(any(Long.class)); + // + // } } \ No newline at end of file diff --git a/src/test/java/com/example/DoroServer/global/jwt/RedisServiceTest.java b/src/test/java/com/example/DoroServer/global/jwt/RedisServiceTest.java new file mode 100644 index 00000000..efcc4c9a --- /dev/null +++ b/src/test/java/com/example/DoroServer/global/jwt/RedisServiceTest.java @@ -0,0 +1,34 @@ +package com.example.DoroServer.global.jwt; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +public class RedisServiceTest { + + @Autowired + private RedisService redisService; + + @Test + @DisplayName("setValues, getValues 테스트") + public void testRedisSetAndGet() { + // given + String key = "test"; + String value = "test"; + + // when + redisService.setValues(key, value); + String returnedValue = redisService.getValues(key); + + // then + assertThat(returnedValue).isEqualTo(value); + + // after + redisService.deleteValues(key); + + } +} \ No newline at end of file diff --git a/src/test/java/com/example/DoroServer/global/message/MessageControllerTest.java b/src/test/java/com/example/DoroServer/global/message/MessageControllerTest.java new file mode 100644 index 00000000..dc3d8491 --- /dev/null +++ b/src/test/java/com/example/DoroServer/global/message/MessageControllerTest.java @@ -0,0 +1,58 @@ +package com.example.DoroServer.global.message; + +import static com.example.DoroServer.global.message.MessageServiceTestSetup.getVerifyAuthNumReq; +import static com.example.DoroServer.global.message.MessageServiceTestSetup.getVerifyAuthNumRes; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; + +import com.example.DoroServer.global.message.dto.VerifyAuthNumReq; +import com.example.DoroServer.global.message.dto.VerifyAuthNumRes; +import com.fasterxml.jackson.databind.ObjectMapper; + +@ExtendWith(MockitoExtension.class) +public class MessageControllerTest { + + @InjectMocks + private MessageController messageController; + + @Mock + private MessageServiceImpl messageService; + + private MockMvc mockMvc; + + @BeforeEach + void setUp() { + mockMvc = MockMvcBuilders.standaloneSetup(messageController).build(); + } + + @Test + @DisplayName("MockMvc를 통한 휴대폰 인증 세션 테스트") + public void verifyAuthNum2Test() throws Exception { + // given + VerifyAuthNumReq verifyAuthNumReq = getVerifyAuthNumReq(); + VerifyAuthNumRes verifyAuthNumRes = getVerifyAuthNumRes(); + + given(messageService.verifyAuthNum2(any(VerifyAuthNumReq.class))).willReturn(verifyAuthNumRes); + + // when & then + mockMvc.perform(post("/message/verify2") + .contentType(MediaType.APPLICATION_JSON) + .content(new ObjectMapper().writeValueAsString(verifyAuthNumReq))) + .andExpect(status().isOk()) + .andExpect(header().exists("Session-Id")); + } +} diff --git a/src/test/java/com/example/DoroServer/global/message/MessageServiceImplTest.java b/src/test/java/com/example/DoroServer/global/message/MessageServiceImplTest.java new file mode 100644 index 00000000..3ef9726b --- /dev/null +++ b/src/test/java/com/example/DoroServer/global/message/MessageServiceImplTest.java @@ -0,0 +1,51 @@ +package com.example.DoroServer.global.message; + +import static org.mockito.BDDMockito.given; +import static org.mockito.Mockito.verify; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.example.DoroServer.global.jwt.RedisService; +import com.example.DoroServer.global.message.dto.VerifyAuthNumReq; +import com.example.DoroServer.global.message.dto.VerifyAuthNumRes; + +import static com.example.DoroServer.global.message.MessageServiceTestSetup.getVerifyAuthNumReq; + +@ExtendWith(MockitoExtension.class) +public class MessageServiceImplTest { + + private MessageServiceImpl messageService; + + @Mock + private RedisService redisService; + + @BeforeEach + void setUp() { + messageService = new MessageServiceImpl("apiKey", "apiSecret", "fromNumber", "http://example.com", "pfid", + "templateId", redisService); + } + + @Test + @DisplayName("verifyAuthNum2 테스트") + void verifyAuthNum2Test() { + // given + VerifyAuthNumReq verifyAuthNumReq = getVerifyAuthNumReq(); + + given(redisService.getValues(verifyAuthNumReq.getMessageType() + verifyAuthNumReq.getPhone())) + .willReturn(verifyAuthNumReq.getAuthNum()); // redisService는 모두 정상적으로 작동한다고 가정 + + // when + VerifyAuthNumRes result = messageService.verifyAuthNum2(verifyAuthNumReq); + + // then + verify(redisService, Mockito.times(1)) + .getValues(verifyAuthNumReq.getMessageType() + verifyAuthNumReq.getPhone()); + assert (result.getSessionId().get("Session-Id") != null); + } +} diff --git a/src/test/java/com/example/DoroServer/global/message/MessageServiceTestSetup.java b/src/test/java/com/example/DoroServer/global/message/MessageServiceTestSetup.java new file mode 100644 index 00000000..9ff3c380 --- /dev/null +++ b/src/test/java/com/example/DoroServer/global/message/MessageServiceTestSetup.java @@ -0,0 +1,29 @@ +package com.example.DoroServer.global.message; + +import com.example.DoroServer.global.message.dto.SendAuthNumReq.MessageType; + +import org.springframework.http.HttpHeaders; + +import com.example.DoroServer.global.message.dto.VerifyAuthNumReq; +import com.example.DoroServer.global.message.dto.VerifyAuthNumRes; + +public class MessageServiceTestSetup { + + public static VerifyAuthNumReq getVerifyAuthNumReq() { + return VerifyAuthNumReq.builder() + .phone("01012345678") + .authNum("123456") + .messageType(MessageType.TEMP) + .build(); + + } + + public static VerifyAuthNumRes getVerifyAuthNumRes() { + HttpHeaders headers = new HttpHeaders(); + headers.add("Session-Id", "some-session-id"); + return VerifyAuthNumRes.builder() + .sessionId(headers) + .build(); + } + +} \ No newline at end of file