From 04bedf6cf72eda19a875cfdb2c41bc97de4c473b Mon Sep 17 00:00:00 2001 From: chominju02 Date: Sat, 26 Jul 2025 02:01:42 +0900 Subject: [PATCH 1/2] MOSU cache: 130 --- .../ApplicationProcessingContext.java | 20 +++ .../application/ApplicationService.java | 130 ++++++++++-------- .../RegisterApplicationStepProcessor.java | 45 ++++++ .../application/application/dto/AppExam.java | 13 ++ .../application/dto/AppExamSubject.java | 17 +++ .../application/dto/AppPayment.java | 14 ++ .../dto/RegisterApplicationCommand.java | 20 +++ .../SaveApplicationStepProcessor.java | 12 ++ .../SaveExamTicketStepProcessor.java | 31 +++++ .../application/exam/ExamService.java | 17 ++- .../domain/application/Subject.java | 12 +- .../mosuserver/domain/exam/ExamJpaEntity.java | 4 + .../application/dto/ApplicationRequest.java | 28 ++-- .../presentation/exam/dto/ExamResponse.java | 13 +- 14 files changed, 299 insertions(+), 77 deletions(-) create mode 100644 src/main/java/life/mosu/mosuserver/application/application/ApplicationProcessingContext.java create mode 100644 src/main/java/life/mosu/mosuserver/application/application/RegisterApplicationStepProcessor.java create mode 100644 src/main/java/life/mosu/mosuserver/application/application/dto/AppExam.java create mode 100644 src/main/java/life/mosu/mosuserver/application/application/dto/AppExamSubject.java create mode 100644 src/main/java/life/mosu/mosuserver/application/application/dto/AppPayment.java create mode 100644 src/main/java/life/mosu/mosuserver/application/application/dto/RegisterApplicationCommand.java create mode 100644 src/main/java/life/mosu/mosuserver/application/application/processor/SaveApplicationStepProcessor.java create mode 100644 src/main/java/life/mosu/mosuserver/application/application/processor/SaveExamTicketStepProcessor.java 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..3a3f9dfc 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java +++ b/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java @@ -1,11 +1,13 @@ 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.application.dto.AppExam; +import life.mosu.mosuserver.application.application.dto.AppExamSubject; +import life.mosu.mosuserver.application.application.dto.AppPayment; +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.ApplicationJpaEntity; @@ -49,50 +51,35 @@ public class ApplicationService { private final ExamJpaRepository examJpaRepository; private final ExamApplicationBulkRepository examApplicationBulkRepository; private final PaymentJpaRepository paymentJpaRepository; + private final RegisterApplicationStepProcessor registerApplicationStepProcessor; +// private final SaveExamTicketStepProcessor saveExamTicketStepProcessor; @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); - } + Set subjects = request.getSubjects(); //해당 시험이 진짜 있는 일정인지, lunch 가 없는 시험인데 lunch 를 신청했는지 - validateExamIdsAndLunchSelection(examApplicationRequests); - - boolean isDuplicate = applicationJpaRepository.existsByUserIdAndExamIds(userId, examIds); - if (isDuplicate) { - throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_DUPLICATED); - } - - ApplicationJpaEntity application = request.toApplicationJpaEntity(userId); - ApplicationJpaEntity savedApplication = applicationJpaRepository.save(application); - Long applicationId = savedApplication.getId(); + validateExamIdsAndLunchSelection(request.examApplication()); - List examApplicationEntities = examApplicationService.register( - RegisterExamApplicationEvent.of(request.examApplication(), applicationId) - ); + validateNoDuplicateApplication(userId, examIds); - // 시험 신청 목록과 과목 multi-insert - examApplicationBulkRepository.saveAllExamApplicationsWithSubjects(examApplicationEntities, - subjects); + /** + * 여기부터 진짜 신청 로직 + */ + registerApplicationStepProcessor.process(RegisterApplicationCommand.of(userId, request, subjects)); + /** + * 수험표 저장 로직 + */ // 수험표 저장 +// saveExamTicketStepProcessor.process(context); + FileRequest fileReq = request.admissionTicket(); if (fileReq.fileName() != null && fileReq.s3Key() != null) { ExamTicketImageJpaEntity examTicketImage = fileReq @@ -112,9 +99,11 @@ public List getApplications(Long userId) { List examApplications = getExamApplications(applications); - Map examMap = getExamMap(examApplications); + List exams = getExams(examApplications); + + List payments = getPayments(examApplications); - Map> subjectMap = getSubjectMap(examApplications); + List<> subjects = getSubjects(examApplications); Map> examResponsesGroupedByApplicationId = groupExamResponsesByApplication(examApplications, examMap, subjectMap); @@ -127,35 +116,52 @@ public List getApplications(Long userId) { .toList(); } + private List getUserApplications(Long userId) { + List applications = applicationJpaRepository.findAllByUserId(userId); + if (applications.isEmpty()) { + throw new CustomRuntimeException(ErrorCode.APPLICATION_LIST_NOT_FOUND); + } + return applications; + } + private void validateExamIdsAndLunchSelection(List requests) { + if (requests == null || requests.isEmpty()) { + throw new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND); + } - List examIds = requests.stream() - .map(ExamApplicationRequest::examId).toList(); + List requestedExamIds = requests.stream() + .map(ExamApplicationRequest::examId) + .toList(); - List existingExams = examJpaRepository.findAllById(examIds); - if (existingExams.size() != examIds.size()) { + List existingExams = examJpaRepository.findAllById(requestedExamIds); + + if (existingExams.size() != requestedExamIds.size()) { throw new CustomRuntimeException(ErrorCode.EXAM_NOT_FOUND); } - Set examsWithoutLunch = existingExams.stream() - .filter(exam -> exam.getLunchName() == null) + validateLunchSelection(requests, existingExams); + } + + private void validateLunchSelection(List requests, + List exams) { + Set examsWithoutLunch = exams.stream() + .filter(exam -> !exam.providesLunch()) .map(ExamJpaEntity::getId) .collect(Collectors.toSet()); - boolean invalidLunchSelection = requests.stream() + boolean hasInvalidLunchRequest = requests.stream() .anyMatch(req -> examsWithoutLunch.contains(req.examId()) && req.isLunchChecked()); - if (invalidLunchSelection) { + if (hasInvalidLunchRequest) { 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); + private void validateNoDuplicateApplication(Long userId, List examIds) { + boolean alreadyApplied = applicationJpaRepository.existsByUserIdAndExamIds(userId, examIds); + if (alreadyApplied) { + throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_DUPLICATED); } - return applications; } private List getExamApplications( @@ -173,48 +179,58 @@ private List getExamApplications( return examApplications; } - private Map getExamMap(List examApplications) { + private List getExams(List examApplications) { List examIds = examApplications.stream() .map(ExamApplicationJpaEntity::getExamId) .distinct() .toList(); return examJpaRepository.findByIdIn(examIds).stream() - .collect(Collectors.toMap(ExamJpaEntity::getId, Function.identity())); + .map(exam -> AppExam.of( + exam.getId(), + exam + )) + .toList(); } - private Map getPaymentMap( + private List getPayments( List examApplications) { List examApplicationIds = examApplications.stream() .map(ExamApplicationJpaEntity::getId) .toList(); return paymentJpaRepository.findByExamApplicationIdIn(examApplicationIds).stream() - .collect(Collectors.toMap(PaymentJpaEntity::getExamApplicationId, - Function.identity())); + .map(payment -> AppPayment.of(payment.getExamApplicationId(), payment)) + .toList(); } - private Map> getSubjectMap( + private Map> getSubjects( List examApplications) { List examApplicationIds = examApplications.stream() .map(ExamApplicationJpaEntity::getId) .toList(); return examSubjectJpaRepository.findByExamApplicationIdIn(examApplicationIds).stream() + .map( + + ) .collect(Collectors.groupingBy(ExamSubjectJpaEntity::getExamApplicationId)); } private Map> groupExamResponsesByApplication( List examApplications, - Map examMap, - Map> subjectMap + List exams, + List subjects, + List payments ) { - Map paymentMap = getPaymentMap(examApplications); +// Map paymentMap = getPaymentMap(examApplications); return examApplications.stream() .map(examApplication -> { Long examApplicationId = examApplication.getId(); - ExamJpaEntity exam = examMap.get(examApplication.getExamId()); + ExamJpaEntity exam = exams.stream() + .findFirst( + ); Set subjects = subjectMap.getOrDefault(examApplicationId, List.of()) .stream() 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..530cd1c2 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/application/RegisterApplicationStepProcessor.java @@ -0,0 +1,45 @@ +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.auth.processor.StepProcessor; +import life.mosu.mosuserver.application.examapplication.ExamApplicationService; +import life.mosu.mosuserver.application.examapplication.dto.RegisterExamApplicationEvent; +import life.mosu.mosuserver.domain.application.ApplicationJpaEntity; +import life.mosu.mosuserver.domain.application.ApplicationJpaRepository; +import life.mosu.mosuserver.domain.examapplication.ExamApplicationJpaEntity; +import life.mosu.mosuserver.infra.persistence.jpa.ExamApplicationBulkRepository; +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) { + ApplicationJpaEntity application = command.applicationRequest() + .toApplicationJpaEntity(command.userId()); + ApplicationJpaEntity savedApplication = applicationJpaRepository.save(application); + + Long applicationId = savedApplication.getId(); + + List examApplicationEntities = examApplicationService.register( + RegisterExamApplicationEvent.of(command.applicationRequest().examApplication(), + 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..368eba53 --- /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 userId, + ApplicationRequest applicationRequest, + Set subjects +) { + + public static RegisterApplicationCommand of( + Long userId, + ApplicationRequest applicationRequest, + Set subjects + ) { + return new RegisterApplicationCommand(userId, 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..4351c552 --- /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.application.auth.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..f05b3cb7 --- /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.application.auth.processor.StepProcessor; +import life.mosu.mosuserver.domain.application.ExamTicketImageJpaEntity; +import life.mosu.mosuserver.domain.application.ExamTicketImageJpaRepository; +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/exam/ExamService.java b/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java index 57ee203c..7b5c9c6d 100644 --- a/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java +++ b/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java @@ -33,7 +33,14 @@ public void register(ExamRequest request) { public List getByArea(String areaName) { Area area = Area.from(areaName); List foundExams = examJpaRepository.findByArea(area); - return ExamResponse.fromList(foundExams); + + return foundExams.stream() + .map(exam -> { + Long count = examQuotaCacheManager.getSchoolApplicationCounts( + exam.getSchoolName()); + return ExamResponse.of(exam, count); + }) + .toList(); } public List getDistinctAreas() { @@ -44,7 +51,13 @@ public List getDistinctAreas() { public List getExams() { List exams = examJpaRepository.findAll(); - return ExamResponse.fromList(exams); + return exams.stream() + .map(exam -> { + Long count = examQuotaCacheManager.getSchoolApplicationCounts( + exam.getSchoolName()); + return ExamResponse.of(exam, count); + }) + .toList(); } } 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/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; +// } } diff --git a/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamResponse.java b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamResponse.java index 2cb446e1..770915c7 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamResponse.java @@ -2,7 +2,6 @@ import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.List; import life.mosu.mosuserver.domain.exam.ExamJpaEntity; import life.mosu.mosuserver.presentation.common.AddressResponse; @@ -11,13 +10,14 @@ public record ExamResponse( String schoolName, AddressResponse address, String area, - Integer capacity, + Long currentQuota, + Integer maxQuota, LocalDateTime deadlineTime, LocalDate examDate, LunchResponse lunch ) { - public static ExamResponse from(ExamJpaEntity exam) { + public static ExamResponse of(ExamJpaEntity exam, Long currentQuota) { AddressResponse address = AddressResponse.from(exam.getAddress()); LunchResponse lunch = LunchResponse.of(exam.getLunchName(), exam.getLunchPrice()); return new ExamResponse( @@ -25,6 +25,7 @@ public static ExamResponse from(ExamJpaEntity exam) { exam.getSchoolName(), address, exam.getArea().getAreaName(), + currentQuota, exam.getCapacity(), exam.getDeadlineTime(), exam.getExamDate(), @@ -32,9 +33,5 @@ public static ExamResponse from(ExamJpaEntity exam) { ); } - public static List fromList(List foundExams) { - return foundExams.stream() - .map(ExamResponse::from) - .toList(); - } + } From fba39e47fefa6b8bf208c1315fe0a8fd6b5e41b8 Mon Sep 17 00:00:00 2001 From: chominju02 Date: Sat, 26 Jul 2025 11:17:55 +0900 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=EC=8B=A0=EC=B2=AD=20=EB=A6=AC?= =?UTF-8?q?=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../application/ApplicationContext.java | 118 +++++++++ .../application/ApplicationService.java | 227 ++---------------- .../RegisterApplicationStepProcessor.java | 14 +- .../dto/RegisterApplicationCommand.java | 6 +- .../SaveApplicationStepProcessor.java | 2 +- .../SaveExamTicketStepProcessor.java | 2 +- .../application/stream/IdStream.java | 11 + .../vaildator/ApplicationValidator.java | 69 ++++++ .../processor/SignUpAccountStepProcessor.java | 1 + .../processor/SignUpProfileStepProcessor.java | 1 + .../exam/cache/ExamQuotaCacheManager.java | 7 +- .../application/ApplicationJpaEntity.java | 1 - .../global/exception/ErrorCode.java | 5 +- .../processor/StepProcessor.java | 2 +- .../mosu/mosuserver/presentation/a/Test.java | 21 ++ 15 files changed, 262 insertions(+), 225 deletions(-) create mode 100644 src/main/java/life/mosu/mosuserver/application/application/ApplicationContext.java create mode 100644 src/main/java/life/mosu/mosuserver/application/application/stream/IdStream.java create mode 100644 src/main/java/life/mosu/mosuserver/application/application/vaildator/ApplicationValidator.java rename src/main/java/life/mosu/mosuserver/{application/auth => global}/processor/StepProcessor.java (53%) create mode 100644 src/main/java/life/mosu/mosuserver/presentation/a/Test.java 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/ApplicationService.java b/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java index 3a3f9dfc..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,37 +1,22 @@ package life.mosu.mosuserver.application.application; import java.util.List; -import java.util.Map; import java.util.Set; -import java.util.stream.Collectors; -import life.mosu.mosuserver.application.application.dto.AppExam; -import life.mosu.mosuserver.application.application.dto.AppExamSubject; -import life.mosu.mosuserver.application.application.dto.AppPayment; 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.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; @@ -44,15 +29,13 @@ 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 SaveExamTicketStepProcessor saveExamTicketStepProcessor; + private final ApplicationValidator validator; @Transactional @@ -64,202 +47,42 @@ public CreateApplicationResponse apply(Long userId, ApplicationRequest request) Set subjects = request.getSubjects(); - //해당 시험이 진짜 있는 일정인지, lunch 가 없는 시험인데 lunch 를 신청했는지 - validateExamIdsAndLunchSelection(request.examApplication()); + // 해당 시험이 진짜 있는 일정인지, lunch 가 없는 시험인데 lunch 를 신청했는지 + validator.RequestNoDuplicateExams(examIds); + validator.ExamIdsAndLunchSelection(request.examApplication()); + validator.NoDuplicateApplication(userId, examIds); - validateNoDuplicateApplication(userId, examIds); + ApplicationJpaEntity application = request.toApplicationJpaEntity(userId); + ApplicationJpaEntity savedApplication = applicationJpaRepository.save(application); - /** - * 여기부터 진짜 신청 로직 - */ - registerApplicationStepProcessor.process(RegisterApplicationCommand.of(userId, request, subjects)); + Long applicationId = savedApplication.getId(); - /** - * 수험표 저장 로직 - */ - // 수험표 저장 -// saveExamTicketStepProcessor.process(context); + 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); - - List exams = getExams(examApplications); - - List payments = getPayments(examApplications); - - List<> subjects = getSubjects(examApplications); - - Map> examResponsesGroupedByApplicationId = - groupExamResponsesByApplication(examApplications, examMap, subjectMap); - - return applications.stream() - .map(app -> ApplicationResponse.of( - app.getId(), - examResponsesGroupedByApplicationId.getOrDefault(app.getId(), List.of()) - )) - .toList(); - } - - private List getUserApplications(Long userId) { List applications = applicationJpaRepository.findAllByUserId(userId); if (applications.isEmpty()) { - throw new CustomRuntimeException(ErrorCode.APPLICATION_LIST_NOT_FOUND); - } - return applications; - } - - private void validateExamIdsAndLunchSelection(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); - } - - validateLunchSelection(requests, existingExams); - } - - private void validateLunchSelection(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); - } - } - - private void validateNoDuplicateApplication(Long userId, List examIds) { - boolean alreadyApplied = applicationJpaRepository.existsByUserIdAndExamIds(userId, examIds); - if (alreadyApplied) { - throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_DUPLICATED); - } - } - - 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 List getExams(List examApplications) { - List examIds = examApplications.stream() - .map(ExamApplicationJpaEntity::getExamId) - .distinct() - .toList(); + List applicationIds = applications.stream().map(ApplicationJpaEntity::getId).toList(); + List examApplications = examApplicationJpaRepository.findByApplicationIdIn( + applicationIds); - return examJpaRepository.findByIdIn(examIds).stream() - .map(exam -> AppExam.of( - exam.getId(), - exam - )) - .toList(); + return new ApplicationContext(applications, examApplications) + .fetchExams(examJpaRepository::findByIdIn) + .fetchSubjects(examSubjectJpaRepository::findByExamApplicationIdIn) + .fetchPayments(paymentJpaRepository::findByExamApplicationIdIn) + .assemble(); } +} - private List getPayments( - List examApplications) { - List examApplicationIds = examApplications.stream() - .map(ExamApplicationJpaEntity::getId) - .toList(); - - return paymentJpaRepository.findByExamApplicationIdIn(examApplicationIds).stream() - .map(payment -> AppPayment.of(payment.getExamApplicationId(), payment)) - .toList(); - } - - private Map> getSubjects( - List examApplications) { - List examApplicationIds = examApplications.stream() - .map(ExamApplicationJpaEntity::getId) - .toList(); - - return examSubjectJpaRepository.findByExamApplicationIdIn(examApplicationIds).stream() - .map( - - ) - .collect(Collectors.groupingBy(ExamSubjectJpaEntity::getExamApplicationId)); - } - - private Map> groupExamResponsesByApplication( - List examApplications, - List exams, - List subjects, - List payments - ) { - -// Map paymentMap = getPaymentMap(examApplications); - return examApplications.stream() - .map(examApplication -> { - Long examApplicationId = examApplication.getId(); - ExamJpaEntity exam = exams.stream() - .findFirst( - ); - - 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 index 530cd1c2..b051186f 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/RegisterApplicationStepProcessor.java +++ b/src/main/java/life/mosu/mosuserver/application/application/RegisterApplicationStepProcessor.java @@ -3,13 +3,13 @@ import jakarta.transaction.Transactional; import java.util.List; import life.mosu.mosuserver.application.application.dto.RegisterApplicationCommand; -import life.mosu.mosuserver.application.auth.processor.StepProcessor; import life.mosu.mosuserver.application.examapplication.ExamApplicationService; import life.mosu.mosuserver.application.examapplication.dto.RegisterExamApplicationEvent; -import life.mosu.mosuserver.domain.application.ApplicationJpaEntity; 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; @@ -24,14 +24,12 @@ public class RegisterApplicationStepProcessor implements StepProcessor examApplicationRequests = command.applicationRequest() + .examApplication(); + final Long applicationId = command.applicationId(); List examApplicationEntities = examApplicationService.register( - RegisterExamApplicationEvent.of(command.applicationRequest().examApplication(), + RegisterExamApplicationEvent.of(examApplicationRequests, applicationId) ); 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 index 368eba53..70dcc6b6 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/dto/RegisterApplicationCommand.java +++ b/src/main/java/life/mosu/mosuserver/application/application/dto/RegisterApplicationCommand.java @@ -5,16 +5,16 @@ import life.mosu.mosuserver.presentation.application.dto.ApplicationRequest; public record RegisterApplicationCommand( - Long userId, + Long applicationId, ApplicationRequest applicationRequest, Set subjects ) { public static RegisterApplicationCommand of( - Long userId, + Long applicationId, ApplicationRequest applicationRequest, Set subjects ) { - return new RegisterApplicationCommand(userId, applicationRequest, 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 index 4351c552..276b8e6d 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/processor/SaveApplicationStepProcessor.java +++ b/src/main/java/life/mosu/mosuserver/application/application/processor/SaveApplicationStepProcessor.java @@ -1,7 +1,7 @@ package life.mosu.mosuserver.application.application.processor; import life.mosu.mosuserver.application.application.ApplicationProcessingContext; -import life.mosu.mosuserver.application.auth.processor.StepProcessor; +import life.mosu.mosuserver.global.processor.StepProcessor; public class SaveApplicationStepProcessor implements StepProcessor { 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 index f05b3cb7..59d96c81 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/processor/SaveExamTicketStepProcessor.java +++ b/src/main/java/life/mosu/mosuserver/application/application/processor/SaveExamTicketStepProcessor.java @@ -1,9 +1,9 @@ package life.mosu.mosuserver.application.application.processor; import life.mosu.mosuserver.application.application.ApplicationProcessingContext; -import life.mosu.mosuserver.application.auth.processor.StepProcessor; 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; 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/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; +}