diff --git a/src/main/java/life/mosu/mosuserver/application/application/ApplicationContext.java b/src/main/java/life/mosu/mosuserver/application/application/ApplicationContext.java new file mode 100644 index 00000000..d27684f8 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/ApplicationContext.java @@ -0,0 +1,118 @@ +package life.mosu.mosuserver.application.application; + +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; +import life.mosu.mosuserver.domain.application.ApplicationJpaEntity; +import life.mosu.mosuserver.domain.exam.ExamJpaEntity; +import life.mosu.mosuserver.domain.examapplication.ExamApplicationJpaEntity; +import life.mosu.mosuserver.domain.examapplication.ExamSubjectJpaEntity; +import life.mosu.mosuserver.domain.payment.PaymentJpaEntity; +import life.mosu.mosuserver.domain.payment.PaymentStatus; +import life.mosu.mosuserver.presentation.application.dto.ApplicationResponse; +import life.mosu.mosuserver.presentation.application.dto.ExamApplicationResponse; + +public record ApplicationContext( + List applications, + List examApplications, + Map examMap, + Map> subjectMap, + Map paymentMap +) { + + public ApplicationContext(List applications, + List examApplications) { + this(applications, examApplications, Map.of(), Map.of(), Map.of()); + } + + public ApplicationContext fetchExams(Function, List> fetcher) { + List examIds = this.examApplications.stream().map(ExamApplicationJpaEntity::getExamId) + .distinct().toList(); + Map newExamMap = fetcher.apply(examIds).stream() + .collect(Collectors.toMap(ExamJpaEntity::getId, Function.identity())); + return new ApplicationContext(this.applications, this.examApplications, newExamMap, + this.subjectMap, this.paymentMap); + } + + public ApplicationContext fetchSubjects( + Function, List> fetcher) { + List examApplicationIds = this.examApplications.stream() + .map(ExamApplicationJpaEntity::getId).toList(); + Map> newSubjectMap = fetcher.apply(examApplicationIds) + .stream() + .collect(Collectors.groupingBy(ExamSubjectJpaEntity::getExamApplicationId)); + return new ApplicationContext(this.applications, this.examApplications, this.examMap, + newSubjectMap, this.paymentMap); + } + + public ApplicationContext fetchPayments(Function, List> fetcher) { + List examApplicationIds = this.examApplications.stream() + .map(ExamApplicationJpaEntity::getId).toList(); + Map newPaymentMap = fetcher.apply(examApplicationIds).stream() + .filter(payment -> payment.getPaymentStatus() == PaymentStatus.DONE) + .collect(Collectors.toMap( + PaymentJpaEntity::getExamApplicationId, + Function.identity())); + + return new ApplicationContext(this.applications, this.examApplications, this.examMap, + this.subjectMap, newPaymentMap); + } + + public List assemble() { + Map> groupedExamResponses = this.examApplications.stream() + .map(this::createExamApplicationResponse) + .filter(Objects::nonNull) + .collect(Collectors.groupingBy(Map.Entry::getKey, + Collectors.mapping(Map.Entry::getValue, Collectors.toList()))); + + return this.applications.stream() + .map(app -> ApplicationResponse.of(app.getId(), + groupedExamResponses.getOrDefault(app.getId(), List.of()))) + .toList(); + } + + private Map.Entry createExamApplicationResponse( + ExamApplicationJpaEntity examApp) throws RuntimeException { + + ExamJpaEntity exam = this.examMap.get(examApp.getExamId()); + if (exam == null) { + return null; + } + + List subjectEntities = this.subjectMap.getOrDefault(examApp.getId(), + List.of()); + Set subjects = subjectEntities.stream().map(es -> es.getSubject().getSubjectName()) + .collect(Collectors.toSet()); + + PaymentJpaEntity payment = this.paymentMap.getOrDefault(examApp.getId(), null); + String paymentStatus = Optional.ofNullable(payment) + .map(p -> p.getPaymentStatus().name()) + .orElse(null); + + Integer totalAmount = Optional.ofNullable(payment) + .map(p -> p.getPaymentAmount().getTotalAmount()) + .orElse(0); + + String lunchName; + if (examApp.getIsLunchChecked()) { + lunchName = exam.getLunchName(); + } else { + lunchName = null; + } + + ExamApplicationResponse response = ExamApplicationResponse.of( + examApp.getId(), + examApp.getCreatedAt(), + paymentStatus, totalAmount, + exam.getSchoolName(), + exam.getExamDate(), + subjects, + lunchName + ); + return Map.entry(examApp.getApplicationId(), response); + } +} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/application/application/ApplicationProcessingContext.java b/src/main/java/life/mosu/mosuserver/application/application/ApplicationProcessingContext.java new file mode 100644 index 00000000..8f942e5a --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/ApplicationProcessingContext.java @@ -0,0 +1,20 @@ +package life.mosu.mosuserver.application.application; + +import life.mosu.mosuserver.presentation.common.FileRequest; + +public record ApplicationProcessingContext( + Long applicationId, + FileRequest fileRequest +) { + + public static ApplicationProcessingContext of( + Long applicationId, + FileRequest fileRequest + ) { + return new ApplicationProcessingContext( + applicationId, + fileRequest + ); + } + +} \ No newline at end of file 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 59d74f17..22359888 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java +++ b/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java @@ -1,35 +1,22 @@ package life.mosu.mosuserver.application.application; -import java.util.HashSet; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.function.Function; -import java.util.stream.Collectors; -import life.mosu.mosuserver.application.examapplication.ExamApplicationService; -import life.mosu.mosuserver.application.examapplication.dto.RegisterExamApplicationEvent; +import life.mosu.mosuserver.application.application.dto.RegisterApplicationCommand; +import life.mosu.mosuserver.application.application.processor.SaveExamTicketStepProcessor; +import life.mosu.mosuserver.application.application.vaildator.ApplicationValidator; import life.mosu.mosuserver.domain.application.ApplicationJpaEntity; import life.mosu.mosuserver.domain.application.ApplicationJpaRepository; -import life.mosu.mosuserver.domain.application.ExamTicketImageJpaEntity; -import life.mosu.mosuserver.domain.application.ExamTicketImageJpaRepository; import life.mosu.mosuserver.domain.application.Subject; -import life.mosu.mosuserver.domain.exam.ExamJpaEntity; import life.mosu.mosuserver.domain.exam.ExamJpaRepository; import life.mosu.mosuserver.domain.examapplication.ExamApplicationJpaEntity; import life.mosu.mosuserver.domain.examapplication.ExamApplicationJpaRepository; -import life.mosu.mosuserver.domain.examapplication.ExamSubjectJpaEntity; import life.mosu.mosuserver.domain.examapplication.ExamSubjectJpaRepository; -import life.mosu.mosuserver.domain.payment.PaymentJpaEntity; import life.mosu.mosuserver.domain.payment.PaymentJpaRepository; -import life.mosu.mosuserver.global.exception.CustomRuntimeException; -import life.mosu.mosuserver.global.exception.ErrorCode; -import life.mosu.mosuserver.infra.persistence.jpa.ExamApplicationBulkRepository; import life.mosu.mosuserver.presentation.application.dto.ApplicationRequest; import life.mosu.mosuserver.presentation.application.dto.ApplicationResponse; import life.mosu.mosuserver.presentation.application.dto.CreateApplicationResponse; import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest; -import life.mosu.mosuserver.presentation.application.dto.ExamApplicationResponse; -import life.mosu.mosuserver.presentation.common.FileRequest; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -42,208 +29,60 @@ public class ApplicationService { private final ApplicationJpaRepository applicationJpaRepository; - private final ExamTicketImageJpaRepository examTicketImageJpaRepository; - private final ExamApplicationService examApplicationService; private final ExamSubjectJpaRepository examSubjectJpaRepository; private final ExamApplicationJpaRepository examApplicationJpaRepository; private final ExamJpaRepository examJpaRepository; - private final ExamApplicationBulkRepository examApplicationBulkRepository; private final PaymentJpaRepository paymentJpaRepository; + private final RegisterApplicationStepProcessor registerApplicationStepProcessor; + private final SaveExamTicketStepProcessor saveExamTicketStepProcessor; + private final ApplicationValidator validator; @Transactional public CreateApplicationResponse apply(Long userId, ApplicationRequest request) { - List examApplicationRequests = request.examApplication(); - Set subjects = request.validatedSubjects(); - - List examIds = examApplicationRequests.stream() + List examIds = request.examApplication().stream() .map(ExamApplicationRequest::examId) .toList(); - // 중복 신청 검증 - Set examIdSet = new HashSet<>(examIds); - if (examIds.size() != examIdSet.size()) { - throw new RuntimeException("같은 시험을 신청할 수 없습니다."); - } - - // 신청을 1개 이상 신청했는지 검증 - if (examIds.isEmpty()) { - throw new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND); - } - - //해당 시험이 진짜 있는 일정인지, lunch 가 없는 시험인데 lunch 를 신청했는지 - validateExamIdsAndLunchSelection(examApplicationRequests); + Set subjects = request.getSubjects(); - boolean isDuplicate = applicationJpaRepository.existsByUserIdAndExamIds(userId, examIds); - if (isDuplicate) { - throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_DUPLICATED); - } + // 해당 시험이 진짜 있는 일정인지, lunch 가 없는 시험인데 lunch 를 신청했는지 + validator.RequestNoDuplicateExams(examIds); + validator.ExamIdsAndLunchSelection(request.examApplication()); + validator.NoDuplicateApplication(userId, examIds); ApplicationJpaEntity application = request.toApplicationJpaEntity(userId); ApplicationJpaEntity savedApplication = applicationJpaRepository.save(application); - Long applicationId = savedApplication.getId(); - List examApplicationEntities = examApplicationService.register( - RegisterExamApplicationEvent.of(request.examApplication(), applicationId) - ); + Long applicationId = savedApplication.getId(); - // 시험 신청 목록과 과목 multi-insert - examApplicationBulkRepository.saveAllExamApplicationsWithSubjects(examApplicationEntities, - subjects); + registerApplicationStepProcessor.process( + RegisterApplicationCommand.of(applicationId, request, subjects)); - // 수험표 저장 - FileRequest fileReq = request.admissionTicket(); - if (fileReq.fileName() != null && fileReq.s3Key() != null) { - ExamTicketImageJpaEntity examTicketImage = fileReq - .toExamTicketImageEntity(applicationId); + saveExamTicketStepProcessor.process( + ApplicationProcessingContext.of(applicationId, request.admissionTicket())); - examTicketImageJpaRepository.save(examTicketImage); - } return CreateApplicationResponse.of(applicationId); } - // TODO: 테스트 필요 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public List getApplications(Long userId) { - - List applications = getUserApplications(userId); - - List examApplications = getExamApplications(applications); - - Map examMap = getExamMap(examApplications); - - Map> subjectMap = getSubjectMap(examApplications); - - Map> examResponsesGroupedByApplicationId = - groupExamResponsesByApplication(examApplications, examMap, subjectMap); - - return applications.stream() - .map(app -> ApplicationResponse.of( - app.getId(), - examResponsesGroupedByApplicationId.getOrDefault(app.getId(), List.of()) - )) - .toList(); - } - - private void validateExamIdsAndLunchSelection(List requests) { - - List examIds = requests.stream() - .map(ExamApplicationRequest::examId).toList(); - - List existingExams = examJpaRepository.findAllById(examIds); - if (existingExams.size() != examIds.size()) { - throw new CustomRuntimeException(ErrorCode.EXAM_NOT_FOUND); - } - - Set examsWithoutLunch = existingExams.stream() - .filter(exam -> exam.getLunchName() == null) - .map(ExamJpaEntity::getId) - .collect(Collectors.toSet()); - - boolean invalidLunchSelection = requests.stream() - .anyMatch(req -> examsWithoutLunch.contains(req.examId()) && req.isLunchChecked()); - - if (invalidLunchSelection) { - throw new CustomRuntimeException(ErrorCode.LUNCH_SELECTION_INVALID); - } - } - - private List getUserApplications(Long userId) { List applications = applicationJpaRepository.findAllByUserId(userId); if (applications.isEmpty()) { - throw new CustomRuntimeException(ErrorCode.APPLICATION_LIST_NOT_FOUND); - } - return applications; - } - - private List getExamApplications( - List applications) { - List applicationIds = applications.stream() - .map(ApplicationJpaEntity::getId) - .toList(); - - List examApplications = - examApplicationJpaRepository.findByApplicationIdIn(applicationIds); - - if (examApplications.isEmpty()) { - throw new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND); + return List.of(); } - return examApplications; - } - - private Map getExamMap(List examApplications) { - List examIds = examApplications.stream() - .map(ExamApplicationJpaEntity::getExamId) - .distinct() - .toList(); - return examJpaRepository.findByIdIn(examIds).stream() - .collect(Collectors.toMap(ExamJpaEntity::getId, Function.identity())); - } - - private Map getPaymentMap( - List examApplications) { - List examApplicationIds = examApplications.stream() - .map(ExamApplicationJpaEntity::getId) - .toList(); + List applicationIds = applications.stream().map(ApplicationJpaEntity::getId).toList(); + List examApplications = examApplicationJpaRepository.findByApplicationIdIn( + applicationIds); - return paymentJpaRepository.findByExamApplicationIdIn(examApplicationIds).stream() - .collect(Collectors.toMap(PaymentJpaEntity::getExamApplicationId, - Function.identity())); + return new ApplicationContext(applications, examApplications) + .fetchExams(examJpaRepository::findByIdIn) + .fetchSubjects(examSubjectJpaRepository::findByExamApplicationIdIn) + .fetchPayments(paymentJpaRepository::findByExamApplicationIdIn) + .assemble(); } +} - private Map> getSubjectMap( - List examApplications) { - List examApplicationIds = examApplications.stream() - .map(ExamApplicationJpaEntity::getId) - .toList(); - - return examSubjectJpaRepository.findByExamApplicationIdIn(examApplicationIds).stream() - .collect(Collectors.groupingBy(ExamSubjectJpaEntity::getExamApplicationId)); - } - - private Map> groupExamResponsesByApplication( - List examApplications, - Map examMap, - Map> subjectMap - ) { - Map paymentMap = getPaymentMap(examApplications); - - return examApplications.stream() - .map(examApplication -> { - Long examApplicationId = examApplication.getId(); - ExamJpaEntity exam = examMap.get(examApplication.getExamId()); - - Set subjects = subjectMap.getOrDefault(examApplicationId, List.of()) - .stream() - .map(es -> es.getSubject().getSubjectName()) - .collect(Collectors.toSet()); - - PaymentJpaEntity payment = paymentMap.get(examApplicationId); - String paymentStatus = - (payment != null) ? payment.getPaymentStatus().name() : null; - Integer totalAmount = (payment != null && payment.getPaymentAmount() != null) - ? payment.getPaymentAmount().getTotalAmount() - : 0; - - ExamApplicationResponse response = ExamApplicationResponse.of( - examApplicationId, - examApplication.getCreatedAt(), - paymentStatus, - totalAmount, - exam.getSchoolName(), - exam.getExamDate(), - subjects, - examApplication.getIsLunchChecked() ? exam.getLunchName() : "신청 안 함" - ); - - return Map.entry(examApplication.getApplicationId(), response); - }) - .collect(Collectors.groupingBy( - Map.Entry::getKey, - Collectors.mapping(Map.Entry::getValue, Collectors.toList()) - )); - } -} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/application/application/RegisterApplicationStepProcessor.java b/src/main/java/life/mosu/mosuserver/application/application/RegisterApplicationStepProcessor.java new file mode 100644 index 00000000..b051186f --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/RegisterApplicationStepProcessor.java @@ -0,0 +1,43 @@ +package life.mosu.mosuserver.application.application; + +import jakarta.transaction.Transactional; +import java.util.List; +import life.mosu.mosuserver.application.application.dto.RegisterApplicationCommand; +import life.mosu.mosuserver.application.examapplication.ExamApplicationService; +import life.mosu.mosuserver.application.examapplication.dto.RegisterExamApplicationEvent; +import life.mosu.mosuserver.domain.application.ApplicationJpaRepository; +import life.mosu.mosuserver.domain.examapplication.ExamApplicationJpaEntity; +import life.mosu.mosuserver.global.processor.StepProcessor; +import life.mosu.mosuserver.infra.persistence.jpa.ExamApplicationBulkRepository; +import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class RegisterApplicationStepProcessor implements StepProcessor { + + private final ApplicationJpaRepository applicationJpaRepository; + private final ExamApplicationService examApplicationService; + private final ExamApplicationBulkRepository examApplicationBulkRepository; + + @Override + @Transactional + public RegisterApplicationCommand process(RegisterApplicationCommand command) { + final List examApplicationRequests = command.applicationRequest() + .examApplication(); + final Long applicationId = command.applicationId(); + + List examApplicationEntities = examApplicationService.register( + RegisterExamApplicationEvent.of(examApplicationRequests, + applicationId) + ); + + // 시험 신청 목록과 과목 multi-insert + examApplicationBulkRepository.saveAllExamApplicationsWithSubjects(examApplicationEntities, + command.subjects()); + return command; + } + + +} diff --git a/src/main/java/life/mosu/mosuserver/application/application/dto/AppExam.java b/src/main/java/life/mosu/mosuserver/application/application/dto/AppExam.java new file mode 100644 index 00000000..88227073 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/dto/AppExam.java @@ -0,0 +1,13 @@ +package life.mosu.mosuserver.application.application.dto; + +import life.mosu.mosuserver.domain.exam.ExamJpaEntity; + +public record AppExam( + Long examApplicationId, + ExamJpaEntity exam +) { + + public static AppExam of(Long examApplicationId, ExamJpaEntity exam) { + return new AppExam(examApplicationId, exam); + } +} diff --git a/src/main/java/life/mosu/mosuserver/application/application/dto/AppExamSubject.java b/src/main/java/life/mosu/mosuserver/application/application/dto/AppExamSubject.java new file mode 100644 index 00000000..62680582 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/dto/AppExamSubject.java @@ -0,0 +1,17 @@ +package life.mosu.mosuserver.application.application.dto; + +import java.util.List; +import life.mosu.mosuserver.domain.examapplication.ExamSubjectJpaEntity; + +public record AppExamSubject( + Long examApplicationId, + List examSubjects +) { + + public static AppExamSubject of( + Long examApplicationId, + List examSubjects) { + return new AppExamSubject(examApplicationId, examSubjects); + } + +} diff --git a/src/main/java/life/mosu/mosuserver/application/application/dto/AppPayment.java b/src/main/java/life/mosu/mosuserver/application/application/dto/AppPayment.java new file mode 100644 index 00000000..83510755 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/dto/AppPayment.java @@ -0,0 +1,14 @@ +package life.mosu.mosuserver.application.application.dto; + +import life.mosu.mosuserver.domain.payment.PaymentJpaEntity; + +public record AppPayment( + Long examApplicationId, + PaymentJpaEntity payment +) { + + public static AppPayment of(Long examApplicationId, PaymentJpaEntity payment) { + return new AppPayment(examApplicationId, payment); + } + +} diff --git a/src/main/java/life/mosu/mosuserver/application/application/dto/RegisterApplicationCommand.java b/src/main/java/life/mosu/mosuserver/application/application/dto/RegisterApplicationCommand.java new file mode 100644 index 00000000..70dcc6b6 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/dto/RegisterApplicationCommand.java @@ -0,0 +1,20 @@ +package life.mosu.mosuserver.application.application.dto; + +import java.util.Set; +import life.mosu.mosuserver.domain.application.Subject; +import life.mosu.mosuserver.presentation.application.dto.ApplicationRequest; + +public record RegisterApplicationCommand( + Long applicationId, + ApplicationRequest applicationRequest, + Set subjects +) { + + public static RegisterApplicationCommand of( + Long applicationId, + ApplicationRequest applicationRequest, + Set subjects + ) { + return new RegisterApplicationCommand(applicationId, applicationRequest, subjects); + } +} diff --git a/src/main/java/life/mosu/mosuserver/application/application/processor/SaveApplicationStepProcessor.java b/src/main/java/life/mosu/mosuserver/application/application/processor/SaveApplicationStepProcessor.java new file mode 100644 index 00000000..276b8e6d --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/processor/SaveApplicationStepProcessor.java @@ -0,0 +1,12 @@ +package life.mosu.mosuserver.application.application.processor; + +import life.mosu.mosuserver.application.application.ApplicationProcessingContext; +import life.mosu.mosuserver.global.processor.StepProcessor; + +public class SaveApplicationStepProcessor implements StepProcessor { + + @Override + public ApplicationProcessingContext process(ApplicationProcessingContext request) { + return null; + } +} diff --git a/src/main/java/life/mosu/mosuserver/application/application/processor/SaveExamTicketStepProcessor.java b/src/main/java/life/mosu/mosuserver/application/application/processor/SaveExamTicketStepProcessor.java new file mode 100644 index 00000000..59d96c81 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/processor/SaveExamTicketStepProcessor.java @@ -0,0 +1,31 @@ +package life.mosu.mosuserver.application.application.processor; + +import life.mosu.mosuserver.application.application.ApplicationProcessingContext; +import life.mosu.mosuserver.domain.application.ExamTicketImageJpaEntity; +import life.mosu.mosuserver.domain.application.ExamTicketImageJpaRepository; +import life.mosu.mosuserver.global.processor.StepProcessor; +import life.mosu.mosuserver.presentation.common.FileRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class SaveExamTicketStepProcessor implements StepProcessor { + + private final ExamTicketImageJpaRepository examTicketImageJpaRepository; + + @Override + public ApplicationProcessingContext process(ApplicationProcessingContext context) { + Long applicationId = context.applicationId(); + FileRequest fileReq = context.fileRequest(); + + if (fileReq.fileName() != null && fileReq.s3Key() != null) { + ExamTicketImageJpaEntity examTicketImage = fileReq + .toExamTicketImageEntity(applicationId); + + examTicketImageJpaRepository.save(examTicketImage); + } + return context; + } + +} diff --git a/src/main/java/life/mosu/mosuserver/application/application/stream/IdStream.java b/src/main/java/life/mosu/mosuserver/application/application/stream/IdStream.java new file mode 100644 index 00000000..1be3defd --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/stream/IdStream.java @@ -0,0 +1,11 @@ +package life.mosu.mosuserver.application.application.stream; + +import java.util.function.Function; +import life.mosu.mosuserver.domain.examapplication.ExamSubjectJpaEntity; + +@FunctionalInterface +public interface IdStream extends Function { + + Long apply(ExamSubjectJpaEntity examSubject); + +} 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 new file mode 100644 index 00000000..b25d7581 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/vaildator/ApplicationValidator.java @@ -0,0 +1,69 @@ +package life.mosu.mosuserver.application.application.vaildator; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import life.mosu.mosuserver.domain.application.ApplicationJpaRepository; +import life.mosu.mosuserver.domain.exam.ExamJpaEntity; +import life.mosu.mosuserver.domain.exam.ExamJpaRepository; +import life.mosu.mosuserver.global.exception.CustomRuntimeException; +import life.mosu.mosuserver.global.exception.ErrorCode; +import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; + +@Component +@RequiredArgsConstructor +public class ApplicationValidator { + + private final ExamJpaRepository examJpaRepository; + private final ApplicationJpaRepository applicationJpaRepository; + + public void RequestNoDuplicateExams(List examIds) { + Set examIdSet = new HashSet<>(examIds); + if (examIds.size() != examIdSet.size()) { + throw new CustomRuntimeException(ErrorCode.EXAM_DUPLICATED); + } + } + + public void ExamIdsAndLunchSelection(List requests) { + if (requests == null || requests.isEmpty()) { + throw new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND); + } + + List requestedExamIds = requests.stream() + .map(ExamApplicationRequest::examId) + .toList(); + + List existingExams = examJpaRepository.findAllById(requestedExamIds); + + if (existingExams.size() != requestedExamIds.size()) { + throw new CustomRuntimeException(ErrorCode.EXAM_NOT_FOUND); + } + + LunchSelection(requests, existingExams); + } + + private void LunchSelection(List requests, + List exams) { + Set examsWithoutLunch = exams.stream() + .filter(exam -> !exam.providesLunch()) + .map(ExamJpaEntity::getId) + .collect(Collectors.toSet()); + + boolean hasInvalidLunchRequest = requests.stream() + .anyMatch(req -> examsWithoutLunch.contains(req.examId()) && req.isLunchChecked()); + + if (hasInvalidLunchRequest) { + throw new CustomRuntimeException(ErrorCode.LUNCH_SELECTION_INVALID); + } + } + + public void NoDuplicateApplication(Long userId, List examIds) { + boolean alreadyApplied = applicationJpaRepository.existsByUserIdAndExamIds(userId, examIds); + if (alreadyApplied) { + throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_DUPLICATED); + } + } +} \ No newline at end of file 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 0732ef75..13a6145e 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 @@ -4,6 +4,7 @@ import life.mosu.mosuserver.domain.user.UserJpaRepository; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; +import life.mosu.mosuserver.global.processor.StepProcessor; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/life/mosu/mosuserver/application/auth/processor/SignUpProfileStepProcessor.java b/src/main/java/life/mosu/mosuserver/application/auth/processor/SignUpProfileStepProcessor.java index d7a7305f..baaf6a3e 100644 --- a/src/main/java/life/mosu/mosuserver/application/auth/processor/SignUpProfileStepProcessor.java +++ b/src/main/java/life/mosu/mosuserver/application/auth/processor/SignUpProfileStepProcessor.java @@ -4,6 +4,7 @@ import life.mosu.mosuserver.application.user.UserService; import life.mosu.mosuserver.domain.profile.ProfileJpaEntity; import life.mosu.mosuserver.domain.user.UserJpaEntity; +import life.mosu.mosuserver.global.processor.StepProcessor; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/life/mosu/mosuserver/application/exam/cache/ExamQuotaCacheManager.java b/src/main/java/life/mosu/mosuserver/application/exam/cache/ExamQuotaCacheManager.java index fa1fae28..3a0ce987 100644 --- a/src/main/java/life/mosu/mosuserver/application/exam/cache/ExamQuotaCacheManager.java +++ b/src/main/java/life/mosu/mosuserver/application/exam/cache/ExamQuotaCacheManager.java @@ -1,11 +1,8 @@ package life.mosu.mosuserver.application.exam.cache; import java.time.LocalDate; -import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.function.Function; -import java.util.stream.Collectors; import life.mosu.mosuserver.domain.exam.ExamJpaRepository; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; @@ -16,7 +13,6 @@ import life.mosu.mosuserver.infra.persistence.redis.operator.CacheWriter; import life.mosu.mosuserver.infra.persistence.redis.operator.VoidCacheAtomicOperator; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.stereotype.Component; import org.springframework.transaction.annotation.Transactional; @@ -25,6 +21,7 @@ @Transactional @Slf4j public class ExamQuotaCacheManager extends KeyValueCacheManager { + private final ExamJpaRepository examJpaRepository; public ExamQuotaCacheManager( @@ -33,7 +30,7 @@ public ExamQuotaCacheManager( CacheReader cacheReader, @Qualifier("examCacheAtomicOperatorMap") - Map> cacheAtomicOperatorMap, + Map> cacheAtomicOperatorMap, ExamJpaRepository examJpaRepository ) { super(cacheLoader, cacheWriter, cacheReader, cacheAtomicOperatorMap); diff --git a/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaEntity.java index a4cadeeb..58bb855f 100644 --- a/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaEntity.java @@ -41,7 +41,6 @@ public ApplicationJpaEntity( final String parentPhoneNumber, final boolean agreedToNotices, final boolean agreedToRefundPolicy - ) { this.userId = userId; this.parentPhoneNumber = parentPhoneNumber; diff --git a/src/main/java/life/mosu/mosuserver/domain/application/Subject.java b/src/main/java/life/mosu/mosuserver/domain/application/Subject.java index acab6b2c..619094a6 100644 --- a/src/main/java/life/mosu/mosuserver/domain/application/Subject.java +++ b/src/main/java/life/mosu/mosuserver/domain/application/Subject.java @@ -1,5 +1,8 @@ package life.mosu.mosuserver.domain.application; +import java.util.Arrays; +import life.mosu.mosuserver.global.exception.CustomRuntimeException; +import life.mosu.mosuserver.global.exception.ErrorCode; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -22,7 +25,7 @@ public enum Subject { WORLD_HISTORY("세계사"), ECONOMICS("경제"), POLITICS_AND_LAW("정치와 법"), - SOCIETY_AND_CULTURE("사회・문화"), + SOCIETY_AND_CULTURE("사회와 문화"), // 과학탐구 PHYSICS_1("물리학Ⅰ"), @@ -35,4 +38,11 @@ public enum Subject { EARTH_SCIENCE_2("지구과학Ⅱ"); private final String subjectName; + + public static Subject getSubject(String subjectName) { + return Arrays.stream(values()) + .filter(subject -> subject.subjectName.equals(subjectName)) + .findFirst() + .orElseThrow(() -> new CustomRuntimeException(ErrorCode.WRONG_SUBJECT_TYPE)); + } } diff --git a/src/main/java/life/mosu/mosuserver/domain/exam/ExamJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/exam/ExamJpaEntity.java index b35663f9..806e0645 100644 --- a/src/main/java/life/mosu/mosuserver/domain/exam/ExamJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/exam/ExamJpaEntity.java @@ -71,4 +71,8 @@ public ExamJpaEntity( this.lunchName = lunchName; this.lunchPrice = lunchPrice; } + + public boolean providesLunch() { + return lunchName != null; + } } 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 a12c4825..e8364b84 100644 --- a/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java +++ b/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java @@ -49,7 +49,7 @@ public enum ErrorCode { SCHOOL_FULL(HttpStatus.CONFLICT, "해당 학교의 신청 정원이 모두 찼습니다."), APPLICATION_SCHOOL_ALREADY_APPLIED(HttpStatus.CONFLICT, "해당 학교를 이미 예약하였습니다."), APPLICATION_SCHOOL_DUPLICATED(HttpStatus.BAD_REQUEST, "동일 일자의 같은 학교를 신청할 수 없습니다."), - + EXAM_DUPLICATED(HttpStatus.BAD_REQUEST, "동일한 시험을 신청할 수 없습니다."), // 프로필 관련 에러 PROFILE_ALREADY_EXISTS(HttpStatus.CONFLICT, "프로필이 이미 존재합니다."), PROFILE_NOT_FOUND(HttpStatus.NOT_FOUND, "프로필을 찾을 수 없습니다."), @@ -99,7 +99,6 @@ public enum ErrorCode { EXAM_APPLICATION_MULTI_INSERT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "신청 학교 정보 삽입 실패하였습니다."), EXAM_SUBJECT_MULTI_INSERT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "응시 과목 정보 삽입 실패하였습니다."), - // 시험 관련 에러 EXAM_NOT_FOUND(HttpStatus.NOT_FOUND, "시험 정보를 찾을 수 없습니다."), @@ -117,7 +116,7 @@ public enum ErrorCode { BANNER_DELETE_FAILURE(HttpStatus.CONFLICT, "배너를 삭제하는 것에 실패하였습니다."), EXCEL_DATA_EMPTY(HttpStatus.NOT_FOUND, "엑셀 데이터가 없습니다."), EXCEL_DOWNLOAD_FAILURE(HttpStatus.CONFLICT, "엑셀 다운로드 중 문제가 발생했습니다."), - CACHE_UPDATE_FAIL(HttpStatus.CONFLICT, "캐시 업데이트에 실패하였습니다."),; + CACHE_UPDATE_FAIL(HttpStatus.CONFLICT, "캐시 업데이트에 실패하였습니다."); private final HttpStatus status; private final String message; diff --git a/src/main/java/life/mosu/mosuserver/application/auth/processor/StepProcessor.java b/src/main/java/life/mosu/mosuserver/global/processor/StepProcessor.java similarity index 53% rename from src/main/java/life/mosu/mosuserver/application/auth/processor/StepProcessor.java rename to src/main/java/life/mosu/mosuserver/global/processor/StepProcessor.java index 611815e0..6247ed75 100644 --- a/src/main/java/life/mosu/mosuserver/application/auth/processor/StepProcessor.java +++ b/src/main/java/life/mosu/mosuserver/global/processor/StepProcessor.java @@ -1,4 +1,4 @@ -package life.mosu.mosuserver.application.auth.processor; +package life.mosu.mosuserver.global.processor; public interface StepProcessor { diff --git a/src/main/java/life/mosu/mosuserver/presentation/a/Test.java b/src/main/java/life/mosu/mosuserver/presentation/a/Test.java new file mode 100644 index 00000000..26210a86 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/a/Test.java @@ -0,0 +1,21 @@ +package life.mosu.mosuserver.presentation.a; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Stream; + +class Item { + +} + +class Cart { + + private final List items = new ArrayList<>(); + Stream +} + +public class Test { + + List items; + Cart cart; +} 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 0f62dab2..17b219f2 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 @@ -55,15 +55,10 @@ public ApplicationJpaEntity toApplicationJpaEntity(Long userId) { // return schoolIds; // } - public Set validatedSubjects() { - Set subjectSet; - try { - subjectSet = subjects.stream() - .map(Subject::valueOf) - .collect(Collectors.toSet()); - } catch (IllegalArgumentException e) { - throw new CustomRuntimeException(ErrorCode.WRONG_SUBJECT_TYPE); - } + public Set getSubjects() { + Set subjectSet = subjects.stream() + .map(Subject::getSubject) + .collect(Collectors.toSet()); if (subjectSet.size() != 2) { throw new CustomRuntimeException(ErrorCode.WRONG_SUBJECT_COUNT); @@ -71,4 +66,19 @@ public Set validatedSubjects() { return subjectSet; } +// Set subjectSet; +// try { +// subjectSet = subjects.stream() +// .map(Subject::valueOf) +// .collect(Collectors.toSet()); +// } catch (IllegalArgumentException e) { +// throw new CustomRuntimeException(ErrorCode.WRONG_SUBJECT_TYPE); +// } +// +// if (subjectSet.size() != 2) { +// throw new CustomRuntimeException(ErrorCode.WRONG_SUBJECT_COUNT); +// } +// +// return subjectSet; +// } }