diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/assignment/dto/response/AssignmentResponseDto.java b/src/main/java/angel_bridge/angel_bridge_server/domain/assignment/dto/response/AssignmentResponseDto.java index 576db72..ea8ecde 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/assignment/dto/response/AssignmentResponseDto.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/assignment/dto/response/AssignmentResponseDto.java @@ -48,10 +48,11 @@ public static AssignmentResponseDto fromOngoing(Assignment assignment, int perfo .build(); } - public static AssignmentResponseDto fromClosed(Education education) { + public static AssignmentResponseDto fromClosed(Education education, int performanceRate) { return AssignmentResponseDto.builder() .educationId(education.getId()) .educationTitle(education.getEducationTitle()) + .performanceRate(performanceRate) .noticeLink(education.getNoticeLink()) .methodLink(education.getMethodLink()) .isFinished(true) diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/assignment/service/AssignmentService.java b/src/main/java/angel_bridge/angel_bridge_server/domain/assignment/service/AssignmentService.java index 27e5920..3dde6f3 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/assignment/service/AssignmentService.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/assignment/service/AssignmentService.java @@ -184,12 +184,12 @@ public AssignmentResponseDto getAssignmentBox(Long educationId, Long memberId) { Assignment currentAssignment = assignmentRepository.findCurrentAssignmentByEducationId(educationId, LocalDateTime.now()) .orElse(null); + int performanceRate = calculatePerformanceRate(educationId, memberId); + if (currentAssignment == null) { - return AssignmentResponseDto.fromClosed(education); + return AssignmentResponseDto.fromClosed(education, performanceRate); } - int performanceRate = calculatePerformanceRate(educationId, memberId); - return AssignmentResponseDto.fromOngoing(currentAssignment, performanceRate); } diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/banner/controller/AdminBannerController.java b/src/main/java/angel_bridge/angel_bridge_server/domain/banner/controller/AdminBannerController.java index eb43bac..9bb8ff0 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/banner/controller/AdminBannerController.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/banner/controller/AdminBannerController.java @@ -33,13 +33,6 @@ public CommonResponse updateBanner(@Valid @RequestPart(v return new CommonResponse<>(bannerService.updateBanner(request, file, bannerId), "해당 배너 이미지 수정에 성공하였습니다."); } - @Operation(summary = "배너 게시 여부 변경", description = "해당 배너 게시 여부를 변경하는 API") - @PatchMapping("/{bannerId}") - public CommonResponse changeIsPost(@PathVariable Long bannerId) { - - return new CommonResponse<>(bannerService.changeIsPost(bannerId), "해당 배너의 게시 여부를 변경하였습니다."); - } - @Operation(summary = "배너 삭제", description = "하나의 배너를 삭제하는 API") @DeleteMapping("/{bannerId}") public CommonResponse deleteBanner(@PathVariable Long bannerId) { diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/banner/dto/request/AdminBannerRequestDto.java b/src/main/java/angel_bridge/angel_bridge_server/domain/banner/dto/request/AdminBannerRequestDto.java index f40a580..5628d9d 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/banner/dto/request/AdminBannerRequestDto.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/banner/dto/request/AdminBannerRequestDto.java @@ -11,11 +11,7 @@ public record AdminBannerRequestDto( String name, @Schema(description = "배너 게시 순서", example = "1") - Integer priority, - - @NotNull - @Schema(description = "배너 게시 유무", example = "true") - Boolean isPost + Integer priority ) { public Banner toEntity(String file) { @@ -23,7 +19,6 @@ public Banner toEntity(String file) { .bannerImage(file) .name(this.name) .priority(this.priority) - .isPost(this.isPost) .build(); } } diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/banner/dto/response/AdminBannerResponseDto.java b/src/main/java/angel_bridge/angel_bridge_server/domain/banner/dto/response/AdminBannerResponseDto.java index aa2f068..1099603 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/banner/dto/response/AdminBannerResponseDto.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/banner/dto/response/AdminBannerResponseDto.java @@ -12,9 +12,7 @@ public record AdminBannerResponseDto( String name, - Integer priority, - - Boolean isPost + Integer priority ) { public static AdminBannerResponseDto from(Banner banner) { @@ -23,7 +21,6 @@ public static AdminBannerResponseDto from(Banner banner) { .imageFile(banner.getBannerImage()) .name(banner.getName()) .priority(banner.getPriority()) - .isPost(banner.getIsPost()) .build(); } } diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/banner/entity/Banner.java b/src/main/java/angel_bridge/angel_bridge_server/domain/banner/entity/Banner.java index 958d310..4a4bff6 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/banner/entity/Banner.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/banner/entity/Banner.java @@ -29,25 +29,16 @@ public class Banner extends BaseEntity { @Column(name = "priority") private Integer priority; - @Column(name = "is_post", nullable = false) - private Boolean isPost; - - public void changeIsPost() { - this.isPost = !this.isPost; - } - public void update(AdminBannerRequestDto request, String file) { this.bannerImage = file; this.name = request.name(); this.priority = request.priority(); - this.isPost = request.isPost(); } @Builder - public Banner(String bannerImage, String name, Integer priority, Boolean isPost) { + public Banner(String bannerImage, String name, Integer priority) { this.bannerImage = bannerImage; this.name = name; this.priority = priority; - this.isPost = isPost; } } diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/banner/service/BannerService.java b/src/main/java/angel_bridge/angel_bridge_server/domain/banner/service/BannerService.java index f2a1a6c..7889e9d 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/banner/service/BannerService.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/banner/service/BannerService.java @@ -80,18 +80,6 @@ public AdminBannerResponseDto updateBanner(AdminBannerRequestDto request, Multip return AdminBannerResponseDto.from(updateBanner); } - // [PATCH] 어드민 배너 게시 여부 변경 - @Transactional - public AdminBannerResponseDto changeIsPost(Long bannerId) { - - Banner banner = findBannerById(bannerId); - - banner.changeIsPost(); - Banner updateBanner = bannerRepository.save(banner); - - return AdminBannerResponseDto.from(updateBanner); - } - // [DELETE] 어드민 배너 삭제 @Transactional public void deleteBanner(Long bannerId) { diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/education/dto/response/EducationDetailResponseDto.java b/src/main/java/angel_bridge/angel_bridge_server/domain/education/dto/response/EducationDetailResponseDto.java index e57da10..2fbeeaf 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/education/dto/response/EducationDetailResponseDto.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/education/dto/response/EducationDetailResponseDto.java @@ -4,6 +4,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import lombok.Builder; +import java.math.BigDecimal; import java.time.LocalDate; @Builder @@ -26,8 +27,8 @@ public record EducationDetailResponseDto( @Schema(description = "모집 마감일", example = "2025-01-05") LocalDate recruitmentEndDate, - @Schema(description = "가격", example = "119,000원") - String price, + @Schema(description = "가격", example = "119,000") + BigDecimal price, @Schema(description = "프리뷰 이미지", example = "프리뷰 이미지 url") String preFile, @@ -39,6 +40,9 @@ public record EducationDetailResponseDto( public static EducationDetailResponseDto from(Education education, String preImage, String detailImage) { + String priceString = education.getPrice(); + BigDecimal price = new BigDecimal(priceString.replaceAll("[^0-9]", "")); + return EducationDetailResponseDto.builder() .title(education.getEducationTitle()) .description(education.getEducationDescription()) @@ -46,7 +50,7 @@ public static EducationDetailResponseDto from(Education education, String preIma .educationEndDate(education.getEducationEndDate()) .recruitmentStartDate(education.getRecruitmentStartDate()) .recruitmentEndDate(education.getRecruitmentEndDate()) - .price(education.getPrice()) + .price(price) .preFile(preImage) .detailFile(detailImage) .build(); diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/education/entity/Education.java b/src/main/java/angel_bridge/angel_bridge_server/domain/education/entity/Education.java index 5fbe3b1..41c41a5 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/education/entity/Education.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/education/entity/Education.java @@ -59,6 +59,10 @@ public class Education extends BaseEntity { @Column(name = "recruitment_status") private RecruitmentStatus recruitmentStatus; + public void setRecruitmentStatus(RecruitmentStatus status) { + this.recruitmentStatus = status; + } + public void update(AdminEducationRequestDto request, String preFile, String detailFile) { this.educationPreImage = preFile; this.educationDetailImage = detailFile; diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/education/service/EducationService.java b/src/main/java/angel_bridge/angel_bridge_server/domain/education/service/EducationService.java index fccc9e9..fce9a74 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/education/service/EducationService.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/education/service/EducationService.java @@ -12,15 +12,15 @@ import angel_bridge.angel_bridge_server.global.s3.service.ImageService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.*; +import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; +import java.time.LocalDate; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -35,6 +35,26 @@ public class EducationService { private final ImageService imageService; private final EducationRepository educationRepository; + // 자정에 recruitment Status 업데이트 + @Transactional + @Scheduled(cron = "0 0 0 * * ?") + public void updateRecruitmentStatus() { + + LocalDate today = LocalDate.now(); + + // 모집 예정 상태 업데이트 + educationRepository.findAllByRecruitmentStartDateAfter(today) + .forEach(education -> education.setRecruitmentStatus(RecruitmentStatus.UPCOMING)); + + // 모집 중 상태 업데이트 + educationRepository.findAllByRecruitmentStartDateBeforeAndRecruitmentEndDateAfter(today, today) + .forEach(education -> education.setRecruitmentStatus(RecruitmentStatus.ONGOING)); + + // 모집 종료 상태 업데이트 + educationRepository.findAllByRecruitmentEndDateBefore(today) + .forEach(education -> education.setRecruitmentStatus(RecruitmentStatus.CLOSED)); + } + // [GET] 일반 사용자 추천 교육 프로그램 조회 public List getRecommendationProgram() { @@ -106,14 +126,14 @@ public AdminEducationResponseDto updateEducation(AdminEducationRequestDto reques // 기존 이미지 삭제 후 새로운 이미지 저장 if (preImage != null && !preImage.isEmpty()) { if (preFile != null && !preFile.isEmpty()) { - imageService.deleteImage(preFile); +// imageService.deleteImage(preFile); } preFile = imageService.uploadImage(preImage); } if (detailImage != null && !detailImage.isEmpty()) { if (detailFile != null && !detailFile.isEmpty()) { - imageService.deleteImage(detailFile); +// imageService.deleteImage(detailFile); } detailFile = imageService.uploadImage(detailImage); @@ -154,20 +174,35 @@ public PagedResponseDto getAllProgram(int page) { if (page == 0) throw new ApplicationException(BAD_REQUEST_ERROR); - Pageable pageable = PageRequest.of(page - 1, 12); - // 페이지 조회 - Page educationPage = educationRepository.findAllActive(pageable); + // 모집 마감이 아닌 데이터와 모집 마감 데이터 모두 조회 + List activeEducations = educationRepository.findAllActiveAndNotClosed(); + List closedEducations = educationRepository.findAllClosed(); - // Education 객체를 EducationResponseDto로 매핑 - List content = educationPage.getContent().stream() + // 모집 중 데이터와 마감 데이터 병합 + List allEducations = new ArrayList<>(); + allEducations.addAll(activeEducations); + allEducations.addAll(closedEducations); + + // 전체 데이터를 EducationResponseDto로 매핑 + List allContent = allEducations.stream() .map(education -> EducationResponseDto.from( education, imageService.getImageUrl(education.getEducationPreImage()))) .toList(); + // 페이지네이션 처리 + int pageSize = 12; + int start = (page - 1) * pageSize; + int end = Math.min(start + pageSize, allContent.size()); + + if (start >= allContent.size()) + throw new ApplicationException(BAD_REQUEST_ERROR); + + List pagedContent = allContent.subList(start, end); + // PagedResponseDto로 변환하여 반환 return PagedResponseDto.from( - new PageImpl<>(content, pageable, educationPage.getTotalElements()) + new PageImpl<>(pagedContent, PageRequest.of(page - 1, pageSize), allContent.size()) ); } @@ -178,7 +213,7 @@ public PagedResponseDto getAllOngoingProgram(int page) { throw new ApplicationException(BAD_REQUEST_ERROR); } - Pageable pageable = PageRequest.of(page - 1, 12); + Pageable pageable = PageRequest.of(page - 1, 12, Sort.by(Sort.Direction.ASC, "recruitmentEndDate")); // 페이지 조회 Page educationPage = educationRepository.findByRecruitmentStatusAndDeletedAtIsNull(RecruitmentStatus.ONGOING, pageable); @@ -202,7 +237,7 @@ public PagedResponseDto getAllUpcomingProgram(int page) { throw new ApplicationException(BAD_REQUEST_ERROR); } - Pageable pageable = PageRequest.of(page - 1, 12); + Pageable pageable = PageRequest.of(page - 1, 12, Sort.by(Sort.Direction.ASC, "recruitmentEndDate")); // 페이지 조회 Page educationPage = educationRepository.findByRecruitmentStatusAndDeletedAtIsNull(RecruitmentStatus.UPCOMING, pageable); diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/enrollment/entity/Enrollment.java b/src/main/java/angel_bridge/angel_bridge_server/domain/enrollment/entity/Enrollment.java index d0bbf4f..2f97c0e 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/enrollment/entity/Enrollment.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/enrollment/entity/Enrollment.java @@ -8,10 +8,12 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.SQLDelete; @Entity @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) +@SQLDelete(sql = "UPDATE Enrollment SET deleted_at = NOW() where enrollment_id = ?") public class Enrollment extends BaseEntity { @Id diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/enrollment/service/EnrollmentService.java b/src/main/java/angel_bridge/angel_bridge_server/domain/enrollment/service/EnrollmentService.java index 5cbf7bd..cc9424c 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/enrollment/service/EnrollmentService.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/enrollment/service/EnrollmentService.java @@ -10,10 +10,7 @@ import angel_bridge.angel_bridge_server.global.repository.MemberRepository; import angel_bridge.angel_bridge_server.global.s3.service.ImageService; import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.*; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -51,7 +48,7 @@ public PagedResponseDto getInProgressProgram(int page, Lo throw new ApplicationException(BAD_REQUEST_ERROR); } - Pageable pageable = PageRequest.of(page - 1, 4); + Pageable pageable = PageRequest.of(page - 1, 4, Sort.by(Sort.Direction.DESC, "createdAt")); Page enrollmentPage = enrollmentRepository.findByMemberAndEnrollmentStatusAndDeletedAtIsNull(member, EnrollmentStatus.IN_PROGRESS, pageable); @@ -75,7 +72,7 @@ public PagedResponseDto getScheduledProgram(int page, Lon throw new ApplicationException(BAD_REQUEST_ERROR); } - Pageable pageable = PageRequest.of(page - 1, 4); + Pageable pageable = PageRequest.of(page - 1, 4, Sort.by(Sort.Direction.DESC, "createdAt")); Page enrollmentPage = enrollmentRepository.findByMemberAndEnrollmentStatusAndDeletedAtIsNull(member, EnrollmentStatus.SCHEDULED, pageable); @@ -99,7 +96,7 @@ public PagedResponseDto getCompletedProgram(int page, Lon throw new ApplicationException(BAD_REQUEST_ERROR); } - Pageable pageable = PageRequest.of(page - 1, 4); + Pageable pageable = PageRequest.of(page - 1, 4, Sort.by(Sort.Direction.DESC, "createdAt")); Page enrollmentPage = enrollmentRepository.findByMemberAndEnrollmentStatusAndDeletedAtIsNull(member, EnrollmentStatus.COMPLETED, pageable); diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/member/controller/AuthController.java b/src/main/java/angel_bridge/angel_bridge_server/domain/member/controller/AuthController.java index 2b258fd..1f53210 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/member/controller/AuthController.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/member/controller/AuthController.java @@ -1,6 +1,7 @@ package angel_bridge.angel_bridge_server.domain.member.controller; import angel_bridge.angel_bridge_server.domain.member.dto.request.AuthRequestDto; +import angel_bridge.angel_bridge_server.domain.member.dto.request.TokenReissueRequestDto; import angel_bridge.angel_bridge_server.domain.member.dto.response.MemberResponseDto; import angel_bridge.angel_bridge_server.domain.member.service.AuthService; import angel_bridge.angel_bridge_server.global.common.response.CommonResponse; @@ -13,10 +14,11 @@ import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; -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 org.springframework.web.bind.annotation.*; + +import java.util.Arrays; +import java.util.Optional; +import java.util.stream.Stream; @RestController @RequestMapping("/api/v1/auth") @@ -28,9 +30,10 @@ public class AuthController { @PostMapping("/reissue") @Operation(summary = "토큰 재발급", description = "토큰 재발급 요청 API") - public CommonResponse reissue(HttpServletRequest request, HttpServletResponse response) { + public CommonResponse reissue(@RequestBody TokenReissueRequestDto requestDto, HttpServletResponse response) { - String refreshToken = authService.extractRefreshToken(request); +// String refreshToken = authService.extractRefreshToken(request); + String refreshToken = requestDto.refreshToken(); authService.validateRefreshToken(refreshToken); String newAccessToken = authService.reissueAccessToken(refreshToken); @@ -41,6 +44,25 @@ public CommonResponse reissue(HttpServletRequest request, HttpServletRespo return new CommonResponse<>("토큰 재발급을 성공하였습니다"); } + @GetMapping("/checkToken") + public CommonResponse checkToken(HttpServletRequest request) { + + String refreshToken = Optional.ofNullable(request.getCookies()) + .map(Arrays::stream) // 쿠키 배열을 스트림으로 변환 + .orElseGet(Stream::empty) // null인 경우 빈 스트림 반환 + .filter(cookie -> "refreshToken".equals(cookie.getName())) // refreshToken 이름 필터링 + .map(Cookie::getValue) // refreshToken 값 추출 + .findFirst() // 첫 번째 값 반환 + .orElse(null); // 없으면 null 반환 + + // refreshToken 값 반환 + if (refreshToken != null) { + return new CommonResponse<>(refreshToken, "Refresh token found"); + } else { + return new CommonResponse<>(null, "No refresh token found"); + } + } + @PostMapping("/signup") @Operation(summary = "회원가입 정보 입력", description = "회원가입 시 초기 정보 입력 받는 API") public CommonResponse saveMemberInfo(@AuthenticationPrincipal CustomOAuth2User userDetails, @Valid @RequestBody AuthRequestDto request) { diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/member/dto/request/TokenReissueRequestDto.java b/src/main/java/angel_bridge/angel_bridge_server/domain/member/dto/request/TokenReissueRequestDto.java new file mode 100644 index 0000000..e1f0f8f --- /dev/null +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/member/dto/request/TokenReissueRequestDto.java @@ -0,0 +1,6 @@ +package angel_bridge.angel_bridge_server.domain.member.dto.request; + +public record TokenReissueRequestDto( + String refreshToken +) { +} diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/member/service/AuthService.java b/src/main/java/angel_bridge/angel_bridge_server/domain/member/service/AuthService.java index 7420ccf..5cb859f 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/member/service/AuthService.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/member/service/AuthService.java @@ -105,7 +105,7 @@ private Cookie createCookie(String key, String value) { cookie.setMaxAge(60 * 60 * 24 * 14); cookie.setHttpOnly(true); cookie.setPath("/"); - // cookie.setSecure(true); + cookie.setSecure(true); return cookie; } diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/payment/controller/PaymentController.java b/src/main/java/angel_bridge/angel_bridge_server/domain/payment/controller/PaymentController.java index ad64b1d..9e4f971 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/payment/controller/PaymentController.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/payment/controller/PaymentController.java @@ -1,6 +1,7 @@ package angel_bridge.angel_bridge_server.domain.payment.controller; import angel_bridge.angel_bridge_server.domain.payment.dto.request.ConfirmPaymentRequestDto; +import angel_bridge.angel_bridge_server.domain.payment.dto.request.SaveAmountRequestDto; import angel_bridge.angel_bridge_server.domain.payment.dto.response.CancelPaymentRequestDto; import angel_bridge.angel_bridge_server.domain.payment.dto.response.PaymentResponseDto; import angel_bridge.angel_bridge_server.domain.payment.service.PaymentService; @@ -9,6 +10,7 @@ import angel_bridge.angel_bridge_server.global.oauth2.dto.CustomOAuth2User; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.servlet.http.HttpSession; import lombok.AllArgsConstructor; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.*; @@ -21,6 +23,22 @@ public class PaymentController { private final PaymentService paymentService; + @Operation(summary = "결제 정보 저장", description = "결제 정보(orderId, amount)를 세션에 임시 저장하는 API") + @PostMapping("/saveAmount") + public CommonResponse saveAmount(HttpSession session, @RequestBody SaveAmountRequestDto saveAmountRequestDto) { + + session.setAttribute(saveAmountRequestDto.orderId(), String.valueOf(saveAmountRequestDto.amount())); + return new CommonResponse<>("결제 정보 저장을 완료했습니다."); + } + + @Operation(summary = "결제 정보 검증", description = "결제 정보(orderId, amount)를 검증하는 API") + @PostMapping ("/verifyAmount") + public CommonResponse verifyAmount(HttpSession session, @RequestBody SaveAmountRequestDto saveAmountRequestDto) { + + paymentService.verifyAmount(session, saveAmountRequestDto); + return new CommonResponse<>("결제 정보 검증을 완료했습니다."); + } + @Operation(summary = "결제 승인", description = "결제 승인 API") @PostMapping("/confirm/{educationId}") public CommonResponse confirmPayment( diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/payment/dto/request/SaveAmountRequestDto.java b/src/main/java/angel_bridge/angel_bridge_server/domain/payment/dto/request/SaveAmountRequestDto.java new file mode 100644 index 0000000..deceae9 --- /dev/null +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/payment/dto/request/SaveAmountRequestDto.java @@ -0,0 +1,8 @@ +package angel_bridge.angel_bridge_server.domain.payment.dto.request; + +public record SaveAmountRequestDto( + + String orderId, + Long amount +) { +} diff --git a/src/main/java/angel_bridge/angel_bridge_server/domain/payment/service/PaymentService.java b/src/main/java/angel_bridge/angel_bridge_server/domain/payment/service/PaymentService.java index c4785c4..5d79ca0 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/domain/payment/service/PaymentService.java +++ b/src/main/java/angel_bridge/angel_bridge_server/domain/payment/service/PaymentService.java @@ -5,6 +5,7 @@ import angel_bridge.angel_bridge_server.domain.enrollment.entity.EnrollmentStatus; import angel_bridge.angel_bridge_server.domain.member.entity.Member; import angel_bridge.angel_bridge_server.domain.payment.dto.request.ConfirmPaymentRequestDto; +import angel_bridge.angel_bridge_server.domain.payment.dto.request.SaveAmountRequestDto; import angel_bridge.angel_bridge_server.domain.payment.dto.response.*; import angel_bridge.angel_bridge_server.domain.payment.entity.TossPayment; import angel_bridge.angel_bridge_server.global.common.response.PagedResponseDto; @@ -15,6 +16,7 @@ import angel_bridge.angel_bridge_server.global.repository.TossPaymentRepository; import angel_bridge.angel_bridge_server.global.s3.service.ImageService; import com.fasterxml.jackson.databind.ObjectMapper; +import jakarta.servlet.http.HttpSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -55,6 +57,17 @@ public PaymentService(WebClient.Builder webClientBuilder, this.imageService = imageService; } + // [GET] 결제 정보 검증 + public void verifyAmount(HttpSession session, SaveAmountRequestDto saveAmountRequestDto) { + + String amount = (String) session.getAttribute(saveAmountRequestDto.orderId()); + if(amount == null || !amount.equals(String.valueOf(saveAmountRequestDto.amount()))) + throw new ApplicationException(INVALID_PAYMENT_DATA); + + // 검증 되었으면 기존 검증 정보 삭제 + session.removeAttribute(saveAmountRequestDto.orderId()); + } + // [POST] 결제 취소 @Transactional public void cancelPayment(CancelPaymentRequestDto cancelPaymentRequestDto, Long enrollmentId) throws Exception { diff --git a/src/main/java/angel_bridge/angel_bridge_server/global/config/SecurityConfig.java b/src/main/java/angel_bridge/angel_bridge_server/global/config/SecurityConfig.java index d743afb..fe7230a 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/global/config/SecurityConfig.java +++ b/src/main/java/angel_bridge/angel_bridge_server/global/config/SecurityConfig.java @@ -50,7 +50,11 @@ public CorsConfiguration getCorsConfiguration(HttpServletRequest request) { "http://localhost:8080", "http://localhost:8081", "http://3.39.14.152:8080", - "http://3.39.14.152:8081" + "http://3.39.14.152:8081", + "https://api.angelbridge.site", + "https://www.angelbridge.site", + "https://angelbridge.site", + "https://abc.angelbridge.site" )); configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS")); diff --git a/src/main/java/angel_bridge/angel_bridge_server/global/config/SwaggerConfig.java b/src/main/java/angel_bridge/angel_bridge_server/global/config/SwaggerConfig.java index 5108d2b..27f275f 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/global/config/SwaggerConfig.java +++ b/src/main/java/angel_bridge/angel_bridge_server/global/config/SwaggerConfig.java @@ -5,6 +5,7 @@ import io.swagger.v3.oas.models.info.Info; import io.swagger.v3.oas.models.security.SecurityRequirement; import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -27,6 +28,7 @@ public OpenAPI openAPI() { return new OpenAPI() .components(components) .info(apiInfo()) + .addServersItem(new Server().url("https://api.angelbridge.site").description("Production server")) .addSecurityItem(securityRequirement); } diff --git a/src/main/java/angel_bridge/angel_bridge_server/global/exception/ExceptionCode.java b/src/main/java/angel_bridge/angel_bridge_server/global/exception/ExceptionCode.java index 861ff90..1b33bb5 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/global/exception/ExceptionCode.java +++ b/src/main/java/angel_bridge/angel_bridge_server/global/exception/ExceptionCode.java @@ -57,7 +57,8 @@ public enum ExceptionCode { // 6000: payment Error PAYMENT_API_FAIL(HttpStatus.BAD_REQUEST, 6001, "결제 api 호출에 실패하였습니다."), NOT_FOUND_PAYMENT_METHOD(HttpStatus.NOT_FOUND, 6002, "결제 방법이 유효하지 않습니다."), - NOT_FOUND_PAYMENT_STATUS(HttpStatus.NOT_FOUND, 6003, "결제 상태가 유효하지 않습니다."); + NOT_FOUND_PAYMENT_STATUS(HttpStatus.NOT_FOUND, 6003, "결제 상태가 유효하지 않습니다."), + INVALID_PAYMENT_DATA(HttpStatus.BAD_REQUEST, 6004, "결제 정보가 유효하지 않습니다."); //7000: [임의] Error diff --git a/src/main/java/angel_bridge/angel_bridge_server/global/oauth2/CustomSuccessHandler.java b/src/main/java/angel_bridge/angel_bridge_server/global/oauth2/CustomSuccessHandler.java index ce008e5..0c32844 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/global/oauth2/CustomSuccessHandler.java +++ b/src/main/java/angel_bridge/angel_bridge_server/global/oauth2/CustomSuccessHandler.java @@ -60,11 +60,12 @@ public void onAuthenticationSuccess(HttpServletRequest request, HttpServletRespo response.setStatus(HttpStatus.OK.value()); response.setContentType("application/json"); response.setCharacterEncoding("UTF-8"); + response.setHeader("Access-Control-Expose-Headers", "Authorization"); response.getWriter().write("{\"result\": \"로그인이 성공적으로 완료되었습니다.\"}"); // TODO: 프론트 배포 시 수정 예정 // 로그인 성공 시 이동 경로 지정 - response.sendRedirect("http://3.39.14.152/env"); + response.sendRedirect("https://www.angelbridge.site"); } // 프론트에 전달할 방식 : 쿠키 방식 @@ -73,8 +74,7 @@ private Cookie createCookie(String key, String value) { Cookie cookie = new Cookie(key, value); cookie.setMaxAge(60 * 60 * 24 * 14); - // TODO: https로 배포 시 추가 예정 - //cookie.setSecure(true); // https 통신에서만 가능하게 함 + cookie.setSecure(true); // https 통신에서만 가능하게 함 cookie.setPath("/"); cookie.setHttpOnly(true); diff --git a/src/main/java/angel_bridge/angel_bridge_server/global/repository/BannerRepository.java b/src/main/java/angel_bridge/angel_bridge_server/global/repository/BannerRepository.java index bf00fa0..8ad8b00 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/global/repository/BannerRepository.java +++ b/src/main/java/angel_bridge/angel_bridge_server/global/repository/BannerRepository.java @@ -11,7 +11,7 @@ public interface BannerRepository extends JpaRepository { Optional findByIdAndDeletedAtIsNull(Long bannerId); - @Query("SELECT b FROM Banner b WHERE b.deletedAt IS NULL AND b.isPost = true ORDER BY b.priority ASC") + @Query("SELECT b FROM Banner b WHERE b.deletedAt IS NULL ORDER BY b.priority ASC") List findAllActiveBannersSortedByPriority(); Optional findByPriorityAndDeletedAtIsNull(int priority); diff --git a/src/main/java/angel_bridge/angel_bridge_server/global/repository/EducationRepository.java b/src/main/java/angel_bridge/angel_bridge_server/global/repository/EducationRepository.java index 755fcc4..d907367 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/global/repository/EducationRepository.java +++ b/src/main/java/angel_bridge/angel_bridge_server/global/repository/EducationRepository.java @@ -8,11 +8,22 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.repository.query.Param; +import java.time.LocalDate; import java.util.List; import java.util.Optional; public interface EducationRepository extends JpaRepository { + List findAllByRecruitmentStartDateAfter(LocalDate date); + List findAllByRecruitmentStartDateBeforeAndRecruitmentEndDateAfter(LocalDate startDate, LocalDate endDate); + List findAllByRecruitmentEndDateBefore(LocalDate date); + + @Query("SELECT e FROM Education e WHERE e.deletedAt IS NULL AND e.recruitmentStatus != 'CLOSED'") + List findAllActiveAndNotClosed(); + + @Query("SELECT e FROM Education e WHERE e.deletedAt IS NULL AND e.recruitmentStatus = 'CLOSED'") + List findAllClosed(); + @Query("SELECT e FROM Education e WHERE e.deletedAt IS NULL") Page findAllActive(Pageable pageable); diff --git a/src/main/java/angel_bridge/angel_bridge_server/global/s3/service/ImageService.java b/src/main/java/angel_bridge/angel_bridge_server/global/s3/service/ImageService.java index d5bea34..298f4fe 100644 --- a/src/main/java/angel_bridge/angel_bridge_server/global/s3/service/ImageService.java +++ b/src/main/java/angel_bridge/angel_bridge_server/global/s3/service/ImageService.java @@ -32,7 +32,8 @@ public class ImageService { */ private String changedImageName(String originName) { String random = UUID.randomUUID().toString(); - String extension = originName.substring(originName.lastIndexOf(".")); + int dotIndex = originName.lastIndexOf("."); + String extension = dotIndex == -1 ? "" : originName.substring(dotIndex); return random + extension; } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index ca8a1ea..abf5469 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -39,7 +39,7 @@ spring: client: registration: kakao: - redirect-uri: http://3.39.14.152:8080/login/oauth2/code/kakao + redirect-uri: https://api.angelbridge.site/login/oauth2/code/kakao server: port: 8080 @@ -58,7 +58,7 @@ spring: client: registration: kakao: - redirect-uri: http://3.39.14.152:8081/login/oauth2/code/kakao + redirect-uri: https://api.angelbridge.site/login/oauth2/code/kakao server: port: 8081 @@ -77,7 +77,7 @@ spring: fail-on-empty-beans: false servlet: multipart: - max-file-size: 5MB + max-file-size: 10MB max-request-size: 10MB security: @@ -99,6 +99,10 @@ spring: userInfoUri: https://kapi.kakao.com/v2/user/me userNameAttribute: id +server: + tomcat: + max-http-form-post-size: 10MB + logging.level: org.hibernate.SQL: debug org.springframework.web: debug