diff --git a/build.gradle b/build.gradle index 621905e8..f6610889 100644 --- a/build.gradle +++ b/build.gradle @@ -87,6 +87,9 @@ dependencies { implementation 'org.apache.poi:poi-ooxml:5.4.0' implementation 'org.springframework.boot:spring-boot-starter-webflux' + + runtimeOnly 'com.h2database:h2' + } tasks.named('test') { diff --git a/src/main/java/life/mosu/mosuserver/MosuServerApplication.java b/src/main/java/life/mosu/mosuserver/MosuServerApplication.java index 35aba227..175ca265 100644 --- a/src/main/java/life/mosu/mosuserver/MosuServerApplication.java +++ b/src/main/java/life/mosu/mosuserver/MosuServerApplication.java @@ -1,6 +1,5 @@ package life.mosu.mosuserver; -import life.mosu.mosuserver.infra.notify.NotifyEventPublisher; import lombok.RequiredArgsConstructor; import org.springframework.boot.CommandLineRunner; import org.springframework.boot.SpringApplication; @@ -10,8 +9,6 @@ @RequiredArgsConstructor public class MosuServerApplication implements CommandLineRunner { - private final NotifyEventPublisher publisher; - public static void main(String[] args) { SpringApplication.run(MosuServerApplication.class, args); } diff --git a/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java b/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java index d47dcddb..879625f1 100644 --- a/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java +++ b/src/main/java/life/mosu/mosuserver/application/admin/AdminService.java @@ -1,55 +1,55 @@ -package life.mosu.mosuserver.application.admin; - -import java.util.List; -import life.mosu.mosuserver.domain.admin.ApplicationQueryRepositoryImpl; -import life.mosu.mosuserver.domain.admin.RefundQueryRepositoryImpl; -import life.mosu.mosuserver.domain.admin.StudentQueryRepositoryImpl; -import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto; -import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter; -import life.mosu.mosuserver.presentation.admin.dto.ApplicationListResponse; -import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse; -import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse; -import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto; -import life.mosu.mosuserver.presentation.admin.dto.StudentFilter; -import life.mosu.mosuserver.presentation.admin.dto.StudentListResponse; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Service; - -@Slf4j -@Service -@RequiredArgsConstructor -public class AdminService { - - private final StudentQueryRepositoryImpl studentQueryRepository; - private final ApplicationQueryRepositoryImpl applicationQueryRepository; - private final RefundQueryRepositoryImpl refundQueryRepository; - - public Page getStudents(StudentFilter filter, Pageable pageable) { - return studentQueryRepository.searchAllStudents(filter, pageable); - } - - public List getStudentExcelData() { - return studentQueryRepository.searchAllStudentsForExcel(); - } - - public List getLunchCounts() { - return applicationQueryRepository.searchAllSchoolLunches(); - } - - public Page getApplications(ApplicationFilter filter, - Pageable pageable) { - return applicationQueryRepository.searchAllApplications(filter, pageable); - } - - public List getApplicationExcelData() { - return applicationQueryRepository.searchAllApplicationsForExcel(); - } - - public Page getRefunds(Pageable pageable) { - return refundQueryRepository.searchAllRefunds(pageable); - } - -} +//package life.mosu.mosuserver.application.admin; +// +//import java.util.List; +//import life.mosu.mosuserver.domain.admin.ApplicationQueryRepositoryImpl; +//import life.mosu.mosuserver.domain.admin.RefundQueryRepositoryImpl; +//import life.mosu.mosuserver.domain.admin.StudentQueryRepositoryImpl; +//import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto; +//import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter; +//import life.mosu.mosuserver.presentation.admin.dto.ApplicationListResponse; +//import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse; +//import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse; +//import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto; +//import life.mosu.mosuserver.presentation.admin.dto.StudentFilter; +//import life.mosu.mosuserver.presentation.admin.dto.StudentListResponse; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.data.domain.Page; +//import org.springframework.data.domain.Pageable; +//import org.springframework.stereotype.Service; +// +//@Slf4j +//@Service +//@RequiredArgsConstructor +//public class AdminService { +// +// private final StudentQueryRepositoryImpl studentQueryRepository; +// private final ApplicationQueryRepositoryImpl applicationQueryRepository; +// private final RefundQueryRepositoryImpl refundQueryRepository; +// +// public Page getStudents(StudentFilter filter, Pageable pageable) { +// return studentQueryRepository.searchAllStudents(filter, pageable); +// } +// +// public List getStudentExcelData() { +// return studentQueryRepository.searchAllStudentsForExcel(); +// } +// +// public List getLunchCounts() { +// return applicationQueryRepository.searchAllSchoolLunches(); +// } +// +// public Page getApplications(ApplicationFilter filter, +// Pageable pageable) { +// return applicationQueryRepository.searchAllApplications(filter, pageable); +// } +// +// public List getApplicationExcelData() { +// return applicationQueryRepository.searchAllApplicationsForExcel(); +// } +// +// public Page getRefunds(Pageable pageable) { +// return refundQueryRepository.searchAllRefunds(pageable); +// } +// +//} 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 10a54dc7..7f32f0d6 100644 --- a/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java +++ b/src/main/java/life/mosu/mosuserver/application/application/ApplicationService.java @@ -1,23 +1,30 @@ package life.mosu.mosuserver.application.application; -import java.util.ArrayList; import java.util.List; import java.util.Set; -import life.mosu.mosuserver.domain.application.AdmissionTicketImageJpaEntity; -import life.mosu.mosuserver.domain.application.AdmissionTicketImageJpaRepository; +import java.util.stream.Collectors; +import life.mosu.mosuserver.application.exam.ExamQuotaCacheManager; +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.applicationschool.ApplicationSchoolJpaEntity; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaRepository; -import life.mosu.mosuserver.domain.school.SchoolJpaEntity; -import life.mosu.mosuserver.domain.school.SchoolJpaRepository; -import life.mosu.mosuserver.domain.school.SchoolQueryRepositoryImpl; +import life.mosu.mosuserver.domain.application.ExamTicketImageJpaEntity; +import life.mosu.mosuserver.domain.application.ExamTicketImageJpaRepository; +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.lunch.LunchJpaEntity; +import life.mosu.mosuserver.domain.lunch.LunchJpaRepository; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; import life.mosu.mosuserver.global.util.FileRequest; import life.mosu.mosuserver.presentation.application.dto.ApplicationRequest; import life.mosu.mosuserver.presentation.application.dto.ApplicationResponse; -import life.mosu.mosuserver.presentation.application.dto.ApplicationSchoolRequest; +import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest; +import life.mosu.mosuserver.presentation.application.dto.ExamWithSubjects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; @@ -30,119 +37,120 @@ public class ApplicationService { private final ApplicationJpaRepository applicationJpaRepository; - private final ApplicationSchoolJpaRepository applicationSchoolJpaRepository; - private final AdmissionTicketImageJpaRepository admissionTicketImageJpaRepository; - private final SchoolQueryRepositoryImpl schoolQueryRepository; - private final SchoolJpaRepository schoolJpaRepository; + private final ExamTicketImageJpaRepository examTicketImageJpaRepository; + private final ExamApplicationService examApplicationService; + private final ExamQuotaCacheManager examQuotaCacheManager; + private final ExamSubjectJpaRepository examSubjectJpaRepository; + private final ExamApplicationJpaRepository examApplicationJpaRepository; + private final ExamJpaRepository examJpaRepository; + private final LunchJpaRepository lunchJpaRepository; - // 신청 @Transactional public ApplicationResponse apply(Long userId, ApplicationRequest request) { - Set schoolRequests = request.schools(); - List savedEntities = new ArrayList<>(); - - ApplicationJpaEntity application = applicationJpaRepository.save(request.toEntity(userId)); - Long applicationId = application.getId(); - - //수험표 저장 - admissionTicketImageJpaRepository.save( - createAdmissionTicketImageIfPresent(request.admissionTicket(), applicationId)); - - for (ApplicationSchoolRequest schoolRequest : schoolRequests) { - //해당 학교가 존재하는 학교인지 - Long schoolId = schoolJpaRepository.findBySchoolNameAndAreaAndExamDate( - schoolRequest.schoolName(), - schoolRequest.validatedArea(schoolRequest.area()), - schoolRequest.examDate()) - .orElseThrow(() -> new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND)) - .getId(); - - //해당 학교를 이미 신청하였는지 - if (applicationSchoolJpaRepository.existsByUserIdAndSchoolId(userId, schoolId)) { - throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_ALREADY_APPLIED); - } - - //해당 학교를 찾을 수 있는지 - SchoolJpaEntity school = schoolJpaRepository.findById(schoolId) - .orElseThrow(() -> new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND)); - - //해당 학교를 신청했음을 저장하기 - ApplicationSchoolJpaEntity applicationSchool = schoolRequest.toEntity(userId, - applicationId, school); - - //반환용 리스트에 저장하기 - savedEntities.add(applicationSchoolJpaRepository.save(applicationSchool)); + + // 중복 신청 검증 + List examIds = request.examApplication().stream() + .map(ExamApplicationRequest::examId) + .toList(); + + boolean isDuplicate = applicationJpaRepository.existsByUserIdAndExamIds(userId, examIds); + if (isDuplicate) { + throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_DUPLICATED); } - return ApplicationResponse.of(applicationId, savedEntities); + ApplicationJpaEntity application = request.toApplicationJpaEntity(userId); + ApplicationJpaEntity savedApplication = applicationJpaRepository.save(application); + Long applicationId = savedApplication.getId(); + + List examApplicationEntities = examApplicationService.register( + RegisterExamApplicationEvent.of(request.examApplication(), applicationId) + ); + examApplicationJpaRepository.saveAll(examApplicationEntities); + + List allExamSubjects = examApplicationEntities.stream() + .flatMap(examApplication -> { + Long examApplicationId = examApplication.getId(); + return request.validatedSubjects().stream() + .map(subject -> ExamSubjectJpaEntity.create(examApplicationId, + subject)); + }) + .toList(); + + examSubjectJpaRepository.saveAll(allExamSubjects); + + FileRequest fileReq = request.admissionTicket(); + if (fileReq.fileName() != null && fileReq.s3Key() != null) { + ExamTicketImageJpaEntity examTicketImage = fileReq + .toExamTicketImageEntity(applicationId); + + examTicketImageJpaRepository.save(examTicketImage); + } + return ApplicationResponse.of(applicationId); } - //신청 - 리팩토링 -// @Transactional -// public ApplicationResponse apply(Long userId, ApplicationRequest request) { -// Set schoolRequests = request.schools(); -//// List savedEntities = new ArrayList<>(); -// -// ApplicationJpaEntity application = applicationJpaRepository.save(request.toEntity(userId)); -// Long applicationId = application.getId(); -// -// //수험표 저장 -// admissionTicketImageJpaRepository.save( -// createAdmissionTicketImageIfPresent(request.admissionTicket(), applicationId)); -// -// List> conditions = schoolRequests.stream() -// .map(school -> Triple.of( -// school.schoolName(), -// school.validatedArea(school.area()), -// school.examDate() -// )) -// .toList(); -// -// //이름, 지역, 날짜 조건에 맞는 학교가 있는지 -// List schoolIds = schoolQueryRepository.findSchoolsByConditions(conditions); -// if (schoolIds.size() != conditions.size()) { -// throw new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND); -// } -// -// //해당 학교를 이미 신청하였는지 -// if (applicationSchoolJpaRepository.existsByUserIdAndSchoolIds(userId, schoolIds)) { -// throw new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_ALREADY_APPLIED); -// } -// - - /// / //해당 학교를 찾을 수 있는지 / SchoolJpaEntity school = - /// schoolJpaRepository.findById(schoolId) / .orElseThrow(() -> new - /// CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND)); -// -// //해당 학교를 신청했음을 저장하기 -// ApplicationSchoolJpaEntity applicationSchool = schoolRequest.toEntity(userId, -// applicationId, school); -// -// //반환용 리스트에 저장하기 -// savedEntities.add(applicationSchoolJpaRepository.save(applicationSchool)); -// -// return ApplicationResponse.of(applicationId, savedEntities); -// } // 전체 신청 내역 조회 @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) public List getApplications(Long userId) { List applications = applicationJpaRepository.findAllByUserId(userId); + if (applications.isEmpty()) { + throw new CustomRuntimeException(ErrorCode.APPLICATION_LIST_NOT_FOUND); + } + return applications.stream() .map(application -> { - List schools = applicationSchoolJpaRepository.findAllByApplicationId( - application.getId()); - return ApplicationResponse.of(application.getId(), schools); + // 해당 신청의 시험 신청들 조회 + List examApplications = + examApplicationJpaRepository.findByApplicationId(application.getId()); + + if (examApplications.isEmpty()) { + throw new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND); + } + + // ExamWithSubjects 리스트 생성 + List exams = examApplications.stream() + .map(examApplication -> { + // 시험 정보 조회 + ExamJpaEntity exam = examJpaRepository.findById( + examApplication.getExamId()) + .orElseThrow(() -> new CustomRuntimeException( + ErrorCode.EXAM_NOT_FOUND)); + + // 점심 정보 조회 (점심은 null일 수 있음) + LunchJpaEntity lunch = null; + if (examApplication.getLunchId() != null) { + lunch = lunchJpaRepository.findById( + examApplication.getLunchId()) + .orElseThrow(() -> new CustomRuntimeException( + ErrorCode.LUNCH_NOT_FOUND)); + } + + // 과목 정보 조회 + List examSubjects = + examSubjectJpaRepository.findByExamApplicationId( + examApplication.getId()); + Set subjects = examSubjects.stream() + .map(examSubject -> examSubject.getSubject() + .getSubjectName()) + .collect(Collectors.toSet()); + + // ExamWithSubjects 생성 + return new ExamWithSubjects( + examApplication.getId(), + exam.getArea().getAreaName(), + exam.getExamDate(), + exam.getSchoolName(), + lunch != null ? lunch.getName() : "신청 안 함", + examApplication.getExamNumber() != null + ? examApplication.getExamNumber() : "", + subjects + ); + }) + .toList(); + + return ApplicationResponse.of(application.getId(), exams); }) .toList(); } - - private AdmissionTicketImageJpaEntity createAdmissionTicketImageIfPresent( - FileRequest fileRequest, Long applicationId) { - return admissionTicketImageJpaRepository.save( - fileRequest.toAdmissionTicketImageEntity(fileRequest.fileName(), - fileRequest.s3Key(), applicationId)); - } - } \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/application/applicationschool/ApplicationSchoolService.java b/src/main/java/life/mosu/mosuserver/application/applicationschool/ApplicationSchoolService.java deleted file mode 100644 index 56f75eda..00000000 --- a/src/main/java/life/mosu/mosuserver/application/applicationschool/ApplicationSchoolService.java +++ /dev/null @@ -1,129 +0,0 @@ -package life.mosu.mosuserver.application.applicationschool; - -import java.time.Duration; -import java.util.Set; -import java.util.stream.Collectors; -import life.mosu.mosuserver.domain.application.AdmissionTicketImageJpaEntity; -import life.mosu.mosuserver.domain.application.AdmissionTicketImageJpaRepository; -import life.mosu.mosuserver.domain.application.ApplicationJpaEntity; -import life.mosu.mosuserver.domain.application.ApplicationJpaRepository; -import life.mosu.mosuserver.domain.application.Subject; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaRepository; -import life.mosu.mosuserver.domain.profile.ProfileJpaEntity; -import life.mosu.mosuserver.domain.profile.ProfileJpaRepository; -import life.mosu.mosuserver.domain.refund.RefundJpaRepository; -import life.mosu.mosuserver.global.exception.CustomRuntimeException; -import life.mosu.mosuserver.global.exception.ErrorCode; -import life.mosu.mosuserver.infra.property.S3Properties; -import life.mosu.mosuserver.infra.storage.application.S3Service; -import life.mosu.mosuserver.presentation.application.dto.ApplicationSchoolResponse; -import life.mosu.mosuserver.presentation.applicationschool.dto.AdmissionTicketResponse; -import life.mosu.mosuserver.presentation.applicationschool.dto.RefundRequest; -import life.mosu.mosuserver.presentation.applicationschool.dto.SubjectUpdateRequest; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Slf4j -public class ApplicationSchoolService { - - private final ApplicationSchoolJpaRepository applicationSchoolJpaRepository; - private final ApplicationJpaRepository applicationJpaRepository; - private final RefundJpaRepository refundJpaRepository; - private final ProfileJpaRepository profileJpaRepository; - private final AdmissionTicketImageJpaRepository admissionTicketImageJpaRepository; - private final S3Service s3Service; - private final S3Properties s3Properties; - - - @Transactional - public ApplicationSchoolResponse updateSubjects( - Long applicationSchoolId, - SubjectUpdateRequest request - ) { - ApplicationSchoolJpaEntity applicationSchool = applicationSchoolJpaRepository.findById( - applicationSchoolId) - .orElseThrow( - () -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND)); - - applicationSchool.updateSubjects(request.subjects()); - return ApplicationSchoolResponse.from(applicationSchool); - } - - @Transactional - public void cancelApplicationSchool( - Long applicationSchoolId, - RefundRequest request - ) { - ApplicationSchoolJpaEntity applicationSchool = applicationSchoolJpaRepository.findById( - applicationSchoolId) - .orElseThrow( - () -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND)); - - Long applicationId = applicationSchool.getApplicationId(); - applicationSchoolJpaRepository.deleteById(applicationSchoolId); - - if (!applicationSchoolJpaRepository.existsByApplicationId(applicationId)) { - applicationJpaRepository.deleteById(applicationId); - } - - refundJpaRepository.save(request.toEntity(applicationSchoolId)); - } - - // 신청 내역 단건 조회 - @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) - public ApplicationSchoolResponse getApplicationSchool(Long applicationSchoolId) { - ApplicationSchoolJpaEntity applicationSchool = applicationSchoolJpaRepository.findById( - applicationSchoolId) - .orElseThrow( - () -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND)); - - return ApplicationSchoolResponse.from(applicationSchool); - } - - // 수험표 조회 - @Transactional(readOnly = true, propagation = Propagation.SUPPORTS) - public AdmissionTicketResponse getAdmissionTicket(Long userId, Long applicationSchoolId) { - ProfileJpaEntity profile = profileJpaRepository.findById(userId) - .orElseThrow(() -> new CustomRuntimeException(ErrorCode.PROFILE_NOT_FOUND)); - - ApplicationSchoolJpaEntity applicationSchool = applicationSchoolJpaRepository.findById( - applicationSchoolId) - .orElseThrow( - () -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND)); - - ApplicationJpaEntity application = applicationJpaRepository.findById( - applicationSchool.getApplicationId()) - .orElseThrow(() -> new CustomRuntimeException(ErrorCode.APPLICATION_NOT_FOUND)); - - AdmissionTicketImageJpaEntity admissionTicketImage = admissionTicketImageJpaRepository.findByApplicationId( - application.getId()); - - Set subjectNames = applicationSchool.getSubjects().stream() - .map(Subject::getSubjectName) - .collect(Collectors.toSet()); - - return AdmissionTicketResponse.of( - getAdmissionTicketImageUrl(admissionTicketImage), - profile.getUserName(), - profile.getBirth(), - applicationSchool.getExaminationNumber(), - subjectNames, - applicationSchool.getSchoolName() - ); - - } - - private String getAdmissionTicketImageUrl(AdmissionTicketImageJpaEntity admissionTicketImage) { - return s3Service.getPreSignedUrl( - admissionTicketImage.getS3Key(), - Duration.ofMinutes(s3Properties.getPresignedUrlExpirationMinutes()) - ); - } - -} diff --git a/src/main/java/life/mosu/mosuserver/application/event/EventAttachmentService.java b/src/main/java/life/mosu/mosuserver/application/event/EventAttachmentService.java index a8443341..db33da79 100644 --- a/src/main/java/life/mosu/mosuserver/application/event/EventAttachmentService.java +++ b/src/main/java/life/mosu/mosuserver/application/event/EventAttachmentService.java @@ -30,11 +30,7 @@ public void createAttachment(List request, EventJpaEntity eventEnti request, eventEntity.getId(), eventAttachmentRepository, - (fileRequest, eventId) -> fileRequest.toEventAttachmentEntity( - fileRequest.fileName(), - fileRequest.s3Key(), - eventEntity.getId() - ), + (fileRequest, eventId) -> fileRequest.toEventAttachmentEntity(eventEntity.getId()), FileRequest::s3Key ); } diff --git a/src/main/java/life/mosu/mosuserver/application/school/SchoolQuotaCacheManager.java b/src/main/java/life/mosu/mosuserver/application/exam/ExamQuotaCacheManager.java similarity index 63% rename from src/main/java/life/mosu/mosuserver/application/school/SchoolQuotaCacheManager.java rename to src/main/java/life/mosu/mosuserver/application/exam/ExamQuotaCacheManager.java index d8dbe4aa..7358d80c 100644 --- a/src/main/java/life/mosu/mosuserver/application/school/SchoolQuotaCacheManager.java +++ b/src/main/java/life/mosu/mosuserver/application/exam/ExamQuotaCacheManager.java @@ -1,40 +1,47 @@ -package life.mosu.mosuserver.application.school; +package life.mosu.mosuserver.application.exam; +import java.time.LocalDate; import java.util.List; -import life.mosu.mosuserver.domain.school.SchoolApplicationProjection; -import life.mosu.mosuserver.domain.school.SchoolJpaEntity; -import life.mosu.mosuserver.domain.school.SchoolJpaRepository; +import life.mosu.mosuserver.domain.exam.ExamJpaEntity; +import life.mosu.mosuserver.domain.exam.ExamJpaRepository; +import life.mosu.mosuserver.domain.exam.SchoolExamCountProjection; import lombok.RequiredArgsConstructor; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; @Service @RequiredArgsConstructor -public class SchoolQuotaCacheManager { +public class ExamQuotaCacheManager { private static final String REDIS_KEY_SCHOOL_MAX_CAPACITY = "school:max_capacity:"; private static final String REDIS_KEY_SCHOOL_CURRENT_APPLICATIONS = "school:current_applications:"; private final RedisTemplate redisTemplate; - private final SchoolJpaRepository schoolRepository; + private final ExamJpaRepository examJpaRepository; public void cacheSchoolMaxCapacities() { - List schools = schoolRepository.findAll(); - for (SchoolJpaEntity school : schools) { - addSchoolMaxCapacity(school.getSchoolName(), school.getCapacity()); + List exams = examJpaRepository.findUpcomingExamInfo(LocalDate.now()); + for (ExamJpaEntity exam : exams) { + addSchoolMaxCapacity( + exam.getSchoolName(), + exam.getCapacity() + ); } } public void cacheSchoolCurrentApplicationCounts() { - List schoolApplications = schoolRepository.countBySchoolNameGroupBy(); - for (SchoolApplicationProjection projection : schoolApplications) { - addSchoolCurrentApplicationCount(projection.schoolName(), projection.count()); + List schoolExamCounts = examJpaRepository.countApplicationsGroupedBySchoolName(); + for (SchoolExamCountProjection projection : schoolExamCounts) { + addSchoolCurrentApplicationCount( + projection.schoolName(), + projection.applicationCount() + ); } } - public void addSchoolMaxCapacity(String schoolName, Long capacity) { + public void addSchoolMaxCapacity(String schoolName, Integer capacity) { String key = REDIS_KEY_SCHOOL_MAX_CAPACITY + schoolName; - redisTemplate.opsForValue().set(key, capacity); + redisTemplate.opsForValue().set(key, Long.valueOf(capacity)); } public void addSchoolCurrentApplicationCount(String schoolName, Long currentCount) { diff --git a/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java b/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java new file mode 100644 index 00000000..0fac3b9f --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/exam/ExamService.java @@ -0,0 +1,50 @@ +package life.mosu.mosuserver.application.exam; + +import java.util.List; +import life.mosu.mosuserver.domain.exam.Area; +import life.mosu.mosuserver.domain.exam.ExamJpaEntity; +import life.mosu.mosuserver.domain.exam.ExamJpaRepository; +import life.mosu.mosuserver.presentation.exam.dto.ExamRequest; +import life.mosu.mosuserver.presentation.exam.dto.ExamResponse; +import life.mosu.mosuserver.presentation.lunch.dto.CreateLunchEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ExamService { + + private final ExamJpaRepository examJpaRepository; + private final ApplicationEventPublisher publisher; + private final ExamQuotaCacheManager examQuotaCacheManager; + + public void register(ExamRequest request) { + ExamJpaEntity exam = request.toEntity(); + ExamJpaEntity savedExam = examJpaRepository.save(exam); + + examQuotaCacheManager.addSchoolCurrentApplicationCount( + savedExam.getSchoolName(), + 0L + ); + examQuotaCacheManager.addSchoolMaxCapacity( + savedExam.getSchoolName(), + savedExam.getCapacity() + ); + createLunch(request.toCreateLunchEvent(savedExam.getId())); + } + + public List getByArea(String areaName) { + Area area = Area.from(areaName); + List foundExams = examJpaRepository.findByArea(area); + return ExamResponse.fromList(foundExams); + } + + public List getDistinctAreas() { + return examJpaRepository.findDistinctAreas(); + } + + private void createLunch(CreateLunchEvent event) { + publisher.publishEvent(event); + } +} diff --git a/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java b/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java new file mode 100644 index 00000000..2edd5e34 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/examapplication/ExamApplicationService.java @@ -0,0 +1,84 @@ +package life.mosu.mosuserver.application.examapplication; + +import java.util.List; +import life.mosu.mosuserver.application.examapplication.dto.RegisterExamApplicationEvent; +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.ExamNumberGenerationService; +import life.mosu.mosuserver.domain.examapplication.ExamSubjectJpaEntity; +import life.mosu.mosuserver.domain.examapplication.ExamSubjectJpaRepository; +import life.mosu.mosuserver.domain.profile.ProfileJpaEntity; +import life.mosu.mosuserver.domain.profile.ProfileJpaRepository; +import life.mosu.mosuserver.global.exception.CustomRuntimeException; +import life.mosu.mosuserver.global.exception.ErrorCode; +import life.mosu.mosuserver.infra.storage.application.S3Service; +import life.mosu.mosuserver.presentation.admin.dto.ExamTicketResponse; +import life.mosu.mosuserver.presentation.examapplication.dto.UpdateSubjectRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class ExamApplicationService { + + private final ExamApplicationJpaRepository examApplicationJpaRepository; + private final ExamSubjectJpaRepository examSubjectJpaRepository; + private final ExamNumberGenerationService examNumberGenerationService; + private final ExamTicketImageJpaRepository examTicketImageJpaRepository; + private final ProfileJpaRepository profileJpaRepository; + private final ExamJpaRepository examJpaRepository; + private final S3Service s3Service; + + + @Transactional + public List register(RegisterExamApplicationEvent event) { + List examApplicationEntities = event.toEntity(); + examNumberGenerationService.grantTo(examApplicationEntities); + return examApplicationJpaRepository.saveAll(examApplicationEntities); + } + + @Transactional + public void updateSubjects(Long examApplicationId, UpdateSubjectRequest request) { + examSubjectJpaRepository.deleteByExamApplicationId(examApplicationId); + List examSubjects = request.toEntityList(examApplicationId); + examSubjectJpaRepository.saveAll(examSubjects); + } + + @Transactional + public ExamTicketResponse getExamTicket(Long userId, Long examApplicationId) { + ExamApplicationJpaEntity examApplication = examApplicationJpaRepository.findById( + examApplicationId) + .orElseThrow( + () -> new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_NOT_FOUND)); + + List examSubjects = examSubjectJpaRepository.findByExamApplicationId( + examApplicationId); + + List subjects = examSubjects.stream() + .map(es -> es.getSubject()) + .toList(); + + Long applicationId = examApplication.getApplicationId(); + ExamTicketImageJpaEntity examTicketImage = examTicketImageJpaRepository.findByApplicationId( + applicationId); + ; + ProfileJpaEntity profile = profileJpaRepository.findById(userId) + .orElseThrow(() -> new CustomRuntimeException(ErrorCode.PROFILE_NOT_FOUND)); + + ExamJpaEntity exam = examJpaRepository.findById(examApplication.getExamId()) + .orElseThrow(() -> new CustomRuntimeException(ErrorCode.EXAM_NOT_FOUND)); + + String examTicketImgUrl = s3Service.getPreSignedUrl(examTicketImage.getS3Key()); + + return ExamTicketResponse.of(examTicketImgUrl, profile.getUserName(), profile.getBirth(), + examApplication.getExamNumber(), subjects, exam.getSchoolName()); + + + } +} diff --git a/src/main/java/life/mosu/mosuserver/application/examapplication/dto/RegisterExamApplicationEvent.java b/src/main/java/life/mosu/mosuserver/application/examapplication/dto/RegisterExamApplicationEvent.java new file mode 100644 index 00000000..ae090401 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/examapplication/dto/RegisterExamApplicationEvent.java @@ -0,0 +1,38 @@ +package life.mosu.mosuserver.application.examapplication.dto; + +import java.util.List; +import life.mosu.mosuserver.domain.examapplication.ExamApplicationJpaEntity; +import life.mosu.mosuserver.presentation.application.dto.ExamApplicationRequest; + +public record RegisterExamApplicationEvent( + List targetExams, + Long applicationId +) { + + public static RegisterExamApplicationEvent of( + List examApplicationRequests, + Long applicationId + ) { + List targetExams = examApplicationRequests.stream() + .map(request -> new TargetExam(request.examId(), request.lunchId())) + .toList(); + return new RegisterExamApplicationEvent(targetExams, applicationId); + } + + public List toEntity() { + return targetExams.stream() + .map(targetExam -> ExamApplicationJpaEntity.create( + applicationId, + targetExam.examId(), + targetExam.lunchId() + )) + .toList(); + } + + public record TargetExam( + Long examId, + Long lunchId + ) { + + } +} diff --git a/src/main/java/life/mosu/mosuserver/application/faq/FaqAttachmentService.java b/src/main/java/life/mosu/mosuserver/application/faq/FaqAttachmentService.java index cf29aa60..1c55c99a 100644 --- a/src/main/java/life/mosu/mosuserver/application/faq/FaqAttachmentService.java +++ b/src/main/java/life/mosu/mosuserver/application/faq/FaqAttachmentService.java @@ -1,12 +1,10 @@ package life.mosu.mosuserver.application.faq; -import java.time.Duration; import java.util.List; import life.mosu.mosuserver.domain.faq.FaqAttachmentJpaEntity; import life.mosu.mosuserver.domain.faq.FaqAttachmentJpaRepository; import life.mosu.mosuserver.domain.faq.FaqJpaEntity; import life.mosu.mosuserver.global.util.FileRequest; -import life.mosu.mosuserver.infra.property.S3Properties; import life.mosu.mosuserver.infra.storage.FileUploadHelper; import life.mosu.mosuserver.infra.storage.application.AttachmentService; import life.mosu.mosuserver.infra.storage.application.S3Service; @@ -23,7 +21,6 @@ public class FaqAttachmentService implements AttachmentService requests, FaqJpaEntity faqEntity) requests, faqEntity.getId(), faqAttachmentJpaRepository, - (req, id) -> req.toFaqAttachmentEntity( - req.fileName(), - req.s3Key(), - faqEntity.getId() - ), + (req, id) -> req.toFaqAttachmentEntity(faqEntity.getId()), FileRequest::s3Key ); } @@ -57,10 +50,7 @@ public List toAttachmentResponses(FaqJpaEntity f return attachments.stream() .map(attachment -> new FaqResponse.AttachmentResponse( attachment.getFileName(), - s3Service.getPreSignedUrl( - attachment.getS3Key(), - Duration.ofMinutes(s3Properties.getPresignedUrlExpirationMinutes()) - ), + s3Service.getPreSignedUrl(attachment.getS3Key()), attachment.getS3Key() )) .toList(); diff --git a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerAttachmentService.java b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerAttachmentService.java index 0a64d546..f6959a4e 100644 --- a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerAttachmentService.java +++ b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAnswerAttachmentService.java @@ -1,12 +1,10 @@ package life.mosu.mosuserver.application.inquiry; -import java.time.Duration; import java.util.List; import life.mosu.mosuserver.domain.inquiryAnswer.InquiryAnswerAttachmentEntity; import life.mosu.mosuserver.domain.inquiryAnswer.InquiryAnswerAttachmentJpaRepository; import life.mosu.mosuserver.domain.inquiryAnswer.InquiryAnswerJpaEntity; import life.mosu.mosuserver.global.util.FileRequest; -import life.mosu.mosuserver.infra.property.S3Properties; import life.mosu.mosuserver.infra.storage.FileUploadHelper; import life.mosu.mosuserver.infra.storage.application.AttachmentService; import life.mosu.mosuserver.infra.storage.application.S3Service; @@ -19,7 +17,6 @@ public class InquiryAnswerAttachmentService implements AttachmentService { - private final S3Properties s3Properties; private final InquiryAnswerAttachmentJpaRepository attachmentRepository; private final FileUploadHelper fileUploadHelper; private final S3Service s3Service; @@ -30,11 +27,7 @@ public void createAttachment(List requests, InquiryAnswerJpaEntity requests, answerEntity.getId(), attachmentRepository, - (req, id) -> req.toInquiryAnswerAttachmentEntity( - req.fileName(), - req.s3Key(), - answerEntity.getId() - ), + (req, id) -> req.toInquiryAnswerAttachmentEntity(answerEntity.getId()), FileRequest::s3Key ); } @@ -62,27 +55,21 @@ public List toAttachmentResponse private InquiryDetailResponse.AttachmentResponse createAttachResponse( InquiryAnswerAttachmentEntity attachment) { - String presignedUrl = s3Service.getPreSignedUrl( - attachment.getS3Key(), - Duration.ofMinutes(s3Properties.getPresignedUrlExpirationMinutes()) - ); + String preSignedUrl = s3Service.getPreSignedUrl(attachment.getS3Key()); return new InquiryDetailResponse.AttachmentResponse( attachment.getFileName(), - presignedUrl + preSignedUrl ); } private InquiryDetailResponse.AttachmentDetailResponse createAttachDetailResponse( InquiryAnswerAttachmentEntity attachment) { - String presignedUrl = s3Service.getPreSignedUrl( - attachment.getS3Key(), - Duration.ofMinutes(s3Properties.getPresignedUrlExpirationMinutes()) - ); + String preSignedUrl = s3Service.getPreSignedUrl(attachment.getS3Key()); return new InquiryDetailResponse.AttachmentDetailResponse( attachment.getFileName(), - presignedUrl, + preSignedUrl, attachment.getS3Key() ); } diff --git a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAttachmentService.java b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAttachmentService.java index ee943dcb..da0420ea 100644 --- a/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAttachmentService.java +++ b/src/main/java/life/mosu/mosuserver/application/inquiry/InquiryAttachmentService.java @@ -1,12 +1,10 @@ package life.mosu.mosuserver.application.inquiry; -import java.time.Duration; import java.util.List; import life.mosu.mosuserver.domain.inquiry.InquiryAttachmentJpaEntity; import life.mosu.mosuserver.domain.inquiry.InquiryAttachmentJpaRepository; import life.mosu.mosuserver.domain.inquiry.InquiryJpaEntity; import life.mosu.mosuserver.global.util.FileRequest; -import life.mosu.mosuserver.infra.property.S3Properties; import life.mosu.mosuserver.infra.storage.FileUploadHelper; import life.mosu.mosuserver.infra.storage.application.AttachmentService; import life.mosu.mosuserver.infra.storage.application.S3Service; @@ -21,7 +19,6 @@ public class InquiryAttachmentService implements AttachmentService requests, InquiryJpaEntity inquiryEntity) { @@ -29,11 +26,7 @@ public void createAttachment(List requests, InquiryJpaEntity inquir requests, inquiryEntity.getId(), inquiryAttachmentJpaRepository, - (req, id) -> req.toInquiryAttachmentEntity( - req.fileName(), - req.s3Key(), - inquiryEntity.getId() - ), + (req, id) -> req.toInquiryAttachmentEntity(inquiryEntity.getId()), FileRequest::s3Key ); } @@ -58,14 +51,11 @@ public List toAttachmentResponse private InquiryDetailResponse.AttachmentDetailResponse createAttachDetailResponse( InquiryAttachmentJpaEntity attachment) { - String presignedUrl = s3Service.getPreSignedUrl( - attachment.getS3Key(), - Duration.ofMinutes(s3Properties.getPresignedUrlExpirationMinutes()) - ); + String preSignedUrl = s3Service.getPreSignedUrl(attachment.getS3Key()); return new InquiryDetailResponse.AttachmentDetailResponse( attachment.getFileName(), - presignedUrl, + preSignedUrl, attachment.getS3Key() ); } diff --git a/src/main/java/life/mosu/mosuserver/application/lunch/LunchEventService.java b/src/main/java/life/mosu/mosuserver/application/lunch/LunchEventService.java new file mode 100644 index 00000000..8147ce3f --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/lunch/LunchEventService.java @@ -0,0 +1,23 @@ +package life.mosu.mosuserver.application.lunch; + + +import java.util.List; +import life.mosu.mosuserver.domain.lunch.LunchJpaEntity; +import life.mosu.mosuserver.domain.lunch.LunchJpaRepository; +import life.mosu.mosuserver.presentation.lunch.dto.CreateLunchEvent; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class LunchEventService { + + private final LunchJpaRepository lunchJpaRepository; + + @Transactional + public void create(CreateLunchEvent request) { + List lunch = request.toEntities(); + lunchJpaRepository.saveAll(lunch); + } +} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/application/lunch/LunchService.java b/src/main/java/life/mosu/mosuserver/application/lunch/LunchService.java new file mode 100644 index 00000000..bff59feb --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/application/lunch/LunchService.java @@ -0,0 +1,20 @@ +package life.mosu.mosuserver.application.lunch; + +import java.util.List; +import life.mosu.mosuserver.domain.lunch.LunchJpaEntity; +import life.mosu.mosuserver.domain.lunch.LunchJpaRepository; +import life.mosu.mosuserver.presentation.lunch.dto.LunchResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class LunchService { + + private final LunchJpaRepository lunchJpaRepository; + + public List getByExamId(Long examId) { + List foundLunch = lunchJpaRepository.findByExamId(examId); + return LunchResponse.fromList(foundLunch); + } +} diff --git a/src/main/java/life/mosu/mosuserver/application/notice/NoticeAttachmentService.java b/src/main/java/life/mosu/mosuserver/application/notice/NoticeAttachmentService.java index 6f03d6a7..d775acf4 100644 --- a/src/main/java/life/mosu/mosuserver/application/notice/NoticeAttachmentService.java +++ b/src/main/java/life/mosu/mosuserver/application/notice/NoticeAttachmentService.java @@ -1,12 +1,10 @@ package life.mosu.mosuserver.application.notice; -import java.time.Duration; import java.util.List; import life.mosu.mosuserver.domain.notice.NoticeAttachmentJpaEntity; import life.mosu.mosuserver.domain.notice.NoticeAttachmentJpaRepository; import life.mosu.mosuserver.domain.notice.NoticeJpaEntity; import life.mosu.mosuserver.global.util.FileRequest; -import life.mosu.mosuserver.infra.property.S3Properties; import life.mosu.mosuserver.infra.storage.FileUploadHelper; import life.mosu.mosuserver.infra.storage.application.AttachmentService; import life.mosu.mosuserver.infra.storage.application.S3Service; @@ -24,7 +22,6 @@ public class NoticeAttachmentService implements AttachmentService requests, NoticeJpaEntity noticeEntity) { @@ -32,11 +29,7 @@ public void createAttachment(List requests, NoticeJpaEntity noticeE requests, noticeEntity.getId(), noticeAttachmentJpaRepository, - (req, id) -> req.toNoticeAttachmentEntity( - req.fileName(), - req.s3Key(), - noticeEntity.getId() - ), + (req, id) -> req.toNoticeAttachmentEntity(noticeEntity.getId()), FileRequest::s3Key ); } @@ -78,10 +71,7 @@ public List toDetailAttResponses( } private String fileUrl(String s3Key) { - return s3Service.getPreSignedUrl( - s3Key, - Duration.ofMinutes(s3Properties.getPresignedUrlExpirationMinutes()) - ); + return s3Service.getPreSignedUrl(s3Key); } } diff --git a/src/main/java/life/mosu/mosuserver/application/notify/NotifyVariableFactory.java b/src/main/java/life/mosu/mosuserver/application/notify/NotifyVariableFactory.java index 1ff1909b..228251e6 100644 --- a/src/main/java/life/mosu/mosuserver/application/notify/NotifyVariableFactory.java +++ b/src/main/java/life/mosu/mosuserver/application/notify/NotifyVariableFactory.java @@ -1,20 +1,15 @@ package life.mosu.mosuserver.application.notify; -import life.mosu.mosuserver.application.notify.dto.ApplicationNotifyRequest; import life.mosu.mosuserver.application.notify.dto.Exam1DayBeforeNotifyRequest; import life.mosu.mosuserver.application.notify.dto.Exam1WeekBeforeNotifyRequest; import life.mosu.mosuserver.application.notify.dto.Exam3DayBeforeNotifyRequest; import life.mosu.mosuserver.application.notify.dto.InquiryAnswerNotifyRequest; -import life.mosu.mosuserver.application.notify.dto.RefundNotifyRequest; import life.mosu.mosuserver.application.notify.dto.SignUpNotifyRequest; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaRepository; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolNotifyProjection; -import life.mosu.mosuserver.domain.applicationschool.OneWeekNotifyProjection; +import life.mosu.mosuserver.domain.exam.ExamInfoProjection; +import life.mosu.mosuserver.domain.exam.ExamJpaRepository; import life.mosu.mosuserver.domain.inquiry.InquiryJpaEntity; import life.mosu.mosuserver.domain.inquiry.InquiryJpaRepository; import life.mosu.mosuserver.domain.refund.RefundJpaRepository; -import life.mosu.mosuserver.domain.refund.RefundNotifyProjection; import life.mosu.mosuserver.global.exception.CustomRuntimeException; import life.mosu.mosuserver.global.exception.ErrorCode; import life.mosu.mosuserver.infra.notify.dto.NotificationEvent; @@ -27,8 +22,8 @@ public class NotifyVariableFactory { private final InquiryJpaRepository inquiryJpaRepository; - private final ApplicationSchoolJpaRepository applicationSchoolJpaRepository; private final RefundJpaRepository refundJpaRepository; + private final ExamJpaRepository examJpaRepository; public NotificationVariable create(NotificationEvent event) { return switch (event.status()) { @@ -55,42 +50,51 @@ private NotificationVariable createInquiryAnswerVariable(Long targetId) { } private NotificationVariable createRefundVariable(Long targetId) { - RefundNotifyProjection projection = refundJpaRepository.findRefundByApplicationSchoolId( - targetId) - .orElseThrow( - () -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND)); - return new RefundNotifyRequest( - projection.paymentKey(), projection.examDate(), projection.schoolName(), - projection.paymentMethod().getName(), projection.reason() - ); +// RefundNotifyProjection projection = refundJpaRepository.findRefundByApplicationSchoolId( +// targetId) +// .orElseThrow( +// () -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND)); +// return new RefundNotifyRequest( +// projection.paymentKey(), projection.examDate(), projection.schoolName(), +// projection.paymentMethod().getName(), projection.reason() +// ); + return null; } private NotificationVariable createApplicationVariable(Long targetId) { - ApplicationSchoolNotifyProjection projection = applicationSchoolJpaRepository.findPaymentByApplicationSchoolId( - targetId); - return ApplicationNotifyRequest.from(projection); +// ApplicationSchoolNotifyProjection projection = applicationSchoolJpaRepository.findPaymentByApplicationSchoolId( +// targetId); +// return ApplicationNotifyRequest.from(projection); + return null; } private NotificationVariable createExam1WeekBeforeVariable(Long targetId) { - OneWeekNotifyProjection projection = applicationSchoolJpaRepository.findOneWeekNotifyByApplicationSchoolId( - targetId); - return new Exam1WeekBeforeNotifyRequest(projection.examDate(), projection.paymentKey(), - projection.schoolName()); + ExamInfoProjection examInfo = examJpaRepository.findExamInfo(targetId) + .orElseThrow( + () -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND)); + return new Exam1WeekBeforeNotifyRequest( + examInfo.getExamDate(), + examInfo.getExamNumber(), + examInfo.getSchoolName()); } private NotificationVariable createExam3DayBeforeVariable(Long targetId) { - ApplicationSchoolJpaEntity entity = applicationSchoolJpaRepository.findById(targetId) + ExamInfoProjection examInfo = examJpaRepository.findExamInfo(targetId) .orElseThrow( () -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND)); - return new Exam3DayBeforeNotifyRequest(entity.getExamDate(), entity.getExaminationNumber(), - entity.getSchoolName()); + return new Exam3DayBeforeNotifyRequest( + examInfo.getExamDate(), + examInfo.getExamNumber(), + examInfo.getSchoolName()); } private NotificationVariable createExam1DayBeforeVariable(Long targetId) { - ApplicationSchoolJpaEntity entity = applicationSchoolJpaRepository.findById(targetId) + ExamInfoProjection examInfo = examJpaRepository.findExamInfo(targetId) .orElseThrow( () -> new CustomRuntimeException(ErrorCode.APPLICATION_SCHOOL_NOT_FOUND)); - return new Exam1DayBeforeNotifyRequest(entity.getExamDate(), entity.getExaminationNumber(), - entity.getSchoolName()); + return new Exam1DayBeforeNotifyRequest( + examInfo.getExamDate(), + examInfo.getExamNumber(), + examInfo.getSchoolName()); } } diff --git a/src/main/java/life/mosu/mosuserver/application/notify/dto/ApplicationNotifyRequest.java b/src/main/java/life/mosu/mosuserver/application/notify/dto/ApplicationNotifyRequest.java index b34957a9..6fe2a340 100644 --- a/src/main/java/life/mosu/mosuserver/application/notify/dto/ApplicationNotifyRequest.java +++ b/src/main/java/life/mosu/mosuserver/application/notify/dto/ApplicationNotifyRequest.java @@ -5,7 +5,6 @@ import java.time.LocalDate; import java.util.Map; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolNotifyProjection; import life.mosu.mosuserver.infra.notify.dto.NotificationButtonUrls; import life.mosu.mosuserver.infra.notify.dto.NotificationButtonUrls.NotificationButtonUrl; import life.mosu.mosuserver.infra.notify.dto.NotificationVariable; @@ -17,16 +16,14 @@ public record ApplicationNotifyRequest( String lunch ) implements NotificationVariable { - public static ApplicationNotifyRequest from( - ApplicationSchoolNotifyProjection applicationSchool) { - return new ApplicationNotifyRequest( - applicationSchool.paymentKey(), - applicationSchool.examDate(), - applicationSchool.schoolName(), - applicationSchool.lunch() != null ? applicationSchool.lunch().getLunchName() : "" - ); - } +// public static ApplicationNotifyRequest from( +// ApplicationSchoolNotifyProjection applicationSchool) { + /// / return new ApplicationNotifyRequest( / + /// applicationSchool.paymentKey(), / applicationSchool.examDate(), / + /// applicationSchool.schoolName(), / applicationSchool.lunch() != null ? + /// applicationSchool.lunch().getLunchName() : "" / ); +// } @Override public NotificationButtonUrls getNotificationButtonUrls() { return NotificationButtonUrls.of( diff --git a/src/main/java/life/mosu/mosuserver/application/payment/PaymentService.java b/src/main/java/life/mosu/mosuserver/application/payment/PaymentService.java index e4601b19..b7cd0419 100644 --- a/src/main/java/life/mosu/mosuserver/application/payment/PaymentService.java +++ b/src/main/java/life/mosu/mosuserver/application/payment/PaymentService.java @@ -3,7 +3,6 @@ import static life.mosu.mosuserver.domain.discount.DiscountPolicy.FIXED_QUANTITY; import java.util.List; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaRepository; import life.mosu.mosuserver.domain.discount.DiscountPolicy; import life.mosu.mosuserver.domain.payment.PaymentJpaEntity; import life.mosu.mosuserver.domain.payment.PaymentRepository; @@ -30,7 +29,7 @@ @RequiredArgsConstructor public class PaymentService { - private final ApplicationSchoolJpaRepository applicationSchoolJpaRepository; + // private final ApplicationSchoolJpaRepository applicationSchoolJpaRepository; private final TossPaymentClient tossPayment; private final OrderIdGenerator orderIdGenerator; private final PaymentRepository paymentRepository; @@ -41,10 +40,10 @@ public PaymentPrepareResponse prepare(PreparePaymentRequest request) { * 인원 수 redis에 동기화 -> 인원수가 넘어가면, application 까지 rollback */ String uuid = orderIdGenerator.generate(); - int applicationCount = request.getSize(); - int totalAmount = DiscountPolicy.calculate(FIXED_QUANTITY, applicationCount); +// int applicationCount = request.getSize(); +// int totalAmount = DiscountPolicy.calculate(FIXED_QUANTITY, applicationCount); - return PaymentPrepareResponse.of(uuid, totalAmount); + return PaymentPrepareResponse.of(uuid, 0); } @Transactional @@ -85,8 +84,9 @@ public void cancel(String paymentId, CancelPaymentRequest request) { private void checkApplicationsExist(List applicationIds) { - boolean existsAll = applicationSchoolJpaRepository.existsAllByIds(applicationIds, - applicationIds.size()); +// boolean existsAll = applicationSchoolJpaRepository.existsAllByIds(applicationIds, +// applicationIds.size()); + boolean existsAll = true; if (!existsAll) { log.warn("Application IDs not found: {}", applicationIds); throw new RuntimeException("존재하지 않는 신청입니다."); diff --git a/src/main/java/life/mosu/mosuserver/application/school/SchoolService.java b/src/main/java/life/mosu/mosuserver/application/school/SchoolService.java deleted file mode 100644 index 6fbc0849..00000000 --- a/src/main/java/life/mosu/mosuserver/application/school/SchoolService.java +++ /dev/null @@ -1,63 +0,0 @@ -package life.mosu.mosuserver.application.school; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import life.mosu.mosuserver.domain.school.SchoolJpaEntity; -import life.mosu.mosuserver.domain.school.SchoolJpaRepository; -import life.mosu.mosuserver.global.exception.CustomRuntimeException; -import life.mosu.mosuserver.global.exception.ErrorCode; -import life.mosu.mosuserver.presentation.school.dto.AvailableSchoolResponse; -import life.mosu.mosuserver.presentation.school.dto.SchoolEditRequest; -import life.mosu.mosuserver.presentation.school.dto.SchoolRegistrationRequest; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -public class SchoolService { - - private final SchoolJpaRepository schoolJpaRepository; - private final SchoolQuotaCacheManager schoolQuotaCacheManager; - - @Transactional(readOnly = true) - public AvailableSchoolResponse getAvailableSchools() { - List schoolEntities = schoolJpaRepository.findAll(); - System.out.println(schoolEntities); - - Map quotaMap = schoolEntities.stream() - .collect(Collectors.toMap( - SchoolJpaEntity::getSchoolName, - school -> schoolQuotaCacheManager.getSchoolApplicationCounts( - school.getSchoolName()).intValue() - )); - - return AvailableSchoolResponse.from(schoolEntities, quotaMap); - } - - @Transactional - public void registerSchool(SchoolRegistrationRequest request) { - schoolJpaRepository.save(request.toEntity()); - schoolQuotaCacheManager.addSchoolMaxCapacity(request.schoolName(), request.capacity()); - schoolQuotaCacheManager.addSchoolCurrentApplicationCount(request.schoolName(), 0L); - } - - @Transactional - public void deleteSchool(Long id) { - if (!schoolJpaRepository.existsById(id)) { - throw new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND); - } - - schoolJpaRepository.deleteById(id); - } - - @Transactional - public void update(Long schoolId, SchoolEditRequest request) { - SchoolJpaEntity school = schoolJpaRepository.findById(schoolId) - .orElseThrow(() -> new CustomRuntimeException(ErrorCode.SCHOOL_NOT_FOUND)); - - school.updateInfo(request); - - } -} diff --git a/src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepositoryImpl.java b/src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepositoryImpl.java index 6628b528..c8995a36 100644 --- a/src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepositoryImpl.java +++ b/src/main/java/life/mosu/mosuserver/domain/admin/ApplicationQueryRepositoryImpl.java @@ -1,257 +1,251 @@ -package life.mosu.mosuserver.domain.admin; - -import com.querydsl.core.Tuple; -import com.querydsl.core.types.Predicate; -import com.querydsl.core.types.dsl.EnumPath; -import com.querydsl.core.types.dsl.Expressions; -import com.querydsl.jpa.impl.JPAQuery; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.time.Duration; -import java.time.format.DateTimeFormatter; -import java.util.HashSet; -import java.util.List; -import java.util.Set; -import java.util.stream.Collectors; -import life.mosu.mosuserver.domain.application.Lunch; -import life.mosu.mosuserver.domain.application.QAdmissionTicketImageJpaEntity; -import life.mosu.mosuserver.domain.application.QApplicationJpaEntity; -import life.mosu.mosuserver.domain.application.Subject; -import life.mosu.mosuserver.domain.applicationschool.QApplicationSchoolJpaEntity; -import life.mosu.mosuserver.domain.payment.QPaymentJpaEntity; -import life.mosu.mosuserver.domain.profile.QProfileJpaEntity; -import life.mosu.mosuserver.domain.school.QSchoolJpaEntity; -import life.mosu.mosuserver.domain.user.QUserJpaEntity; -import life.mosu.mosuserver.infra.property.S3Properties; -import life.mosu.mosuserver.infra.storage.application.S3Service; -import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto; -import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter; -import life.mosu.mosuserver.presentation.admin.dto.ApplicationListResponse; -import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse; -import life.mosu.mosuserver.presentation.applicationschool.dto.AdmissionTicketResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class ApplicationQueryRepositoryImpl implements ApplicationQueryRepository { - - private static final DateTimeFormatter EXCEL_DT_FORMATTER = - DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); - - private final JPAQueryFactory queryFactory; - private final S3Properties s3Properties; - private final S3Service s3Service; - - private final QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity; - private final QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity; - private final QApplicationSchoolJpaEntity applicationSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity; - private final QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity; - private final QAdmissionTicketImageJpaEntity admissionTicketImage = QAdmissionTicketImageJpaEntity.admissionTicketImageJpaEntity; - private final QUserJpaEntity user = QUserJpaEntity.userJpaEntity; - private final QSchoolJpaEntity school = QSchoolJpaEntity.schoolJpaEntity; - - @Override - public Page searchAllApplications(ApplicationFilter filter, - Pageable pageable) { - - JPAQuery query = baseQuery() - .where( - buildNameCondition(filter.name()), - buildPhoneCondition(filter.phone()) - ) - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()); - - List content = query.fetch().stream() - .map(tuple -> { - Long appSchoolId = tuple.get(applicationSchool.id); - Set subjects = appSchoolId != null - ? findSubjectsByApplicationSchoolId(appSchoolId) - : new HashSet<>(); - return mapToResponse(tuple, subjects); - }) - .toList(); - - return new PageImpl<>(content, pageable, content.size()); - } - - @Override - public List searchAllApplicationsForExcel() { - JPAQuery query = baseQuery(); - return query.fetch().stream() - .map(tuple -> { - Long appSchoolId = tuple.get(applicationSchool.id); - Set subjects = appSchoolId != null - ? findSubjectsByApplicationSchoolId(appSchoolId) - : new HashSet<>(); - return mapToExcel(tuple, subjects); - }) - .toList(); - } - - @Override - public List searchAllSchoolLunches() { - return queryFactory - .select( - school.schoolName, - applicationSchool.lunch.count() - ) - .from(applicationSchool) - .rightJoin(school).on(applicationSchool.schoolId.eq(school.id)) - .where(applicationSchool.lunch.ne(Lunch.NONE)) - .groupBy(school.id, school.schoolName) - .fetch() - .stream() - .map(t -> new SchoolLunchResponse( - t.get(school.schoolName), - t.get(applicationSchool.lunch.count()) - )) - .toList(); - } - - - private JPAQuery baseQuery() { - return queryFactory - .select( - applicationSchool.id, - payment.paymentKey, - applicationSchool.examinationNumber, - profile.userName, - profile.gender, - profile.birth, - profile.phoneNumber, - application.guardianPhoneNumber, - profile.education, - profile.schoolInfo.schoolName, - profile.grade, - applicationSchool.lunch, - applicationSchool.schoolName, - applicationSchool.examDate, - admissionTicketImage.s3Key, - admissionTicketImage.fileName, - payment.paymentStatus, - payment.paymentMethod, - application.createdAt - ) - .from(applicationSchool) - .leftJoin(application).on(applicationSchool.applicationId.eq(application.id)) - .leftJoin(payment).on(payment.applicationSchoolId.eq(applicationSchool.id)) - .leftJoin(user).on(application.userId.eq(user.id)) - .leftJoin(profile).on(profile.userId.eq(user.id)) - .leftJoin(admissionTicketImage) - .on(admissionTicketImage.applicationId.eq(application.id)); - } - - private Predicate buildNameCondition(String name) { - return (name == null || name.isBlank()) - ? null - : profile.userName.contains(name); - } - - private Predicate buildPhoneCondition(String phone) { - return (phone == null || phone.isBlank()) - ? null - : profile.phoneNumber.contains(phone); - } - - private Set findSubjectsByApplicationSchoolId(Long applicationSchoolId) { - EnumPath subject = Expressions.enumPath(Subject.class, "subject"); - return new HashSet<>( - queryFactory - .select(subject) - .from(applicationSchool) - .join(applicationSchool.subjects, subject) - .where(applicationSchool.id.eq(applicationSchoolId)) - .fetch() - ); - } - - private ApplicationListResponse mapToResponse(Tuple tuple, Set subjects) { - Set subjectNames = subjects.stream() - .map(Subject::getSubjectName) - .collect(Collectors.toSet()); - - String lunchName = tuple.get(applicationSchool.lunch).getLunchName(); - - String s3Key = tuple.get(admissionTicketImage.s3Key); - String url = getAdmissionTicketImageUrl(s3Key); - - AdmissionTicketResponse admissionTicket = AdmissionTicketResponse.of( - url, - tuple.get(profile.userName), - tuple.get(profile.birth), - tuple.get(applicationSchool.examinationNumber), - subjectNames, - tuple.get(applicationSchool.schoolName) - ); - - return new ApplicationListResponse( - tuple.get(payment.paymentKey), - tuple.get(applicationSchool.examinationNumber), - tuple.get(profile.userName), - tuple.get(profile.gender).getGenderName(), - tuple.get(profile.birth), - tuple.get(profile.phoneNumber), - tuple.get(application.guardianPhoneNumber), - tuple.get(profile.education).getEducationName(), - tuple.get(profile.schoolInfo.schoolName), - tuple.get(profile.grade).getGradeName(), - lunchName, - subjectNames, - tuple.get(applicationSchool.schoolName), - tuple.get(applicationSchool.examDate), - tuple.get(admissionTicketImage.fileName), - tuple.get(payment.paymentStatus), - tuple.get(payment.paymentMethod), - tuple.get(application.createdAt), - admissionTicket - ); - } - - private ApplicationExcelDto mapToExcel(Tuple tuple, Set subjects) { - Set subjectNames = subjects.stream() - .map(Subject::getSubjectName) - .collect(Collectors.toSet()); - - String lunchName = tuple.get(applicationSchool.lunch).getLunchName(); - String genderName = tuple.get(profile.gender).getGenderName(); - String gradeName = tuple.get(profile.grade).getGradeName(); - String educationName = tuple.get(profile.education).getEducationName(); - String appliedAt = tuple.get(application.createdAt) - .format(EXCEL_DT_FORMATTER); - - return new ApplicationExcelDto( - tuple.get(payment.paymentKey), - tuple.get(applicationSchool.examinationNumber), - tuple.get(profile.userName), - genderName, - tuple.get(profile.birth), - tuple.get(profile.phoneNumber), - tuple.get(application.guardianPhoneNumber), - educationName, - tuple.get(profile.schoolInfo.schoolName), - gradeName, - lunchName, - subjectNames, - tuple.get(applicationSchool.schoolName), - tuple.get(applicationSchool.examDate), - tuple.get(admissionTicketImage.fileName), - tuple.get(payment.paymentStatus), - tuple.get(payment.paymentMethod), - appliedAt - ); - } - - private String getAdmissionTicketImageUrl(String s3Key) { - if (s3Key == null || s3Key.isBlank()) { - return null; - } - return s3Service.getPreSignedUrl( - s3Key, - Duration.ofMinutes(s3Properties.getPresignedUrlExpirationMinutes()) - ); - } -} \ No newline at end of file +//package life.mosu.mosuserver.domain.admin; +// +//import com.querydsl.core.Tuple; +//import com.querydsl.core.types.Predicate; +//import com.querydsl.core.types.dsl.EnumPath; +//import com.querydsl.core.types.dsl.Expressions; +//import com.querydsl.jpa.impl.JPAQuery; +//import com.querydsl.jpa.impl.JPAQueryFactory; +//import java.time.format.DateTimeFormatter; +//import java.util.HashSet; +//import java.util.List; +//import java.util.Set; +//import java.util.stream.Collectors; +//import life.mosu.mosuserver.domain.application.Lunch; +//import life.mosu.mosuserver.domain.application.QAdmissionTicketImageJpaEntity; +//import life.mosu.mosuserver.domain.application.QApplicationJpaEntity; +//import life.mosu.mosuserver.domain.application.Subject; +//import life.mosu.mosuserver.domain.applicationschool.QApplicationSchoolJpaEntity; +//import life.mosu.mosuserver.domain.payment.QPaymentJpaEntity; +//import life.mosu.mosuserver.domain.profile.QProfileJpaEntity; +//import life.mosu.mosuserver.domain.school.QSchoolJpaEntity; +//import life.mosu.mosuserver.domain.user.QUserJpaEntity; +//import life.mosu.mosuserver.infra.storage.application.S3Service; +//import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto; +//import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter; +//import life.mosu.mosuserver.presentation.admin.dto.ApplicationListResponse; +//import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse; +//import life.mosu.mosuserver.presentation.applicationschool.dto.AdmissionTicketResponse; +//import lombok.RequiredArgsConstructor; +//import org.springframework.data.domain.Page; +//import org.springframework.data.domain.PageImpl; +//import org.springframework.data.domain.Pageable; +//import org.springframework.stereotype.Repository; +// +//@Repository +//@RequiredArgsConstructor +//public class ApplicationQueryRepositoryImpl implements ApplicationQueryRepository { +// +// private static final DateTimeFormatter EXCEL_DT_FORMATTER = +// DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); +// +// private final JPAQueryFactory queryFactory; +// private final S3Service s3Service; +// +// private final QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity; +// private final QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity; +// private final QApplicationSchoolJpaEntity applicationSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity; +// private final QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity; +// private final QAdmissionTicketImageJpaEntity admissionTicketImage = QAdmissionTicketImageJpaEntity.admissionTicketImageJpaEntity; +// private final QUserJpaEntity user = QUserJpaEntity.userJpaEntity; +// private final QSchoolJpaEntity school = QSchoolJpaEntity.schoolJpaEntity; +// +// @Override +// public Page searchAllApplications(ApplicationFilter filter, +// Pageable pageable) { +// +// JPAQuery query = baseQuery() +// .where( +// buildNameCondition(filter.name()), +// buildPhoneCondition(filter.phone()) +// ) +// .offset(pageable.getOffset()) +// .limit(pageable.getPageSize()); +// +// List content = query.fetch().stream() +// .map(tuple -> { +// Long appSchoolId = tuple.get(applicationSchool.id); +// Set subjects = appSchoolId != null +// ? findSubjectsByApplicationSchoolId(appSchoolId) +// : new HashSet<>(); +// return mapToResponse(tuple, subjects); +// }) +// .toList(); +// +// return new PageImpl<>(content, pageable, content.size()); +// } +// +// @Override +// public List searchAllApplicationsForExcel() { +// JPAQuery query = baseQuery(); +// return query.fetch().stream() +// .map(tuple -> { +// Long appSchoolId = tuple.get(applicationSchool.id); +// Set subjects = appSchoolId != null +// ? findSubjectsByApplicationSchoolId(appSchoolId) +// : new HashSet<>(); +// return mapToExcel(tuple, subjects); +// }) +// .toList(); +// } +// +// @Override +// public List searchAllSchoolLunches() { +// return queryFactory +// .select( +// school.schoolName, +// applicationSchool.lunch.count() +// ) +// .from(applicationSchool) +// .rightJoin(school).on(applicationSchool.schoolId.eq(school.id)) +// .where(applicationSchool.lunch.ne(Lunch.NONE)) +// .groupBy(school.id, school.schoolName) +// .fetch() +// .stream() +// .map(t -> new SchoolLunchResponse( +// t.get(school.schoolName), +// t.get(applicationSchool.lunch.count()) +// )) +// .toList(); +// } +// +// +// private JPAQuery baseQuery() { +// return queryFactory +// .select( +// applicationSchool.id, +// payment.paymentKey, +// applicationSchool.examinationNumber, +// profile.userName, +// profile.gender, +// profile.birth, +// profile.phoneNumber, +// application.guardianPhoneNumber, +// profile.education, +// profile.schoolInfo.schoolName, +// profile.grade, +// applicationSchool.lunch, +// applicationSchool.schoolName, +// applicationSchool.examDate, +// admissionTicketImage.s3Key, +// admissionTicketImage.fileName, +// payment.paymentStatus, +// payment.paymentMethod, +// application.createdAt +// ) +// .from(applicationSchool) +// .leftJoin(application).on(applicationSchool.applicationId.eq(application.id)) +// .leftJoin(payment).on(payment.applicationSchoolId.eq(applicationSchool.id)) +// .leftJoin(user).on(application.userId.eq(user.id)) +// .leftJoin(profile).on(profile.userId.eq(user.id)) +// .leftJoin(admissionTicketImage) +// .on(admissionTicketImage.applicationId.eq(application.id)); +// } +// +// private Predicate buildNameCondition(String name) { +// return (name == null || name.isBlank()) +// ? null +// : profile.userName.contains(name); +// } +// +// private Predicate buildPhoneCondition(String phone) { +// return (phone == null || phone.isBlank()) +// ? null +// : profile.phoneNumber.contains(phone); +// } +// +// private Set findSubjectsByApplicationSchoolId(Long applicationSchoolId) { +// EnumPath subject = Expressions.enumPath(Subject.class, "subject"); +// return new HashSet<>( +// queryFactory +// .select(subject) +// .from(applicationSchool) +// .join(applicationSchool.subjects, subject) +// .where(applicationSchool.id.eq(applicationSchoolId)) +// .fetch() +// ); +// } +// +// private ApplicationListResponse mapToResponse(Tuple tuple, Set subjects) { +// Set subjectNames = subjects.stream() +// .map(Subject::getSubjectName) +// .collect(Collectors.toSet()); +// +// String lunchName = tuple.get(applicationSchool.lunch).getLunchName(); +// +// String s3Key = tuple.get(admissionTicketImage.s3Key); +// String url = getAdmissionTicketImageUrl(s3Key); +// +// AdmissionTicketResponse admissionTicket = AdmissionTicketResponse.of( +// url, +// tuple.get(profile.userName), +// tuple.get(profile.birth), +// tuple.get(applicationSchool.examinationNumber), +// subjectNames, +// tuple.get(applicationSchool.schoolName) +// ); +// +// return new ApplicationListResponse( +// tuple.get(payment.paymentKey), +// tuple.get(applicationSchool.examinationNumber), +// tuple.get(profile.userName), +// tuple.get(profile.gender).getGenderName(), +// tuple.get(profile.birth), +// tuple.get(profile.phoneNumber), +// tuple.get(application.guardianPhoneNumber), +// tuple.get(profile.education).getEducationName(), +// tuple.get(profile.schoolInfo.schoolName), +// tuple.get(profile.grade).getGradeName(), +// lunchName, +// subjectNames, +// tuple.get(applicationSchool.schoolName), +// tuple.get(applicationSchool.examDate), +// tuple.get(admissionTicketImage.fileName), +// tuple.get(payment.paymentStatus), +// tuple.get(payment.paymentMethod), +// tuple.get(application.createdAt), +// admissionTicket +// ); +// } +// +// private ApplicationExcelDto mapToExcel(Tuple tuple, Set subjects) { +// Set subjectNames = subjects.stream() +// .map(Subject::getSubjectName) +// .collect(Collectors.toSet()); +// +// String lunchName = tuple.get(applicationSchool.lunch).getLunchName(); +// String genderName = tuple.get(profile.gender).getGenderName(); +// String gradeName = tuple.get(profile.grade).getGradeName(); +// String educationName = tuple.get(profile.education).getEducationName(); +// String appliedAt = tuple.get(application.createdAt) +// .format(EXCEL_DT_FORMATTER); +// +// return new ApplicationExcelDto( +// tuple.get(payment.paymentKey), +// tuple.get(applicationSchool.examinationNumber), +// tuple.get(profile.userName), +// genderName, +// tuple.get(profile.birth), +// tuple.get(profile.phoneNumber), +// tuple.get(application.guardianPhoneNumber), +// educationName, +// tuple.get(profile.schoolInfo.schoolName), +// gradeName, +// lunchName, +// subjectNames, +// tuple.get(applicationSchool.schoolName), +// tuple.get(applicationSchool.examDate), +// tuple.get(admissionTicketImage.fileName), +// tuple.get(payment.paymentStatus), +// tuple.get(payment.paymentMethod), +// appliedAt +// ); +// } +// +// private String getAdmissionTicketImageUrl(String s3Key) { +// if (s3Key == null || s3Key.isBlank()) { +// return null; +// } +// return s3Service.getPreSignedUrl(s3Key); +// } +//} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/domain/admin/RefundQueryRepositoryImpl.java b/src/main/java/life/mosu/mosuserver/domain/admin/RefundQueryRepositoryImpl.java index 3853ff13..540977b2 100644 --- a/src/main/java/life/mosu/mosuserver/domain/admin/RefundQueryRepositoryImpl.java +++ b/src/main/java/life/mosu/mosuserver/domain/admin/RefundQueryRepositoryImpl.java @@ -1,84 +1,84 @@ -package life.mosu.mosuserver.domain.admin; - -import static life.mosu.mosuserver.domain.base.BaseTimeEntity.formatDate; - -import com.querydsl.core.Tuple; -import com.querydsl.jpa.impl.JPAQuery; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.util.List; -import life.mosu.mosuserver.domain.application.QApplicationJpaEntity; -import life.mosu.mosuserver.domain.applicationschool.QApplicationSchoolJpaEntity; -import life.mosu.mosuserver.domain.payment.PaymentMethod; -import life.mosu.mosuserver.domain.payment.QPaymentJpaEntity; -import life.mosu.mosuserver.domain.profile.QProfileJpaEntity; -import life.mosu.mosuserver.domain.refund.QRefundJpaEntity; -import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.PageImpl; -import org.springframework.data.domain.Pageable; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class RefundQueryRepositoryImpl implements RefundQueryRepository { - - private final JPAQueryFactory queryFactory; - - QRefundJpaEntity refund = QRefundJpaEntity.refundJpaEntity; - QApplicationSchoolJpaEntity appSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity; - QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity; - QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity; - QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity; - - @Override - public Page searchAllRefunds(Pageable pageable) { - long total = baseQuery().fetch().size(); - - List content = baseQuery() - .offset(pageable.getOffset()) - .limit(pageable.getPageSize()) - .fetch() - .stream() - .map(this::mapToResponse) - .toList(); - - return new PageImpl<>(content, pageable, total); - - } - - private JPAQuery baseQuery() { - return queryFactory - .select( - refund.id, - appSchool.examinationNumber, - profile.userName, - profile.phoneNumber, - refund.createdAt, - refund.agreedAt, - payment.paymentMethod, - refund.reason - ) - .from(refund) - .leftJoin(appSchool).on(refund.applicationSchoolId.eq(appSchool.id)) - .leftJoin(application).on(appSchool.applicationId.eq(application.id)) - .leftJoin(profile).on(profile.userId.eq(application.userId)) - .leftJoin(payment).on(payment.applicationSchoolId.eq(appSchool.id)); - } - - private RefundListResponse mapToResponse(Tuple tuple) { - PaymentMethod paymentMethod = tuple.get(payment.paymentMethod); - return new RefundListResponse( - tuple.get(refund.id), - tuple.get(appSchool.examinationNumber), - tuple.get(profile.userName), - tuple.get(profile.phoneNumber), - formatDate(tuple.get(refund.createdAt)), - formatDate(tuple.get(refund.agreedAt)), - paymentMethod != null ? paymentMethod.getName() : "N/A", - tuple.get(refund.reason) - ); - } - - -} \ No newline at end of file +//package life.mosu.mosuserver.domain.admin; +// +//import static life.mosu.mosuserver.domain.base.BaseTimeEntity.formatDate; +// +//import com.querydsl.core.Tuple; +//import com.querydsl.jpa.impl.JPAQuery; +//import com.querydsl.jpa.impl.JPAQueryFactory; +//import java.util.List; +//import life.mosu.mosuserver.domain.application.QApplicationJpaEntity; +//import life.mosu.mosuserver.domain.applicationschoolX.QApplicationSchoolJpaEntity; +//import life.mosu.mosuserver.domain.payment.PaymentMethod; +//import life.mosu.mosuserver.domain.payment.QPaymentJpaEntity; +//import life.mosu.mosuserver.domain.profile.QProfileJpaEntity; +//import life.mosu.mosuserver.domain.refund.QRefundJpaEntity; +//import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse; +//import lombok.RequiredArgsConstructor; +//import org.springframework.data.domain.Page; +//import org.springframework.data.domain.PageImpl; +//import org.springframework.data.domain.Pageable; +//import org.springframework.stereotype.Repository; +// +//@Repository +//@RequiredArgsConstructor +//public class RefundQueryRepositoryImpl implements RefundQueryRepository { +// +// private final JPAQueryFactory queryFactory; +// +// QRefundJpaEntity refund = QRefundJpaEntity.refundJpaEntity; +// QApplicationSchoolJpaEntity appSchool = QApplicationSchoolJpaEntity.applicationSchoolJpaEntity; +// QApplicationJpaEntity application = QApplicationJpaEntity.applicationJpaEntity; +// QProfileJpaEntity profile = QProfileJpaEntity.profileJpaEntity; +// QPaymentJpaEntity payment = QPaymentJpaEntity.paymentJpaEntity; +// +// @Override +// public Page searchAllRefunds(Pageable pageable) { +// long total = baseQuery().fetch().size(); +// +// List content = baseQuery() +// .offset(pageable.getOffset()) +// .limit(pageable.getPageSize()) +// .fetch() +// .stream() +// .map(this::mapToResponse) +// .toList(); +// +// return new PageImpl<>(content, pageable, total); +// +// } +// +// private JPAQuery baseQuery() { +// return queryFactory +// .select( +// refund.id, +// appSchool.examinationNumber, +// profile.userName, +// profile.phoneNumber, +// refund.createdAt, +// refund.agreedAt, +// payment.paymentMethod, +// refund.reason +// ) +// .from(refund) +// .leftJoin(appSchool).on(refund.applicationSchoolId.eq(appSchool.id)) +// .leftJoin(application).on(appSchool.applicationId.eq(application.id)) +// .leftJoin(profile).on(profile.userId.eq(application.userId)) +// .leftJoin(payment).on(payment.applicationSchoolId.eq(appSchool.id)); +// } +// +// private RefundListResponse mapToResponse(Tuple tuple) { +// PaymentMethod paymentMethod = tuple.get(payment.paymentMethod); +// return new RefundListResponse( +// tuple.get(refund.id), +// tuple.get(appSchool.examinationNumber), +// tuple.get(profile.userName), +// tuple.get(profile.phoneNumber), +// formatDate(tuple.get(refund.createdAt)), +// formatDate(tuple.get(refund.agreedAt)), +// paymentMethod != null ? paymentMethod.getName() : "N/A", +// tuple.get(refund.reason) +// ); +// } +// +// +//} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/domain/application/AdmissionTicketImageJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/application/AdmissionTicketImageJpaRepository.java deleted file mode 100644 index 1202a7c3..00000000 --- a/src/main/java/life/mosu/mosuserver/domain/application/AdmissionTicketImageJpaRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package life.mosu.mosuserver.domain.application; - -import org.springframework.data.jpa.repository.JpaRepository; - -public interface AdmissionTicketImageJpaRepository extends JpaRepository { - - AdmissionTicketImageJpaEntity findByApplicationId(Long applicationId); -} 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 93484f15..a4cadeeb 100644 --- a/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaEntity.java @@ -26,8 +26,8 @@ public class ApplicationJpaEntity extends BaseTimeEntity { @Column(name = "user_id") private Long userId; - @Column(name = "guardian_phone_number") - private String guardianPhoneNumber; + @Column(name = "parent_phone_number") + private String parentPhoneNumber; @Column(name = "agreed_to_notices") private Boolean agreedToNotices; @@ -38,13 +38,13 @@ public class ApplicationJpaEntity extends BaseTimeEntity { @Builder public ApplicationJpaEntity( final Long userId, - final String guardianPhoneNumber, + final String parentPhoneNumber, final boolean agreedToNotices, final boolean agreedToRefundPolicy ) { this.userId = userId; - this.guardianPhoneNumber = guardianPhoneNumber; + this.parentPhoneNumber = parentPhoneNumber; this.agreedToNotices = agreedToNotices; this.agreedToRefundPolicy = agreedToRefundPolicy; } diff --git a/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaRepository.java index da55fe09..88f25df6 100644 --- a/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/application/ApplicationJpaRepository.java @@ -2,8 +2,18 @@ import java.util.List; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; public interface ApplicationJpaRepository extends JpaRepository { List findAllByUserId(Long userId); + + @Query("SELECT CASE WHEN COUNT(a) > 0 THEN true ELSE false END " + + "FROM ApplicationJpaEntity a " + + "JOIN ExamApplicationJpaEntity ea ON a.id = ea.applicationId " + + "JOIN ExamJpaEntity e ON ea.examId = e.id " + + "WHERE a.userId = :userId " + + "AND e.id IN :examIds") + boolean existsByUserIdAndExamIds(@Param("userId") Long userId, @Param("examIds") List examIds); } diff --git a/src/main/java/life/mosu/mosuserver/domain/application/AdmissionTicketImageJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/application/ExamTicketImageJpaEntity.java similarity index 56% rename from src/main/java/life/mosu/mosuserver/domain/application/AdmissionTicketImageJpaEntity.java rename to src/main/java/life/mosu/mosuserver/domain/application/ExamTicketImageJpaEntity.java index 1e06851e..1bae40c6 100644 --- a/src/main/java/life/mosu/mosuserver/domain/application/AdmissionTicketImageJpaEntity.java +++ b/src/main/java/life/mosu/mosuserver/domain/application/ExamTicketImageJpaEntity.java @@ -1,6 +1,11 @@ package life.mosu.mosuserver.domain.application; -import jakarta.persistence.*; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; import life.mosu.mosuserver.infra.storage.domain.File; import life.mosu.mosuserver.infra.storage.domain.Visibility; import lombok.Builder; @@ -11,21 +16,22 @@ @Getter @Entity -@Table(name = "admission_ticket_image") +@Table(name = "exam_ticket_image") @NoArgsConstructor(access = lombok.AccessLevel.PROTECTED) @SoftDelete -public class AdmissionTicketImageJpaEntity extends File { +public class ExamTicketImageJpaEntity extends File { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "admission_ticket_image_id", nullable = false) + @Column(name = "exam_ticket_image_id", nullable = false) private Long id; @Column(name = "application_id", nullable = false) private Long applicationId; @Builder - public AdmissionTicketImageJpaEntity(final String fileName, final String s3Key, final Visibility visibility, final Long applicationId) { + public ExamTicketImageJpaEntity(final String fileName, final String s3Key, + final Visibility visibility, final Long applicationId) { super(fileName, s3Key, visibility); this.applicationId = applicationId; } diff --git a/src/main/java/life/mosu/mosuserver/domain/application/ExamTicketImageJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/application/ExamTicketImageJpaRepository.java new file mode 100644 index 00000000..1d0c05ab --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/application/ExamTicketImageJpaRepository.java @@ -0,0 +1,9 @@ +package life.mosu.mosuserver.domain.application; + +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExamTicketImageJpaRepository extends + JpaRepository { + + ExamTicketImageJpaEntity findByApplicationId(Long applicationId); +} diff --git a/src/main/java/life/mosu/mosuserver/domain/applicationschool/ApplicationSchoolJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/applicationschool/ApplicationSchoolJpaEntity.java deleted file mode 100644 index 66185540..00000000 --- a/src/main/java/life/mosu/mosuserver/domain/applicationschool/ApplicationSchoolJpaEntity.java +++ /dev/null @@ -1,94 +0,0 @@ -package life.mosu.mosuserver.domain.applicationschool; - -import jakarta.persistence.*; -import life.mosu.mosuserver.domain.application.Lunch; -import life.mosu.mosuserver.domain.application.Subject; -import life.mosu.mosuserver.domain.base.BaseTimeEntity; -import life.mosu.mosuserver.domain.school.AddressJpaVO; -import life.mosu.mosuserver.domain.school.Area; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -import java.time.LocalDate; -import java.util.HashSet; -import java.util.Set; - -@Entity -@Table(name = "application_school") -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class ApplicationSchoolJpaEntity extends BaseTimeEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "application_school_id") - private Long id; - - @Column(name = "application_id") - private Long applicationId; - - @Column(name = "user_id") - private Long userId; - - @Column(name = "school_id") - private Long schoolId; - - @Column(name = "application_school_name") - private String schoolName; - - @Enumerated(EnumType.STRING) - private Area area; - - @Embedded - private AddressJpaVO address; - - @Column(name = "exam_date") - private LocalDate examDate; - - @Enumerated(EnumType.STRING) - private Lunch lunch; - - @Column(name = "examination_number") - private String examinationNumber; - - @ElementCollection(fetch = FetchType.EAGER) - @CollectionTable(name = "exam_subject", joinColumns = @JoinColumn(name = "application_school_id")) - @Enumerated(EnumType.STRING) - private Set subjects = new HashSet<>(); - - - @Builder - public ApplicationSchoolJpaEntity( - final Long userId, - final Long applicationId, - final Long schoolId, - final String schoolName, - final Area area, - final AddressJpaVO address, - final LocalDate examDate, - final Lunch lunch, - final String examinationNumber, - final Set subjects - ) { - this.userId = userId; - this.applicationId = applicationId; - this.schoolId = schoolId; - this.schoolName = schoolName; - this.area = area; - this.address = address; - this.examDate = examDate; - this.lunch = lunch; - this.examinationNumber = examinationNumber; - this.subjects = subjects; - } - - public void generateExaminationNumber() { - - } - - public void updateSubjects(Set subjects) { - this.subjects = subjects; - } -} diff --git a/src/main/java/life/mosu/mosuserver/domain/applicationschool/ApplicationSchoolJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/applicationschool/ApplicationSchoolJpaRepository.java deleted file mode 100644 index d850d8d3..00000000 --- a/src/main/java/life/mosu/mosuserver/domain/applicationschool/ApplicationSchoolJpaRepository.java +++ /dev/null @@ -1,42 +0,0 @@ -package life.mosu.mosuserver.domain.applicationschool; - -import java.util.List; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; -import org.springframework.data.repository.query.Param; - -public interface ApplicationSchoolJpaRepository extends - JpaRepository { - - boolean existsByUserIdAndSchoolId(Long userId, Long schoolId); - - List findAllByApplicationId(Long applicationId); - - boolean existsByApplicationId(Long applicationId); - - - @Query("SELECT COUNT(a) = :size FROM ApplicationSchoolJpaEntity a WHERE a.id IN :applicationSchoolIds") - boolean existsAllByIds(@Param("applicationSchoolIds") List applicationSchoolIds, - @Param("size") long size); - - @Query(""" - SELECT new life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolNotifyProjection( - p.paymentKey, a.examDate, a.schoolName, a.lunch - ) - FROM ApplicationSchoolJpaEntity a - LEFT JOIN PaymentJpaEntity p ON a.id = p.applicationSchoolId - WHERE a.id = :applicationSchoolId - """) - ApplicationSchoolNotifyProjection findPaymentByApplicationSchoolId(Long applicationSchoolId); - - @Query(""" - SELECT new life.mosu.mosuserver.domain.applicationschool.OneWeekNotifyProjection( - p.paymentKey, a.examDate, a.schoolName - ) - FROM ApplicationSchoolJpaEntity a - JOIN PaymentJpaEntity p ON a.id = p.applicationSchoolId - WHERE a.id = :applicationSchoolId - """) - OneWeekNotifyProjection findOneWeekNotifyByApplicationSchoolId(Long applicationSchoolId); - -} diff --git a/src/main/java/life/mosu/mosuserver/domain/applicationschool/ApplicationSchoolNotifyProjection.java b/src/main/java/life/mosu/mosuserver/domain/applicationschool/ApplicationSchoolNotifyProjection.java deleted file mode 100644 index 00d64e07..00000000 --- a/src/main/java/life/mosu/mosuserver/domain/applicationschool/ApplicationSchoolNotifyProjection.java +++ /dev/null @@ -1,13 +0,0 @@ -package life.mosu.mosuserver.domain.applicationschool; - -import java.time.LocalDate; -import life.mosu.mosuserver.domain.application.Lunch; - -public record ApplicationSchoolNotifyProjection( - String paymentKey, - LocalDate examDate, - String schoolName, - Lunch lunch -) { - -} diff --git a/src/main/java/life/mosu/mosuserver/domain/applicationschool/OneWeekNotifyProjection.java b/src/main/java/life/mosu/mosuserver/domain/applicationschool/OneWeekNotifyProjection.java deleted file mode 100644 index cb63335b..00000000 --- a/src/main/java/life/mosu/mosuserver/domain/applicationschool/OneWeekNotifyProjection.java +++ /dev/null @@ -1,11 +0,0 @@ -package life.mosu.mosuserver.domain.applicationschool; - -import java.time.LocalDate; - -public record OneWeekNotifyProjection( - String paymentKey, - LocalDate examDate, - String schoolName -) { - -} diff --git a/src/main/java/life/mosu/mosuserver/domain/school/AddressJpaVO.java b/src/main/java/life/mosu/mosuserver/domain/exam/AddressJpaVO.java similarity index 82% rename from src/main/java/life/mosu/mosuserver/domain/school/AddressJpaVO.java rename to src/main/java/life/mosu/mosuserver/domain/exam/AddressJpaVO.java index a54a89ec..463addb6 100644 --- a/src/main/java/life/mosu/mosuserver/domain/school/AddressJpaVO.java +++ b/src/main/java/life/mosu/mosuserver/domain/exam/AddressJpaVO.java @@ -1,4 +1,4 @@ -package life.mosu.mosuserver.domain.school; +package life.mosu.mosuserver.domain.exam; import jakarta.persistence.Column; import jakarta.persistence.Embeddable; @@ -23,9 +23,9 @@ public class AddressJpaVO { @Builder public AddressJpaVO( - String zipcode, - String street, - String detail + String zipcode, + String street, + String detail ) { this.zipcode = zipcode; this.street = street; diff --git a/src/main/java/life/mosu/mosuserver/domain/school/Area.java b/src/main/java/life/mosu/mosuserver/domain/exam/Area.java similarity index 93% rename from src/main/java/life/mosu/mosuserver/domain/school/Area.java rename to src/main/java/life/mosu/mosuserver/domain/exam/Area.java index b894ee47..aa9f2cef 100644 --- a/src/main/java/life/mosu/mosuserver/domain/school/Area.java +++ b/src/main/java/life/mosu/mosuserver/domain/exam/Area.java @@ -1,4 +1,4 @@ -package life.mosu.mosuserver.domain.school; +package life.mosu.mosuserver.domain.exam; import java.util.Arrays; import life.mosu.mosuserver.global.exception.CustomRuntimeException; diff --git a/src/main/java/life/mosu/mosuserver/domain/exam/ExamInfoProjection.java b/src/main/java/life/mosu/mosuserver/domain/exam/ExamInfoProjection.java new file mode 100644 index 00000000..4adcb80e --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/exam/ExamInfoProjection.java @@ -0,0 +1,14 @@ +package life.mosu.mosuserver.domain.exam; + +import java.time.LocalDate; +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class ExamInfoProjection { + + private LocalDate examDate; + private String examNumber; + private String SchoolName; +} diff --git a/src/main/java/life/mosu/mosuserver/domain/exam/ExamJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/exam/ExamJpaEntity.java new file mode 100644 index 00000000..d8d55f7e --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/exam/ExamJpaEntity.java @@ -0,0 +1,63 @@ +package life.mosu.mosuserver.domain.exam; + + +import jakarta.persistence.Column; +import jakarta.persistence.Embedded; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import java.time.LocalDate; +import java.time.LocalDateTime; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "exam") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ExamJpaEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "school_name") + private String schoolName; + + @Embedded + private AddressJpaVO address; + + @Enumerated(EnumType.STRING) + private Area area; + + @Column(name = "capacity") + private Integer capacity; + + @Column(name = "deadline_time") + private LocalDateTime deadlineTime; + + @Column(name = "exam_date", nullable = false) + private LocalDate examDate; + + @Builder + public ExamJpaEntity( + String schoolName, + AddressJpaVO address, + Area area, + LocalDate examDate, + Integer capacity, + LocalDateTime deadlineTime) { + this.schoolName = schoolName; + this.address = address; + this.area = area; + this.examDate = examDate; + this.capacity = capacity; + this.deadlineTime = deadlineTime; + } +} diff --git a/src/main/java/life/mosu/mosuserver/domain/exam/ExamJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/exam/ExamJpaRepository.java new file mode 100644 index 00000000..5577bd2f --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/exam/ExamJpaRepository.java @@ -0,0 +1,47 @@ +package life.mosu.mosuserver.domain.exam; + +import io.lettuce.core.dynamic.annotation.Param; +import java.time.LocalDate; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +public interface ExamJpaRepository extends JpaRepository { + + List findByArea(Area area); + + @Query("SELECT DISTINCT e.area FROM ExamJpaEntity e") + List findDistinctAreas(); + + @Query(""" + SELECT new life.mosu.mosuserver.domain.exam.ExamInfoProjection( + e.examDate, + ea.examNumber, + e.schoolName + ) + FROM ExamApplicationJpaEntity ea + JOIN ExamJpaEntity e ON ea.examId = e.id + WHERE ea.examId = :examId + """) + Optional findExamInfo(@Param("examId") Long examId); + + @Query(""" + SELECT e + FROM ExamJpaEntity e + WHERE e.examDate > :today + """) + List findUpcomingExamInfo(@Param("today") LocalDate today); + + @Query(""" + SELECT new life.mosu.mosuserver.domain.exam.SchoolExamCountProjection( + e.schoolName, + COUNT(ea.id) + ) + FROM ExamApplicationJpaEntity ea + JOIN ExamJpaEntity e ON ea.examId = e.id + GROUP BY e.schoolName + """) + List countApplicationsGroupedBySchoolName(); + +} diff --git a/src/main/java/life/mosu/mosuserver/domain/exam/SchoolExamCountProjection.java b/src/main/java/life/mosu/mosuserver/domain/exam/SchoolExamCountProjection.java new file mode 100644 index 00000000..4d242dda --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/exam/SchoolExamCountProjection.java @@ -0,0 +1,8 @@ +package life.mosu.mosuserver.domain.exam; + +public record SchoolExamCountProjection( + String schoolName, + Long applicationCount +) { + +} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamApplicationJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamApplicationJpaEntity.java new file mode 100644 index 00000000..dddf42cd --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamApplicationJpaEntity.java @@ -0,0 +1,54 @@ +package life.mosu.mosuserver.domain.examapplication; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "exam_application") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ExamApplicationJpaEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "application_id") + private Long applicationId; + + @Column(name = "exam_id") + private Long examId; + + @Column(name = "lunch_id") + private Long lunchId; + + @Column(name = "exam_number") + private String examNumber; + + @Builder + public ExamApplicationJpaEntity(Long applicationId, Long examId, Long lunchId) { + this.applicationId = applicationId; + this.examId = examId; + this.lunchId = lunchId; + } + + public static ExamApplicationJpaEntity create(Long applicationId, Long examId, Long lunchId) { + return ExamApplicationJpaEntity.builder() + .applicationId(applicationId) + .examId(examId) + .lunchId(lunchId) + .build(); + } + + public void grantExamNumber(String examNumber) { + this.examNumber = examNumber; + } +} diff --git a/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamApplicationJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamApplicationJpaRepository.java new file mode 100644 index 00000000..b3b6707e --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamApplicationJpaRepository.java @@ -0,0 +1,11 @@ +package life.mosu.mosuserver.domain.examapplication; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExamApplicationJpaRepository extends + JpaRepository { + + List findByApplicationId(Long applicationId); + +} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamNumberGenerationService.java b/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamNumberGenerationService.java new file mode 100644 index 00000000..cf19bc18 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamNumberGenerationService.java @@ -0,0 +1,20 @@ +package life.mosu.mosuserver.domain.examapplication; + +import java.util.List; +import java.util.UUID; +import org.springframework.stereotype.Component; + +@Component +public class ExamNumberGenerationService { + + public void grantTo(List examApplicationEntities) { + examApplicationEntities.forEach(examApplicationEntity -> { + String examNumber = generateExamNumber(); + examApplicationEntity.grantExamNumber(examNumber); + }); + } + + private String generateExamNumber() { + return UUID.randomUUID().toString(); + } +} diff --git a/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamSubjectJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamSubjectJpaEntity.java new file mode 100644 index 00000000..df6eb494 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamSubjectJpaEntity.java @@ -0,0 +1,42 @@ +package life.mosu.mosuserver.domain.examapplication; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import life.mosu.mosuserver.domain.application.Subject; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Entity +@Table(name = "exam_subject") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class ExamSubjectJpaEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "exam_application_id") + private Long examApplicationId; + + @Enumerated(EnumType.STRING) + private Subject subject; + + @Builder + public ExamSubjectJpaEntity(Long examApplicationId, Subject subject) { + this.examApplicationId = examApplicationId; + this.subject = subject; + } + + public static ExamSubjectJpaEntity create(Long examApplicationId, Subject subject) { + return new ExamSubjectJpaEntity(examApplicationId, subject); + } +} diff --git a/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamSubjectJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamSubjectJpaRepository.java new file mode 100644 index 00000000..001a788e --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/examapplication/ExamSubjectJpaRepository.java @@ -0,0 +1,11 @@ +package life.mosu.mosuserver.domain.examapplication; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ExamSubjectJpaRepository extends JpaRepository { + + List findByExamApplicationId(Long examApplicationId); + + void deleteByExamApplicationId(Long examApplicationId); +} diff --git a/src/main/java/life/mosu/mosuserver/domain/lunch/LunchJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/lunch/LunchJpaEntity.java new file mode 100644 index 00000000..d8f306ed --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/lunch/LunchJpaEntity.java @@ -0,0 +1,40 @@ +package life.mosu.mosuserver.domain.lunch; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + + +@Getter +@Entity +@Table(name = "lunch") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class LunchJpaEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(name = "lunch_name", nullable = false) + private String name; + + @Column(name = "lunch_price", nullable = false) + private Integer price; + + @Column(name = "exam_id") + private Long examId; + + @Builder + public LunchJpaEntity(String name, Integer price, Long examId) { + this.name = name; + this.price = price; + this.examId = examId; + } +} diff --git a/src/main/java/life/mosu/mosuserver/domain/lunch/LunchJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/lunch/LunchJpaRepository.java new file mode 100644 index 00000000..05645b5a --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/domain/lunch/LunchJpaRepository.java @@ -0,0 +1,9 @@ +package life.mosu.mosuserver.domain.lunch; + +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface LunchJpaRepository extends JpaRepository { + + List findByExamId(Long examId); +} diff --git a/src/main/java/life/mosu/mosuserver/domain/refund/RefundJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/refund/RefundJpaRepository.java index f499039f..c27bfe50 100644 --- a/src/main/java/life/mosu/mosuserver/domain/refund/RefundJpaRepository.java +++ b/src/main/java/life/mosu/mosuserver/domain/refund/RefundJpaRepository.java @@ -1,24 +1,22 @@ package life.mosu.mosuserver.domain.refund; -import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; public interface RefundJpaRepository extends JpaRepository { - @Query(""" - SELECT new life.mosu.mosuserver.domain.refund.RefundNotifyProjection( - p.paymentKey, - a.examDate, - a.schoolName, - p.paymentMethod, - r.reason - ) - FROM RefundJpaEntity r - LEFT JOIN PaymentJpaEntity p ON r.applicationSchoolId = p.applicationSchoolId - LEFT JOIN ApplicationSchoolJpaEntity a ON r.applicationSchoolId = a.id - WHERE r.applicationSchoolId = :applicationSchoolId - """) - Optional findRefundByApplicationSchoolId(Long applicationSchoolId); +// @Query(""" +// SELECT new life.mosu.mosuserver.domain.refund.RefundNotifyProjection( +// p.paymentKey, +// a.examDate, +// a.schoolName, +// p.paymentMethod, +// r.reason +// ) +// FROM RefundJpaEntity r +// LEFT JOIN PaymentJpaEntity p ON r.applicationSchoolId = p.applicationSchoolId +// LEFT JOIN ApplicationSchoolJpaEntity a ON r.applicationSchoolId = a.id +// WHERE r.applicationSchoolId = :applicationSchoolId +// """) +// Optional findRefundByApplicationSchoolId(Long applicationSchoolId); } diff --git a/src/main/java/life/mosu/mosuserver/domain/school/LunchMenu.java b/src/main/java/life/mosu/mosuserver/domain/school/LunchMenu.java deleted file mode 100644 index 148a7b3c..00000000 --- a/src/main/java/life/mosu/mosuserver/domain/school/LunchMenu.java +++ /dev/null @@ -1,28 +0,0 @@ -package life.mosu.mosuserver.domain.school; - -import jakarta.persistence.Embeddable; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import life.mosu.mosuserver.domain.application.Lunch; -import lombok.AccessLevel; -import lombok.AllArgsConstructor; -import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Embeddable -@Getter -@EqualsAndHashCode() -@AllArgsConstructor(access = AccessLevel.PRIVATE) -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class LunchMenu { - - @Enumerated(EnumType.STRING) - private Lunch lunch; - - private Integer lunchPrice; - - public static LunchMenu of(Lunch lunch, Integer lunchPrice) { - return new LunchMenu(lunch, lunchPrice); - } -} diff --git a/src/main/java/life/mosu/mosuserver/domain/school/SchoolApplicationProjection.java b/src/main/java/life/mosu/mosuserver/domain/school/SchoolApplicationProjection.java deleted file mode 100644 index 86b24bfb..00000000 --- a/src/main/java/life/mosu/mosuserver/domain/school/SchoolApplicationProjection.java +++ /dev/null @@ -1,5 +0,0 @@ -package life.mosu.mosuserver.domain.school; - -public record SchoolApplicationProjection(Long schoolId, String schoolName, Long count) { - -} diff --git a/src/main/java/life/mosu/mosuserver/domain/school/SchoolJpaEntity.java b/src/main/java/life/mosu/mosuserver/domain/school/SchoolJpaEntity.java deleted file mode 100644 index 22ba639f..00000000 --- a/src/main/java/life/mosu/mosuserver/domain/school/SchoolJpaEntity.java +++ /dev/null @@ -1,82 +0,0 @@ -package life.mosu.mosuserver.domain.school; - - -import jakarta.persistence.CollectionTable; -import jakarta.persistence.Column; -import jakarta.persistence.ElementCollection; -import jakarta.persistence.Embedded; -import jakarta.persistence.Entity; -import jakarta.persistence.EnumType; -import jakarta.persistence.Enumerated; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.Table; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.Set; -import life.mosu.mosuserver.presentation.school.dto.SchoolEditRequest; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@Table(name = "school") -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class SchoolJpaEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - @Column(name = "school_id") - private Long id; - - @Column(name = "school_name") - private String schoolName; - - @Embedded - private AddressJpaVO address; - - @Enumerated(EnumType.STRING) - private Area area; - - @ElementCollection - @CollectionTable(name = "school_lunch_menu") - private Set lunchMenus = Collections.synchronizedSet(new LinkedHashSet<>()); - - @Column(name = "deadline_time") - private LocalDateTime deadlineTime; - - @Column(name = "exam_date") - private LocalDate examDate; - - @Column(name = "capacity") - private Long capacity; - - @Builder - public SchoolJpaEntity(String schoolName, Area area, LocalDate examDate, - Long capacity, LocalDateTime deadlineTime, AddressJpaVO address, - Set lunchMenu - ) { - this.schoolName = schoolName; - this.area = area; - this.examDate = examDate; - this.capacity = capacity; - this.deadlineTime = deadlineTime; - this.address = address; - this.lunchMenus = lunchMenu; - } - - public void updateInfo(SchoolEditRequest request) { - this.schoolName = request.schoolName(); - this.area = Area.from(request.area()); - this.address = request.schoolAddress().toValueObject(); - this.examDate = request.examDate(); - this.lunchMenus = request.lunchMenus(); - this.deadlineTime = request.deadlineTime(); - this.capacity = request.capacity(); - } -} diff --git a/src/main/java/life/mosu/mosuserver/domain/school/SchoolJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/school/SchoolJpaRepository.java deleted file mode 100644 index ad0b7cc5..00000000 --- a/src/main/java/life/mosu/mosuserver/domain/school/SchoolJpaRepository.java +++ /dev/null @@ -1,23 +0,0 @@ -package life.mosu.mosuserver.domain.school; - -import java.time.LocalDate; -import java.util.List; -import java.util.Optional; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -public interface SchoolJpaRepository extends JpaRepository { - - @Query(""" - SELECT new life.mosu.mosuserver.domain.school.SchoolApplicationProjection(s.id, s.schoolName, COUNT(a)) - FROM SchoolJpaEntity s - LEFT JOIN ApplicationSchoolJpaEntity a ON a.schoolId = s.id - LEFT JOIN PaymentJpaEntity p ON p.applicationSchoolId = a.id - WHERE p.paymentStatus = 'DONE' - GROUP BY s.id, s.schoolName - """) - List countBySchoolNameGroupBy(); - - Optional findBySchoolNameAndAreaAndExamDate(String schoolName, Area area, - LocalDate examDate); -} diff --git a/src/main/java/life/mosu/mosuserver/domain/school/SchoolLunchJpaRepository.java b/src/main/java/life/mosu/mosuserver/domain/school/SchoolLunchJpaRepository.java new file mode 100644 index 00000000..e69de29b diff --git a/src/main/java/life/mosu/mosuserver/domain/school/SchoolQueryRepository.java b/src/main/java/life/mosu/mosuserver/domain/school/SchoolQueryRepository.java deleted file mode 100644 index 395d9973..00000000 --- a/src/main/java/life/mosu/mosuserver/domain/school/SchoolQueryRepository.java +++ /dev/null @@ -1,12 +0,0 @@ -package life.mosu.mosuserver.domain.school; - -import java.time.LocalDate; -import java.util.List; -import org.apache.commons.lang3.tuple.Triple; - -public interface SchoolQueryRepository { - - List findSchoolsByConditions( - List> conditions); - -} diff --git a/src/main/java/life/mosu/mosuserver/domain/school/SchoolQueryRepositoryImpl.java b/src/main/java/life/mosu/mosuserver/domain/school/SchoolQueryRepositoryImpl.java deleted file mode 100644 index 99d69be9..00000000 --- a/src/main/java/life/mosu/mosuserver/domain/school/SchoolQueryRepositoryImpl.java +++ /dev/null @@ -1,38 +0,0 @@ -package life.mosu.mosuserver.domain.school; - -import com.querydsl.core.BooleanBuilder; -import com.querydsl.jpa.impl.JPAQueryFactory; -import java.time.LocalDate; -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.apache.commons.lang3.tuple.Triple; -import org.springframework.stereotype.Repository; - -@Repository -@RequiredArgsConstructor -public class SchoolQueryRepositoryImpl implements SchoolQueryRepository { - - private final JPAQueryFactory queryFactory; - - @Override - public List findSchoolsByConditions( - List> conditions) { - BooleanBuilder builder = new BooleanBuilder(); - - for (Triple cond : conditions) { - builder.or( - QSchoolJpaEntity.schoolJpaEntity.schoolName.eq(cond.getLeft()) - .and(QSchoolJpaEntity.schoolJpaEntity.area.eq(cond.getMiddle())) - .and(QSchoolJpaEntity.schoolJpaEntity.examDate.eq(cond.getRight())) - ); - } - - return queryFactory - .select(QSchoolJpaEntity.schoolJpaEntity.id) - .from(QSchoolJpaEntity.schoolJpaEntity) - .where(builder) - .fetch(); - } - - -} 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 4a0d5563..b2989c14 100644 --- a/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java +++ b/src/main/java/life/mosu/mosuserver/global/exception/ErrorCode.java @@ -36,13 +36,14 @@ public enum ErrorCode { WRONG_AREA_TYPE(HttpStatus.BAD_REQUEST, "잘못된 지역명 입니다."), // 신청 학교 관련 에러 - APPLICATION_SCHOOL_NOT_FOUND(HttpStatus.NOT_FOUND, "신청한 학교 정보를 찾을 수 없습니다."), + EXAM_APPLICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "신청한 학교 정보를 찾을 수 없습니다."), APPLICATION_SCHOOL_LIST_NOT_FOUND(HttpStatus.NOT_FOUND, "신청한 학교 목록을 찾을 수 없습니다."), - APPLICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "신청 정보를 찾을 수 없습니다."), + APPLICATION_SCHOOL_NOT_FOUND(HttpStatus.NOT_FOUND, "신청 정보를 찾을 수 없습니다."), APPLICATION_LIST_NOT_FOUND(HttpStatus.NOT_FOUND, "신청 내역을 찾을 수 없습니다."), SCHOOL_NOT_FOUND(HttpStatus.NOT_FOUND, "학교가 존재하지 않습니다."), SCHOOL_FULL(HttpStatus.CONFLICT, "해당 학교의 신청 정원이 모두 찼습니다."), APPLICATION_SCHOOL_ALREADY_APPLIED(HttpStatus.CONFLICT, "해당 학교를 이미 예약하였습니다."), + APPLICATION_SCHOOL_DUPLICATED(HttpStatus.BAD_REQUEST, "동일 일자의 같은 학교를 신청할 수 없습니다."), // 프로필 관련 에러 PROFILE_ALREADY_EXISTS(HttpStatus.CONFLICT, "프로필이 이미 존재합니다."), @@ -87,7 +88,20 @@ public enum ErrorCode { // 알림톡 관련 에러 NOTIFY_STATUS_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 알림을 찾을 수 없습니다."), - STRATEGY_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 전략을 찾을 수 없습니다."); + STRATEGY_NOT_FOUND(HttpStatus.NOT_FOUND, "해당 전략을 찾을 수 없습니다."), + + // multi-insert 관련 + EXAM_APPLICATION_MULTI_INSERT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "신청 학교 정보 삽입 실패하였습니다."), + EXAM_SUBJECT_MULTI_INSERT_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "응시 과목 정보 삽입 실패하였습니다."), + + + // 시험 관련 에러 + EXAM_NOT_FOUND(HttpStatus.NOT_FOUND, "시험 정보를 찾을 수 없습니다."), + + //lunch 관련 + LUNCH_NOT_FOUND(HttpStatus.NOT_FOUND, "점심 정보를 찾을 수 없습니다."), + LUNCH_PRICE_UPDATE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "가격 수정에 실패하였습니다."); + private final HttpStatus status; private final String message; diff --git a/src/main/java/life/mosu/mosuserver/global/initializer/DatabaseInitializer.java b/src/main/java/life/mosu/mosuserver/global/initializer/DatabaseInitializer.java index b8cb99e3..a62faec4 100644 --- a/src/main/java/life/mosu/mosuserver/global/initializer/DatabaseInitializer.java +++ b/src/main/java/life/mosu/mosuserver/global/initializer/DatabaseInitializer.java @@ -1,380 +1,380 @@ -package life.mosu.mosuserver.global.initializer; - -import jakarta.annotation.PostConstruct; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashSet; -import java.util.List; -import java.util.Random; -import java.util.Set; -import java.util.UUID; -import life.mosu.mosuserver.domain.application.ApplicationJpaEntity; -import life.mosu.mosuserver.domain.application.ApplicationJpaRepository; -import life.mosu.mosuserver.domain.application.Lunch; -import life.mosu.mosuserver.domain.application.Subject; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaRepository; -import life.mosu.mosuserver.domain.event.DurationJpaVO; -import life.mosu.mosuserver.domain.event.EventJpaEntity; -import life.mosu.mosuserver.domain.event.EventJpaRepository; -import life.mosu.mosuserver.domain.inquiry.InquiryJpaEntity; -import life.mosu.mosuserver.domain.inquiry.InquiryJpaRepository; -import life.mosu.mosuserver.domain.inquiryAnswer.InquiryAnswerJpaEntity; -import life.mosu.mosuserver.domain.inquiryAnswer.InquiryAnswerJpaRepository; -import life.mosu.mosuserver.domain.notice.NoticeJpaEntity; -import life.mosu.mosuserver.domain.notice.NoticeJpaRepository; -import life.mosu.mosuserver.domain.payment.PaymentAmountVO; -import life.mosu.mosuserver.domain.payment.PaymentJpaEntity; -import life.mosu.mosuserver.domain.payment.PaymentMethod; -import life.mosu.mosuserver.domain.payment.PaymentRepository; -import life.mosu.mosuserver.domain.payment.PaymentStatus; -import life.mosu.mosuserver.domain.profile.Education; -import life.mosu.mosuserver.domain.profile.Gender; -import life.mosu.mosuserver.domain.profile.Grade; -import life.mosu.mosuserver.domain.profile.ProfileJpaEntity; -import life.mosu.mosuserver.domain.profile.ProfileJpaRepository; -import life.mosu.mosuserver.domain.profile.SchoolInfoJpaVO; -import life.mosu.mosuserver.domain.school.AddressJpaVO; -import life.mosu.mosuserver.domain.school.Area; -import life.mosu.mosuserver.domain.school.LunchMenu; -import life.mosu.mosuserver.domain.school.SchoolJpaEntity; -import life.mosu.mosuserver.domain.school.SchoolJpaRepository; -import life.mosu.mosuserver.domain.user.UserJpaEntity; -import life.mosu.mosuserver.domain.user.UserJpaRepository; -import life.mosu.mosuserver.domain.user.UserRole; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.security.crypto.password.PasswordEncoder; -import org.springframework.stereotype.Component; - -@Slf4j -@Component -@RequiredArgsConstructor -public class DatabaseInitializer { - - private final UserJpaRepository userRepository; - private final ProfileJpaRepository profileRepository; - private final SchoolJpaRepository schoolRepository; - private final ApplicationJpaRepository applicationRepository; - private final ApplicationSchoolJpaRepository applicationSchoolRepository; - private final InquiryJpaRepository inquiryJpaRepository; - private final NoticeJpaRepository noticeJpaRepository; - private final InquiryAnswerJpaRepository inquiryAnswerJpaRepository; - private final EventJpaRepository eventRepository; - private final PaymentRepository paymentRepository; - private final PasswordEncoder passwordEncoder; - - @PostConstruct - public void init() { - if (userRepository.count() > 0 || schoolRepository.count() > 0) { - log.info("이미 더미 데이터가 존재하여 전체 초기화를 건너뜁니다."); - return; - } - - log.info("전체 더미 데이터 초기화를 시작합니다..."); - Random random = new Random(); - - List createdUsers = initializeUsersAndProfiles(random); - List createdSchools = initializeSchools(); - List createdAppSchools = initializeApplications(createdUsers, - createdSchools, random); - initializePayments(createdAppSchools); - initializeBoardItems(createdUsers, random); - - log.info("모든 더미 데이터 초기화가 완료되었습니다."); - } - - private List initializeUsersAndProfiles(Random random) { - List createdUsers = new ArrayList<>(); - for (int i = 1; i <= 10; i++) { - UserJpaEntity user = UserJpaEntity.builder() - .loginId("user" + i) - .password(passwordEncoder.encode("password" + i + "!")) - .gender((i % 2 == 0) ? Gender.MALE : Gender.FEMALE) - .name((i % 2 == 0) ? "김철수" + i : "이영희" + i) - .birth(LocalDate.of(1990 + (i % 5), (i % 12) + 1, (i % 28) + 1)) - .agreedToTermsOfService(true) - .agreedToPrivacyPolicy(true) - .agreedToMarketing(random.nextBoolean()) - .userRole((i == 1) ? UserRole.ROLE_ADMIN : UserRole.ROLE_USER) - .build(); - userRepository.save(user); - createdUsers.add(user); - - ProfileJpaEntity profile = ProfileJpaEntity.builder() - .userId(user.getId()) - .userName(user.getName()) - .gender(user.getGender()) - .birth(user.getBirth()) - .phoneNumber("010-4870-5466") - .email("user" + i + "@example.com") - .education(Education.values()[random.nextInt(Education.values().length)]) - .schoolInfo(new SchoolInfoJpaVO("모수대학교" + (i % 3 + 1), "123-23", "서울시 모수구 모수동")) - .grade(Grade.values()[random.nextInt(Grade.values().length)]) - .build(); - - profile.registerRecommenderPhoneNumber((i % 3 == 0) ? "010-1234-5678" : null); - profileRepository.save(profile); - } - log.info("User 및 Profile 데이터 {}건 생성 완료.", createdUsers.size()); - return createdUsers; - } - - private List initializeSchools() { - List schools = new ArrayList<>(List.of( - SchoolJpaEntity.builder() - .schoolName("대치중학교") - .area(Area.DAECHI) - .address(new AddressJpaVO("06234", "서울특별시", "강남구 대치동 987")) - .examDate(LocalDate.of(2025, 10, 19)) - .capacity(532L) - .deadlineTime(LocalDateTime.of(2025, 10, 10, 23, 59)) - .lunchMenu( - Set.of( - LunchMenu.of( - Lunch.OPTION1, 12000 - ), - LunchMenu.of( - Lunch.OPTION2, 13000 - ) - ) - ) - .build(), - SchoolJpaEntity.builder() - .schoolName("목운중학교") - .area(Area.MOKDONG) - .address(new AddressJpaVO("07995", "서울특별시", "양천구 목동서로 369")) - .examDate(LocalDate.of(2025, 10, 26)) - .capacity(896L) - .deadlineTime(LocalDateTime.of(2025, 10, 17, 23, 59)) - .lunchMenu( - Set.of( - LunchMenu.of( - Lunch.OPTION1, 12000 - ), - LunchMenu.of( - Lunch.OPTION2, 13000 - ) - ) - ) - .build(), - SchoolJpaEntity.builder() - .schoolName("신서중학교") - .area(Area.MOKDONG) - .address(new AddressJpaVO("08018", "서울특별시", "양천구 신정로 250")) - .examDate(LocalDate.of(2025, 11, 2)) - .capacity(896L) - .deadlineTime(LocalDateTime.of(2025, 10, 24, 23, 59)) - .lunchMenu( - Set.of( - LunchMenu.of( - Lunch.OPTION4, 12000 - ), - LunchMenu.of( - Lunch.OPTION2, 13000 - ) - ) - ) - .build(), - SchoolJpaEntity.builder() - .schoolName("개원중학교") - .area(Area.DAECHI) - .address(new AddressJpaVO("06327", "서울특별시", "강남구 개포로 619")) - .examDate(LocalDate.of(2025, 10, 26)) - .capacity(840L) - .deadlineTime(LocalDateTime.of(2025, 10, 17, 23, 59)) - .lunchMenu( - Set.of( - LunchMenu.of( - Lunch.OPTION4, 12000 - ), - LunchMenu.of( - Lunch.OPTION2, 13000 - ) - ) - ) - .build(), - SchoolJpaEntity.builder() - .schoolName("문래중학교") - .area(Area.MOKDONG) - .address(new AddressJpaVO("07291", "서울특별시", "영등포구 문래로 195")) - .examDate(LocalDate.of(2025, 10, 19)) - .capacity(558L) - .deadlineTime(LocalDateTime.of(2025, 10, 10, 23, 59)) - .lunchMenu( - Set.of( - LunchMenu.of( - Lunch.OPTION6, 12000 - ), - LunchMenu.of( - Lunch.OPTION2, 13000 - ) - ) - ) - .build(), - SchoolJpaEntity.builder() - .schoolName("온곡중학교") - .area(Area.NOWON) - .address(new AddressJpaVO("01673", "서울특별시", "노원구 덕릉로 70길 99")) - .examDate(LocalDate.of(2025, 10, 19)) - .capacity(448L) - .deadlineTime(LocalDateTime.of(2025, 10, 10, 23, 59)) - .lunchMenu( - Set.of( - LunchMenu.of( - Lunch.OPTION1, 12000 - ), - LunchMenu.of( - Lunch.OPTION3, 13000 - ) - ) - ) - .build() - )); - schoolRepository.saveAll(schools); - - log.info("School 데이터 {}건 생성 완료.", schools.size()); - return schools; - } - - private List initializeApplications(List users, - List schools, - Random random) { - List createdAppSchools = new ArrayList<>(); - for (int i = 0; i < users.size(); i++) { - UserJpaEntity user = users.get(i); - ApplicationJpaEntity application = applicationRepository.save( - ApplicationJpaEntity.builder() - .userId(user.getId()) - .guardianPhoneNumber("010-9876-" + String.format("%04d", 1000 + i)) - .agreedToNotices(true) - .agreedToRefundPolicy(true) - .build() - ); - - Collections.shuffle(schools); - int schoolsToApply = random.nextInt(2) + 2; - - for (int j = 0; j < Math.min(schoolsToApply, schools.size()); j++) { - SchoolJpaEntity school = schools.get(j); - Set subjects = new HashSet<>(); - subjects.add(Subject.values()[random.nextInt(Subject.values().length)]); - if (random.nextBoolean()) { - subjects.add(Subject.values()[random.nextInt(Subject.values().length)]); - } - - ApplicationSchoolJpaEntity appSchool = applicationSchoolRepository.save( - ApplicationSchoolJpaEntity.builder() - .userId(user.getId()) - .applicationId(application.getId()) - .schoolId(school.getId()) - .schoolName(school.getSchoolName()) - .area(school.getArea()) - .address(school.getAddress()) - .examDate(LocalDate.of(2025, 10, 20 + i)) - .lunch(Lunch.values()[random.nextInt(Lunch.values().length)]) - .examinationNumber( - String.format("EXAM-%d-%d", user.getId(), school.getId())) - .subjects(subjects) - .build() - ); - createdAppSchools.add(appSchool); - } - } - log.info("Application 및 ApplicationSchool 데이터 생성 완료."); - return createdAppSchools; - } - - private void initializePayments(List applicationSchools) { - List payments = new ArrayList<>(); - int successCount = applicationSchools.size() / 2; - - for (int i = 0; i < applicationSchools.size(); i++) { - ApplicationSchoolJpaEntity appSchool = applicationSchools.get(i); - String orderId = "order-" + UUID.randomUUID().toString().substring(0, 8); - - if (i < successCount) { - PaymentAmountVO paymentAmount = PaymentAmountVO.of( - 12000, - 10909, - 1091, - 12000, - 0 - ); - - PaymentJpaEntity payment = PaymentJpaEntity.of( - appSchool.getId(), - "pkey-" + UUID.randomUUID().toString().substring(0, 12), - orderId, - PaymentStatus.DONE, - paymentAmount, - PaymentMethod.CARD - ); - payments.add(payment); - } else { - PaymentJpaEntity payment = PaymentJpaEntity.ofFailure( - appSchool.getId(), - orderId, - PaymentStatus.ABORTED, - 12000 - ); - payments.add(payment); - } - } - paymentRepository.saveAll(payments); - log.info("Payment 데이터 {}건 생성 완료 (성공: {}, 실패: {}).", payments.size(), successCount, - payments.size() - successCount); - } - - private void initializeBoardItems(List users, Random random) { - for (int i = 1; i <= 10; i++) { - UserJpaEntity author = users.get(random.nextInt(users.size())); - noticeJpaRepository.save(NoticeJpaEntity.builder() - .title("공지사항 제목 " + i) - .content("이것은 " + i + "번째 공지사항의 내용입니다.") - .userId(author.getId()) - .author(author.getName()) - .build()); - } - - List inquiries = new ArrayList<>(); - for (int i = 1; i <= 10; i++) { - UserJpaEntity author = users.get(random.nextInt(users.size())); - inquiries.add(inquiryJpaRepository.save( - InquiryJpaEntity.builder() - .title("문의 제목 " + i) - .content("안녕하세요. " + i + "번째 문의 내용입니다.") - .userId(author.getId()) - .author(author.getName()) - .build()) - ); - } - - for (int i = 1; i <= 10; i++) { - InquiryJpaEntity inquiryToAnswer = inquiries.get(random.nextInt(inquiries.size())); - - UserJpaEntity answerer = users.get(random.nextInt(users.size())); - inquiryAnswerJpaRepository.save(InquiryAnswerJpaEntity.builder() - .title("Re: " + inquiryToAnswer.getTitle()) - .content("문의하신 내용에 대한 답변입니다. " + i + "번째 답변입니다.") - .inquiryId(inquiryToAnswer.getId()) - .userId(answerer.getId()) - .build()); - inquiryToAnswer.updateStatusToComplete(); - inquiryJpaRepository.save(inquiryToAnswer); - } - - for (int i = 1; i <= 10; i++) { - LocalDate startDate = LocalDate.now().plusDays(i * 2); - LocalDate endDate = startDate.plusDays(random.nextInt(7) + 3); - eventRepository.save(EventJpaEntity.builder() - .title("이벤트 제목 " + i) - .duration(new DurationJpaVO(startDate, endDate)) - .eventLink("https://example.com/event/" + i) - .build()); - } - log.info("Board(Notice, Inquiry, Event) 데이터 생성 완료."); - } -} +//package life.mosu.mosuserver.global.initializer; +// +//import jakarta.annotation.PostConstruct; +//import java.time.LocalDate; +//import java.time.LocalDateTime; +//import java.util.ArrayList; +//import java.util.Collections; +//import java.util.HashSet; +//import java.util.List; +//import java.util.Random; +//import java.util.Set; +//import java.util.UUID; +//import life.mosu.mosuserver.domain.application.ApplicationJpaEntity; +//import life.mosu.mosuserver.domain.application.ApplicationJpaRepository; +//import life.mosu.mosuserver.domain.application.Lunch; +//import life.mosu.mosuserver.domain.application.Subject; +//import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; +//import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaRepository; +//import life.mosu.mosuserver.domain.event.DurationJpaVO; +//import life.mosu.mosuserver.domain.event.EventJpaEntity; +//import life.mosu.mosuserver.domain.event.EventJpaRepository; +//import life.mosu.mosuserver.domain.inquiry.InquiryJpaEntity; +//import life.mosu.mosuserver.domain.inquiry.InquiryJpaRepository; +//import life.mosu.mosuserver.domain.inquiryAnswer.InquiryAnswerJpaEntity; +//import life.mosu.mosuserver.domain.inquiryAnswer.InquiryAnswerJpaRepository; +//import life.mosu.mosuserver.domain.notice.NoticeJpaEntity; +//import life.mosu.mosuserver.domain.notice.NoticeJpaRepository; +//import life.mosu.mosuserver.domain.payment.PaymentAmountVO; +//import life.mosu.mosuserver.domain.payment.PaymentJpaEntity; +//import life.mosu.mosuserver.domain.payment.PaymentMethod; +//import life.mosu.mosuserver.domain.payment.PaymentRepository; +//import life.mosu.mosuserver.domain.payment.PaymentStatus; +//import life.mosu.mosuserver.domain.profile.Education; +//import life.mosu.mosuserver.domain.profile.Gender; +//import life.mosu.mosuserver.domain.profile.Grade; +//import life.mosu.mosuserver.domain.profile.ProfileJpaEntity; +//import life.mosu.mosuserver.domain.profile.ProfileJpaRepository; +//import life.mosu.mosuserver.domain.profile.SchoolInfoJpaVO; +//import life.mosu.mosuserver.domain.exam.AddressJpaVO; +//import life.mosu.mosuserver.domain.exam.Area; +//import life.mosu.mosuserver.domain.school.LunchMenu; +//import life.mosu.mosuserver.domain.school.SchoolJpaEntity; +//import life.mosu.mosuserver.domain.school.SchoolJpaRepository; +//import life.mosu.mosuserver.domain.user.UserJpaEntity; +//import life.mosu.mosuserver.domain.user.UserJpaRepository; +//import life.mosu.mosuserver.domain.user.UserRole; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.security.crypto.password.PasswordEncoder; +//import org.springframework.stereotype.Component; +// +//@Slf4j +//@Component +//@RequiredArgsConstructor +//public class DatabaseInitializer { +// +// private final UserJpaRepository userRepository; +// private final ProfileJpaRepository profileRepository; +// private final SchoolJpaRepository schoolRepository; +// private final ApplicationJpaRepository applicationRepository; +// private final ApplicationSchoolJpaRepository applicationSchoolRepository; +// private final InquiryJpaRepository inquiryJpaRepository; +// private final NoticeJpaRepository noticeJpaRepository; +// private final InquiryAnswerJpaRepository inquiryAnswerJpaRepository; +// private final EventJpaRepository eventRepository; +// private final PaymentRepository paymentRepository; +// private final PasswordEncoder passwordEncoder; +// +// @PostConstruct +// public void init() { +// if (userRepository.count() > 0 || schoolRepository.count() > 0) { +// log.info("이미 더미 데이터가 존재하여 전체 초기화를 건너뜁니다."); +// return; +// } +// +// log.info("전체 더미 데이터 초기화를 시작합니다..."); +// Random random = new Random(); +// +// List createdUsers = initializeUsersAndProfiles(random); +// List createdSchools = initializeSchools(); +// List createdAppSchools = initializeApplications(createdUsers, +// createdSchools, random); +// initializePayments(createdAppSchools); +// initializeBoardItems(createdUsers, random); +// +// log.info("모든 더미 데이터 초기화가 완료되었습니다."); +// } +// +// private List initializeUsersAndProfiles(Random random) { +// List createdUsers = new ArrayList<>(); +// for (int i = 1; i <= 10; i++) { +// UserJpaEntity user = UserJpaEntity.builder() +// .loginId("user" + i) +// .password(passwordEncoder.encode("password" + i + "!")) +// .gender((i % 2 == 0) ? Gender.MALE : Gender.FEMALE) +// .name((i % 2 == 0) ? "김철수" + i : "이영희" + i) +// .birth(LocalDate.of(1990 + (i % 5), (i % 12) + 1, (i % 28) + 1)) +// .agreedToTermsOfService(true) +// .agreedToPrivacyPolicy(true) +// .agreedToMarketing(random.nextBoolean()) +// .userRole((i == 1) ? UserRole.ROLE_ADMIN : UserRole.ROLE_USER) +// .build(); +// userRepository.save(user); +// createdUsers.add(user); +// +// ProfileJpaEntity profile = ProfileJpaEntity.builder() +// .userId(user.getId()) +// .userName(user.getName()) +// .gender(user.getGender()) +// .birth(user.getBirth()) +// .phoneNumber("010-4870-5466") +// .email("user" + i + "@example.com") +// .education(Education.values()[random.nextInt(Education.values().length)]) +// .schoolInfo(new SchoolInfoJpaVO("모수대학교" + (i % 3 + 1), "123-23", "서울시 모수구 모수동")) +// .grade(Grade.values()[random.nextInt(Grade.values().length)]) +// .build(); +// +// profile.registerRecommenderPhoneNumber((i % 3 == 0) ? "010-1234-5678" : null); +// profileRepository.save(profile); +// } +// log.info("User 및 Profile 데이터 {}건 생성 완료.", createdUsers.size()); +// return createdUsers; +// } +// +// private List initializeSchools() { +// List schools = new ArrayList<>(List.of( +// SchoolJpaEntity.builder() +// .schoolName("대치중학교") +// .area(Area.DAECHI) +// .address(new AddressJpaVO("06234", "서울특별시", "강남구 대치동 987")) +// .examDate(LocalDate.of(2025, 10, 19)) +// .capacity(532L) +// .deadlineTime(LocalDateTime.of(2025, 10, 10, 23, 59)) +// .lunchMenu( +// Set.of( +// LunchMenu.of( +// Lunch.OPTION1, 12000 +// ), +// LunchMenu.of( +// Lunch.OPTION2, 13000 +// ) +// ) +// ) +// .build(), +// SchoolJpaEntity.builder() +// .schoolName("목운중학교") +// .area(Area.MOKDONG) +// .address(new AddressJpaVO("07995", "서울특별시", "양천구 목동서로 369")) +// .examDate(LocalDate.of(2025, 10, 26)) +// .capacity(896L) +// .deadlineTime(LocalDateTime.of(2025, 10, 17, 23, 59)) +// .lunchMenu( +// Set.of( +// LunchMenu.of( +// Lunch.OPTION1, 12000 +// ), +// LunchMenu.of( +// Lunch.OPTION2, 13000 +// ) +// ) +// ) +// .build(), +// SchoolJpaEntity.builder() +// .schoolName("신서중학교") +// .area(Area.MOKDONG) +// .address(new AddressJpaVO("08018", "서울특별시", "양천구 신정로 250")) +// .examDate(LocalDate.of(2025, 11, 2)) +// .capacity(896L) +// .deadlineTime(LocalDateTime.of(2025, 10, 24, 23, 59)) +// .lunchMenu( +// Set.of( +// LunchMenu.of( +// Lunch.OPTION4, 12000 +// ), +// LunchMenu.of( +// Lunch.OPTION2, 13000 +// ) +// ) +// ) +// .build(), +// SchoolJpaEntity.builder() +// .schoolName("개원중학교") +// .area(Area.DAECHI) +// .address(new AddressJpaVO("06327", "서울특별시", "강남구 개포로 619")) +// .examDate(LocalDate.of(2025, 10, 26)) +// .capacity(840L) +// .deadlineTime(LocalDateTime.of(2025, 10, 17, 23, 59)) +// .lunchMenu( +// Set.of( +// LunchMenu.of( +// Lunch.OPTION4, 12000 +// ), +// LunchMenu.of( +// Lunch.OPTION2, 13000 +// ) +// ) +// ) +// .build(), +// SchoolJpaEntity.builder() +// .schoolName("문래중학교") +// .area(Area.MOKDONG) +// .address(new AddressJpaVO("07291", "서울특별시", "영등포구 문래로 195")) +// .examDate(LocalDate.of(2025, 10, 19)) +// .capacity(558L) +// .deadlineTime(LocalDateTime.of(2025, 10, 10, 23, 59)) +// .lunchMenu( +// Set.of( +// LunchMenu.of( +// Lunch.OPTION6, 12000 +// ), +// LunchMenu.of( +// Lunch.OPTION2, 13000 +// ) +// ) +// ) +// .build(), +// SchoolJpaEntity.builder() +// .schoolName("온곡중학교") +// .area(Area.NOWON) +// .address(new AddressJpaVO("01673", "서울특별시", "노원구 덕릉로 70길 99")) +// .examDate(LocalDate.of(2025, 10, 19)) +// .capacity(448L) +// .deadlineTime(LocalDateTime.of(2025, 10, 10, 23, 59)) +// .lunchMenu( +// Set.of( +// LunchMenu.of( +// Lunch.OPTION1, 12000 +// ), +// LunchMenu.of( +// Lunch.OPTION3, 13000 +// ) +// ) +// ) +// .build() +// )); +// schoolRepository.saveAll(schools); +// +// log.info("School 데이터 {}건 생성 완료.", schools.size()); +// return schools; +// } +// +// private List initializeApplications(List users, +// List schools, +// Random random) { +// List createdAppSchools = new ArrayList<>(); +// for (int i = 0; i < users.size(); i++) { +// UserJpaEntity user = users.get(i); +// ApplicationJpaEntity application = applicationRepository.save( +// ApplicationJpaEntity.builder() +// .userId(user.getId()) +// .guardianPhoneNumber("010-9876-" + String.format("%04d", 1000 + i)) +// .agreedToNotices(true) +// .agreedToRefundPolicy(true) +// .build() +// ); +// +// Collections.shuffle(schools); +// int schoolsToApply = random.nextInt(2) + 2; +// +// for (int j = 0; j < Math.min(schoolsToApply, schools.size()); j++) { +// SchoolJpaEntity school = schools.get(j); +// Set subjects = new HashSet<>(); +// subjects.add(Subject.values()[random.nextInt(Subject.values().length)]); +// if (random.nextBoolean()) { +// subjects.add(Subject.values()[random.nextInt(Subject.values().length)]); +// } +// +// ApplicationSchoolJpaEntity appSchool = applicationSchoolRepository.save( +// ApplicationSchoolJpaEntity.builder() +// .userId(user.getId()) +// .applicationId(application.getId()) +// .schoolId(school.getId()) +// .schoolName(school.getSchoolName()) +// .area(school.getArea()) +// .address(school.getAddress()) +// .examDate(LocalDate.of(2025, 10, 20 + i)) +// .lunch(Lunch.values()[random.nextInt(Lunch.values().length)]) +// .examinationNumber( +// String.format("EXAM-%d-%d", user.getId(), school.getId())) +// .subjects(subjects) +// .build() +// ); +// createdAppSchools.add(appSchool); +// } +// } +// log.info("Application 및 ApplicationSchool 데이터 생성 완료."); +// return createdAppSchools; +// } +// +// private void initializePayments(List applicationSchools) { +// List payments = new ArrayList<>(); +// int successCount = applicationSchools.size() / 2; +// +// for (int i = 0; i < applicationSchools.size(); i++) { +// ApplicationSchoolJpaEntity appSchool = applicationSchools.get(i); +// String orderId = "order-" + UUID.randomUUID().toString().substring(0, 8); +// +// if (i < successCount) { +// PaymentAmountVO paymentAmount = PaymentAmountVO.of( +// 12000, +// 10909, +// 1091, +// 12000, +// 0 +// ); +// +// PaymentJpaEntity payment = PaymentJpaEntity.of( +// appSchool.getId(), +// "pkey-" + UUID.randomUUID().toString().substring(0, 12), +// orderId, +// PaymentStatus.DONE, +// paymentAmount, +// PaymentMethod.CARD +// ); +// payments.add(payment); +// } else { +// PaymentJpaEntity payment = PaymentJpaEntity.ofFailure( +// appSchool.getId(), +// orderId, +// PaymentStatus.ABORTED, +// 12000 +// ); +// payments.add(payment); +// } +// } +// paymentRepository.saveAll(payments); +// log.info("Payment 데이터 {}건 생성 완료 (성공: {}, 실패: {}).", payments.size(), successCount, +// payments.size() - successCount); +// } +// +// private void initializeBoardItems(List users, Random random) { +// for (int i = 1; i <= 10; i++) { +// UserJpaEntity author = users.get(random.nextInt(users.size())); +// noticeJpaRepository.save(NoticeJpaEntity.builder() +// .title("공지사항 제목 " + i) +// .content("이것은 " + i + "번째 공지사항의 내용입니다.") +// .userId(author.getId()) +// .author(author.getName()) +// .build()); +// } +// +// List inquiries = new ArrayList<>(); +// for (int i = 1; i <= 10; i++) { +// UserJpaEntity author = users.get(random.nextInt(users.size())); +// inquiries.add(inquiryJpaRepository.save( +// InquiryJpaEntity.builder() +// .title("문의 제목 " + i) +// .content("안녕하세요. " + i + "번째 문의 내용입니다.") +// .userId(author.getId()) +// .author(author.getName()) +// .build()) +// ); +// } +// +// for (int i = 1; i <= 10; i++) { +// InquiryJpaEntity inquiryToAnswer = inquiries.get(random.nextInt(inquiries.size())); +// +// UserJpaEntity answerer = users.get(random.nextInt(users.size())); +// inquiryAnswerJpaRepository.save(InquiryAnswerJpaEntity.builder() +// .title("Re: " + inquiryToAnswer.getTitle()) +// .content("문의하신 내용에 대한 답변입니다. " + i + "번째 답변입니다.") +// .inquiryId(inquiryToAnswer.getId()) +// .userId(answerer.getId()) +// .build()); +// inquiryToAnswer.updateStatusToComplete(); +// inquiryJpaRepository.save(inquiryToAnswer); +// } +// +// for (int i = 1; i <= 10; i++) { +// LocalDate startDate = LocalDate.now().plusDays(i * 2); +// LocalDate endDate = startDate.plusDays(random.nextInt(7) + 3); +// eventRepository.save(EventJpaEntity.builder() +// .title("이벤트 제목 " + i) +// .duration(new DurationJpaVO(startDate, endDate)) +// .eventLink("https://example.com/event/" + i) +// .build()); +// } +// log.info("Board(Notice, Inquiry, Event) 데이터 생성 완료."); +// } +//} diff --git a/src/main/java/life/mosu/mosuserver/global/runner/ApplicationSchoolPreWarmRunner.java b/src/main/java/life/mosu/mosuserver/global/runner/ApplicationSchoolPreWarmRunner.java index 2f01d66f..a44035b9 100644 --- a/src/main/java/life/mosu/mosuserver/global/runner/ApplicationSchoolPreWarmRunner.java +++ b/src/main/java/life/mosu/mosuserver/global/runner/ApplicationSchoolPreWarmRunner.java @@ -1,6 +1,6 @@ package life.mosu.mosuserver.global.runner; -import life.mosu.mosuserver.application.school.SchoolQuotaCacheManager; +import life.mosu.mosuserver.application.exam.ExamQuotaCacheManager; import lombok.RequiredArgsConstructor; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; @@ -10,10 +10,10 @@ @Component public class ApplicationSchoolPreWarmRunner { - private final SchoolQuotaCacheManager schoolQuotaCacheManager; + private final ExamQuotaCacheManager examQuotaCacheManager; @EventListener(ApplicationReadyEvent.class) public void preloadSchoolData() { - schoolQuotaCacheManager.preloadSchoolData(); + examQuotaCacheManager.preloadSchoolData(); } } diff --git a/src/main/java/life/mosu/mosuserver/global/util/FileRequest.java b/src/main/java/life/mosu/mosuserver/global/util/FileRequest.java index 524144b5..760958fc 100644 --- a/src/main/java/life/mosu/mosuserver/global/util/FileRequest.java +++ b/src/main/java/life/mosu/mosuserver/global/util/FileRequest.java @@ -1,7 +1,7 @@ package life.mosu.mosuserver.global.util; import io.swagger.v3.oas.annotations.media.Schema; -import life.mosu.mosuserver.domain.application.AdmissionTicketImageJpaEntity; +import life.mosu.mosuserver.domain.application.ExamTicketImageJpaEntity; import life.mosu.mosuserver.domain.event.EventAttachmentJpaEntity; import life.mosu.mosuserver.domain.faq.FaqAttachmentJpaEntity; import life.mosu.mosuserver.domain.inquiry.InquiryAttachmentJpaEntity; @@ -21,7 +21,7 @@ public record FileRequest( ) { - public FaqAttachmentJpaEntity toFaqAttachmentEntity(String fileName, String s3Key, Long faqId) { + public FaqAttachmentJpaEntity toFaqAttachmentEntity(Long faqId) { return FaqAttachmentJpaEntity.builder() .fileName(fileName) .s3Key(s3Key) @@ -30,30 +30,27 @@ public FaqAttachmentJpaEntity toFaqAttachmentEntity(String fileName, String s3Ke .build(); } - public NoticeAttachmentJpaEntity toNoticeAttachmentEntity(String fileName, String s3Key, - Long noticeId) { - return NoticeAttachmentJpaEntity.builder() + public InquiryAttachmentJpaEntity toInquiryAttachmentEntity(Long inquiryId) { + return InquiryAttachmentJpaEntity.builder() .fileName(fileName) .s3Key(s3Key) - .visibility(Visibility.PUBLIC) - .noticeId(noticeId) + .visibility(Visibility.PRIVATE) + .inquiryId(inquiryId) .build(); } - public InquiryAttachmentJpaEntity toInquiryAttachmentEntity(String fileName, String s3Key, - Long inquiryId) { - return InquiryAttachmentJpaEntity.builder() + + public NoticeAttachmentJpaEntity toNoticeAttachmentEntity(Long noticeId) { + return NoticeAttachmentJpaEntity.builder() .fileName(fileName) .s3Key(s3Key) - .visibility(Visibility.PRIVATE) - .inquiryId(inquiryId) + .visibility(Visibility.PUBLIC) + .noticeId(noticeId) .build(); } - - public AdmissionTicketImageJpaEntity toAdmissionTicketImageEntity(String fileName, String s3Key, - Long applicationId) { - return AdmissionTicketImageJpaEntity.builder() + public ExamTicketImageJpaEntity toExamTicketImageEntity(Long applicationId) { + return ExamTicketImageJpaEntity.builder() .fileName(fileName) .s3Key(s3Key) .visibility(Visibility.PRIVATE) @@ -61,9 +58,7 @@ public AdmissionTicketImageJpaEntity toAdmissionTicketImageEntity(String fileNam .build(); } - public InquiryAnswerAttachmentEntity toInquiryAnswerAttachmentEntity(String fileName, - String s3Key, - Long inquiryAnswerId) { + public InquiryAnswerAttachmentEntity toInquiryAnswerAttachmentEntity(Long inquiryAnswerId) { return InquiryAnswerAttachmentEntity.builder() .fileName(fileName) .s3Key(s3Key) @@ -72,8 +67,7 @@ public InquiryAnswerAttachmentEntity toInquiryAnswerAttachmentEntity(String file .build(); } - public EventAttachmentJpaEntity toEventAttachmentEntity(String fileName, String s3Key, - Long eventId) { + public EventAttachmentJpaEntity toEventAttachmentEntity(Long eventId) { return EventAttachmentJpaEntity.builder() .fileName(fileName) .s3Key(s3Key) diff --git a/src/main/java/life/mosu/mosuserver/infra/respository/BulkRepository.java b/src/main/java/life/mosu/mosuserver/infra/respository/BulkRepository.java index d495c486..e389cfe5 100644 --- a/src/main/java/life/mosu/mosuserver/infra/respository/BulkRepository.java +++ b/src/main/java/life/mosu/mosuserver/infra/respository/BulkRepository.java @@ -1,25 +1,91 @@ -package life.mosu.mosuserver.infra.respository; - -import java.util.List; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; -import lombok.RequiredArgsConstructor; -import org.springframework.jdbc.core.JdbcTemplate; -import org.springframework.stereotype.Repository; -import org.springframework.transaction.annotation.Transactional; - -@Repository -@RequiredArgsConstructor -public class BulkRepository { - - private final JdbcTemplate jdbcTemplate; - - @Transactional - public void saveAllApplicationSchools(List applicationSchools) { - String sql = """ - INSERT INTO application_school - (user_id, application_id, school_id, school_name, area, exam_date) - VALUES (?, ?, ?, ?, ?, ?) - """; - } - -} +//package life.mosu.mosuserver.infra.respository; +// +//import java.sql.PreparedStatement; +//import java.sql.ResultSet; +//import java.sql.SQLException; +//import java.sql.Statement; +//import java.sql.Timestamp; +//import java.time.LocalDateTime; +//import java.util.ArrayList; +//import java.util.List; +//import life.mosu.mosuserver.domain.application.Subject; +//import life.mosu.mosuserver.domain.examapplication.ExamApplicationJpaEntity; +//import life.mosu.mosuserver.global.exception.CustomRuntimeException; +//import life.mosu.mosuserver.global.exception.ErrorCode; +//import lombok.RequiredArgsConstructor; +//import org.springframework.dao.DataAccessException; +//import org.springframework.jdbc.core.ConnectionCallback; +//import org.springframework.jdbc.core.JdbcTemplate; +//import org.springframework.stereotype.Repository; +//import org.springframework.transaction.annotation.Transactional; +// +//@Repository +//@RequiredArgsConstructor +//public class ExamApplicationBulkRepository { +// +// private static final String SQL_INSERT_EXAM_APPLICATION = """ +// INSERT INTO exam_application +// (created_at, updated_at, application_id, user_id, +// examId, examination_number) +// VALUES (?,?,?,?,?,?) +// """; +// private static final String SQL_INSERT_SUBJECT = """ +// INSERT INTO exam_subject (exam_application_id, subjects) VALUES (?, ?) +// """; +// private final JdbcTemplate jdbcTemplate; +// +// +// @Transactional +// public List saveAllExamApplicationsWithSubjects( +// List entities) { +// +// List generatedIds = jdbcTemplate.execute((ConnectionCallback>) con -> { +// try (PreparedStatement ps = con.prepareStatement( +// SQL_INSERT_EXAM_APPLICATION, Statement.RETURN_GENERATED_KEYS)) { +// for (ExamApplicationJpaEntity e : entities) { +// ps.setTimestamp(1, Timestamp.valueOf(LocalDateTime.now())); +// ps.setTimestamp(2, Timestamp.valueOf(LocalDateTime.now())); +// ps.setLong(3, e.getApplicationId()); +// ps.setLong(4, e.getUserId()); +// ps.setLong(5, e.examId()); +// ps.setString(6, e.getExaminationNumber()); +// ps.addBatch(); +// } +// +// ps.executeBatch(); +// +// List ids = new ArrayList<>(); +// try (ResultSet rs = ps.getGeneratedKeys()) { +// while (rs.next()) { +// ids.add(rs.getLong(1)); +// } +// } +// return ids; +// +// } catch (SQLException e) { +// throw new CustomRuntimeException(ErrorCode.EXAM_APPLICATION_MULTI_INSERT_ERROR); +// } +// }); +// +// List subjectParams = new ArrayList<>(); +// +// for (int i = 0; i < entities.size(); i++) { +// Long applicationSchoolId = generatedIds.get(i); +// +// for (Subject subj : entities.get(i).getSubjects()) { +// subjectParams.add(new Object[]{applicationSchoolId, String.valueOf(subj)}); +// } +// } +// +// try { +// if (!subjectParams.isEmpty()) { +// jdbcTemplate.batchUpdate(SQL_INSERT_SUBJECT, subjectParams); +// } +// } catch (DataAccessException e) { +// throw new CustomRuntimeException(ErrorCode.EXAM_SUBJECT_MULTI_INSERT_ERROR); +// } +// +// return generatedIds; +// } +// +//} diff --git a/src/main/java/life/mosu/mosuserver/infra/storage/application/S3Service.java b/src/main/java/life/mosu/mosuserver/infra/storage/application/S3Service.java index 12fa4fd9..415d7962 100644 --- a/src/main/java/life/mosu/mosuserver/infra/storage/application/S3Service.java +++ b/src/main/java/life/mosu/mosuserver/infra/storage/application/S3Service.java @@ -1,26 +1,32 @@ package life.mosu.mosuserver.infra.storage.application; +import java.io.IOException; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.Duration; import java.util.List; +import java.util.UUID; +import life.mosu.mosuserver.infra.property.S3Properties; import life.mosu.mosuserver.infra.storage.domain.File; import life.mosu.mosuserver.infra.storage.domain.Folder; import life.mosu.mosuserver.infra.storage.presentation.dto.FileUploadResponse; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import software.amazon.awssdk.core.sync.RequestBody; import software.amazon.awssdk.services.s3.S3Client; -import software.amazon.awssdk.services.s3.model.*; +import software.amazon.awssdk.services.s3.model.DeleteObjectRequest; +import software.amazon.awssdk.services.s3.model.GetObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectRequest; +import software.amazon.awssdk.services.s3.model.PutObjectTaggingRequest; +import software.amazon.awssdk.services.s3.model.S3Exception; +import software.amazon.awssdk.services.s3.model.Tag; +import software.amazon.awssdk.services.s3.model.Tagging; import software.amazon.awssdk.services.s3.presigner.S3Presigner; import software.amazon.awssdk.services.s3.presigner.model.GetObjectPresignRequest; -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.Duration; -import java.util.UUID; -import lombok.extern.slf4j.Slf4j; - @Slf4j @Service @RequiredArgsConstructor @@ -28,6 +34,7 @@ public class S3Service { private final S3Client s3Client; private final S3Presigner s3Presigner; + private final S3Properties s3Properties; @Value("${aws.s3.bucket-name}") private String bucketName; @@ -72,12 +79,12 @@ public void deleteFile(File file) { public void updateFileTagToActive(String key) { PutObjectTaggingRequest tagReq = PutObjectTaggingRequest.builder() - .bucket(bucketName) - .key(key) - .tagging(Tagging.builder() - .tagSet(List.of(Tag.builder().key("status").value("active").build())) - .build()) - .build(); + .bucket(bucketName) + .key(key) + .tagging(Tagging.builder() + .tagSet(List.of(Tag.builder().key("status").value("active").build())) + .build()) + .build(); s3Client.putObjectTagging(tagReq); } @@ -85,22 +92,24 @@ public void updateFileTagToActive(String key) { public String getUrl(File file) { return file.isPublic() ? getPublicUrl(file.getS3Key()) - : getPreSignedUrl(file.getS3Key(), Duration.ofMinutes(preSignedUrlExpirationMinutes)); + : getPreSignedUrl(file.getS3Key()); } public String getPublicUrl(String s3Key) { return String.format("https://%s.s3.amazonaws.com/%s", bucketName, s3Key); } - public String getPreSignedUrl(String s3Key, Duration expireDuration) { + public String getPreSignedUrl(String s3Key) { GetObjectRequest getObjectRequest = GetObjectRequest.builder() .bucket(bucketName) .key(s3Key) .build(); - + Duration expirationDuration = Duration.ofMinutes( + s3Properties.getPresignedUrlExpirationMinutes() + ); GetObjectPresignRequest preSignRequest = GetObjectPresignRequest.builder() .getObjectRequest(getObjectRequest) - .signatureDuration(expireDuration) + .signatureDuration(expirationDuration) .build(); return s3Presigner.presignGetObject(preSignRequest).url().toString(); diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java b/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java index e4761c6b..2c1f7f9e 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/AdminController.java @@ -1,111 +1,111 @@ -package life.mosu.mosuserver.presentation.admin; - -import jakarta.servlet.http.HttpServletResponse; -import jakarta.validation.Valid; -import java.io.IOException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.util.List; -import life.mosu.mosuserver.application.admin.AdminService; -import life.mosu.mosuserver.global.util.ApiResponseWrapper; -import life.mosu.mosuserver.global.util.excel.SimpleExcelFile; -import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto; -import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter; -import life.mosu.mosuserver.presentation.admin.dto.ApplicationListResponse; -import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse; -import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse; -import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto; -import life.mosu.mosuserver.presentation.admin.dto.StudentFilter; -import life.mosu.mosuserver.presentation.admin.dto.StudentListResponse; -import lombok.RequiredArgsConstructor; -import org.springframework.data.domain.Page; -import org.springframework.data.domain.Pageable; -import org.springframework.data.web.PageableDefault; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.ModelAttribute; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/admin") -public class AdminController implements AdminControllerDocs { - - private final AdminService adminService; - - @GetMapping("/students") -// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") - public ResponseEntity>> getStudents( - @Valid @ModelAttribute StudentFilter filter, - Pageable pageable - ) { - Page result = adminService.getStudents(filter, pageable); - return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "학생 목록 조회 성공", result)); - } - - @GetMapping("/excel/students") -// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") - public void downloadStudentInfo( - HttpServletResponse response) throws IOException { - String fileName = URLEncoder.encode("학생정보목록.xlsx", StandardCharsets.UTF_8) - .replaceAll("\\+", "%20"); - - response.setContentType( - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName); - - List data = adminService.getStudentExcelData(); - SimpleExcelFile excelFile = new SimpleExcelFile<>(data, - StudentExcelDto.class); - - excelFile.write(response.getOutputStream()); - } - - @GetMapping("/lunches") -// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") - public ResponseEntity>> getLunchCounts() { - List result = adminService.getLunchCounts(); - return ResponseEntity.ok( - ApiResponseWrapper.success(HttpStatus.OK, "학교별 도시락 신청 수 조회 성공", result)); - } - - @GetMapping("/applications") -// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") - public ResponseEntity>> getApplications( - @Valid @ModelAttribute ApplicationFilter filter, - Pageable pageable - ) { - Page result = adminService.getApplications(filter, pageable); - return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "신청 목록 조회 성공", result)); - } - - @GetMapping("/excel/applications") -// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") - public void downloadApplicationInfo(HttpServletResponse response) throws IOException { - String fileName = URLEncoder.encode("신청 목록.xlsx", StandardCharsets.UTF_8) - .replaceAll("\\+", "%20"); - - response.setContentType( - "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); - response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName); - - List data = adminService.getApplicationExcelData(); - SimpleExcelFile excelFile = new SimpleExcelFile<>(data, - ApplicationExcelDto.class); - - excelFile.write(response.getOutputStream()); - } - - @GetMapping("/refunds") -// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") - public ResponseEntity>> getRefundCounts( - @PageableDefault(size = 15) Pageable pageable - ) { - Page result = adminService.getRefunds(pageable); - return ResponseEntity.ok( - ApiResponseWrapper.success(HttpStatus.OK, "환불 신청 수 조회 성공", result)); - } - -} +//package life.mosu.mosuserver.presentation.admin; +// +//import jakarta.servlet.http.HttpServletResponse; +//import jakarta.validation.Valid; +//import java.io.IOException; +//import java.net.URLEncoder; +//import java.nio.charset.StandardCharsets; +//import java.util.List; +//import life.mosu.mosuserver.application.admin.AdminService; +//import life.mosu.mosuserver.global.util.ApiResponseWrapper; +//import life.mosu.mosuserver.global.util.excel.SimpleExcelFile; +//import life.mosu.mosuserver.presentation.admin.dto.ApplicationExcelDto; +//import life.mosu.mosuserver.presentation.admin.dto.ApplicationFilter; +//import life.mosu.mosuserver.presentation.admin.dto.AppliQcationListResponse; +//import life.mosu.mosuserver.presentation.admin.dto.RefundListResponse; +//import life.mosu.mosuserver.presentation.admin.dto.SchoolLunchResponse; +//import life.mosu.mosuserver.presentation.admin.dto.StudentExcelDto; +//import life.mosu.mosuserver.presentation.admin.dto.StudentFilter; +//import life.mosu.mosuserver.presentation.admin.dto.StudentListResponse; +//import lombok.RequiredArgsConstructor; +//import org.springframework.data.domain.Page; +//import org.springframework.data.domain.Pageable; +//import org.springframework.data.web.PageableDefault; +//import org.springframework.http.HttpStatus; +//import org.springframework.http.ResponseEntity; +//import org.springframework.web.bind.annotation.GetMapping; +//import org.springframework.web.bind.annotation.ModelAttribute; +//import org.springframework.web.bind.annotation.RequestMapping; +//import org.springframework.web.bind.annotation.RestController; +// +//@RestController +//@RequiredArgsConstructor +//@RequestMapping("/admin") +//public class AdminController implements AdminControllerDocs { +// +// private final AdminService adminService; +// +// @GetMapping("/students") +/// / @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") +// public ResponseEntity>> getStudents( +// @Valid @ModelAttribute StudentFilter filter, +// Pageable pageable +// ) { +// Page result = adminService.getStudents(filter, pageable); +// return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "학생 목록 조회 성공", result)); +// } +// +// @GetMapping("/excel/students") +//// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") +// public void downloadStudentInfo( +// HttpServletResponse response) throws IOException { +// String fileName = URLEncoder.encode("학생정보목록.xlsx", StandardCharsets.UTF_8) +// .replaceAll("\\+", "%20"); +// +// response.setContentType( +// "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); +// response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName); +// +// List data = adminService.getStudentExcelData(); +// SimpleExcelFile excelFile = new SimpleExcelFile<>(data, +// StudentExcelDto.class); +// +// excelFile.write(response.getOutputStream()); +// } +// +// @GetMapping("/lunches") +//// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") +// public ResponseEntity>> getLunchCounts() { +// List result = adminService.getLunchCounts(); +// return ResponseEntity.ok( +// ApiResponseWrapper.success(HttpStatus.OK, "학교별 도시락 신청 수 조회 성공", result)); +// } +// +// @GetMapping("/applications") +//// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") +// public ResponseEntity>> getApplications( +// @Valid @ModelAttribute ApplicationFilter filter, +// Pageable pageable +// ) { +// Page result = adminService.getApplications(filter, pageable); +// return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "신청 목록 조회 성공", result)); +// } +// +// @GetMapping("/excel/applications") +//// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") +// public void downloadApplicationInfo(HttpServletResponse response) throws IOException { +// String fileName = URLEncoder.encode("신청 목록.xlsx", StandardCharsets.UTF_8) +// .replaceAll("\\+", "%20"); +// +// response.setContentType( +// "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); +// response.setHeader("Content-Disposition", "attachment; filename*=UTF-8''" + fileName); +// +// List data = adminService.getApplicationExcelData(); +// SimpleExcelFile excelFile = new SimpleExcelFile<>(data, +// ApplicationExcelDto.class); +// +// excelFile.write(response.getOutputStream()); +// } +// +// @GetMapping("/refunds") +//// @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") +// public ResponseEntity>> getRefundCounts( +// @PageableDefault(size = 15) Pageable pageable +// ) { +// Page result = adminService.getRefunds(pageable); +// return ResponseEntity.ok( +// ApiResponseWrapper.success(HttpStatus.OK, "환불 신청 수 조회 성공", result)); +// } +// +//} diff --git a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationListResponse.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationListResponse.java index 96890175..9024d445 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationListResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ApplicationListResponse.java @@ -6,7 +6,6 @@ import java.util.Set; import life.mosu.mosuserver.domain.payment.PaymentMethod; import life.mosu.mosuserver.domain.payment.PaymentStatus; -import life.mosu.mosuserver.presentation.applicationschool.dto.AdmissionTicketResponse; @Schema(description = "관리자 신청 목록 응답 DTO") public record ApplicationListResponse( @@ -66,7 +65,7 @@ public record ApplicationListResponse( LocalDateTime applicationDate, @Schema(description = "수험표 응답 정보") - AdmissionTicketResponse admissionTicket + ExamTicketResponse examTicket ) { diff --git a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/AdmissionTicketResponse.java b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ExamTicketResponse.java similarity index 61% rename from src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/AdmissionTicketResponse.java rename to src/main/java/life/mosu/mosuserver/presentation/admin/dto/ExamTicketResponse.java index aee32c89..bb119d9c 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/AdmissionTicketResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/admin/dto/ExamTicketResponse.java @@ -1,14 +1,13 @@ -package life.mosu.mosuserver.presentation.applicationschool.dto; +package life.mosu.mosuserver.presentation.admin.dto; import io.swagger.v3.oas.annotations.media.Schema; import java.time.LocalDate; -import java.util.Set; - -@Schema(description = "수험표 응답 DTO") -public record AdmissionTicketResponse( +import java.util.List; +import life.mosu.mosuserver.domain.application.Subject; +public record ExamTicketResponse( @Schema(description = "수험표 이미지 URL", example = "https://s3.amazonaws.com/bucket/admission/2025-00001.jpg") - String admissionTicketImageUrl, + String examTicketImageUrl, @Schema(description = "응시자 이름", example = "홍길동") String userName, @@ -17,31 +16,30 @@ public record AdmissionTicketResponse( LocalDate birth, @Schema(description = "수험 번호", example = "20250001") - String examinationNumber, + String examNumber, @Schema(description = "응시 과목 목록", example = "[\"생명과학\", \"지구과학\"]") - Set subjects, + List subjects, @Schema(description = "응시 학교명", example = "대치중학교") String schoolName - ) { - public static AdmissionTicketResponse of( - String admissionTicketImageUrl, + public static ExamTicketResponse of( + String examTicketImageUrl, String userName, LocalDate birth, - String examinationNumber, - Set subjects, + String examNumber, + List subjects, String schoolName ) { - return new AdmissionTicketResponse( - admissionTicketImageUrl, + return new ExamTicketResponse( + examTicketImageUrl, userName, birth, - examinationNumber, + examNumber, subjects, schoolName ); } -} +} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/ApplicationController.java b/src/main/java/life/mosu/mosuserver/presentation/application/ApplicationController.java index 0f8eea8e..b4ff4fed 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/application/ApplicationController.java +++ b/src/main/java/life/mosu/mosuserver/presentation/application/ApplicationController.java @@ -27,7 +27,7 @@ public class ApplicationController implements ApplicationControllerDocs { //신청 @PostMapping -// @PreAuthorize("isAuthenticated() and hasRole('USER')") + // @PreAuthorize("isAuthenticated() and hasRole('USER')") public ResponseEntity> apply( @RequestParam Long userId, @Valid @RequestBody ApplicationRequest request 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 877cecb3..c2342896 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 @@ -2,9 +2,14 @@ import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotNull; +import java.util.List; import java.util.Set; +import java.util.stream.Collectors; import life.mosu.mosuserver.domain.application.ApplicationJpaEntity; +import life.mosu.mosuserver.domain.application.Subject; import life.mosu.mosuserver.global.annotation.PhoneNumberPattern; +import life.mosu.mosuserver.global.exception.CustomRuntimeException; +import life.mosu.mosuserver.global.exception.ErrorCode; import life.mosu.mosuserver.global.util.FileRequest; @Schema(description = "시험 신청 요청 DTO") @@ -15,24 +20,48 @@ public record ApplicationRequest( @Schema(description = "보호자 전화번호 (전화번호 형식은 010-XXXX-XXXX 이어야 합니다.)", example = "010-1234-5678") @PhoneNumberPattern - String guardianPhoneNumber, + String parentPhoneNumber, - @Schema(description = "신청 학교 목록", required = true) + @Schema(description = "시험 신청 Id 목록", required = true) @NotNull - Set schools, + List examApplication, @Schema(description = "약관 동의 정보", required = true) @NotNull - AgreementRequest agreementRequest + AgreementRequest agreement, + @Schema(description = "응시 과목 목록 (예: PHYSICS_1)", example = "[\"PHYSICS_1\", \"ETHICS_AND_IDEOLOGY\"]") + Set subjects ) { - public ApplicationJpaEntity toEntity(Long userId) { + public ApplicationJpaEntity toApplicationJpaEntity(Long userId) { return ApplicationJpaEntity.builder() .userId(userId) - .guardianPhoneNumber(guardianPhoneNumber) - .agreedToNotices(agreementRequest().agreedToNotices()) - .agreedToRefundPolicy(agreementRequest().agreedToRefundPolicy()) + .parentPhoneNumber(parentPhoneNumber) + .agreedToNotices(agreement().agreedToNotices()) + .agreedToRefundPolicy(agreement().agreedToRefundPolicy()) .build(); } + +// public List validatedDuplicatedSchoolLunchIds() { +// Set seenSchoolLunchIds = new HashSet<>(schoolIds.size()); +// +// for (Long schoolId : schoolIds) { +// if (!seenSchoolLunchIds.add(schoolId)) { +// throw new IllegalArgumentException("중복된 학교 ID 발견: " + schoolId); +// } +// } +// +// return schoolIds; +// } + + public Set validatedSubjects() { + try { + return subjects.stream() + .map(Subject::valueOf) + .collect(Collectors.toSet()); + } catch (IllegalArgumentException e) { + throw new CustomRuntimeException(ErrorCode.WRONG_SUBJECT_TYPE); + } + } } diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationResponse.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationResponse.java index a02d87a2..0d24fd97 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationResponse.java @@ -2,27 +2,22 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.List; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; @Schema(description = "신청 응답 DTO") public record ApplicationResponse( @Schema(description = "신청 ID", example = "1") Long applicationId, - - @Schema(description = "신청 학교 목록") - List schools - + + @Schema(description = "시험 목록") + List exams ) { - public static ApplicationResponse of( - Long applicationId, - List schoolEntities - ) { - List schoolResponses = schoolEntities.stream() - .map(ApplicationSchoolResponse::from) - .toList(); - - return new ApplicationResponse(applicationId, schoolResponses); + public static ApplicationResponse of(Long applicationId) { + return new ApplicationResponse(applicationId, null); + } + + public static ApplicationResponse of(Long applicationId, List exams) { + return new ApplicationResponse(applicationId, exams); } } diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolRequest.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolRequest.java deleted file mode 100644 index d34bec33..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolRequest.java +++ /dev/null @@ -1,80 +0,0 @@ -package life.mosu.mosuserver.presentation.application.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import java.time.LocalDate; -import java.util.Set; -import java.util.stream.Collectors; -import life.mosu.mosuserver.domain.application.Lunch; -import life.mosu.mosuserver.domain.application.Subject; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; -import life.mosu.mosuserver.domain.school.Area; -import life.mosu.mosuserver.domain.school.SchoolJpaEntity; -import life.mosu.mosuserver.global.exception.CustomRuntimeException; -import life.mosu.mosuserver.global.exception.ErrorCode; - -@Schema(description = "신청 학교 요청 DTO") -public record ApplicationSchoolRequest( - - @Schema(description = "학교 이름", example = "대치중학교") - @NotBlank(message = "학교 이름은 필수입니다.") - String schoolName, - - @Schema(description = "지역 코드 (예: DAECHI, NOWON, MOKDONG)", example = "DAECHI") - String area, - - @Schema(description = "시험 날짜", example = "2025-08-10") - @NotNull(message = "시험 날짜는 필수입니다.") - LocalDate examDate, - - @Schema(description = "도시락 여부 (NONE 또는 OPTION1)", example = "NONE") - @NotBlank(message = "점심 여부는 필수입니다.") - String lunch, - - @Schema(description = "응시 과목 목록 (예: PHYSICS_1)", example = "[\"PHYSICS_1\", \"ETHICS_AND_IDEOLOGY\"]") - Set subjects - -) { - - public ApplicationSchoolJpaEntity toEntity(Long userId, Long applicationId, - SchoolJpaEntity school) { - return ApplicationSchoolJpaEntity.builder() - .userId(userId) - .applicationId(applicationId) - .schoolId(school.getId()) - .schoolName(school.getSchoolName()) - .area(school.getArea()) - .address(school.getAddress()) - .examDate(examDate) - .lunch(validatedLunch(lunch)) - .subjects(validatedSubjects(subjects)) - .build(); - } - - private Set validatedSubjects(Set subjects) { - try { - return subjects.stream() - .map(Subject::valueOf) - .collect(Collectors.toSet()); - } catch (IllegalArgumentException e) { - throw new CustomRuntimeException(ErrorCode.WRONG_SUBJECT_TYPE); - } - } - - private Lunch validatedLunch(String lunch) { - try { - return Lunch.from(lunch); - } catch (IllegalArgumentException e) { - throw new CustomRuntimeException(ErrorCode.WRONG_LUNCH_TYPE); - } - } - - public Area validatedArea(String area) { - try { - return Area.from(area); - } catch (IllegalArgumentException e) { - throw new CustomRuntimeException(ErrorCode.WRONG_AREA_TYPE); - } - } -} diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolResponse.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolResponse.java deleted file mode 100644 index 5db7f835..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ApplicationSchoolResponse.java +++ /dev/null @@ -1,54 +0,0 @@ -package life.mosu.mosuserver.presentation.application.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDate; -import java.util.Set; -import java.util.stream.Collectors; -import life.mosu.mosuserver.domain.application.Subject; -import life.mosu.mosuserver.domain.applicationschool.ApplicationSchoolJpaEntity; - -@Schema(description = "신청 학교 응답 DTO") -public record ApplicationSchoolResponse( - - @Schema(description = "신청 학교 ID", example = "1") - Long applicationSchoolId, - - @Schema(description = "지역 이름", example = "대치") - String area, - - @Schema(description = "시험 날짜", example = "2025-08-10") - LocalDate examDate, - - @Schema(description = "학교 이름", example = "대치중학교") - String schoolName, - - @Schema(description = "도시락 신청 여부", example = "신청 안 함") - String lunch, - - @Schema(description = "수험 번호", example = "20250001") - String examinationNumber, - - @Schema(description = "신청 과목 목록", example = "[\"생활과 윤리\", \"정치와 법\"]") - Set subjects - -) { - - public static ApplicationSchoolResponse from(ApplicationSchoolJpaEntity applicationSchool) { - String areaName = applicationSchool.getArea().getAreaName(); - String lunchName = applicationSchool.getLunch().getLunchName(); - - Set subjectNames = applicationSchool.getSubjects().stream() - .map(Subject::getSubjectName) - .collect(Collectors.toSet()); - - return new ApplicationSchoolResponse( - applicationSchool.getId(), - areaName, - applicationSchool.getExamDate(), - applicationSchool.getSchoolName(), - lunchName, - applicationSchool.getExaminationNumber(), - subjectNames - ); - } -} diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ExamApplicationRequest.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ExamApplicationRequest.java new file mode 100644 index 00000000..826371f4 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ExamApplicationRequest.java @@ -0,0 +1,13 @@ +package life.mosu.mosuserver.presentation.application.dto; + +import life.mosu.mosuserver.application.examapplication.dto.RegisterExamApplicationEvent.TargetExam; + +public record ExamApplicationRequest( + Long examId, + Long lunchId +) { + + public TargetExam toTargetExam() { + return new TargetExam(examId, lunchId); + } +} diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/ExamWithSubjects.java b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ExamWithSubjects.java new file mode 100644 index 00000000..3d301512 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/application/dto/ExamWithSubjects.java @@ -0,0 +1,30 @@ +package life.mosu.mosuserver.presentation.application.dto; + +import io.swagger.v3.oas.annotations.media.Schema; +import java.time.LocalDate; +import java.util.Set; + +@Schema(description = "시험 정보와 과목 정보") +public record ExamWithSubjects( + @Schema(description = "시험 신청 ID", example = "1") + Long examId, + + @Schema(description = "지역", example = "대치") + String area, + + @Schema(description = "시험 날짜", example = "2025-08-10") + LocalDate examDate, + + @Schema(description = "학교명", example = "대치중학교") + String schoolName, + + @Schema(description = "점심 메뉴", example = "신청 안 함") + String lunch, + + @Schema(description = "수험 번호", example = "20250001") + String examinationNumber, + + @Schema(description = "응시 과목 목록", example = "[\"생활과 윤리\", \"정치와 법\"]") + Set subjects +) { +} diff --git a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/ApplicationSchoolController.java b/src/main/java/life/mosu/mosuserver/presentation/applicationschool/ApplicationSchoolController.java deleted file mode 100644 index e4ce51d9..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/ApplicationSchoolController.java +++ /dev/null @@ -1,73 +0,0 @@ -package life.mosu.mosuserver.presentation.applicationschool; - -import life.mosu.mosuserver.application.applicationschool.ApplicationSchoolService; -import life.mosu.mosuserver.global.annotation.UserId; -import life.mosu.mosuserver.global.util.ApiResponseWrapper; -import life.mosu.mosuserver.presentation.application.dto.ApplicationSchoolResponse; -import life.mosu.mosuserver.presentation.applicationschool.dto.AdmissionTicketResponse; -import life.mosu.mosuserver.presentation.applicationschool.dto.RefundRequest; -import life.mosu.mosuserver.presentation.applicationschool.dto.SubjectUpdateRequest; -import lombok.RequiredArgsConstructor; -import lombok.extern.slf4j.Slf4j; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -@Slf4j -@RestController -@RequestMapping("/applicationschool") -@RequiredArgsConstructor -public class ApplicationSchoolController implements ApplicationSchoolControllerDocs { - - private final ApplicationSchoolService applicationSchoolService; - - @DeleteMapping("/{applicationSchoolId}/cancel") - public ResponseEntity> cancelApplicationSchool( - @PathVariable("applicationSchoolId") Long applicationSchoolId, - @RequestParam Long userId, - @RequestBody RefundRequest request - ) { - applicationSchoolService.cancelApplicationSchool(applicationSchoolId, request); - return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "신청 학교 취소 및 환불 처리 완료")); - } - - @PutMapping("/{applicationSchoolId}/subjects") - public ResponseEntity> updateSubjects( - @PathVariable("applicationSchoolId") Long applicationSchoolId, - @RequestParam Long userId, - @RequestBody SubjectUpdateRequest request - ) { - ApplicationSchoolResponse response = applicationSchoolService.updateSubjects( - applicationSchoolId, request); - return ResponseEntity.ok( - ApiResponseWrapper.success(HttpStatus.OK, "신청 과목 수정 성공", response)); - } - - @GetMapping("/{applicationSchoolId}") - public ResponseEntity> getDetail( - @UserId Long userId, - @PathVariable("applicationSchoolId") Long applicationSchoolId - ) { - ApplicationSchoolResponse response = applicationSchoolService.getApplicationSchool( - applicationSchoolId); - return ResponseEntity.ok( - ApiResponseWrapper.success(HttpStatus.OK, "신청 상세 조회 성공", response)); - } - - @GetMapping("/{applicationSchoolId}/admissionticket") - public ResponseEntity> getAdmissionTicket( - @UserId Long userId, - @PathVariable("applicationSchoolId") Long applicationSchoolId - ) { - AdmissionTicketResponse response = applicationSchoolService.getAdmissionTicket(userId, - applicationSchoolId); - return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "수험표 조회 성공", response)); - } -} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/ApplicationSchoolControllerDocs.java b/src/main/java/life/mosu/mosuserver/presentation/applicationschool/ApplicationSchoolControllerDocs.java deleted file mode 100644 index 24fb8481..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/ApplicationSchoolControllerDocs.java +++ /dev/null @@ -1,80 +0,0 @@ -package life.mosu.mosuserver.presentation.applicationschool; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.enums.ParameterIn; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.Valid; -import life.mosu.mosuserver.global.util.ApiResponseWrapper; -import life.mosu.mosuserver.presentation.application.dto.ApplicationSchoolResponse; -import life.mosu.mosuserver.presentation.applicationschool.dto.AdmissionTicketResponse; -import life.mosu.mosuserver.presentation.applicationschool.dto.RefundRequest; -import life.mosu.mosuserver.presentation.applicationschool.dto.SubjectUpdateRequest; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; - -@Tag(name = "Application School API", description = "개별 신청 학교 관련 API 명세") -public interface ApplicationSchoolControllerDocs { - - @Operation(summary = "신청 학교 취소", description = "사용자가 신청한 학교를 취소 요청합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "취소 및 환불 처리 성공") - }) - ResponseEntity> cancelApplicationSchool( - @Parameter(name = "applicationSchoolId", description = "신청 학교 ID", in = ParameterIn.PATH) - @PathVariable Long applicationSchoolId, - - @Parameter(name = "userId", description = "사용자 ID", in = ParameterIn.QUERY) - @PathVariable Long userId, - - @Parameter(description = "환불 요청 정보", required = true) - @RequestBody @Valid RefundRequest request - ); - - @Operation(summary = "신청 과목 수정", description = "신청한 학교에 대해 과목 목록을 수정합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "과목 수정 성공", - content = @Content(schema = @Schema(implementation = ApplicationSchoolResponse.class))) - }) - ResponseEntity> updateSubjects( - @Parameter(name = "applicationSchoolId", description = "신청 학교 ID", in = ParameterIn.PATH) - @PathVariable Long applicationSchoolId, - - @Parameter(name = "userId", description = "사용자 ID", in = ParameterIn.QUERY) - @PathVariable Long userId, - - @Parameter(description = "수정할 과목 목록", required = true) - @RequestBody @Valid SubjectUpdateRequest request - ); - - @Operation(summary = "신청 상세 조회", description = "신청한 학교의 상세 정보를 조회합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "상세 조회 성공", - content = @Content(schema = @Schema(implementation = ApplicationSchoolResponse.class))) - }) - ResponseEntity> getDetail( - @Parameter(name = "userId", description = "사용자 ID", in = ParameterIn.QUERY) - @PathVariable Long userId, - - @Parameter(name = "applicationSchoolId", description = "신청 학교 ID", in = ParameterIn.PATH) - @PathVariable Long applicationSchoolId - ); - - @Operation(summary = "수험표 조회", description = "신청한 학교에 대한 수험표를 조회합니다.") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "수험표 조회 성공", - content = @Content(schema = @Schema(implementation = AdmissionTicketResponse.class))) - }) - ResponseEntity> getAdmissionTicket( - @Parameter(name = "userId", description = "사용자 ID", in = ParameterIn.QUERY) - @PathVariable Long userId, - - @Parameter(name = "applicationSchoolId", description = "신청 학교 ID", in = ParameterIn.PATH) - @PathVariable Long applicationSchoolId - ); -} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/RefundRequest.java b/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/RefundRequest.java deleted file mode 100644 index fc28bdb3..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/RefundRequest.java +++ /dev/null @@ -1,29 +0,0 @@ -package life.mosu.mosuserver.presentation.applicationschool.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.time.LocalDateTime; -import life.mosu.mosuserver.domain.refund.RefundJpaEntity; - -@Schema(description = "환불 요청 DTO") -public record RefundRequest( - - @Schema(description = "환불 사유", example = "개인 사정으로 인해 응시가 어려움") - String reason, - - @Schema(description = "환불 정책 동의 여부", example = "true") - Boolean refundAgreed, - - @Schema(description = "환불 동의 시각 (ISO 8601 형식)", example = "2025-07-10T15:30:00") - LocalDateTime agreedAt - -) { - - public RefundJpaEntity toEntity(Long applicationSchoolId) { - return RefundJpaEntity.builder() - .applicationSchoolId(applicationSchoolId) - .reason(reason) - .refundAgreed(refundAgreed) - .agreedAt(agreedAt) - .build(); - } -} diff --git a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/SubjectUpdateRequest.java b/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/SubjectUpdateRequest.java deleted file mode 100644 index c6be5eb8..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/applicationschool/dto/SubjectUpdateRequest.java +++ /dev/null @@ -1,19 +0,0 @@ -package life.mosu.mosuserver.presentation.applicationschool.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import java.util.Set; -import life.mosu.mosuserver.domain.application.Subject; - -@Schema(description = "과목 수정 요청 DTO") -public record SubjectUpdateRequest( - - @Schema( - description = "과목 목록 (Subject Enum 값들)", - example = "[\"LIFE_AND_ETHICS\", \"ETHICS_AND_IDEOLOGY\"]", - required = true - ) - Set subjects - -) { - -} diff --git a/src/main/java/life/mosu/mosuserver/presentation/common/AddressResponse.java b/src/main/java/life/mosu/mosuserver/presentation/common/AddressResponse.java index 13086583..bd81c559 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/common/AddressResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/common/AddressResponse.java @@ -1,6 +1,6 @@ package life.mosu.mosuserver.presentation.common; -import life.mosu.mosuserver.domain.school.AddressJpaVO; +import life.mosu.mosuserver.domain.exam.AddressJpaVO; public record AddressResponse( String zipcode, diff --git a/src/main/java/life/mosu/mosuserver/presentation/exam/ExamController.java b/src/main/java/life/mosu/mosuserver/presentation/exam/ExamController.java new file mode 100644 index 00000000..5aac2ca0 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/exam/ExamController.java @@ -0,0 +1,51 @@ +package life.mosu.mosuserver.presentation.exam; + +import java.util.List; +import life.mosu.mosuserver.application.exam.ExamService; +import life.mosu.mosuserver.domain.exam.Area; +import life.mosu.mosuserver.global.util.ApiResponseWrapper; +import life.mosu.mosuserver.presentation.exam.dto.ExamRequest; +import life.mosu.mosuserver.presentation.exam.dto.ExamResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/exams") +@RequiredArgsConstructor +public class ExamController { + + private final ExamService examService; + + @PostMapping + public ResponseEntity> register( + @RequestBody ExamRequest request + ) { + examService.register(request); + return ResponseEntity.ok( + ApiResponseWrapper.success(HttpStatus.CREATED, "시험 등록 성공")); + + } + + @GetMapping + public ResponseEntity>> getByArea( + @RequestParam String areaName + ) { + List response = examService.getByArea(areaName); + return ResponseEntity.ok( + ApiResponseWrapper.success(HttpStatus.OK, "지역별 시험 조회 성공", response)); + } + + @GetMapping("/areas") + public ResponseEntity>> getDistinctAreas() { + List response = examService.getDistinctAreas(); + return ResponseEntity.ok( + ApiResponseWrapper.success(HttpStatus.OK, "시험 지역 조회 성공", response)); + } +} diff --git a/src/main/java/life/mosu/mosuserver/presentation/application/dto/AddressRequest.java b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/AddressRequest.java similarity index 85% rename from src/main/java/life/mosu/mosuserver/presentation/application/dto/AddressRequest.java rename to src/main/java/life/mosu/mosuserver/presentation/exam/dto/AddressRequest.java index cd5304b7..899af20c 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/application/dto/AddressRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/AddressRequest.java @@ -1,7 +1,7 @@ -package life.mosu.mosuserver.presentation.application.dto; +package life.mosu.mosuserver.presentation.exam.dto; import io.swagger.v3.oas.annotations.media.Schema; -import life.mosu.mosuserver.domain.school.AddressJpaVO; +import life.mosu.mosuserver.domain.exam.AddressJpaVO; @Schema(description = "주소 요청 DTO") public record AddressRequest( diff --git a/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamLunchRequest.java b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamLunchRequest.java new file mode 100644 index 00000000..2462dcc5 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamLunchRequest.java @@ -0,0 +1,13 @@ +package life.mosu.mosuserver.presentation.exam.dto; + +import life.mosu.mosuserver.presentation.lunch.dto.CreateLunchEvent.LunchRequest; + +public record ExamLunchRequest( + String name, + Integer price +) { + + public LunchRequest toLunchRequest() { + return new LunchRequest(name, price); + } +} diff --git a/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamRequest.java b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamRequest.java new file mode 100644 index 00000000..8348e31d --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamRequest.java @@ -0,0 +1,51 @@ +package life.mosu.mosuserver.presentation.exam.dto; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import life.mosu.mosuserver.domain.exam.AddressJpaVO; +import life.mosu.mosuserver.domain.exam.Area; +import life.mosu.mosuserver.domain.exam.ExamJpaEntity; +import life.mosu.mosuserver.presentation.lunch.dto.CreateLunchEvent; +import life.mosu.mosuserver.presentation.lunch.dto.CreateLunchEvent.LunchRequest; + +public record ExamRequest( + String schoolName, + String areaName, + AddressRequest address, + LocalDate examDate, + Integer capacity, + LocalDateTime deadlineTime, + List lunch +) { + + public ExamJpaEntity toEntity() { + AddressJpaVO parsedAddress = address.toValueObject(); + return ExamJpaEntity.builder() + .schoolName(schoolName) + .area(parseArea(areaName)) + .address(parsedAddress) + .examDate(examDate) + .capacity(capacity) + .deadlineTime(deadlineTime) + .build(); + } + + public CreateLunchEvent toCreateLunchEvent(Long examId) { + List lunchRequest = lunch.stream() + .map(ExamLunchRequest::toLunchRequest) + .toList(); + + return CreateLunchEvent.of( + examId, lunchRequest + ); + } + + private Area parseArea(String areaName) { + try { + return Area.from(areaName); + } catch (Exception e) { + throw new RuntimeException(e); + } + } +} 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 new file mode 100644 index 00000000..ef12dc92 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/exam/dto/ExamResponse.java @@ -0,0 +1,38 @@ +package life.mosu.mosuserver.presentation.exam.dto; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.List; +import life.mosu.mosuserver.domain.exam.Area; +import life.mosu.mosuserver.domain.exam.ExamJpaEntity; +import life.mosu.mosuserver.presentation.common.AddressResponse; + +public record ExamResponse( + Long id, + String schoolName, + AddressResponse address, + Area area, + Integer capacity, + LocalDateTime deadlineTime, + LocalDate examDate +) { + + public static ExamResponse from(ExamJpaEntity exam) { + AddressResponse address = AddressResponse.from(exam.getAddress()); + return new ExamResponse( + exam.getId(), + exam.getSchoolName(), + address, + exam.getArea(), + exam.getCapacity(), + exam.getDeadlineTime(), + exam.getExamDate() + ); + } + + public static List fromList(List foundExams) { + return foundExams.stream() + .map(ExamResponse::from) + .toList(); + } +} diff --git a/src/main/java/life/mosu/mosuserver/presentation/examapplication/ExamApplicationController.java b/src/main/java/life/mosu/mosuserver/presentation/examapplication/ExamApplicationController.java new file mode 100644 index 00000000..08553545 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/examapplication/ExamApplicationController.java @@ -0,0 +1,45 @@ +package life.mosu.mosuserver.presentation.examapplication; + +import life.mosu.mosuserver.application.examapplication.ExamApplicationService; +import life.mosu.mosuserver.global.util.ApiResponseWrapper; +import life.mosu.mosuserver.presentation.admin.dto.ExamTicketResponse; +import life.mosu.mosuserver.presentation.examapplication.dto.UpdateSubjectRequest; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/exam-application") +@RequiredArgsConstructor +public class ExamApplicationController { + + private final ExamApplicationService examApplicationService; + + @PutMapping("{examApplicationId}/subjects") + public ResponseEntity> updateSubjects( + @PathVariable("examApplicationId") Long examApplicationId, + @RequestBody UpdateSubjectRequest request + ) { + examApplicationService.updateSubjects(examApplicationId, request); + return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "과목 수정을 완료했습니다.")); + } + + @GetMapping("{examApplicationId}/exam-ticket") + public ResponseEntity> getExamTicket( + @RequestParam Long userId, + @PathVariable("examApplicationId") Long examApplicationId + ) { + ExamTicketResponse response = examApplicationService.getExamTicket(userId, + examApplicationId); + return ResponseEntity.ok( + ApiResponseWrapper.success(HttpStatus.OK, "수험표 발급을 완료했습니다.", response)); + } + +} diff --git a/src/main/java/life/mosu/mosuserver/presentation/examapplication/dto/UpdateSubjectRequest.java b/src/main/java/life/mosu/mosuserver/presentation/examapplication/dto/UpdateSubjectRequest.java new file mode 100644 index 00000000..e8fd07f7 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/examapplication/dto/UpdateSubjectRequest.java @@ -0,0 +1,27 @@ +package life.mosu.mosuserver.presentation.examapplication.dto; + +import java.util.List; +import life.mosu.mosuserver.domain.application.Subject; +import life.mosu.mosuserver.domain.examapplication.ExamSubjectJpaEntity; + +public record UpdateSubjectRequest( + List subjects +) { + + private List validatedSubject() { + return subjects.stream() + .map(Subject::valueOf) + .toList(); + } + + public List toEntityList(Long examApplicationId) { + return validatedSubject().stream() + .map(subject -> toEntity(examApplicationId, subject)) + .toList(); + } + + private ExamSubjectJpaEntity toEntity(Long examApplicationId, Subject subject) { + return ExamSubjectJpaEntity.create(examApplicationId, subject); + } + +} diff --git a/src/main/java/life/mosu/mosuserver/presentation/lunch/LunchController.java b/src/main/java/life/mosu/mosuserver/presentation/lunch/LunchController.java new file mode 100644 index 00000000..86d54b0d --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/lunch/LunchController.java @@ -0,0 +1,32 @@ +package life.mosu.mosuserver.presentation.lunch; + +import java.util.List; +import life.mosu.mosuserver.application.lunch.LunchService; +import life.mosu.mosuserver.global.util.ApiResponseWrapper; +import life.mosu.mosuserver.presentation.lunch.dto.LunchResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequestMapping("/lunch") +@RequiredArgsConstructor +public class LunchController { + + private final LunchService lunchService; + + @GetMapping() + public ResponseEntity>> getByExamId( + @RequestParam Long examId) { + List lunchResponses = lunchService.getByExamId(examId); + return ResponseEntity.ok( + ApiResponseWrapper.success(HttpStatus.OK, "시험에 호환되는 도시락 전체 조회 성공", lunchResponses)); + } + /** + * TODO: ExamId 의 Lunch 추가 및 제거, 수정 + */ +} diff --git a/src/main/java/life/mosu/mosuserver/presentation/lunch/LunchEventListener.java b/src/main/java/life/mosu/mosuserver/presentation/lunch/LunchEventListener.java new file mode 100644 index 00000000..6726bc56 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/lunch/LunchEventListener.java @@ -0,0 +1,22 @@ +package life.mosu.mosuserver.presentation.lunch; + +import life.mosu.mosuserver.application.lunch.LunchEventService; +import life.mosu.mosuserver.global.annotation.ReactiveEventListener; +import life.mosu.mosuserver.presentation.lunch.dto.CreateLunchEvent; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Component; + +@Component +@Slf4j +@RequiredArgsConstructor +public class LunchEventListener { + + private final LunchEventService lunchEventService; + + @ReactiveEventListener + public void createLunch(CreateLunchEvent event) { + lunchEventService.create(event); + } +} + diff --git a/src/main/java/life/mosu/mosuserver/presentation/lunch/dto/CreateLunchEvent.java b/src/main/java/life/mosu/mosuserver/presentation/lunch/dto/CreateLunchEvent.java new file mode 100644 index 00000000..ef244802 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/lunch/dto/CreateLunchEvent.java @@ -0,0 +1,35 @@ +package life.mosu.mosuserver.presentation.lunch.dto; + +import java.util.List; +import life.mosu.mosuserver.domain.lunch.LunchJpaEntity; + +public record CreateLunchEvent( + Long examId, + List lunchRequests +) { + + public static CreateLunchEvent of(Long examId, List lunchRequest) { + return new CreateLunchEvent(examId, lunchRequest); + } + + public List toEntities() { + return lunchRequests.stream() + .map(request -> request.toEntity(examId)) // examId 전달 + .toList(); + } + + public record LunchRequest( + String name, + Integer price + ) { + + public LunchJpaEntity toEntity(Long examId) { + return LunchJpaEntity.builder() + .name(name) + .price(price) + .examId(examId) + .build(); + } + } +} + diff --git a/src/main/java/life/mosu/mosuserver/presentation/lunch/dto/LunchResponse.java b/src/main/java/life/mosu/mosuserver/presentation/lunch/dto/LunchResponse.java new file mode 100644 index 00000000..606fdc16 --- /dev/null +++ b/src/main/java/life/mosu/mosuserver/presentation/lunch/dto/LunchResponse.java @@ -0,0 +1,21 @@ +package life.mosu.mosuserver.presentation.lunch.dto; + +import java.util.List; +import life.mosu.mosuserver.domain.lunch.LunchJpaEntity; + +public record LunchResponse( + Long id, + String name, + Integer price +) { + + public static List fromList(List lunches) { + return lunches.stream() + .map(LunchResponse::from) + .toList(); + } + + public static LunchResponse from(LunchJpaEntity lunch) { + return new LunchResponse(lunch.getId(), lunch.getName(), lunch.getPrice()); + } +} diff --git a/src/main/java/life/mosu/mosuserver/presentation/payment/dto/PreparePaymentRequest.java b/src/main/java/life/mosu/mosuserver/presentation/payment/dto/PreparePaymentRequest.java index 1a956cca..e3ca9aac 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/payment/dto/PreparePaymentRequest.java +++ b/src/main/java/life/mosu/mosuserver/presentation/payment/dto/PreparePaymentRequest.java @@ -1,26 +1,7 @@ package life.mosu.mosuserver.presentation.payment.dto; -import jakarta.validation.Valid; -import jakarta.validation.constraints.NotEmpty; -import jakarta.validation.constraints.NotNull; -import java.util.List; - public record PreparePaymentRequest( - @NotEmpty(message = "결제 항목은 비어 있을 수 없습니다.") - List<@Valid Item> items + Long applicationId ) { - public int getSize() { - return items.size(); - } - - public record Item( - @NotNull(message = "applicationSchoolId는 필수입니다.") - Long applicationSchoolId, - - @NotNull(message = "name은 필수입니다.") - String name - ) { - - } } diff --git a/src/main/java/life/mosu/mosuserver/presentation/school/SchoolController.java b/src/main/java/life/mosu/mosuserver/presentation/school/SchoolController.java deleted file mode 100644 index a21e02e0..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/school/SchoolController.java +++ /dev/null @@ -1,63 +0,0 @@ -package life.mosu.mosuserver.presentation.school; - -import life.mosu.mosuserver.application.school.SchoolService; -import life.mosu.mosuserver.global.util.ApiResponseWrapper; -import life.mosu.mosuserver.presentation.school.dto.AvailableSchoolResponse; -import life.mosu.mosuserver.presentation.school.dto.SchoolEditRequest; -import life.mosu.mosuserver.presentation.school.dto.SchoolRegistrationRequest; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.DeleteMapping; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; - -@RestController -@RequestMapping("/school") -@RequiredArgsConstructor -public class SchoolController implements SchoolControllerDocs { - - private final SchoolService schoolService; - - @PostMapping - public ResponseEntity> create( - @RequestBody SchoolRegistrationRequest request - ) { - schoolService.registerSchool(request); - return ResponseEntity.status(HttpStatus.CREATED) - .body(ApiResponseWrapper.success(HttpStatus.CREATED, "학교 등록 성공")); - } - - @GetMapping - public ResponseEntity> getSchools() { - - AvailableSchoolResponse schools = schoolService.getAvailableSchools(); - - return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "학교 조회 성공", schools)); - } - - @PutMapping("/{id}") - // @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") - public ResponseEntity> update( - @RequestBody SchoolEditRequest request, - @PathVariable Long id - ) { - schoolService.update(id, request); - return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "학교 수정 성공")); - } - - @DeleteMapping("/{id}") - // @PreAuthorize("isAuthenticated() and hasRole('ADMIN')") - public ResponseEntity> delete( - @PathVariable Long id - ) { - schoolService.deleteSchool(id); - return ResponseEntity.ok(ApiResponseWrapper.success(HttpStatus.OK, "학교 삭제 성공")); - } - -} diff --git a/src/main/java/life/mosu/mosuserver/presentation/school/SchoolControllerDocs.java b/src/main/java/life/mosu/mosuserver/presentation/school/SchoolControllerDocs.java deleted file mode 100644 index 395e9965..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/school/SchoolControllerDocs.java +++ /dev/null @@ -1,32 +0,0 @@ -package life.mosu.mosuserver.presentation.school; - -import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.media.ArraySchema; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; -import io.swagger.v3.oas.annotations.tags.Tag; -import life.mosu.mosuserver.global.util.ApiResponseWrapper; -import life.mosu.mosuserver.presentation.school.dto.AvailableSchoolResponse; -import life.mosu.mosuserver.presentation.school.dto.SchoolRegistrationRequest; -import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.RequestBody; - -@Tag(name = "School", description = "학교 정보 관련 API") -public interface SchoolControllerDocs { - - @Operation(summary = "[관리자] 학교 정보 등록", description = "새로운 학교 정보를 시스템에 등록합니다. 인가 추가 예정") - @ApiResponses(value = { - @ApiResponse(responseCode = "201", description = "학교 등록 성공") - }) - ResponseEntity> create(@RequestBody SchoolRegistrationRequest request); - - @Operation(summary = "[사용자] 전체 학교 목록 조회", description = "시스템에 등록된 모든 학교 목록을 조회합니다. 인가 추가 예정") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "학교 목록 조회 성공", - content = @Content(array = @ArraySchema(schema = @Schema(implementation = AvailableSchoolResponse.class)))), - @ApiResponse(responseCode = "500", description = "서버 내부 오류") - }) - ResponseEntity> getSchools(); -} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/school/dto/AreaDetail.java b/src/main/java/life/mosu/mosuserver/presentation/school/dto/AreaDetail.java index 7a8ed9e2..e69de29b 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/school/dto/AreaDetail.java +++ b/src/main/java/life/mosu/mosuserver/presentation/school/dto/AreaDetail.java @@ -1,34 +0,0 @@ -package life.mosu.mosuserver.presentation.school.dto; - -import java.util.List; -import java.util.stream.Collectors; -import life.mosu.mosuserver.domain.school.SchoolJpaEntity; - -public record AreaDetail( - String schoolName, - List examInfo, - List lunchInfo -) { - - public static AreaDetail from(SchoolJpaEntity entity, int currentQuota) { - ExamInfo examInfo = new ExamInfo( - entity.getExamDate(), - currentQuota, - entity.getCapacity().intValue(), - entity.getId() - ); - - List lunchInfo = entity.getLunchMenus().stream() - .map(menu -> new LunchInfo( - menu.getLunch().getLunchName(), - menu.getLunchPrice() - )) - .collect(Collectors.toList()); - - return new AreaDetail( - entity.getSchoolName(), - List.of(examInfo), - lunchInfo - ); - } -} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/school/dto/AreaMeta.java b/src/main/java/life/mosu/mosuserver/presentation/school/dto/AreaMeta.java index f4adce8d..e69de29b 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/school/dto/AreaMeta.java +++ b/src/main/java/life/mosu/mosuserver/presentation/school/dto/AreaMeta.java @@ -1,23 +0,0 @@ -package life.mosu.mosuserver.presentation.school.dto; - -import java.util.List; -import java.util.Map; -import life.mosu.mosuserver.domain.school.SchoolJpaEntity; - -public record AreaMeta( - String areaName, - List areaDetails -) { - - public static AreaMeta from(String areaName, List schools, - Map quotaMap) { - List areaDetails = schools.stream() - .map(school -> { - int currentQuota = quotaMap.getOrDefault(school.getSchoolName(), 0); - return AreaDetail.from(school, currentQuota); - }) - .toList(); - - return new AreaMeta(areaName, areaDetails); - } -} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/school/dto/AvailableSchoolResponse.java b/src/main/java/life/mosu/mosuserver/presentation/school/dto/AvailableSchoolResponse.java index 7384ada2..e69de29b 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/school/dto/AvailableSchoolResponse.java +++ b/src/main/java/life/mosu/mosuserver/presentation/school/dto/AvailableSchoolResponse.java @@ -1,29 +0,0 @@ -package life.mosu.mosuserver.presentation.school.dto; - -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import life.mosu.mosuserver.domain.school.SchoolJpaEntity; - -public record AvailableSchoolResponse( - List areaNames, - List areaMeta -) { - - public static AvailableSchoolResponse from(List entities, - Map quotaMap) { - Map> schoolsByArea = entities.stream() - .collect(Collectors.groupingBy(entity -> entity.getArea().getAreaName())); - - List areaNames = schoolsByArea.keySet() - .stream() - .sorted() - .toList(); - - List areaMeta = areaNames.stream() - .map(areaName -> AreaMeta.from(areaName, schoolsByArea.get(areaName), quotaMap)) - .toList(); - - return new AvailableSchoolResponse(areaNames, areaMeta); - } -} diff --git a/src/main/java/life/mosu/mosuserver/presentation/school/dto/ExamInfo.java b/src/main/java/life/mosu/mosuserver/presentation/school/dto/ExamInfo.java deleted file mode 100644 index 09d5dfff..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/school/dto/ExamInfo.java +++ /dev/null @@ -1,12 +0,0 @@ -package life.mosu.mosuserver.presentation.school.dto; - -import java.time.LocalDate; - -public record ExamInfo( - LocalDate examDate, - Integer currentQuota, - Integer maxQuota, - Long schoolId -) { - -} diff --git a/src/main/java/life/mosu/mosuserver/presentation/school/dto/LunchInfo.java b/src/main/java/life/mosu/mosuserver/presentation/school/dto/LunchInfo.java index fe058721..e69de29b 100644 --- a/src/main/java/life/mosu/mosuserver/presentation/school/dto/LunchInfo.java +++ b/src/main/java/life/mosu/mosuserver/presentation/school/dto/LunchInfo.java @@ -1,8 +0,0 @@ -package life.mosu.mosuserver.presentation.school.dto; - -public record LunchInfo( - String lunchName, - Integer lunchPrice -) { - -} diff --git a/src/main/java/life/mosu/mosuserver/presentation/school/dto/LunchMenuRequest.java b/src/main/java/life/mosu/mosuserver/presentation/school/dto/LunchMenuRequest.java deleted file mode 100644 index 76b8790e..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/school/dto/LunchMenuRequest.java +++ /dev/null @@ -1,17 +0,0 @@ -package life.mosu.mosuserver.presentation.school.dto; - -import life.mosu.mosuserver.domain.application.Lunch; -import life.mosu.mosuserver.domain.school.LunchMenu; - -public record LunchMenuRequest( - String lunch, - Integer lunchPrice -) { - - public LunchMenu toEntity() { - return LunchMenu.of( - Lunch.from(lunch), - lunchPrice - ); - } -} diff --git a/src/main/java/life/mosu/mosuserver/presentation/school/dto/SchoolEditRequest.java b/src/main/java/life/mosu/mosuserver/presentation/school/dto/SchoolEditRequest.java deleted file mode 100644 index a8cc9030..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/school/dto/SchoolEditRequest.java +++ /dev/null @@ -1,44 +0,0 @@ -package life.mosu.mosuserver.presentation.school.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Set; -import life.mosu.mosuserver.domain.school.LunchMenu; -import life.mosu.mosuserver.presentation.application.dto.AddressRequest; - -@Schema(description = "학교 수정 요청 DTO") -public record SchoolEditRequest( - @Schema(description = "지역 (예: DAECHI, MOKDONG, NOWON)", example = "DAECHI") - @NotNull(message = "지역은 필수 입니다.") - String area, - - @Schema(description = "학교 주소 정보") - @NotNull(message = "주소 정보는 필수 입니다.") - AddressRequest schoolAddress, - - @Schema(description = "날짜 (YYYY-MM-DD)", example = "2025-11-20") - @NotNull(message = "시험 날짜는 필수입니다.") - LocalDate examDate, - - @Schema(description = "학교명", example = "모수고등학교") - @NotBlank(message = "학교 이름은 필수 입니다.") - String schoolName, - - @Schema(description = "점심 메뉴 목록") - Set lunchMenus, - - @NotNull - @Schema(description = "신청 마감 일시", example = "2025-05-30T10:00:00") - LocalDateTime deadlineTime, - - @Schema(description = "수용 인원", example = "300") - @NotNull(message = "수용 인원은 필수입니다.") - @Positive(message = "수용 인원은 양수여야 합니다.") - Long capacity -) { - -} diff --git a/src/main/java/life/mosu/mosuserver/presentation/school/dto/SchoolRegistrationRequest.java b/src/main/java/life/mosu/mosuserver/presentation/school/dto/SchoolRegistrationRequest.java deleted file mode 100644 index 4ba3d484..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/school/dto/SchoolRegistrationRequest.java +++ /dev/null @@ -1,64 +0,0 @@ -package life.mosu.mosuserver.presentation.school.dto; - -import io.swagger.v3.oas.annotations.media.Schema; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Positive; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Set; -import java.util.stream.Collectors; -import life.mosu.mosuserver.domain.school.Area; -import life.mosu.mosuserver.domain.school.SchoolJpaEntity; -import life.mosu.mosuserver.presentation.application.dto.AddressRequest; - - -@Schema(description = "학교 생성/등록 요청 DTO") -public record SchoolRegistrationRequest( - - @Schema(description = "지역 (예: DAECHI, MOKDONG, NOWON)", example = "DAECHI") - @NotNull(message = "지역은 필수 입니다.") - String area, - - @Schema(description = "학교 주소 정보") - @NotNull(message = "주소 정보는 필수 입니다.") - AddressRequest schoolAddress, - - @Schema(description = "날짜 (YYYY-MM-DD)", example = "2025-11-20") - @NotNull(message = "시험 날짜는 필수입니다.") - LocalDate examDate, - - @Schema(description = "학교명", example = "모수고등학교") - @NotBlank(message = "학교 이름은 필수 입니다.") - String schoolName, - - Set lunchMenus, - - @NotNull - @Schema(description = "신청 마감 일시", example = "2025-05-30T10:00:00") - LocalDateTime deadlineTime, - - @Schema(description = "수용 인원", example = "300") - @NotNull(message = "수용 인원은 필수입니다.") - @Positive(message = "수용 인원은 양수여야 합니다.") - Long capacity -) { - - public SchoolJpaEntity toEntity() { - return SchoolJpaEntity.builder() - .address(schoolAddress.toValueObject()) - .area(Area.from(area)) - .examDate(examDate) - .schoolName(schoolName) - .lunchMenu( - lunchMenus.stream() - .map(LunchMenuRequest::toEntity) - .collect(Collectors.toSet()) - ) - .deadlineTime(deadlineTime) - .capacity(capacity) - .build(); - } - - -} \ No newline at end of file diff --git a/src/main/java/life/mosu/mosuserver/presentation/school/dto/SchoolResponse.java b/src/main/java/life/mosu/mosuserver/presentation/school/dto/SchoolResponse.java deleted file mode 100644 index 6dc4a61e..00000000 --- a/src/main/java/life/mosu/mosuserver/presentation/school/dto/SchoolResponse.java +++ /dev/null @@ -1,30 +0,0 @@ -package life.mosu.mosuserver.presentation.school.dto; - -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.util.Set; -import life.mosu.mosuserver.domain.school.LunchMenu; -import life.mosu.mosuserver.domain.school.SchoolJpaEntity; - -public record SchoolResponse( - Long id, - String schoolName, - String area, - Set lunchMenus, - LocalDateTime deadlineTime, - LocalDate examDate, - Long capacity -) { - - public static SchoolResponse from(SchoolJpaEntity school) { - return new SchoolResponse( - school.getId(), - school.getSchoolName(), - school.getArea().getAreaName(), - school.getLunchMenus(), - school.getDeadlineTime(), - school.getExamDate(), - school.getCapacity() - ); - } -}