From 6e365b99aed1ffdcf77140520ea9333c9b596696 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:40:03 +0900 Subject: [PATCH 01/36] =?UTF-8?q?refactor:=20banner=20valid=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mosuserver/presentation/admin/AdminBannerController.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/AdminBannerController.java b/src/main/java/life/mosu/mosuserver/presentation/admin/AdminBannerController.java index eb852b2f..b8257969 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/AdminBannerController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/AdminBannerController.java @@ -1,5 +1,6 @@ package life.mosu.mosuserver.presentation.admin; +import jakarta.validation.Valid; import java.util.List; import life.mosu.mosuserver.application.admin.AdminBannerService; import life.mosu.mosuserver.global.util.ApiResponseWrapper; @@ -28,7 +29,7 @@ public class AdminBannerController { @PostMapping @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") public ResponseEntity> create( - @RequestBody BannerRequest request) { + @Valid @RequestBody BannerRequest request) { adminBannerService.create(request); return ResponseEntity.ok( ApiResponseWrapper.success(HttpStatus.OK, "배너 등록 성공")); From 510da445e3b61e48d0dd86a6381d34500b684ee6 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:42:22 +0900 Subject: [PATCH 02/36] =?UTF-8?q?refactor:=20AdminDashboard=20response=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../admin/AdminDashboardService.java | 17 +++++++++++++---- .../admin/dto/DashBoardResponse.java | 17 ++++++++++++++--- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java b/src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java index e772217f..97426a29 100644 --- a/src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java +++ b/src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java @@ -1,7 +1,9 @@ package life.mosu.mosuserver.application.admin; import life.mosu.mosuserver.domain.examapplication.repository.ExamApplicationJpaRepository; +import life.mosu.mosuserver.domain.refund.entity.RefundStatus; import life.mosu.mosuserver.domain.refund.repository.RefundJpaRepository; +import life.mosu.mosuserver.domain.user.entity.UserRole; import life.mosu.mosuserver.domain.user.repository.UserJpaRepository; import life.mosu.mosuserver.presentation.admin.dto.DashBoardResponse; import lombok.RequiredArgsConstructor; @@ -17,10 +19,17 @@ public class AdminDashboardService { // 대시보드 정보 조회 public DashBoardResponse getAll() { - Long applicationCounts = examApplicationJpaRepository.count(); - Long refundCounts = refundJpaRepository.count(); - Long userCounts = userJpaRepository.count(); - return new DashBoardResponse(applicationCounts, refundCounts, userCounts); + Long applicationCounts = examApplicationJpaRepository.countAll(); + Long refundDoneCounts = refundJpaRepository.countByRefundStatus(RefundStatus.DONE); + Long refundAbortedCounts = refundJpaRepository.countByRefundStatus(RefundStatus.ABORTED); + Long userCounts = userJpaRepository.countByUserRoleNot(UserRole.ROLE_ADMIN); + + return DashBoardResponse.of( + applicationCounts, + refundDoneCounts, + refundAbortedCounts, + userCounts + ); } diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java index 0b63f660..fb57f0cf 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java @@ -2,12 +2,23 @@ public record DashBoardResponse( Long applicationCounts, - Long refundCounts, + Long refundDoneCounts, + Long refundAbortedCounts, Long userCounts ) { - public static DashBoardResponse of(Long applicationCounts, Long refundCounts, Long userCounts) { - return new DashBoardResponse(applicationCounts, refundCounts, userCounts); + public static DashBoardResponse of( + Long applicationCounts, + Long refundDoneCounts, + Long refundAbortedCounts, + Long userCounts + ) { + return new DashBoardResponse( + applicationCounts, + refundDoneCounts, + refundAbortedCounts, + userCounts + ); } } From 84a88f248b49baf3e8486a8fb41c8709683ae379 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:43:27 +0900 Subject: [PATCH 03/36] =?UTF-8?q?refactor:=20AdminDashboard=20count=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExamApplicationJpaRepository.java | 37 ++++++++++++++++++- .../repository/RefundJpaRepository.java | 4 ++ .../user/repository/UserJpaRepository.java | 3 ++ 3 files changed, 42 insertions(+), 2 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamApplicationJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamApplicationJpaRepository.java index a1e378ec..363d9ea6 100644 --- a/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamApplicationJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamApplicationJpaRepository.java @@ -44,9 +44,12 @@ public interface ExamApplicationJpaRepository extends JOIN PaymentJpaEntity p on p.examApplicationId = ea.id WHERE ea.id = :examApplicationId AND p.paymentStatus = 'DONE' + AND ea.userId = :userId + AND p.deleted = false """) - Optional findExamApplicationInfoById(Long userId, - Long examApplicationId); + Optional findExamApplicationInfoById( + @Param("userId") Long userId, + @Param("examApplicationId") Long examApplicationId); @Query(""" @@ -67,6 +70,7 @@ Optional findExamApplicationInfoById(Long userId, WHERE ea.id = :examApplicationId AND u.id = :userId AND p.paymentStatus = 'DONE' + AND p.deleted = false """) Optional findExamTicketInfoProjectionById( @Param("userId") Long userId, @@ -105,6 +109,7 @@ Optional findExamTicketInfoProjectionById( JOIN PaymentJpaEntity p on p.examApplicationId = ea.id WHERE ea.id = :targetId AND p.paymentStatus = 'DONE' + AND p.deleted = false """) Optional findExamAndPaymentByExamApplicationId( @Param("targetId") Long targetId); @@ -121,6 +126,7 @@ Optional findExamAndPaymentByExamApplicationId( JOIN PaymentJpaEntity p ON p.examApplicationId = ea.id WHERE ea.id = :examApplicationId AND p.paymentStatus = 'DONE' + AND p.deleted = false """) Optional findExamInfo(@Param("examApplicationId") Long examApplicationId); @@ -137,6 +143,7 @@ Optional findExamAndPaymentByExamApplicationId( JOIN PaymentJpaEntity p ON p.examApplicationId = ea.id WHERE ea.id = :examApplicationId AND p.paymentStatus = 'DONE' + AND p.deleted = false """) Optional findExamInfoWithExamNumber( @Param("examApplicationId") Long examApplicationId); @@ -148,6 +155,7 @@ SELECT case when COUNT(ea) > 0 then true else false end WHERE ea.id = :examApplicationId AND ea.userId = :userId AND p.paymentStatus = 'DONE' + AND p.deleted = false """) boolean existByUserIdAndExamApplicationId(@Param("userId") Long userId, @Param("examApplicationId") Long examApplicationId); @@ -161,4 +169,29 @@ boolean existByUserIdAndExamApplicationId(@Param("userId") Long userId, AND e.deleted = false """) Optional findByOrderId(String orderId); + + @Query( + """ + SELECT CASE WHEN COUNT(ea) > 0 THEN true ELSE false END + FROM ExamApplicationJpaEntity ea + JOIN PaymentJpaEntity p ON ea.id = p.examApplicationId + WHERE ea.userId = :userId + AND p.paymentStatus = 'DONE' + AND p.deleted = false + AND ea.examId IN :examIds + """ + ) + boolean existsByUserIdAndExamIds( + @Param("userId") Long userId, + @Param("examIds") List examIds); + + + @Query(""" + SELECT COUNT(ea) + FROM ExamApplicationJpaEntity ea + JOIN PaymentJpaEntity p ON ea.id = p.examApplicationId + WHERE p.paymentStatus = 'DONE' + AND p.deleted = false + """) + long countAll(); } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java index bea9ebcb..d9489b8e 100644 --- a/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java @@ -4,6 +4,7 @@ import java.util.List; import java.util.Optional; import life.mosu.mosuserver.domain.refund.entity.RefundJpaEntity; +import life.mosu.mosuserver.domain.refund.entity.RefundStatus; import life.mosu.mosuserver.domain.refund.projection.RefundNotifyProjection; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -53,4 +54,7 @@ WHERE r.refundStatus in ('ABORTED') AND r.createdAt < :time """) List findFailedRefunds(@Param("time") LocalDateTime time); + + long countByRefundStatus(RefundStatus refundStatus); + } diff --git a/src/main/java/life/mosu/mosuserver/domain/user/repository/UserJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/user/repository/UserJpaRepository.java index cd553965..84ae8a87 100644 --- a/src/main/java/life/mosu/mosuserver/domain/user/repository/UserJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/user/repository/UserJpaRepository.java @@ -2,6 +2,7 @@ import java.util.Optional; import life.mosu.mosuserver.domain.user.entity.UserJpaEntity; +import life.mosu.mosuserver.domain.user.entity.UserRole; import org.springframework.data.jpa.repository.JpaRepository; public interface UserJpaRepository extends JpaRepository { @@ -14,4 +15,6 @@ public interface UserJpaRepository extends JpaRepository { Optional findByNameAndPhoneNumber(String name, String phoneNumber); Optional findByPhoneNumber(String phoneNumber); + + long countByUserRoleNot(UserRole userRole); } From cc3e5898d6b077d3a635f0b4a0dfe96c6433c9c8 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:44:50 +0900 Subject: [PATCH 04/36] =?UTF-8?q?refactor:=20=EC=8B=A0=EC=B2=AD=20?= =?UTF-8?q?=EC=A4=91=EB=B3=B5=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ApplicationJpaRepository.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/application/repository/ApplicationJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/application/repository/ApplicationJpaRepository.java index 4eff05c4..38613467 100644 --- a/src/main/java/life/mosu/mosuserver/domain/application/repository/ApplicationJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/application/repository/ApplicationJpaRepository.java @@ -29,21 +29,6 @@ AND a.status IN ('PENDING', 'ABORT') """) List findAllByUserId(@Param("userId") Long userId); - @Query( - """ - SELECT CASE WHEN COUNT(a) > 0 THEN true ELSE false END - FROM ApplicationJpaEntity a - JOIN ExamApplicationJpaEntity ea ON a.id = ea.applicationId - JOIN ExamJpaEntity e ON ea.examId = e.id - JOIN PaymentJpaEntity p ON ea.id = p.examApplicationId - WHERE a.userId = :userId - AND p.paymentStatus = 'DONE' - AND e.id IN :examIds - """ - ) - boolean existsByUserIdAndExamIds(@Param("userId") Long userId, - @Param("examIds") List examIds); - @Modifying @Query(value = """ UPDATE application a From 36f5a5880402751fce477640471d9489cfd079ea Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:45:33 +0900 Subject: [PATCH 05/36] =?UTF-8?q?refactor:=20=EC=8B=A0=EC=B2=AD=20?= =?UTF-8?q?=EC=95=BD=EA=B4=80=20=EB=8F=99=EC=9D=98=20=EC=B2=B4=ED=81=AC=20?= =?UTF-8?q?validator=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ApplicationService.java | 4 +- .../vaildator/ApplicationValidator.java | 37 ++++++++++++------- .../application/dto/AgreementRequest.java | 4 ++ 3 files changed, 31 insertions(+), 14 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java b/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java index 84198f13..09362530 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java +++ b/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java @@ -46,6 +46,9 @@ public CreateApplicationResponse apply(Long userId, ApplicationRequest request) List examIds = request.examApplication().stream() .map(ExamApplicationRequest::examId) .toList(); + + validator.agreedToTerms(request); + validator.requestNoDuplicateExams(examIds); return handleApplication( userId, examIds, @@ -78,7 +81,6 @@ private CreateApplicationResponse handleApplication( List examApplications, FileRequest admissionTicket ) { - validator.requestNoDuplicateExams(examIds); List exams = examJpaRepository.findAllById(examIds); validator.examDateNotPassed(exams); validator.examNotFull(exams); diff --git a/src/main/java/life/mosu/mosuserver/application/application/vaildator/ApplicationValidator.java b/src/main/java/life/mosu/mosuserver/application/application/vaildator/ApplicationValidator.java index 4df221c6..e6f450ab 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/vaildator/ApplicationValidator.java +++ b/src/main/java/life/mosu/mosuserver/application/application/vaildator/ApplicationValidator.java @@ -6,29 +6,41 @@ import java.util.Set; import java.util.stream.Collectors; import life.mosu.mosuserver.application.exam.cache.ExamQuotaCacheManager; -import life.mosu.mosuserver.domain.application.repository.ApplicationJpaRepository; import life.mosu.mosuserver.domain.exam.entity.ExamJpaEntity; import life.mosu.mosuserver.domain.exam.entity.ExamJpaRepository; import life.mosu.mosuserver.domain.exam.entity.ExamStatus; +import life.mosu.mosuserver.domain.examapplication.repository.ExamApplicationJpaRepository; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; +import life.mosu.mosuserver.presentation.application.dto.ApplicationRequest; import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +@Slf4j @Component @RequiredArgsConstructor public class ApplicationValidator { private final ExamJpaRepository examJpaRepository; - private final ApplicationJpaRepository applicationJpaRepository; + private final ExamApplicationJpaRepository examApplicationJpaRepository; private final ExamQuotaCacheManager examQuotaCacheManager; + public void agreedToTerms(ApplicationRequest request) { + if (!request.agreement().validateAgreement()) { + throw new CustomRuntimeException(ErrorCode.NOT_AGREED_TO_TERMS); + } + } + public void requestNoDuplicateExams(List examIds) { Set examIdSet = new HashSet<>(examIds); if (examIds.size() != examIdSet.size()) { throw new CustomRuntimeException(ErrorCode.EXAM_DUPLICATED); } + if (examIdSet.isEmpty()) { + throw new CustomRuntimeException(ErrorCode.EXAM_NOT_APPLIED); + } } public void examIdsAndLunchSelection(List requests) { @@ -39,12 +51,9 @@ public void examIdsAndLunchSelection(List requests) { List requestedExamIds = requests.stream() .map(ExamApplicationRequest::examId) .toList(); + Set examIdSet = new HashSet<>(requestedExamIds); - List existingExams = examJpaRepository.findAllById(requestedExamIds); - - if (existingExams.size() != requestedExamIds.size()) { - throw new CustomRuntimeException(ErrorCode.EXAM_NOT_FOUND); - } + List existingExams = examJpaRepository.findAllById(examIdSet); lunchSelection(requests, existingExams); } @@ -56,16 +65,18 @@ private void lunchSelection(List requests, .map(ExamJpaEntity::getId) .collect(Collectors.toSet()); - boolean hasInvalidLunchRequest = requests.stream() - .anyMatch(req -> examsWithoutLunch.contains(req.examId()) && req.isLunchChecked()); + requests.stream() + .filter(req -> req.isLunchChecked() && examsWithoutLunch.contains(req.examId())) + .findFirst() + .ifPresent(req -> { + throw new CustomRuntimeException(ErrorCode.LUNCH_SELECTION_INVALID); + }); - if (hasInvalidLunchRequest) { - throw new CustomRuntimeException(ErrorCode.LUNCH_SELECTION_INVALID); - } } public void noDuplicateApplication(Long userId, List examIds) { - boolean alreadyApplied = applicationJpaRepository.existsByUserIdAndExamIds(userId, examIds); + boolean alreadyApplied = examApplicationJpaRepository.existsByUserIdAndExamIds(userId, + examIds); if (alreadyApplied) { throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_DUPLICATED); } diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/AgreementRequest.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/AgreementRequest.java index 37f1ed2a..ef15a8f1 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/application/dto/AgreementRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/application/dto/AgreementRequest.java @@ -13,4 +13,8 @@ public record AgreementRequest( ) { + public boolean validateAgreement() { + return agreedToNotices && agreedToRefundPolicy; + } + } From c9f0463b430c4896fde8db68f6121b77aeddcea3 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:45:58 +0900 Subject: [PATCH 06/36] =?UTF-8?q?refactor:=20=EC=8B=A0=EC=B2=AD=20?= =?UTF-8?q?=EC=9D=91=EC=8B=9C=EA=B3=BC=EB=AA=A9=20notNull=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../presentation/application/dto/ApplicationRequest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java index 5a168cfc..f8457695 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java @@ -30,8 +30,9 @@ public record ApplicationRequest( @NotNull AgreementRequest agreement, - @Schema(description = "응시 과목 목록 (예: PHYSICS_1)", example = "[\"PHYSICS_1\", \"ETHICS_AND_IDEOLOGY\"]") - Set subjects + @Schema(description = "응시 과목 목록") + @NotNull + List subjects ) { public ApplicationJpaEntity toApplicationJpaEntity(Long userId) { From 77ec41d3ccd7cb4135b340d56672725c6e2a6073 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:47:31 +0900 Subject: [PATCH 07/36] =?UTF-8?q?refactor:=20event=20null=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=EB=B0=8F=20duration=20startDate=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/event/EventService.java | 4 +--- .../presentation/event/dto/DurationRequest.java | 2 +- .../presentation/event/dto/EventRequest.java | 15 +++++++-------- .../presentation/event/dto/EventResponse.java | 5 +++-- 4 files changed, 12 insertions(+), 14 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/event/EventService.java b/src/main/java/life/mosu/mosuserver/application/event/EventService.java index 0fb020ed..aba74b2b 100644 --- a/src/main/java/life/mosu/mosuserver/application/event/EventService.java +++ b/src/main/java/life/mosu/mosuserver/application/event/EventService.java @@ -22,13 +22,11 @@ public class EventService { private final EventJpaRepository eventJpaRepository; private final EventQueryRepository eventQueryRepository; - // private final EventAttachmentService attachmentService; private final S3Service s3Service; @Transactional public void createEvent(EventRequest request) { - EventJpaEntity eventEntity = eventJpaRepository.save(request.toEntity()); -// attachmentService.createAttachment(request.optionalAttachment(), eventEntity); + eventJpaRepository.save(request.toEntity()); } @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) diff --git a/src/main/java/life/mosu/mosuserver/presentation/event/dto/DurationRequest.java b/src/main/java/life/mosu/mosuserver/presentation/event/dto/DurationRequest.java index 91120477..cec147b8 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/event/dto/DurationRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/event/dto/DurationRequest.java @@ -16,6 +16,6 @@ public record DurationRequest( ) { public DurationJpaVO toDurationJpaVO() { - return new DurationJpaVO(startDate, endDate); + return new DurationJpaVO(LocalDate.now(), endDate); } } diff --git a/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventRequest.java b/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventRequest.java index 2c3af3f9..ae0241e7 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventRequest.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import java.util.List; import life.mosu.mosuserver.domain.event.entity.EventJpaEntity; import life.mosu.mosuserver.domain.file.Visibility; import life.mosu.mosuserver.presentation.common.FileRequest; @@ -25,18 +24,18 @@ public record EventRequest( ) { - public List optionalAttachment() { - FileRequest parsedAttachment = this.attachment; - return parsedAttachment == null ? List.of() : List.of(parsedAttachment); - } +// public List optionalAttachment() { +// FileRequest parsedAttachment = this.attachment; +// return parsedAttachment == null ? List.of() : List.of(parsedAttachment); +// } public EventJpaEntity toEntity() { return EventJpaEntity.builder() .title(title) .eventLink(eventLink) - .duration(duration.toDurationJpaVO()) - .fileName(attachment().fileName()) - .s3Key(attachment().s3Key()) + .duration(duration != null ? duration.toDurationJpaVO() : null) + .fileName(attachment != null ? attachment().fileName() : null) + .s3Key(attachment != null ? attachment().s3Key() : null) .visibility(Visibility.PUBLIC) .build(); } diff --git a/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventResponse.java b/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventResponse.java index d346a04f..ed33a7f9 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventResponse.java @@ -34,8 +34,9 @@ public static EventResponse of(EventJpaEntity event, String eventUrl) { return new EventResponse( event.getId(), event.getTitle(), - event.getDuration().getEndDate(), - event.getEventLink(), + event.getDuration() != null ? event.getDuration().getEndDate() + : null, + event.getEventLink() != null ? event.getEventLink() : null, attachment ); } From 59a4aeeb715cc93105f7486d827350e49f25f298 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:48:43 +0900 Subject: [PATCH 08/36] =?UTF-8?q?refactor:=20recommendation=20=EB=B3=80?= =?UTF-8?q?=EC=88=98=20=EB=B3=80=EA=B2=BD=20=EB=B0=8F=20notBlank=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../RecommendationDetailsProjection.java | 4 ++-- .../admin/dto/RecommendationExcelDto.java | 16 ++++++++-------- .../dto/RecommendationRequest.java | 2 +- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/admin/projection/RecommendationDetailsProjection.java b/src/main/java/life/mosu/mosuserver/domain/admin/projection/RecommendationDetailsProjection.java index 741804cc..c6a8bc59 100644 --- a/src/main/java/life/mosu/mosuserver/domain/admin/projection/RecommendationDetailsProjection.java +++ b/src/main/java/life/mosu/mosuserver/domain/admin/projection/RecommendationDetailsProjection.java @@ -10,8 +10,8 @@ public record RecommendationDetailsProjection( LocalDate birth, String recommendeeName, String recommendeePhoneNumber, - String recommendeeBank, - String recommendeeAccountNumber + String recommenderBank, + String recommenderAccountNumber ) { } diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/RecommendationExcelDto.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/RecommendationExcelDto.java index 9cd9e401..3fe78aa7 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/RecommendationExcelDto.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/RecommendationExcelDto.java @@ -31,13 +31,13 @@ public record RecommendationExcelDto( @ExcelColumn(headerName = "피추천자 전화번호") String recommendeePhoneNumber, - @Schema(description = "피추천자 은행명", example = "신한은행") - @ExcelColumn(headerName = "피추천자 은행명") - String recommendeeBank, + @Schema(description = "추천자 은행명", example = "신한은행") + @ExcelColumn(headerName = "추천자 은행명") + String recommenderBank, - @Schema(description = "피추천자 계좌번호", example = "110123456789") - @ExcelColumn(headerName = "피추천자 계좌번호") - String recommendeeAccountNumber + @Schema(description = "추천자 계좌번호", example = "110123456789") + @ExcelColumn(headerName = "추천자 계좌번호") + String recommenderAccountNumber ) { public static RecommendationExcelDto of(RecommendationDetailsProjection projection) { @@ -48,8 +48,8 @@ public static RecommendationExcelDto of(RecommendationDetailsProjection projecti projection.birth(), projection.recommendeeName(), projection.recommendeePhoneNumber(), - projection.recommendeeBank(), - projection.recommendeeAccountNumber() + projection.recommenderBank(), + projection.recommenderAccountNumber() ); } diff --git a/src/main/java/life/mosu/mosuserver/presentation/recommendation/dto/RecommendationRequest.java b/src/main/java/life/mosu/mosuserver/presentation/recommendation/dto/RecommendationRequest.java index aceb9692..df41e56f 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/recommendation/dto/RecommendationRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/recommendation/dto/RecommendationRequest.java @@ -6,7 +6,7 @@ public record RecommendationRequest( @NotBlank String name, - @PhoneNumberPattern String phoneNumber, + @NotBlank @PhoneNumberPattern String phoneNumber, @NotBlank String bank, @NotBlank String accountNumber ) { From da821bda0a4263ca52f88440fbc3051d3eeb1295 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:49:18 +0900 Subject: [PATCH 09/36] =?UTF-8?q?refactor:=20profile=20null=20=EC=B2=B4?= =?UTF-8?q?=ED=81=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mosu/mosuserver/domain/profile/entity/ProfileJpaEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/profile/entity/ProfileJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/profile/entity/ProfileJpaEntity.java index a75d443a..799c5fe6 100644 --- a/src/main/java/life/mosu/mosuserver/domain/profile/entity/ProfileJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/profile/entity/ProfileJpaEntity.java @@ -89,7 +89,7 @@ public ProfileJpaEntity( public void edit(final EditProfileRequest request) { this.email = request.email(); this.education = request.education(); - this.schoolInfo = request.schoolInfo().toEntity(); + this.schoolInfo = request.schoolInfo() != null ? request.schoolInfo().toEntity() : null; this.grade = request.grade(); } From eb090a8d07d0d5625db66dd9950d1a7a4f2f5120 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:51:49 +0900 Subject: [PATCH 10/36] =?UTF-8?q?refactor:=20=EC=8B=A0=EC=B2=AD=20multi-in?= =?UTF-8?q?sert=20=EC=8B=9C=20deleted=20=EC=98=B5=EC=85=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80,=20=EA=B3=BC=EB=AA=A9=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EC=8B=9C=20p.deleted=20=EC=98=B5=EC=85=98=20=EA=B2=80=EC=82=AC?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/ExamSubjectJpaRepository.java | 1 + .../persistence/jpa/ExamApplicationBulkRepository.java | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamSubjectJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamSubjectJpaRepository.java index 3aad5dac..e595a9e6 100644 --- a/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamSubjectJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamSubjectJpaRepository.java @@ -29,6 +29,7 @@ public interface ExamSubjectJpaRepository extends JpaRepository saveAllExamApplicationsWithSubjects( ps.setLong(5, e.getExamId()); ps.setBoolean(6, e.getIsLunchChecked()); ps.setString(7, e.getExamNumber()); -// ps.setBoolean(8, e.getDeleted()); + ps.setBoolean(8, false); ps.addBatch(); log.info( From f883a22733a318893faa26f785a02c3f2b701dfb Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:52:17 +0900 Subject: [PATCH 11/36] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../life/mosu/mosuserver/global/exception/ErrorCode.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java b/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java index e9918494..2daedbea 100644 --- a/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java +++ b/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java @@ -44,11 +44,13 @@ public enum ErrorCode { USER_INFO_INVALID(HttpStatus.BAD_REQUEST, "유효하지 않은 사용자 정보입니다."), USER_NOT_ACCESS_FORBIDDEN(HttpStatus.BAD_REQUEST, "접근 권한이 없는 사용자입니다"), USER_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "사용자 저장에 실패했습니다."), + // 신청 관련 에러 WRONG_SUBJECT_TYPE(HttpStatus.BAD_REQUEST, "잘못된 과목명 입니다."), WRONG_LUNCH_TYPE(HttpStatus.BAD_REQUEST, "잘못된 도시락명 입니다."), WRONG_AREA_TYPE(HttpStatus.BAD_REQUEST, "잘못된 지역명 입니다."), WRONG_SUBJECT_COUNT(HttpStatus.BAD_REQUEST, "응시과목은 반드시 다른 과목 2개를 신청해야 합니다."), + NOT_AGREED_TO_TERMS(HttpStatus.BAD_REQUEST, "신청 시 모든 약관에 동의해야 합니다."), // 수험표 관련 에러 EXAM_TICKET_NOT_OPEN(HttpStatus.BAD_REQUEST, "수험표 조회 기간이 아닙니다."), @@ -124,7 +126,8 @@ public enum ErrorCode { // 시험 관련 에러 EXAM_NOT_FOUND(HttpStatus.NOT_FOUND, "시험 정보를 찾을 수 없습니다."), EXAM_DATE_PASSED(HttpStatus.BAD_REQUEST, "이미 지난 시험입니다."), - + EXAM_NOT_APPLIED(HttpStatus.BAD_REQUEST, "1개 이상의 시험을 신청해야 합니다."), + EXAM_DATE_AFTER_DEADLINE(HttpStatus.BAD_REQUEST, "시험일은 접수 마감일 이후여야 합니다."), //lunch 관련 LUNCH_NOT_FOUND(HttpStatus.NOT_FOUND, "점심 정보를 찾을 수 없습니다."), LUNCH_PRICE_UPDATE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "가격 수정에 실패하였습니다."), @@ -162,8 +165,8 @@ public enum ErrorCode { INVALID_VIRTUAL_ACCOUNT_DEPOSIT_EVENT(HttpStatus.BAD_REQUEST, "유효하지 않은 가상 계좌 입금 이벤트입니다."), VIRTUAL_ACCOUNT_CREATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "가상 계좌 생성에 실패했습니다."), - VIRTUAL_ACCOUNT_LOG_NOT_FOUND(HttpStatus.NOT_FOUND, "가상 계좌 로그를 찾을 수 없습니다."), - ; + VIRTUAL_ACCOUNT_LOG_NOT_FOUND(HttpStatus.NOT_FOUND, "가상 계좌 로그를 찾을 수 없습니다."); + private final HttpStatus status; private final String message; From 37fc04923e7599a1f17d9ab68ebbeb5dc6d17687 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:54:51 +0900 Subject: [PATCH 12/36] =?UTF-8?q?refactor:=20request=20valid=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=B0=8F=20lunch=20=EB=93=B1=EB=A1=9D=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mosuserver/application/exam/ExamService.java | 8 ++++++++ .../domain/exam/entity/ExamJpaEntity.java | 2 +- .../presentation/exam/ExamController.java | 8 ++++---- .../presentation/exam/dto/ExamRequest.java | 16 +++++++++------- 4 files changed, 22 insertions(+), 12 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java b/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java index 8d47026e..03714ab2 100644 --- a/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java +++ b/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java @@ -23,7 +23,9 @@ public class ExamService { @Transactional public void register(ExamRequest request) { + validateExamDate(request); ExamJpaEntity exam = request.toEntity(); + examJpaRepository.save(exam); } @@ -71,4 +73,10 @@ public void close(Long examId) { .orElseThrow(() -> new CustomRuntimeException(ErrorCode.EXAM_NOT_FOUND)); exam.close(); } + + private void validateExamDate(ExamRequest request) { + if (!request.deadlineTime().isBefore(request.examDate().atStartOfDay())) { + throw new CustomRuntimeException(ErrorCode.EXAM_DATE_AFTER_DEADLINE); + } + } } diff --git a/src/main/java/life/mosu/mosuserver/domain/exam/entity/ExamJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/exam/entity/ExamJpaEntity.java index 24c157dc..591e999c 100644 --- a/src/main/java/life/mosu/mosuserver/domain/exam/entity/ExamJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/exam/entity/ExamJpaEntity.java @@ -80,7 +80,7 @@ public ExamJpaEntity( } public boolean hasNotLunch() { - return lunchName == null; + return this.lunchName == null || this.lunchPrice == null; } public void close() { diff --git a/src/main/java/life/mosu/mosuserver/presentation/exam/ExamController.java b/src/main/java/life/mosu/mosuserver/presentation/exam/ExamController.java index 5ff384c2..e0c14fe5 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/exam/ExamController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/exam/ExamController.java @@ -1,5 +1,6 @@ package life.mosu.mosuserver.presentation.exam; +import jakarta.validation.Valid; import java.util.List; import life.mosu.mosuserver.application.exam.ExamService; import life.mosu.mosuserver.global.util.ApiResponseWrapper; @@ -29,7 +30,7 @@ public class ExamController { @PostMapping @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") public ResponseEntity> register( - @RequestBody ExamRequest request + @Valid @RequestBody ExamRequest request ) { examService.register(request); return ResponseEntity.ok( @@ -45,7 +46,7 @@ public ResponseEntity>> getExams() { @GetMapping public ResponseEntity>> getByArea( - @RequestParam String areaName + @Valid @RequestParam String areaName ) { List response = examService.getByArea(areaName); return ResponseEntity.ok( @@ -65,10 +66,9 @@ public ResponseEntity> delete(@PathVariable Long examId examService.delete(examId); return ResponseEntity.ok( ApiResponseWrapper.success(HttpStatus.OK, "시험장 삭제 성공")); - } - @PatchMapping("/{examId}") + @PatchMapping("/{examId}/close") @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") public ResponseEntity> close(@PathVariable Long examId) { examService.close(examId); diff --git a/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamRequest.java b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamRequest.java index 2c793688..e5b0616f 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamRequest.java @@ -1,5 +1,7 @@ package life.mosu.mosuserver.presentation.exam.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import java.time.LocalDateTime; import life.mosu.mosuserver.domain.exam.entity.AddressJpaVO; @@ -7,11 +9,11 @@ import life.mosu.mosuserver.domain.exam.entity.ExamJpaEntity; public record ExamRequest( - String schoolName, - String areaName, - AddressRequest address, - LocalDate examDate, - Integer capacity, + @NotBlank String schoolName, + @NotBlank String areaName, + @NotNull AddressRequest address, + @NotNull LocalDate examDate, + @NotNull Integer capacity, LocalDateTime deadlineTime, LunchRequest lunch ) { @@ -25,8 +27,8 @@ public ExamJpaEntity toEntity() { .examDate(examDate) .capacity(capacity) .deadlineTime(deadlineTime) - .lunchName(lunch.name()) - .lunchPrice(lunch.price()) + .lunchName(lunch != null ? lunch.name() : null) + .lunchPrice(lunch != null ? lunch.price() : null) .build(); } From 1562bbca277956c7e69e4df0211752f90bff0a6f Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:55:40 +0900 Subject: [PATCH 13/36] =?UTF-8?q?remove:=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/processor/GetApplicationsStepProcessor.java | 3 --- .../mosu/mosuserver/presentation/admin/dto/BannerRequest.java | 1 - .../presentation/profile/dto/EditProfileRequest.java | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/application/processor/GetApplicationsStepProcessor.java b/src/main/java/life/mosu/mosuserver/application/application/processor/GetApplicationsStepProcessor.java index e664b43b..ee282f6b 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/processor/GetApplicationsStepProcessor.java +++ b/src/main/java/life/mosu/mosuserver/application/application/processor/GetApplicationsStepProcessor.java @@ -13,10 +13,8 @@ import life.mosu.mosuserver.presentation.application.dto.ApplicationResponse; import life.mosu.mosuserver.presentation.examapplication.dto.ExamApplicationWithStatus; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -@Slf4j @Component @RequiredArgsConstructor public class GetApplicationsStepProcessor implements @@ -33,7 +31,6 @@ public class GetApplicationsStepProcessor implements public List process(Long userId) { List applications = applicationJpaRepository.findAllByUserId(userId); - log.info("applications info: {}", applications.size()); if (applications.isEmpty()) { return List.of(); } diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/BannerRequest.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/BannerRequest.java index 1abb7a7d..ddfece7b 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/BannerRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/BannerRequest.java @@ -12,7 +12,6 @@ public record BannerRequest( FileRequest file ) { - //배너 등록할 때 fileRequest 가 없을 때는 어떤 식으로 하면 되는지 public BannerJpaEntity toEntity() { return BannerJpaEntity.builder() .fileName(file != null ? file.fileName() : null) diff --git a/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java b/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java index 40f439b6..003733bf 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java @@ -9,7 +9,7 @@ public record EditProfileRequest( @Schema(description = "이메일 주소", example = "hong@example.com") String email, - @Schema(description = "학력 정보 (Enum: ELEMENTARY, MIDDLE, HIGH_SCHOOL, UNIVERSITY 등)", example = "HIGH_SCHOOL") + @Schema(description = "학력 정보") Education education, @Schema(description = "학교 정보", implementation = SchoolInfoRequest.class) From af910d3b437d279bedde41dd257050df4f2422a0 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 06:56:42 +0900 Subject: [PATCH 14/36] =?UTF-8?q?refactor:=20examTicketImgUrl=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=95=A8=EC=88=98=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ExamApplicationService.java | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java b/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java index 8fd8d1ef..ffc471fa 100644 --- a/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java +++ b/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java @@ -41,7 +41,6 @@ public class ExamApplicationService { private final S3Service s3Service; private final FixedQuantityDiscountCalculator calculator; - @Transactional public List register(RegisterExamApplicationEvent event) { List examApplicationEntities = event.toEntity(); @@ -57,7 +56,6 @@ public void updateSubjects(Long userId, Long examApplicationId, examSubjectJpaRepository.deleteExamSubjectsWithDonePayment(examApplicationId); List examSubjects = request.toEntityList(examApplicationId); examSubjectJpaRepository.saveAll(examSubjects); - } @Transactional(propagation = Propagation.REQUIRES_NEW) @@ -97,27 +95,17 @@ public ExamTicketResponse getExamTicket(Long userId, Long examApplicationId) { .map(Subject::getSubjectName) .toList(); - String s3Key = examTicketInfo.s3Key(); - String examTicketImgUrl = null; - - if (s3Key != null) { - examTicketImgUrl = s3Service.getPreSignedUrl(s3Key); - } + String examTicketImgUrl = getExamTicketImgUrl(examTicketInfo); return ExamTicketResponse.of(examTicketImgUrl, examTicketInfo.userName(), examTicketInfo.birth(), examTicketInfo.examNumber(), subjects, examTicketInfo.schoolName()); - } public ExamApplicationInfoResponse getApplication(Long userId, Long examApplicationId, Long applicationId) { - validateUser(userId, examApplicationId); - //상세 조회는 done 만 가능 -// Integer examApplicationCount = paymentJpaRepository.countByExamApplicationId( -// examApplicationId); List examApplicationEntities = examApplicationJpaRepository.findByApplicationId( applicationId); int lunchCount = (int) examApplicationEntities.stream() @@ -135,6 +123,7 @@ public ExamApplicationInfoResponse getApplication(Long userId, Long examApplicat Set subjects = examSubjects.stream() .map(ExamSubjectJpaEntity::getSubjectName) .collect(Collectors.toSet()); + //totalAmount 는 Lunch 가격이 포함되었을 수도 있음 //totalAmount - Lunch 가격으로 getAppliedDiscountAmount() 메소드에 넣어야함. @@ -187,4 +176,15 @@ private void validateExamTicketOpenDate(LocalDate examDate, String examNumber) { throw new CustomRuntimeException(ErrorCode.EXAM_TICKET_NOT_OPEN); } } + + private String getExamTicketImgUrl(ExamTicketInfoProjection examTicketInfo) { + String s3Key = examTicketInfo.s3Key(); + String examTicketImgUrl = null; + + if (s3Key != null) { + examTicketImgUrl = s3Service.getPreSignedUrl(s3Key); + } + return examTicketImgUrl; + } + } From ebfa48dd8b810ba6aba592bbbf31a7c818fc8c15 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 21:20:39 +0900 Subject: [PATCH 15/36] =?UTF-8?q?refactor:=20=EA=B4=80=EB=A6=AC=EC=9E=90?= =?UTF-8?q?=20DashBoardResponse=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/admin/AdminDashboardService.java | 12 ++++-------- .../refund/repository/RefundJpaRepository.java | 3 --- .../presentation/admin/dto/DashBoardResponse.java | 3 --- 3 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java b/src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java index 97426a29..90a6fa96 100644 --- a/src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java +++ b/src/main/java/life/mosu/mosuserver/application/admin/AdminDashboardService.java @@ -1,8 +1,7 @@ package life.mosu.mosuserver.application.admin; import life.mosu.mosuserver.domain.examapplication.repository.ExamApplicationJpaRepository; -import life.mosu.mosuserver.domain.refund.entity.RefundStatus; -import life.mosu.mosuserver.domain.refund.repository.RefundJpaRepository; +import life.mosu.mosuserver.domain.refund.repository.RefundFailureLogJpaRepository; import life.mosu.mosuserver.domain.user.entity.UserRole; import life.mosu.mosuserver.domain.user.repository.UserJpaRepository; import life.mosu.mosuserver.presentation.admin.dto.DashBoardResponse; @@ -15,22 +14,19 @@ public class AdminDashboardService { private final ExamApplicationJpaRepository examApplicationJpaRepository; private final UserJpaRepository userJpaRepository; - private final RefundJpaRepository refundJpaRepository; + private final RefundFailureLogJpaRepository refundFailureLogJpaRepository; // 대시보드 정보 조회 public DashBoardResponse getAll() { + Long applicationCounts = examApplicationJpaRepository.countAll(); - Long refundDoneCounts = refundJpaRepository.countByRefundStatus(RefundStatus.DONE); - Long refundAbortedCounts = refundJpaRepository.countByRefundStatus(RefundStatus.ABORTED); + Long refundAbortedCounts = refundFailureLogJpaRepository.count(); Long userCounts = userJpaRepository.countByUserRoleNot(UserRole.ROLE_ADMIN); return DashBoardResponse.of( applicationCounts, - refundDoneCounts, refundAbortedCounts, userCounts ); } - - } diff --git a/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java index d9489b8e..a406f6c4 100644 --- a/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java @@ -4,7 +4,6 @@ import java.util.List; import java.util.Optional; import life.mosu.mosuserver.domain.refund.entity.RefundJpaEntity; -import life.mosu.mosuserver.domain.refund.entity.RefundStatus; import life.mosu.mosuserver.domain.refund.projection.RefundNotifyProjection; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -55,6 +54,4 @@ WHERE r.refundStatus in ('ABORTED') """) List findFailedRefunds(@Param("time") LocalDateTime time); - long countByRefundStatus(RefundStatus refundStatus); - } diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java index fb57f0cf..1ef7589a 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java @@ -2,20 +2,17 @@ public record DashBoardResponse( Long applicationCounts, - Long refundDoneCounts, Long refundAbortedCounts, Long userCounts ) { public static DashBoardResponse of( Long applicationCounts, - Long refundDoneCounts, Long refundAbortedCounts, Long userCounts ) { return new DashBoardResponse( applicationCounts, - refundDoneCounts, refundAbortedCounts, userCounts ); From f279815ecbc0a09f5255196237d2b0b077477b8b Mon Sep 17 00:00:00 2001 From: chominju02 Date: Fri, 8 Aug 2025 21:20:53 +0900 Subject: [PATCH 16/36] =?UTF-8?q?refactor:=20whiteList=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../mosuserver/global/filter/Whitelist.java | 70 +++++++++++-------- 1 file changed, 42 insertions(+), 28 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/global/filter/Whitelist.java b/src/main/java/life/mosu/mosuserver/global/filter/Whitelist.java index 5f70c3b5..9800722d 100644 --- a/src/main/java/life/mosu/mosuserver/global/filter/Whitelist.java +++ b/src/main/java/life/mosu/mosuserver/global/filter/Whitelist.java @@ -13,47 +13,50 @@ public enum Whitelist { // ALL METHOD 허용 - KMC("/api/v1/kmc", WhitelistMethod.ALL), - API_DOCS("/api/v1/api-docs", WhitelistMethod.ALL), - ACTUATOR("/api/v1/actuator", WhitelistMethod.ALL), - LOGIN("/api/v1/auth/login", WhitelistMethod.ALL), - SWAGGER("/api/v1/swagger", WhitelistMethod.ALL), - SWAGGER_UI("/api/v1/swagger-ui", WhitelistMethod.ALL), - VIRTUAL_ACCOUNT("/api/v1/virtual-account", WhitelistMethod.ALL), - ADMISSION_TICKET("/api/v1/admission-ticket", WhitelistMethod.ALL), - FILE("/api/v1/s3", WhitelistMethod.ALL), + KMC("/api/v1/kmc", WhitelistMethod.ALL, MatchType.START_WITH), + API_DOCS("/api/v1/api-docs", WhitelistMethod.ALL, MatchType.START_WITH), + ACTUATOR("/api/v1/actuator", WhitelistMethod.ALL, MatchType.START_WITH), + LOGIN("/api/v1/auth/login", WhitelistMethod.ALL, MatchType.START_WITH), + SWAGGER("/api/v1/swagger", WhitelistMethod.ALL, MatchType.START_WITH), + SWAGGER_UI("/api/v1/swagger-ui", WhitelistMethod.ALL, MatchType.START_WITH), + VIRTUAL_ACCOUNT("/api/v1/virtual-account", WhitelistMethod.ALL, MatchType.START_WITH), + ADMISSION_TICKET("/api/v1/admission-ticket", WhitelistMethod.ALL, MatchType.START_WITH), + FILE("/api/v1/s3", WhitelistMethod.ALL, MatchType.START_WITH), // 정적 리소스 - CSS("/api/v1/css", WhitelistMethod.GET), - JS("/api/v1/js", WhitelistMethod.GET), - FONTS("/api/v1/fonts", WhitelistMethod.GET), - IMAGES("/api/v1/images", WhitelistMethod.GET), + CSS("/api/v1/css", WhitelistMethod.GET, MatchType.START_WITH), + JS("/api/v1/js", WhitelistMethod.GET, MatchType.START_WITH), + FONTS("/api/v1/fonts", WhitelistMethod.GET, MatchType.START_WITH), + IMAGES("/api/v1/images", WhitelistMethod.GET, MatchType.START_WITH), // OAuth 관련 - OAUTH2("/api/v1/oauth2", WhitelistMethod.ALL), - OAUTH("/api/v1/oauth", WhitelistMethod.ALL), + OAUTH2("/api/v1/oauth2", WhitelistMethod.ALL, MatchType.START_WITH), + OAUTH("/api/v1/oauth", WhitelistMethod.ALL, MatchType.START_WITH), // 삭제 예정 - MASTER("/api/v1/master", WhitelistMethod.ALL), + MASTER("/api/v1/master", WhitelistMethod.ALL, MatchType.START_WITH), // 조회만 가능한 PATH - EVENT("/api/v1/event", WhitelistMethod.GET), - FAQ("/api/v1/faq", WhitelistMethod.GET), - NOTICE("/api/v1/notice", WhitelistMethod.GET), - USER_ID_CHECK("/api/v1/user/check-id", WhitelistMethod.GET), - CUSTOMER_KEY_CHECK("/api/v1/user/customer-key", WhitelistMethod.GET), - EXAM("/api/v1/exam", WhitelistMethod.GET), - EXAM_AREAS("/api/v1/exam/areas", WhitelistMethod.GET), - EXAM_ALL("/api/v1/exam/all", WhitelistMethod.GET), + EVENT("/api/v1/event/list", WhitelistMethod.GET, MatchType.START_WITH), + FAQ("/api/v1/faq", WhitelistMethod.GET, MatchType.START_WITH), + NOTICE("/api/v1/notice", WhitelistMethod.GET, MatchType.START_WITH), + USER_ID_CHECK("/api/v1/user/check-id", WhitelistMethod.GET, MatchType.START_WITH), + CUSTOMER_KEY_CHECK("/api/v1/user/customer-key", WhitelistMethod.GET, MatchType.START_WITH), + EXAM("/api/v1/exam", WhitelistMethod.GET, MatchType.EXACT), + EXAM_AREAS("/api/v1/exam/areas", WhitelistMethod.GET, MatchType.EXACT), + EXAM_ALL("/api/v1/exam/all", WhitelistMethod.GET, MatchType.EXACT), //USER find-id - USER_FIND_ID("/api/v1/user/me/find-id", WhitelistMethod.POST), + USER_FIND_ID("/api/v1/user/me/find-id", WhitelistMethod.POST, MatchType.START_WITH), + //USER find-password - USER_FIND_PASSWORD("/api/v1/user/me/find-password", WhitelistMethod.POST), + USER_FIND_PASSWORD("/api/v1/user/me/find-password", WhitelistMethod.POST, MatchType.START_WITH), + + APPLICATION_GUEST("/api/v1/applications/guest", WhitelistMethod.ALL, MatchType.START_WITH); - APPLICATION_GUEST("/api/v1/applications/guest", WhitelistMethod.ALL); private final String path; private final WhitelistMethod method; + private final MatchType matchType; public static boolean isWhitelisted(final HttpServletRequest request) { return findMatch(request).isPresent(); @@ -64,9 +67,20 @@ private static Optional findMatch(final HttpServletRequest request) { String requestMethod = request.getMethod(); return Arrays.stream(values()) - .filter(url -> requestUri.startsWith(url.path)) + .filter(url -> { + if (url.matchType == MatchType.EXACT) { + return requestUri.equals(url.path); + } else { + return requestUri.startsWith(url.path); + } + }) .filter(url -> url.method == WhitelistMethod.ALL || url.method.name() .equalsIgnoreCase(requestMethod)) .findFirst(); } + + public enum MatchType { + EXACT, + START_WITH + } } \ No newline at end of file From 3a9d195e3b14d4eb260abef5687584413ec5c3a2 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:57:09 +0900 Subject: [PATCH 17/36] feat: add agreement validation method to AgreementRequest --- .../presentation/application/dto/AgreementRequest.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/AgreementRequest.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/AgreementRequest.java index 37f1ed2a..ef15a8f1 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/application/dto/AgreementRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/application/dto/AgreementRequest.java @@ -13,4 +13,8 @@ public record AgreementRequest( ) { + public boolean validateAgreement() { + return agreedToNotices && agreedToRefundPolicy; + } + } From 5951dc17e17ab9a5a0d952dbaa099f3d198bd267 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:57:13 +0900 Subject: [PATCH 18/36] feat: remove existsByUserIdAndExamIds query from ApplicationJpaRepository --- .../repository/ApplicationJpaRepository.java | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/application/repository/ApplicationJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/application/repository/ApplicationJpaRepository.java index 4eff05c4..38613467 100644 --- a/src/main/java/life/mosu/mosuserver/domain/application/repository/ApplicationJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/application/repository/ApplicationJpaRepository.java @@ -29,21 +29,6 @@ AND a.status IN ('PENDING', 'ABORT') """) List findAllByUserId(@Param("userId") Long userId); - @Query( - """ - SELECT CASE WHEN COUNT(a) > 0 THEN true ELSE false END - FROM ApplicationJpaEntity a - JOIN ExamApplicationJpaEntity ea ON a.id = ea.applicationId - JOIN ExamJpaEntity e ON ea.examId = e.id - JOIN PaymentJpaEntity p ON ea.id = p.examApplicationId - WHERE a.userId = :userId - AND p.paymentStatus = 'DONE' - AND e.id IN :examIds - """ - ) - boolean existsByUserIdAndExamIds(@Param("userId") Long userId, - @Param("examIds") List examIds); - @Modifying @Query(value = """ UPDATE application a From f85c830bd0a39679c6e1d6022aad7b22a63d9a1d Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:57:20 +0900 Subject: [PATCH 19/36] feat: update subjects field to use List instead of Set in ApplicationRequest --- .../presentation/application/dto/ApplicationRequest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java index 5a168cfc..f8457695 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationRequest.java @@ -30,8 +30,9 @@ public record ApplicationRequest( @NotNull AgreementRequest agreement, - @Schema(description = "응시 과목 목록 (예: PHYSICS_1)", example = "[\"PHYSICS_1\", \"ETHICS_AND_IDEOLOGY\"]") - Set subjects + @Schema(description = "응시 과목 목록") + @NotNull + List subjects ) { public ApplicationJpaEntity toApplicationJpaEntity(Long userId) { From a46f2acf5fab4153c30b58c2ab505f2ae259912f Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:57:32 +0900 Subject: [PATCH 20/36] feat: enhance application validation by adding agreement check and improving duplicate exam handling --- .../application/ApplicationService.java | 4 +- .../vaildator/ApplicationValidator.java | 37 ++++++++++++------- 2 files changed, 27 insertions(+), 14 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java b/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java index 84198f13..09362530 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java +++ b/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java @@ -46,6 +46,9 @@ public CreateApplicationResponse apply(Long userId, ApplicationRequest request) List examIds = request.examApplication().stream() .map(ExamApplicationRequest::examId) .toList(); + + validator.agreedToTerms(request); + validator.requestNoDuplicateExams(examIds); return handleApplication( userId, examIds, @@ -78,7 +81,6 @@ private CreateApplicationResponse handleApplication( List examApplications, FileRequest admissionTicket ) { - validator.requestNoDuplicateExams(examIds); List exams = examJpaRepository.findAllById(examIds); validator.examDateNotPassed(exams); validator.examNotFull(exams); diff --git a/src/main/java/life/mosu/mosuserver/application/application/vaildator/ApplicationValidator.java b/src/main/java/life/mosu/mosuserver/application/application/vaildator/ApplicationValidator.java index 4df221c6..e6f450ab 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/vaildator/ApplicationValidator.java +++ b/src/main/java/life/mosu/mosuserver/application/application/vaildator/ApplicationValidator.java @@ -6,29 +6,41 @@ import java.util.Set; import java.util.stream.Collectors; import life.mosu.mosuserver.application.exam.cache.ExamQuotaCacheManager; -import life.mosu.mosuserver.domain.application.repository.ApplicationJpaRepository; import life.mosu.mosuserver.domain.exam.entity.ExamJpaEntity; import life.mosu.mosuserver.domain.exam.entity.ExamJpaRepository; import life.mosu.mosuserver.domain.exam.entity.ExamStatus; +import life.mosu.mosuserver.domain.examapplication.repository.ExamApplicationJpaRepository; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; +import life.mosu.mosuserver.presentation.application.dto.ApplicationRequest; import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +@Slf4j @Component @RequiredArgsConstructor public class ApplicationValidator { private final ExamJpaRepository examJpaRepository; - private final ApplicationJpaRepository applicationJpaRepository; + private final ExamApplicationJpaRepository examApplicationJpaRepository; private final ExamQuotaCacheManager examQuotaCacheManager; + public void agreedToTerms(ApplicationRequest request) { + if (!request.agreement().validateAgreement()) { + throw new CustomRuntimeException(ErrorCode.NOT_AGREED_TO_TERMS); + } + } + public void requestNoDuplicateExams(List examIds) { Set examIdSet = new HashSet<>(examIds); if (examIds.size() != examIdSet.size()) { throw new CustomRuntimeException(ErrorCode.EXAM_DUPLICATED); } + if (examIdSet.isEmpty()) { + throw new CustomRuntimeException(ErrorCode.EXAM_NOT_APPLIED); + } } public void examIdsAndLunchSelection(List requests) { @@ -39,12 +51,9 @@ public void examIdsAndLunchSelection(List requests) { List requestedExamIds = requests.stream() .map(ExamApplicationRequest::examId) .toList(); + Set examIdSet = new HashSet<>(requestedExamIds); - List existingExams = examJpaRepository.findAllById(requestedExamIds); - - if (existingExams.size() != requestedExamIds.size()) { - throw new CustomRuntimeException(ErrorCode.EXAM_NOT_FOUND); - } + List existingExams = examJpaRepository.findAllById(examIdSet); lunchSelection(requests, existingExams); } @@ -56,16 +65,18 @@ private void lunchSelection(List requests, .map(ExamJpaEntity::getId) .collect(Collectors.toSet()); - boolean hasInvalidLunchRequest = requests.stream() - .anyMatch(req -> examsWithoutLunch.contains(req.examId()) && req.isLunchChecked()); + requests.stream() + .filter(req -> req.isLunchChecked() && examsWithoutLunch.contains(req.examId())) + .findFirst() + .ifPresent(req -> { + throw new CustomRuntimeException(ErrorCode.LUNCH_SELECTION_INVALID); + }); - if (hasInvalidLunchRequest) { - throw new CustomRuntimeException(ErrorCode.LUNCH_SELECTION_INVALID); - } } public void noDuplicateApplication(Long userId, List examIds) { - boolean alreadyApplied = applicationJpaRepository.existsByUserIdAndExamIds(userId, examIds); + boolean alreadyApplied = examApplicationJpaRepository.existsByUserIdAndExamIds(userId, + examIds); if (alreadyApplied) { throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_DUPLICATED); } From cd6da2bb3106c7abb2dcdd32425e872ab04e858d Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:57:40 +0900 Subject: [PATCH 21/36] feat: remove commented code from BannerRequest to improve clarity --- .../mosu/mosuserver/presentation/admin/dto/BannerRequest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/BannerRequest.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/BannerRequest.java index 1abb7a7d..ddfece7b 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/BannerRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/BannerRequest.java @@ -12,7 +12,6 @@ public record BannerRequest( FileRequest file ) { - //배너 등록할 때 fileRequest 가 없을 때는 어떤 식으로 하면 되는지 public BannerJpaEntity toEntity() { return BannerJpaEntity.builder() .fileName(file != null ? file.fileName() : null) From c82514922b5cd5d0155ec645de35dd49d2f1c543 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:57:46 +0900 Subject: [PATCH 22/36] feat: rename refundCounts to refundAbortedCounts in DashBoardResponse for clarity --- .../presentation/admin/dto/DashBoardResponse.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java index 0b63f660..1ef7589a 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/DashBoardResponse.java @@ -2,12 +2,20 @@ public record DashBoardResponse( Long applicationCounts, - Long refundCounts, + Long refundAbortedCounts, Long userCounts ) { - public static DashBoardResponse of(Long applicationCounts, Long refundCounts, Long userCounts) { - return new DashBoardResponse(applicationCounts, refundCounts, userCounts); + public static DashBoardResponse of( + Long applicationCounts, + Long refundAbortedCounts, + Long userCounts + ) { + return new DashBoardResponse( + applicationCounts, + refundAbortedCounts, + userCounts + ); } } From 26e233c5b887504c93bf14a86b0f3a511532b68c Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:57:54 +0900 Subject: [PATCH 23/36] feat: update DurationRequest to use current date for startDate in toDurationJpaVO method --- .../mosuserver/presentation/event/dto/DurationRequest.java | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/presentation/event/dto/DurationRequest.java b/src/main/java/life/mosu/mosuserver/presentation/event/dto/DurationRequest.java index 91120477..ebc06f04 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/event/dto/DurationRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/event/dto/DurationRequest.java @@ -6,16 +6,13 @@ @Schema(description = "이벤트 기간 요청 DTO") public record DurationRequest( - - @Schema(description = "이벤트 시작일", example = "2025-07-01") - LocalDate startDate, - + @Schema(description = "이벤트 종료일", example = "2025-07-31") LocalDate endDate ) { public DurationJpaVO toDurationJpaVO() { - return new DurationJpaVO(startDate, endDate); + return new DurationJpaVO(LocalDate.now(), endDate); } } From 201b2b8150d7ce4892a2bc90b8492039fbd0f191 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:58:31 +0900 Subject: [PATCH 24/36] feat: simplify education description in EditProfileRequest schema --- .../mosuserver/presentation/profile/dto/EditProfileRequest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java b/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java index 40f439b6..003733bf 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/profile/dto/EditProfileRequest.java @@ -9,7 +9,7 @@ public record EditProfileRequest( @Schema(description = "이메일 주소", example = "hong@example.com") String email, - @Schema(description = "학력 정보 (Enum: ELEMENTARY, MIDDLE, HIGH_SCHOOL, UNIVERSITY 등)", example = "HIGH_SCHOOL") + @Schema(description = "학력 정보") Education education, @Schema(description = "학교 정보", implementation = SchoolInfoRequest.class) From ec853275ea686b280455a746efe358f3add958ea Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:58:37 +0900 Subject: [PATCH 25/36] feat: add error codes for terms agreement and exam application requirements in ErrorCode --- .../life/mosu/mosuserver/global/exception/ErrorCode.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java b/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java index 12499a2a..e5f3f131 100644 --- a/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java +++ b/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java @@ -45,11 +45,13 @@ public enum ErrorCode { USER_INFO_INVALID(HttpStatus.BAD_REQUEST, "유효하지 않은 사용자 정보입니다."), USER_NOT_ACCESS_FORBIDDEN(HttpStatus.BAD_REQUEST, "접근 권한이 없는 사용자입니다"), USER_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "사용자 저장에 실패했습니다."), + // 신청 관련 에러 WRONG_SUBJECT_TYPE(HttpStatus.BAD_REQUEST, "잘못된 과목명 입니다."), WRONG_LUNCH_TYPE(HttpStatus.BAD_REQUEST, "잘못된 도시락명 입니다."), WRONG_AREA_TYPE(HttpStatus.BAD_REQUEST, "잘못된 지역명 입니다."), WRONG_SUBJECT_COUNT(HttpStatus.BAD_REQUEST, "응시과목은 반드시 다른 과목 2개를 신청해야 합니다."), + NOT_AGREED_TO_TERMS(HttpStatus.BAD_REQUEST, "신청 시 모든 약관에 동의해야 합니다."), // 수험표 관련 에러 EXAM_TICKET_NOT_OPEN(HttpStatus.BAD_REQUEST, "수험표 조회 기간이 아닙니다."), @@ -125,7 +127,8 @@ public enum ErrorCode { // 시험 관련 에러 EXAM_NOT_FOUND(HttpStatus.NOT_FOUND, "시험 정보를 찾을 수 없습니다."), EXAM_DATE_PASSED(HttpStatus.BAD_REQUEST, "이미 지난 시험입니다."), - + EXAM_NOT_APPLIED(HttpStatus.BAD_REQUEST, "1개 이상의 시험을 신청해야 합니다."), + EXAM_DATE_AFTER_DEADLINE(HttpStatus.BAD_REQUEST, "시험일은 접수 마감일 이후여야 합니다."), //lunch 관련 LUNCH_NOT_FOUND(HttpStatus.NOT_FOUND, "점심 정보를 찾을 수 없습니다."), LUNCH_PRICE_UPDATE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "가격 수정에 실패하였습니다."), @@ -163,8 +166,8 @@ public enum ErrorCode { INVALID_VIRTUAL_ACCOUNT_DEPOSIT_EVENT(HttpStatus.BAD_REQUEST, "유효하지 않은 가상 계좌 입금 이벤트입니다."), VIRTUAL_ACCOUNT_CREATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "가상 계좌 생성에 실패했습니다."), - VIRTUAL_ACCOUNT_LOG_NOT_FOUND(HttpStatus.NOT_FOUND, "가상 계좌 로그를 찾을 수 없습니다."), - ; + VIRTUAL_ACCOUNT_LOG_NOT_FOUND(HttpStatus.NOT_FOUND, "가상 계좌 로그를 찾을 수 없습니다."); + private final HttpStatus status; private final String message; From c72ab753aab13be42eec90131e968b144f3669c0 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:58:46 +0900 Subject: [PATCH 26/36] feat: improve null handling for attachment and duration in EventRequest and EventResponse --- .../presentation/event/dto/EventRequest.java | 15 +++++++-------- .../presentation/event/dto/EventResponse.java | 5 +++-- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventRequest.java b/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventRequest.java index 2c3af3f9..ae0241e7 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventRequest.java @@ -2,7 +2,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotBlank; -import java.util.List; import life.mosu.mosuserver.domain.event.entity.EventJpaEntity; import life.mosu.mosuserver.domain.file.Visibility; import life.mosu.mosuserver.presentation.common.FileRequest; @@ -25,18 +24,18 @@ public record EventRequest( ) { - public List optionalAttachment() { - FileRequest parsedAttachment = this.attachment; - return parsedAttachment == null ? List.of() : List.of(parsedAttachment); - } +// public List optionalAttachment() { +// FileRequest parsedAttachment = this.attachment; +// return parsedAttachment == null ? List.of() : List.of(parsedAttachment); +// } public EventJpaEntity toEntity() { return EventJpaEntity.builder() .title(title) .eventLink(eventLink) - .duration(duration.toDurationJpaVO()) - .fileName(attachment().fileName()) - .s3Key(attachment().s3Key()) + .duration(duration != null ? duration.toDurationJpaVO() : null) + .fileName(attachment != null ? attachment().fileName() : null) + .s3Key(attachment != null ? attachment().s3Key() : null) .visibility(Visibility.PUBLIC) .build(); } diff --git a/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventResponse.java b/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventResponse.java index d346a04f..ed33a7f9 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/event/dto/EventResponse.java @@ -34,8 +34,9 @@ public static EventResponse of(EventJpaEntity event, String eventUrl) { return new EventResponse( event.getId(), event.getTitle(), - event.getDuration().getEndDate(), - event.getEventLink(), + event.getDuration() != null ? event.getDuration().getEndDate() + : null, + event.getEventLink() != null ? event.getEventLink() : null, attachment ); } From e9372d1fcc290ea8527c1cb4bd02f71e36ae11eb Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:58:53 +0900 Subject: [PATCH 27/36] feat: add deleted flag handling in ExamApplication repository queries --- .../ExamApplicationJpaRepository.java | 37 ++++++++++++++++++- .../jpa/ExamApplicationBulkRepository.java | 6 +-- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamApplicationJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamApplicationJpaRepository.java index a1e378ec..363d9ea6 100644 --- a/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamApplicationJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamApplicationJpaRepository.java @@ -44,9 +44,12 @@ public interface ExamApplicationJpaRepository extends JOIN PaymentJpaEntity p on p.examApplicationId = ea.id WHERE ea.id = :examApplicationId AND p.paymentStatus = 'DONE' + AND ea.userId = :userId + AND p.deleted = false """) - Optional findExamApplicationInfoById(Long userId, - Long examApplicationId); + Optional findExamApplicationInfoById( + @Param("userId") Long userId, + @Param("examApplicationId") Long examApplicationId); @Query(""" @@ -67,6 +70,7 @@ Optional findExamApplicationInfoById(Long userId, WHERE ea.id = :examApplicationId AND u.id = :userId AND p.paymentStatus = 'DONE' + AND p.deleted = false """) Optional findExamTicketInfoProjectionById( @Param("userId") Long userId, @@ -105,6 +109,7 @@ Optional findExamTicketInfoProjectionById( JOIN PaymentJpaEntity p on p.examApplicationId = ea.id WHERE ea.id = :targetId AND p.paymentStatus = 'DONE' + AND p.deleted = false """) Optional findExamAndPaymentByExamApplicationId( @Param("targetId") Long targetId); @@ -121,6 +126,7 @@ Optional findExamAndPaymentByExamApplicationId( JOIN PaymentJpaEntity p ON p.examApplicationId = ea.id WHERE ea.id = :examApplicationId AND p.paymentStatus = 'DONE' + AND p.deleted = false """) Optional findExamInfo(@Param("examApplicationId") Long examApplicationId); @@ -137,6 +143,7 @@ Optional findExamAndPaymentByExamApplicationId( JOIN PaymentJpaEntity p ON p.examApplicationId = ea.id WHERE ea.id = :examApplicationId AND p.paymentStatus = 'DONE' + AND p.deleted = false """) Optional findExamInfoWithExamNumber( @Param("examApplicationId") Long examApplicationId); @@ -148,6 +155,7 @@ SELECT case when COUNT(ea) > 0 then true else false end WHERE ea.id = :examApplicationId AND ea.userId = :userId AND p.paymentStatus = 'DONE' + AND p.deleted = false """) boolean existByUserIdAndExamApplicationId(@Param("userId") Long userId, @Param("examApplicationId") Long examApplicationId); @@ -161,4 +169,29 @@ boolean existByUserIdAndExamApplicationId(@Param("userId") Long userId, AND e.deleted = false """) Optional findByOrderId(String orderId); + + @Query( + """ + SELECT CASE WHEN COUNT(ea) > 0 THEN true ELSE false END + FROM ExamApplicationJpaEntity ea + JOIN PaymentJpaEntity p ON ea.id = p.examApplicationId + WHERE ea.userId = :userId + AND p.paymentStatus = 'DONE' + AND p.deleted = false + AND ea.examId IN :examIds + """ + ) + boolean existsByUserIdAndExamIds( + @Param("userId") Long userId, + @Param("examIds") List examIds); + + + @Query(""" + SELECT COUNT(ea) + FROM ExamApplicationJpaEntity ea + JOIN PaymentJpaEntity p ON ea.id = p.examApplicationId + WHERE p.paymentStatus = 'DONE' + AND p.deleted = false + """) + long countAll(); } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/infra/persistence/jpa/ExamApplicationBulkRepository.java b/src/main/java/life/mosu/mosuserver/infra/persistence/jpa/ExamApplicationBulkRepository.java index 7d03fe42..0b65c608 100644 --- a/src/main/java/life/mosu/mosuserver/infra/persistence/jpa/ExamApplicationBulkRepository.java +++ b/src/main/java/life/mosu/mosuserver/infra/persistence/jpa/ExamApplicationBulkRepository.java @@ -29,8 +29,8 @@ public class ExamApplicationBulkRepository { private static final String SQL_INSERT_EXAM_APPLICATION = """ INSERT INTO exam_application (created_at, updated_at, application_id, - user_id, exam_id, lunch_checked, exam_number) - VALUES (?, ?, ?, ?, ?, ?, ?) + user_id, exam_id, lunch_checked, exam_number, deleted) + VALUES (?, ?, ?, ?, ?, ?, ?, ?) """; private static final String SQL_INSERT_EXAM_SUBJECT = """ INSERT INTO exam_subject (exam_application_id, subject) VALUES (?, ?) @@ -53,7 +53,7 @@ public List saveAllExamApplicationsWithSubjects( ps.setLong(5, e.getExamId()); ps.setBoolean(6, e.getIsLunchChecked()); ps.setString(7, e.getExamNumber()); -// ps.setBoolean(8, e.getDeleted()); + ps.setBoolean(8, false); ps.addBatch(); log.info( From 29053d7daec888f13b9e9a3ffa0f7765d13b25e1 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:59:05 +0900 Subject: [PATCH 28/36] feat: refactor exam ticket image URL retrieval and enhance request validation --- .../ExamApplicationService.java | 26 +++++++++---------- .../presentation/exam/ExamController.java | 6 ++--- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java b/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java index bde22bd6..6ae044d7 100644 --- a/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java +++ b/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java @@ -41,7 +41,6 @@ public class ExamApplicationService { private final S3Service s3Service; private final FixedQuantityDiscountCalculator calculator; - @Transactional public List register(RegisterExamApplicationEvent event) { List examApplicationEntities = event.toEntity(); @@ -57,7 +56,6 @@ public void updateSubjects(Long userId, Long examApplicationId, examSubjectJpaRepository.deleteExamSubjectsWithDonePayment(examApplicationId); List examSubjects = request.toEntityList(examApplicationId); examSubjectJpaRepository.saveAll(examSubjects); - } @Transactional(propagation = Propagation.REQUIRES_NEW) @@ -97,27 +95,17 @@ public ExamTicketResponse getExamTicket(Long userId, Long examApplicationId) { .map(Subject::getSubjectName) .toList(); - String s3Key = examTicketInfo.s3Key(); - String examTicketImgUrl = null; - - if (s3Key != null) { - examTicketImgUrl = s3Service.getPreSignedUrl(s3Key); - } + String examTicketImgUrl = getExamTicketImgUrl(examTicketInfo); return ExamTicketResponse.of(examTicketImgUrl, examTicketInfo.userName(), examTicketInfo.birth(), examTicketInfo.examNumber(), subjects, examTicketInfo.schoolName()); - } public ExamApplicationInfoResponse getApplication(Long userId, Long examApplicationId, Long applicationId) { - validateUser(userId, examApplicationId); - //상세 조회는 done 만 가능 -// Integer examApplicationCount = paymentJpaRepository.countByExamApplicationId( -// examApplicationId); List examApplicationEntities = examApplicationJpaRepository.findByApplicationId( applicationId); int lunchCount = (int) examApplicationEntities.stream() @@ -135,6 +123,7 @@ public ExamApplicationInfoResponse getApplication(Long userId, Long examApplicat Set subjects = examSubjects.stream() .map(ExamSubjectJpaEntity::getSubjectName) .collect(Collectors.toSet()); + //totalAmount 는 Lunch 가격이 포함되었을 수도 있음 //totalAmount - Lunch 가격으로 getAppliedDiscountAmount() 메소드에 넣어야함. @@ -187,4 +176,15 @@ private void validateExamTicketOpenDate(LocalDate examDate, String examNumber) { throw new CustomRuntimeException(ErrorCode.EXAM_TICKET_NOT_OPEN); } } + + private String getExamTicketImgUrl(ExamTicketInfoProjection examTicketInfo) { + String s3Key = examTicketInfo.s3Key(); + String examTicketImgUrl = null; + + if (s3Key != null) { + examTicketImgUrl = s3Service.getPreSignedUrl(s3Key); + } + return examTicketImgUrl; + } + } diff --git a/src/main/java/life/mosu/mosuserver/presentation/exam/ExamController.java b/src/main/java/life/mosu/mosuserver/presentation/exam/ExamController.java index 5ff384c2..9e18c357 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/exam/ExamController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/exam/ExamController.java @@ -1,5 +1,6 @@ package life.mosu.mosuserver.presentation.exam; +import jakarta.validation.Valid; import java.util.List; import life.mosu.mosuserver.application.exam.ExamService; import life.mosu.mosuserver.global.util.ApiResponseWrapper; @@ -29,7 +30,7 @@ public class ExamController { @PostMapping @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") public ResponseEntity> register( - @RequestBody ExamRequest request + @Valid @RequestBody ExamRequest request ) { examService.register(request); return ResponseEntity.ok( @@ -65,10 +66,9 @@ public ResponseEntity> delete(@PathVariable Long examId examService.delete(examId); return ResponseEntity.ok( ApiResponseWrapper.success(HttpStatus.OK, "시험장 삭제 성공")); - } - @PatchMapping("/{examId}") + @PatchMapping("/{examId}/close") @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") public ResponseEntity> close(@PathVariable Long examId) { examService.close(examId); From a1d61d40ae26f2fd623d04752c280aaf81fd5675 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:59:12 +0900 Subject: [PATCH 29/36] feat: enhance lunch availability check to include lunch price validation --- .../life/mosu/mosuserver/domain/exam/entity/ExamJpaEntity.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/exam/entity/ExamJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/exam/entity/ExamJpaEntity.java index 24c157dc..591e999c 100644 --- a/src/main/java/life/mosu/mosuserver/domain/exam/entity/ExamJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/exam/entity/ExamJpaEntity.java @@ -80,7 +80,7 @@ public ExamJpaEntity( } public boolean hasNotLunch() { - return lunchName == null; + return this.lunchName == null || this.lunchPrice == null; } public void close() { From d7ecbe28741ffb349517269e711fea4e4db1d4ca Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 20:59:19 +0900 Subject: [PATCH 30/36] feat: add validation annotations for required fields in ExamRequest --- .../presentation/exam/dto/ExamRequest.java | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamRequest.java b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamRequest.java index 2c793688..e5b0616f 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamRequest.java @@ -1,5 +1,7 @@ package life.mosu.mosuserver.presentation.exam.dto; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import java.time.LocalDate; import java.time.LocalDateTime; import life.mosu.mosuserver.domain.exam.entity.AddressJpaVO; @@ -7,11 +9,11 @@ import life.mosu.mosuserver.domain.exam.entity.ExamJpaEntity; public record ExamRequest( - String schoolName, - String areaName, - AddressRequest address, - LocalDate examDate, - Integer capacity, + @NotBlank String schoolName, + @NotBlank String areaName, + @NotNull AddressRequest address, + @NotNull LocalDate examDate, + @NotNull Integer capacity, LocalDateTime deadlineTime, LunchRequest lunch ) { @@ -25,8 +27,8 @@ public ExamJpaEntity toEntity() { .examDate(examDate) .capacity(capacity) .deadlineTime(deadlineTime) - .lunchName(lunch.name()) - .lunchPrice(lunch.price()) + .lunchName(lunch != null ? lunch.name() : null) + .lunchPrice(lunch != null ? lunch.price() : null) .build(); } From d3d563a2998f6347aa9f4205d797949e37192857 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 21:00:34 +0900 Subject: [PATCH 31/36] feat: add exam date validation in register method of ExamService --- .../mosu/mosuserver/application/exam/ExamService.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java b/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java index 8d47026e..03714ab2 100644 --- a/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java +++ b/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java @@ -23,7 +23,9 @@ public class ExamService { @Transactional public void register(ExamRequest request) { + validateExamDate(request); ExamJpaEntity exam = request.toEntity(); + examJpaRepository.save(exam); } @@ -71,4 +73,10 @@ public void close(Long examId) { .orElseThrow(() -> new CustomRuntimeException(ErrorCode.EXAM_NOT_FOUND)); exam.close(); } + + private void validateExamDate(ExamRequest request) { + if (!request.deadlineTime().isBefore(request.examDate().atStartOfDay())) { + throw new CustomRuntimeException(ErrorCode.EXAM_DATE_AFTER_DEADLINE); + } + } } From 64800d9a5da4f18a349aa0c329ba7535866e0fd8 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 21:00:42 +0900 Subject: [PATCH 32/36] feat: add filter for deleted payments in deleteExamSubjectsWithDonePayment query --- .../examapplication/repository/ExamSubjectJpaRepository.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamSubjectJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamSubjectJpaRepository.java index 3aad5dac..e595a9e6 100644 --- a/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamSubjectJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/examapplication/repository/ExamSubjectJpaRepository.java @@ -29,6 +29,7 @@ public interface ExamSubjectJpaRepository extends JpaRepository Date: Mon, 11 Aug 2025 21:00:50 +0900 Subject: [PATCH 33/36] feat: remove logging from GetApplicationsStepProcessor --- .../application/processor/GetApplicationsStepProcessor.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/application/processor/GetApplicationsStepProcessor.java b/src/main/java/life/mosu/mosuserver/application/application/processor/GetApplicationsStepProcessor.java index e664b43b..ee282f6b 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/processor/GetApplicationsStepProcessor.java +++ b/src/main/java/life/mosu/mosuserver/application/application/processor/GetApplicationsStepProcessor.java @@ -13,10 +13,8 @@ import life.mosu.mosuserver.presentation.application.dto.ApplicationResponse; import life.mosu.mosuserver.presentation.examapplication.dto.ExamApplicationWithStatus; import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; -@Slf4j @Component @RequiredArgsConstructor public class GetApplicationsStepProcessor implements @@ -33,7 +31,6 @@ public class GetApplicationsStepProcessor implements public List process(Long userId) { List applications = applicationJpaRepository.findAllByUserId(userId); - log.info("applications info: {}", applications.size()); if (applications.isEmpty()) { return List.of(); } From 716093d48f9f486c5d2767b64ac458debee0ea92 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 21:01:00 +0900 Subject: [PATCH 34/36] feat: rename recommendee fields to recommender in projection and DTO --- .../RecommendationDetailsProjection.java | 4 ++-- .../refund/repository/RefundJpaRepository.java | 1 + .../admin/dto/RecommendationExcelDto.java | 16 ++++++++-------- 3 files changed, 11 insertions(+), 10 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/domain/admin/projection/RecommendationDetailsProjection.java b/src/main/java/life/mosu/mosuserver/domain/admin/projection/RecommendationDetailsProjection.java index 741804cc..c6a8bc59 100644 --- a/src/main/java/life/mosu/mosuserver/domain/admin/projection/RecommendationDetailsProjection.java +++ b/src/main/java/life/mosu/mosuserver/domain/admin/projection/RecommendationDetailsProjection.java @@ -10,8 +10,8 @@ public record RecommendationDetailsProjection( LocalDate birth, String recommendeeName, String recommendeePhoneNumber, - String recommendeeBank, - String recommendeeAccountNumber + String recommenderBank, + String recommenderAccountNumber ) { } diff --git a/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java index bea9ebcb..a406f6c4 100644 --- a/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/refund/repository/RefundJpaRepository.java @@ -53,4 +53,5 @@ WHERE r.refundStatus in ('ABORTED') AND r.createdAt < :time """) List findFailedRefunds(@Param("time") LocalDateTime time); + } diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/RecommendationExcelDto.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/RecommendationExcelDto.java index 9cd9e401..3fe78aa7 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/RecommendationExcelDto.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/RecommendationExcelDto.java @@ -31,13 +31,13 @@ public record RecommendationExcelDto( @ExcelColumn(headerName = "피추천자 전화번호") String recommendeePhoneNumber, - @Schema(description = "피추천자 은행명", example = "신한은행") - @ExcelColumn(headerName = "피추천자 은행명") - String recommendeeBank, + @Schema(description = "추천자 은행명", example = "신한은행") + @ExcelColumn(headerName = "추천자 은행명") + String recommenderBank, - @Schema(description = "피추천자 계좌번호", example = "110123456789") - @ExcelColumn(headerName = "피추천자 계좌번호") - String recommendeeAccountNumber + @Schema(description = "추천자 계좌번호", example = "110123456789") + @ExcelColumn(headerName = "추천자 계좌번호") + String recommenderAccountNumber ) { public static RecommendationExcelDto of(RecommendationDetailsProjection projection) { @@ -48,8 +48,8 @@ public static RecommendationExcelDto of(RecommendationDetailsProjection projecti projection.birth(), projection.recommendeeName(), projection.recommendeePhoneNumber(), - projection.recommendeeBank(), - projection.recommendeeAccountNumber() + projection.recommenderBank(), + projection.recommenderAccountNumber() ); } From 14dcdf9c06c48841d98211a81e4cbc3169057bda Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 21:01:21 +0900 Subject: [PATCH 35/36] feat: add method to count users by role exclusion in UserJpaRepository --- .../mosuserver/domain/user/repository/UserJpaRepository.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/java/life/mosu/mosuserver/domain/user/repository/UserJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/user/repository/UserJpaRepository.java index e5038a33..a8d59e8d 100644 --- a/src/main/java/life/mosu/mosuserver/domain/user/repository/UserJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/user/repository/UserJpaRepository.java @@ -2,6 +2,7 @@ import java.util.Optional; import life.mosu.mosuserver.domain.user.entity.UserJpaEntity; +import life.mosu.mosuserver.domain.user.entity.UserRole; import org.springframework.data.jpa.repository.JpaRepository; public interface UserJpaRepository extends JpaRepository { @@ -14,5 +15,7 @@ public interface UserJpaRepository extends JpaRepository { Optional findByPhoneNumber(String phoneNumber); + long countByUserRoleNot(UserRole userRole); + boolean existsByPhoneNumber(String phoneNumber); } From 40843624c46484748bfad22f8337d05aff8af816 Mon Sep 17 00:00:00 2001 From: KNU-K Date: Mon, 11 Aug 2025 21:02:56 +0900 Subject: [PATCH 36/36] feat: simplify user existence check in SignUpAccountStepProcessor --- .../auth/processor/SignUpAccountStepProcessor.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/life/mosu/mosuserver/application/auth/processor/SignUpAccountStepProcessor.java b/src/main/java/life/mosu/mosuserver/application/auth/processor/SignUpAccountStepProcessor.java index 6adfd03c..be712eff 100644 --- a/src/main/java/life/mosu/mosuserver/application/auth/processor/SignUpAccountStepProcessor.java +++ b/src/main/java/life/mosu/mosuserver/application/auth/processor/SignUpAccountStepProcessor.java @@ -18,8 +18,9 @@ public class SignUpAccountStepProcessor implements StepProcessor